From 9457397af45f9d69423c5168d23fddd998a5f0fd Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Fri, 19 Sep 2025 13:58:08 -0700 Subject: [PATCH 01/10] Sync to origin/release/692 (#2012) Another week, another release! ## Analysis/Autocomplete - Improve recursive type lookups by using scoped tracking of processed types. - Enforce recursion limits in subtyping on type packs, not just on types. - Simplify type checking for intersections between tables and discriminants containing read-only table properties. - Allow fields provided by `__index` to satisfy subtyping relationships. - Improve the ability for the type checker to do proper generic substitution in `for ... in` loops. - Fix a fragment autocomplete bug that caused fragments to be selected incorrectly in `for ... in` loops. - Fix a crash caused by `typeof` containing an unterminated function definition: `typeof(function())`. - Fix a flagging issue that may have been causing stack overflows in the previous release. ## Runtime - Support constant folding for interpolated strings. - Fix a bug caused by the empty string being the result of constant folding. - Add helper macros in Bytecode.h to help access data in Luau auxiliary instruction bits. - Add support for branchless `==`/`~=` comparisons in CodeGen (in certain cases). --- Co-authored-by: Andy Friesen Co-authored-by: Annie Tang Co-authored-by: Ariel Weiss Co-authored-by: Hunter Goldstein Co-authored-by: Ilya Rezvov Co-authored-by: Sora Kanosue Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/AstUtils.h | 14 +- Analysis/include/Luau/ConstraintSolver.h | 2 +- Analysis/include/Luau/Subtyping.h | 38 +- Analysis/include/Luau/TypeChecker2.h | 6 + Analysis/include/Luau/TypePack.h | 2 +- Analysis/include/Luau/TypePath.h | 46 +- Analysis/include/Luau/VisitType.h | 2 +- Analysis/src/AstUtils.cpp | 26 +- Analysis/src/Constraint.cpp | 4 +- Analysis/src/ConstraintGenerator.cpp | 113 +++- Analysis/src/ConstraintSolver.cpp | 38 +- Analysis/src/FragmentAutocomplete.cpp | 9 +- Analysis/src/OverloadResolution.cpp | 37 +- Analysis/src/Simplify.cpp | 8 +- Analysis/src/Subtyping.cpp | 704 +++++++++++++-------- Analysis/src/TypeChecker2.cpp | 258 ++++++-- Analysis/src/TypePack.cpp | 6 +- Analysis/src/TypePath.cpp | 16 +- CodeGen/include/Luau/IrData.h | 16 +- CodeGen/include/Luau/IrUtils.h | 2 + CodeGen/include/Luau/IrVisitUseDef.h | 10 + CodeGen/src/BytecodeAnalysis.cpp | 8 +- CodeGen/src/CodeGen.cpp | 2 +- CodeGen/src/CodeGenA64.cpp | 12 +- CodeGen/src/CodeGenX64.cpp | 2 +- CodeGen/src/IrBuilder.cpp | 67 ++ CodeGen/src/IrDump.cpp | 4 + CodeGen/src/IrLoweringA64.cpp | 207 +++++- CodeGen/src/IrLoweringX64.cpp | 125 +++- CodeGen/src/IrTranslation.cpp | 124 +++- CodeGen/src/IrTranslation.h | 4 + CodeGen/src/IrUtils.cpp | 56 ++ CodeGen/src/IrValueLocationTracking.cpp | 5 +- CodeGen/src/IrValueLocationTracking.h | 2 +- CodeGen/src/OptimizeConstProp.cpp | 111 ++++ Common/include/Luau/Bytecode.h | 18 + Compiler/src/Compiler.cpp | 107 +++- Compiler/src/ConstantFolding.cpp | 83 ++- VM/src/lobject.h | 2 +- VM/src/lstate.h | 2 +- VM/src/lvmexecute.cpp | 18 +- tests/Compiler.test.cpp | 89 ++- tests/ConstraintSolver.test.cpp | 28 + tests/FragmentAutocomplete.test.cpp | 27 + tests/IrLowering.test.cpp | 379 ++++++++++- tests/Normalize.test.cpp | 4 +- tests/Simplify.test.cpp | 46 +- tests/Subtyping.test.cpp | 4 +- tests/TypeFunction.test.cpp | 5 +- tests/TypeInfer.definitions.test.cpp | 10 +- tests/TypeInfer.functions.test.cpp | 39 +- tests/TypeInfer.generics.test.cpp | 50 +- tests/TypeInfer.intersectionTypes.test.cpp | 6 +- tests/TypeInfer.modules.test.cpp | 4 +- tests/TypeInfer.oop.test.cpp | 127 ++++ tests/TypeInfer.operators.test.cpp | 29 +- tests/TypeInfer.provisional.test.cpp | 31 + tests/TypeInfer.refinements.test.cpp | 39 +- tests/TypeInfer.singletons.test.cpp | 5 +- tests/TypeInfer.tables.test.cpp | 13 + tests/TypeInfer.test.cpp | 50 +- tests/TypeInfer.unionTypes.test.cpp | 4 +- tests/TypeInfer.unknownnever.test.cpp | 13 +- tests/TypePath.test.cpp | 6 +- tests/Unifier2.test.cpp | 11 +- tests/conformance/native.luau | 44 ++ tests/conformance/stringinterp.luau | 2 + 67 files changed, 2753 insertions(+), 628 deletions(-) diff --git a/Analysis/include/Luau/AstUtils.h b/Analysis/include/Luau/AstUtils.h index 01ed15a3..0be03925 100644 --- a/Analysis/include/Luau/AstUtils.h +++ b/Analysis/include/Luau/AstUtils.h @@ -13,7 +13,15 @@ namespace Luau // uniquely held references. Append these types to 'uniqueTypes'. void findUniqueTypes(NotNull> uniqueTypes, AstExpr* expr, NotNull> astTypes); -void findUniqueTypes(NotNull> uniqueTypes, AstArray exprs, NotNull> astTypes); -void findUniqueTypes(NotNull> uniqueTypes, const std::vector& exprs, NotNull> astTypes); +void findUniqueTypes( + NotNull> uniqueTypes, + AstArray exprs, + NotNull> astTypes +); +void findUniqueTypes( + NotNull> uniqueTypes, + const std::vector& exprs, + NotNull> astTypes +); -} +} // namespace Luau diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index f5000830..1b01de4f 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -300,7 +300,7 @@ struct ConstraintSolver ValueContext context, bool inConditional, bool suppressSimplification, - DenseHashSet& seen + Set& seen ); /** diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index ca2e2334..f56f5fe6 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -59,8 +59,8 @@ inline const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::k * isn't strictly lexical (see test nested_generic_argument_type_packs), and that nested generic packs can shadow existing ones * such as with the type ((A...) -> (), (A...) -> ()) -> (), which should result in three independent bindings for A... . * To handle this, we maintain a stack of frames, each of which contains a mapping for the generic packs bound in that scope, as well as a pointers to - * its parent and child scopes. Inside each frame, we map the generic pack to an optional type pack, which is nullopt if we have not yet encountered a mapping - * for that generic pack in this scope. + * its parent and child scopes. Inside each frame, we map the generic pack to an optional type pack, which is nullopt if we have not yet encountered a + * mapping for that generic pack in this scope. */ struct MappedGenericEnvironment @@ -282,7 +282,6 @@ struct Subtyping SubtypingResult cache(SubtypingEnvironment& env, SubtypingResult res, TypeId subTy, TypeId superTy); SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull scope); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull scope); template SubtypingResult isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope); @@ -321,7 +320,13 @@ struct Subtyping NotNull scope ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull scope); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, bool forceCovariantTest, NotNull scope); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const TableType* subTable, + const TableType* superTable, + bool forceCovariantTest, + NotNull scope + ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull scope); SubtypingResult isCovariantWith( @@ -420,6 +425,31 @@ struct Subtyping NotNull scope ); + // Pack subtyping + SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull scope); + std::optional isSubTailCovariantWith( + SubtypingEnvironment& env, + std::vector& outputResults, + TypePackId subTp, + TypePackId subTail, + TypePackId superTp, + size_t superHeadStartIndex, + const std::vector& superHead, + std::optional superTail, + NotNull scope + ); + std::optional isCovariantWithSuperTail( + SubtypingEnvironment& env, + std::vector& outputResults, + TypePackId subTp, + size_t subHeadStartIndex, + const std::vector& subHead, + std::optional subTail, + TypePackId superTp, + TypePackId superTail, + NotNull scope + ); + bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp); // Clip with LuauSubtypingGenericPacksDoesntUseVariance bool bindGeneric_DEPRECATED(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) const; diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index 663f262d..96cc18d5 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -200,6 +200,12 @@ struct TypeChecker2 bool testIsSubtype(TypeId subTy, TypeId superTy, Location location); bool testIsSubtype(TypePackId subTy, TypePackId superTy, Location location); + + void maybeReportSubtypingError(TypeId subTy, TypeId superTy, const Location& location); + // Tests whether subTy is a subtype of superTy in the context of a function iterator for a for-in statement. + // Includes some extra logic to help locate errors to the values and variables of the for-in statement. + void testIsSubtypeForInStat(TypeId iterFunc, TypeId prospectiveFunc, const AstStatForIn& forInStat); + void reportError(TypeError e); void reportErrors(ErrorVec errors); PropertyTypes lookupProp( diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index 5dcced9a..12c1e6cb 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -267,7 +267,7 @@ LUAU_NOINLINE Unifiable::Bound* emplaceTypePack(TypeP TypePackId sliceTypePack( size_t sliceIndex, TypePackId toBeSliced, - std::vector& head, + const std::vector& head, std::optional tail, NotNull builtinTypes, NotNull arena diff --git a/Analysis/include/Luau/TypePath.h b/Analysis/include/Luau/TypePath.h index 1dc959d4..1ffd613f 100644 --- a/Analysis/include/Luau/TypePath.h +++ b/Analysis/include/Luau/TypePath.h @@ -242,18 +242,13 @@ std::string toString(const TypePath::Path& path, bool prefixDot = false); std::string toStringHuman(const TypePath::Path& path); // To keep my head straight when clipping: -// LuauReturnMappedGenericPacksFromSubtyping2 expects mappedGenericPacks AND arena +// LuauReturnMappedGenericPacksFromSubtyping3 expects mappedGenericPacks AND arena // LuauSubtypingGenericPacksDoesntUseVariance expects just arena. this is the final state -// TODO: clip below two along with `LuauReturnMappedGenericPacksFromSubtyping2` +// TODO: clip below two along with `LuauReturnMappedGenericPacksFromSubtyping3` std::optional traverse_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes); std::optional traverse_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes); -std::optional traverse( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull arena -); +std::optional traverse(TypePackId root, const Path& path, NotNull builtinTypes, NotNull arena); // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance std::optional traverse_DEPRECATED( TypePackId root, @@ -262,12 +257,7 @@ std::optional traverse_DEPRECATED( NotNull> mappedGenericPacks, NotNull arena ); -std::optional traverse( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull arena -); +std::optional traverse(TypeId root, const Path& path, NotNull builtinTypes, NotNull arena); // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance std::optional traverse_DEPRECATED( TypeId root, @@ -306,12 +296,7 @@ std::optional traverseForType_DEPRECATED( /// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @param arena a TypeArena, required if path has a PackSlice component /// @returns the TypeId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForType( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull arena -); +std::optional traverseForType(TypeId root, const Path& path, NotNull builtinTypes, NotNull arena); /// Traverses a path from a type pack to its end point, which must be a type. /// @param root the entry point of the traversal @@ -341,12 +326,7 @@ std::optional traverseForType_DEPRECATED( /// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @param arena a TypeArena, required if path has a PackSlice component /// @returns the TypeId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForType( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull arena -); +std::optional traverseForType(TypePackId root, const Path& path, NotNull builtinTypes, NotNull arena); /// Traverses a path from a type to its end point, which must be a type pack. This overload will fail if the path contains a PackSlice component or a /// mapped generic pack. @@ -377,12 +357,7 @@ std::optional traverseForPack_DEPRECATED( /// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @param arena a TypeArena, required if path has a PackSlice component /// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForPack( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull arena -); +std::optional traverseForPack(TypeId root, const Path& path, NotNull builtinTypes, NotNull arena); /// Traverses a path from a type pack to its end point, which must be a type pack. /// @param root the entry point of the traversal @@ -412,12 +387,7 @@ std::optional traverseForPack_DEPRECATED( /// @param builtinTypes the built-in types in use (used to acquire the string metatable) /// @param arena a TypeArena, required if path has a PackSlice component /// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForPack( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull arena -); +std::optional traverseForPack(TypePackId root, const Path& path, NotNull builtinTypes, NotNull arena); /// Traverses a path of Index and PackSlices to compute the index of the type the path points to /// Returns std::nullopt if the path isn't n PackSlice components followed by an Index component diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index c3534479..033847a0 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -336,7 +336,7 @@ struct GenericTypeVisitor { if (auto ty = prop.readTy) traverse(*ty); - + // In the case that the readType and the writeType are // the same pointer, just traverse once. Traversing each // property twice would have pretty significant diff --git a/Analysis/src/AstUtils.cpp b/Analysis/src/AstUtils.cpp index c72eab19..65488202 100644 --- a/Analysis/src/AstUtils.cpp +++ b/Analysis/src/AstUtils.cpp @@ -14,7 +14,8 @@ struct AstExprTableFinder : AstVisitor explicit AstExprTableFinder(NotNull> result, NotNull> astTypes) : result(result) , astTypes(astTypes) - {} + { + } bool visit(AstExpr* expr) override { @@ -38,8 +39,13 @@ void findUniqueTypes(NotNull> uniqueTypes, AstExpr* expr, N expr->visit(&finder); } -template -void findUniqueTypes(NotNull> uniqueTypes, Iter startIt, Iter endIt, NotNull> astTypes) +template +void findUniqueTypes( + NotNull> uniqueTypes, + Iter startIt, + Iter endIt, + NotNull> astTypes +) { while (startIt != endIt) { @@ -51,14 +57,22 @@ void findUniqueTypes(NotNull> uniqueTypes, Iter startIt, It } -void findUniqueTypes(NotNull> uniqueTypes, AstArray exprs, NotNull> astTypes) +void findUniqueTypes( + NotNull> uniqueTypes, + AstArray exprs, + NotNull> astTypes +) { findUniqueTypes(uniqueTypes, exprs.begin(), exprs.end(), astTypes); } -void findUniqueTypes(NotNull> uniqueTypes, const std::vector& exprs, NotNull> astTypes) +void findUniqueTypes( + NotNull> uniqueTypes, + const std::vector& exprs, + NotNull> astTypes +) { findUniqueTypes(uniqueTypes, exprs.begin(), exprs.end(), astTypes); } -} +} // namespace Luau diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index c95dcea2..4376d18b 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -5,6 +5,7 @@ #include "Luau/VisitType.h" LUAU_FASTFLAGVARIABLE(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) namespace Luau { @@ -87,7 +88,8 @@ TypeIds Constraint::getMaybeMutatedFreeTypes() const if (auto ec = get(*this)) { rci.traverse(ec->resultType); - // `EqualityConstraints` should not mutate `assignmentType`. + if (FFlag::LuauNoOrderingTypeFunctions) + rci.traverse(ec->assignmentType); } else if (auto sc = get(*this)) { diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 51e09834..57cb31d4 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -52,6 +52,8 @@ LUAU_FASTFLAGVARIABLE(LuauInitializeDefaultGenericParamsAtProgramPoint) LUAU_FASTFLAGVARIABLE(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAGVARIABLE(LuauCacheDuplicateHasPropConstraints) LUAU_FASTFLAGVARIABLE(LuauNoMoreComparisonTypeFunctions) +LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) +LUAU_FASTFLAGVARIABLE(LuauDontReferenceScopePtrFromHashTable) namespace Luau { @@ -1796,7 +1798,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* scope->typeAliasLocations[alias->name.value] = alias->location; scope->typeAliasNameLocations[alias->name.value] = alias->nameLocation; - ScopePtr* defnScope = astTypeAliasDefiningScopes.find(alias); + ScopePtr* defnScopePtr = astTypeAliasDefiningScopes.find(alias); std::unordered_map* typeBindings; if (alias->exported) @@ -1807,13 +1809,20 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* // These will be undefined if the alias was a duplicate definition, in which // case we just skip over it. auto bindingIt = typeBindings->find(alias->name.value); - if (bindingIt == typeBindings->end() || defnScope == nullptr) + if (bindingIt == typeBindings->end() || defnScopePtr == nullptr) return ControlFlow::None; + ScopePtr defnScope = *defnScopePtr; + if (FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint) - resolveGenericDefaultParameters(*defnScope, alias, bindingIt->second); + resolveGenericDefaultParameters(FFlag::LuauDontReferenceScopePtrFromHashTable ? defnScope : *defnScopePtr, alias, bindingIt->second); - TypeId ty = resolveType(*defnScope, alias->type, /* inTypeArguments */ false, /* replaceErrorWithFresh */ false); + TypeId ty = resolveType( + FFlag::LuauDontReferenceScopePtrFromHashTable ? defnScope : *defnScopePtr, + alias->type, + /* inTypeArguments */ false, + /* replaceErrorWithFresh */ false + ); TypeId aliasTy = bindingIt->second.type; LUAU_ASSERT(get(aliasTy)); @@ -1826,12 +1835,28 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* emplaceType(asMutable(aliasTy), ty); std::vector typeParams; - for (auto tyParam : createGenerics(*defnScope, alias->generics, /* useCache */ true, /* addTypes */ false)) - typeParams.push_back(tyParam.second.ty); + if (FFlag::LuauDontReferenceScopePtrFromHashTable) + { + for (const auto& tyParam : createGenerics(defnScope, alias->generics, /* useCache */ true, /* addTypes */ false)) + typeParams.push_back(tyParam.second.ty); + } + else + { + for (auto tyParam : createGenerics(*defnScopePtr, alias->generics, /* useCache */ true, /* addTypes */ false)) + typeParams.push_back(tyParam.second.ty); + } std::vector typePackParams; - for (auto tpParam : createGenericPacks(*defnScope, alias->genericPacks, /* useCache */ true, /* addTypes */ false)) - typePackParams.push_back(tpParam.second.tp); + if (FFlag::LuauDontReferenceScopePtrFromHashTable) + { + for (const auto& tpParam : createGenericPacks(defnScope, alias->genericPacks, /* useCache */ true, /* addTypes */ false)) + typePackParams.push_back(tpParam.second.tp); + } + else + { + for (auto tpParam : createGenericPacks(*defnScopePtr, alias->genericPacks, /* useCache */ true, /* addTypes */ false)) + typePackParams.push_back(tpParam.second.tp); + } addConstraint( scope, @@ -2520,7 +2545,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* scope->bindings[targetLocal->local].typeId = resultTy; DefId def = dfg->getDef(targetLocal); - scope->lvalueTypes[def] = resultTy; // TODO: typestates: track this as an assignment + scope->lvalueTypes[def] = resultTy; // TODO: typestates: track this as an assignment updateRValueRefinements(scope, def, resultTy); // TODO: typestates: track this as an assignment // HACK: If we have a targetLocal, it has already been added to the @@ -2839,7 +2864,7 @@ Inference ConstraintGenerator::checkIndexName( if (FFlag::LuauCacheDuplicateHasPropConstraints) { - if (auto cachedHasPropResult = propIndexPairsSeen.find({obj,index})) + if (auto cachedHasPropResult = propIndexPairsSeen.find({obj, index})) result = *cachedHasPropResult; } @@ -3074,41 +3099,69 @@ Inference ConstraintGenerator::checkAstExprBinary( { addConstraint(scope, location, EqualityConstraint{leftType, rightType}); - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, location); - return Inference{resultType, std::move(refinement)}; + if (FFlag::LuauNoOrderingTypeFunctions) + { + return Inference{builtinTypes->booleanType, std::move(refinement)}; + } + else + { + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, location); + return Inference{resultType, std::move(refinement)}; + } } case AstExprBinary::Op::CompareGe: { addConstraint(scope, location, EqualityConstraint{leftType, rightType}); - TypeId resultType = createTypeFunctionInstance( - builtinTypeFunctions().ltFunc, - {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` - {}, - scope, - location - ); - return Inference{resultType, std::move(refinement)}; + if (FFlag::LuauNoOrderingTypeFunctions) + { + return Inference{builtinTypes->booleanType, std::move(refinement)}; + } + else + { + TypeId resultType = createTypeFunctionInstance( + builtinTypeFunctions().ltFunc, + {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` + {}, + scope, + location + ); + return Inference{resultType, std::move(refinement)}; + } } case AstExprBinary::Op::CompareLe: { addConstraint(scope, location, EqualityConstraint{leftType, rightType}); - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, location); - return Inference{resultType, std::move(refinement)}; + if (FFlag::LuauNoOrderingTypeFunctions) + { + return Inference{builtinTypes->booleanType, std::move(refinement)}; + } + else + { + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, location); + return Inference{resultType, std::move(refinement)}; + } } case AstExprBinary::Op::CompareGt: { addConstraint(scope, location, EqualityConstraint{leftType, rightType}); - TypeId resultType = createTypeFunctionInstance( - builtinTypeFunctions().leFunc, - {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` - {}, - scope, - location - ); - return Inference{resultType, std::move(refinement)}; + if (FFlag::LuauNoOrderingTypeFunctions) + { + return Inference{builtinTypes->booleanType, std::move(refinement)}; + } + else + { + TypeId resultType = createTypeFunctionInstance( + builtinTypeFunctions().leFunc, + {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` + {}, + scope, + location + ); + return Inference{resultType, std::move(refinement)}; + } } case AstExprBinary::Op::CompareEq: case AstExprBinary::Op::CompareNe: diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index e9de78d4..bc6653df 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -50,10 +50,33 @@ LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAGVARIABLE(LuauScopedSeenSetInLookupTableProp) namespace Luau { +struct SeenScope +{ + Set& seen; + TypeId ty; + + SeenScope(Set& seen, TypeId ty) + : seen(seen) + , ty(ty) + { + seen.insert(ty); + } + + ~SeenScope() + { + seen.erase(ty); + } + + // Delete copy constructor and copy assignment operator to prevent copying + SeenScope(const SeenScope&) = delete; + SeenScope& operator=(const SeenScope&) = delete; +}; + bool SubtypeConstraintRecord::operator==(const SubtypeConstraintRecord& other) const { return (subTy == other.subTy) && (superTy == other.superTy) && (variance == other.variance); @@ -464,9 +487,7 @@ void ConstraintSolver::run() if (FFlag::DebugLuauLogSolver) { - printf( - "Starting solver for module %s (%s)\n", module->humanReadableName.c_str(), module->name.c_str() - ); + printf("Starting solver for module %s (%s)\n", module->humanReadableName.c_str(), module->name.c_str()); dump(this, opts); printf("Bindings:\n"); dumpBindings(rootScope, opts); @@ -3090,7 +3111,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( bool suppressSimplification ) { - DenseHashSet seen{nullptr}; + Set seen{nullptr}; return lookupTableProp(constraint, subjectType, propName, context, inConditional, suppressSimplification, seen); } @@ -3101,12 +3122,17 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( ValueContext context, bool inConditional, bool suppressSimplification, - DenseHashSet& seen + Set& seen ) { if (seen.contains(subjectType)) return {}; - seen.insert(subjectType); + + std::optional ss; + if (FFlag::LuauScopedSeenSetInLookupTableProp) + ss.emplace(seen, subjectType); + else + seen.insert(subjectType); subjectType = follow(subjectType); diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 53f0696b..e67b8c8a 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -37,6 +37,7 @@ LUAU_FASTFLAGVARIABLE(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAGVARIABLE(LuauForInProvidesRecommendations) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTakesInnermostRefinement) LUAU_FASTFLAG(LuauSuggestHotComments) +LUAU_FASTFLAGVARIABLE(LuauForInRangesConsiderInLocation) namespace Luau { @@ -197,7 +198,13 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo if (!forIn->hasIn) return nonEmpty; else - return Location{forIn->inLocation.begin, cursorPosition}; + { + // [for ... in ... do] - the cursor can either be between [for ... in] or [in ... do] + if (FFlag::LuauForInRangesConsiderInLocation && cursorPosition < forIn->inLocation.begin) + return nonEmpty; + else + return Location{forIn->inLocation.begin, cursorPosition}; + } } } return empty; diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 4b4c513d..84e436fb 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -12,7 +12,7 @@ #include "Luau/Unifier2.h" LUAU_FASTFLAG(LuauLimitUnification) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) @@ -45,7 +45,12 @@ OverloadResolver::OverloadResolver( { } -std::pair OverloadResolver::selectOverload(TypeId ty, TypePackId argsPack, NotNull> uniqueTypes, bool useFreeTypeBounds) +std::pair OverloadResolver::selectOverload( + TypeId ty, + TypePackId argsPack, + NotNull> uniqueTypes, + bool useFreeTypeBounds +) { auto tryOne = [&](TypeId f) { @@ -100,7 +105,13 @@ std::pair OverloadResolver::selectOverload(T return {Analysis::OverloadIsNonviable, ty}; } -void OverloadResolver::resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector* argExprs, NotNull> uniqueTypes) +void OverloadResolver::resolve( + TypeId fnTy, + const TypePack* args, + AstExpr* selfExpr, + const std::vector* argExprs, + NotNull> uniqueTypes +) { fnTy = follow(fnTy); @@ -341,7 +352,7 @@ std::pair OverloadResolver::checkOverload_ return {Analysis::Ok, {}}; } - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) { if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { @@ -451,27 +462,29 @@ std::pair OverloadResolver::checkOverload_ : argExprs->size() != 0 ? argExprs->back()->location : fnExpr->location; - // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance and LuauReturnMappedGenericPacksFromSubtyping2 + // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance and + // LuauReturnMappedGenericPacksFromSubtyping3 std::optional failedSubTy; if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) failedSubTy = traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); else failedSubTy = traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes); - // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance and LuauReturnMappedGenericPacksFromSubtyping2 + // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance and + // LuauReturnMappedGenericPacksFromSubtyping3 std::optional failedSuperTy; if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) failedSuperTy = traverseForType_DEPRECATED( prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena ); else failedSuperTy = traverseForType_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); else if (failedSubTy && failedSuperTy) { @@ -501,7 +514,7 @@ std::pair OverloadResolver::checkOverload_ } } } - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && reason.superPath.components.size() > 1) + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && reason.superPath.components.size() > 1) { // traverseForIndex only has a value if path is of form [...PackSlice, Index] if (const auto index = @@ -534,7 +547,7 @@ std::pair OverloadResolver::checkOverload_ std::optional failedSubPack; if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes, arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) failedSubPack = traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); else failedSubPack = traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes); @@ -542,7 +555,7 @@ std::pair OverloadResolver::checkOverload_ std::optional failedSuperPack; if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) failedSuperPack = traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); else diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 4be86352..7f4e122d 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -25,6 +25,7 @@ LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauPushTypeConstraint) LUAU_FASTFLAGVARIABLE(LuauMorePreciseExternTableRelation) +LUAU_FASTFLAGVARIABLE(LuauSimplifyRefinementOfReadOnlyProperty) namespace Luau { @@ -1414,9 +1415,12 @@ std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) if (1 == lt->props.size()) { const auto [propName, leftProp] = *begin(lt->props); + const bool leftPropIsRefinable = FFlag::LuauSimplifyRefinementOfReadOnlyProperty + ? leftProp.isShared() || leftProp.isReadOnly() + : leftProp.isShared(); auto it = rt->props.find(propName); - if (it != rt->props.end() && leftProp.isShared() && it->second.isShared()) + if (it != rt->props.end() && leftPropIsRefinable && it->second.isShared()) { Relation r = relate(*leftProp.readTy, *it->second.readTy); @@ -1428,7 +1432,7 @@ std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) case Relation::Coincident: return right; case Relation::Subset: - if (1 == rt->props.size()) + if (1 == rt->props.size() && leftProp.isShared()) return left; break; default: diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index b62b4da7..f3718315 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -20,7 +20,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) -LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauEmplaceNotPushBack) @@ -28,6 +28,8 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAGVARIABLE(LuauTrackUniqueness) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericPacksDoesntUseVariance) LUAU_FASTFLAGVARIABLE(LuauSubtypingUnionsAndIntersectionsInGenericBounds) +LUAU_FASTFLAGVARIABLE(LuauIndexInMetatableSubtyping) +LUAU_FASTFLAGVARIABLE(LuauSubtypingPackRecursionLimits) namespace Luau { @@ -199,7 +201,7 @@ static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const Subtyp template static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull builtinTypes, NotNull arena) { - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2); + LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); if (!FFlag::DebugLuauSubtypingCheckPathValidity) return; @@ -812,7 +814,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull p{subTy, superTy}; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) result.mappedGenericPacks_DEPRECATED = env.mappedGenericPacks_DEPRECATED; if (result.isCacheable) @@ -906,7 +911,7 @@ struct SeenTypePackSetPopper : seenTypes(seenTypes) , pair(std::move(pair)) { - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2); + LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); } SeenTypePackSetPopper(const SeenTypePackSetPopper&) = delete; @@ -916,7 +921,7 @@ struct SeenTypePackSetPopper ~SeenTypePackSetPopper() { - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2); + LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); seenTypes->erase(pair); } }; @@ -942,7 +947,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub const SubtypingResult* cachedResult = resultCache.find({subTy, superTy}); if (cachedResult) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks_DEPRECATED) env.mappedGenericPacks_DEPRECATED.try_insert(genericTp, boundTp); @@ -954,7 +959,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub cachedResult = env.tryFindSubtypingResult({subTy, superTy}); if (cachedResult) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks_DEPRECATED) env.mappedGenericPacks_DEPRECATED.try_insert(genericTp, boundTp); @@ -1265,7 +1270,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) assertReasoningValid(subTy, superTy, result, builtinTypes, arena); else assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); @@ -1273,13 +1278,35 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub return cache(env, std::move(result), subTy, superTy); } +/* + * Subtyping of packs is fairly involved. There are three parts to the test. + * + * 1. If both packs have types at their heads, we do a pairwise test for each + * pair of types. + * 2. If the finite parts of the packs are of inequal length and the pack on the + * opposite side has a tail, we test that. (eg + * testing concrete types against variadics or a generic pack) + * 3. Lastly, do a subtype test on non-finite tails. (eg between two generic + * packs or variadics) + */ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull scope) { + UnifierCounters& counters = normalizer->sharedState->counters; + std::optional rc; + + if (FFlag::LuauSubtypingPackRecursionLimits) + { + rc.emplace(&counters.recursionCount); + + if (counters.recursionLimit > 0 && counters.recursionLimit < counters.recursionCount) + return SubtypingResult{false, true}; + } + subTp = follow(subTp); superTp = follow(superTp); std::optional popper = std::nullopt; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) { std::pair typePair = {subTp, superTp}; if (!seenPacks.insert(typePair)) @@ -1311,125 +1338,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId { if (subTail) { - if (auto vt = get(*subTail)) - { - for (size_t i = headSize; i < superHead.size(); ++i) - results.push_back(isCovariantWith(env, vt->ty, superHead[i], scope) - .withSubPath(TypePath::PathBuilder().tail().variadic().build()) - .withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})); - } - else if (get(*subTail)) - { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) - { - MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(*subTail); - SubtypingResult result; - if (get_if(&lookupResult)) - result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} - .withSubComponent(TypePath::PackField::Tail) - .withSuperComponent(TypePath::PackSlice{headSize}); - else - { - TypePackId superTailPack = sliceTypePack(headSize, superTp, superHead, superTail, builtinTypes, arena); - - if (const TypePackId* mappedGen = get_if(&lookupResult)) - { - // Subtype against the mapped generic pack. - TypePackId subTpToCompare = *mappedGen; - - // If mappedGen has a hidden variadic tail, we clip it for better arity mismatch reporting. - const TypePack* tp = get(*mappedGen); - if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) - subTpToCompare = arena->addTypePack(tp->head); - - result = isCovariantWith(env, subTpToCompare, superTailPack, scope) - .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}})) - .withSuperComponent(TypePath::PackSlice{headSize}); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - bool ok = env.mappedGenericPacks.bindGeneric(*subTail, superTailPack); - result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false} - .withSubComponent(TypePath::PackField::Tail) - .withSuperComponent(TypePath::PackSlice{headSize}); - } - } - - results.push_back(result); - return SubtypingResult::all(results); - } - else if (variance == Variance::Covariant) - { - // For any non-generic type T: - // - // (X) -> () <: (T) -> () - - TypePackId superTailPack; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) - { - if (headSize == 0) - superTailPack = superTp; - else if (headSize == superHead.size()) - superTailPack = superTail ? *superTail : builtinTypes->emptyTypePack; - else - { - auto superHeadIter = begin(superHead); - for (size_t i = 0; i < headSize; ++i) - ++superHeadIter; - std::vector headSlice(std::move(superHeadIter), end(superHead)); - superTailPack = arena->addTypePack(std::move(headSlice), superTail); - } - } - else - { - // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice = std::vector(begin(superHead), begin(superHead) + headSize); - superTailPack = arena->addTypePack(std::move(headSlice), superTail); - } - - if (TypePackId* other = env.getMappedPackBounds_DEPRECATED(*subTail)) - { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) - { - const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) - { - TypePackId taillessTp = arena->addTypePack(tp->head); - results.push_back(isCovariantWith(env, taillessTp, superTailPack, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperComponent(TypePath::PackSlice{headSize})); - } - else - results.push_back(isCovariantWith(env, *other, superTailPack, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperComponent(TypePath::PackSlice{headSize})); - } - else - results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail)); - } - else - env.mappedGenericPacks_DEPRECATED.try_insert(*subTail, superTailPack); - - // FIXME? Not a fan of the early return here. It makes the - // control flow harder to reason about. - return SubtypingResult::all(results); - } - else - { - // For any non-generic type T: - // - // (T) -> () (X) -> () - // - return SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail); - } - } - else if (get(*subTail)) - return SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail); - else - return SubtypingResult{false} - .withSubComponent(TypePath::PackField::Tail) - .withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}}); + std::optional sr = isSubTailCovariantWith(env, results, subTp, *subTail, superTp, headSize, superHead, superTail, scope); + if (sr) + return *sr; } else { @@ -1441,123 +1352,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId { if (superTail) { - if (auto vt = get(*superTail)) - { - for (size_t i = headSize; i < subHead.size(); ++i) - results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope) - .withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}) - .withSuperPath(TypePath::PathBuilder().tail().variadic().build())); - } - else if (get(*superTail)) - { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) - { - MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(*superTail); - SubtypingResult result; - if (get_if(&lookupResult)) - result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} - .withSubComponent(TypePath::PackSlice{headSize}) - .withSuperComponent(TypePath::PackField::Tail); - else - { - TypePackId subTailPack = sliceTypePack(headSize, subTp, subHead, subTail, builtinTypes, arena); - - if (const TypePackId* mappedGen = get_if(&lookupResult)) - { - TypePackId superTpToCompare = *mappedGen; - - // Subtype against the mapped generic pack. - const TypePack* tp = get(*mappedGen); - if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) - superTpToCompare = arena->addTypePack(tp->head); - - result = isCovariantWith(env, subTailPack, superTpToCompare, scope) - .withSubComponent(TypePath::PackSlice{headSize}) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}})); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - bool ok = env.mappedGenericPacks.bindGeneric(*superTail, subTailPack); - result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false} - .withSubComponent(TypePath::PackSlice{headSize}) - .withSuperComponent(TypePath::PackField::Tail); - } - } - - results.push_back(result); - return SubtypingResult::all(results); - } - else if (variance == Variance::Contravariant) - { - // For any non-generic type T: - // - // (X...) -> () <: (T) -> () - - TypePackId subTailPack; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) - { - if (headSize == 0) - subTailPack = subTp; - else if (headSize == subHead.size()) - subTailPack = subTail ? *subTail : builtinTypes->emptyTypePack; - else - { - auto subHeadIter = begin(subHead); - for (size_t i = 0; i < headSize; ++i) - ++subHeadIter; - std::vector headSlice(std::move(subHeadIter), end(subHead)); - subTailPack = arena->addTypePack(std::move(headSlice), subTail); - } - } - else - { - // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice = std::vector(begin(subHead), begin(subHead) + headSize); - subTailPack = arena->addTypePack(std::move(headSlice), subTail); - } - - if (TypePackId* other = env.getMappedPackBounds_DEPRECATED(*superTail)) - { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) - { - const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) - { - TypePackId taillessTp = arena->addTypePack(tp->head); - results.push_back(isCovariantWith(env, subTailPack, taillessTp, scope) - .withSubComponent(TypePath::PackSlice{headSize}) - .withSuperComponent(TypePath::PackField::Tail)); - } - else - results.push_back(isCovariantWith(env, subTailPack, *other, scope) - .withSubComponent(TypePath::PackSlice{headSize}) - .withSuperComponent(TypePath::PackField::Tail)); - } - else - results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); - } - else - env.mappedGenericPacks_DEPRECATED.try_insert(*superTail, subTailPack); - - // FIXME? Not a fan of the early return here. It makes the - // control flow harder to reason about. - return SubtypingResult::all(results); - } - else - { - // For any non-generic type T: - // - // () -> T () -> X... - return SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail); - } - } - else if (get(*superTail)) - return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail); - else - return SubtypingResult{false} - .withSuperComponent(TypePath::PackField::Tail) - .withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}}); + std::optional sr = isCovariantWithSuperTail(env, results, subTp, headSize, subHead, subTail, superTp, *superTail, scope); + if (sr) + return *sr; } else return {false}; @@ -1590,31 +1387,27 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (const TypePackId* currMapping = get_if(&subLookupResult)) { results.push_back(isCovariantWith(env, *currMapping, *superTail, scope) - .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})) - .withSuperComponent(TypePath::PackField::Tail)); + .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})) + .withSuperComponent(TypePath::PackField::Tail)); } else if (get_if(&subLookupResult)) { bool ok = env.mappedGenericPacks.bindGeneric(*subTail, *superTail); results.push_back( - SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( - TypePath::PackField::Tail - ) + SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail) ); } else if (const TypePackId* currMapping = get_if(&superLookupResult)) { results.push_back(isCovariantWith(env, *subTail, *currMapping, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))); + .withSubComponent(TypePath::PackField::Tail) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))); } else if (get_if(&superLookupResult)) { bool ok = env.mappedGenericPacks.bindGeneric(*superTail, *subTail); results.push_back( - SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( - TypePath::PackField::Tail - ) + SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail) ); } else @@ -1831,7 +1624,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId SubtypingResult result = SubtypingResult::all(results); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) assertReasoningValid(subTp, superTp, result, builtinTypes, arena); else assertReasoningValid_DEPRECATED(subTp, superTp, result, builtinTypes); @@ -1839,6 +1632,287 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId return result; } +/* Check the tail of the subtype pack against a slice of the finite part of the + * pack in supertype position. For example, in the following type, the head of + * the pack in supertype position is longer than that of the subtype head: + * + * (number, string, any...) <: (number, string, boolean, thread, any...), + * + * This function handles the mismatched heads: any... <: (boolean, thread) + * + * Notably, this function does _not_ handle the test between the actual tail + * packs. + * + * The contract on this function is a bit strange. If the function returns a + * SubtypingResult, it should be considered to be the result for the entire pack + * subtyping relation. It is not necessary to further check the tails. + */ +std::optional Subtyping::isSubTailCovariantWith( + SubtypingEnvironment& env, + std::vector& outputResults, + TypePackId subTp, + TypePackId subTail, + TypePackId superTp, + size_t superHeadStartIndex, + const std::vector& superHead, + std::optional superTail, + NotNull scope +) +{ + if (auto vt = get(subTail)) + { + for (size_t i = superHeadStartIndex; i < superHead.size(); ++i) + outputResults.push_back(isCovariantWith(env, vt->ty, superHead[i], scope) + .withSubPath(TypePath::PathBuilder().tail().variadic().build()) + .withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})); + return std::nullopt; + } + else if (get(subTail)) + { + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(subTail); + SubtypingResult result; + if (get_if(&lookupResult)) + result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} + .withSubComponent(TypePath::PackField::Tail) + .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); + else + { + TypePackId superTailPack = sliceTypePack(superHeadStartIndex, superTp, superHead, superTail, builtinTypes, arena); + + if (const TypePackId* mappedGen = get_if(&lookupResult)) + { + // Subtype against the mapped generic pack. + TypePackId subTpToCompare = *mappedGen; + + // If mappedGen has a hidden variadic tail, we clip it for better arity mismatch reporting. + const TypePack* tp = get(*mappedGen); + if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) + subTpToCompare = arena->addTypePack(tp->head); + + result = isCovariantWith(env, subTpToCompare, superTailPack, scope) + .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}})) + .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); + } + else + { + LUAU_ASSERT(get_if(&lookupResult)); + bool ok = env.mappedGenericPacks.bindGeneric(subTail, superTailPack); + result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false} + .withSubComponent(TypePath::PackField::Tail) + .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); + } + } + + outputResults.push_back(result); + return SubtypingResult::all(outputResults); + } + else if (variance == Variance::Covariant) + { + // For any non-generic type T: + // + // (X) -> () <: (T) -> () + + TypePackId superTailPack; + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) + { + if (superHeadStartIndex == 0) + superTailPack = superTp; + else if (superHeadStartIndex == superHead.size()) + superTailPack = superTail ? *superTail : builtinTypes->emptyTypePack; + else + { + auto superHeadIter = begin(superHead); + for (size_t i = 0; i < superHeadStartIndex; ++i) + ++superHeadIter; + std::vector headSlice(std::move(superHeadIter), end(superHead)); + superTailPack = arena->addTypePack(std::move(headSlice), superTail); + } + } + else + { + // Possible optimization: If headSize == 0 then we can just use subTp as-is. + std::vector headSlice = std::vector(begin(superHead), begin(superHead) + int(superHeadStartIndex)); + superTailPack = arena->addTypePack(std::move(headSlice), superTail); + } + + if (TypePackId* other = env.getMappedPackBounds_DEPRECATED(subTail)) + { + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) + { + const TypePack* tp = get(*other); + if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) + { + TypePackId taillessTp = arena->addTypePack(tp->head); + outputResults.push_back(isCovariantWith(env, taillessTp, superTailPack, scope) + .withSubComponent(TypePath::PackField::Tail) + .withSuperComponent(TypePath::PackSlice{superHeadStartIndex})); + } + else + outputResults.push_back(isCovariantWith(env, *other, superTailPack, scope) + .withSubComponent(TypePath::PackField::Tail) + .withSuperComponent(TypePath::PackSlice{superHeadStartIndex})); + } + else + outputResults.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail)); + } + else + env.mappedGenericPacks_DEPRECATED.try_insert(subTail, superTailPack); + + // FIXME? Not a fan of the early return here. It makes the + // control flow harder to reason about. + return SubtypingResult::all(outputResults); + } + else + { + // For any non-generic type T: + // + // (T) -> () (X) -> () + // + return SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail); + } + } + else if (get(subTail)) + return SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail); + else + return SubtypingResult{false} + .withSubComponent(TypePath::PackField::Tail) + .withError({scope->location, UnexpectedTypePackInSubtyping{subTail}}); +} + +std::optional Subtyping::isCovariantWithSuperTail( + SubtypingEnvironment& env, + std::vector& results, + TypePackId subTp, + size_t subHeadStartIndex, + const std::vector& subHead, + std::optional subTail, + TypePackId superTp, + TypePackId superTail, + NotNull scope +) +{ + if (auto vt = get(superTail)) + { + for (size_t i = subHeadStartIndex; i < subHead.size(); ++i) + results.push_back(isCovariantWith(env, subHead[i], vt->ty, scope) + .withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}) + .withSuperPath(TypePath::PathBuilder().tail().variadic().build())); + return std::nullopt; + } + else if (get(superTail)) + { + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(superTail); + SubtypingResult result; + if (get_if(&lookupResult)) + result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} + .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) + .withSuperComponent(TypePath::PackField::Tail); + else + { + TypePackId subTailPack = sliceTypePack(subHeadStartIndex, subTp, subHead, subTail, builtinTypes, arena); + + if (const TypePackId* mappedGen = get_if(&lookupResult)) + { + TypePackId superTpToCompare = *mappedGen; + + // Subtype against the mapped generic pack. + const TypePack* tp = get(*mappedGen); + if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) + superTpToCompare = arena->addTypePack(tp->head); + + result = isCovariantWith(env, subTailPack, superTpToCompare, scope) + .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}})); + } + else + { + LUAU_ASSERT(get_if(&lookupResult)); + bool ok = env.mappedGenericPacks.bindGeneric(superTail, subTailPack); + result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false} + .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) + .withSuperComponent(TypePath::PackField::Tail); + } + } + + results.push_back(result); + return SubtypingResult::all(results); + } + else if (variance == Variance::Contravariant) + { + // For any non-generic type T: + // + // (X...) -> () <: (T) -> () + + TypePackId subTailPack; + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) + { + if (subHeadStartIndex == 0) + subTailPack = subTp; + else if (subHeadStartIndex == subHead.size()) + subTailPack = subTail ? *subTail : builtinTypes->emptyTypePack; + else + { + auto subHeadIter = begin(subHead); + for (size_t i = 0; i < subHeadStartIndex; ++i) + ++subHeadIter; + std::vector headSlice(std::move(subHeadIter), end(subHead)); + subTailPack = arena->addTypePack(std::move(headSlice), subTail); + } + } + else + { + // Possible optimization: If headSize == 0 then we can just use subTp as-is. + std::vector headSlice = std::vector(begin(subHead), begin(subHead) + int(subHeadStartIndex)); + subTailPack = arena->addTypePack(std::move(headSlice), subTail); + } + + if (TypePackId* other = env.getMappedPackBounds_DEPRECATED(superTail)) + { + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) + { + const TypePack* tp = get(*other); + if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) + { + TypePackId taillessTp = arena->addTypePack(tp->head); + results.push_back(isCovariantWith(env, subTailPack, taillessTp, scope) + .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) + .withSuperComponent(TypePath::PackField::Tail)); + } + else + results.push_back(isCovariantWith(env, subTailPack, *other, scope) + .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) + .withSuperComponent(TypePath::PackField::Tail)); + } + else + results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); + } + else + env.mappedGenericPacks_DEPRECATED.try_insert(superTail, subTailPack); + + // FIXME? Not a fan of the early return here. It makes the + // control flow harder to reason about. + return SubtypingResult::all(results); + } + else + { + // For any non-generic type T: + // + // () -> T () -> X... + return SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail); + } + } + else if (get(superTail)) + return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail); + else + return SubtypingResult{false} + .withSuperComponent(TypePath::PackField::Tail) + .withError({scope->location, UnexpectedTypePackInSubtyping{superTail}}); +} + template SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope) { @@ -1866,7 +1940,7 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy& } } - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) assertReasoningValid(subTy, superTy, result, builtinTypes, arena); else assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); @@ -1888,7 +1962,7 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su reasoning.variance = SubtypingVariance::Invariant; } - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) assertReasoningValid(subTy, superTy, result, builtinTypes, arena); else assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); @@ -2326,16 +2400,102 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta { if (auto subTable = get(follow(subMt->table))) { - // Metatables cannot erase properties from the table they're attached to, so - // the subtyping rule for this is just if the table component is a subtype - // of the supertype table. - // - // There's a flaw here in that if the __index metamethod contributes a new - // field that would satisfy the subtyping relationship, we'll erroneously say - // that the metatable isn't a subtype of the table, even though they have - // compatible properties/shapes. We'll revisit this later when we have a - // better understanding of how important this is. - return isCovariantWith(env, subTable, superTable, /*forceCovariantTest*/ false, scope); + if (FFlag::LuauIndexInMetatableSubtyping) + { + auto doDefault = [&]() + { + return isCovariantWith(env, subTable, superTable, /* forceCovariantTest */ false, scope); + }; + + // My kingdom for `do` notation. + + // TODO CLI-169235: This logic is very mechanical and is, + // effectively a repeat of the logic for the `index<_, _>` + // type function. These should use similar logic. Otherwise + // this all constantly falls over for the same reasons + // structural subtypying falls over. + // + // Notably, this does not support `__index` as a function. + + auto subMTTable = get(follow(subMt->metatable)); + if (!subMTTable) + return doDefault(); + + auto __index = subMTTable->props.find("__index"); + if (__index == subMTTable->props.end()) + return doDefault(); + + // `read`-only __index sounds reasonable, but write-only + // or non-shared sounds weird. + if (!__index->second.readTy) + return doDefault(); + + auto __indexAsTable = get(follow(*__index->second.readTy)); + if (!__indexAsTable) + return doDefault(); + + // Consider the snippet: + // + // local ItemContainer = {} + // ItemContainer.__index = ItemContainer + // + // function ItemContainer.new() + // local self = {} + // setmetatable(self, ItemContainer) + // return self + // end + // + // function ItemContainer:removeItem(itemId, itemType) + // self:getItem(itemId, itemType) + // end + // + // function ItemContainer:getItem(itemId, itemType) end + // + // local container = ItemContainer.new() + // container:removeItem(0, "magic") + // + // When we go to check this, we're effectively asking whether + // `container` is a subtype of the first argument of + // `container.removeItem`. `container` has a metatable with the + // `__index` metamethod, so we need to include those fields in the + // subtype check. + // + // However, we need to include a read only view of those fields. + // Consider: + // + // local Foobar = {} + // Foobar.__index = Foobar + // Foobar.const = 42 + // + // local foobar = setmetatable({}, Foobar) + // + // local _: { const: number } = foobar + // + // This should error, as we cannot write to `const`. + + TableType fauxSubTable{*subTable}; + for (auto& [name, prop] : __indexAsTable->props) + { + if (prop.readTy && fauxSubTable.props.find(name) == fauxSubTable.props.end()) + fauxSubTable.props[name] = Property::readonly(*prop.readTy); + } + + return isCovariantWith(env, &fauxSubTable, superTable, /* forceCovariantTest */ false, scope); + + } + else + { + // Metatables cannot erase properties from the table they're attached to, so + // the subtyping rule for this is just if the table component is a subtype + // of the supertype table. + // + // There's a flaw here in that if the __index metamethod contributes a new + // field that would satisfy the subtyping relationship, we'll erroneously say + // that the metatable isn't a subtype of the table, even though they have + // compatible properties/shapes. We'll revisit this later when we have a + // better understanding of how important this is. + return isCovariantWith(env, subTable, superTable, /* forceCovariantTest */ false, scope); + } } else { @@ -2915,7 +3075,7 @@ bool Subtyping::bindGeneric_DEPRECATED(SubtypingEnvironment& env, TypePackId sub if (TypePackId* m = env.getMappedPackBounds_DEPRECATED(subTp)) return *m == superTp; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) { // We shouldn't bind generic type packs to themselves if (subTp == superTp) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 8a7b8abb..c8929b30 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -33,7 +33,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauTrackUniqueness) @@ -48,6 +48,8 @@ LUAU_FASTFLAGVARIABLE(LuauRemoveGenericErrorForParams) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAGVARIABLE(LuauAddErrorCaseForIncompatibleTypePacks) LUAU_FASTFLAGVARIABLE(LuauAddConditionalContextForTernary) +LUAU_FASTFLAGVARIABLE(LuauCheckForInWithSubtyping) +LUAU_FASTFLAGVARIABLE(LuauNoOrderingTypeFunctions) namespace Luau { @@ -996,10 +998,16 @@ void TypeChecker2::visit(AstStatForIn* forInStatement) reportError(GenericError{"__iter metamethod's next() function does not return enough values"}, getLocation(forInStatement->values)); else reportError(GenericError{"next() does not return enough values"}, forInStatement->values.data[0]->location); + + if (FFlag::LuauCheckForInWithSubtyping) + return; } - for (size_t i = 0; i < std::min(expectedVariableTypes.head.size(), variableTypes.size()); ++i) - testIsSubtype(variableTypes[i], expectedVariableTypes.head[i], forInStatement->vars.data[i]->location); + if (!FFlag::LuauCheckForInWithSubtyping) + { + for (size_t i = 0; i < std::min(expectedVariableTypes.head.size(), variableTypes.size()); ++i) + testIsSubtype(variableTypes[i], expectedVariableTypes.head[i], forInStatement->vars.data[i]->location); + } // nextFn is going to be invoked with (arrayTy, startIndexTy) @@ -1029,27 +1037,61 @@ void TypeChecker2::visit(AstStatForIn* forInStatement) reportError(GenericError{"__iter metamethod must return (next[, table[, state]])"}, getLocation(forInStatement->values)); else reportError(CountMismatch{2, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->values.data[0]->location); - } + if (FFlag::LuauCheckForInWithSubtyping) + return; + } else if (actualArgCount < minCount) { if (isMm) reportError(GenericError{"__iter metamethod must return (next[, table[, state]])"}, getLocation(forInStatement->values)); else reportError(CountMismatch{2, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->values.data[0]->location); - } + if (FFlag::LuauCheckForInWithSubtyping) + return; + } - if (iterTys.size() >= 2 && flattenedArgTypes.head.size() > 0) + if (FFlag::LuauCheckForInWithSubtyping) { - size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0; - testIsSubtype(iterTys[1], flattenedArgTypes.head[0], forInStatement->values.data[valueIndex]->location); - } + const TypeId iterFunc = follow(iterTys[0]); + + std::vector prospectiveArgTypes = std::vector(iterTys.begin() + 1, iterTys.end()); + // Right pad with nils if needed + if (const TypePack* iterFuncArgs = get(follow(iterFtv->argTypes)); + iterFuncArgs && iterFuncArgs->head.size() > prospectiveArgTypes.size()) + prospectiveArgTypes.resize(iterFuncArgs->head.size(), builtinTypes->nilType); + const TypePackId prospectiveArgs = arena.addTypePack(prospectiveArgTypes, std::nullopt); - if (iterTys.size() == 3 && flattenedArgTypes.head.size() > 1) + std::vector prospectiveRetTypes = {}; + if (variableTypes.size() > 0) // Type inference intersects the control variable with ~nil, so we make it optional here + prospectiveRetTypes.emplace_back(arena.addType(UnionType{{variableTypes[0], builtinTypes->nilType}})); + if (variableTypes.size() > 1) + prospectiveRetTypes.emplace_back(variableTypes[1]); + // Right pad with anys, since not all the return values are used (eg for key in pairs(t)) + if (const TypePack* iterFuncRets = get(follow(iterFtv->retTypes)); + iterFuncRets && iterFuncRets->head.size() > prospectiveRetTypes.size()) + prospectiveRetTypes.resize(iterFuncRets->head.size(), builtinTypes->anyType); + // Add a variadic any tail because sometimes iterFunc returns a variadic pack (see forin_metatable_iter_mm) + const TypePackId prospectiveRets = arena.addTypePack(prospectiveRetTypes, builtinTypes->anyTypePack); + + const TypeId prospectiveFunction = arena.addType(FunctionType{prospectiveArgs, prospectiveRets, std::nullopt, isMm}); + + testIsSubtypeForInStat(iterFunc, prospectiveFunction, *forInStatement); + } + else { - size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0; - testIsSubtype(iterTys[2], flattenedArgTypes.head[1], forInStatement->values.data[valueIndex]->location); + if (iterTys.size() >= 2 && flattenedArgTypes.head.size() > 0) + { + size_t valueIndex = forInStatement->values.size > 1 ? 1 : 0; + testIsSubtype(iterTys[1], flattenedArgTypes.head[0], forInStatement->values.data[valueIndex]->location); + } + + if (iterTys.size() == 3 && flattenedArgTypes.head.size() > 1) + { + size_t valueIndex = forInStatement->values.size > 2 ? 2 : 0; + testIsSubtype(iterTys[2], flattenedArgTypes.head[1], forInStatement->values.data[valueIndex]->location); + } } }; @@ -2154,7 +2196,7 @@ static bool isOkToCompare( // Comparison with never is always ok. else if (NormalizationResult::True != normalizer.isInhabited(normLeft.get()) || - NormalizationResult::True != normalizer.isInhabited(normRight.get())) + NormalizationResult::True != normalizer.isInhabited(normRight.get())) return true; // Comparisons between different string singleton types is allowed even @@ -2390,16 +2432,23 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) { if (isComparison) { - reportError( - GenericError{format( - "Types '%s' and '%s' cannot be compared with %s because neither type's metatable has a '%s' metamethod", - toString(leftType).c_str(), - toString(rightType).c_str(), - toString(expr->op).c_str(), - it->second - )}, - expr->location - ); + if (FFlag::LuauNoOrderingTypeFunctions) + { + reportError(CannotCompareUnrelatedTypes{leftType, rightType, expr->op}, expr->location); + } + else + { + reportError( + GenericError{format( + "Types '%s' and '%s' cannot be compared with %s because neither type's metatable has a '%s' metamethod", + toString(leftType).c_str(), + toString(rightType).c_str(), + toString(expr->op).c_str(), + it->second + )}, + expr->location + ); + } } else { @@ -2421,15 +2470,22 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) { if (isComparison) { - reportError( - GenericError{format( - "Types '%s' and '%s' cannot be compared with %s because neither type has a metatable", - toString(leftType).c_str(), - toString(rightType).c_str(), - toString(expr->op).c_str() - )}, - expr->location - ); + if (FFlag::LuauNoOrderingTypeFunctions) + { + reportError(CannotCompareUnrelatedTypes{leftType, rightType, expr->op}, expr->location); + } + else + { + reportError( + GenericError{format( + "Types '%s' and '%s' cannot be compared with %s because neither type has a metatable", + toString(leftType).c_str(), + toString(rightType).c_str(), + toString(expr->op).c_str() + )}, + expr->location + ); + } } else { @@ -2481,16 +2537,37 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) if (normLeft && normalizer.isInhabited(normLeft.get()) == NormalizationResult::False) return builtinTypes->booleanType; - if (normLeft && normLeft->isExactlyNumber()) + if (FFlag::LuauNoOrderingTypeFunctions) { - testIsSubtype(rightType, builtinTypes->numberType, expr->right->location); - return builtinTypes->booleanType; - } + // This could be a little wasteful, as we already have normalized + // types, but correctly handles cases like `_: (T & number) <= _: (T & number)`. + + if (subtyping->isSubtype(leftType, builtinTypes->numberType, scope).isSubtype) + { + testIsSubtype(rightType, builtinTypes->numberType, expr->right->location); + return builtinTypes->booleanType; + } - if (normLeft && normLeft->isSubtypeOfString()) + if (subtyping->isSubtype(leftType, builtinTypes->stringType, scope).isSubtype) + { + testIsSubtype(rightType, builtinTypes->stringType, expr->right->location); + return builtinTypes->booleanType; + } + } + else { - testIsSubtype(rightType, builtinTypes->stringType, expr->right->location); - return builtinTypes->booleanType; + + if (normLeft && normLeft->isExactlyNumber()) + { + testIsSubtype(rightType, builtinTypes->numberType, expr->right->location); + return builtinTypes->booleanType; + } + + if (normLeft && normLeft->isSubtypeOfString()) + { + testIsSubtype(rightType, builtinTypes->stringType, expr->right->location); + return builtinTypes->booleanType; + } } reportError( @@ -2959,7 +3036,7 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc std::optional optSubLeaf; if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes, subtyping->arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) optSubLeaf = traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks_DEPRECATED}, subtyping->arena); else optSubLeaf = traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes); @@ -2967,7 +3044,7 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc std::optional optSuperLeaf; if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes, subtyping->arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) optSuperLeaf = traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks_DEPRECATED}, subtyping->arena); else @@ -3320,6 +3397,105 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location return r.isSubtype; } +void TypeChecker2::maybeReportSubtypingError(const TypeId subTy, const TypeId superTy, const Location& location) +{ + LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping); + switch (shouldSuppressErrors(NotNull{&normalizer}, subTy).orElse(shouldSuppressErrors(NotNull{&normalizer}, superTy))) + { + case ErrorSuppression::Suppress: + return; + case ErrorSuppression::NormalizationFailed: + reportError(NormalizationTooComplex{}, location); + break; + case ErrorSuppression::DoNotSuppress: + break; + default: + break; + } + + reportError(TypeMismatch{superTy, subTy}, location); +} + +void TypeChecker2::testIsSubtypeForInStat(const TypeId iterFunc, const TypeId prospectiveFunc, const AstStatForIn& forInStat) +{ + LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping); + LUAU_ASSERT(get(follow(iterFunc))); + LUAU_ASSERT(get(follow(prospectiveFunc))); + + const Location& iterFuncLocation = forInStat.values.data[0]->location; + + const NotNull scope{findInnermostScope(iterFuncLocation)}; + SubtypingResult r = subtyping->isSubtype(iterFunc, prospectiveFunc, scope); + + if (!isErrorSuppressing(iterFuncLocation, iterFunc)) + { + for (auto& e : r.errors) + e.location = iterFuncLocation; + } + reportErrors(std::move(r.errors)); + + if (r.normalizationTooComplex) + reportError(NormalizationTooComplex{}, iterFuncLocation); + + if (r.isSubtype) + return; + + for (auto& reasoning : r.reasoning) + { + // We can give more specific errors if superPath reasoning is of form [Arguments|Returns, PackIndex[n]] + if (reasoning.subPath.empty() || reasoning.superPath.components.size() != 2) + { + maybeReportSubtypingError(prospectiveFunc, iterFunc, iterFuncLocation); + return; + } + + const TypePath::PackField* pf = get_if(&reasoning.superPath.components[0]); + + if (!pf || *pf == TypePath::PackField::Tail) + { + maybeReportSubtypingError(prospectiveFunc, iterFunc, iterFuncLocation); + return; + } + + const TypePath::Index* index = get_if(&reasoning.superPath.components[1]); + if (!index || index->variant != TypePath::Index::Variant::Pack) + { + maybeReportSubtypingError(prospectiveFunc, iterFunc, iterFuncLocation); + return; + } + + std::optional subLeaf = FFlag::LuauSubtypingGenericPacksDoesntUseVariance + ? traverseForType(iterFunc, reasoning.subPath, builtinTypes, subtyping->arena) + : traverseForType_DEPRECATED(iterFunc, reasoning.subPath, builtinTypes); + if (!subLeaf) + continue; + + std::optional superLeaf = FFlag::LuauSubtypingGenericPacksDoesntUseVariance + ? traverseForType(prospectiveFunc, reasoning.superPath, builtinTypes, subtyping->arena) + : traverseForType_DEPRECATED(prospectiveFunc, reasoning.superPath, builtinTypes); + if (!superLeaf) + continue; + + if (*pf == TypePath::PackField::Arguments) + { + // The first component of `forInStat.values` is the iterator function itself + Location loc = index->index >= forInStat.values.size ? iterFuncLocation : forInStat.values.data[index->index + 1]->location; + maybeReportSubtypingError(*subLeaf, *superLeaf, loc); + } + else if (*pf == TypePath::PackField::Returns) + { + Location loc = index->index > forInStat.vars.size ? iterFuncLocation : forInStat.vars.data[index->index]->location; + maybeReportSubtypingError(*subLeaf, *superLeaf, loc); + } + else + { + LUAU_ASSERT(!"Unknown PackField type"); + maybeReportSubtypingError(prospectiveFunc, iterFunc, iterFuncLocation); + return; + } + } +} + void TypeChecker2::reportError(TypeErrorData data, const Location& location) { if (auto utk = get_if(&data)) diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index 6ebeda69..1cb3e36d 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -5,7 +5,7 @@ #include "Luau/TxnLog.h" #include "Luau/TypeArena.h" -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) namespace Luau @@ -460,7 +460,7 @@ std::pair, std::optional> flatten_DEPRECATED( const DenseHashMap& mappedGenericPacks ) { - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2); + LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); tp = mappedGenericPacks.contains(tp) ? *mappedGenericPacks.find(tp) : tp; @@ -547,7 +547,7 @@ LUAU_NOINLINE Unifiable::Bound* emplaceTypePack(TypeP TypePackId sliceTypePack( const size_t sliceIndex, const TypePackId toBeSliced, - std::vector& head, + const std::vector& head, const std::optional tail, const NotNull builtinTypes, const NotNull arena diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index 06b45f5d..332389bd 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -16,7 +16,7 @@ #include LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) @@ -366,7 +366,7 @@ struct TraversalState NotNull builtinTypes; // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance const DenseHashMap* mappedGenericPacks_DEPRECATED = nullptr; - // TODO: make NotNull when LuauReturnMappedGenericPacksFromSubtyping2 is clipped + // TODO: make NotNull when LuauReturnMappedGenericPacksFromSubtyping3 is clipped TypeArena* arena = nullptr; int steps = 0; @@ -497,7 +497,7 @@ struct TraversalState { auto currentPack = get(current); LUAU_ASSERT(currentPack); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { if (const auto tp = get(*currentPack)) { @@ -656,7 +656,7 @@ struct TraversalState if (auto tail = it.tail()) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance && + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance && mappedGenericPacks_DEPRECATED && mappedGenericPacks_DEPRECATED->contains(*tail)) updateCurrent(*mappedGenericPacks_DEPRECATED->find(*tail)); @@ -674,9 +674,9 @@ struct TraversalState bool traverse(const TypePath::PackSlice slice) { - // TODO: clip these checks once LuauReturnMappedGenericPacksFromSubtyping2 is clipped + // TODO: clip these checks once LuauReturnMappedGenericPacksFromSubtyping3 is clipped // arena and mappedGenericPacks_DEPRECATED should be NonNull once that happens - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2); + LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { LUAU_ASSERT(arena); @@ -688,11 +688,11 @@ struct TraversalState if (checkInvariants()) return false; - // TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping2 is clipped + // TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping3 is clipped // arena and mappedGenericPacks should be NonNull once that happens if (!FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) LUAU_ASSERT(arena && mappedGenericPacks_DEPRECATED); else if (!arena || !mappedGenericPacks_DEPRECATED) return false; diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index c0eadc77..df613199 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -233,6 +233,18 @@ enum class IrCmd : uint8_t // C: condition CMP_INT, + // Perform a comparison of two tags. Result is an integer register containing 0 or 1 + CMP_TAG, + // A, B: tag + // C: condition (eq/neq) + + // Perform tag and value comparison. Result is an integer register containing 0 or 1 + CMP_SPLIT_TVALUE, + // A: tag + // B: tag (constant: boolean/string) + // C, D: value + // E: condition (eq/neq) + // Unconditional jump // A: block/vmexit/undef JUMP, @@ -462,7 +474,7 @@ enum class IrCmd : uint8_t CHECK_NO_METATABLE, // Guard against executing in unsafe environment, exits to VM on check failure - // A: vmexit/vmexit/undef + // A: block/vmexit/undef // When undef is specified, execution is aborted on check failure CHECK_SAFE_ENV, @@ -653,7 +665,7 @@ enum class IrCmd : uint8_t // C: Kn (prototype) FALLBACK_DUPCLOSURE, - // Prepare loop variables for a generic for loop, jump to the loop backedge unconditionally + // Prepare loop variables for a generic for loop, jump to the loop back edge unconditionally // A: unsigned int (bytecode instruction index) // B: Rn (loop state start, updates Rn Rn+1 Rn+2) // C: block diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index 520aec45..b66fd6f5 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -113,6 +113,8 @@ inline bool hasResult(IrCmd cmd) case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: case IrCmd::CMP_INT: + case IrCmd::CMP_TAG: + case IrCmd::CMP_SPLIT_TVALUE: case IrCmd::TABLE_LEN: case IrCmd::TABLE_SETNUM: case IrCmd::STRING_LEN: diff --git a/CodeGen/include/Luau/IrVisitUseDef.h b/CodeGen/include/Luau/IrVisitUseDef.h index 029507f4..7d3f7438 100644 --- a/CodeGen/include/Luau/IrVisitUseDef.h +++ b/CodeGen/include/Luau/IrVisitUseDef.h @@ -4,6 +4,8 @@ #include "Luau/Common.h" #include "Luau/IrData.h" +LUAU_FASTFLAG(LuauCodegenDirectCompare) + namespace Luau { namespace CodeGen @@ -37,10 +39,18 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i visitor.use(inst.a); visitor.use(inst.b); break; + case IrCmd::CMP_TAG: + if (FFlag::LuauCodegenDirectCompare) + visitor.maybeUse(inst.a); + break; case IrCmd::JUMP_IF_TRUTHY: case IrCmd::JUMP_IF_FALSY: visitor.use(inst.a); break; + case IrCmd::JUMP_EQ_TAG: + if (FFlag::LuauCodegenDirectCompare) + visitor.maybeUse(inst.a); + break; // A <- B, C case IrCmd::DO_ARITH: visitor.maybeUse(inst.b); // Argument can also be a VmConst diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index c0fee693..54110aa8 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -154,7 +154,7 @@ static BytecodeRegTypeInfo* findRegType(BytecodeTypeInfo& info, uint8_t reg, int auto b = info.regTypes.begin() + info.regTypeOffsets[reg]; auto e = info.regTypes.begin() + info.regTypeOffsets[reg + 1]; - // Doen't have info + // Doesn't have info if (b == e) return nullptr; @@ -636,7 +636,7 @@ void buildBytecodeBlocks(IrFunction& function, const std::vector& jumpT int target = getJumpTarget(*pc, uint32_t(i)); - // Implicit fallthroughs terminate the block and might start a new one + // Implicit fallthrough terminate the block and might start a new one if (target >= 0 && !isFastCall(op)) { bcBlocks.back().finishpc = i; @@ -1225,8 +1225,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) applyBuiltinCall(LuauBuiltinFunction(bfid), bcType); regTags[LUAU_INSN_B(*pc)] = bcType.a; - regTags[aux & 0xff] = bcType.b; - regTags[(aux >> 8) & 0xff] = bcType.c; + regTags[LUAU_INSN_AUX_A(aux)] = bcType.b; + regTags[LUAU_INSN_AUX_B(aux)] = bcType.c; regTags[ra] = bcType.result; refineRegType(bcTypeInfo, ra, i, bcType.result); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index c8e88231..850311e1 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -210,7 +210,7 @@ bool isSupported() #endif // We require AVX1 support for VEX encoded XMM operations - // We also requre SSE4.1 support for ROUNDSD but the AVX check below covers it + // We also require SSE4.1 support for ROUNDSD but the AVX check below covers it // https://en.wikipedia.org/wiki/CPUID#EAX=1:_Processor_Info_and_Feature_Bits if ((cpuinfo[2] & (1 << 28)) == 0) return false; diff --git a/CodeGen/src/CodeGenA64.cpp b/CodeGen/src/CodeGenA64.cpp index 6505f788..f35d4684 100644 --- a/CodeGen/src/CodeGenA64.cpp +++ b/CodeGen/src/CodeGenA64.cpp @@ -117,7 +117,7 @@ static void emitContinueCall(AssemblyBuilderA64& build, ModuleHelpers& helpers) build.mov(rClosure, x0); - CODEGEN_ASSERT(offsetof(Proto, code) == offsetof(Proto, k) + 8); + static_assert(offsetof(Proto, code) == offsetof(Proto, k) + sizeof(Proto::k)); build.ldp(rConstants, rCode, mem(x1, offsetof(Proto, k))); // proto->k, proto->code build.br(x2); @@ -184,12 +184,12 @@ void emitReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers) if (DFFlag::AddReturnExectargetCheck) { // Get new instruction location - CODEGEN_ASSERT(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + 8); + static_assert(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + sizeof(Proto::execdata)); build.ldp(x3, x4, mem(x1, offsetof(Proto, execdata))); build.cbz(x4, helpers.exitContinueVmClearNativeFlag); } - CODEGEN_ASSERT(offsetof(Proto, code) == offsetof(Proto, k) + 8); + static_assert(offsetof(Proto, code) == offsetof(Proto, k) + sizeof(Proto::k)); build.ldp(rConstants, rCode, mem(x1, offsetof(Proto, k))); // proto->k, proto->code // Get instruction index from instruction pointer @@ -201,7 +201,7 @@ void emitReturn(AssemblyBuilderA64& build, ModuleHelpers& helpers) if (!DFFlag::AddReturnExectargetCheck) { // Get new instruction location and jump to it - CODEGEN_ASSERT(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + 8); + static_assert(offsetof(Proto, exectarget) == offsetof(Proto, execdata) + sizeof(Proto::execdata)); build.ldp(x3, x4, mem(x1, offsetof(Proto, execdata))); } build.ldr(w2, mem(x3, x2)); @@ -240,7 +240,7 @@ static EntryLocations buildEntryFunction(AssemblyBuilderA64& build, UnwindBuilde build.ldr(rBase, mem(x0, offsetof(lua_State, base))); // L->base - CODEGEN_ASSERT(offsetof(Proto, code) == offsetof(Proto, k) + 8); + static_assert(offsetof(Proto, code) == offsetof(Proto, k) + sizeof(Proto::k)); build.ldp(rConstants, rCode, mem(x1, offsetof(Proto, k))); // proto->k, proto->code build.ldr(x9, mem(x0, offsetof(lua_State, ci))); // L->ci @@ -300,7 +300,7 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext) return false; } - // Set the offset at the begining so that functions in new blocks will not overlay the locations + // Set the offset at the beginning so that functions in new blocks will not overlay the locations // specified by the unwind information of the entry function unwind.setBeginOffset(build.getLabelOffset(entryLocations.prologueEnd)); diff --git a/CodeGen/src/CodeGenX64.cpp b/CodeGen/src/CodeGenX64.cpp index 0884fe68..233996ef 100644 --- a/CodeGen/src/CodeGenX64.cpp +++ b/CodeGen/src/CodeGenX64.cpp @@ -215,7 +215,7 @@ bool initHeaderFunctions(BaseCodeGenContext& codeGenContext) return false; } - // Set the offset at the begining so that functions in new blocks will not overlay the locations + // Set the offset at the beginning so that functions in new blocks will not overlay the locations // specified by the unwind information of the entry function unwind.setBeginOffset(build.getLabelOffset(entryLocations.prologueEnd)); diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 3d673f92..1b5a238d 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -12,6 +12,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCodegenDirectCompare) + namespace Luau { namespace CodeGen @@ -252,6 +254,31 @@ void IrBuilder::rebuildBytecodeBasicBlocks(Proto* proto) buildBytecodeBlocks(function, jumpTargets); } +static bool isDirectCompare(Proto* proto, const Instruction* pc, int i) +{ + // Matching the compiler sequence for generating 0 or 1 based on a comparison between values: + // LOP_JUMP** Lx + // [aux] + // LOADB Rx, 0 +1 + // Lx: LOADB Rx, 1 + if (i + 3 < proto->sizecode && LUAU_INSN_D(*pc) == 2) + { + const Instruction loadTrue = pc[2]; + const Instruction loadFalse = pc[3]; + + if (LUAU_INSN_OP(loadTrue) == LOP_LOADB && LUAU_INSN_OP(loadFalse) == LOP_LOADB) + { + bool sameTarget = LUAU_INSN_A(loadTrue) == LUAU_INSN_A(loadFalse); + bool zeroAndOne = LUAU_INSN_B(loadTrue) == 0 && LUAU_INSN_B(loadFalse) == 1; + bool correctJumps = LUAU_INSN_C(loadTrue) == 1 && LUAU_INSN_C(loadFalse) == 0; + + return sameTarget && zeroAndOne && correctJumps; + } + } + + return false; +} + void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) { switch (int(op)) @@ -354,15 +381,55 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstJumpX(*this, pc, i); break; case LOP_JUMPXEQKNIL: + if (FFlag::LuauCodegenDirectCompare && isDirectCompare(function.proto, pc, i)) + { + translateInstJumpxEqNilShortcut(*this, pc, i); + + // We complete the current instruction and the first LOADB, but we do not skip the second LOADB + // This is because the second LOADB was a jump target so there is a block prepared to handle it + cmdSkipTarget = i + 3; + break; + } + translateInstJumpxEqNil(*this, pc, i); break; case LOP_JUMPXEQKB: + if (FFlag::LuauCodegenDirectCompare && isDirectCompare(function.proto, pc, i)) + { + translateInstJumpxEqBShortcut(*this, pc, i); + + // We complete the current instruction and the first LOADB, but we do not skip the second LOADB + // This is because the second LOADB was a jump target so there is a block prepared to handle it + cmdSkipTarget = i + 3; + break; + } + translateInstJumpxEqB(*this, pc, i); break; case LOP_JUMPXEQKN: + if (FFlag::LuauCodegenDirectCompare && isDirectCompare(function.proto, pc, i)) + { + translateInstJumpxEqNShortcut(*this, pc, i); + + // We complete the current instruction and the first LOADB, but we do not skip the second LOADB + // This is because the second LOADB was a jump target so there is a block prepared to handle it + cmdSkipTarget = i + 3; + break; + } + translateInstJumpxEqN(*this, pc, i); break; case LOP_JUMPXEQKS: + if (FFlag::LuauCodegenDirectCompare && isDirectCompare(function.proto, pc, i)) + { + translateInstJumpxEqSShortcut(*this, pc, i); + + // We complete the current instruction and the first LOADB, but we do not skip the second LOADB + // This is because the second LOADB was a jump target so there is a block prepared to handle it + cmdSkipTarget = i + 3; + break; + } + translateInstJumpxEqS(*this, pc, i); break; case LOP_ADD: diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 2937a5f4..5217ad10 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -193,6 +193,10 @@ const char* getCmdName(IrCmd cmd) return "CMP_ANY"; case IrCmd::CMP_INT: return "CMP_INT"; + case IrCmd::CMP_TAG: + return "CMP_TAG"; + case IrCmd::CMP_SPLIT_TVALUE: + return "CMP_SPLIT_TVALUE"; case IrCmd::JUMP: return "JUMP"; case IrCmd::JUMP_IF_TRUTHY: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 3880349b..c7f0d42b 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAG(LuauCodeGenUnassignedBcTargetAbort) LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) +LUAU_FASTFLAG(LuauCodegenDirectCompare) namespace Luau { @@ -261,7 +262,7 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, , valueTracker(function) , exitHandlerMap(~0u) { - valueTracker.setRestoreCallack( + valueTracker.setRestoreCallback( this, [](void* context, IrInst& inst) { @@ -845,12 +846,12 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } else { - Label notbool, exit; + Label notBool, exit; // use the fact that NIL is the only value less than BOOLEAN to do two tag comparisons at once CODEGEN_ASSERT(LUA_TNIL == 0 && LUA_TBOOLEAN == 1); build.cmp(regOp(inst.a), LUA_TBOOLEAN); - build.b(ConditionA64::NotEqual, notbool); + build.b(ConditionA64::NotEqual, notBool); if (inst.b.kind == IrOpKind::Constant) build.mov(inst.regA64, intOp(inst.b) == 0 ? 1 : 0); @@ -860,7 +861,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.b(exit); // not boolean => result is true iff tag was nil - build.setLabel(notbool); + build.setLabel(notBool); build.cset(inst.regA64, ConditionA64::Less); build.setLabel(exit); @@ -916,6 +917,127 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) inst.regA64 = regs.takeReg(w0, index); break; } + case IrCmd::CMP_TAG: + { + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); + + IrCondition cond = conditionOp(inst.c); + CODEGEN_ASSERT(cond == IrCondition::Equal || cond == IrCondition::NotEqual); + RegisterA64 aReg = noreg; + RegisterA64 bReg = noreg; + + if (inst.a.kind == IrOpKind::Inst) + { + aReg = regOp(inst.a); + } + else if (inst.a.kind == IrOpKind::VmReg) + { + aReg = regs.allocTemp(KindA64::w); + AddressA64 addr = tempAddr(inst.a, offsetof(TValue, tt)); + build.ldr(aReg, addr); + } + else + { + CODEGEN_ASSERT(inst.a.kind == IrOpKind::Constant); + } + + if (inst.b.kind == IrOpKind::Inst) + { + bReg = regOp(inst.b); + } + else if (inst.b.kind == IrOpKind::VmReg) + { + bReg = regs.allocTemp(KindA64::w); + AddressA64 addr = tempAddr(inst.b, offsetof(TValue, tt)); + build.ldr(bReg, addr); + } + else + { + CODEGEN_ASSERT(inst.b.kind == IrOpKind::Constant); + } + + if (inst.a.kind == IrOpKind::Constant) + { + build.cmp(bReg, tagOp(inst.a)); + build.cset(inst.regA64, getInverseCondition(getConditionInt(cond))); + } + else if (inst.b.kind == IrOpKind::Constant) + { + build.cmp(aReg, tagOp(inst.b)); + build.cset(inst.regA64, getConditionInt(cond)); + } + else + { + build.cmp(aReg, bReg); + build.cset(inst.regA64, getConditionInt(cond)); + } + break; + } + case IrCmd::CMP_SPLIT_TVALUE: + { + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); + + // Second operand of this instruction must be a constant + // Without a constant type, we wouldn't know the correct way to compare the values at lowering time + CODEGEN_ASSERT(inst.b.kind == IrOpKind::Constant); + + IrCondition cond = conditionOp(inst.e); + CODEGEN_ASSERT(cond == IrCondition::Equal || cond == IrCondition::NotEqual); + + // Check tag equality first + RegisterA64 temp = regs.allocTemp(KindA64::w); + + if (inst.a.kind != IrOpKind::Constant) + { + build.cmp(regOp(inst.a), tagOp(inst.b)); + build.cset(temp, getConditionInt(cond)); + } + else + { + // Constant folding had to handle different constant tags + CODEGEN_ASSERT(tagOp(inst.a) == tagOp(inst.b)); + } + + if (tagOp(inst.b) == LUA_TBOOLEAN) + { + if (inst.c.kind == IrOpKind::Constant) + build.cmp(regOp(inst.d), intOp(inst.c)); // swapped arguments + else if (inst.d.kind == IrOpKind::Constant) + build.cmp(regOp(inst.c), intOp(inst.d)); + else + build.cmp(regOp(inst.c), regOp(inst.d)); + + build.cset(inst.regA64, getConditionInt(cond)); + } + else if (tagOp(inst.b) == LUA_TSTRING) + { + build.cmp(regOp(inst.c), regOp(inst.d)); + build.cset(inst.regA64, getConditionInt(cond)); + } + else if (tagOp(inst.b) == LUA_TNUMBER) + { + RegisterA64 temp1 = tempDouble(inst.c); + RegisterA64 temp2 = tempDouble(inst.d); + + build.fcmp(temp1, temp2); + build.cset(inst.regA64, getConditionFP(cond)); + } + else + { + CODEGEN_ASSERT(!"unsupported type tag in CMP_SPLIT_TVALUE"); + } + + if (inst.a.kind != IrOpKind::Constant) + { + if (cond == IrCondition::Equal) + build.and_(inst.regA64, inst.regA64, temp); + else + build.orr(inst.regA64, inst.regA64, temp); + } + break; + } case IrCmd::JUMP: if (inst.a.kind == IrOpKind::Undef || inst.a.kind == IrOpKind::VmExit) { @@ -964,18 +1086,67 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { RegisterA64 zr = noreg; - if (inst.a.kind == IrOpKind::Constant && tagOp(inst.a) == 0) - zr = regOp(inst.b); - else if (inst.b.kind == IrOpKind::Constant && tagOp(inst.b) == 0) - zr = regOp(inst.a); - else if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant) - build.cmp(regOp(inst.a), tagOp(inst.b)); - else if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Inst) - build.cmp(regOp(inst.a), regOp(inst.b)); - else if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Inst) - build.cmp(regOp(inst.b), tagOp(inst.a)); + if (FFlag::LuauCodegenDirectCompare) + { + RegisterA64 aReg = noreg; + RegisterA64 bReg = noreg; + + if (inst.a.kind == IrOpKind::Inst) + { + aReg = regOp(inst.a); + } + else if (inst.a.kind == IrOpKind::VmReg) + { + aReg = regs.allocTemp(KindA64::w); + AddressA64 addr = tempAddr(inst.a, offsetof(TValue, tt)); + build.ldr(aReg, addr); + } + else + { + CODEGEN_ASSERT(inst.a.kind == IrOpKind::Constant); + } + + if (inst.b.kind == IrOpKind::Inst) + { + bReg = regOp(inst.b); + } + else if (inst.b.kind == IrOpKind::VmReg) + { + bReg = regs.allocTemp(KindA64::w); + AddressA64 addr = tempAddr(inst.b, offsetof(TValue, tt)); + build.ldr(bReg, addr); + } + else + { + CODEGEN_ASSERT(inst.b.kind == IrOpKind::Constant); + } + + if (inst.a.kind == IrOpKind::Constant && tagOp(inst.a) == 0) + zr = bReg; + else if (inst.b.kind == IrOpKind::Constant && tagOp(inst.b) == 0) + zr = aReg; + else if (inst.b.kind == IrOpKind::Constant) + build.cmp(aReg, tagOp(inst.b)); + else if (inst.a.kind == IrOpKind::Constant) + build.cmp(bReg, tagOp(inst.a)); + else + build.cmp(aReg, bReg); + } else - CODEGEN_ASSERT(!"Unsupported instruction form"); + { + if (inst.a.kind == IrOpKind::Constant && tagOp(inst.a) == 0) + zr = regOp(inst.b); + else if (inst.b.kind == IrOpKind::Constant && tagOp(inst.b) == 0) + zr = regOp(inst.a); + else if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Constant) + build.cmp(regOp(inst.a), tagOp(inst.b)); + else if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Inst) + build.cmp(regOp(inst.a), regOp(inst.b)); + else if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Inst) + build.cmp(regOp(inst.b), tagOp(inst.a)); + else + CODEGEN_ASSERT(!"Unsupported instruction form"); + } if (isFallthroughBlock(blockOp(inst.d), next)) { @@ -1702,7 +1873,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1w = castReg(KindA64::w, temp1); RegisterA64 temp2 = regs.allocTemp(KindA64::x); - CODEGEN_ASSERT(offsetof(LuaNode, key.value) == offsetof(LuaNode, key) && kOffsetOfTKeyTagNext >= 8 && kOffsetOfTKeyTagNext < 16); + static_assert(offsetof(LuaNode, key.value) == offsetof(LuaNode, key) && kOffsetOfTKeyTagNext >= 8 && kOffsetOfTKeyTagNext < 16); build.ldp(temp1, temp2, mem(regOp(inst.a), offsetof(LuaNode, key))); // load key.value into temp1 and key.tt (alongside other bits) into temp2 build.ubfx(temp2, temp2, (kOffsetOfTKeyTagNext - 8) * 8, kTKeyTagBits); // .tt is right before .next, and 8 bytes are skipped by ldp build.cmp(temp2, LUA_TSTRING); @@ -1832,7 +2003,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1 = regs.allocTemp(KindA64::x); RegisterA64 temp2 = regs.allocTemp(KindA64::x); - CODEGEN_ASSERT(offsetof(global_State, totalbytes) == offsetof(global_State, GCthreshold) + 8); + static_assert(offsetof(global_State, totalbytes) == offsetof(global_State, GCthreshold) + sizeof(global_State::GCthreshold)); Label skip; build.ldp(temp1, temp2, mem(rGlobalState, offsetof(global_State, GCthreshold))); build.cmp(temp1, temp2); @@ -1938,7 +2109,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(temp1, mem(rState, offsetof(lua_State, openupval))); build.cbz(temp1, skip); - // ra <= L->openuval->v + // ra <= L->openupval->v build.ldr(temp1, mem(temp1, offsetof(UpVal, v))); build.add(temp2, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); build.cmp(temp2, temp1); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 5edb180f..ffe65077 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauCodeGenUnassignedBcTargetAbort) LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTFLAGVARIABLE(LuauCodeGenVBlendpdReorder) LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) +LUAU_FASTFLAG(LuauCodegenDirectCompare) namespace Luau { @@ -37,7 +38,7 @@ IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers, , valueTracker(function) , exitHandlerMap(~0u) { - valueTracker.setRestoreCallack( + valueTracker.setRestoreCallback( ®s, [](void* context, IrInst& inst) { @@ -832,7 +833,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // TODO: if we have a single user which is a STORE_INT, we are missing the opportunity to write directly to target inst.regX64 = regs.allocRegOrReuse(SizeX64::dword, index, {inst.a, inst.b}); - Label saveone, savezero, exit; + Label saveOne, saveZero, exit; if (inst.a.kind == IrOpKind::Constant) { @@ -842,29 +843,29 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) else { build.cmp(regOp(inst.a), LUA_TNIL); - build.jcc(ConditionX64::Equal, saveone); + build.jcc(ConditionX64::Equal, saveOne); build.cmp(regOp(inst.a), LUA_TBOOLEAN); - build.jcc(ConditionX64::NotEqual, savezero); + build.jcc(ConditionX64::NotEqual, saveZero); } if (inst.b.kind == IrOpKind::Constant) { // If value is 1, we fallthrough to storing 0 if (intOp(inst.b) == 0) - build.jmp(saveone); + build.jmp(saveOne); } else { build.cmp(regOp(inst.b), 0); - build.jcc(ConditionX64::Equal, saveone); + build.jcc(ConditionX64::Equal, saveOne); } - build.setLabel(savezero); + build.setLabel(saveZero); build.mov(inst.regX64, 0); build.jmp(exit); - build.setLabel(saveone); + build.setLabel(saveOne); build.mov(inst.regX64, 1); build.setLabel(exit); @@ -921,6 +922,112 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) inst.regX64 = regs.takeReg(eax, index); break; } + case IrCmd::CMP_TAG: + { + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + // Cannot reuse operand registers as a target because we have to modify it before the comparison + inst.regX64 = regs.allocReg(SizeX64::dword, index); + + // We are going to operate on byte register, those do not clear high bits on write + build.xor_(inst.regX64, inst.regX64); + + IrCondition cond = conditionOp(inst.c); + CODEGEN_ASSERT(cond == IrCondition::Equal || cond == IrCondition::NotEqual); + ConditionX64 condX64 = getConditionInt(cond); + + if (tagOp(inst.b) == LUA_TNIL && inst.a.kind == IrOpKind::Inst) + build.test(regOp(inst.a), regOp(inst.a)); + else + build.cmp(memRegTagOp(inst.a), tagOp(inst.b)); + + build.setcc(condX64, byteReg(inst.regX64)); + + break; + } + case IrCmd::CMP_SPLIT_TVALUE: + { + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + // Cannot reuse operand registers as a target because we have to modify it before the comparison + inst.regX64 = regs.allocReg(SizeX64::dword, index); + + // Second operand of this instruction must be a constant + // Without a constant type, we wouldn't know the correct way to compare the values at lowering time + CODEGEN_ASSERT(inst.b.kind == IrOpKind::Constant); + + // We are going to operate on byte registers, those do not clear high bits on write + build.xor_(inst.regX64, inst.regX64); + + IrCondition cond = conditionOp(inst.e); + CODEGEN_ASSERT(cond == IrCondition::Equal || cond == IrCondition::NotEqual); + + // Check tag equality first + ScopedRegX64 tmp1{regs, SizeX64::byte}; + + if (inst.a.kind != IrOpKind::Constant) + { + build.cmp(regOp(inst.a), tagOp(inst.b)); + build.setcc(getConditionInt(cond), byteReg(tmp1.reg)); + } + else + { + // Constant folding had to handle different constant tags + CODEGEN_ASSERT(tagOp(inst.a) == tagOp(inst.b)); + } + + if (tagOp(inst.b) == LUA_TBOOLEAN) + { + if (inst.c.kind == IrOpKind::Constant) + build.cmp(regOp(inst.d), intOp(inst.c)); // swapped arguments + else if (inst.d.kind == IrOpKind::Constant) + build.cmp(regOp(inst.c), intOp(inst.d)); + else + build.cmp(regOp(inst.c), regOp(inst.d)); + + build.setcc(getConditionInt(cond), byteReg(inst.regX64)); + } + else if (tagOp(inst.b) == LUA_TSTRING) + { + build.cmp(regOp(inst.c), regOp(inst.d)); + build.setcc(getConditionInt(cond), byteReg(inst.regX64)); + } + else if (tagOp(inst.b) == LUA_TNUMBER) + { + if (inst.c.kind == IrOpKind::Constant) + build.vucomisd(regOp(inst.d), memRegDoubleOp(inst.c)); // swapped arguments + else if (inst.d.kind == IrOpKind::Constant) + build.vucomisd(regOp(inst.c), memRegDoubleOp(inst.d)); + else + build.vucomisd(regOp(inst.c), regOp(inst.d)); + + ScopedRegX64 tmp2{regs, SizeX64::dword}; + + if (cond == IrCondition::Equal) + { + build.mov(tmp2.reg, 0); + build.setcc(ConditionX64::NotParity, byteReg(inst.regX64)); + build.cmov(ConditionX64::NotEqual, inst.regX64, tmp2.reg); + } + else + { + build.mov(tmp2.reg, 1); + build.setcc(ConditionX64::Parity, byteReg(inst.regX64)); + build.cmov(ConditionX64::NotEqual, inst.regX64, tmp2.reg); + } + } + else + { + CODEGEN_ASSERT(!"unsupported type tag in CMP_SPLIT_TVALUE"); + } + + if (inst.a.kind != IrOpKind::Constant) + { + if (cond == IrCondition::Equal) + build.and_(byteReg(inst.regX64), byteReg(tmp1.reg)); + else + build.or_(byteReg(inst.regX64), byteReg(tmp1.reg)); + } + break; + } case IrCmd::JUMP: jumpOrAbortOnUndef(inst.a, next); break; @@ -1688,7 +1795,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.test(tmp1.reg, tmp1.reg); build.jcc(ConditionX64::Zero, next); - // ra <= L->openuval->v + // ra <= L->openupval->v build.lea(tmp2.reg, addr[rBase + vmRegOp(inst.a) * sizeof(TValue)]); build.cmp(tmp2.reg, qword[tmp1.reg + offsetof(UpVal, v)]); build.jcc(ConditionX64::Above, next); diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 40f584ed..f981a194 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -12,6 +12,8 @@ #include "lstate.h" #include "ltm.h" +LUAU_FASTFLAG(LuauCodegenDirectCompare) + namespace Luau { namespace CodeGen @@ -268,11 +270,35 @@ void translateInstJumpxEqNil(IrBuilder& build, const Instruction* pc, int pcpos) build.beginBlock(next); } +void translateInstJumpxEqNilShortcut(IrBuilder& build, const Instruction* pc, int pcpos) +{ + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + int rr = LUAU_INSN_A(pc[2]); + + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + bool not_ = LUAU_INSN_AUX_NOT(aux) != 0; + + IrOp next = build.blockAtInst(pcpos + 4); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + + IrOp result = build.inst(IrCmd::CMP_TAG, ta, build.constTag(LUA_TNIL), build.cond(not_ ? IrCondition::NotEqual : IrCondition::Equal)); + + build.inst(IrCmd::STORE_TAG, build.vmReg(rr), build.constTag(LUA_TBOOLEAN)); + build.inst(IrCmd::STORE_INT, build.vmReg(rr), result); + build.inst(IrCmd::JUMP, next); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos) { int ra = LUAU_INSN_A(*pc); uint32_t aux = pc[1]; - bool not_ = (aux & 0x80000000) != 0; + bool not_ = LUAU_INSN_AUX_NOT(aux) != 0; IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); IrOp next = build.blockAtInst(pcpos + 2); @@ -285,7 +311,34 @@ void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos) build.beginBlock(checkValue); IrOp va = build.inst(IrCmd::LOAD_INT, build.vmReg(ra)); - build.inst(IrCmd::JUMP_CMP_INT, va, build.constInt(aux & 0x1), build.cond(IrCondition::Equal), not_ ? next : target, not_ ? target : next); + build.inst(IrCmd::JUMP_CMP_INT, va, build.constInt(LUAU_INSN_AUX_KB(aux)), build.cond(IrCondition::Equal), not_ ? next : target, not_ ? target : next); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + +void translateInstJumpxEqBShortcut(IrBuilder& build, const Instruction* pc, int pcpos) +{ + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + int rr = LUAU_INSN_A(pc[2]); + + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + bool not_ = LUAU_INSN_AUX_NOT(aux) != 0; + + IrOp next = build.blockAtInst(pcpos + 4); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + IrOp va = build.inst(IrCmd::LOAD_INT, build.vmReg(ra)); + IrOp vb = build.constInt(LUAU_INSN_AUX_KB(aux)); + + IrOp result = + build.inst(IrCmd::CMP_SPLIT_TVALUE, ta, build.constTag(LUA_TBOOLEAN), va, vb, build.cond(not_ ? IrCondition::NotEqual : IrCondition::Equal)); + + build.inst(IrCmd::STORE_TAG, build.vmReg(rr), build.constTag(LUA_TBOOLEAN)); + build.inst(IrCmd::STORE_INT, build.vmReg(rr), result); + build.inst(IrCmd::JUMP, next); // Fallthrough in original bytecode is implicit, so we start next internal block here if (build.isInternalBlock(next)) @@ -296,7 +349,7 @@ void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos) { int ra = LUAU_INSN_A(*pc); uint32_t aux = pc[1]; - bool not_ = (aux & 0x80000000) != 0; + bool not_ = LUAU_INSN_AUX_NOT(aux) != 0; IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); IrOp next = build.blockAtInst(pcpos + 2); @@ -310,7 +363,7 @@ void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos) IrOp va = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra)); CODEGEN_ASSERT(build.function.proto); - TValue protok = build.function.proto->k[aux & 0xffffff]; + TValue protok = build.function.proto->k[LUAU_INSN_AUX_KV(aux)]; CODEGEN_ASSERT(protok.tt == LUA_TNUMBER); IrOp vb = build.constDouble(protok.value.n); @@ -322,11 +375,43 @@ void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos) build.beginBlock(next); } +void translateInstJumpxEqNShortcut(IrBuilder& build, const Instruction* pc, int pcpos) +{ + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + int rr = LUAU_INSN_A(pc[2]); + + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + bool not_ = LUAU_INSN_AUX_NOT(aux) != 0; + + IrOp next = build.blockAtInst(pcpos + 4); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + IrOp va = build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(ra)); + + CODEGEN_ASSERT(build.function.proto); + TValue protok = build.function.proto->k[LUAU_INSN_AUX_KV(aux)]; + + CODEGEN_ASSERT(protok.tt == LUA_TNUMBER); + IrOp vb = build.constDouble(protok.value.n); + + IrOp result = + build.inst(IrCmd::CMP_SPLIT_TVALUE, ta, build.constTag(LUA_TNUMBER), va, vb, build.cond(not_ ? IrCondition::NotEqual : IrCondition::Equal)); + + build.inst(IrCmd::STORE_TAG, build.vmReg(rr), build.constTag(LUA_TBOOLEAN)); + build.inst(IrCmd::STORE_INT, build.vmReg(rr), result); + build.inst(IrCmd::JUMP, next); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos) { int ra = LUAU_INSN_A(*pc); uint32_t aux = pc[1]; - bool not_ = (aux & 0x80000000) != 0; + bool not_ = LUAU_INSN_AUX_NOT(aux) != 0; IrOp target = build.blockAtInst(pcpos + 1 + LUAU_INSN_D(*pc)); IrOp next = build.blockAtInst(pcpos + 2); @@ -337,7 +422,7 @@ void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos) build.beginBlock(checkValue); IrOp va = build.inst(IrCmd::LOAD_POINTER, build.vmReg(ra)); - IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmConst(aux & 0xffffff)); + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmConst(LUAU_INSN_AUX_KV(aux))); build.inst(IrCmd::JUMP_EQ_POINTER, va, vb, not_ ? next : target, not_ ? target : next); @@ -346,6 +431,33 @@ void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos) build.beginBlock(next); } +void translateInstJumpxEqSShortcut(IrBuilder& build, const Instruction* pc, int pcpos) +{ + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + int rr = LUAU_INSN_A(pc[2]); + + int ra = LUAU_INSN_A(*pc); + uint32_t aux = pc[1]; + bool not_ = LUAU_INSN_AUX_NOT(aux) != 0; + + IrOp next = build.blockAtInst(pcpos + 4); + + IrOp ta = build.inst(IrCmd::LOAD_TAG, build.vmReg(ra)); + IrOp va = build.inst(IrCmd::LOAD_POINTER, build.vmReg(ra)); + IrOp vb = build.inst(IrCmd::LOAD_POINTER, build.vmConst(LUAU_INSN_AUX_KV(aux))); + + IrOp result = + build.inst(IrCmd::CMP_SPLIT_TVALUE, ta, build.constTag(LUA_TSTRING), va, vb, build.cond(not_ ? IrCondition::NotEqual : IrCondition::Equal)); + + build.inst(IrCmd::STORE_TAG, build.vmReg(rr), build.constTag(LUA_TBOOLEAN)); + build.inst(IrCmd::STORE_INT, build.vmReg(rr), result); + build.inst(IrCmd::JUMP, next); + + // Fallthrough in original bytecode is implicit, so we start next internal block here + if (build.isInternalBlock(next)) + build.beginBlock(next); +} + static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc, IrOp opb, IrOp opc, int pcpos, TMS tm) { BytecodeTypes bcTypes = build.function.getBytecodeTypesAt(pcpos); diff --git a/CodeGen/src/IrTranslation.h b/CodeGen/src/IrTranslation.h index 662799b3..e5492049 100644 --- a/CodeGen/src/IrTranslation.h +++ b/CodeGen/src/IrTranslation.h @@ -30,9 +30,13 @@ void translateInstJumpIfEq(IrBuilder& build, const Instruction* pc, int pcpos, b void translateInstJumpIfCond(IrBuilder& build, const Instruction* pc, int pcpos, IrCondition cond); void translateInstJumpX(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstJumpxEqNil(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpxEqNilShortcut(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpxEqBShortcut(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpxEqNShortcut(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos); +void translateInstJumpxEqSShortcut(IrBuilder& build, const Instruction* pc, int pcpos); void translateInstBinary(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm); void translateInstBinaryK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm); void translateInstBinaryRK(IrBuilder& build, const Instruction* pc, int pcpos, TMS tm); diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index d8b194ab..84284327 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -17,6 +17,7 @@ #include LUAU_FASTFLAG(LuauCodeGenDirectBtest) +LUAU_FASTFLAG(LuauCodegenDirectCompare) namespace Luau { @@ -199,6 +200,8 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: case IrCmd::CMP_INT: + case IrCmd::CMP_TAG: + case IrCmd::CMP_SPLIT_TVALUE: return IrValueKind::Int; case IrCmd::JUMP: case IrCmd::JUMP_IF_TRUTHY: @@ -806,6 +809,59 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 substitute(function, inst, build.constInt(0)); } break; + case IrCmd::CMP_TAG: + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + substitute(function, inst, build.constInt(function.tagOp(inst.a) == function.tagOp(inst.b) ? 1 : 0)); + } + break; + case IrCmd::CMP_SPLIT_TVALUE: + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + + if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) + { + IrCondition cond = conditionOp(inst.e); + + if (cond == IrCondition::Equal) + { + if (function.tagOp(inst.a) != function.tagOp(inst.b)) + { + substitute(function, inst, build.constInt(0)); + } + else if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant) + { + if (function.tagOp(inst.a) == LUA_TBOOLEAN) + substitute(function, inst, build.constInt(compare(function.intOp(inst.c), function.intOp(inst.d), cond) ? 1 : 0)); + else if (function.tagOp(inst.a) == LUA_TNUMBER) + substitute(function, inst, build.constInt(compare(function.doubleOp(inst.c), function.doubleOp(inst.d), cond) ? 1 : 0)); + else + CODEGEN_ASSERT(!"unsupported type"); + } + } + else if (cond == IrCondition::NotEqual) + { + if (function.tagOp(inst.a) != function.tagOp(inst.b)) + { + substitute(function, inst, build.constInt(1)); + } + else if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant) + { + if (function.tagOp(inst.a) == LUA_TBOOLEAN) + substitute(function, inst, build.constInt(compare(function.intOp(inst.c), function.intOp(inst.d), cond) ? 1 : 0)); + else if (function.tagOp(inst.a) == LUA_TNUMBER) + substitute(function, inst, build.constInt(compare(function.doubleOp(inst.c), function.doubleOp(inst.d), cond) ? 1 : 0)); + else + CODEGEN_ASSERT(!"unsupported type"); + } + } + else + { + CODEGEN_ASSERT(!"unsupported condition"); + } + } + break; case IrCmd::JUMP_EQ_TAG: if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) { diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index 4a4d148f..94a0151c 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -16,7 +16,7 @@ IrValueLocationTracking::IrValueLocationTracking(IrFunction& function) vmRegValue.fill(kInvalidInstIdx); } -void IrValueLocationTracking::setRestoreCallack(void* context, void (*callback)(void* context, IrInst& inst)) +void IrValueLocationTracking::setRestoreCallback(void* context, void (*callback)(void* context, IrInst& inst)) { restoreCallbackCtx = context; restoreCallback = callback; @@ -99,8 +99,10 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::LOAD_FLOAT: case IrCmd::LOAD_TVALUE: case IrCmd::CMP_ANY: + case IrCmd::CMP_TAG: case IrCmd::JUMP_IF_TRUTHY: case IrCmd::JUMP_IF_FALSY: + case IrCmd::JUMP_EQ_TAG: case IrCmd::SET_TABLE: case IrCmd::SET_UPVALUE: case IrCmd::INTERRUPT: @@ -131,7 +133,6 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: - case IrCmd::JUMP_EQ_TAG: case IrCmd::JUMP_CMP_NUM: case IrCmd::FLOOR_NUM: case IrCmd::CEIL_NUM: diff --git a/CodeGen/src/IrValueLocationTracking.h b/CodeGen/src/IrValueLocationTracking.h index 5ce9c23f..4210d5df 100644 --- a/CodeGen/src/IrValueLocationTracking.h +++ b/CodeGen/src/IrValueLocationTracking.h @@ -14,7 +14,7 @@ struct IrValueLocationTracking { IrValueLocationTracking(IrFunction& function); - void setRestoreCallack(void* context, void (*callback)(void* context, IrInst& inst)); + void setRestoreCallback(void* context, void (*callback)(void* context, IrInst& inst)); void beforeInstLowering(IrInst& inst); void afterInstLowering(IrInst& inst, uint32_t instIdx); diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 6901bb73..dff7dc81 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -7,6 +7,8 @@ #include "Luau/IrUtils.h" #include "lua.h" +#include "lobject.h" +#include "lstate.h" #include #include @@ -22,6 +24,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) LUAU_FASTFLAG(LuauCodeGenDirectBtest) +LUAU_FASTFLAG(LuauCodegenDirectCompare) namespace Luau { @@ -59,6 +62,42 @@ struct NumberedInstruction uint32_t finishPos = 0; }; +static uint8_t tryGetTagForTypename(std::string_view name, bool forTypeof) +{ + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + + if (name == "nil") + return LUA_TNIL; + + if (name == "boolean") + return LUA_TBOOLEAN; + + if (name == "number") + return LUA_TNUMBER; + + // typeof(vector) can be changed by environment + // TODO: support the environment option + if (name == "vector" && !forTypeof) + return LUA_TVECTOR; + + if (name == "string") + return LUA_TSTRING; + + if (name == "table") + return LUA_TTABLE; + + if (name == "function") + return LUA_TFUNCTION; + + if (name == "thread") + return LUA_TTHREAD; + + if (name == "buffer") + return LUA_TBUFFER; + + return 0xff; +} + // Data we know about the current VM state struct ConstPropState { @@ -1337,8 +1376,80 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::CMP_ANY: state.invalidateUserCall(); break; + case IrCmd::CMP_TAG: + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + break; + case IrCmd::CMP_SPLIT_TVALUE: + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + + if (function.proto) + { + uint8_t tagA = inst.a.kind == IrOpKind::Constant ? function.tagOp(inst.a) : state.tryGetTag(inst.a); + uint8_t tagB = inst.b.kind == IrOpKind::Constant ? function.tagOp(inst.b) : state.tryGetTag(inst.b); + + // Try to find pattern like type(x) == 'tagname' or typeof(x) == 'tagname' + if (tagA == LUA_TSTRING && tagB == LUA_TSTRING && inst.c.kind == IrOpKind::Inst && inst.d.kind == IrOpKind::Inst) + { + const IrInst& lhs = function.instOp(inst.c); + const IrInst& rhs = function.instOp(inst.d); + + if (rhs.cmd == IrCmd::LOAD_POINTER && rhs.a.kind == IrOpKind::VmConst) + { + TValue name = function.proto->k[vmConstOp(rhs.a)]; + CODEGEN_ASSERT(name.tt == LUA_TSTRING); + std::string_view nameStr{svalue(&name), tsvalue(&name)->len}; + + if (int tag = tryGetTagForTypename(nameStr, lhs.cmd == IrCmd::GET_TYPEOF); tag != 0xff) + { + if (lhs.cmd == IrCmd::GET_TYPE) + { + replace(function, block, index, {IrCmd::CMP_TAG, lhs.a, build.constTag(tag), inst.e}); + foldConstants(build, function, block, index); + } + else if (lhs.cmd == IrCmd::GET_TYPEOF) + { + replace(function, block, index, {IrCmd::CMP_TAG, lhs.a, build.constTag(tag), inst.e}); + foldConstants(build, function, block, index); + } + } + } + } + } + break; case IrCmd::JUMP: + break; case IrCmd::JUMP_EQ_POINTER: + if (FFlag::LuauCodegenDirectCompare && function.proto) + { + // Try to find pattern like type(x) == 'tagname' or typeof(x) == 'tagname' + if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Inst) + { + const IrInst& lhs = function.instOp(inst.a); + const IrInst& rhs = function.instOp(inst.b); + + if (rhs.cmd == IrCmd::LOAD_POINTER && rhs.a.kind == IrOpKind::VmConst) + { + TValue name = function.proto->k[vmConstOp(rhs.a)]; + CODEGEN_ASSERT(name.tt == LUA_TSTRING); + std::string_view nameStr{svalue(&name), tsvalue(&name)->len}; + + if (int tag = tryGetTagForTypename(nameStr, lhs.cmd == IrCmd::GET_TYPEOF); tag != 0xff) + { + if (lhs.cmd == IrCmd::GET_TYPE) + { + replace(function, block, index, {IrCmd::JUMP_EQ_TAG, lhs.a, build.constTag(tag), inst.c, inst.d}); + foldConstants(build, function, block, index); + } + else if (lhs.cmd == IrCmd::GET_TYPEOF) + { + replace(function, block, index, {IrCmd::JUMP_EQ_TAG, lhs.a, build.constTag(tag), inst.c, inst.d}); + foldConstants(build, function, block, index); + } + } + } + } + } + break; case IrCmd::JUMP_SLOT_MATCH: case IrCmd::TABLE_LEN: break; diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index 7d90d475..8fce28f2 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -51,6 +51,7 @@ // # Bytecode type information history // Version 1: (from bytecode version 4) Type information for function signature. Currently supported. // Version 2: (from bytecode version 4) Type information for arguments, upvalues, locals and some temporaries. Currently supported. +// Version 3: (from bytecode version 5) Type information for userdata type names and their index mapping. Currently supported. // Bytecode opcode, part of the instruction header enum LuauOpcode @@ -437,6 +438,23 @@ enum LuauOpcode // E encoding: one signed 24-bit value #define LUAU_INSN_E(insn) (int32_t(insn) >> 8) +// Auxiliary AB: two 8-bit values, containing registers or small numbers +// Used in FASTCALL3 +#define LUAU_INSN_AUX_A(aux) ((aux) & 0xff) +#define LUAU_INSN_AUX_B(aux) (((aux) >> 8) & 0xff) + +// Auxiliary KV: unsigned 24-bit constant index +// Used in LOP_JUMPXEQK* instructions +#define LUAU_INSN_AUX_KV(aux) ((aux) & 0xffffff) + +// Auxiliary KB: 1-bit constant value +// Used in LOP_JUMPXEQKB instruction +#define LUAU_INSN_AUX_KB(aux) ((aux) & 0x1) + +// Auxiliary NOT: 1-bit negation flag +// Used in LOP_JUMPXEQK* instructions +#define LUAU_INSN_AUX_NOT(aux) ((aux) >> 31) + // Bytecode tags, used internally for bytecode encoded as a string enum LuauBytecodeTag { diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 4a46e10b..c5730d04 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -25,6 +25,7 @@ LUAU_FASTINTVARIABLE(LuauCompileLoopUnrollThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) +LUAU_FASTFLAG(LuauInterpStringConstFolding) namespace Luau { @@ -40,6 +41,23 @@ static const uint8_t kInvalidReg = 255; static const uint32_t kDefaultAllocPc = ~0u; +void escapeAndAppend(std::string& buffer, const char* str, size_t len) +{ + if (memchr(str, '%', len)) + { + for (size_t characterIndex = 0; characterIndex < len; ++characterIndex) + { + char character = str[characterIndex]; + buffer.push_back(character); + + if (character == '%') + buffer.push_back('%'); + } + } + else + buffer.append(str, len); +} + CompileError::CompileError(const Location& location, std::string message) : location(location) , message(std::move(message)) @@ -1781,31 +1799,68 @@ struct Compiler formatCapacity += string.size + std::count(string.data, string.data + string.size, '%'); } + size_t skippedSubExpr = 0; + if (FFlag::LuauInterpStringConstFolding) + { + for (size_t index = 0; index < expr->expressions.size; ++index) + { + const Constant* c = constants.find(expr->expressions.data[index]); + if (c && c->type == Constant::Type::Type_String) + { + formatCapacity += c->stringLength + std::count(c->valueString, c->valueString + c->stringLength, '%'); + skippedSubExpr++; + } + else + formatCapacity += 2; // "%*" + } + } + std::string formatString; formatString.reserve(formatCapacity); - size_t stringsLeft = expr->strings.size; + if (FFlag::LuauInterpStringConstFolding) + { + LUAU_ASSERT(expr->strings.size == expr->expressions.size + 1); + for (size_t idx = 0; idx < expr->strings.size; idx++) + { + AstArray string = expr->strings.data[idx]; + escapeAndAppend(formatString, string.data, string.size); - for (AstArray string : expr->strings) + if (idx < expr->expressions.size) + { + const Constant* c = constants.find(expr->expressions.data[idx]); + if (c && c->type == Constant::Type::Type_String) + escapeAndAppend(formatString, c->valueString, c->stringLength); + else + formatString += "%*"; + } + } + } + else { - if (memchr(string.data, '%', string.size)) + size_t stringsLeft = expr->strings.size; + + for (AstArray string : expr->strings) { - for (size_t characterIndex = 0; characterIndex < string.size; ++characterIndex) + if (memchr(string.data, '%', string.size)) { - char character = string.data[characterIndex]; - formatString.push_back(character); + for (size_t characterIndex = 0; characterIndex < string.size; ++characterIndex) + { + char character = string.data[characterIndex]; + formatString.push_back(character); - if (character == '%') - formatString.push_back('%'); + if (character == '%') + formatString.push_back('%'); + } } - } - else - formatString.append(string.data, string.size); + else + formatString.append(string.data, string.size); - stringsLeft--; + stringsLeft--; - if (stringsLeft > 0) - formatString += "%*"; + if (stringsLeft > 0) + formatString += "%*"; + } } size_t formatStringSize = formatString.size(); @@ -1824,12 +1879,26 @@ struct Compiler RegScope rs(this); - uint8_t baseReg = allocReg(expr, unsigned(2 + expr->expressions.size)); + uint8_t baseReg = allocReg(expr, unsigned(2 + expr->expressions.size - skippedSubExpr)); emitLoadK(baseReg, formatStringIndex); - for (size_t index = 0; index < expr->expressions.size; ++index) - compileExprTempTop(expr->expressions.data[index], uint8_t(baseReg + 2 + index)); + if (FFlag::LuauInterpStringConstFolding) + { + size_t skipped = 0; + for (size_t index = 0; index < expr->expressions.size; ++index) + { + AstExpr* subExpr = expr->expressions.data[index]; + const Constant* c = constants.find(subExpr); + if (!c || c->type != Constant::Type::Type_String) + compileExprTempTop(subExpr, uint8_t(baseReg + 2 + index - skipped)); + else + skipped++; + } + } + else + for (size_t index = 0; index < expr->expressions.size; ++index) + compileExprTempTop(expr->expressions.data[index], uint8_t(baseReg + 2 + index)); BytecodeBuilder::StringRef formatMethod = sref(AstName("format")); @@ -1839,7 +1908,7 @@ struct Compiler bytecode.emitABC(LOP_NAMECALL, baseReg, baseReg, uint8_t(BytecodeBuilder::getStringHash(formatMethod))); bytecode.emitAux(formatMethodIndex); - bytecode.emitABC(LOP_CALL, baseReg, uint8_t(expr->expressions.size + 2), 2); + bytecode.emitABC(LOP_CALL, baseReg, uint8_t(expr->expressions.size + 2 - skippedSubExpr), 2); bytecode.emitABC(LOP_MOVE, target, baseReg, 0); } @@ -3386,7 +3455,7 @@ struct Compiler resolveAssignConflicts(stat, vars, stat->values); // compute rhs into (mostly) fresh registers - // note that when the lhs assigment is a local, we evaluate directly into that register + // note that when the lhs assignment is a local, we evaluate directly into that register // this is possible because resolveAssignConflicts renamed conflicting locals into temporaries // after this, vars[i].valueReg is set to a register with the value for *all* vars, but some have already been assigned for (size_t i = 0; i < stat->vars.size && i < stat->values.size; ++i) diff --git a/Compiler/src/ConstantFolding.cpp b/Compiler/src/ConstantFolding.cpp index 4790ffd7..e0dd1e05 100644 --- a/Compiler/src/ConstantFolding.cpp +++ b/Compiler/src/ConstantFolding.cpp @@ -7,7 +7,8 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauStringConstFolding) +LUAU_FASTFLAGVARIABLE(LuauStringConstFolding2) +LUAU_FASTFLAGVARIABLE(LuauInterpStringConstFolding) namespace Luau { @@ -286,17 +287,24 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l break; case AstExprBinary::Concat: - if (FFlag::LuauStringConstFolding) + if (FFlag::LuauStringConstFolding2) if (la.type == Constant::Type_String && ra.type == Constant::Type_String) { result.type = Constant::Type_String; result.stringLength = la.stringLength + ra.stringLength; - std::string tmp; - tmp.reserve(result.stringLength + 1); - tmp.append(la.valueString, la.stringLength); - tmp.append(ra.valueString, ra.stringLength); - AstName name = names.getOrAdd(tmp.c_str(), result.stringLength); - result.valueString = name.value; + if (la.stringLength == 0) + result.valueString = ra.valueString; + else if (ra.stringLength == 0) + result.valueString = la.valueString; + else + { + std::string tmp; + tmp.reserve(result.stringLength + 1); + tmp.append(la.valueString, la.stringLength); + tmp.append(ra.valueString, ra.stringLength); + AstName name = names.getOrAdd(tmp.c_str(), result.stringLength); + result.valueString = name.value; + } } break; @@ -367,6 +375,48 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l } } +static void foldInterpString(Constant& result, AstExprInterpString* expr, DenseHashMap& constants, AstNameTable& names) +{ + LUAU_ASSERT(expr->strings.size == expr->expressions.size + 1); + size_t resultLength = 0; + for (size_t index = 0; index < expr->strings.size; ++index) + { + resultLength += expr->strings.data[index].size; + if (index < expr->expressions.size) + { + const Constant* c = constants.find(expr->expressions.data[index]); + LUAU_ASSERT(c != nullptr && c->type == Constant::Type::Type_String); + resultLength += c->stringLength; + } + } + result.type = Constant::Type_String; + result.stringLength = resultLength; + + if (resultLength == 0) + { + result.valueString = ""; + return; + } + + std::string tmp; + tmp.reserve(resultLength); + + for (size_t index = 0; index < expr->strings.size; ++index) + { + AstArray string = expr->strings.data[index]; + tmp.append(string.data, string.size); + if (index < expr->expressions.size) + { + const Constant* c = constants.find(expr->expressions.data[index]); + tmp.append(c->valueString, c->stringLength); + } + } + result.type = Constant::Type_String; + result.stringLength = resultLength; + AstName name = names.getOrAdd(tmp.c_str(), resultLength); + result.valueString = name.value; +} + struct ConstantVisitor : AstVisitor { DenseHashMap& constants; @@ -555,8 +605,21 @@ struct ConstantVisitor : AstVisitor } else if (AstExprInterpString* expr = node->as()) { - for (AstExpr* expression : expr->expressions) - analyze(expression); + if (FFlag::LuauInterpStringConstFolding) + { + bool onlyConstantSubExpr = true; + for (AstExpr* expression : expr->expressions) + if (analyze(expression).type != Constant::Type_String) + onlyConstantSubExpr = false; + + if (onlyConstantSubExpr) + foldInterpString(result, expr, constants, names); + } + else + { + for (AstExpr* expression : expr->expressions) + analyze(expression); + } } else { diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 19001626..626d8a85 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -294,9 +294,9 @@ typedef struct Proto uint8_t maxstacksize; uint8_t flags; - TValue* k; // constants used by the function Instruction* code; // function bytecode + struct Proto** p; // functions defined inside the function const Instruction* codeentry; diff --git a/VM/src/lstate.h b/VM/src/lstate.h index 77c674a6..ef4e9f28 100644 --- a/VM/src/lstate.h +++ b/VM/src/lstate.h @@ -180,9 +180,9 @@ typedef struct global_State GCObject* grayagain; // list of objects to be traversed atomically GCObject* weak; // list of weak tables (to be cleared) - size_t GCthreshold; // when totalbytes > GCthreshold, run GC step size_t totalbytes; // number of bytes currently allocated + int gcgoal; // see LUAI_GCGOAL int gcstepmul; // see LUAI_GCSTEPMUL int gcstepsize; // see LUAI_GCSTEPSIZE diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index 67765678..dc10e65f 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -2904,8 +2904,8 @@ static void luau_execute(lua_State* L) int skip = LUAU_INSN_C(insn) - 1; uint32_t aux = *pc++; TValue* arg1 = VM_REG(LUAU_INSN_B(insn)); - TValue* arg2 = VM_REG(aux & 0xff); - TValue* arg3 = VM_REG((aux >> 8) & 0xff); + TValue* arg2 = VM_REG(LUAU_INSN_AUX_A(aux)); + TValue* arg3 = VM_REG(LUAU_INSN_AUX_B(aux)); LUAU_ASSERT(unsigned(pc - cl->l.p->code + skip) < unsigned(cl->l.p->sizecode)); @@ -2980,7 +2980,7 @@ static void luau_execute(lua_State* L) StkId ra = VM_REG(LUAU_INSN_A(insn)); static_assert(LUA_TNIL == 0, "we expect type-1 to be negative iff type is nil"); - // condition is equivalent to: int(ttisnil(ra)) != (aux >> 31) + // condition is equivalent to: int(ttisnil(ra)) != LUAU_INSN_AUX_NOT(aux) pc += int((ttype(ra) - 1) ^ aux) < 0 ? LUAU_INSN_D(insn) : 1; LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); VM_NEXT(); @@ -2992,7 +2992,7 @@ static void luau_execute(lua_State* L) uint32_t aux = *pc; StkId ra = VM_REG(LUAU_INSN_A(insn)); - pc += int(ttisboolean(ra) && bvalue(ra) == int(aux & 1)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1; + pc += int(ttisboolean(ra) && bvalue(ra) == int(LUAU_INSN_AUX_KB(aux))) != LUAU_INSN_AUX_NOT(aux) ? LUAU_INSN_D(insn) : 1; LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); VM_NEXT(); } @@ -3002,18 +3002,18 @@ static void luau_execute(lua_State* L) Instruction insn = *pc++; uint32_t aux = *pc; StkId ra = VM_REG(LUAU_INSN_A(insn)); - TValue* kv = VM_KV(aux & 0xffffff); + TValue* kv = VM_KV(LUAU_INSN_AUX_KV(aux)); LUAU_ASSERT(ttisnumber(kv)); #if defined(__aarch64__) // On several ARM chips (Apple M1/M2, Neoverse N1), comparing the result of a floating-point comparison is expensive, and a branch // is much cheaper; on some 32-bit ARM chips (Cortex A53) the performance is about the same so we prefer less branchy variant there - if (aux >> 31) + if (LUAU_INSN_AUX_NOT(aux)) pc += !(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1; else pc += (ttisnumber(ra) && nvalue(ra) == nvalue(kv)) ? LUAU_INSN_D(insn) : 1; #else - pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1; + pc += int(ttisnumber(ra) && nvalue(ra) == nvalue(kv)) != LUAU_INSN_AUX_NOT(aux) ? LUAU_INSN_D(insn) : 1; #endif LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); VM_NEXT(); @@ -3024,10 +3024,10 @@ static void luau_execute(lua_State* L) Instruction insn = *pc++; uint32_t aux = *pc; StkId ra = VM_REG(LUAU_INSN_A(insn)); - TValue* kv = VM_KV(aux & 0xffffff); + TValue* kv = VM_KV(LUAU_INSN_AUX_KV(aux)); LUAU_ASSERT(ttisstring(kv)); - pc += int(ttisstring(ra) && gcvalue(ra) == gcvalue(kv)) != (aux >> 31) ? LUAU_INSN_D(insn) : 1; + pc += int(ttisstring(ra) && gcvalue(ra) == gcvalue(kv)) != LUAU_INSN_AUX_NOT(aux) ? LUAU_INSN_D(insn) : 1; LUAU_ASSERT(unsigned(pc - cl->l.p->code) < unsigned(cl->l.p->sizecode)); VM_NEXT(); } diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 182d48e0..f2210c78 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -23,8 +23,9 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauRecursionLimit) -LUAU_FASTFLAG(LuauStringConstFolding) +LUAU_FASTFLAG(LuauStringConstFolding2) LUAU_FASTFLAG(LuauCompileTypeofFold) +LUAU_FASTFLAG(LuauInterpStringConstFolding) using namespace Luau; @@ -1415,11 +1416,11 @@ TEST_CASE("InterpStringWithNoExpressions") TEST_CASE("InterpStringZeroCost") { CHECK_EQ( - "\n" + compileFunction0(R"(local _ = `hello, {"world"}!`)"), + "\n" + compileFunction0(R"(local _ = `hello, {42}!`)"), R"( LOADK R1 K0 ['hello, %*!'] -LOADK R3 K1 ['world'] -NAMECALL R1 R1 K2 ['format'] +LOADN R3 42 +NAMECALL R1 R1 K1 ['format'] CALL R1 2 1 MOVE R0 R1 RETURN R0 0 @@ -1432,7 +1433,7 @@ TEST_CASE("InterpStringRegisterCleanup") CHECK_EQ( "\n" + compileFunction0(R"( local a, b, c = nil, "um", "uh oh" - a = `foo{"bar"}` + a = `foo{42}` print(a) )"), @@ -1441,11 +1442,11 @@ LOADNIL R0 LOADK R1 K0 ['um'] LOADK R2 K1 ['uh oh'] LOADK R3 K2 ['foo%*'] -LOADK R5 K3 ['bar'] -NAMECALL R3 R3 K4 ['format'] +LOADN R5 42 +NAMECALL R3 R3 K3 ['format'] CALL R3 2 1 MOVE R0 R3 -GETIMPORT R3 6 [print] +GETIMPORT R3 5 [print] MOVE R4 R0 CALL R3 1 0 RETURN R0 0 @@ -1459,6 +1460,51 @@ TEST_CASE("InterpStringRegisterLimit") CHECK_THROWS_AS(compileFunction0(("local a = `" + rep("{1}", 253) + "`").c_str()), std::exception); } +TEST_CASE("InterpStringConstFold") +{ + ScopedFastFlag sff{FFlag::LuauInterpStringConstFolding, true}; + + CHECK_EQ( + "\n" + compileFunction0(R"(local empty = ""; return `{empty}`)"), + R"( +LOADK R0 K0 [''] +RETURN R0 1 +)" + ); + + CHECK_EQ( + "\n" + compileFunction0(R"(local world = "world"; return `hello, {world}!`)"), + R"( +LOADK R0 K0 ['hello, world!'] +RETURN R0 1 +)" + ); + + CHECK_EQ( + "\n" + compileFunction0(R"(local not_string = 42; local world = "world"; return `hello, {world} {not_string}!`)"), + R"( +LOADK R1 K0 ['hello, world %*!'] +LOADN R3 42 +NAMECALL R1 R1 K1 ['format'] +CALL R1 2 1 +MOVE R0 R1 +RETURN R0 1 +)" + ); + + CHECK_EQ( + "\n" + compileFunction0(R"(local not_string = 42; local str = "%s%s%s"; return `hello, {str} {not_string}!`)"), + R"( +LOADK R1 K0 ['hello, %%s%%s%%s %*!'] +LOADN R3 42 +NAMECALL R1 R1 K1 ['format'] +CALL R1 2 1 +MOVE R0 R1 +RETURN R0 1 +)" + ); +} + TEST_CASE("ConstantFoldArith") { CHECK_EQ("\n" + compileFunction0("return 10 + 2"), R"( @@ -9403,7 +9449,32 @@ RETURN R1 7 TEST_CASE("ConstStringFolding") { - ScopedFastFlag sff{FFlag::LuauStringConstFolding, true}; + ScopedFastFlag sff{FFlag::LuauStringConstFolding2, true}; + + CHECK_EQ( + "\n" + compileFunction(R"(return "" .. "")", 0, 2), + R"( +LOADK R0 K0 [''] +RETURN R0 1 +)" + ); + + CHECK_EQ( + "\n" + compileFunction(R"(return "a" .. "")", 0, 2), + R"( +LOADK R0 K0 ['a'] +RETURN R0 1 +)" + ); + + CHECK_EQ( + "\n" + compileFunction(R"(return "" .. "a")", 0, 2), + R"( +LOADK R0 K0 ['a'] +RETURN R0 1 +)" + ); + CHECK_EQ( "\n" + compileFunction(R"(local hello = "hello"; local world = "world"; return hello .. " " .. world)", 0, 2), R"( diff --git a/tests/ConstraintSolver.test.cpp b/tests/ConstraintSolver.test.cpp index b83fb345..67f54feb 100644 --- a/tests/ConstraintSolver.test.cpp +++ b/tests/ConstraintSolver.test.cpp @@ -5,6 +5,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauScopedSeenSetInLookupTableProp); using namespace Luau; @@ -61,4 +62,31 @@ TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "proper_let_generalization") CHECK("(unknown) -> number" == toString(idType)); } +TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "table_prop_access_diamond") +{ + ScopedFastFlag sff(FFlag::LuauScopedSeenSetInLookupTableProp, true); + + CheckResult result = check(R"( + export type ItemDetails = { Id: number } + + export type AssetDetails = ItemDetails & {} + export type BundleDetails = ItemDetails & {} + + export type CatalogPage = { AssetDetails | BundleDetails } + + local function isRestricted(item: number) end + + -- Clear all item tiles and create new ones for the items in the specified page + local function displayPage(catalogPage: CatalogPage) + for _, itemDetails in catalogPage do + if isRestricted(itemDetails.Id) then + continue + end + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index b0f8b650..fc214376 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -36,6 +36,7 @@ LUAU_FASTFLAG(LuauFragmentAutocompleteTakesInnermostRefinement) LUAU_FASTFLAG(LuauSuggestHotComments) LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) +LUAU_FASTFLAG(LuauForInRangesConsiderInLocation) static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { @@ -4549,6 +4550,32 @@ local function whatever() end ); } +TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "in_place_edit_of_for_loop_before_in_keyword_returns_fragment_starting_from_for") +{ + ScopedFastFlag sff{FFlag::LuauForInRangesConsiderInLocation, true}; + std::string source = R"( +local x = {} +for i, value in x do + print(i) +end +)"; + + std::string dest = R"( +local x = {} +for @1, value in x do + print(i) +end +)"; + autocompleteFragmentInBothSolvers( + source, + dest, + '1', + [](auto& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + } + ); +} // NOLINTEND(bugprone-unchecked-optional-access) diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 2ae747fc..ac5faff9 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauCompileVectorLerp) LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) LUAU_FASTFLAG(LuauCodeGenVectorLerp) LUAU_FASTFLAG(LuauCodeGenFMA) +LUAU_FASTFLAG(LuauCodegenDirectCompare) static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant) { @@ -475,13 +476,13 @@ TEST_CASE("VectorLerp") }; if (FFlag::LuauCodeGenFMA) { - CHECK_EQ( - "\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3lerp(a: vector, b: vector, t: number) return vector.lerp(a, b, t) end )"), - R"( + R"( ; function vec3lerp($arg0, $arg1, $arg2) line 2 bb_0: CHECK_TAG R0, tvector, exit(entry) @@ -509,13 +510,13 @@ end } else { - CHECK_EQ( - "\n" + getCodegenAssembly(R"( + CHECK_EQ( + "\n" + getCodegenAssembly(R"( local function vec3lerp(a: vector, b: vector, t: number) return vector.lerp(a, b, t) end )"), - R"( + R"( ; function vec3lerp($arg0, $arg1, $arg2) line 2 bb_0: CHECK_TAG R0, tvector, exit(entry) @@ -637,6 +638,372 @@ end ); } +TEST_CASE("StringCompare") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a) + return a == "test" +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_bytecode_0: + %0 = LOAD_TAG R0 + %1 = LOAD_POINTER R0 + %2 = LOAD_POINTER K0 ('test') + %3 = CMP_SPLIT_TVALUE %0, tstring, %1, %2, eq + STORE_TAG R1, tboolean + STORE_INT R1, %3 + JUMP bb_bytecode_2 +bb_bytecode_2: + INTERRUPT 4u + RETURN R1, 1i +)" + ); +} + +TEST_CASE("StringCompareAnnotated") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a: string) + return a == "test" +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_0: + CHECK_TAG R0, tstring, exit(entry) + JUMP bb_4 +bb_4: + JUMP bb_bytecode_1 +bb_bytecode_1: + %5 = LOAD_POINTER R0 + %6 = LOAD_POINTER K0 ('test') + %7 = CMP_SPLIT_TVALUE tstring, tstring, %5, %6, eq + STORE_TAG R1, tboolean + STORE_INT R1, %7 + JUMP bb_bytecode_3 +bb_bytecode_3: + INTERRUPT 4u + RETURN R1, 1i +)" + ); +} + +TEST_CASE("NilCompare") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a) + return a == nil +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_bytecode_0: + %0 = LOAD_TAG R0 + %1 = CMP_TAG %0, tnil, eq + STORE_TAG R1, tboolean + STORE_INT R1, %1 + JUMP bb_bytecode_2 +bb_bytecode_2: + INTERRUPT 4u + RETURN R1, 1i +)" + ); +} + +TEST_CASE("BooleanCompare") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a) + return { a == true, a == false, a ~= true, a ~= false } +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_bytecode_0: + SET_SAVEDPC 1u + %1 = NEW_TABLE 4u, 0u + STORE_POINTER R1, %1 + STORE_TAG R1, ttable + CHECK_GC + %5 = LOAD_TAG R0 + %6 = LOAD_INT R0 + %7 = CMP_SPLIT_TVALUE %5, tboolean, %6, 1i, eq + STORE_TAG R2, tboolean + STORE_INT R2, %7 + JUMP bb_bytecode_2 +bb_bytecode_2: + %14 = LOAD_TAG R0 + %15 = LOAD_INT R0 + %16 = CMP_SPLIT_TVALUE %14, tboolean, %15, 0i, eq + STORE_TAG R3, tboolean + STORE_INT R3, %16 + JUMP bb_bytecode_4 +bb_bytecode_4: + %23 = LOAD_TAG R0 + %24 = LOAD_INT R0 + %25 = CMP_SPLIT_TVALUE %23, tboolean, %24, 1i, not_eq + STORE_TAG R4, tboolean + STORE_INT R4, %25 + JUMP bb_bytecode_6 +bb_bytecode_6: + %32 = LOAD_TAG R0 + %33 = LOAD_INT R0 + %34 = CMP_SPLIT_TVALUE %32, tboolean, %33, 0i, not_eq + STORE_TAG R5, tboolean + STORE_INT R5, %34 + JUMP bb_bytecode_8 +bb_bytecode_8: + SETLIST 18u, R1, R2, 4i, 1u, 4u + INTERRUPT 20u + RETURN R1, 1i +)" + ); +} + +TEST_CASE("NumberCompare") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a) + return { a == 4.0, a ~= 3.0 } +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_bytecode_0: + SET_SAVEDPC 1u + %1 = NEW_TABLE 2u, 0u + STORE_POINTER R1, %1 + STORE_TAG R1, ttable + CHECK_GC + %5 = LOAD_TAG R0 + %6 = LOAD_DOUBLE R0 + %7 = CMP_SPLIT_TVALUE %5, tnumber, %6, 4, eq + STORE_TAG R2, tboolean + STORE_INT R2, %7 + JUMP bb_bytecode_2 +bb_bytecode_2: + %14 = LOAD_TAG R0 + %15 = LOAD_DOUBLE R0 + %16 = CMP_SPLIT_TVALUE %14, tnumber, %15, 3, not_eq + STORE_TAG R3, tboolean + STORE_INT R3, %16 + JUMP bb_bytecode_4 +bb_bytecode_4: + SETLIST 10u, R1, R2, 2i, 1u, 2u + INTERRUPT 12u + RETURN R1, 1i +)" + ); +} + +TEST_CASE("TypeCompare") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a) + return type(a) == "number" +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_bytecode_0: + CHECK_SAFE_ENV exit(1) + %1 = LOAD_TAG R0 + %2 = GET_TYPE %1 + STORE_POINTER R2, %2 + STORE_TAG R2, tstring + %8 = CMP_TAG %1, tnumber, eq + STORE_TAG R1, tboolean + STORE_INT R1, %8 + JUMP bb_bytecode_2 +bb_bytecode_2: + INTERRUPT 9u + RETURN R1, 1i +)" + ); +} + +TEST_CASE("TypeofCompare") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a) + return typeof(a) == "number" +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_bytecode_0: + CHECK_SAFE_ENV exit(1) + %1 = GET_TYPEOF R0 + STORE_POINTER R2, %1 + STORE_TAG R2, tstring + %7 = CMP_TAG R0, tnumber, eq + STORE_TAG R1, tboolean + STORE_INT R1, %7 + JUMP bb_bytecode_2 +bb_bytecode_2: + INTERRUPT 9u + RETURN R1, 1i +)" + ); +} + +TEST_CASE("TypeofCompareCustom") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a) + return typeof(a) == "User" +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_bytecode_0: + CHECK_SAFE_ENV exit(1) + %1 = GET_TYPEOF R0 + STORE_POINTER R2, %1 + STORE_TAG R2, tstring + %6 = LOAD_POINTER K2 ('User') + %7 = CMP_SPLIT_TVALUE tstring, tstring, %1, %6, eq + STORE_TAG R1, tboolean + STORE_INT R1, %7 + JUMP bb_bytecode_2 +bb_bytecode_2: + INTERRUPT 9u + RETURN R1, 1i +)" + ); +} + +TEST_CASE("TypeCondition") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a, b) + if type(a) == "number" then + return a + b + end + return nil +end +)" + ), + R"( +; function foo($arg0, $arg1) line 2 +bb_bytecode_0: + CHECK_SAFE_ENV exit(1) + %1 = LOAD_TAG R0 + %2 = GET_TYPE %1 + STORE_POINTER R2, %2 + STORE_TAG R2, tstring + JUMP bb_4 +bb_4: + %7 = LOAD_POINTER R2 + %8 = LOAD_POINTER K2 ('number') + JUMP_EQ_POINTER %7, %8, bb_3, bb_bytecode_1 +bb_3: + CHECK_TAG R0, tnumber, bb_fallback_5 + CHECK_TAG R1, tnumber, bb_fallback_5 + %14 = LOAD_DOUBLE R0 + %16 = ADD_NUM %14, R1 + STORE_DOUBLE R2, %16 + STORE_TAG R2, tnumber + JUMP bb_6 +bb_6: + INTERRUPT 8u + RETURN R2, 1i +bb_bytecode_1: + STORE_TAG R2, tnil + INTERRUPT 10u + RETURN R2, 1i +)" + ); +} + +TEST_CASE("AssertTypeGuard") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly( + R"( +local function foo(a) + assert(type(a) == "number") + return a * 2 +end +)" + ), + R"( +; function foo($arg0) line 2 +bb_bytecode_0: + CHECK_SAFE_ENV exit(1) + %1 = LOAD_TAG R0 + %2 = GET_TYPE %1 + STORE_POINTER R3, %2 + STORE_TAG R3, tstring + %8 = CMP_TAG %1, tnumber, eq + STORE_TAG R2, tboolean + STORE_INT R2, %8 + JUMP bb_bytecode_2 +bb_bytecode_2: + CHECK_TRUTHY tboolean, R2, exit(10) + JUMP bb_5 +bb_5: + CHECK_TAG R0, tnumber, bb_fallback_6 + %28 = LOAD_DOUBLE R0 + %29 = ADD_NUM %28, %28 + STORE_DOUBLE R1, %29 + STORE_TAG R1, tnumber + JUMP bb_7 +bb_7: + INTERRUPT 14u + RETURN R1, 1i +)" + ); +} + TEST_CASE("VectorConstantTag") { CHECK_EQ( diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index fbae3183..ec0e66a6 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -16,7 +16,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSolverAgnosticStringification) using namespace Luau; @@ -1266,7 +1266,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}, + {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}, }; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index a63ad778..00ca0a66 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -10,6 +10,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) +LUAU_FASTFLAG(LuauSimplifyRefinementOfReadOnlyProperty) LUAU_DYNAMIC_FASTINT(LuauSimplificationComplexityLimit) namespace @@ -91,11 +92,11 @@ struct SimplifyFixture : Fixture return bool(get(follow(a))); } - TypeId mkTable(std::map propTypes) + TypeId mkTable(std::map propTypes) { TableType::Props props; - for (const auto& [name, ty] : propTypes) - props[name] = Property{ty}; + for (const auto& [name, prop] : propTypes) + props[name] = prop; return arena->addType(TableType{props, {}, TypeLevel{}, TableState::Sealed}); } @@ -643,4 +644,43 @@ TEST_CASE_FIXTURE(SimplifyFixture, "(error | string) & any") CHECK("*error-type* | string" == toString(res)); } +TEST_CASE_FIXTURE(SimplifyFixture, "{ x: number, y: number } & { x: unknown }") +{ + ScopedFastFlag sff{FFlag::LuauSimplifyRefinementOfReadOnlyProperty, true}; + + TypeId leftTy = mkTable({{"x", builtinTypes->numberType}, {"y", builtinTypes->numberType}}); + TypeId rightTy = mkTable({{"x", Property::rw(builtinTypes->unknownType)}}); + + CHECK(leftTy == intersect(leftTy, rightTy)); +} + +TEST_CASE_FIXTURE(SimplifyFixture, "{ x: number, y: number } & { read x: unknown }") +{ + ScopedFastFlag sff{FFlag::LuauSimplifyRefinementOfReadOnlyProperty, true}; + + TypeId leftTy = mkTable({{"x", builtinTypes->numberType}, {"y", builtinTypes->numberType}}); + TypeId rightTy = mkTable({{"x", Property::readonly(builtinTypes->unknownType)}}); + + CHECK(leftTy == intersect(leftTy, rightTy)); +} + +TEST_CASE_FIXTURE(SimplifyFixture, "{ read x: Child } & { x: Parent }") +{ + ScopedFastFlag sff{FFlag::LuauSimplifyRefinementOfReadOnlyProperty, true}; + + createSomeExternTypes(getFrontend()); + + TypeId parentTy = getFrontend().globals.globalScope->exportedTypeBindings["Parent"].type; + REQUIRE(parentTy); + + TypeId childTy = getFrontend().globals.globalScope->exportedTypeBindings["Child"].type; + REQUIRE(childTy); + + TypeId leftTy = mkTable({{"x", Property::readonly(childTy)}}); + TypeId rightTy = mkTable({{"x", parentTy}}); + + // TODO: This could be { read x: Child, write x: Parent } + CHECK("{ read x: Child } & { x: Parent }" == toString(intersect(leftTy, rightTy))); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 943d5e0b..98097acf 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -16,7 +16,7 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) @@ -1403,7 +1403,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "({ x: T }) -> T <: ({ method: ({ x: T } TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance") { - ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; TypeId longTy = arena.addType( diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 230a4887..c0068633 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -1888,10 +1888,7 @@ TEST_CASE_FIXTURE(TFFixture, "reduce_degenerate_refinement") TEST_CASE_FIXTURE(Fixture, "generic_type_functions_should_not_get_stuck_or") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauNoMoreComparisonTypeFunctions, false} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNoMoreComparisonTypeFunctions, false}}; CheckResult result = check(R"( local function init(data) diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index 22a0d2ed..eb24d994 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -12,6 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTINT(LuauTypeInferRecursionLimit) +LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) TEST_SUITE_BEGIN("DefinitionTests"); @@ -575,6 +576,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauNoMoreComparisonTypeFunctions, true}, + {FFlag::LuauNoOrderingTypeFunctions, true}, }; CheckResult result = check(R"( @@ -596,10 +598,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func") end )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - // There are three errors in the above snippet, but they should all be where - // clause needed errors. - for (const auto& e : result.errors) - CHECK(get(e)); + auto err1 = get(result.errors[0]); + REQUIRE(err1); + CHECK_EQ(err1->suggestedToAnnotate, "item"); + CHECK_EQ(err1->op, AstExprBinary::Op::CompareLe); } TEST_CASE_FIXTURE(Fixture, "vector3_overflow") diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 96f76a53..7ad0928f 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -30,8 +30,10 @@ LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauFixNilRightPad) +LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) +LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -1439,6 +1441,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_lib_function_function_argument { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, + {FFlag::LuauNoOrderingTypeFunctions, true}, + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, + {FFlag::LuauNoScopeShallNotSubsumeAll, true}, }; CheckResult result = check(R"( @@ -1446,14 +1452,10 @@ local a = {{x=4}, {x=7}, {x=1}} table.sort(a, function(x, y) return x.x < y.x end) )"); - if (FFlag::LuauSubtypingReportGenericBoundMismatches2) - { - // FIXME CLI-161355 - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors[0])); - } - else - LUAU_REQUIRE_NO_ERRORS(result); + // FIXME CLI-161355 + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK(get(result.errors[0])); + CHECK(get(result.errors[1])); } TEST_CASE_FIXTURE(Fixture, "variadic_any_is_compatible_with_a_generic_TypePack") @@ -2393,7 +2395,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}, - {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}, + {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}, }; CheckResult result = check(R"( @@ -2609,19 +2611,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type") if (!FFlag::LuauSolverV2) return; + ScopedFastFlag _{FFlag::LuauNoOrderingTypeFunctions, true}; + CheckResult result = check(R"( function fib(n, u) return (n or u) and (n < u and n + fib(n,u)) end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - auto err = get(result.errors.back()); - LUAU_ASSERT(err); - CHECK("number" == toString(err->recommendedReturn)); - REQUIRE(err->recommendedArgs.size() == 2); - CHECK("number" == toString(err->recommendedArgs[0].second)); - CHECK("number" == toString(err->recommendedArgs[1].second)); + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK(get(result.errors[0])); + auto err2 = get(result.errors[1]); + LUAU_ASSERT(err2); + CHECK("number" == toString(err2->recommendedReturn)); + REQUIRE(err2->recommendedArgs.size() == 2); + CHECK("number" == toString(err2->recommendedArgs[0].second)); + CHECK("number" == toString(err2->recommendedArgs[1].second)); } TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type_2") diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 91ddb335..a624620f 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -9,7 +9,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) @@ -999,7 +999,7 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; CheckResult result = check(R"( function test(a: number) @@ -1027,7 +1027,7 @@ wrapper(test) TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; CheckResult result = check(R"( function test2(a: number, b: string) @@ -1071,7 +1071,7 @@ wrapper(test2, 1, "") TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; CheckResult result = check(R"( function test2(a: number) @@ -1101,7 +1101,7 @@ wrapper(test2, 1) TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return_no_error") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; CheckResult result = check(R"( function test2(a: number) @@ -1119,7 +1119,7 @@ wrapper(test2, "hello") TEST_CASE_FIXTURE(Fixture, "nested_generic_argument_type_packs") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; CheckResult result = check(R"( function test2(a: number) @@ -2097,4 +2097,42 @@ TEST_CASE_FIXTURE(Fixture, "array_of_singletons_should_subtype_against_generic_a LUAU_REQUIRE_NO_ERRORS(res); } +TEST_CASE_FIXTURE(BuiltinsFixture, "gh1985_array_of_union_for_generic") +{ + ScopedFastFlag _[] = { + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, + {FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds, true} + }; + + CheckResult res = check(R"( + local function clear(arr: { T }) table.clear(arr) end + local a: { true | false } + -- This obviously shouldn't error, '{ true | false }' should fit '{ T }' + -- TypeError: The generic type parameter Twas found to have invalid bounds. Its lower bounds were [true, false], and its upper bounds were [true]. + clear(a) + )"); + + LUAU_REQUIRE_NO_ERRORS(res); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "gh1985_array_of_union_for_generic_2") +{ + ScopedFastFlag _[] = { + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, + {FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds, true} + }; + + CheckResult res = check(R"( + local function id(arr: { T }): { T } return arr end + local a: { true | false } + local b = id(a) + )"); + + LUAU_REQUIRE_NO_ERRORS(res); +} + + + TEST_SUITE_END(); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 42b7865d..9adea1a7 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -10,7 +10,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) @@ -1155,7 +1155,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") { ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; - ScopedFastFlag sff2{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag sff2{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; CheckResult result = check(R"( function f() @@ -1187,7 +1187,7 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; CheckResult result = check(R"( diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 235fad48..bc9fc19d 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -13,7 +13,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTINT(LuauSolverConstraintLimit) @@ -822,7 +822,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any") TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_function_mutation") { - ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}}; + ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}}; fileResolver.source["game/A"] = R"( function test2(a: number, b: string) diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 990f8c30..de1ce75a 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -2,6 +2,9 @@ #include "Luau/AstQuery.h" #include "Luau/BuiltinDefinitions.h" +#include "Luau/Common.h" +#include "Luau/Error.h" +#include "Luau/Frontend.h" #include "Luau/Type.h" #include "Luau/VisitType.h" @@ -15,6 +18,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauIndexInMetatableSubtyping) TEST_SUITE_BEGIN("TypeInferOOP"); @@ -622,4 +626,127 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern_2") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "oop_invoke_with_inferred_self_type") +{ + ScopedFastFlag _{FFlag::LuauIndexInMetatableSubtyping, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local ItemContainer = {} + ItemContainer.__index = ItemContainer + + function ItemContainer.new() + local self = {} + setmetatable(self, ItemContainer) + return self + end + + function ItemContainer:removeItem(itemId, itemType) + self:getItem(itemId, itemType) + end + + function ItemContainer:getItem(itemId, itemType): () + end + + local container = ItemContainer.new() + + container:removeItem(0, "magic") + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oop_invoke_with_inferred_self_and_property") +{ + ScopedFastFlag _{FFlag::LuauIndexInMetatableSubtyping, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local ItemContainer = {} + ItemContainer.__index = ItemContainer + + function ItemContainer.new(name) + local self = {name = name} + setmetatable(self, ItemContainer) + return self + end + + function ItemContainer:removeItem(itemId, itemType) + print(self.name) + self:getItem(itemId, itemType) + end + + function ItemContainer:getItem(itemId, itemType): () + end + + local container = ItemContainer.new("library") + + container:removeItem(0, "magic") + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_field_allows_upcast") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauIndexInMetatableSubtyping, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local Foobar = {} + Foobar.__index = Foobar + Foobar.const = 42 + + local foobar = setmetatable({}, Foobar) + + local _: { read const: number } = foobar + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_field_disallows_invalid_upcast") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauIndexInMetatableSubtyping, true}, + }; + + CheckResult results = check(R"( + local Foobar = {} + Foobar.__index = Foobar + Foobar.const = 42 + + local foobar = setmetatable({}, Foobar) + + local _: { const: number } = foobar + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + auto err = get(results.errors[0]); + REQUIRE(err); + CHECK_EQ("{ const: number }", toString(err->wantedType)); + CHECK_EQ("{ @metatable t1, { } } where t1 = { __index: t1, const: number }", toString(err->givenType, {/* exhaustive */ true})); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_field_precedence_for_subtyping") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauIndexInMetatableSubtyping, true}, + }; + + CheckResult results = check(R"( + local function foobar1(_: { read foo: number }) end + local function foobar2(_: { read bar: boolean }) end + local function foobar3(_: { read foo: string }) end + + local t = { foo = 4 } + setmetatable(t, { __index = { foo = "heh", bar = true }}) + foobar1(t) + foobar2(t) + foobar3(t) + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + auto err = get(results.errors[0]); + REQUIRE(err); + CHECK_EQ("{ read foo: string }", toString(err->wantedType, {/* exhaustive */ true})); + CHECK_EQ("{ @metatable { __index: { bar: boolean, foo: string } }, { foo: number } }", toString(err->givenType, { /* exhaustive */ true})); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index ef829e15..19d20149 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauTrackUniqueness) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) TEST_SUITE_BEGIN("TypeInferOperators"); @@ -293,6 +294,8 @@ TEST_CASE_FIXTURE(Fixture, "compare_strings") TEST_CASE_FIXTURE(Fixture, "cannot_indirectly_compare_types_that_do_not_have_a_metatable") { + ScopedFastFlag _{FFlag::LuauNoOrderingTypeFunctions, true}; + CheckResult result = check(R"( local a = {} local b = {} @@ -303,9 +306,7 @@ TEST_CASE_FIXTURE(Fixture, "cannot_indirectly_compare_types_that_do_not_have_a_m if (FFlag::LuauSolverV2) { - UninhabitedTypeFunction* utf = get(result.errors[0]); - REQUIRE(utf); - REQUIRE_EQ(toString(utf->ty), "lt"); + REQUIRE(get(result.errors[0])); } else { @@ -317,6 +318,8 @@ TEST_CASE_FIXTURE(Fixture, "cannot_indirectly_compare_types_that_do_not_have_a_m TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators") { + ScopedFastFlag _{FFlag::LuauNoOrderingTypeFunctions, true}; + CheckResult result = check(R"( local M = {} function M.new() @@ -333,9 +336,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_indirectly_compare_types_that_do_not_ if (FFlag::LuauSolverV2) { - UninhabitedTypeFunction* utf = get(result.errors[0]); - REQUIRE(utf); - REQUIRE_EQ(toString(utf->ty), "lt"); + REQUIRE(get(result.errors[0])); } else { @@ -865,6 +866,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "and_binexps_dont_unify") TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operators") { + ScopedFastFlag _{FFlag::LuauNoOrderingTypeFunctions, true}; + CheckResult result = check(R"( local a: boolean = true local b: boolean = false @@ -875,9 +878,9 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato if (FFlag::LuauSolverV2) { - UninhabitedTypeFunction* utf = get(result.errors[0]); - REQUIRE(utf); - REQUIRE_EQ(toString(utf->ty), "lt"); + GenericError* ge = get(result.errors[0]); + REQUIRE(ge); + CHECK_EQ("Types 'boolean' and 'boolean' cannot be compared with relational operator <", ge->message); } else { @@ -889,6 +892,8 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operators2") { + ScopedFastFlag _{FFlag::LuauNoOrderingTypeFunctions, true}; + CheckResult result = check(R"( local a: number | string = "" local b: number | string = 1 @@ -906,9 +911,9 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato if (FFlag::LuauSolverV2) { - UninhabitedTypeFunction* utf = get(result.errors[0]); - REQUIRE(utf); - REQUIRE_EQ(toString(utf->ty), "lt"); + GenericError* ge = get(result.errors[0]); + REQUIRE(ge); + CHECK_EQ("Types 'number | string' and 'number | string' cannot be compared with relational operator <", ge->message); } else { diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index b6f174d7..fbbc532d 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -1380,4 +1380,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_and_test_two_props") CHECK(Position{3, 61} == result.errors[0].location.end); } +TEST_CASE_FIXTURE(BuiltinsFixture, "function_indexer_satisfies_reading_property") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + + // We would like this code to have _no_ errors, but it requires one of: + // (a) Being able to express read-only indexers, as that is the type of + // `__index` when it is a function. + // (b) Metatable aware semantic subtyping for tables. + CheckResult result = check(R"( + local t = setmetatable({}, { + __index = function (_, _prop: string): number + return 42 + end + }) + + local function readX(tbl: { read X: number }) + print(tbl.X) + end + + -- This should work as `__index` being a function should semantically + -- be the same as having an indexer. + readX(t) + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + auto err = get(result.errors[0]); + REQUIRE(err); + CHECK_EQ("{ @metatable { __index: (unknown, string) -> number }, { } }", toString(err->givenType, { /* exhaustive */ true})); + CHECK_EQ("{ read X: number }", toString(err->wantedType)); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 99a42cce..2e2b48fe 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -19,10 +19,11 @@ LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAG(LuauAddConditionalContextForTernary) +LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) using namespace Luau; @@ -2248,6 +2249,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" {FFlag::LuauSolverV2, true}, {FFlag::LuauNoMoreComparisonTypeFunctions, true}, {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, + {FFlag::LuauNoOrderingTypeFunctions, true}, }; CheckResult result = check(R"( @@ -2259,11 +2261,26 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" end )"); - LUAU_CHECK_ERROR_COUNT(2, result); + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "check_refinement_to_primitive_and_compare") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauNoMoreComparisonTypeFunctions, true}, + {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, + {FFlag::LuauNoOrderingTypeFunctions, true}, + }; + + CheckResult result = check(R"( + local function comesAfterLuau(word) + return type(word) == "string" and word > "luau" + end + )"); - // For some reason we emit three error here. - for (const auto& e : result.errors) - CHECK(get(e)); + LUAU_REQUIRE_NO_ERRORS(result); + CHECK_EQ("(unknown) -> boolean", toString(requireType("comesAfterLuau"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant") @@ -2271,6 +2288,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_ ScopedFastFlag _[] = { {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, + {FFlag::LuauNoOrderingTypeFunctions, true}, }; // FIXME CLI-141364: An underlying bug in normalization means the type of @@ -2283,15 +2301,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_ and math.floor(k) == k -- no float keys end )"); - if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - // For some reason we emit two errors here. - for (const auto& e : result.errors) - CHECK(get(e)); - } - else - LUAU_REQUIRE_NO_ERRORS(result); + + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "ex") diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 766888b1..09a06cef 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -411,7 +411,10 @@ local a: Animal = { tag = 'cat', cafood = 'something' } LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::LuauSolverV2) - CHECK(R"(Table type '{ cafood: string, tag: "cat" }' not compatible with type 'Cat' because the former is missing field 'catfood')" == toString(result.errors[0])); + CHECK( + R"(Table type '{ cafood: string, tag: "cat" }' not compatible with type 'Cat' because the former is missing field 'catfood')" == + toString(result.errors[0]) + ); else { const std::string expected = R"(Type 'a' could not be converted into 'Cat | Dog' diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 3793fa9d..f383c998 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -6121,4 +6121,17 @@ end LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "string_indexer_satisfies_read_only_property") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + + // NOTE: Unclear if this should be allowed, but for the type solver's + // current state I think it's reasonable. + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function foo(t: { [string]: number }): { read X: number } + return t + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 327d53c4..5af83940 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -25,13 +25,14 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAG(LuauOccursCheckInCommit) LUAU_FASTFLAG(LuauEGFixGenericsList) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAG(LuauTryToOptimizeSetTypeUnification) +LUAU_FASTFLAG(LuauDontReferenceScopePtrFromHashTable) using namespace Luau; @@ -2541,7 +2542,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_missing_type_pack_follow") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}, + {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}, }; LUAU_REQUIRE_ERRORS(check(R"( @@ -2579,6 +2580,7 @@ do end )")); } +#if 0 // CLI-166473: re-enable after flakiness is resolved TEST_CASE_FIXTURE(Fixture, "txnlog_checks_for_occurrence_before_self_binding_a_type") { ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, false}, {FFlag::LuauOccursCheckInCommit, true}}; @@ -2621,6 +2623,7 @@ TEST_CASE_FIXTURE(Fixture, "txnlog_checks_for_occurrence_before_self_binding_a_t return f4 )"); } +#endif TEST_CASE_FIXTURE(Fixture, "constraint_generation_recursion_limit") { @@ -2685,4 +2688,47 @@ TEST_CASE_FIXTURE(Fixture, "avoid_unification_inferring_never_for_refined_param" CHECK_EQ("({ read getItem: (number) -> (number?, ...unknown) }, number) -> ()", toString(requireType("__removeItem"))); } +TEST_CASE_FIXTURE(BuiltinsFixture, "unterminated_function_body_causes_constraint_generator_crash") +{ + ScopedFastFlag _{FFlag::LuauDontReferenceScopePtrFromHashTable, true}; + // This should not crash + CheckResult result = check(R"( +export type t = { + func : typeof( + function + ) +} + +export type t1 = t12 + +export type t2 = {} + +export type t3 = { + foo:number + bar:number +} + +export type t4 = "foobar" + +export type t5 = string + +export type t6 = number + +export type t7 = "foobar" + +export type t8 = "foobar" + +export type t9 = typeof(1) + +export type t10 = typeof(1) + +export type t11 = typeof(1) + +export type t12 = { + b:number + pb:number +} +)"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 7f160bfe..8c718c26 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -897,9 +897,7 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ( - "(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) - ); + CHECK_EQ("(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))); } TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types_2") diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index 1bae4837..0ccb7930 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -7,6 +7,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) TEST_SUITE_BEGIN("TypeInferUnknownNever"); @@ -327,6 +328,8 @@ TEST_CASE_FIXTURE(Fixture, "length_of_never") TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators") { + ScopedFastFlag _{FFlag::LuauNoOrderingTypeFunctions, true}; + CheckResult result = check(R"( local function ord(x: nil, y) return x ~= nil and x > y @@ -334,17 +337,11 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i )"); + LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors[0])); - CHECK_EQ("(nil, a) -> false | le", toString(requireType("ord"))); - } + CHECK_EQ("(nil, nil & ~nil) -> boolean", toString(requireType("ord"))); else - { - LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("(nil, a) -> boolean", toString(requireType("ord"))); - } } TEST_CASE_FIXTURE(Fixture, "math_operators_and_never") diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp index 93623f8b..72cd16e2 100644 --- a/tests/TypePath.test.cpp +++ b/tests/TypePath.test.cpp @@ -18,7 +18,7 @@ using namespace Luau::TypePath; LUAU_FASTFLAG(LuauSolverV2); LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps); -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2); +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3); LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) struct TypePathFixture : Fixture @@ -470,7 +470,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail") TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; TypeArena& arena = getFrontend().globals.globalTypes; unfreeze(arena); @@ -487,7 +487,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail") TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_finite_pack") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; TypeArena& arena = getFrontend().globals.globalTypes; unfreeze(arena); diff --git a/tests/Unifier2.test.cpp b/tests/Unifier2.test.cpp index 2e3bf96c..8ae7ce94 100644 --- a/tests/Unifier2.test.cpp +++ b/tests/Unifier2.test.cpp @@ -139,7 +139,7 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "unify_avoid_free_type_intersection_in_ub_fro // 'a TypeId freeTy = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType}); // 'a & ~(false?) - TypeId subTy = arena.addType(IntersectionType{{ freeTy, builtinTypes.truthyType}}); + TypeId subTy = arena.addType(IntersectionType{{freeTy, builtinTypes.truthyType}}); // number? TypeId superTy = arena.addType(UnionType{{builtinTypes.numberType, builtinTypes.nilType}}); u2.unify(subTy, superTy); @@ -153,13 +153,10 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "unify_unfortunate_free_type_lb_from_intersec // 'a TypeId freeTy = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType}); // 'a? - TypeId superTy = arena.addType(UnionType{{ freeTy, builtinTypes.nilType}}); + TypeId superTy = arena.addType(UnionType{{freeTy, builtinTypes.nilType}}); // string & ~"foo" - TypeId subTy = arena.addType(IntersectionType{{builtinTypes.stringType, arena.addType( - NegationType{ - arena.addType(SingletonType{StringSingleton{"foo"}}) - } - )}}); + TypeId subTy = + arena.addType(IntersectionType{{builtinTypes.stringType, arena.addType(NegationType{arena.addType(SingletonType{StringSingleton{"foo"}})})}}); u2.unify(subTy, superTy); // TODO CLI-168953: This is not correct. The lower bound should be diff --git a/tests/conformance/native.luau b/tests/conformance/native.luau index f0fe3ce6..39be8afc 100644 --- a/tests/conformance/native.luau +++ b/tests/conformance/native.luau @@ -674,4 +674,48 @@ end assert(splitStoreRestore(2).ad() == 15) +local function guard1(a) + local c = 0 + if type(a) == "number" then + c = a * 2 + end + return c +end + +assert(guard1(4) == 8) +assert(guard1({}) == 0) + +local function guard2(a) + local c = 0 + if type(a) ~= "number" then + c = 4 + end + return c +end + +assert(guard2(4) == 0) +assert(guard2({}) == 4) + +local function guard3(a) + local c = 0 + if typeof(a) == "number" then + c = a * 2 + end + return c +end + +assert(guard3(4) == 8) +assert(guard3({}) == 0) + +local function guard4(a) + local c = 0 + if typeof(a) ~= "number" then + c = 4 + end + return c +end + +assert(guard4(4) == 0) +assert(guard4({}) == 4) + return('OK') diff --git a/tests/conformance/stringinterp.luau b/tests/conformance/stringinterp.luau index efb25bae..7e2b5495 100644 --- a/tests/conformance/stringinterp.luau +++ b/tests/conformance/stringinterp.luau @@ -56,4 +56,6 @@ assertEq(shadowsString(1), "Value is 1") assertEq(`\u{0041}\t`, "A\t") +assertEq(`{"5"} + {7} = 12`, "5 + 7 = 12") + return "OK" From 4b3bb06d4abe4595168106bf6925b643484e98ce Mon Sep 17 00:00:00 2001 From: Alexander McCord <11488393+alexmccord@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:24:57 -0700 Subject: [PATCH 02/10] Fix perfect forwarding in `TypedAllocator::allocate`. (#2008) The original wasn't actually allowing universal references and if you have a datatype with a deleted copy constructor, the template substitution would fail here. This lets me use `TypedAllocator` for some type that isn't copyable. I doubt there would be any performance difference due to copy elision anyway. --- Analysis/include/Luau/TypedAllocator.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analysis/include/Luau/TypedAllocator.h b/Analysis/include/Luau/TypedAllocator.h index a5dd17b6..98f0cad7 100644 --- a/Analysis/include/Luau/TypedAllocator.h +++ b/Analysis/include/Luau/TypedAllocator.h @@ -49,7 +49,7 @@ class TypedAllocator T* block = stuff.back(); T* res = block + currentBlockSize; - new (res) T(std::forward(args...)); + new (res) T(std::forward(args)...); ++currentBlockSize; return res; } From 0084576e6f8dc9865f27d296f0357794894bd38d Mon Sep 17 00:00:00 2001 From: Hunter Goldstein Date: Fri, 26 Sep 2025 13:21:09 -0700 Subject: [PATCH 03/10] Sync to upstream/release/693 (#2021) # New Solver * Avoid bidirectional inference always forcing constraints for simple code such as the example below. Fixes #2017. ```luau type Suit = "Hearts" | "Spades" | "Clubs" | "Diamonds" local getSuits(): { Suit } return { "Hearts", "Spades", "Clubs", "Diamonds" } end ``` * Iterating over an empty pack, such as in the below example, should error but still allow type inference to complete. ```luau local function foo() end -- has type `() -> ()` for _ in foo() do end -- Errors, as you can't iterate over `()`, but type inference still completes. ``` * Implement arity based overload selection: this allows for overloaded functions with generics to more consistently infer reasonable results. For example: ```luau -- This code no longer errors as we determine that the first overload to -- table.insert is what the author intended. table.insert({} :: { any }, 42) ``` * Allow the`table` type to be a subtype of generic tables. This means code like the following no longer raises a type error: ```luau local function doSomething(t: unknown) if type(t) == "table" then -- Prior we would error as `table` is not a subtype of a generic table -- Also works with `pairs` and similar builtin functions. local foo, bar = next(t) end end ``` * Descend into intersection types when performing bidirectional inference, this allows us to correctly bidirectionally infer code like: ```luau type A = { foo: "a" } type B = { bar: "b" } type AB = A & B -- No longer errors as the literals "a" and "b" will be inferred to be their singleton types. local t: AB = { foo = "a", bar = "b" } ``` * #2008 * Fix intersections between table types and extern types to preserve intersections when either type contains an indexer, fixing a regression introduced when trying to refine table and extern types more precisely: ```luau function f(obj: { [any]: any }, functionName: string) if typeof(obj) == "userdata" then -- No longer errors as we still have a `class & { [any]: any }` rather than a `class` local _ = obj[functionName] end end ``` * Separated recursion limits for different parts of the new solver. No immediate changes, but this creates more tools to tamp down on stack overflows without affecting other subsystems. # Runtime * Implement "stackless" `pcall` / `xpcall` in yieldable contexts: this lets recursive calls to `pcall` nest further, erroring at the Luau call stack limit (20000 calls as of this writing) rather than the C call stack limit (200 calls as of this writing) --- Co-authored-by: Ariel Weiss Co-authored-by: Hunter Goldstein Co-authored-by: Sora Kanosue Co-authored-by: Varun Saini Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --------- Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood Co-authored-by: Menarul Alam Co-authored-by: Aviral Goel Co-authored-by: Vighnesh Co-authored-by: Vyacheslav Egorov Co-authored-by: Ariel Weiss Co-authored-by: Andy Friesen --- Analysis/include/Luau/BuiltinTypeFunctions.h | 4 +- Analysis/include/Luau/Frontend.h | 12 +- Analysis/include/Luau/OverloadResolution.h | 12 + Analysis/include/Luau/Subtyping.h | 4 +- Analysis/include/Luau/TableLiteralInference.h | 5 +- Analysis/include/Luau/Type.h | 2 + Analysis/include/Luau/TypeUtils.h | 3 + Analysis/src/BuiltinDefinitions.cpp | 37 +- Analysis/src/BuiltinTypeFunctions.cpp | 78 ++- Analysis/src/ConstraintGenerator.cpp | 467 +++++++++++------- Analysis/src/ConstraintSolver.cpp | 69 +-- Analysis/src/Error.cpp | 13 +- Analysis/src/Frontend.cpp | 245 ++++++++- Analysis/src/Normalize.cpp | 45 +- Analysis/src/OverloadResolution.cpp | 208 ++++++-- Analysis/src/Simplify.cpp | 10 +- Analysis/src/Subtyping.cpp | 89 +++- Analysis/src/TableLiteralInference.cpp | 62 ++- Analysis/src/ToString.cpp | 192 ++----- Analysis/src/Type.cpp | 8 +- Analysis/src/TypeChecker2.cpp | 4 +- Analysis/src/TypeUtils.cpp | 33 +- Analysis/src/Unifier2.cpp | 7 +- CLI/src/Analyze.cpp | 5 +- Common/include/Luau/ExperimentalFlags.h | 1 - Sources.cmake | 1 + VM/src/lapi.cpp | 15 +- VM/src/laux.cpp | 15 +- VM/src/lbaselib.cpp | 43 +- VM/src/ldo.cpp | 306 +++++++++--- VM/src/ldo.h | 11 + VM/src/lvmexecute.cpp | 2 +- tests/Autocomplete.test.cpp | 5 +- tests/Conformance.test.cpp | 128 ++++- tests/EqSatSimplification.test.cpp | 13 +- tests/Fixture.cpp | 6 + tests/Fixture.h | 1 + tests/FragmentAutocomplete.test.cpp | 66 ++- tests/Frontend.test.cpp | 15 +- tests/Normalize.test.cpp | 12 +- tests/OverloadResolver.test.cpp | 165 +++++++ tests/RuntimeLimits.test.cpp | 4 +- tests/Simplify.test.cpp | 10 + tests/Subtyping.test.cpp | 4 +- tests/ToString.test.cpp | 20 - tests/TypeFunction.test.cpp | 23 +- tests/TypeFunction.user.test.cpp | 2 - tests/TypeInfer.aliases.test.cpp | 6 - tests/TypeInfer.builtins.test.cpp | 69 ++- tests/TypeInfer.classes.test.cpp | 48 +- tests/TypeInfer.functions.test.cpp | 30 +- tests/TypeInfer.generics.test.cpp | 4 - tests/TypeInfer.intersectionTypes.test.cpp | 5 - tests/TypeInfer.loops.test.cpp | 13 +- tests/TypeInfer.modules.test.cpp | 4 - tests/TypeInfer.oop.test.cpp | 2 - tests/TypeInfer.provisional.test.cpp | 5 - tests/TypeInfer.refinements.test.cpp | 16 - tests/TypeInfer.singletons.test.cpp | 12 +- tests/TypeInfer.tables.test.cpp | 205 ++++++-- tests/TypeInfer.test.cpp | 9 + tests/TypeInfer.tryUnify.test.cpp | 2 - tests/TypeInfer.typePacks.test.cpp | 7 - tests/TypeInfer.typestates.test.cpp | 2 - tests/TypeInfer.unionTypes.test.cpp | 4 - tests/TypeVar.test.cpp | 2 - tests/conformance/apicalls.luau | 4 + tests/conformance/cyield.luau | 47 ++ tests/conformance/errors.luau | 4 +- tests/conformance/pcall.luau | 25 +- tests/conformance/tmerror.luau | 63 ++- 71 files changed, 2232 insertions(+), 838 deletions(-) create mode 100644 tests/OverloadResolver.test.cpp diff --git a/Analysis/include/Luau/BuiltinTypeFunctions.h b/Analysis/include/Luau/BuiltinTypeFunctions.h index e10b72f0..8c36c00c 100644 --- a/Analysis/include/Luau/BuiltinTypeFunctions.h +++ b/Analysis/include/Luau/BuiltinTypeFunctions.h @@ -9,6 +9,8 @@ namespace Luau struct BuiltinTypeFunctions { BuiltinTypeFunctions(); + BuiltinTypeFunctions(const BuiltinTypeFunctions&) = delete; + void operator=(const BuiltinTypeFunctions&) = delete; TypeFunction userFunc; @@ -51,6 +53,6 @@ struct BuiltinTypeFunctions void addToScope(NotNull arena, NotNull scope) const; }; -const BuiltinTypeFunctions& builtinTypeFunctions(); +const BuiltinTypeFunctions& builtinTypeFunctions_DEPRECATED(); } // namespace Luau diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 0ec7f065..cbcb2274 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -225,14 +225,19 @@ struct Frontend ); // Batch module checking. Queue modules and check them together, retrieve results with 'getCheckResult' - // If provided, 'executeTask' function is allowed to call the 'task' function on any thread and return without waiting for 'task' to complete + // If provided, 'executeTasks' function is allowed to call any item in 'tasks' on any thread and return without waiting for them to complete void queueModuleCheck(const std::vector& names); void queueModuleCheck(const ModuleName& name); - std::vector checkQueuedModules( + std::vector checkQueuedModules_DEPRECATED( std::optional optionOverride = {}, std::function task)> executeTask = {}, std::function progress = {} ); + std::vector checkQueuedModules( + std::optional optionOverride = {}, + std::function> tasks)> executeTasks = {}, + std::function progress = {} + ); std::optional getCheckResult(const ModuleName& name, bool accumulateNested, bool forAutocomplete = false); std::vector getRequiredScripts(const ModuleName& name); @@ -270,7 +275,8 @@ struct Frontend void checkBuildQueueItems(std::vector& items); void recordItemResult(const BuildQueueItem& item); void performQueueItemTask(std::shared_ptr state, size_t itemPos); - void sendQueueItemTask(std::shared_ptr state, size_t itemPos); + void sendQueueItemTask_DEPRECATED(std::shared_ptr state, size_t itemPos); + void sendQueueItemTasks(std::shared_ptr state, const std::vector& items); void sendQueueCycleItemTask(std::shared_ptr state); static LintResult classifyLints(const std::vector& warnings, const Config& config); diff --git a/Analysis/include/Luau/OverloadResolution.h b/Analysis/include/Luau/OverloadResolution.h index dbe4c06d..19954886 100644 --- a/Analysis/include/Luau/OverloadResolution.h +++ b/Analysis/include/Luau/OverloadResolution.h @@ -107,6 +107,18 @@ struct OverloadResolver std::optional failedSubTy, std::optional failedSuperTy ) const; + + // Checks if the candidate args are arity-compatible with the desired parameters. + // Used during overload selection to do arity-based filtering of overloads. + // We do not accept nil in place of a generic unless that generic is explicitly optional. + bool isArityCompatible(TypePackId candidate, TypePackId desired, NotNull builtinTypes) const; + + bool testFunctionTypeForOverloadSelection( + const FunctionType* ftv, + NotNull> uniqueTypes, + TypePackId argsPack, + bool useFreeTypeBounds + ); }; struct SolveResult diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index f56f5fe6..6bfe5800 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -269,7 +269,9 @@ struct Subtyping // TODO recursion limits SubtypingResult isSubtype(TypeId subTy, TypeId superTy, NotNull scope); - SubtypingResult isSubtype( + SubtypingResult isSubtype(TypePackId subTp, TypePackId superTp, NotNull scope, const std::vector& bindableGenerics); + // Clip with FFlagLuauPassBindableGenericsByReference + SubtypingResult isSubtype_DEPRECATED( TypePackId subTp, TypePackId superTp, NotNull scope, diff --git a/Analysis/include/Luau/TableLiteralInference.h b/Analysis/include/Luau/TableLiteralInference.h index 2da6301d..068c52fe 100644 --- a/Analysis/include/Luau/TableLiteralInference.h +++ b/Analysis/include/Luau/TableLiteralInference.h @@ -3,6 +3,7 @@ #pragma once #include "Luau/Ast.h" +#include "Luau/ConstraintSolver.h" #include "Luau/DenseHash.h" #include "Luau/NotNull.h" #include "Luau/TypeFwd.h" @@ -27,8 +28,8 @@ struct PushTypeResult PushTypeResult pushTypeInto( NotNull> astTypes, NotNull> astExpectedTypes, - NotNull builtinTypes, - NotNull arena, + NotNull solver, + NotNull constraint, NotNull unifier, NotNull subtyping, TypeId expectedType, diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index cc184c53..f0e7fafa 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -38,6 +38,7 @@ struct TypeFun; struct Constraint; struct Subtyping; struct TypeChecker2; +struct BuiltinTypeFunctions; enum struct SolverMode { @@ -1000,6 +1001,7 @@ struct BuiltinTypes bool debugFreezeArena = false; public: + std::unique_ptr typeFunctions; const TypeId nilType; const TypeId numberType; const TypeId stringType; diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 0e0a217b..5caad0e6 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -336,6 +336,9 @@ bool isApproximatelyTruthyType(TypeId ty); // Unwraps any grouping expressions iteratively. AstExpr* unwrapGroup(AstExpr* expr); +// Returns true if ty is optional, ie if it is a supertype of nil +bool isOptionalType(TypeId ty, NotNull builtinTypes); + // These are magic types used in `TypeChecker2` and `NonStrictTypeChecker` // // `_luau_print` causes it's argument to be printed out, as in: diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 008a5ead..d3106f8a 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -35,6 +35,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) namespace Luau { @@ -368,8 +369,17 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC NotNull builtinTypes = globals.builtinTypes; NotNull globalScope{globals.globalScope.get()}; - if (frontend.getLuauSolverMode() == SolverMode::New) - builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); + if (FFlag::LuauBuiltinTypeFunctionsArentGlobal) + { + if (frontend.getLuauSolverMode() == SolverMode::New) + builtinTypes->typeFunctions->addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); + } + else + { + if (frontend.getLuauSolverMode() == SolverMode::New) + builtinTypeFunctions_DEPRECATED().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); + } + LoadDefinitionFileResult loadResult = frontend.loadDefinitionFile( globals, globals.globalScope, getBuiltinDefinitionSource(), "@luau", /* captureComments */ false, typeCheckForAutocomplete @@ -438,7 +448,13 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC if (frontend.getLuauSolverMode() == SolverMode::New) { // getmetatable : (T) -> getmetatable - TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().getmetatableFunc, {genericT}}); + TypeId getmtReturn = arena.addType( + TypeFunctionInstanceType{ + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->getmetatableFunc + : builtinTypeFunctions_DEPRECATED().getmetatableFunc, + {genericT} + } + ); addGlobalBinding(globals, "getmetatable", makeFunction(arena, std::nullopt, {genericT}, {}, {genericT}, {getmtReturn}), "@luau"); } else @@ -450,7 +466,13 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC if (frontend.getLuauSolverMode() == SolverMode::New) { // setmetatable(T, MT) -> setmetatable - TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}}); + TypeId setmtReturn = arena.addType( + TypeFunctionInstanceType{ + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->setmetatableFunc + : builtinTypeFunctions_DEPRECATED().setmetatableFunc, + {genericT, genericMT} + } + ); addGlobalBinding( globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau" ); @@ -482,7 +504,12 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId refinedTy = arena.addType( TypeFunctionInstanceType{ - NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {} + NotNull{ + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? &builtinTypes->typeFunctions->intersectFunc + : &builtinTypeFunctions_DEPRECATED().intersectFunc + }, + {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, + {} } ); diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index 1295db89..a356e018 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -29,6 +29,8 @@ LUAU_FASTFLAG(LuauEGFixGenericsList) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauRawGetHandlesNil) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) +LUAU_FASTFLAGVARIABLE(LuauBuiltinTypeFunctionsArentGlobal) +LUAU_FASTFLAG(LuauPassBindableGenericsByReference) namespace Luau { @@ -110,7 +112,10 @@ std::optional> tryDistributeTypeFunctionApp( TypeId resultTy = ctx->arena->addType( TypeFunctionInstanceType{ - NotNull{&builtinTypeFunctions().unionFunc}, + NotNull{ + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? &ctx->builtins->typeFunctions->unionFunc + : &builtinTypeFunctions_DEPRECATED().unionFunc + }, std::move(results), {}, } @@ -238,7 +243,12 @@ TypeFunctionReductionResult lenTypeFunction( return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? + if (FFlag::LuauPassBindableGenericsByReference) + { + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope, {}).isSubtype) + return {std::nullopt, Reduction::Erroneous, {}, {}}; + } + else if (!subtyping.isSubtype_DEPRECATED(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, Reduction::Erroneous, {}, {}}; // `len` must return a `number`. @@ -322,7 +332,13 @@ TypeFunctionReductionResult unmTypeFunction( if (!FFlag::LuauEGFixGenericsList) { Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? + if (FFlag::LuauPassBindableGenericsByReference) + { + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope, {}).isSubtype) + return {std::nullopt, Reduction::Erroneous, {}, {}}; + } + else if (!subtyping.isSubtype_DEPRECATED(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope) + .isSubtype) // TODO: is this the right variance? return {std::nullopt, Reduction::Erroneous, {}, {}}; } @@ -679,7 +695,12 @@ TypeFunctionReductionResult concatTypeFunction( return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? + if (FFlag::LuauPassBindableGenericsByReference) + { + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope, {}).isSubtype) + return {std::nullopt, Reduction::Erroneous, {}, {}}; + } + else if (!subtyping.isSubtype_DEPRECATED(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, Reduction::Erroneous, {}, {}}; return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; @@ -888,7 +909,12 @@ static TypeFunctionReductionResult comparisonTypeFunction( return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? + if (FFlag::LuauPassBindableGenericsByReference) + { + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope, {}).isSubtype) + return {std::nullopt, Reduction::Erroneous, {}, {}}; + } + else if (!subtyping.isSubtype_DEPRECATED(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, Reduction::Erroneous, {}, {}}; return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}}; @@ -1017,7 +1043,12 @@ TypeFunctionReductionResult eqTypeFunction( return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; - if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? + if (FFlag::LuauPassBindableGenericsByReference) + { + if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope, {}).isSubtype) + return {std::nullopt, Reduction::Erroneous, {}, {}}; + } + else if (!subtyping.isSubtype_DEPRECATED(inferredArgPack, instantiatedMmFtv->argTypes, ctx->scope).isSubtype) // TODO: is this the right variance? return {std::nullopt, Reduction::Erroneous, {}, {}}; return {ctx->builtins->booleanType, Reduction::MaybeOk, {}, {}}; @@ -1575,11 +1606,23 @@ struct CollectUnionTypeOptions : TypeOnceVisitor bool visit(TypeId ty, const TypeFunctionInstanceType& tfit) override { - if (tfit.function->name != builtinTypeFunctions().unionFunc.name) + if (FFlag::LuauBuiltinTypeFunctionsArentGlobal) { - options.insert(ty); - blockingTypes.insert(ty); - return false; + if (tfit.function->name != ctx->builtins->typeFunctions->unionFunc.name) + { + options.insert(ty); + blockingTypes.insert(ty); + return false; + } + } + else + { + if (tfit.function->name != builtinTypeFunctions_DEPRECATED().unionFunc.name) + { + options.insert(ty); + blockingTypes.insert(ty); + return false; + } } return true; } @@ -2031,8 +2074,16 @@ bool searchPropsAndIndexer( if (auto tfit = get(indexType)) { // if we have an index function here, it means we're in a cycle, so let's see if it's well-founded if we tie the knot - if (tfit->function.get() == &builtinTypeFunctions().indexFunc) - indexType = follow(tblIndexer->indexResultType); + if (FFlag::LuauBuiltinTypeFunctionsArentGlobal) + { + if (tfit->function.get() == &ctx->builtins->typeFunctions->indexFunc) + indexType = follow(tblIndexer->indexResultType); + } + else + { + if (tfit->function.get() == &builtinTypeFunctions_DEPRECATED().indexFunc) + indexType = follow(tblIndexer->indexResultType); + } } if (isSubtype(ty, indexType, ctx->scope, ctx->builtins, ctx->simplifier, *ctx->ice, SolverMode::New)) @@ -2677,7 +2728,8 @@ void BuiltinTypeFunctions::addToScope(NotNull arena, NotNull s scope->exportedTypeBindings[getmetatableFunc.name] = mkUnaryTypeFunction(&getmetatableFunc); } -const BuiltinTypeFunctions& builtinTypeFunctions() + +const BuiltinTypeFunctions& builtinTypeFunctions_DEPRECATED() { static std::unique_ptr result = std::make_unique(); diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 57cb31d4..c1acb577 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -31,13 +31,15 @@ #include #include +LUAU_FASTFLAG(LuauIndividualRecursionLimits) +LUAU_DYNAMIC_FASTINTVARIABLE(LuauConstraintGeneratorRecursionLimit, 300) + LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2) -LUAU_FASTFLAGVARIABLE(LuauTypeFunNoScopeMapRef) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) @@ -45,7 +47,7 @@ LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAGVARIABLE(LuauInstantiateResolvedTypeFunctions) -LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraint) +LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraint2) LUAU_FASTFLAGVARIABLE(LuauEGFixGenericsList) LUAU_FASTFLAGVARIABLE(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAGVARIABLE(LuauInitializeDefaultGenericParamsAtProgramPoint) @@ -54,6 +56,7 @@ LUAU_FASTFLAGVARIABLE(LuauCacheDuplicateHasPropConstraints) LUAU_FASTFLAGVARIABLE(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauDontReferenceScopePtrFromHashTable) +LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) namespace Luau { @@ -569,7 +572,14 @@ void ConstraintGenerator::computeRefinement( } if (eq) - discriminantTy = createTypeFunctionInstance(builtinTypeFunctions().singletonFunc, {discriminantTy}, {}, scope, location); + discriminantTy = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->singletonFunc + : builtinTypeFunctions_DEPRECATED().singletonFunc, + {discriminantTy}, + {}, + scope, + location + ); for (const RefinementKey* key = proposition->key; key; key = key->parent) { @@ -683,12 +693,27 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat return ty; } std::vector args = {ty}; - const TypeFunction& func = kind == RefinementsOpKind::Intersect ? builtinTypeFunctions().intersectFunc : builtinTypeFunctions().refineFunc; - LUAU_ASSERT(!func.name.empty()); - args.insert(args.end(), discriminants.begin(), discriminants.end()); - TypeId resultType = createTypeFunctionInstance(func, std::move(args), {}, scope, location); - discriminants.clear(); - return resultType; + if (FFlag::LuauBuiltinTypeFunctionsArentGlobal) + { + const TypeFunction& func = + kind == RefinementsOpKind::Intersect ? builtinTypes->typeFunctions->intersectFunc : builtinTypes->typeFunctions->refineFunc; + LUAU_ASSERT(!func.name.empty()); + args.insert(args.end(), discriminants.begin(), discriminants.end()); + TypeId resultType = createTypeFunctionInstance(func, std::move(args), {}, scope, location); + discriminants.clear(); + return resultType; + } + else + { + const TypeFunction& func = + kind == RefinementsOpKind::Intersect ? builtinTypeFunctions_DEPRECATED().intersectFunc : builtinTypeFunctions_DEPRECATED().refineFunc; + LUAU_ASSERT(!func.name.empty()); + args.insert(args.end(), discriminants.begin(), discriminants.end()); + TypeId resultType = createTypeFunctionInstance(func, std::move(args), {}, scope, location); + discriminants.clear(); + return resultType; + } + }; for (auto& [def, partition] : refinements) @@ -743,7 +768,14 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat ty = flushConstraints(kind, ty, discriminants); if (partition.shouldAppendNilType) - ty = createTypeFunctionInstance(builtinTypeFunctions().weakoptionalFunc, {ty}, {}, scope, location); + ty = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->weakoptionalFunc + : builtinTypeFunctions_DEPRECATED().weakoptionalFunc, + {ty}, + {}, + scope, + location + ); updateRValueRefinements(scope, def, ty); } } @@ -855,7 +887,16 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc udtfData.definition = function; TypeId typeFunctionTy = arena->addType( - TypeFunctionInstanceType{NotNull{&builtinTypeFunctions().userFunc}, std::move(typeParams), {}, function->name, udtfData} + TypeFunctionInstanceType{ + NotNull{ + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? &builtinTypes->typeFunctions->userFunc + : &builtinTypeFunctions_DEPRECATED().userFunc + }, + std::move(typeParams), + {}, + function->name, + udtfData + } ); TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy}; @@ -987,10 +1028,21 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco { RecursionCounter counter{&recursionCount}; - if (recursionCount >= FInt::LuauCheckRecursionLimit) + if (FFlag::LuauIndividualRecursionLimits) { - reportCodeTooComplex(block->location); - return ControlFlow::None; + if (recursionCount >= DFInt::LuauConstraintGeneratorRecursionLimit) + { + reportCodeTooComplex(block->location); + return ControlFlow::None; + } + } + else + { + if (recursionCount >= FInt::LuauCheckRecursionLimit) + { + reportCodeTooComplex(block->location); + return ControlFlow::None; + } } checkAliases(scope, block); @@ -1010,16 +1062,30 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat) { std::optional counter; std::optional limiter; + if (FFlag::LuauNoConstraintGenRecursionLimitIce) { counter.emplace(&recursionCount); - if (recursionCount >= FInt::LuauCheckRecursionLimit) + if (FFlag::LuauIndividualRecursionLimits) { - reportCodeTooComplex(stat->location); - return ControlFlow::None; + if (recursionCount >= DFInt::LuauConstraintGeneratorRecursionLimit) + { + reportCodeTooComplex(stat->location); + return ControlFlow::None; + } + } + else + { + if (recursionCount >= FInt::LuauCheckRecursionLimit) + { + reportCodeTooComplex(stat->location); + return ControlFlow::None; + } } } + else if (FFlag::LuauIndividualRecursionLimits) + limiter.emplace("ConstraintGenerator", &recursionCount, DFInt::LuauConstraintGeneratorRecursionLimit); else limiter.emplace("ConstraintGenerator", &recursionCount, FInt::LuauCheckRecursionLimit); @@ -1322,8 +1388,13 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI const DefId keyDef = dfg->getDef(keyVar); const TypeId loopVar = loopScope->lvalueTypes[keyDef]; - const TypeId intersectionTy = - createTypeFunctionInstance(builtinTypeFunctions().intersectFunc, {loopVar, builtinTypes->notNilType}, {}, loopScope, keyVar->location); + const TypeId intersectionTy = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->intersectFunc : builtinTypeFunctions_DEPRECATED().intersectFunc, + {loopVar, builtinTypes->notNilType}, + {}, + loopScope, + keyVar->location + ); loopScope->bindings[keyVar] = Binding{intersectionTy, keyVar->location}; loopScope->lvalueTypes[keyDef] = intersectionTy; @@ -1880,150 +1951,76 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio reportError(function->location, ReservedIdentifier{"typeof"}); } - if (FFlag::LuauTypeFunNoScopeMapRef) - { - auto scopeIt = astTypeFunctionEnvironmentScopes.find(function); - LUAU_ASSERT(scopeIt); - - ScopePtr environmentScope = *scopeIt; - - Checkpoint startCheckpoint = checkpoint(this); - FunctionSignature sig = checkFunctionSignature(environmentScope, function->body, /* expectedType */ std::nullopt); - - // Place this function as a child of the non-type function scope - if (FFlag::LuauEmplaceNotPushBack) - scope->children.emplace_back(sig.signatureScope.get()); - else - scope->children.push_back(NotNull{sig.signatureScope.get()}); - - interiorFreeTypes.emplace_back(); - checkFunctionBody(sig.bodyScope, function->body); - Checkpoint endCheckpoint = checkpoint(this); - - TypeId generalizedTy = arena->addType(BlockedType{}); - NotNull gc = addConstraint( - sig.signatureScope, - function->location, - GeneralizationConstraint{ - generalizedTy, - sig.signature, - std::vector{}, - } - ); - - sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); - sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); + auto scopeIt = astTypeFunctionEnvironmentScopes.find(function); + LUAU_ASSERT(scopeIt); - getMutable(generalizedTy)->setOwner(gc); - interiorFreeTypes.pop_back(); + ScopePtr environmentScope = *scopeIt; - Constraint* previous = nullptr; - forEachConstraint( - startCheckpoint, - endCheckpoint, - this, - [gc, &previous](const ConstraintPtr& constraint) - { - gc->dependencies.emplace_back(constraint.get()); - - if (auto psc = get(*constraint); psc && psc->returns) - { - if (previous) - { - if (FFlag::LuauEmplaceNotPushBack) - constraint->dependencies.emplace_back(previous); - else - constraint->dependencies.push_back(NotNull{previous}); - } - - previous = constraint.get(); - } - } - ); - - std::optional existingFunctionTy = environmentScope->lookup(function->name); - - if (!existingFunctionTy) - ice->ice("checkAliases did not populate type function name", function->nameLocation); - - TypeId unpackedTy = follow(*existingFunctionTy); - - if (auto bt = get(unpackedTy); bt && nullptr == bt->getOwner()) - emplaceType(asMutable(unpackedTy), generalizedTy); + Checkpoint startCheckpoint = checkpoint(this); + FunctionSignature sig = checkFunctionSignature(environmentScope, function->body, /* expectedType */ std::nullopt); - return ControlFlow::None; - } + // Place this function as a child of the non-type function scope + if (FFlag::LuauEmplaceNotPushBack) + scope->children.emplace_back(sig.signatureScope.get()); else - { - auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); - LUAU_ASSERT(scopePtr); + scope->children.push_back(NotNull{sig.signatureScope.get()}); - Checkpoint startCheckpoint = checkpoint(this); - FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt); - - // Place this function as a child of the non-type function scope - if (FFlag::LuauEmplaceNotPushBack) - scope->children.emplace_back(sig.signatureScope.get()); - else - scope->children.push_back(NotNull{sig.signatureScope.get()}); + interiorFreeTypes.emplace_back(); + checkFunctionBody(sig.bodyScope, function->body); + Checkpoint endCheckpoint = checkpoint(this); - interiorFreeTypes.emplace_back(); - checkFunctionBody(sig.bodyScope, function->body); - Checkpoint endCheckpoint = checkpoint(this); + TypeId generalizedTy = arena->addType(BlockedType{}); + NotNull gc = addConstraint( + sig.signatureScope, + function->location, + GeneralizationConstraint{ + generalizedTy, + sig.signature, + std::vector{}, + } + ); - TypeId generalizedTy = arena->addType(BlockedType{}); - NotNull gc = addConstraint( - sig.signatureScope, - function->location, - GeneralizationConstraint{ - generalizedTy, - sig.signature, - std::vector{}, - } - ); + sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); - sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); - sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); + getMutable(generalizedTy)->setOwner(gc); + interiorFreeTypes.pop_back(); - getMutable(generalizedTy)->setOwner(gc); - interiorFreeTypes.pop_back(); + Constraint* previous = nullptr; + forEachConstraint( + startCheckpoint, + endCheckpoint, + this, + [gc, &previous](const ConstraintPtr& constraint) + { + gc->dependencies.emplace_back(constraint.get()); - Constraint* previous = nullptr; - forEachConstraint( - startCheckpoint, - endCheckpoint, - this, - [gc, &previous](const ConstraintPtr& constraint) + if (auto psc = get(*constraint); psc && psc->returns) { - gc->dependencies.emplace_back(constraint.get()); - - if (auto psc = get(*constraint); psc && psc->returns) + if (previous) { - if (previous) - { - if (FFlag::LuauEmplaceNotPushBack) - constraint->dependencies.emplace_back(previous); - else - constraint->dependencies.push_back(NotNull{previous}); - } - - previous = constraint.get(); + if (FFlag::LuauEmplaceNotPushBack) + constraint->dependencies.emplace_back(previous); + else + constraint->dependencies.push_back(NotNull{previous}); } + + previous = constraint.get(); } - ); + } + ); - std::optional existingFunctionTy = (*scopePtr)->lookup(function->name); + std::optional existingFunctionTy = environmentScope->lookup(function->name); - if (!existingFunctionTy) - ice->ice("checkAliases did not populate type function name", function->nameLocation); + if (!existingFunctionTy) + ice->ice("checkAliases did not populate type function name", function->nameLocation); - TypeId unpackedTy = follow(*existingFunctionTy); + TypeId unpackedTy = follow(*existingFunctionTy); - if (auto bt = get(unpackedTy); bt && nullptr == bt->getOwner()) - emplaceType(asMutable(unpackedTy), generalizedTy); + if (auto bt = get(unpackedTy); bt && nullptr == bt->getOwner()) + emplaceType(asMutable(unpackedTy), generalizedTy); - return ControlFlow::None; - } + return ControlFlow::None; } ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareGlobal* global) @@ -2114,7 +2111,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte if (!FFlag::LuauNoConstraintGenRecursionLimitIce) counter.emplace(&recursionCount); - if (recursionCount >= FInt::LuauCheckRecursionLimit) + if (FFlag::LuauIndividualRecursionLimits && recursionCount >= DFInt::LuauConstraintGeneratorRecursionLimit) + { + reportCodeTooComplex(declaredExternType->indexer->location); + } + else if (recursionCount >= FInt::LuauCheckRecursionLimit) { reportCodeTooComplex(declaredExternType->indexer->location); } @@ -2342,10 +2343,21 @@ InferencePack ConstraintGenerator::checkPack( { RecursionCounter counter{&recursionCount}; - if (recursionCount >= FInt::LuauCheckRecursionLimit) + if (FFlag::LuauIndividualRecursionLimits) { - reportCodeTooComplex(expr->location); - return InferencePack{builtinTypes->errorTypePack}; + if (recursionCount >= DFInt::LuauConstraintGeneratorRecursionLimit) + { + reportCodeTooComplex(expr->location); + return InferencePack{builtinTypes->errorTypePack}; + } + } + else + { + if (recursionCount >= FInt::LuauCheckRecursionLimit) + { + reportCodeTooComplex(expr->location); + return InferencePack{builtinTypes->errorTypePack}; + } } InferencePack result; @@ -2638,10 +2650,21 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std:: { RecursionCounter counter{&recursionCount}; - if (recursionCount >= FInt::LuauCheckRecursionLimit) + if (FFlag::LuauIndividualRecursionLimits) { - reportCodeTooComplex(expr->location); - return Inference{builtinTypes->errorType}; + if (recursionCount >= DFInt::LuauConstraintGeneratorRecursionLimit) + { + reportCodeTooComplex(expr->location); + return Inference{builtinTypes->errorType}; + } + } + else + { + if (recursionCount >= FInt::LuauCheckRecursionLimit) + { + reportCodeTooComplex(expr->location); + return Inference{builtinTypes->errorType}; + } } // We may recurse a given expression more than once when checking compound @@ -3003,12 +3026,24 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary) { case AstExprUnary::Op::Not: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().notFunc, {operandType}, {}, scope, unary->location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->notFunc : builtinTypeFunctions_DEPRECATED().notFunc, + {operandType}, + {}, + scope, + unary->location + ); return Inference{resultType, refinementArena.negation(refinement)}; } case AstExprUnary::Op::Len: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().lenFunc, {operandType}, {}, scope, unary->location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->lenFunc : builtinTypeFunctions_DEPRECATED().lenFunc, + {operandType}, + {}, + scope, + unary->location + ); if (FFlag::LuauNumericUnaryOpsDontProduceNegationRefinements) return Inference{resultType, std::move(refinement)}; else @@ -3016,7 +3051,13 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary) } case AstExprUnary::Op::Minus: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unmFunc, {operandType}, {}, scope, unary->location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->unmFunc : builtinTypeFunctions_DEPRECATED().unmFunc, + {operandType}, + {}, + scope, + unary->location + ); if (FFlag::LuauNumericUnaryOpsDontProduceNegationRefinements) return Inference{resultType, std::move(refinement)}; else @@ -3047,52 +3088,112 @@ Inference ConstraintGenerator::checkAstExprBinary( { case AstExprBinary::Op::Add: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().addFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->addFunc : builtinTypeFunctions_DEPRECATED().addFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Sub: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().subFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->subFunc : builtinTypeFunctions_DEPRECATED().subFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Mul: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().mulFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->mulFunc : builtinTypeFunctions_DEPRECATED().mulFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Div: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().divFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->divFunc : builtinTypeFunctions_DEPRECATED().divFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::FloorDiv: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().idivFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->idivFunc : builtinTypeFunctions_DEPRECATED().idivFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Pow: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().powFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->powFunc : builtinTypeFunctions_DEPRECATED().powFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Mod: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().modFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->modFunc : builtinTypeFunctions_DEPRECATED().modFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Concat: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().concatFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->concatFunc : builtinTypeFunctions_DEPRECATED().concatFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::And: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().andFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->andFunc : builtinTypeFunctions_DEPRECATED().andFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::Or: { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().orFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->orFunc : builtinTypeFunctions_DEPRECATED().orFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } case AstExprBinary::Op::CompareLt: @@ -3105,7 +3206,13 @@ Inference ConstraintGenerator::checkAstExprBinary( } else { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().ltFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->ltFunc : builtinTypeFunctions_DEPRECATED().ltFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } } @@ -3120,7 +3227,7 @@ Inference ConstraintGenerator::checkAstExprBinary( else { TypeId resultType = createTypeFunctionInstance( - builtinTypeFunctions().ltFunc, + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->ltFunc : builtinTypeFunctions_DEPRECATED().ltFunc, {rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)` {}, scope, @@ -3139,7 +3246,13 @@ Inference ConstraintGenerator::checkAstExprBinary( } else { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().leFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->leFunc : builtinTypeFunctions_DEPRECATED().leFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } } @@ -3154,7 +3267,7 @@ Inference ConstraintGenerator::checkAstExprBinary( else { TypeId resultType = createTypeFunctionInstance( - builtinTypeFunctions().leFunc, + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->leFunc : builtinTypeFunctions_DEPRECATED().leFunc, {rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)` {}, scope, @@ -3184,7 +3297,13 @@ Inference ConstraintGenerator::checkAstExprBinary( else if (rightSubscripted) rightType = makeUnion(scope, location, rightType, builtinTypes->nilType); - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().eqFunc, {leftType, rightType}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->eqFunc : builtinTypeFunctions_DEPRECATED().eqFunc, + {leftType, rightType}, + {}, + scope, + location + ); return Inference{resultType, std::move(refinement)}; } } @@ -3577,7 +3696,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, ttv->indexer = TableIndexer{indexKey, indexValue}; } - if (FFlag::LuauPushTypeConstraint && expectedType) + if (FFlag::LuauPushTypeConstraint2 && expectedType) { addConstraint( scope, @@ -4418,7 +4537,13 @@ TypeId ConstraintGenerator::makeUnion(std::vector options) TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().intersectFunc, {lhs, rhs}, {}, scope, location); + TypeId resultType = createTypeFunctionInstance( + FFlag::LuauBuiltinTypeFunctionsArentGlobal ? builtinTypes->typeFunctions->intersectFunc : builtinTypeFunctions_DEPRECATED().intersectFunc, + {lhs, rhs}, + {}, + scope, + location + ); return resultType; } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index bc6653df..363f26d6 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -49,8 +49,9 @@ LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) -LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAGVARIABLE(LuauScopedSeenSetInLookupTableProp) +LUAU_FASTFLAGVARIABLE(LuauIterableBindNotUnify) namespace Luau { @@ -1088,8 +1089,16 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNullerrorType, ty); + if (FFlag::LuauIterableBindNotUnify) + { + for (TypeId ty : c.variables) + bind(constraint, ty, builtinTypes->errorType); + } + else + { + for (TypeId ty : c.variables) + unify(constraint, builtinTypes->errorType, ty); + } return true; } @@ -1642,48 +1651,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull generics{nullptr}; - - bool found = false; - - ContainsGenerics_DEPRECATED() - : TypeOnceVisitor("ContainsGenerics_DEPRECATED", FFlag::LuauExplicitSkipBoundTypes) - { - } - - bool visit(TypeId ty) override - { - return !found; - } - - bool visit(TypeId ty, const GenericType&) override - { - found |= generics.contains(ty); - return true; - } - - bool visit(TypeId ty, const TypeFunctionInstanceType&) override - { - return !found; - } - - bool visit(TypePackId tp, const GenericTypePack&) override - { - found |= generics.contains(tp); - return !found; - } - - bool hasGeneric(TypeId ty) - { - traverse(ty); - auto ret = found; - found = false; - return ret; - } -}; - namespace { struct ContainsGenerics : public TypeOnceVisitor @@ -1844,11 +1811,11 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull constraint, bool force) { - LUAU_ASSERT(FFlag::LuauPushTypeConstraint); + LUAU_ASSERT(FFlag::LuauPushTypeConstraint2); Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFunctions}; Subtyping subtyping{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; @@ -2875,10 +2842,12 @@ bool ConstraintSolver::tryDispatch(const PushTypeConstraint& c, NotNullprops.find("__call"); if (it != mtt->props.end()) { - if (FFlag::LuauSolverAgnosticStringification) - { - return it->second.readTy; - } - else - { - if (FFlag::LuauSolverV2) - return it->second.readTy; - else - return it->second.type_DEPRECATED(); - } + return it->second.readTy; } else return std::nullopt; diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 5d48801b..0fdf09aa 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -46,6 +46,7 @@ LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) +LUAU_FASTFLAGVARIABLE(LuauBatchedExecuteTask) namespace Luau { @@ -77,7 +78,8 @@ struct BuildQueueItem struct BuildQueueWorkState { - std::function task)> executeTask; + std::function task)> executeTask_DEPRECATED; + std::function> tasks)> executeTasks; std::vector buildQueueItems; @@ -547,7 +549,7 @@ void Frontend::queueModuleCheck(const ModuleName& name) moduleQueue.push_back(name); } -std::vector Frontend::checkQueuedModules( +std::vector Frontend::checkQueuedModules_DEPRECATED( std::optional optionOverride, std::function task)> executeTask, std::function progress @@ -611,7 +613,7 @@ std::vector Frontend::checkQueuedModules( }; } - state->executeTask = executeTask; + state->executeTask_DEPRECATED = executeTask; state->remaining = state->buildQueueItems.size(); // Record dependencies between modules @@ -637,7 +639,7 @@ std::vector Frontend::checkQueuedModules( for (size_t i = 0; i < state->buildQueueItems.size(); i++) { if (state->buildQueueItems[i].dirtyDependencies == 0) - sendQueueItemTask(state, i); + sendQueueItemTask_DEPRECATED(state, i); } // If not a single item was found, a cycle in the graph was hit @@ -709,8 +711,208 @@ std::vector Frontend::checkQueuedModules( // Items cannot be submitted while holding the lock for (size_t i : nextItems) - sendQueueItemTask(state, i); + sendQueueItemTask_DEPRECATED(state, i); + nextItems.clear(); + + if (state->processing == 0) + { + // Typechecking might have been cancelled by user, don't return partial results + if (cancelled) + return {}; + + // We might have stopped because of a pending exception + if (itemWithException) + recordItemResult(state->buildQueueItems[*itemWithException]); + } + + // If we aren't done, but don't have anything processing, we hit a cycle + if (state->remaining != 0 && state->processing == 0) + sendQueueCycleItemTask(state); + } + + std::vector checkedModules; + checkedModules.reserve(state->buildQueueItems.size()); + + for (size_t i = 0; i < state->buildQueueItems.size(); i++) + checkedModules.push_back(std::move(state->buildQueueItems[i].name)); + + return checkedModules; +} + +std::vector Frontend::checkQueuedModules( + std::optional optionOverride, + std::function> tasks)> executeTasks, + std::function progress +) +{ + FrontendOptions frontendOptions = optionOverride.value_or(options); + if (getLuauSolverMode() == SolverMode::New) + frontendOptions.forAutocomplete = false; + + // By taking data into locals, we make sure queue is cleared at the end, even if an ICE or a different exception is thrown + std::vector currModuleQueue; + std::swap(currModuleQueue, moduleQueue); + + DenseHashSet seen{{}}; + + std::shared_ptr state = std::make_shared(); + + for (const ModuleName& name : currModuleQueue) + { + if (seen.contains(name)) + continue; + + if (!isDirty(name, frontendOptions.forAutocomplete)) + { + seen.insert(name); + continue; + } + + std::vector queue; + bool cycleDetected = parseGraph( + queue, + name, + frontendOptions.forAutocomplete, + [&seen](const ModuleName& name) + { + return seen.contains(name); + } + ); + + addBuildQueueItems(state->buildQueueItems, queue, cycleDetected, seen, frontendOptions); + } + + if (state->buildQueueItems.empty()) + return {}; + + // We need a mapping from modules to build queue slots + std::unordered_map moduleNameToQueue; + + for (size_t i = 0; i < state->buildQueueItems.size(); i++) + { + BuildQueueItem& item = state->buildQueueItems[i]; + moduleNameToQueue[item.name] = i; + } + + // Default task execution is single-threaded and immediate + if (!executeTasks) + { + executeTasks = [](std::vector> tasks) + { + for (auto& task : tasks) + task(); + }; + } + + state->executeTasks = executeTasks; + state->remaining = state->buildQueueItems.size(); + + // Record dependencies between modules + for (size_t i = 0; i < state->buildQueueItems.size(); i++) + { + BuildQueueItem& item = state->buildQueueItems[i]; + + for (const ModuleName& dep : item.sourceNode->requireSet) + { + if (auto it = sourceNodes.find(dep); it != sourceNodes.end()) + { + if (it->second->hasDirtyModule(frontendOptions.forAutocomplete)) + { + item.dirtyDependencies++; + + state->buildQueueItems[moduleNameToQueue[dep]].reverseDeps.push_back(i); + } + } + } + } + + std::vector nextItems; + + // In the first pass, check all modules with no pending dependencies + for (size_t i = 0; i < state->buildQueueItems.size(); i++) + { + if (state->buildQueueItems[i].dirtyDependencies == 0) + nextItems.push_back(i); + } + + if (!nextItems.empty()) + { + sendQueueItemTasks(state, nextItems); nextItems.clear(); + } + + // If not a single item was found, a cycle in the graph was hit + if (state->processing == 0) + sendQueueCycleItemTask(state); + + std::optional itemWithException; + bool cancelled = false; + + while (state->remaining != 0) + { + { + std::unique_lock guard(state->mtx); + + // If nothing is ready yet, wait + state->cv.wait( + guard, + [state] + { + return !state->readyQueueItems.empty(); + } + ); + + // Handle checked items + for (size_t i : state->readyQueueItems) + { + const BuildQueueItem& item = state->buildQueueItems[i]; + + // If exception was thrown, stop adding new items and wait for processing items to complete + if (item.exception) + itemWithException = i; + + if (item.module && item.module->cancelled) + cancelled = true; + + if (itemWithException || cancelled) + break; + + recordItemResult(item); + + // Notify items that were waiting for this dependency + for (size_t reverseDep : item.reverseDeps) + { + BuildQueueItem& reverseDepItem = state->buildQueueItems[reverseDep]; + + LUAU_ASSERT(reverseDepItem.dirtyDependencies != 0); + reverseDepItem.dirtyDependencies--; + + // In case of a module cycle earlier, check if unlocked an item that was already processed + if (!reverseDepItem.processing && reverseDepItem.dirtyDependencies == 0) + nextItems.push_back(reverseDep); + } + } + + LUAU_ASSERT(state->processing >= state->readyQueueItems.size()); + state->processing -= state->readyQueueItems.size(); + + LUAU_ASSERT(state->remaining >= state->readyQueueItems.size()); + state->remaining -= state->readyQueueItems.size(); + state->readyQueueItems.clear(); + } + + if (progress) + { + if (!progress(state->buildQueueItems.size() - state->remaining, state->buildQueueItems.size())) + cancelled = true; + } + + // Items cannot be submitted while holding the lock + if (!nextItems.empty()) + { + sendQueueItemTasks(state, nextItems); + nextItems.clear(); + } if (state->processing == 0) { @@ -1228,7 +1430,7 @@ void Frontend::performQueueItemTask(std::shared_ptr state, state->cv.notify_one(); } -void Frontend::sendQueueItemTask(std::shared_ptr state, size_t itemPos) +void Frontend::sendQueueItemTask_DEPRECATED(std::shared_ptr state, size_t itemPos) { BuildQueueItem& item = state->buildQueueItems[itemPos]; @@ -1237,7 +1439,7 @@ void Frontend::sendQueueItemTask(std::shared_ptr state, siz state->processing++; - state->executeTask( + state->executeTask_DEPRECATED( [this, state, itemPos]() { performQueueItemTask(state, itemPos); @@ -1245,6 +1447,30 @@ void Frontend::sendQueueItemTask(std::shared_ptr state, siz ); } +void Frontend::sendQueueItemTasks(std::shared_ptr state, const std::vector& items) +{ + std::vector> tasks; + tasks.reserve(items.size()); + + for (size_t itemPos : items) + { + BuildQueueItem& item = state->buildQueueItems[itemPos]; + + LUAU_ASSERT(!item.processing); + item.processing = true; + + tasks.emplace_back( + [this, state, itemPos]() + { + performQueueItemTask(state, itemPos); + } + ); + } + + state->processing += items.size(); + state->executeTasks(std::move(tasks)); +} + void Frontend::sendQueueCycleItemTask(std::shared_ptr state) { for (size_t i = 0; i < state->buildQueueItems.size(); i++) @@ -1253,7 +1479,10 @@ void Frontend::sendQueueCycleItemTask(std::shared_ptr state if (!item.processing) { - sendQueueItemTask(std::move(state), i); + if (FFlag::LuauBatchedExecuteTask) + sendQueueItemTasks(std::move(state), {i}); + else + sendQueueItemTask_DEPRECATED(std::move(state), i); break; } } diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 1ad5eb96..14e74d88 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -22,9 +22,9 @@ LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000) LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTFLAG(LuauPassBindableGenericsByReference) namespace Luau { @@ -2925,20 +2925,17 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) return NormalizationResult::HitLimits; - if (FFlag::LuauNormalizationReorderFreeTypeIntersect) + for (auto& [tyvar, inter] : there.tyvars) { - for (auto& [tyvar, inter] : there.tyvars) + int index = tyvarIndex(tyvar); + if (ignoreSmallerTyvars < index) { - int index = tyvarIndex(tyvar); - if (ignoreSmallerTyvars < index) + auto [found, fresh] = here.tyvars.emplace(tyvar, std::make_unique(NormalizedType{builtinTypes})); + if (fresh) { - auto [found, fresh] = here.tyvars.emplace(tyvar, std::make_unique(NormalizedType{builtinTypes})); - if (fresh) - { - NormalizationResult res = unionNormals(*found->second, here, index); - if (res != NormalizationResult::True) - return res; - } + NormalizationResult res = unionNormals(*found->second, here, index); + if (res != NormalizationResult::True) + return res; } } } @@ -2955,24 +2952,6 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor intersectFunctions(here.functions, there.functions); intersectTables(here.tables, there.tables); - if (!FFlag::LuauNormalizationReorderFreeTypeIntersect) - { - for (auto& [tyvar, inter] : there.tyvars) - { - int index = tyvarIndex(tyvar); - if (ignoreSmallerTyvars < index) - { - auto [found, fresh] = here.tyvars.emplace(tyvar, std::make_unique(NormalizedType{builtinTypes})); - if (fresh) - { - NormalizationResult res = unionNormals(*found->second, here, index); - if (res != NormalizationResult::True) - return res; - } - } - } - } - for (auto it = here.tyvars.begin(); it != here.tyvars.end();) { TypeId tyvar = it->first; @@ -3456,7 +3435,8 @@ bool isSubtype( { Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; - return subtyping.isSubtype(subPack, superPack, scope).isSubtype; + return FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(subPack, superPack, scope, {}).isSubtype + : subtyping.isSubtype_DEPRECATED(subPack, superPack, scope).isSubtype; } else { @@ -3473,7 +3453,8 @@ bool isSubtype( { Subtyping subtyping{builtinTypes, NotNull{&arena}, simplifier, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&ice}}; - return subtyping.isSubtype(subPack, superPack, scope).isSubtype; + return FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(subPack, superPack, scope, {}).isSubtype + : subtyping.isSubtype_DEPRECATED(subPack, superPack, scope).isSubtype; } else { diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 84e436fb..05987ef2 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -17,6 +17,8 @@ LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAGVARIABLE(LuauFilterOverloadsByArity) +LUAU_FASTFLAG(LuauPassBindableGenericsByReference) namespace Luau { @@ -52,57 +54,88 @@ std::pair OverloadResolver::selectOverload( bool useFreeTypeBounds ) { - auto tryOne = [&](TypeId f) + if (FFlag::LuauFilterOverloadsByArity) { - if (auto ftv = get(f)) + TypeId t = follow(ty); + + if (const FunctionType* fn = get(t)) { - Subtyping::Variance variance = subtyping.variance; - subtyping.variance = Subtyping::Variance::Contravariant; - subtyping.uniqueTypes = uniqueTypes; - SubtypingResult r; - if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + if (testFunctionTypeForOverloadSelection(fn, uniqueTypes, argsPack, useFreeTypeBounds)) + return {Analysis::Ok, ty}; + else + return {Analysis::OverloadIsNonviable, ty}; + } + else if (auto it = get(t)) + { + for (TypeId component : it) { - std::vector generics; - generics.reserve(ftv->generics.size()); - for (TypeId g : ftv->generics) + const FunctionType* fn = get(follow(component)); + // Only consider function overloads with compatible arities + if (!fn || !isArityCompatible(argsPack, fn->argTypes, builtinTypes)) + continue; + + if (testFunctionTypeForOverloadSelection(fn, uniqueTypes, argsPack, useFreeTypeBounds)) + return {Analysis::Ok, component}; + } + } + + return {Analysis::OverloadIsNonviable, ty}; + } + else + { + auto tryOne = [&](TypeId f) + { + if (auto ftv = get(f)) + { + Subtyping::Variance variance = subtyping.variance; + subtyping.variance = Subtyping::Variance::Contravariant; + subtyping.uniqueTypes = uniqueTypes; + SubtypingResult r; + if (FFlag::LuauSubtypingGenericsDoesntUseVariance) { - g = follow(g); - if (get(g)) - generics.emplace_back(g); + std::vector generics; + generics.reserve(ftv->generics.size()); + for (TypeId g : ftv->generics) + { + g = follow(g); + if (get(g)) + generics.emplace_back(g); + } + r = FFlag::LuauPassBindableGenericsByReference + ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, generics) + : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope, generics); } - r = subtyping.isSubtype( - argsPack, ftv->argTypes, scope, !generics.empty() ? std::optional>{generics} : std::nullopt - ); - } - else - r = subtyping.isSubtype(argsPack, ftv->argTypes, scope); - subtyping.variance = variance; + else + r = FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, {}) + : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope); + subtyping.variance = variance; - if (!useFreeTypeBounds && !r.assumedConstraints.empty()) - return false; + if (!useFreeTypeBounds && !r.assumedConstraints.empty()) + return false; - if (r.isSubtype) - return true; - } + if (r.isSubtype) + return true; + } - return false; - }; + return false; + }; - TypeId t = follow(ty); + TypeId t = follow(ty); - if (tryOne(ty)) - return {Analysis::Ok, ty}; + if (tryOne(ty)) + return {Analysis::Ok, ty}; - if (auto it = get(t)) - { - for (TypeId component : it) + if (auto it = get(t)) { - if (tryOne(component)) - return {Analysis::Ok, component}; + for (TypeId component : it) + { + if (tryOne(component)) + return {Analysis::Ok, component}; + } } - } - return {Analysis::OverloadIsNonviable, ty}; + return {Analysis::OverloadIsNonviable, ty}; + } } void OverloadResolver::resolve( @@ -128,6 +161,20 @@ void OverloadResolver::resolve( if (resolution.find(ty) != resolution.end()) continue; + if (FFlag::LuauFilterOverloadsByArity) + { + if (const FunctionType* fn = get(follow(ty))) + { + // If the overload isn't arity compatible, report the mismatch and don't do more work + const TypePackId argPack = arena->addTypePack(*args); + if (!isArityCompatible(argPack, fn->argTypes, builtinTypes)) + { + add(ArityMismatch, ty, {}); + continue; + } + } + } + auto [analysis, errors] = checkOverload(ty, args, selfExpr, argExprs, uniqueTypes); add(analysis, ty, std::move(errors)); } @@ -165,7 +212,8 @@ std::optional OverloadResolver::testIsSubtype(const Location& location std::optional OverloadResolver::testIsSubtype(const Location& location, TypePackId subTy, TypePackId superTy) { - auto r = subtyping.isSubtype(subTy, superTy, scope); + auto r = FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(subTy, superTy, scope, {}) + : subtyping.isSubtype_DEPRECATED(subTy, superTy, scope); ErrorVec errors; if (r.normalizationTooComplex) @@ -272,6 +320,87 @@ void OverloadResolver::maybeEmplaceError( } } +bool OverloadResolver::isArityCompatible(const TypePackId candidate, const TypePackId desired, NotNull builtinTypes) const +{ + LUAU_ASSERT(FFlag::LuauFilterOverloadsByArity); + + auto [candidateHead, candidateTail] = flatten(candidate); + auto [desiredHead, desiredTail] = flatten(desired); + + // Handle mismatched head sizes + if (candidateHead.size() < desiredHead.size()) + { + if (candidateTail) + return true; // A tail can fill in remaining values + + // If the candidate is shorter than desired and has no tail, it can only match if the extra desired args are all optional + for (size_t i = candidateHead.size(); i < desiredHead.size(); ++i) + { + if (const TypeId ty = follow(desiredHead[i]); !isOptionalType(ty, builtinTypes)) + return false; + } + } + + if (desiredTail && candidateHead.size() <= desiredHead.size() && !candidateTail) + { + // A non-tail candidate can't match a desired tail unless the tail accepts nils + // We don't allow generic packs to implicitly accept an empty pack here + TypePackId desiredTailTP = follow(*desiredTail); + + if (desiredTailTP == builtinTypes->unknownTypePack || desiredTailTP == builtinTypes->anyTypePack) + return true; + + if (const VariadicTypePack* vtp = get(desiredTailTP)) + return vtp->ty == builtinTypes->nilType; + + return false; + } + + // There aren't any other failure conditions; we don't care if we pass more args than needed + + return true; +} + +bool OverloadResolver::testFunctionTypeForOverloadSelection( + const FunctionType* ftv, + NotNull> uniqueTypes, + TypePackId argsPack, + bool useFreeTypeBounds +) +{ + LUAU_ASSERT(FFlag::LuauFilterOverloadsByArity); + + Subtyping::Variance variance = subtyping.variance; + subtyping.variance = Subtyping::Variance::Contravariant; + subtyping.uniqueTypes = uniqueTypes; + SubtypingResult r; + if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + { + std::vector generics; + generics.reserve(ftv->generics.size()); + for (TypeId g : ftv->generics) + { + g = follow(g); + if (get(g)) + generics.emplace_back(g); + } + r = FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, generics) + : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope, generics); + } + else + r = FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, {}) + : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope); + subtyping.variance = variance; + + if (!useFreeTypeBounds && !r.assumedConstraints.empty()) + return false; + + if (r.isSubtype) + return true; + + return false; +} + std::pair OverloadResolver::checkOverload_( TypeId fnTy, const FunctionType* fn, @@ -638,7 +767,8 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors) nonFunctions.push_back(ty); break; case ArityMismatch: - LUAU_ASSERT(!errors.empty()); + if (!FFlag::LuauFilterOverloadsByArity) + LUAU_ASSERT(!errors.empty()); arityMismatches.emplace_back(ty, std::move(errors)); break; case OverloadIsNonviable: diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 7f4e122d..46b84df6 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -23,9 +23,10 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSimplificationIterationLimit, 128) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) -LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAGVARIABLE(LuauMorePreciseExternTableRelation) LUAU_FASTFLAGVARIABLE(LuauSimplifyRefinementOfReadOnlyProperty) +LUAU_FASTFLAGVARIABLE(LuauExternTableIndexersIntersect) namespace Luau { @@ -269,6 +270,11 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen); Relation relateTableToExternType(const TableType* table, const ExternType* cls, SimplifierSeenSet& seen) { + // If either the table or the extern type have an indexer, just bail. + // There's rapidly diminishing returns on doing something smart for + // indexers compared to refining exact members. + if (FFlag::LuauExternTableIndexersIntersect && (table->indexer || cls->indexer)) + return Relation::Intersects; for (auto& [name, prop] : table->props) { @@ -472,7 +478,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) if (auto ut = get(left)) { - if (FFlag::LuauPushTypeConstraint) + if (FFlag::LuauPushTypeConstraint2) { for (TypeId part : ut) { diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index f3718315..8d45170d 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -18,6 +18,9 @@ #include "Luau/TypePath.h" #include "Luau/TypeUtils.h" +LUAU_FASTFLAGVARIABLE(LuauIndividualRecursionLimits) +LUAU_DYNAMIC_FASTINTVARIABLE(LuauSubtypingRecursionLimit, 100) + LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping3) @@ -30,6 +33,8 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericPacksDoesntUseVariance) LUAU_FASTFLAGVARIABLE(LuauSubtypingUnionsAndIntersectionsInGenericBounds) LUAU_FASTFLAGVARIABLE(LuauIndexInMetatableSubtyping) LUAU_FASTFLAGVARIABLE(LuauSubtypingPackRecursionLimits) +LUAU_FASTFLAGVARIABLE(LuauSubtypingPrimitiveAndGenericTableTypes) +LUAU_FASTFLAGVARIABLE(LuauPassBindableGenericsByReference) namespace Luau { @@ -822,9 +827,62 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull scope, const std::vector& bindableGenerics) +{ + LUAU_ASSERT(FFlag::LuauPassBindableGenericsByReference); + + SubtypingEnvironment env; + if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + { + for (TypeId g : bindableGenerics) + env.mappedGenerics[follow(g)] = {SubtypingEnvironment::GenericBounds{}}; + } + + SubtypingResult result = isCovariantWith(env, subTp, superTp, scope); + + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + if (!env.mappedGenericPacks_DEPRECATED.empty()) + result.mappedGenericPacks_DEPRECATED = std::move(env.mappedGenericPacks_DEPRECATED); + } + + if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + { + for (TypeId bg : bindableGenerics) + { + bg = follow(bg); + + LUAU_ASSERT(env.mappedGenerics.contains(bg)); + + if (const std::vector* bounds = env.mappedGenerics.find(bg)) + { + // Bounds should have exactly one entry + LUAU_ASSERT(bounds->size() == 1); + if (FFlag::LuauSubtypingReportGenericBoundMismatches2) + { + if (bounds->empty()) + continue; + if (const GenericType* gen = get(bg)) + result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name)); + } + else if (!bounds->empty()) + result.andAlso(checkGenericBounds_DEPRECATED(bounds->back(), env, scope)); + } + } + } + + return result; +} -SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull scope, std::optional> bindableGenerics) +SubtypingResult Subtyping::isSubtype_DEPRECATED( + TypePackId subTp, + TypePackId superTp, + NotNull scope, + std::optional> bindableGenerics +) { + LUAU_ASSERT(!FFlag::LuauPassBindableGenericsByReference); + SubtypingEnvironment env; if (FFlag::LuauSubtypingGenericsDoesntUseVariance && bindableGenerics) { @@ -931,9 +989,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub { UnifierCounters& counters = normalizer->sharedState->counters; RecursionCounter rc(&counters.recursionCount); - - if (counters.recursionLimit > 0 && counters.recursionLimit < counters.recursionCount) - return SubtypingResult{false, true}; + if (FFlag::LuauIndividualRecursionLimits) + { + if (DFInt::LuauSubtypingRecursionLimit > 0 && DFInt::LuauSubtypingRecursionLimit < counters.recursionCount) + return SubtypingResult{false, true}; + } + else + { + if (counters.recursionLimit > 0 && counters.recursionLimit < counters.recursionCount) + return SubtypingResult{false, true}; + } subTy = follow(subTy); superTy = follow(superTy); @@ -1298,8 +1363,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId { rc.emplace(&counters.recursionCount); - if (counters.recursionLimit > 0 && counters.recursionLimit < counters.recursionCount) - return SubtypingResult{false, true}; + if (FFlag::LuauIndividualRecursionLimits) + { + if (DFInt::LuauSubtypingRecursionLimit > 0 && counters.recursionCount > DFInt::LuauSubtypingRecursionLimit) + return SubtypingResult{false, true}; + } + else + { + if (counters.recursionLimit > 0 && counters.recursionLimit < counters.recursionCount) + return SubtypingResult{false, true}; + } } subTp = follow(subTp); @@ -2680,7 +2753,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim } else if (subPrim->type == PrimitiveType::Table) { - const bool isSubtype = superTable->props.empty() && !superTable->indexer.has_value(); + const bool isSubtype = FFlag::LuauSubtypingPrimitiveAndGenericTableTypes + ? superTable->props.empty() && (!superTable->indexer.has_value() || superTable->state == TableState::Generic) + : superTable->props.empty() && !superTable->indexer.has_value(); return {isSubtype}; } diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index 357f3f1e..b76f33e0 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -4,6 +4,7 @@ #include "Luau/Ast.h" #include "Luau/Common.h" +#include "Luau/HashUtil.h" #include "Luau/Simplify.h" #include "Luau/Subtyping.h" #include "Luau/Type.h" @@ -12,6 +13,7 @@ #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" +LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintIntersection) namespace Luau { @@ -25,25 +27,27 @@ struct BidirectionalTypePusher NotNull> astTypes; NotNull> astExpectedTypes; - NotNull builtinTypes; - NotNull arena; + NotNull solver; + NotNull constraint; NotNull unifier; NotNull subtyping; std::vector incompleteInferences; + DenseHashSet, PairHash> seen{{nullptr, nullptr}}; + BidirectionalTypePusher( NotNull> astTypes, NotNull> astExpectedTypes, - NotNull builtinTypes, - NotNull arena, + NotNull solver, + NotNull constraint, NotNull unifier, NotNull subtyping ) : astTypes{astTypes} , astExpectedTypes{astExpectedTypes} - , builtinTypes{builtinTypes} - , arena{arena} + , solver{solver} + , constraint{constraint} , unifier{unifier} , subtyping{subtyping} { @@ -54,11 +58,18 @@ struct BidirectionalTypePusher if (!astTypes->contains(expr)) { LUAU_ASSERT(false); - return builtinTypes->errorType; + return solver->builtinTypes->errorType; } TypeId exprType = *astTypes->find(expr); + if (FFlag::LuauPushTypeConstraintIntersection) + { + if (seen.contains({expectedType, expr})) + return exprType; + seen.insert({expectedType, expr}); + } + expectedType = follow(expectedType); exprType = follow(exprType); @@ -110,14 +121,15 @@ struct BidirectionalTypePusher if (expr->is()) { auto ft = get(exprType); - if (ft && get(ft->lowerBound) && fastIsSubtype(builtinTypes->stringType, ft->upperBound) && - fastIsSubtype(ft->lowerBound, builtinTypes->stringType)) + if (ft && get(ft->lowerBound) && fastIsSubtype(solver->builtinTypes->stringType, ft->upperBound) && + fastIsSubtype(ft->lowerBound, solver->builtinTypes->stringType)) { // if the upper bound is a subtype of the expected type, we can push the expected type in Relation upperBoundRelation = relate(ft->upperBound, expectedType); if (upperBoundRelation == Relation::Subset || upperBoundRelation == Relation::Coincident) { emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); return exprType; } @@ -128,6 +140,7 @@ struct BidirectionalTypePusher if (lowerBoundRelation == Relation::Subset || lowerBoundRelation == Relation::Coincident) { emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); return exprType; } } @@ -135,14 +148,15 @@ struct BidirectionalTypePusher else if (expr->is()) { auto ft = get(exprType); - if (ft && get(ft->lowerBound) && fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && - fastIsSubtype(ft->lowerBound, builtinTypes->booleanType)) + if (ft && get(ft->lowerBound) && fastIsSubtype(solver->builtinTypes->booleanType, ft->upperBound) && + fastIsSubtype(ft->lowerBound, solver->builtinTypes->booleanType)) { // if the upper bound is a subtype of the expected type, we can push the expected type in Relation upperBoundRelation = relate(ft->upperBound, expectedType); if (upperBoundRelation == Relation::Subset || upperBoundRelation == Relation::Coincident) { emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); return exprType; } @@ -153,6 +167,7 @@ struct BidirectionalTypePusher if (lowerBoundRelation == Relation::Subset || lowerBoundRelation == Relation::Coincident) { emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); return exprType; } } @@ -164,6 +179,7 @@ struct BidirectionalTypePusher if (auto ft = get(exprType); ft && fastIsSubtype(ft->upperBound, expectedType)) { emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); return exprType; } @@ -179,6 +195,8 @@ struct BidirectionalTypePusher // TODO: Push argument / return types into the lambda. return exprType; + // TODO: CLI-169235: This probably ought to use the same logic as + // `index` to determine what the type of a given member is. if (auto exprTable = expr->as()) { const TableType* expectedTableTy = get(expectedType); @@ -189,11 +207,21 @@ struct BidirectionalTypePusher { std::vector parts{begin(utv), end(utv)}; - std::optional tt = extractMatchingTableType(parts, exprType, builtinTypes); + std::optional tt = extractMatchingTableType(parts, exprType, solver->builtinTypes); if (tt) (void)pushType(*tt, expr); } + else if (auto itv = get(expectedType); FFlag::LuauPushTypeConstraintIntersection && itv) + { + for (const auto part : itv) + (void)pushType(part, expr); + + // Reset the expected type for this expression prior, + // otherwise the expected type will be the last part + // of the intersection, which does not seem ideal. + (*astExpectedTypes)[expr] = expectedType; + } return exprType; } @@ -217,7 +245,7 @@ struct BidirectionalTypePusher // { foo = bar } // // Then the intent is probably to push `T` into `bar`. - if (expectedTableTy->indexer && fastIsSubtype(builtinTypes->stringType, expectedTableTy->indexer->indexType)) + if (expectedTableTy->indexer && fastIsSubtype(solver->builtinTypes->stringType, expectedTableTy->indexer->indexType)) (void)pushType(expectedTableTy->indexer->indexResultType, item.value); // If it's just an extra property and the expected type @@ -243,7 +271,7 @@ struct BidirectionalTypePusher { if (expectedTableTy->indexer) { - unifier->unify(expectedTableTy->indexer->indexType, builtinTypes->numberType); + unifier->unify(expectedTableTy->indexer->indexType, solver->builtinTypes->numberType); (void)pushType(expectedTableTy->indexer->indexResultType, item.value); } } @@ -274,15 +302,15 @@ struct BidirectionalTypePusher PushTypeResult pushTypeInto( NotNull> astTypes, NotNull> astExpectedTypes, - NotNull builtinTypes, - NotNull arena, + NotNull solver, + NotNull constraint, NotNull unifier, NotNull subtyping, TypeId expectedType, const AstExpr* expr ) { - BidirectionalTypePusher btp{astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping}; + BidirectionalTypePusher btp{astTypes, astExpectedTypes, solver, constraint, unifier, subtyping}; (void)btp.pushType(expectedType, expr); return {std::move(btp.incompleteInferences)}; } diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 2054b18c..8b832a39 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -21,7 +21,6 @@ LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) /* @@ -89,27 +88,10 @@ struct FindCyclicTypes final : TypeVisitor { if (!visited.insert(ty)) return false; - if (FFlag::LuauSolverAgnosticStringification) - { - LUAU_ASSERT(ft.lowerBound); - LUAU_ASSERT(ft.upperBound); - traverse(ft.lowerBound); - traverse(ft.upperBound); - } - else if (FFlag::LuauSolverV2) - { - // TODO: Replace these if statements with assert()s when we - // delete FFlag::LuauSolverV2. - // - // When the old solver is used, these pointers are always - // unused. When the new solver is used, they are never null. - - if (ft.lowerBound) - traverse(ft.lowerBound); - if (ft.upperBound) - traverse(ft.upperBound); - } - + LUAU_ASSERT(ft.lowerBound); + LUAU_ASSERT(ft.upperBound); + traverse(ft.lowerBound); + traverse(ft.upperBound); return false; } @@ -446,11 +428,7 @@ struct TypeStringifier void stringify(const std::string& name, const Property& prop) { - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) - return _newStringify(name, prop); - - emitKey(name); - stringify(prop.type_DEPRECATED()); + return _newStringify(name, prop); } void stringify(TypePackId tp); @@ -509,96 +487,39 @@ struct TypeStringifier state.result.invalid = true; // Free types are guaranteed to have upper and lower bounds now. - if (FFlag::LuauSolverAgnosticStringification) + LUAU_ASSERT(ftv.lowerBound); + LUAU_ASSERT(ftv.upperBound); + const TypeId lowerBound = follow(ftv.lowerBound); + const TypeId upperBound = follow(ftv.upperBound); + if (get(lowerBound) && get(upperBound)) { - LUAU_ASSERT(ftv.lowerBound); - LUAU_ASSERT(ftv.upperBound); - const TypeId lowerBound = follow(ftv.lowerBound); - const TypeId upperBound = follow(ftv.upperBound); - if (get(lowerBound) && get(upperBound)) - { - state.emit("'"); - state.emit(state.getName(ty)); - if (FInt::DebugLuauVerboseTypeNames >= 1) - state.emit(ftv.polarity); - } - else - { - state.emit("("); - if (!get(lowerBound)) - { - stringify(lowerBound); - state.emit(" <: "); - } - state.emit("'"); - state.emit(state.getName(ty)); - - if (FInt::DebugLuauVerboseTypeNames >= 1) - state.emit(ftv.polarity); - - if (!get(upperBound)) - { - state.emit(" <: "); - stringify(upperBound); - } - state.emit(")"); - } - return; + state.emit("'"); + state.emit(state.getName(ty)); + if (FInt::DebugLuauVerboseTypeNames >= 1) + state.emit(ftv.polarity); } - else if (FFlag::LuauSolverV2 && ftv.lowerBound && ftv.upperBound) + else { - const TypeId lowerBound = follow(ftv.lowerBound); - const TypeId upperBound = follow(ftv.upperBound); - if (get(lowerBound) && get(upperBound)) + state.emit("("); + if (!get(lowerBound)) { - state.emit("'"); - state.emit(state.getName(ty)); - if (FInt::DebugLuauVerboseTypeNames >= 1) - state.emit(ftv.polarity); + stringify(lowerBound); + state.emit(" <: "); } - else + state.emit("'"); + state.emit(state.getName(ty)); + + if (FInt::DebugLuauVerboseTypeNames >= 1) + state.emit(ftv.polarity); + + if (!get(upperBound)) { - state.emit("("); - if (!get(lowerBound)) - { - stringify(lowerBound); - state.emit(" <: "); - } - state.emit("'"); - state.emit(state.getName(ty)); - - if (FInt::DebugLuauVerboseTypeNames >= 1) - state.emit(ftv.polarity); - - if (!get(upperBound)) - { - state.emit(" <: "); - stringify(upperBound); - } - state.emit(")"); + state.emit(" <: "); + stringify(upperBound); } - return; - } - - if (FInt::DebugLuauVerboseTypeNames >= 1) - state.emit("free-"); - - state.emit(state.getName(ty)); - - if (FFlag::LuauSolverAgnosticStringification && FInt::DebugLuauVerboseTypeNames >= 1) - state.emit(ftv.polarity); - else if (FFlag::LuauSolverV2 && FInt::DebugLuauVerboseTypeNames >= 1) - state.emit(ftv.polarity); - - - if (FInt::DebugLuauVerboseTypeNames >= 2) - { - state.emit("-"); - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) - state.emitLevel(ftv.scope); - else - state.emit(ftv.level); + state.emit(")"); } + return; } void operator()(TypeId, const BoundType& btv) @@ -626,10 +547,7 @@ struct TypeStringifier if (FInt::DebugLuauVerboseTypeNames >= 2) { state.emit("-"); - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) - state.emitLevel(gtv.scope); - else - state.emit(gtv.level); + state.emitLevel(gtv.scope); } } @@ -729,11 +647,8 @@ struct TypeStringifier state.emit(">"); } - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) - { - if (ftv.isCheckedFunction) - state.emit("@checked "); - } + if (ftv.isCheckedFunction) + state.emit("@checked "); state.emit("("); @@ -826,35 +741,16 @@ struct TypeStringifier std::string openbrace = "@@@"; std::string closedbrace = "@@@?!"; - switch (state.opts.hideTableKind - ? ((FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) ? TableState::Sealed : TableState::Unsealed) - : ttv.state) + switch (state.opts.hideTableKind ? TableState::Sealed : ttv.state) { case TableState::Sealed: - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) - { - openbrace = "{"; - closedbrace = "}"; - } - else - { - state.result.invalid = true; - openbrace = "{|"; - closedbrace = "|}"; - } + openbrace = "{"; + closedbrace = "}"; break; case TableState::Unsealed: - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) - { - state.result.invalid = true; - openbrace = "{|"; - closedbrace = "|}"; - } - else - { - openbrace = "{"; - closedbrace = "}"; - } + state.result.invalid = true; + openbrace = "{|"; + closedbrace = "|}"; break; case TableState::Free: state.result.invalid = true; @@ -1356,10 +1252,7 @@ struct TypePackStringifier if (FInt::DebugLuauVerboseTypeNames >= 2) { state.emit("-"); - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) - state.emitLevel(pack.scope); - else - state.emit(pack.level); + state.emitLevel(pack.scope); } state.emit("..."); @@ -1378,10 +1271,7 @@ struct TypePackStringifier if (FInt::DebugLuauVerboseTypeNames >= 2) { state.emit("-"); - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticStringification) - state.emitLevel(pack.scope); - else - state.emit(pack.level); + state.emitLevel(pack.scope); } state.emit("..."); diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index b48aeb92..65012feb 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -2,6 +2,7 @@ #include "Luau/Type.h" #include "Luau/BuiltinDefinitions.h" +#include "Luau/BuiltinTypeFunctions.h" #include "Luau/Common.h" #include "Luau/ConstraintSolver.h" #include "Luau/DenseHash.h" @@ -16,6 +17,7 @@ #include "Luau/VisitType.h" #include +#include #include #include #include @@ -30,8 +32,6 @@ LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) -LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticVisitType) -LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticSetType) namespace Luau { @@ -719,8 +719,7 @@ TypeId Property::type_DEPRECATED() const void Property::setType(TypeId ty) { readTy = ty; - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticSetType) - writeTy = ty; + writeTy = ty; } void Property::makeShared() @@ -1004,6 +1003,7 @@ TypeId makeStringMetatable(NotNull builtinTypes, SolverMode mode); BuiltinTypes::BuiltinTypes() : arena(new TypeArena) , debugFreezeArena(FFlag::DebugLuauFreezeArena) + , typeFunctions(std::make_unique()) , nilType(arena->addType(Type{PrimitiveType{PrimitiveType::NilType}, /*persistent*/ true})) , numberType(arena->addType(Type{PrimitiveType{PrimitiveType::Number}, /*persistent*/ true})) , stringType(arena->addType(Type{PrimitiveType{PrimitiveType::String}, /*persistent*/ true})) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index c8929b30..3e97769b 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -50,6 +50,7 @@ LUAU_FASTFLAGVARIABLE(LuauAddErrorCaseForIncompatibleTypePacks) LUAU_FASTFLAGVARIABLE(LuauAddConditionalContextForTernary) LUAU_FASTFLAGVARIABLE(LuauCheckForInWithSubtyping) LUAU_FASTFLAGVARIABLE(LuauNoOrderingTypeFunctions) +LUAU_FASTFLAG(LuauPassBindableGenericsByReference) namespace Luau { @@ -3380,7 +3381,8 @@ bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location location) { NotNull scope{findInnermostScope(location)}; - SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); + SubtypingResult r = FFlag::LuauPassBindableGenericsByReference ? subtyping->isSubtype(subTy, superTy, scope, {}) + : subtyping->isSubtype_DEPRECATED(subTy, superTy, scope); if (!isErrorSuppressing(location, subTy)) { diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 7946c744..046a6109 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -16,7 +16,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauVariadicAnyPackShouldBeErrorSuppressing) -LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAG(LuauPushTypeConstraint2) +LUAU_FASTFLAG(LuauFilterOverloadsByArity) namespace Luau { @@ -714,7 +715,7 @@ std::optional extractMatchingTableType(std::vector& tables, Type } } - if (FFlag::LuauPushTypeConstraint && fastIsSubtype(propType, expectedType)) + if (FFlag::LuauPushTypeConstraint2 && fastIsSubtype(propType, expectedType)) return ty; } } @@ -747,6 +748,34 @@ AstExpr* unwrapGroup(AstExpr* expr) return expr; } +bool isOptionalType(TypeId ty, NotNull builtinTypes) +{ + LUAU_ASSERT(FFlag::LuauFilterOverloadsByArity); + + ty = follow(ty); + + if (ty == builtinTypes->nilType || ty == builtinTypes->anyType || ty == builtinTypes->unknownType) + return true; + else if (const PrimitiveType* pt = get(ty)) + return pt->type == PrimitiveType::NilType; + else if (const UnionType* ut = get(ty)) + { + for (TypeId option : ut) + { + option = follow(option); + + if (option == builtinTypes->nilType || option == builtinTypes->anyType || option == builtinTypes->unknownType) + return true; + else if (const PrimitiveType* pt = get(option); pt && pt->type == PrimitiveType::NilType) + return true; + } + + return false; + } + + return false; +} + bool isApproximatelyFalsyType(TypeId ty) { ty = follow(ty); diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index b4b03a7a..71e6a0c4 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -20,6 +20,9 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) +LUAU_FASTFLAG(LuauIndividualRecursionLimits) +LUAU_DYNAMIC_FASTINTVARIABLE(LuauUnifierRecursionLimit, 100) + LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauLimitUnification) LUAU_FASTFLAGVARIABLE(LuauUnifyShortcircuitSomeIntersectionsAndUnions) @@ -111,7 +114,7 @@ Unifier2::Unifier2(NotNull arena, NotNull builtinTypes, , scope(scope) , ice(ice) , limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2 - , recursionLimit(FInt::LuauTypeInferRecursionLimit) + , recursionLimit(FFlag::LuauIndividualRecursionLimits ? DFInt::LuauUnifierRecursionLimit : FInt::LuauTypeInferRecursionLimit) , uninhabitedTypeFunctions(nullptr) { } @@ -128,7 +131,7 @@ Unifier2::Unifier2( , scope(scope) , ice(ice) , limits(TypeCheckLimits{}) // TODO: typecheck limits in unifier2 - , recursionLimit(FInt::LuauTypeInferRecursionLimit) + , recursionLimit(FFlag::LuauIndividualRecursionLimits ? DFInt::LuauUnifierRecursionLimit : FInt::LuauTypeInferRecursionLimit) , uninhabitedTypeFunctions(uninhabitedTypeFunctions) { } diff --git a/CLI/src/Analyze.cpp b/CLI/src/Analyze.cpp index 1a2cafb4..592e1d0e 100644 --- a/CLI/src/Analyze.cpp +++ b/CLI/src/Analyze.cpp @@ -421,9 +421,10 @@ int main(int argc, char** argv) checkedModules = frontend.checkQueuedModules( std::nullopt, - [&](std::function f) + [&](std::vector> tasks) { - scheduler.push(std::move(f)); + for (auto& task : tasks) + scheduler.push(std::move(task)); } ); } diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index e5f55f5c..c2015ed0 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -16,7 +16,6 @@ inline bool isAnalysisFlagExperimental(const char* flag) "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative "StudioReportLuauAny2", // takes telemetry data for usage of any types "LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly - "LuauNormalizationReorderFreeTypeIntersect", // requires fixes in lua-apps code, also terrifyingly "LuauSolverV2", "UseNewLuauTypeSolverDefaultEnabled", // This can change the default solver used in cli applications, so it also needs to be disabled. Will // require fixes in lua-apps code diff --git a/Sources.cmake b/Sources.cmake index d6a29aa0..ba2ff48c 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -496,6 +496,7 @@ if(TARGET Luau.UnitTest) tests/NonStrictTypeChecker.test.cpp tests/Normalize.test.cpp tests/NotNull.test.cpp + tests/OverloadResolver.test.cpp tests/Parser.test.cpp tests/RegisterCallbacks.cpp tests/RegisterCallbacks.h diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 73e626dc..184c1ad3 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -15,6 +15,8 @@ #include +LUAU_FASTFLAG(LuauResumeFix) + /* * This file contains most implementations of core Lua APIs from lua.h. * @@ -1093,10 +1095,17 @@ int lua_cpcall(lua_State* L, lua_CFunction func, void* ud) c.func = func; c.ud = ud; - int status = luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0); + if (FFlag::LuauResumeFix) + { + return luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0); + } + else + { + int status = luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0); - adjustresults(L, 0); - return status; + adjustresults(L, 0); + return status; + } } int lua_status(lua_State* L) diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index e5ae693d..2d6662cf 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -11,6 +11,8 @@ #include +LUAU_FASTFLAG(LuauStacklessPcall) + // convert a stack index to positive #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1) @@ -359,8 +361,17 @@ int luaL_callyieldable(lua_State* L, int nargs, int nresults) lua_call(L, nargs, nresults); - if (L->status == LUA_YIELD || L->status == LUA_BREAK) - return -1; // -1 is a marker for yielding from C + if (FFlag::LuauStacklessPcall) + { + // yielding means we need to propagate yield; resume will call continuation function later + if (isyielded(L)) + return C_CALL_YIELD; + } + else + { + if (L->status == LUA_YIELD || L->status == LUA_BREAK) + return -1; // -1 is a marker for yielding from C + } return cl->c.cont(L, LUA_OK); } diff --git a/VM/src/lbaselib.cpp b/VM/src/lbaselib.cpp index 62d3e553..f1b34bc1 100644 --- a/VM/src/lbaselib.cpp +++ b/VM/src/lbaselib.cpp @@ -13,6 +13,7 @@ LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauXpcallContNoYield, false) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauXpcallContErrorHandling, false) +LUAU_FASTFLAG(LuauStacklessPcall) static void writestring(const char* s, size_t l) { @@ -285,7 +286,15 @@ static void luaB_pcallrun(lua_State* L, void* ud) { StkId func = (StkId)ud; - luaD_call(L, func, LUA_MULTRET); + if (FFlag::LuauStacklessPcall) + { + // if we can yield, schedule a call setup with postponed reentry + luaD_callint(L, func, LUA_MULTRET, lua_isyieldable(L) != 0); + } + else + { + luaD_call(L, func, LUA_MULTRET); + } } static int luaB_pcally(lua_State* L) @@ -299,12 +308,21 @@ static int luaB_pcally(lua_State* L) int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0); - // necessary to accomodate functions that return lots of values + // necessary to accommodate functions that return lots of values expandstacklimit(L, L->top); - // yielding means we need to propagate yield; resume will call continuation function later - if (status == 0 && (L->status == LUA_YIELD || L->status == LUA_BREAK)) - return -1; // -1 is a marker for yielding from C + if (FFlag::LuauStacklessPcall) + { + // yielding means we need to propagate yield; resume will call continuation function later + if (status == 0 && isyielded(L)) + return C_CALL_YIELD; + } + else + { + // yielding means we need to propagate yield; resume will call continuation function later + if (status == 0 && (L->status == LUA_YIELD || L->status == LUA_BREAK)) + return -1; // -1 is a marker for yielding from C + } // immediate return (error or success) lua_rawcheckstack(L, 1); @@ -353,9 +371,18 @@ static int luaB_xpcally(lua_State* L) // necessary to accommodate functions that return lots of values expandstacklimit(L, L->top); - // yielding means we need to propagate yield; resume will call continuation function later - if (status == 0 && (L->status == LUA_YIELD || L->status == LUA_BREAK)) - return -1; // -1 is a marker for yielding from C + if (FFlag::LuauStacklessPcall) + { + // yielding means we need to propagate yield; resume will call continuation function later + if (status == 0 && isyielded(L)) + return C_CALL_YIELD; + } + else + { + // yielding means we need to propagate yield; resume will call continuation function later + if (status == 0 && (L->status == LUA_YIELD || L->status == LUA_BREAK)) + return -1; // -1 is a marker for yielding from C + } // immediate return (error or success) lua_rawcheckstack(L, 1); diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index 58a7e01a..d9b5ed2e 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -18,6 +18,8 @@ #include LUAU_DYNAMIC_FASTFLAG(LuauXpcallContErrorHandling) +LUAU_FASTFLAGVARIABLE(LuauStacklessPcall) +LUAU_FASTFLAGVARIABLE(LuauResumeFix) // keep max stack allocation request under 1GB #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) @@ -251,7 +253,7 @@ void luaD_checkCstack(lua_State* L) luaD_throw(L, LUA_ERRERR); // error while handling stack error } -static void performcall(lua_State* L, StkId func, int nresults) +static void performcall(lua_State* L, StkId func, int nresults, bool preparereentry) { if (luau_precall(L, func, nresults) == PCRLUA) { // is a Lua function? @@ -261,7 +263,17 @@ static void performcall(lua_State* L, StkId func, int nresults) L->isactive = true; luaC_threadbarrier(L); - luau_execute(L); // call it + if (FFlag::LuauStacklessPcall) + { + if (preparereentry) + L->status = SCHEDULED_REENTRY; + else + luau_execute(L); + } + else + { + luau_execute(L); // call it + } if (!oldactive) L->isactive = false; @@ -274,7 +286,7 @@ static void performcall(lua_State* L, StkId func, int nresults) ** When returns, all the results are on the stack, starting at the original ** function position. */ -void luaD_call(lua_State* L, StkId func, int nresults) +void luaD_callint(lua_State* L, StkId func, int nresults, bool preparereentry) { if (++L->nCcalls >= LUAI_MAXCCALLS) luaD_checkCstack(L); @@ -296,9 +308,9 @@ void luaD_call(lua_State* L, StkId func, int nresults) ptrdiff_t funcoffset = savestack(L, func); ptrdiff_t cioffset = saveci(L, L->ci); - performcall(L, func, nresults); + performcall(L, func, nresults, preparereentry); - bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK; + bool yielded = FFlag::LuauStacklessPcall ? isyielded(L) : L->status == LUA_YIELD || L->status == LUA_BREAK; if (fromyieldableccall) { @@ -321,6 +333,11 @@ void luaD_call(lua_State* L, StkId func, int nresults) luaC_checkGC(L); } +void luaD_call(lua_State* L, StkId func, int nresults) +{ + luaD_callint(L, func, nresults, /* preparereentry */ false); +} + // Non-yieldable version of luaD_call, used primarily to call an error handler which cannot yield void luaD_callny(lua_State* L, StkId func, int nresults) { @@ -331,9 +348,12 @@ void luaD_callny(lua_State* L, StkId func, int nresults) ptrdiff_t funcoffset = savestack(L, func); - performcall(L, func, nresults); + performcall(L, func, nresults, /* preparereentry */ false); - LUAU_ASSERT(L->status != LUA_YIELD && L->status != LUA_BREAK); + if (FFlag::LuauStacklessPcall) + LUAU_ASSERT(!isyielded(L)); + else + LUAU_ASSERT(L->status != LUA_YIELD && L->status != LUA_BREAK); if (nresults != LUA_MULTRET) L->top = restorestack(L, funcoffset) + nresults; @@ -368,11 +388,14 @@ void luaD_seterrorobj(lua_State* L, int errcode, StkId oldtop) static void resume_continue(lua_State* L) { - // unroll Lua/C combined stack, processing continuations - while (L->status == 0 && L->ci > L->base_ci) + // unroll Luau/C combined stack, processing continuations + while ((FFlag::LuauStacklessPcall ? L->status == LUA_OK || L->status == SCHEDULED_REENTRY : L->status == LUA_OK) && L->ci > L->base_ci) { LUAU_ASSERT(L->baseCcalls == L->nCcalls); + if (FFlag::LuauStacklessPcall) + L->status = LUA_OK; + Closure* cl = curr_func(L); if (cl->isC) @@ -390,7 +413,7 @@ static void resume_continue(lua_State* L) } else { - // Lua continuation; it terminates at the end of the stack or at another C continuation + // Luau continuation; it terminates at the end of the stack or at another C continuation luau_execute(L); } } @@ -400,49 +423,110 @@ static void resume(lua_State* L, void* ud) { StkId firstArg = cast_to(StkId, ud); - if (L->status == 0) + if (FFlag::LuauStacklessPcall) { - // start coroutine - LUAU_ASSERT(L->ci == L->base_ci && firstArg >= L->base); - if (firstArg == L->base) - luaG_runerror(L, "cannot resume dead coroutine"); + if (L->status == LUA_OK) + { + // start coroutine + LUAU_ASSERT(L->ci == L->base_ci && firstArg >= L->base); + if (firstArg == L->base) + luaG_runerror(L, "cannot resume dead coroutine"); - if (luau_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA) - return; + int precallresult = luau_precall(L, firstArg - 1, LUA_MULTRET); - L->ci->flags |= LUA_CALLINFO_RETURN; - } - else - { - // resume from previous yield or break - LUAU_ASSERT(firstArg >= L->base); - LUAU_ASSERT(L->status == LUA_YIELD || L->status == LUA_BREAK); - L->status = 0; + // on scheduled reentry, we will continue into the yield-continue block below + if (L->status == SCHEDULED_REENTRY) + { + firstArg = L->base; + } + else + { + // C function is either completed or yielded, exit + if (precallresult != PCRLUA) + return; - Closure* cl = curr_func(L); + // mark to not return past the current Luau function frame + L->ci->flags |= LUA_CALLINFO_RETURN; + } + } - if (cl->isC) + // restore from yield or reentry + if (L->status != LUA_OK) { - // if the top stack frame is a C call continuation, resume_continue will handle that case - if (!cl->c.cont) + // resume from previous yield or break + LUAU_ASSERT(firstArg >= L->base); + LUAU_ASSERT(isyielded(L)); + L->status = LUA_OK; + + Closure* cl = curr_func(L); + + if (cl->isC) { - // finish interrupted execution of `OP_CALL' - luau_poscall(L, firstArg); + // if the top stack frame is a C call continuation, resume_continue will handle that case + if (!cl->c.cont) + { + // finish interrupted execution of `OP_CALL' + luau_poscall(L, firstArg); + } + else + { + // restore arguments we have protected for C continuation + L->base = L->ci->base; + } } else { - // restore arguments we have protected for C continuation + // yielded inside a hook: just continue its execution L->base = L->ci->base; } } + } + else + { + if (L->status == 0) + { + // start coroutine + LUAU_ASSERT(L->ci == L->base_ci && firstArg >= L->base); + if (firstArg == L->base) + luaG_runerror(L, "cannot resume dead coroutine"); + + if (luau_precall(L, firstArg - 1, LUA_MULTRET) != PCRLUA) + return; + + L->ci->flags |= LUA_CALLINFO_RETURN; + } else { - // yielded inside a hook: just continue its execution - L->base = L->ci->base; + // resume from previous yield or break + LUAU_ASSERT(firstArg >= L->base); + LUAU_ASSERT(L->status == LUA_YIELD || L->status == LUA_BREAK); + L->status = 0; + + Closure* cl = curr_func(L); + + if (cl->isC) + { + // if the top stack frame is a C call continuation, resume_continue will handle that case + if (!cl->c.cont) + { + // finish interrupted execution of `OP_CALL' + luau_poscall(L, firstArg); + } + else + { + // restore arguments we have protected for C continuation + L->base = L->ci->base; + } + } + else + { + // yielded inside a hook: just continue its execution + L->base = L->ci->base; + } } } - // run continuations from the stack; typically resumes Lua code and pcalls + // run continuations from the stack; typically resumes Luau code and pcalls resume_continue(L); } @@ -487,9 +571,13 @@ static void resume_handle(lua_State* L, void* ud) // make sure we don't run the handler the second time ci->flags &= ~LUA_CALLINFO_HANDLE; - // restore thread status to 0 since we're handling the error + // restore thread status to LUA_OK since we're handling the error int status = L->status; - L->status = 0; + + if (FFlag::LuauResumeFix) + L->status = LUA_OK; + else + L->status = 0; // push error object to stack top if it's not already there if (status != LUA_ERRRUN) @@ -529,7 +617,7 @@ static int resume_error(lua_State* L, const char* msg, int narg) return LUA_ERRRUN; } -static void resume_finish(lua_State* L, int status) +static void resume_finish_DEPRECATED(lua_State* L, int status) { L->nCcalls = L->baseCcalls; L->isactive = false; @@ -546,11 +634,10 @@ static void resume_finish(lua_State* L, int status) } } -int lua_resume(lua_State* L, lua_State* from, int nargs) +static int resume_start(lua_State* L, lua_State* from, int nargs) { api_check(L, L->top - L->base >= nargs); - int status; if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci)) return resume_error(L, "cannot resume non-suspended coroutine", nargs); @@ -563,49 +650,142 @@ int lua_resume(lua_State* L, lua_State* from, int nargs) luaC_threadbarrier(L); - status = luaD_rawrunprotected(L, resume, L->top - nargs); + return LUA_OK; +} +static int resume_finish(lua_State* L, int status) +{ CallInfo* ch = NULL; - while (status != 0 && (ch = resume_findhandler(L)) != NULL) + while (status != LUA_OK && (ch = resume_findhandler(L)) != NULL) { + if (FFlag::LuauStacklessPcall && lua_isyieldable(L) != 0 && L->global->cb.debugprotectederror) + { + L->global->cb.debugprotectederror(L); + + // debug hook is only allowed to break + if (L->status == LUA_BREAK) + { + status = LUA_OK; + break; + } + } + L->status = cast_byte(status); status = luaD_rawrunprotected(L, resume_handle, ch); } - resume_finish(L, status); - --L->nCcalls; + // C call count base was set to an incremented value of C call count in resume, so we decrement here + L->nCcalls = --L->baseCcalls; + + // make execution context non-yieldable as we are leaving the resume + L->baseCcalls = L->nCcalls; + + L->isactive = false; + + if (status != LUA_OK) + { + L->status = cast_byte(status); + luaD_seterrorobj(L, status, L->top); + L->ci->top = L->top; + } + else if (L->status == LUA_OK) + { + expandstacklimit(L, L->top); + } + return L->status; } -int lua_resumeerror(lua_State* L, lua_State* from) +int lua_resume(lua_State* L, lua_State* from, int nargs) { - api_check(L, L->top - L->base >= 1); + if (FFlag::LuauStacklessPcall || FFlag::LuauResumeFix) + { + if (int starterror = resume_start(L, from, nargs)) + return starterror; - int status; - if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci)) - return resume_error(L, "cannot resume non-suspended coroutine", 1); + int status = luaD_rawrunprotected(L, resume, L->top - nargs); - L->nCcalls = from ? from->nCcalls : 0; - if (L->nCcalls >= LUAI_MAXCCALLS) - return resume_error(L, "C stack overflow", 1); + return resume_finish(L, status); + } + else + { + api_check(L, L->top - L->base >= nargs); - L->baseCcalls = ++L->nCcalls; - L->isactive = true; + int status; + if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci)) + return resume_error(L, "cannot resume non-suspended coroutine", nargs); - luaC_threadbarrier(L); + L->nCcalls = from ? from->nCcalls : 0; + if (L->nCcalls >= LUAI_MAXCCALLS) + return resume_error(L, "C stack overflow", nargs); - status = LUA_ERRRUN; + L->baseCcalls = ++L->nCcalls; + L->isactive = true; - CallInfo* ch = NULL; - while (status != 0 && (ch = resume_findhandler(L)) != NULL) + luaC_threadbarrier(L); + + status = luaD_rawrunprotected(L, resume, L->top - nargs); + + CallInfo* ch = NULL; + while (status != 0 && (ch = resume_findhandler(L)) != NULL) + { + L->status = cast_byte(status); + status = luaD_rawrunprotected(L, resume_handle, ch); + } + + resume_finish_DEPRECATED(L, status); + --L->nCcalls; + return L->status; + } +} + +int lua_resumeerror(lua_State* L, lua_State* from) +{ + if (FFlag::LuauStacklessPcall || FFlag::LuauResumeFix) { - L->status = cast_byte(status); - status = luaD_rawrunprotected(L, resume_handle, ch); + if (int starterror = resume_start(L, from, 1)) + return starterror; + + int status = LUA_ERRRUN; + + if (CallInfo* ci = resume_findhandler(L)) + { + L->status = cast_byte(status); + status = luaD_rawrunprotected(L, resume_handle, ci); + } + + return resume_finish(L, status); } + else + { + api_check(L, L->top - L->base >= 1); - resume_finish(L, status); - --L->nCcalls; - return L->status; + int status; + if (L->status != LUA_YIELD && L->status != LUA_BREAK && (L->status != 0 || L->ci != L->base_ci)) + return resume_error(L, "cannot resume non-suspended coroutine", 1); + + L->nCcalls = from ? from->nCcalls : 0; + if (L->nCcalls >= LUAI_MAXCCALLS) + return resume_error(L, "C stack overflow", 1); + + L->baseCcalls = ++L->nCcalls; + L->isactive = true; + + luaC_threadbarrier(L); + + status = LUA_ERRRUN; + + CallInfo* ch = NULL; + while (status != 0 && (ch = resume_findhandler(L)) != NULL) + { + L->status = cast_byte(status); + status = luaD_rawrunprotected(L, resume_handle, ch); + } + + resume_finish_DEPRECATED(L, status); + --L->nCcalls; + return L->status; + } } int lua_yield(lua_State* L, int nresults) diff --git a/VM/src/ldo.h b/VM/src/ldo.h index dce1c64f..23f25ce6 100644 --- a/VM/src/ldo.h +++ b/VM/src/ldo.h @@ -45,16 +45,27 @@ #define saveci(L, p) ((char*)(p) - (char*)L->base_ci) #define restoreci(L, n) ((CallInfo*)((char*)L->base_ci + (n))) +#define isyielded(L) ((L)->status == LUA_YIELD || (L)->status == LUA_BREAK || (L)->status == SCHEDULED_REENTRY) + // results from luaD_precall #define PCRLUA 0 // initiated a call to a Lua function #define PCRC 1 // did a call to a C function #define PCRYIELD 2 // C function yielded +// return value for a yielded C call +#define C_CALL_YIELD -1 + +// luaD_call can 'yield' into an immediate reentry +// reentry will remove extra call frames from C call stack and continue execution +// this lua_State::status code is internal and should not be used by users +#define SCHEDULED_REENTRY 0x7f + // type of protected functions, to be ran by `runprotected' typedef void (*Pfunc)(lua_State* L, void* ud); LUAI_FUNC CallInfo* luaD_growCI(lua_State* L); +LUAI_FUNC void luaD_callint(lua_State* L, StkId func, int nresults, bool forreentry); LUAI_FUNC void luaD_call(lua_State* L, StkId func, int nresults); LUAI_FUNC void luaD_callny(lua_State* L, StkId func, int nresults); LUAI_FUNC int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t oldtop, ptrdiff_t ef); diff --git a/VM/src/lvmexecute.cpp b/VM/src/lvmexecute.cpp index dc10e65f..213499e0 100644 --- a/VM/src/lvmexecute.cpp +++ b/VM/src/lvmexecute.cpp @@ -140,7 +140,7 @@ LUAU_NOINLINE void luau_callhook(lua_State* L, lua_Hook hook, void* userdata) ptrdiff_t ci_top = savestack(L, L->ci->top); int status = L->status; - // if the hook is called externally on a paused thread, we need to make sure the paused thread can emit Lua calls + // if the hook is called externally on a paused thread, we need to make sure the paused thread can emit Luau calls if (status == LUA_YIELD || status == LUA_BREAK) { L->status = 0; diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 74692ede..78dde55a 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -16,11 +16,12 @@ #include +LUAU_DYNAMIC_FASTINT(LuauSubtypingRecursionLimit) + LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauIncludeBreakContinueStatements) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSuggestHotComments) LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) @@ -2203,7 +2204,6 @@ local abc = bar(@1) TEST_CASE_FIXTURE(ACFixture, "type_correct_sealed_table") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; check(R"( local function f(a: { x: number, y: number }) return a.x + a.y end @@ -3908,6 +3908,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_subtyping_recursion_limit") return; ScopedFastInt luauTypeInferRecursionLimit{FInt::LuauTypeInferRecursionLimit, 10}; + ScopedFastInt luauSubtypingRecursionLimit{DFInt::LuauSubtypingRecursionLimit, 10}; const int parts = 100; std::string source; diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index cb4a55a1..1675cdd2 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -46,6 +46,8 @@ LUAU_DYNAMIC_FASTFLAG(LuauXpcallContNoYield) LUAU_FASTFLAG(LuauCodeGenBetterBytecodeAnalysis) LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) LUAU_FASTFLAG(LuauCodeGenRestoreFromSplitStore) +LUAU_FASTFLAG(LuauStacklessPcall) +LUAU_FASTFLAG(LuauResumeFix) static lua_CompileOptions defaultOptions() { @@ -196,7 +198,7 @@ using StateRef = std::unique_ptr; static StateRef runConformance( const char* name, void (*setup)(lua_State* L) = nullptr, - void (*yield)(lua_State* L) = nullptr, + bool (*yield)(lua_State* L) = nullptr, lua_State* initialLuaState = nullptr, lua_CompileOptions* options = nullptr, bool skipCodegen = false, @@ -291,8 +293,12 @@ static StateRef runConformance( while (yield && (status == LUA_YIELD || status == LUA_BREAK)) { - yield(L); - status = lua_resume(L, nullptr, 0); + bool resumeError = yield(L); + + if (resumeError) + status = lua_resumeerror(L, nullptr); + else + status = lua_resume(L, nullptr, 0); } luaC_validate(L); @@ -739,6 +745,9 @@ TEST_CASE("Literals") TEST_CASE("Errors") { + ScopedFastFlag luauXpcallContErrorHandling{DFFlag::LuauXpcallContErrorHandling, true}; + ScopedFastFlag luauStacklessPcall{FFlag::LuauStacklessPcall, true}; + runConformance("errors.luau"); } @@ -838,6 +847,7 @@ static int cxxthrow(lua_State* L) TEST_CASE("PCall") { ScopedFastFlag luauXpcallContErrorHandling{DFFlag::LuauXpcallContErrorHandling, true}; + ScopedFastFlag luauStacklessPcall{FFlag::LuauStacklessPcall, true}; runConformance( "pcall.luau", @@ -1380,7 +1390,7 @@ TEST_CASE("Debugger") ); lua_setglobal(L, "breakpoint"); }, - [](lua_State* L) + [](lua_State* L) -> bool { CHECK(breakhits % 2 == 1); @@ -1478,6 +1488,8 @@ TEST_CASE("Debugger") lua_resume(interruptedthread, nullptr, 0); interruptedthread = nullptr; } + + return false; }, nullptr, &copts, @@ -1514,7 +1526,7 @@ TEST_CASE("InterruptInspection") skipbreak = !skipbreak; }; }, - [](lua_State* L) + [](lua_State* L) -> bool { // Debug info can be retrieved from every location lua_Debug ar = {}; @@ -1529,6 +1541,8 @@ TEST_CASE("InterruptInspection") }, nullptr ); + + return false; }, nullptr, nullptr, @@ -1609,7 +1623,7 @@ TEST_CASE("NDebugGetUpValue") runConformance( "ndebug_upvalues.luau", nullptr, - [](lua_State* L) + [](lua_State* L) -> bool { lua_checkstack(L, LUA_MINSTACK); @@ -1624,6 +1638,8 @@ TEST_CASE("NDebugGetUpValue") CHECK(strcmp(u, "") == 0); CHECK(lua_tointeger(L, -1) == 5); lua_pop(L, 2); + + return false; }, nullptr, &copts, @@ -1855,6 +1871,8 @@ static int cpcallTest(lua_State* L) TEST_CASE("ApiCalls") { + ScopedFastFlag luauResumeFix{FFlag::LuauResumeFix, true}; + StateRef globalState = runConformance("apicalls.luau", nullptr, nullptr, lua_newstate(limitedRealloc, nullptr)); lua_State* L = globalState.get(); @@ -1869,17 +1887,49 @@ TEST_CASE("ApiCalls") lua_pop(L, 1); } + // lua_call variadic + { + lua_getfield(L, LUA_GLOBALSINDEX, "getnresults"); + lua_pushinteger(L, 200); + lua_call(L, 1, LUA_MULTRET); + CHECK(lua_gettop(L) == 200); + lua_pop(L, 200); + } + // lua_pcall { lua_getfield(L, LUA_GLOBALSINDEX, "add"); lua_pushnumber(L, 40); lua_pushnumber(L, 2); - lua_pcall(L, 2, 1, 0); + int status = lua_pcall(L, 2, 1, 0); + CHECK(status == LUA_OK); CHECK(lua_isnumber(L, -1)); CHECK(lua_tonumber(L, -1) == 42); lua_pop(L, 1); } + // lua_pcall variadic + { + lua_getfield(L, LUA_GLOBALSINDEX, "getnresults"); + lua_pushinteger(L, 200); + int status = lua_pcall(L, 1, LUA_MULTRET, 0); + CHECK(status == LUA_OK); + CHECK(lua_gettop(L) == 200); + lua_pop(L, 200); + } + + // baselib pcall variadic + { + lua_getfield(L, LUA_GLOBALSINDEX, "pcall"); + lua_getfield(L, LUA_GLOBALSINDEX, "getnresults"); + lua_pushinteger(L, 200); + lua_call(L, 2, LUA_MULTRET); + CHECK(lua_gettop(L) == 201); + lua_pop(L, 200); + CHECK(lua_toboolean(L, -1) == 1); + lua_pop(L, 1); + } + // lua_cpcall success { bool shouldFail = false; @@ -1923,6 +1973,46 @@ TEST_CASE("ApiCalls") lua_pop(L, LUAI_MAXCSTACK - 1); } + // lua_resume does not make thread yieldable + { + lua_State* L2 = lua_newthread(L); + + lua_pushcclosurek( + L2, + [](lua_State* L) + { + CHECK(lua_isyieldable(L) == 0); + return 0; + }, + nullptr, + 0, + nullptr + ); + lua_call(L2, 0, 0); + + lua_getfield(L2, LUA_GLOBALSINDEX, "getnresults"); + lua_pushinteger(L2, 1); + int status = lua_resume(L2, nullptr, 1); + REQUIRE(status == LUA_OK); + CHECK(lua_gettop(L2) == 1); + lua_pop(L2, 1); + + lua_pushcclosurek( + L2, + [](lua_State* L) + { + CHECK(lua_isyieldable(L) == 0); + return 0; + }, + nullptr, + 0, + nullptr + ); + lua_call(L2, 0, 0); + + lua_pop(L, 1); + } + // lua_equal with a sleeping thread wake up { lua_State* L2 = lua_newthread(L); @@ -1944,6 +2034,8 @@ TEST_CASE("ApiCalls") CHECK(lua_equal(L2, -1, -2) == 1); lua_pop(L2, 2); + + lua_pop(L, 1); } // lua_clonefunction + fenv @@ -2003,6 +2095,7 @@ TEST_CASE("ApiCalls") lua_getfield(L, LUA_GLOBALSINDEX, "largealloc"); int res = lua_pcall(L, 0, 0, 0); CHECK(res == LUA_ERRMEM); + lua_pop(L, 1); } // lua_pcall on OOM with an error handler @@ -2012,7 +2105,7 @@ TEST_CASE("ApiCalls") int res = lua_pcall(L, 0, 1, -2); CHECK(res == LUA_ERRMEM); CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "oops") == 0)); - lua_pop(L, 1); + lua_pop(L, 2); } // lua_pcall on OOM with an error handler that errors @@ -2022,7 +2115,7 @@ TEST_CASE("ApiCalls") int res = lua_pcall(L, 0, 1, -2); CHECK(res == LUA_ERRERR); CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "error in error handling") == 0)); - lua_pop(L, 1); + lua_pop(L, 2); } // lua_pcall on OOM with an error handler that OOMs @@ -2032,7 +2125,7 @@ TEST_CASE("ApiCalls") int res = lua_pcall(L, 0, 1, -2); CHECK(res == LUA_ERRMEM); CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "not enough memory") == 0)); - lua_pop(L, 1); + lua_pop(L, 2); } // lua_pcall on error with an error handler that OOMs @@ -2042,8 +2135,10 @@ TEST_CASE("ApiCalls") int res = lua_pcall(L, 0, 1, -2); CHECK(res == LUA_ERRERR); CHECK((lua_isstring(L, -1) && strcmp(lua_tostring(L, -1), "error in error handling") == 0)); - lua_pop(L, 1); + lua_pop(L, 2); } + + CHECK(lua_gettop(L) == 0); } TEST_CASE("ApiAtoms") @@ -2305,17 +2400,18 @@ TEST_CASE("TagMethodError") // when doLuaBreak is true the test additionally calls lua_break to ensure breaking the debugger doesn't cause the VM to crash for (bool doLuaBreak : {false, true}) { - expectedHits = {22, 32}; + expectedHits = {37, 54, 73}; static int index; static bool luaBreak; index = 0; luaBreak = doLuaBreak; - // 'yieldCallback' doesn't do anything, but providing the callback to runConformance - // ensures that the call to lua_break doesn't cause an error to be generated because - // runConformance doesn't expect the VM to be in the state LUA_BREAK. - auto yieldCallback = [](lua_State* L) {}; + // To restore from a protected error break, we return 'true' so that test runner will use lua_resumeerror + auto yieldCallback = [](lua_State* L) -> bool + { + return true; + }; runConformance( "tmerror.luau", diff --git a/tests/EqSatSimplification.test.cpp b/tests/EqSatSimplification.test.cpp index 3ac1dd60..64debe81 100644 --- a/tests/EqSatSimplification.test.cpp +++ b/tests/EqSatSimplification.test.cpp @@ -388,8 +388,9 @@ TEST_CASE_FIXTURE(ESFixture, "(number) -> string | (string) -> number") TEST_CASE_FIXTURE(ESFixture, "add") { CHECK( - "number" == - simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {getBuiltins()->numberType, getBuiltins()->numberType}})) + "number" == simplifyStr(arena->addType( + TypeFunctionInstanceType{getBuiltinTypeFunctions().addFunc, {getBuiltins()->numberType, getBuiltins()->numberType}} + )) ); } @@ -397,7 +398,7 @@ TEST_CASE_FIXTURE(ESFixture, "union") { CHECK( "number" == simplifyStr(arena->addType( - TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {getBuiltins()->numberType, getBuiltins()->numberType}} + TypeFunctionInstanceType{getBuiltinTypeFunctions().unionFunc, {getBuiltins()->numberType, getBuiltins()->numberType}} )) ); } @@ -667,7 +668,7 @@ TEST_CASE_FIXTURE(ESFixture, "{ tag: \"Part\", x: number? } & { x: string }") TEST_CASE_FIXTURE(ESFixture, "Child & add") { const TypeId u = arena->addType(UnionType{{childClass, anotherChild, getBuiltins()->stringType}}); - const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().addFunc, {u, parentClass}, {}}); + const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions().addFunc, {u, parentClass}, {}}); const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}}); @@ -677,7 +678,7 @@ TEST_CASE_FIXTURE(ESFixture, "Child & add TEST_CASE_FIXTURE(ESFixture, "Child & intersect") { const TypeId u = arena->addType(UnionType{{childClass, anotherChild, getBuiltins()->stringType}}); - const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().intersectFunc, {u, parentClass}, {}}); + const TypeId intersectTf = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions().intersectFunc, {u, parentClass}, {}}); const TypeId intersection = arena->addType(IntersectionType{{childClass, intersectTf}}); @@ -695,7 +696,7 @@ TEST_CASE_FIXTURE(ESFixture, "lt == boolean") for (const auto& [lhs, rhs] : cases) { - const TypeId tfun = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().ltFunc, {lhs, rhs}}); + const TypeId tfun = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions().ltFunc, {lhs, rhs}}); CHECK("boolean" == simplifyStr(tfun)); } } diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 247d2982..e6bc25f2 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -29,6 +29,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauForceAllNewSolverTests); +LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) extern std::optional randomSeed; // tests/main.cpp @@ -689,6 +690,11 @@ NotNull Fixture::getBuiltins() return NotNull{builtinTypes}; } +const BuiltinTypeFunctions& Fixture::getBuiltinTypeFunctions() +{ + return FFlag::LuauBuiltinTypeFunctionsArentGlobal ? *getBuiltins()->typeFunctions : builtinTypeFunctions_DEPRECATED(); +} + Frontend& Fixture::getFrontend() { if (frontend) diff --git a/tests/Fixture.h b/tests/Fixture.h index 4bd2b891..0163484a 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -186,6 +186,7 @@ struct Fixture LoadDefinitionFileResult loadDefinition(const std::string& source, bool forAutocomplete = false); // TODO: test theory about dynamic dispatch NotNull getBuiltins(); + const BuiltinTypeFunctions& getBuiltinTypeFunctions(); virtual Frontend& getFrontend(); private: diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index fc214376..a49eb7a5 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -27,7 +27,6 @@ using namespace Luau; LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauBetterReverseDependencyTracking) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule) LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) @@ -1614,7 +1613,6 @@ TEST_SUITE_BEGIN("FragmentAutocompleteTests"); TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_fragment_autocomplete") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; ToStringOptions opt; opt.exhaustive = true; opt.exhaustive = true; @@ -2991,7 +2989,6 @@ end) TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; ToStringOptions opt; opt.exhaustive = true; opt.exhaustive = true; @@ -4577,6 +4574,69 @@ end ); } +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "bidirectionally_inferred_table_member") +{ + std::string source = R"( +type Foo = { foo1: string, bar1: number } +type Bar = { foo2: boolean, bar2: string } +type Baz = { foo3: number, bar3: boolean } + +local X: Foo & Bar & Baz = {} +)"; + + std::string dest = R"( +type Foo = { foo1: string, bar1: number } +type Bar = { foo2: boolean, bar2: string } +type Baz = { foo3: number, bar3: boolean } + +local X: Foo & Bar & Baz = { f@1 } + +)"; + autocompleteFragmentInBothSolvers( + source, + dest, + '1', + [](auto& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("foo1") > 0); + CHECK(result.result->acResults.entryMap.count("foo2") > 0); + CHECK(result.result->acResults.entryMap.count("foo3") > 0); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "oss_1850") +{ + std::string source = R"( +type t = { name: "t", } | { name: "ts", person: "dog" } + +local t:t +if t.name == "ts" then +end + )"; + std::string dest = R"( +type t = { name: "t", } | { name: "ts", person: "dog" } + +local t:t +if t.name == "ts" then + t.@1 +end + )"; + + autocompleteFragmentInBothSolvers( + source, + dest, + '1', + [](auto& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("name") > 0); + CHECK(result.result->acResults.entryMap.count("person") > 0); + } + ); +} + // NOLINTEND(bugprone-unchecked-optional-access) TEST_SUITE_END(); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 83d493e5..01e93de3 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -16,7 +16,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauBatchedExecuteTask) namespace { @@ -127,7 +127,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "real_source") TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}"; fileResolver.source["game/Gui/Modules/B"] = R"( local Modules = game:GetService('Gui').Modules @@ -260,7 +259,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "cycle_detection_between_check_and_nocheck") TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/Gui/Modules/A"] = R"( --!nocheck local Modules = game:GetService('Gui').Modules @@ -374,7 +372,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "cycle_error_paths") TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/A"] = R"( return {hello = 2} )"; @@ -435,7 +432,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface_longer") TEST_CASE_FIXTURE(FrontendFixture, "cycle_incremental_type_surface_exports") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/A"] = R"( local b = require(game.B) export type atype = { x: b.btype } @@ -528,7 +524,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "dont_recheck_script_that_hasnt_been_marked_d TEST_CASE_FIXTURE(FrontendFixture, "recheck_if_dependent_script_is_dirty") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}"; fileResolver.source["game/Gui/Modules/B"] = R"( local Modules = game:GetService('Gui').Modules @@ -868,7 +863,6 @@ TEST_CASE_FIXTURE(FrontendFixture, "discard_type_graphs") TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; Frontend fe{&fileResolver, &configResolver, {false}}; fileResolver.source["Module/A"] = R"( @@ -1741,6 +1735,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_ TEST_CASE_FIXTURE(FrontendFixture, "queue_check_simple") { + ScopedFastFlag luauBatchedExecuteTask{FFlag::LuauBatchedExecuteTask, true}; + fileResolver.source["game/Gui/Modules/A"] = R"( --!strict return {hello=5, world=true} @@ -1762,6 +1758,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "queue_check_simple") TEST_CASE_FIXTURE(FrontendFixture, "queue_check_cycle_instant") { + ScopedFastFlag luauBatchedExecuteTask{FFlag::LuauBatchedExecuteTask, true}; + fileResolver.source["game/Gui/Modules/A"] = R"( --!strict local Modules = game:GetService('Gui').Modules @@ -1787,6 +1785,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "queue_check_cycle_instant") TEST_CASE_FIXTURE(FrontendFixture, "queue_check_cycle_delayed") { + ScopedFastFlag luauBatchedExecuteTask{FFlag::LuauBatchedExecuteTask, true}; + fileResolver.source["game/Gui/Modules/C"] = R"( --!strict return {c_value = 5} @@ -1818,6 +1818,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "queue_check_cycle_delayed") TEST_CASE_FIXTURE(FrontendFixture, "queue_check_propagates_ice") { + ScopedFastFlag luauBatchedExecuteTask{FFlag::LuauBatchedExecuteTask, true}; ScopedFastFlag sffs{FFlag::DebugLuauMagicTypes, true}; ModuleName mm = fromString("MainModule"); diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index ec0e66a6..d833dcd5 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -15,9 +15,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) -LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) using namespace Luau; @@ -295,7 +293,6 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "mismatched_indexers") TEST_CASE_FIXTURE(IsSubtypeFixture, "cyclic_table") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; check(R"( type A = {method: (A) -> ()} local a: A @@ -794,7 +791,6 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function_with_annotation") TEST_CASE_FIXTURE(Fixture, "cyclic_table_normalizes_sensibly") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local Cyclic = {} function Cyclic.get() @@ -855,7 +851,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_extern_types_with_intersect TEST_CASE_FIXTURE(NormalizeFixture, "intersection_of_metatables_where_the_metatable_is_top_or_bottom") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CHECK("{ @metatable *error-type*, { } }" == toString(normal("Mt<{}, any> & Mt<{}, err>"))); } @@ -948,7 +943,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "extern_types_and_never") TEST_CASE_FIXTURE(NormalizeFixture, "top_table_type") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CHECK("table" == toString(normal("{} | tbl"))); CHECK("{ }" == toString(normal("{} & tbl"))); CHECK("never" == toString(normal("number & tbl"))); @@ -1119,10 +1113,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy") TEST_CASE_FIXTURE(NormalizeFixture, "free_type_intersection_ordering") { - ScopedFastFlag sff[] = { - {FFlag::LuauSolverV2, true}, // Affects stringification of free types. - {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; // Affects stringification of free types. TypeId freeTy = arena.freshType(getBuiltins(), getGlobalScope()); TypeId orderA = arena.addType(IntersectionType{{freeTy, getBuiltins()->stringType}}); @@ -1133,7 +1124,6 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_intersection_ordering") TypeId orderB = arena.addType(IntersectionType{{getBuiltins()->stringType, freeTy}}); auto normB = normalize(orderB); REQUIRE(normB); - // Prior to LuauNormalizationReorderFreeTypeIntersect this became `never` :skull: CHECK_EQ("'a & string", toString(typeFromNormal(*normB))); } diff --git a/tests/OverloadResolver.test.cpp b/tests/OverloadResolver.test.cpp new file mode 100644 index 00000000..fec25c42 --- /dev/null +++ b/tests/OverloadResolver.test.cpp @@ -0,0 +1,165 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "doctest.h" +#include "Fixture.h" + +#include "Luau/OverloadResolution.h" +#include "Luau/Normalize.h" +#include "Luau/UnifierSharedState.h" + +LUAU_FASTFLAG(LuauFilterOverloadsByArity) + +using namespace Luau; + +struct OverloadResolverFixture : Fixture +{ + TypeArena arena; + SimplifierPtr simplifier = newSimplifier(NotNull{&arena}, getBuiltins()); + UnifierSharedState sharedState{&ice}; + Normalizer normalizer{&arena, getBuiltins(), NotNull{&sharedState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old}; + InternalErrorReporter iceReporter; + TypeCheckLimits limits; + TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}}; + Scope rootScope{getBuiltins()->emptyTypePack}; + Location callLocation; + + OverloadResolver resolver = mkResolver(); + + OverloadResolver mkResolver() + { + return OverloadResolver{ + getBuiltins(), + NotNull{&arena}, + NotNull{simplifier.get()}, + NotNull{&normalizer}, + NotNull{&typeFunctionRuntime}, + NotNull{&rootScope}, + NotNull{&iceReporter}, + NotNull{&limits}, + callLocation + }; + } + + DenseHashSet kEmptySet{nullptr}; + Location kDummyLocation; + AstExprConstantNil kDummyExpr{kDummyLocation}; + std::vector kEmptyExprs; + + TypePackId pack(std::initializer_list tys) + { + return arena.addTypePack(tys); + } + + TypePackId pack(std::initializer_list tys, TypePackVariant tail) + { + return arena.addTypePack(tys, arena.addTypePack(std::move(tail))); + } + + TypeId fn(std::initializer_list args, std::initializer_list rets) + { + return arena.addType(FunctionType{pack(args), pack(rets)}); + } + + // `&` + TypeId meet(TypeId a, TypeId b) + { + return arena.addType(IntersectionType{{a, b}}); + } + TypeId meet(std::initializer_list parts) + { + return arena.addType(IntersectionType{parts}); + } + + // (number) -> number + const TypeId numberToNumber = fn({getBuiltins()->numberType}, {getBuiltins()->numberType}); + // (number, number) -> number + const TypeId numberNumberToNumber = fn({getBuiltins()->numberType, getBuiltins()->numberType}, {getBuiltins()->numberType}); + // (number) -> string + const TypeId numberToString = fn({getBuiltins()->numberType}, {getBuiltins()->stringType}); + // (string) -> string + const TypeId stringToString = fn({getBuiltins()->stringType}, {getBuiltins()->stringType}); + + // (number) -> number & (string) -> string + const TypeId numberToNumberAndStringToString = meet(numberToNumber, stringToString); + // (number) -> number & (number, number) -> number + const TypeId numberToNumberAndNumberNumberToNumber = meet(numberToNumber, numberNumberToNumber); +}; + +TEST_SUITE_BEGIN("OverloadResolverTest"); + +TEST_CASE_FIXTURE(OverloadResolverFixture, "basic_overload_selection") +{ + // ty: (number) -> number & (string) -> string + // args: (number) + auto [analysis, overload] = + resolver.selectOverload(numberToNumberAndStringToString, pack({getBuiltins()->numberType}), NotNull{&kEmptySet}, false); + + REQUIRE_EQ(OverloadResolver::Analysis::Ok, analysis); + REQUIRE_EQ(numberToNumber, overload); +} + +TEST_CASE_FIXTURE(OverloadResolverFixture, "basic_overload_selection1") +{ + // ty: (number) -> number & (string) -> string + // args: (string) + auto [analysis, overload] = + resolver.selectOverload(numberToNumberAndStringToString, pack({getBuiltins()->stringType}), NotNull{&kEmptySet}, false); + + REQUIRE_EQ(OverloadResolver::Analysis::Ok, analysis); + REQUIRE_EQ(stringToString, overload); +} + +TEST_CASE_FIXTURE(OverloadResolverFixture, "overloads_with_different_arities") +{ + // ty: (number) -> number & (number, number) -> number + // args: (number) + auto [analysis, overload] = + resolver.selectOverload(numberToNumberAndNumberNumberToNumber, pack({getBuiltins()->numberType}), NotNull{&kEmptySet}, false); + + REQUIRE_EQ(OverloadResolver::Analysis::Ok, analysis); + REQUIRE_EQ(numberToNumber, overload); +} + +TEST_CASE_FIXTURE(OverloadResolverFixture, "overloads_with_different_arities1") +{ + // ty: (number) -> number & (number, number) -> number + // args: (number, number) + auto [analysis, overload] = resolver.selectOverload( + numberToNumberAndNumberNumberToNumber, pack({getBuiltins()->numberType, getBuiltins()->numberType}), NotNull{&kEmptySet}, false + ); + + REQUIRE_EQ(OverloadResolver::Analysis::Ok, analysis); + REQUIRE_EQ(numberNumberToNumber, overload); +} + +TEST_CASE_FIXTURE(OverloadResolverFixture, "separate_non_viable_overloads_by_arity_mismatch") +{ + ScopedFastFlag sff{FFlag::LuauFilterOverloadsByArity, true}; + + // ty: ((number)->number) & ((number)->string) & ((number, number)->number) + // args: (string) + OverloadResolver r = mkResolver(); + + const TypePack args = TypePack{{builtinTypes->stringType}, std::nullopt}; + r.resolve(meet({numberToNumber, numberToString, numberNumberToNumber}), &args, &kDummyExpr, &kEmptyExprs, NotNull{&kEmptySet}); + + CHECK(r.ok.empty()); + CHECK(r.nonFunctions.empty()); + CHECK_EQ(1, r.arityMismatches.size()); + CHECK_EQ(numberNumberToNumber, r.arityMismatches[0].first); + + CHECK_EQ(2, r.nonviableOverloads.size()); + bool numberToNumberFound = false; + bool numberToStringFound = false; + for (const auto& [ty, _] : r.nonviableOverloads) + { + if (ty == numberToNumber) + numberToNumberFound = true; + else if (ty == numberToString) + numberToStringFound = true; + } + CHECK(numberToNumberFound); + CHECK(numberToStringFound); +} + +TEST_SUITE_END(); // OverloadResolverTest diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index a4b1220c..38933d88 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -290,7 +290,7 @@ TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type") CHECK(hasError(result)); } -TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5)) +TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(1.0)) { ScopedFastFlag sff[] = { // These flags are required to surface the problem. @@ -429,7 +429,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai CHECK(frontend->stats.dynamicConstraintsCreated < 40); } -TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_should_cache_pairs_in_seen_set" * doctest::timeout(0.5)) +TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_should_cache_pairs_in_seen_set" * doctest::timeout(1.0)) { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index 00ca0a66..4d98fb28 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -683,4 +683,14 @@ TEST_CASE_FIXTURE(SimplifyFixture, "{ read x: Child } & { x: Parent }") CHECK("{ read x: Child } & { x: Parent }" == toString(intersect(leftTy, rightTy))); } +TEST_CASE_FIXTURE(SimplifyFixture, "intersect_parts_empty_table_non_empty") +{ + TypeId emptyTable = arena->addType(TableType{}); + TableType nonEmpty; + nonEmpty.props["p"] = arena->addType(UnionType{{getBuiltins()->numberType, getBuiltins()->stringType}}); + TypeId nonEmptyTable = arena->addType(std::move(nonEmpty)); + // FIXME CLI-170522: This is wrong. + CHECK("never" == toString(simplifyIntersection(getBuiltins(), arena, {nonEmptyTable, emptyTable}).result)); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 98097acf..633ed1c5 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauPassBindableGenericsByReference) using namespace Luau; @@ -75,6 +76,7 @@ struct SubtypeFixture : Fixture TypeFunctionRuntime typeFunctionRuntime{NotNull{&iceReporter}, NotNull{&limits}}; ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + ScopedFastFlag sff1{FFlag::LuauPassBindableGenericsByReference, true}; ScopePtr rootScope{new Scope(getBuiltins()->emptyTypePack)}; ScopePtr moduleScope{new Scope(rootScope)}; @@ -198,7 +200,7 @@ struct SubtypeFixture : Fixture SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy) { - return subtyping.isSubtype(subTy, superTy, NotNull{rootScope.get()}); + return subtyping.isSubtype(subTy, superTy, NotNull{rootScope.get()}, {}); } TypeId helloType = arena.addType(SingletonType{StringSingleton{"hello"}}); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index f4184f38..637b29e7 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -14,10 +14,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) -LUAU_FASTFLAG(LuauSolverAgnosticSetType) TEST_SUITE_BEGIN("ToString"); @@ -49,7 +47,6 @@ TEST_CASE_FIXTURE(Fixture, "bound_types") TEST_CASE_FIXTURE(Fixture, "free_types") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; DOES_NOT_PASS_NEW_SOLVER_GUARD(); CheckResult result = check("local a"); @@ -60,7 +57,6 @@ TEST_CASE_FIXTURE(Fixture, "free_types") TEST_CASE_FIXTURE(Fixture, "free_types_stringify_the_same_regardless_of_solver") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; TypeArena a; TypeId t = a.addType(FreeType{getFrontend().globals.globalScope.get(), getFrontend().builtinTypes->neverType, getFrontend().builtinTypes->unknownType}); @@ -70,7 +66,6 @@ TEST_CASE_FIXTURE(Fixture, "free_types_stringify_the_same_regardless_of_solver") TEST_CASE_FIXTURE(Fixture, "cyclic_table") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; Type cyclicTable{TypeVariant(TableType())}; TableType* tableOne = getMutable(&cyclicTable); tableOne->props["self"] = {&cyclicTable}; @@ -89,7 +84,6 @@ TEST_CASE_FIXTURE(Fixture, "named_table") TEST_CASE_FIXTURE(Fixture, "empty_table") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local a: {} )"); @@ -104,7 +98,6 @@ TEST_CASE_FIXTURE(Fixture, "empty_table") TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local a: { prop: string, anotherProp: number, thirdProp: boolean } )"); @@ -146,7 +139,6 @@ TEST_CASE_FIXTURE(Fixture, "long_disjunct_of_nil_is_nil_not_question_mark") TEST_CASE_FIXTURE(Fixture, "metatable") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; Type table{TypeVariant(TableType())}; Type metatable{TypeVariant(TableType())}; Type mtv{TypeVariant(MetatableType{&table, &metatable})}; @@ -180,10 +172,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "named_metatable_toStringNamedFunction") TEST_CASE_FIXTURE(BuiltinsFixture, "exhaustive_toString_of_cyclic_table") { - ScopedFastFlag sff[] = { - {FFlag::LuauSolverAgnosticStringification, true}, - {FFlag::LuauSolverAgnosticSetType, true}, - }; CheckResult result = check(R"( --!strict local Vec3 = {} @@ -337,7 +325,6 @@ TEST_CASE_FIXTURE(Fixture, "complex_unions_printed_on_multiple_lines") TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; TableType ttv{}; for (char c : std::string("abcdefghijklmno")) ttv.props[std::string(1, c)] = {getBuiltins()->numberType}; @@ -352,7 +339,6 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_is_still_capped_when_exhaustive") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; TableType ttv{}; for (char c : std::string("abcdefg")) ttv.props[std::string(1, c)] = {getBuiltins()->numberType}; @@ -367,7 +353,6 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_is_still_capped_when_exhaust TEST_CASE_FIXTURE(Fixture, "quit_stringifying_type_when_length_is_exceeded") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( function f0() end function f1(f) return f or f0 end @@ -437,7 +422,6 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_type_is_still_capped_when_exhaustive") TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table_state_braces") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; TableType ttv{TableState::Sealed, TypeLevel{}}; for (char c : std::string("abcdefghij")) ttv.props[std::string(1, c)] = {getBuiltins()->numberType}; @@ -471,7 +455,6 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_cyclic_intersection_type_bails_early") TEST_CASE_FIXTURE(Fixture, "stringifying_array_uses_array_syntax") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; TableType ttv{TableState::Sealed, TypeLevel{}}; ttv.indexer = TableIndexer{getBuiltins()->numberType, getBuiltins()->stringType}; @@ -618,7 +601,6 @@ function foo(a, b) return a(b) end TEST_CASE_FIXTURE(Fixture, "toString_the_boundTo_table_type_contained_within_a_TypePack") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; Type tv1{TableType{}}; TableType* ttv = getMutable(&tv1); ttv->state = TableState::Sealed; @@ -862,7 +844,6 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( --!strict function f1(t: {a : number, b: string, c: {d: string}}) : {a : number, b : string, c : { d : number}} @@ -935,7 +916,6 @@ TEST_CASE_FIXTURE(Fixture, "read_only_properties") TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; TypeArena arena; TypePackId thePack = arena.addTypePack({getBuiltins()->numberType, getBuiltins()->numberType}); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index c0068633..a18deabd 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauRefineOccursCheckDirectRecursion) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauRawGetHandlesNil) +LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) struct TypeFunctionFixture : Fixture { @@ -1757,13 +1758,17 @@ struct TFFixture { TypeArena arena_; NotNull arena{&arena_}; - BuiltinTypes builtinTypes_; NotNull getBuiltins() { return NotNull{&builtinTypes_}; } + NotNull getBuiltinTypeFunctions() + { + return FFlag::LuauBuiltinTypeFunctionsArentGlobal ? NotNull{builtinTypes_.typeFunctions.get()} : NotNull{&builtinTypeFunctions}; + } + ScopePtr globalScope = std::make_shared(getBuiltins()->anyTypePack); InternalErrorReporter ice; @@ -1793,7 +1798,7 @@ TEST_CASE_FIXTURE(TFFixture, "refine") { TypeId g = arena->addType(GenericType{globalScope.get(), Polarity::Negative}); - TypeId refineTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.refineFunc, {g, getBuiltins()->truthyType}}); + TypeId refineTy = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions()->refineFunc, {g, getBuiltins()->truthyType}}); FunctionGraphReductionResult res = reduceTypeFunctions(refineTy, Location{}, tfc); @@ -1809,7 +1814,7 @@ TEST_CASE_FIXTURE(TFFixture, "or<'a, 'b>") TypeId aType = arena->freshType(getBuiltins(), globalScope.get()); TypeId bType = arena->freshType(getBuiltins(), globalScope.get()); - TypeId orType = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.orFunc, {aType, bType}}); + TypeId orType = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions()->orFunc, {aType, bType}}); FunctionGraphReductionResult res = reduceTypeFunctions(orType, Location{}, tfc); @@ -1821,7 +1826,7 @@ TEST_CASE_FIXTURE(TFFixture, "a_type_function_parameterized_on_generics_is_solve TypeId a = arena->addType(GenericType{"A"}); TypeId b = arena->addType(GenericType{"B"}); - TypeId addTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {a, b}}); + TypeId addTy = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions()->addFunc, {a, b}}); reduceTypeFunctions(addTy, Location{}, tfc); @@ -1836,9 +1841,9 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_solved_tf_is_solved") TypeId a = arena->addType(GenericType{"A"}); TypeId b = arena->addType(GenericType{"B"}); - TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {a, b}}); + TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions()->addFunc, {a, b}}); - TypeId outerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.numberType, innerAddTy}}); + TypeId outerAddTy = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions()->addFunc, {builtinTypes_.numberType, innerAddTy}}); reduceTypeFunctions(outerAddTy, Location{}, tfc); @@ -1850,9 +1855,9 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_solved_tf_is_solved") TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_stuck_tf_is_stuck") { - TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.bufferType, builtinTypes_.booleanType}}); + TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions()->addFunc, {builtinTypes_.bufferType, builtinTypes_.booleanType}}); - TypeId outerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.numberType, innerAddTy}}); + TypeId outerAddTy = arena->addType(TypeFunctionInstanceType{getBuiltinTypeFunctions()->addFunc, {builtinTypes_.numberType, innerAddTy}}); reduceTypeFunctions(outerAddTy, Location{}, tfc); @@ -1873,7 +1878,7 @@ TEST_CASE_FIXTURE(TFFixture, "reduce_degenerate_refinement") TypeId root = arena->addType(BlockedType{}); TypeId refinement = arena->addType( TypeFunctionInstanceType{ - builtinTypeFunctions.refineFunc, + getBuiltinTypeFunctions()->refineFunc, { root, builtinTypes_.unknownType, diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index f875477e..9d20f364 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -9,7 +9,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauTypeFunNoScopeMapRef) LUAU_FASTFLAG(LuauInstantiateResolvedTypeFunctions) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -2447,7 +2446,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_fuzz_environment_scope_crash") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - ScopedFastFlag luauTypeFunNoScopeMapRef{FFlag::LuauTypeFunNoScopeMapRef, true}; CheckResult result = check(R"( local _, running = ... diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index e184a106..7e063fa1 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -10,7 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauInitializeDefaultGenericParamsAtProgramPoint) LUAU_FASTFLAG(LuauAddErrorCaseForIncompatibleTypePacks) @@ -309,7 +308,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_stop_typechecking_after_reporting_duplicate_typ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_type") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type Table = { a: T } @@ -327,7 +325,6 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_type2") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type Table = { a: T } @@ -591,7 +588,6 @@ type Cool = typeof(c) TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_of_an_imported_recursive_type") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/A"] = R"( export type X = { a: number, b: X? } @@ -618,7 +614,6 @@ type X = Import.X TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_of_an_imported_recursive_generic_type") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/A"] = R"( export type X = { a: T, b: U, C: X? } @@ -840,7 +835,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_typevars_are_not_considered_to_escape_their_ */ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_unification_with_any") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local function x() diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 576b860f..bc6064aa 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -12,8 +12,11 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) -LUAU_FASTFLAG(LuauSolverAgnosticSetType) +LUAU_FASTFLAG(LuauSubtypingPrimitiveAndGenericTableTypes) +LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) +LUAU_FASTFLAG(LuauFilterOverloadsByArity) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) TEST_SUITE_BEGIN("BuiltinTests"); @@ -372,7 +375,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_unpacks_arg_types_correctly") TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type A = {tag: "A", x: number} type B = {tag: "B", y: string} @@ -417,7 +419,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t = table.pack(1, "foo", true) )"); @@ -428,7 +429,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack") TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_variadic") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( --!strict function f(): (string, ...number) @@ -444,7 +444,6 @@ local t = table.pack(f()) TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t = table.pack(1, 2, true) )"); @@ -1106,7 +1105,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_returns_false_and_string_iff_it_knows TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t1: {a: number} = {a = 42} local t2: {b: string} = {b = "hello"} @@ -1587,9 +1585,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_find_should_not_crash") TEST_CASE_FIXTURE(BuiltinsFixture, "table_dot_clone_type_states") { - ScopedFastFlag sff[] = { - {FFlag::LuauSolverAgnosticStringification, true}, {FFlag::LuauSolverAgnosticSetType, true}, {FFlag::LuauTableCloneClonesType3, true} - }; + ScopedFastFlag sff{FFlag::LuauTableCloneClonesType3, true}; CheckResult result = check(R"( local t1 = {} t1.x = 5 @@ -1729,7 +1725,12 @@ table.insert(1::any, 2::any) TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_requires_all_fields") { - ScopedFastFlag _{FFlag::LuauNoScopeShallNotSubsumeAll, true}; + ScopedFastFlag _[] = { + {FFlag::LuauNoScopeShallNotSubsumeAll, true}, + {FFlag::LuauFilterOverloadsByArity, true}, + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} + }; CheckResult result = check(R"( local function huh(): { { x: number, y: string } } @@ -1780,4 +1781,50 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_known_ CHECK_EQ("\"lol\"", toString(requireTypeAtPosition(Position{4, 23}))); } +TEST_CASE_FIXTURE(BuiltinsFixture, "next_with_refined_any") +{ + ScopedFastFlag lsv2{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSubtypingPrimitiveAndGenericTableTypes, true}, {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true} + }; + + CheckResult result = check(R"( + --!strict + local t: any = {"hello", "world"} + if type(t) == "table" and next(t) then + local foo, bar = next(t) + local _ = foo + local _ = bar + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ(toString(requireTypeAtPosition(Position{5, 23})), "unknown?"); + CHECK_EQ(toString(requireTypeAtPosition(Position{6, 23})), "unknown"); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "pairs_with_refined_any") +{ + ScopedFastFlag lsv2{FFlag::LuauSolverV2, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingPrimitiveAndGenericTableTypes, true}; + + CheckResult result = check(R"( + --!strict + local t: any = {"hello", "world"} + if type(t) == "table" and pairs(t) then + local foo, bar, lorem = pairs(t) + local _ = foo + local _ = bar + local _ = lorem + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK_EQ(toString(requireTypeAtPosition(Position{5, 23})), "({+ [unknown]: unknown +}, unknown?) -> (unknown?, unknown)"); + CHECK_EQ(toString(requireTypeAtPosition(Position{6, 23})), "{+ [unknown]: unknown +}"); + CHECK_EQ(toString(requireTypeAtPosition(Position{7, 23})), "nil"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 40f6b2bd..fbccbf88 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -16,7 +16,8 @@ using std::nullopt; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauMorePreciseExternTableRelation) -LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAG(LuauPushTypeConstraint2) +LUAU_FASTFLAG(LuauExternTableIndexersIntersect) TEST_SUITE_BEGIN("TypeInferExternTypes"); @@ -1046,7 +1047,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_superset") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushTypeConstraint, true}, + {FFlag::LuauPushTypeConstraint2, true}, {FFlag::LuauMorePreciseExternTableRelation, true}, }; @@ -1091,4 +1092,47 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_idempotent") CHECK_EQ("(Foobar) -> Foobar", toString(requireType("update"))); } +TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_intersect_with_table_indexer") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauMorePreciseExternTableRelation, true}, + {FFlag::LuauExternTableIndexersIntersect, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function f(obj: { [any]: any }, functionName: string) + if typeof(obj) == "userdata" then + local _ = obj[functionName] + end + end + )")); + + CHECK_EQ("class & { [any]: any }", toString(requireTypeAtPosition({3, 28}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_with_indexer_intersect_table") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauMorePreciseExternTableRelation, true}, + {FFlag::LuauExternTableIndexersIntersect, true}, + }; + + loadDefinition(R"( + declare extern type Foobar with + [string]: unknown + end + )"); + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function update(obj: Foobar) + assert(typeof(obj.Baz) == "number") + return obj + end + )")); + + CHECK_EQ("(Foobar) -> Foobar & { read Baz: number }", toString(requireType("update"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 7ad0928f..66f8950f 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -24,16 +24,15 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauCollapseShouldNotCrash) LUAU_FASTFLAG(LuauFormatUseLastPosition) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauFixNilRightPad) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) +LUAU_FASTFLAG(LuauFilterOverloadsByArity) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -248,6 +247,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified") TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_count") { + ScopedFastFlag _{FFlag::LuauFilterOverloadsByArity, true}; + CheckResult result = check(R"( local multiply: ((number)->number) & ((number)->string) & ((number, number)->number) multiply("") @@ -273,7 +274,10 @@ TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_ REQUIRE(ei); if (FFlag::LuauSolverV2) - CHECK("Available overloads: (number) -> number; (number) -> string; and (number, number) -> number" == ei->message); + { + // TODO CLI-170535: Improve message so we show overloads with matching and non-matching arities + CHECK("Available overloads: (number) -> number; and (number) -> string" == ei->message); + } else CHECK_EQ("Other overloads are also not viable: (number) -> string", ei->message); } @@ -1377,7 +1381,6 @@ f(function(x) return x * 2 end) TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; // FIXME: CLI-116133 bidirectional type inference needs to push expected types in for higher-order function calls DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -2243,7 +2246,6 @@ TEST_CASE_FIXTURE(Fixture, "inner_frees_become_generic_in_dcr") TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_not_enclosing") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local foo local bar @@ -2267,7 +2269,6 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local function foo(x: a, y: a?) return x @@ -2360,7 +2361,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local function f(t: { x: number } & { y: string }) t() @@ -3329,4 +3329,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1640") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1854") +{ + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + local function bug() + local counter = 1 + local work = buffer.create(64) + local function get_block() + buffer.writeu32(work, 48, counter) + counter = (counter + 1) % 0x100000000 + return work + end + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index a624620f..f2a04e9c 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -15,7 +15,6 @@ LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAG(LuauSubtypingUnionsAndIntersectionsInGenericBounds) @@ -68,7 +67,6 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_local_function2") TEST_CASE_FIXTURE(Fixture, "unions_and_generics") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type foo = (T | {T}) -> T local foo = (nil :: any) :: foo @@ -903,7 +901,6 @@ end TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( --!strict -- At one point this produced a UAF @@ -1451,7 +1448,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_2") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_3") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local function foldl(arr: {a}, init: b, f: (b, a) -> b) local r = init diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 9adea1a7..cf3ed7a4 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -164,7 +163,6 @@ TEST_CASE_FIXTURE(Fixture, "propagates_name") TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guaranteed_to_exist") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type A = {x: {y: number}} type B = {x: {y: number}} @@ -649,7 +647,6 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( function f(x: { p : number?, q : string? } & { p : number?, q : number?, r : number? }) local y : { p : number?, q : nil, r : number? } = x -- OK @@ -682,7 +679,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( function f(x : { p : number?, q : any } & { p : unknown, q : string? }) local y : { p : number?, q : string? } = x -- OK @@ -736,7 +732,6 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_never_properties") TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( function f(x : ((number?) -> ({ p : number } & { q : number })) & ((string?) -> ({ p : number } & { r : number }))) local y : (nil) -> { p : number, q : number, r : number} = x -- OK diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index f6ad24c1..a225c0eb 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -15,8 +15,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) +LUAU_FASTFLAG(LuauIterableBindNotUnify) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -256,14 +256,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_just_one_iterator_is_ok") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators_dcr") { - ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauIterableBindNotUnify, true}, + }; CheckResult result = check(R"( function no_iter() end for key in no_iter() do end )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); + LUAU_REQUIRE_ERROR_COUNT(1, result); + auto err = get(result.errors[0]); + REQUIRE(err); + CHECK_EQ("for..in loops require at least one value to iterate over. Got zero", err->message); } TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_a_custom_iterator_should_type_check") @@ -947,7 +953,6 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_lower_bound_is_string_3") TEST_CASE_FIXTURE(BuiltinsFixture, "cli_68448_iterators_need_not_accept_nil") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; // CLI-116500 if (FFlag::LuauSolverV2) return; diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index bc9fc19d..f38fe997 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -18,8 +18,6 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTINT(LuauSolverConstraintLimit) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) -LUAU_FASTFLAG(LuauSolverAgnosticSetType) using namespace Luau; @@ -162,7 +160,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "require_a_variadic_function") TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_table_freeze") { - ScopedFastFlag sff = {FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/A"] = R"( --!strict return { @@ -270,7 +267,6 @@ a = tbl.abc.def TEST_CASE_FIXTURE(BuiltinsFixture, "general_require_type_mismatch") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/A"] = R"( return { def = 4 } )"; diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index de1ce75a..d84f54b8 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -17,7 +17,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauIndexInMetatableSubtyping) TEST_SUITE_BEGIN("TypeInferOOP"); @@ -336,7 +335,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "flag_when_index_metamethod_returns_0_values" TEST_CASE_FIXTURE(BuiltinsFixture, "augmenting_an_unsealed_table_with_a_metatable") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local A = {number = 8} diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index fbbc532d..d24015da 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) TEST_SUITE_BEGIN("ProvisionalTests"); @@ -279,7 +278,6 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_equals_another_lvalue_with_no_overlap") // Just needs to fully support equality refinement. Which is annoying without type states. TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type T = {x: string, y: number} | {x: nil, y: nil} @@ -536,7 +534,6 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint") TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together") { - ScopedFastFlag sff_stringification{FFlag::LuauSolverAgnosticStringification, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, false}; TypeArena arena; @@ -840,7 +837,6 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "functions_with_mismatching_arity_but_any_is TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_type_is_illegal") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t: {x: number?} = {x = nil} @@ -966,7 +962,6 @@ TEST_CASE_FIXTURE(Fixture, "floating_generics_should_not_be_allowed") TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together") { - ScopedFastFlag sff_stringification{FFlag::LuauSolverAgnosticStringification, true}; ScopedFastFlag sff{FFlag::LuauSolverV2, false}; TypeArena arena; diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 2e2b48fe..56cc85b2 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -11,13 +11,11 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) -LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauRefineNoRefineAlways) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) @@ -692,10 +690,6 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil") TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") { - ScopedFastFlag sff[] = { - {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, - }; - CheckResult result = check(R"( local function f(a, b: string?) if a == b then @@ -725,7 +719,6 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_equal") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local function f(a: any, b: {x: number}?) if a ~= b then @@ -854,7 +847,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_not_to_be_string") TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local function f(x: string | {x: number} | {y: boolean}) if type(x) == "table" then @@ -891,7 +883,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_functions") TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_tables") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type XYCoord = {x: number} & {y: number} local function f(t: XYCoord?) @@ -1046,7 +1037,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string") TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local function f(t: {x: boolean}?) if not t or t.x then @@ -1299,7 +1289,6 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type T = {tag: "missing", x: nil} | {tag: "exists", x: string} @@ -1434,7 +1423,6 @@ TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersect TEST_CASE_FIXTURE(RefinementExternTypeFixture, "discriminate_from_isa_of_x") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type T = {tag: "Part", x: Part} | {tag: "Folder", x: Folder} @@ -2248,7 +2236,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauNoMoreComparisonTypeFunctions, true}, - {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, {FFlag::LuauNoOrderingTypeFunctions, true}, }; @@ -2269,7 +2256,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "check_refinement_to_primitive_and_compare") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauNoMoreComparisonTypeFunctions, true}, - {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, {FFlag::LuauNoOrderingTypeFunctions, true}, }; @@ -2287,7 +2273,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_ { ScopedFastFlag _[] = { {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, - {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, {FFlag::LuauNoOrderingTypeFunctions, true}, }; @@ -2355,7 +2340,6 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symb TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_t_after_return_references_all_reachable_points") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t = {} diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 09a06cef..a879850d 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -8,8 +8,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) -LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAG(LuauPushTypeConstraint2) TEST_SUITE_BEGIN("TypeSingletons"); @@ -292,7 +291,6 @@ TEST_CASE_FIXTURE(Fixture, "tagged_unions_immutable_tag") TEST_CASE_FIXTURE(Fixture, "table_has_a_boolean") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t={a=1,b=false} )"); @@ -399,7 +397,7 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") { - ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + ScopedFastFlag _{FFlag::LuauPushTypeConstraint2, true}; CheckResult result = check(R"( type Cat = { tag: 'cat', catfood: string } @@ -678,7 +676,7 @@ TEST_CASE_FIXTURE(Fixture, "tagged_union_in_ternary") TEST_CASE_FIXTURE(Fixture, "table_literal_with_singleton_union_values") { - ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + ScopedFastFlag _{FFlag::LuauPushTypeConstraint2, true}; CheckResult result = check(R"( local t1: {[string]: "a" | "b"} = { a = "a", b = "b" } @@ -691,7 +689,7 @@ TEST_CASE_FIXTURE(Fixture, "table_literal_with_singleton_union_values") TEST_CASE_FIXTURE(Fixture, "singleton_type_mismatch_via_variable") { - ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + ScopedFastFlag _{FFlag::LuauPushTypeConstraint2, true}; CheckResult result = check(R"( local c = "c" @@ -711,7 +709,7 @@ TEST_CASE_FIXTURE(Fixture, "singleton_type_mismatch_via_variable") TEST_CASE_FIXTURE(Fixture, "cli_163481_any_indexer_pushes_type") { - ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + ScopedFastFlag _{FFlag::LuauPushTypeConstraint2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( --!strict diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index f383c998..7034b76c 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -23,18 +23,18 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauAllowMixedTables) -LUAU_FASTFLAG(LuauSolverAgnosticSetType) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSimplifyIntersectionForLiteralSubtypeCheck) LUAU_FASTFLAG(LuauCacheDuplicateHasPropConstraints) +LUAU_FASTFLAG(LuauPushTypeConstraintIntersection) +LUAU_FASTFLAG(LuauFilterOverloadsByArity) TEST_SUITE_BEGIN("TableTests"); @@ -100,7 +100,6 @@ TEST_CASE_FIXTURE(Fixture, "basic") TEST_CASE_FIXTURE(Fixture, "augment_table") { - ScopedFastFlag sff[] = {{FFlag::LuauSolverAgnosticSetType, true}, {FFlag::LuauSolverAgnosticStringification, true}}; CheckResult result = check(R"( local t = {} t.foo = 'bar' @@ -118,7 +117,6 @@ TEST_CASE_FIXTURE(Fixture, "augment_table") TEST_CASE_FIXTURE(Fixture, "augment_nested_table") { - ScopedFastFlag sff[] = {{FFlag::LuauSolverAgnosticSetType, true}, {FFlag::LuauSolverAgnosticStringification, true}}; CheckResult result = check(R"( local t = { p = {} } t.p.foo = 'bar' @@ -172,7 +170,6 @@ TEST_CASE_FIXTURE(Fixture, "index_expression_is_checked_against_the_indexer_type TEST_CASE_FIXTURE(Fixture, "cannot_augment_sealed_table") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( function mkt() return {prop=999} @@ -782,7 +779,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table") TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( function Symbol(n) return { __name=n } @@ -842,7 +838,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_its_variable_type_and_unifiable") TEST_CASE_FIXTURE(Fixture, "indexer_mismatch") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t1: { [string]: string } = {} local t2: { [number]: number } = {} @@ -1668,7 +1663,6 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key") // Could be flaky if the fix has regressed. TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; // CLI-114792 We don't report MissingProperties DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -1692,7 +1686,6 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2") TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_indexer") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type StringToStringMap = { [string]: string } local rt: StringToStringMap = { ["foo"] = 1 } @@ -1722,7 +1715,6 @@ TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_i TEST_CASE_FIXTURE(Fixture, "casting_sealed_tables_with_props_into_table_with_indexer") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type StringToStringMap = { [string]: string } function mkrt() return { ["foo"] = 1 } end @@ -1750,7 +1742,6 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer2") TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer3") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local function foo(a: {[string]: number, a: string}) end foo({ a = 1 }) @@ -1789,7 +1780,6 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer4") TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multiple_errors") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( function f(vec1: {x: number}): {x: number, y: number, z: number} return vec1 @@ -1847,8 +1837,6 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_dont_report_multiple_errors") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; - CheckResult result = check(R"( function mkvec3() return {x = 1, y = 2, z = 3} end function mkvec1() return {x = 1} end @@ -1883,7 +1871,6 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_is_ok") TEST_CASE_FIXTURE(Fixture, "type_mismatch_on_massive_table_is_cut_short") { ScopedFastInt sfis{FInt::LuauTableTypeMaximumStringifierLength, 40}; - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( @@ -2100,7 +2087,6 @@ TEST_CASE_FIXTURE(Fixture, "only_ascribe_synthetic_names_at_module_scope") TEST_CASE_FIXTURE(Fixture, "hide_table_error_properties") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( --!strict @@ -2370,6 +2356,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_properties_in_strict") { + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauNoScopeShallNotSubsumeAll, true}, + {FFlag::LuauFilterOverloadsByArity, true}, + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} + }; + CheckResult result = check(R"( --!strict local buttons = {} @@ -2378,11 +2372,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope table.insert(buttons, { a = 3 }) )"); - // FIXME(CLI-165431): fixing subtyping revealed an overload selection problems - if (FFlag::LuauSolverV2 && FFlag::LuauNoScopeShallNotSubsumeAll) - LUAU_REQUIRE_ERROR_COUNT(4, result); - else - LUAU_REQUIRE_NO_ERRORS(result); + // FIXME(CLI-169950): fixing subtyping revealed an overload selection problem. + // fixing the overload selection problem revealed another subtyping problem + LUAU_REQUIRE_ERROR_COUNT(2, result); } TEST_CASE_FIXTURE(Fixture, "error_detailed_prop") @@ -2454,7 +2446,6 @@ Type 'number' could not be converted into 'string' in an invariant context)"; TEST_CASE_FIXTURE(BuiltinsFixture, "error_detailed_metatable_prop") { ScopedFastFlag sff[] = { - {FFlag::LuauSolverAgnosticStringification, true}, {FFlag::LuauInstantiateInSubtyping, true}, }; CheckResult result = check(R"( @@ -2627,7 +2618,6 @@ a.p = { x = 9 } TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_error") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( --!strict type Super = { x : number } @@ -2676,7 +2666,6 @@ TEST_CASE_FIXTURE(Fixture, "explicitly_typed_table_with_indexer") TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_metatable_type_call") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; // CLI-114782 DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -2801,6 +2790,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "give_up_after_one_metatable_index_look_up") TEST_CASE_FIXTURE(Fixture, "confusing_indexing") { + ScopedFastFlag sffs[] = { + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintIntersection, true}, + }; + CheckResult result = check(R"( type T = {} & {p: number | string} local function f(t: T) @@ -2810,14 +2804,7 @@ TEST_CASE_FIXTURE(Fixture, "confusing_indexing") local foo = f({p = "string"}) )"); - if (FFlag::LuauSolverV2) - { - // CLI-114781 Bidirectional checking can't see through the intersection - LUAU_REQUIRE_ERROR_COUNT(1, result); - } - else - LUAU_REQUIRE_NO_ERRORS(result); - + LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("number | string", toString(requireType("foo"))); } @@ -3422,7 +3409,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_simple_call") TEST_CASE_FIXTURE(BuiltinsFixture, "access_index_metamethod_that_returns_variadic") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type Foo = {x: string} local t = {} @@ -3481,7 +3467,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_invalidate_the_properties_iterator_of_free_tabl TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t: {x: number?}? = {x = nil} local u = t.x and t or 5 @@ -3503,7 +3488,6 @@ TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early") TEST_CASE_FIXTURE(Fixture, "accidentally_checked_prop_in_opposite_branch") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t: {x: number?}? = {x = nil} local u = t and t.x == 5 or t.x == 31337 @@ -3664,7 +3648,6 @@ TEST_CASE_FIXTURE(Fixture, "prop_access_on_key_whose_types_mismatches") TEST_CASE_FIXTURE(Fixture, "prop_access_on_unions_of_indexers_where_key_whose_types_mismatches") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t: { [number]: number } | { [boolean]: number } = {} local u = t.x @@ -3862,7 +3845,6 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table { ScopedFastFlag sff[] = { {FFlag::LuauInstantiateInSubtyping, true}, - {FFlag::LuauSolverAgnosticStringification, true}, }; CheckResult result = check(R"( @@ -3957,7 +3939,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_has_a_side_effect") TEST_CASE_FIXTURE(BuiltinsFixture, "tables_should_be_fully_populated") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t = { x = 5 :: NonexistingTypeWhichEndsUpReturningAnErrorType, @@ -4292,7 +4273,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict") TEST_CASE_FIXTURE(Fixture, "simple_method_definition") { - ScopedFastFlag sff[] = {{FFlag::LuauSolverAgnosticStringification, true}}; CheckResult result = check(R"( local T = {} @@ -4421,7 +4401,6 @@ TEST_CASE_FIXTURE(Fixture, "new_solver_supports_read_write_properties") TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( function one(tbl: {x: any}) end function two(tbl: {x: string}) one(tbl) end -- ok, string <: any and any <: string @@ -4834,7 +4813,6 @@ TEST_CASE_FIXTURE(Fixture, "insert_a_and_f_of_a_into_table_res_in_a_loop") TEST_CASE_FIXTURE(BuiltinsFixture, "ipairs_adds_an_unbounded_indexer") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( --!strict @@ -5266,7 +5244,6 @@ TEST_CASE_FIXTURE(Fixture, "empty_union_container_overflow") TEST_CASE_FIXTURE(Fixture, "inference_in_constructor") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; LUAU_CHECK_NO_ERRORS(check(R"( local function new(y) local t: { x: number } = { x = y } @@ -5450,7 +5427,7 @@ TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference") TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauPushTypeConstraint, true}}; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauPushTypeConstraint2, true}}; auto result = check(R"( type File = { @@ -5838,7 +5815,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, - {FFlag::LuauPushTypeConstraint, true}, + {FFlag::LuauPushTypeConstraint2, true}, }; CheckResult results = check(R"( @@ -6051,7 +6028,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1935") TEST_CASE_FIXTURE(Fixture, "result_like_tagged_union") { - ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + ScopedFastFlag _{FFlag::LuauPushTypeConstraint2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( --!strict @@ -6073,7 +6050,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1924") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushTypeConstraint, true}, + {FFlag::LuauPushTypeConstraint2, true}, }; CheckResult result = check(R"( @@ -6091,7 +6068,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1924") TEST_CASE_FIXTURE(BuiltinsFixture, "cli_167052") { - ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + ScopedFastFlag _{FFlag::LuauPushTypeConstraint2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local Children = newproxy() @@ -6121,6 +6098,47 @@ end LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_any_and_true") +{ + ScopedFastFlag _{FFlag::LuauFilterOverloadsByArity, true}; + + CheckResult result = check(R"( + table.insert({} :: any, true) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_array_of_any") +{ + CheckResult result = check(R"( + table.insert({} :: { any }, 42) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "bad_insert_type_mismatch") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, + {FFlag::LuauNoScopeShallNotSubsumeAll, true}, + {FFlag::LuauFilterOverloadsByArity, true}, + }; + + CheckResult result = check(R"( + local function doInsert(t: { string }) + table.insert(t, true) + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_REQUIRE_ERROR(result, GenericBoundsMismatch); +} + + TEST_CASE_FIXTURE(Fixture, "string_indexer_satisfies_read_only_property") { ScopedFastFlag _{FFlag::LuauSolverV2, true}; @@ -6134,4 +6152,95 @@ TEST_CASE_FIXTURE(Fixture, "string_indexer_satisfies_read_only_property") )")); } +TEST_CASE_FIXTURE(Fixture, "bidirectional_inference_works_through_intersections") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintIntersection, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + type x = {} & ({ state: "1" } | { state: "2" }) + local x: x = { state = "2" } + )")); +} + +TEST_CASE_FIXTURE(Fixture, "bidirectional_inference_intersection_other_intersection_example") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintIntersection, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + type A = { foo: "a" } + type B = { bar: "b" } + type AB = A & B + local t: AB = { + foo = "a", + bar = "b", + } + )")); +} + +TEST_CASE_FIXTURE(Fixture, "do_not_force_on_simple_bidirectional_inference") +{ + ScopedFastFlag _{FFlag::DebugLuauAssertOnForcedConstraint, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + type Role = "Citizen" + + local function getRoles(): { Role } + return { 'Citizen' } + end + )")); +} + +TEST_CASE_FIXTURE(Fixture, "oss_2017") +{ + ScopedFastFlag _{FFlag::DebugLuauAssertOnForcedConstraint, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + local class = {} + class.__index = class + + type Role = string + local ROLES_PLAYERS_REQUIRED: {[Role]: number} = {} + + function class.distributeRoles() + for _, role: Role in class.getAllRoles() do + local _ = ROLES_PLAYERS_REQUIRED[role] + end + end + + function class.getAllRoles(): { Role } + return { 'Citizen', 'Mafia', 'Detective', 'Bodyguard', 'Jester' } + end + + return class + )")); +} + +TEST_CASE_FIXTURE(Fixture, "oss_1953") +{ + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + + CheckResult result = check(R"( + type A = { kind: "a" } + type B = { kind: "b" } + + local function foo(fn: () -> A | B | T) + local v = fn() + return v and v.kind + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + auto err = get(result.errors[0]); + CHECK_EQ("kind", err->key); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 5af83940..95ed8afa 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -2,6 +2,7 @@ #include "Luau/AstQuery.h" #include "Luau/BuiltinDefinitions.h" +#include "Luau/Common.h" #include "Luau/Frontend.h" #include "Luau/Scope.h" #include "Luau/TypeInfer.h" @@ -14,6 +15,9 @@ #include +LUAU_DYNAMIC_FASTINT(LuauConstraintGeneratorRecursionLimit) +LUAU_DYNAMIC_FASTINT(LuauSubtypingRecursionLimit) + LUAU_FASTFLAG(LuauFixLocationSpanTableIndexExpr) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) @@ -422,6 +426,8 @@ TEST_CASE_FIXTURE(Fixture, "check_block_recursion_limit") ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100}; ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100}; + ScopedFastInt luauConstraintGeneratorRecursionLimit{DFInt::LuauConstraintGeneratorRecursionLimit, limit - 100}; + ScopedFastInt luauSubtypingRecursionLimit{DFInt::LuauSubtypingRecursionLimit, limit - 100}; CheckResult result = check(rep("do ", limit) + "local a = 1" + rep(" end", limit)); @@ -440,6 +446,8 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit") #endif ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100}; ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100}; + ScopedFastInt luauConstraintGeneratorRecursionLimit{DFInt::LuauConstraintGeneratorRecursionLimit, limit - 100}; + ScopedFastInt luauSubtypingRecursionLimit{DFInt::LuauSubtypingRecursionLimit, limit - 100}; CheckResult result = check(R"(("foo"))" + rep(":lower()", limit)); @@ -2630,6 +2638,7 @@ TEST_CASE_FIXTURE(Fixture, "constraint_generation_recursion_limit") ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNoConstraintGenRecursionLimitIce, true}}; // Lowers the recursion limit for the constraint generator ScopedFastInt i{FInt::LuauCheckRecursionLimit, 5}; + ScopedFastInt luauConstraintGeneratorRecursionLimit{DFInt::LuauConstraintGeneratorRecursionLimit, 5}; // This shouldn't ICE CheckResult result = check(R"( diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 1f08273c..459b2c67 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -13,7 +13,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauUnifierRecursionOnRestart); -LUAU_FASTFLAG(LuauSolverAgnosticStringification) struct TryUnifyFixture : Fixture { @@ -276,7 +275,6 @@ TEST_CASE_FIXTURE(Fixture, "variadics_should_use_reversed_properly") TEST_CASE_FIXTURE(BuiltinsFixture, "cli_41095_concat_log_in_sealed_table_unification") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( --!strict diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index ffa680e9..d6a95c42 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -12,7 +12,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauRemoveGenericErrorForParams) LUAU_FASTFLAG(LuauAddErrorCaseForIncompatibleTypePacks) @@ -294,8 +293,6 @@ end TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; - CheckResult result = check(R"( type Packed = (T...) -> T... local a: Packed<> @@ -360,8 +357,6 @@ local c: Packed TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_type_packs_import") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; - fileResolver.source["game/A"] = R"( export type Packed = { a: T, b: (U...) -> () } return {} @@ -393,7 +388,6 @@ local d: { a: typeof(c) } TEST_CASE_FIXTURE(BuiltinsFixture, "type_pack_type_parameters") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; fileResolver.source["game/A"] = R"( export type Packed = { a: T, b: (U...) -> () } return {} @@ -921,7 +915,6 @@ type C = A TEST_CASE_FIXTURE(Fixture, "type_alias_defaults_recursive_type") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type F ()> = (K) -> V type R = { m: F } diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 5046c5b3..ded28cdd 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -6,7 +6,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) using namespace Luau; @@ -584,7 +583,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1547") TEST_CASE_FIXTURE(Fixture, "modify_captured_table_field") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local state = { x = 0 } function incr() diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 8c718c26..7e04ab76 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -10,8 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSolverAgnosticStringification) - TEST_SUITE_BEGIN("UnionTypes"); TEST_CASE_FIXTURE(Fixture, "fuzzer_union_with_one_part_assertion") @@ -403,7 +401,6 @@ TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors") TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors_2") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type A = { x: number } & { y: number } function f(a: A?) @@ -524,7 +521,6 @@ local oh : boolean = t.y TEST_CASE_FIXTURE(Fixture, "error_detailed_union_part") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( type X = { x: number } type Y = { y: number } diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index 220dc749..c823bc72 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -11,7 +11,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TypeTests"); @@ -221,7 +220,6 @@ TEST_CASE_FIXTURE(Fixture, "UnionTypeIterator_with_only_cyclic_union") */ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure") { - ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; Type ftv11{FreeType{TypeLevel{}, getBuiltins()->neverType, getBuiltins()->unknownType}}; TypePackVar tp24{TypePack{{&ftv11}}}; diff --git a/tests/conformance/apicalls.luau b/tests/conformance/apicalls.luau index 8db62d96..3982e5a8 100644 --- a/tests/conformance/apicalls.luau +++ b/tests/conformance/apicalls.luau @@ -30,4 +30,8 @@ function oops() return "oops" end +function getnresults(n) + return table.unpack(table.create(n, 1)) +end + return('OK') diff --git a/tests/conformance/cyield.luau b/tests/conformance/cyield.luau index 157d9176..15df83b3 100644 --- a/tests/conformance/cyield.luau +++ b/tests/conformance/cyield.luau @@ -132,4 +132,51 @@ passthroughcheckyield(passthroughCallArgReuse) passthroughcheckyield(passthroughCallVaradic) passthroughcheckyield(passthroughCallWithState) +-- yieldable C function that acts as a pass-through (regular, pcall to Luau) +local function nonyieldablepcall(x, y) + local status, result = pcall(nonyieldable, x, y) + assert(status) + return result +end + +local function passthroughcheckpcall(f) + local x = f(nonyieldablepcall, 1, 2) + assert(x == 0.5) +end + +passthroughcheckpcall(passthroughCall) +passthroughcheckpcall(passthroughCallMoreResults) +passthroughcheckpcall(passthroughCallArgReuse) +passthroughcheckpcall(passthroughCallVaradic) +passthroughcheckpcall(passthroughCallWithState) + +-- yieldable C function that acts as a pass-through (regular, pcall to C) +local function nonyieldablepcallc(x, y) + local status, result = pcall(assert, x / y) + assert(status) + return result +end + +local function passthroughcheckpcallc(f) + local x = f(nonyieldablepcallc, 1, 2) + assert(x == 0.5) +end + +passthroughcheckpcallc(passthroughCall) +passthroughcheckpcallc(passthroughCallMoreResults) +passthroughcheckpcallc(passthroughCallArgReuse) +passthroughcheckpcallc(passthroughCallVaradic) +passthroughcheckpcallc(passthroughCallWithState) + +-- yieldable C function that acts as a pass-through (regular, direct pcall) +local function passthroughcheckpcallcdirect(f) + local status, x = f(pcall, function(y) return 1 / y end, 2) + assert(status) + assert(x == 0.5) +end + +-- only variadic functions are tested here because pcall returns status as well +passthroughcheckpcallcdirect(passthroughCallVaradic) +passthroughcheckpcallcdirect(passthroughCallWithState) + return "OK" diff --git a/tests/conformance/errors.luau b/tests/conformance/errors.luau index 57d2b693..ef00e13a 100644 --- a/tests/conformance/errors.luau +++ b/tests/conformance/errors.luau @@ -139,7 +139,7 @@ X=2;assert(lineerror(p) == 1) lineerror = nil -if not limitedstack then +do C = 0 -- local l = debug.getinfo(1, "l").currentline function y () C=C+1; y() end @@ -264,7 +264,7 @@ local function f (x) end end -if not limitedstack then +do f(3) end diff --git a/tests/conformance/pcall.luau b/tests/conformance/pcall.luau index 9e691e2e..16a0fb87 100644 --- a/tests/conformance/pcall.luau +++ b/tests/conformance/pcall.luau @@ -56,14 +56,14 @@ checkresults({ true, 42 }, pcall(setmetatable({}, { __call = function(self, arg) checkerror(pcall(function() local a = nil / 5 end)) checkerror(pcall(function() select(-100) end)) -if not limitedstack then +do -- complex error tests - stack overflow, and stack overflow through pcall function stackinfinite() return stackinfinite() end checkerror(pcall(stackinfinite)) function stackover() return pcall(stackover) end local res = {pcall(stackover)} - assert(#res == 200) -- stack limit (MAXCCALLS) is 200 + assert(#res == 10000) -- stack limit (LUAI_MAXCALLS is 20000 and we use 2 calls per recursion) end -- yield tests @@ -292,7 +292,26 @@ if not limitedstack then coroutine.wrap(foo)() -- call another coroutine end checkerror(pcall(foo)) -- triggers C stack overflow - assert(count + 1 == 200) -- stack limit (MAXCCALLS) is 200, -1 for first pcall + assert(count == 200) -- stack limit (MAXCCALLS) is 200 +end + +-- protected and regular calls without C stack limit +do + local function regularadd(depth: number, a: number, b: number): number + if depth == 0 then return 1 end + + return a + regularadd(depth - 1, b, a + b) + end + + local function protectedadd(depth: number, a: number, b: number): number + if depth == 0 then return 1 end + + local result, s = pcall(protectedadd, depth - 1, b, a + b) + assert(result) + return a + s + end + + assert(regularadd(2000, 0, 1) == protectedadd(2000, 0, 1)) end return 'OK' diff --git a/tests/conformance/tmerror.luau b/tests/conformance/tmerror.luau index fef077ea..0af0dead 100644 --- a/tests/conformance/tmerror.luau +++ b/tests/conformance/tmerror.luau @@ -1,41 +1,78 @@ -- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details --- This file is based on Lua 5.x tests -- https://github.com/lua/lua/tree/master/testes --- Generate an error (i.e. throw an exception) inside a tag method which is indirectly --- called via pcall. +-- Generate an error (i.e. throw an exception) inside a tag method which is indirectly called via pcall. -- This test is meant to detect a regression in handling errors inside a tag method +local status, result + local testtable = {} -setmetatable(testtable, { __index = function() error("Error") end }) +setmetatable(testtable, { + __index = function() + error("Error") + end +}) -pcall(function() - testtable.missingmethod() +status, result = pcall(function() + testtable.missingmethod() end) +assert(status == false) +assert(string.find(result, ":11: Error") ~= nil) -- local testtable2 = {} -setmetatable(testtable2, { __index = function() pcall(function() error("Error") end) end }) +setmetatable(testtable2, { + __index = function() + local status, result = pcall(function() + error("Error") + end) + assert(status == false) + assert(string.find(result, ":26: Error") ~= nil) + return nil + end +}) local m2 = testtable2.missingmethod -pcall(function() - testtable2.missingmethod() +status, result = pcall(function() + testtable2.missingmethod() end) +assert(status == false) +assert(string.find(result, ":37: attempt to call a nil value") ~= nil) -- local testtable3 = {} -setmetatable(testtable3, { __index = function() pcall(error, "Error") end }) +setmetatable(testtable3, { __index = function() + local status, result = pcall(error, "Error") + assert(status == false) + assert(result == "Error") + return nil +end }) local m3 = testtable3.missingmethod -pcall(function() - testtable3.missingmethod() +status, result = pcall(function() + testtable3.missingmethod() end) +assert(status == false) +assert(string.find(result, ":54: attempt to call a nil value") ~= nil) -- local testtable4 = {} -setmetatable(testtable4, { __index = function() pcall(error) end }) +setmetatable(testtable4, { + __index = function() + local status, result = pcall(error) + assert(status == false) + assert(result == nil) + return nil + end +}) local m4 = testtable4.missingmember +status, result = pcall(function() + testtable4.missingmethod() +end) +assert(status == false) +assert(string.find(result, ":73: attempt to call a nil value") ~= nil) + return('OK') From bb3956547affc1af7b7d196f96756895255dd05d Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Fri, 3 Oct 2025 09:13:36 -0700 Subject: [PATCH 04/10] Sync to upstream/release/694 (#2033) ## What's Changed? This week we have improvements in new type solver inference, performance optimizations for the new type solver as well as fixes for optimization passes in native code generation. - Fixed the order of errors returned by `Frontend::getCheckResult` with `accumulateNested` flag - Typechecker now uses `userdata` instead of `class` as the extern type name ## New Type Solver - When a string is passed to a function expecting an argument that might be a string singleton, bidirectional type inference will choose the lower bound (string literal) for that argument (Fixes #2010) - Fixed incorrect definition of `vector.lerp` (Fixes #2024) - Added error suppression in type path traversal. Without it, errors with `*error-type*` were sometimes visible (Fixes #1840) - Fixed another case of combinatorial explosion in union type normalization which could have caused a hang - Fixed a crash on out-of-bounds access during `for..in` statement typechecking ## Runtime - Fixed an assertion in native code generation in a sequence of `nil` and `boolean` stores to a local - Fixed incorrect lowering in rare cases when LuauCodegenDirectCompare was enabled ## Internal Contributors Co-authored-by: Andy Friesen Co-authored-by: Annie Tang Co-authored-by: Ariel Weiss Co-authored-by: Hunter Goldstein Co-authored-by: Ilya Rezvov Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/Normalize.h | 2 + Analysis/src/AutocompleteCore.cpp | 16 +- Analysis/src/ConstraintSolver.cpp | 12 +- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 6 +- Analysis/src/Frontend.cpp | 40 ++++- Analysis/src/Normalize.cpp | 35 +++- Analysis/src/Simplify.cpp | 28 +++- Analysis/src/TableLiteralInference.cpp | 18 ++ Analysis/src/Type.cpp | 2 +- Analysis/src/TypeChecker2.cpp | 20 ++- Analysis/src/TypePath.cpp | 94 ++++++++++- CodeGen/include/Luau/IrData.h | 6 +- CodeGen/include/Luau/IrVisitUseDef.h | 6 +- CodeGen/src/BytecodeAnalysis.cpp | 128 +++++--------- CodeGen/src/IrBuilder.cpp | 10 +- CodeGen/src/IrLoweringA64.cpp | 11 +- CodeGen/src/IrLoweringX64.cpp | 9 +- CodeGen/src/IrTranslateBuiltins.cpp | 28 +--- CodeGen/src/IrTranslation.cpp | 10 +- CodeGen/src/IrUtils.cpp | 100 ++++++----- CodeGen/src/OptimizeConstProp.cpp | 36 +++- tests/Autocomplete.test.cpp | 21 --- tests/Conformance.test.cpp | 10 +- tests/EqSatSimplification.test.cpp | 10 +- tests/IrBuilder.test.cpp | 177 ++++++++++++++++++++ tests/IrLowering.test.cpp | 29 ++-- tests/Normalize.test.cpp | 33 +++- tests/OverloadResolver.test.cpp | 1 + tests/RuntimeLimits.test.cpp | 5 - tests/Simplify.test.cpp | 5 - tests/Subtyping.test.cpp | 69 +++++++- tests/TypeInfer.builtins.test.cpp | 18 ++ tests/TypeInfer.classes.test.cpp | 2 +- tests/TypeInfer.provisional.test.cpp | 5 +- tests/TypeInfer.refinements.test.cpp | 21 ++- tests/TypeInfer.singletons.test.cpp | 21 +++ tests/TypeInfer.tables.test.cpp | 4 +- tests/TypeInfer.test.cpp | 18 ++ 38 files changed, 740 insertions(+), 326 deletions(-) diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index 435c86bb..328ff542 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -202,6 +202,8 @@ enum class NormalizationResult // * G is a union of generic/free/blocked types, intersected with a normalized type struct NormalizedType { + NotNull builtinTypes; + // The top part of the type. // This type is either never, unknown, or any. // If this type is not never, all the other fields are null. diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index 3498be24..3d8c4c4d 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -27,7 +27,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) -LUAU_FASTFLAGVARIABLE(LuauIncludeBreakContinueStatements) LUAU_FASTFLAGVARIABLE(LuauSuggestHotComments) LUAU_FASTFLAG(LuauAutocompleteAttributes) @@ -1203,7 +1202,6 @@ static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding& static bool isValidBreakContinueContext(const std::vector& ancestry, Position position) { - LUAU_ASSERT(FFlag::LuauIncludeBreakContinueStatements); for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) { if ((*it)->is() || (*it)->is() || (*it)->is() || (*it)->is() || @@ -1267,18 +1265,10 @@ static AutocompleteEntryMap autocompleteStatement( scope = scope->parent; } - if (FFlag::LuauIncludeBreakContinueStatements) + bool shouldIncludeBreakAndContinue = isValidBreakContinueContext(ancestry, position); + for (const std::string_view kw : kStatementStartingKeywords) { - bool shouldIncludeBreakAndContinue = isValidBreakContinueContext(ancestry, position); - for (const std::string_view kw : kStatementStartingKeywords) - { - if ((kw != "break" && kw != "continue") || shouldIncludeBreakAndContinue) - result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); - } - } - else - { - for (const std::string_view kw : kStatementStartingKeywords) + if ((kw != "break" && kw != "continue") || shouldIncludeBreakAndContinue) result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 363f26d6..e7ceefc4 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -52,6 +52,7 @@ LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAGVARIABLE(LuauScopedSeenSetInLookupTableProp) LUAU_FASTFLAGVARIABLE(LuauIterableBindNotUnify) +LUAU_FASTFLAGVARIABLE(LuauAvoidOverloadSelectionForFunctionType) namespace Luau { @@ -1579,10 +1580,15 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullargs, NotNull{&module->astTypes}); - auto [status, overload] = resolver.selectOverload(fn, argsPack, NotNull{&uniqueTypes}, /*useFreeTypeBounds*/ force); TypeId overloadToUse = fn; - if (status == OverloadResolver::Analysis::Ok) - overloadToUse = overload; + // NOTE: This probably ends up capturing union types as well, but + // that should be fairly uncommon. + if (!FFlag::LuauAvoidOverloadSelectionForFunctionType || !is(fn)) + { + auto [status, overload] = resolver.selectOverload(fn, argsPack, NotNull{&uniqueTypes}, /*useFreeTypeBounds*/ force); + if (status == OverloadResolver::Analysis::Ok) + overloadToUse = overload; + } TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, c.result}); Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}}; diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 8bb1a4ca..12b8c8af 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -1,7 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BuiltinDefinitions.h" -LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp) +LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp2) LUAU_FASTFLAGVARIABLE(LuauRawGetHandlesNil) namespace Luau @@ -339,7 +339,7 @@ declare vector: { clamp: @checked (vec: vector, min: vector, max: vector) -> vector, max: @checked (vector, ...vector) -> vector, min: @checked (vector, ...vector) -> vector, - lerp: @checked (vec1: vector, vec2: vector, t: number) -> number, + lerp: @checked (vec1: vector, vec2: vector, t: number) -> vector, zero: vector, one: vector, @@ -389,7 +389,7 @@ std::string getBuiltinDefinitionSource() result += kBuiltinDefinitionDebugSrc; result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionBufferSrc; - if (FFlag::LuauTypeCheckerVectorLerp) + if (FFlag::LuauTypeCheckerVectorLerp2) { result += kBuiltinDefinitionVectorSrc; } diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 0fdf09aa..20d9a485 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -47,6 +47,7 @@ LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAGVARIABLE(LuauBatchedExecuteTask) +LUAU_FASTFLAGVARIABLE(LuauAccumulateErrorsInOrder) namespace Luau { @@ -286,18 +287,39 @@ ErrorVec accumulateErrors( Module& module = *modulePtr; - std::sort( - module.errors.begin(), - module.errors.end(), - [](const TypeError& e1, const TypeError& e2) -> bool - { - return e1.location.begin > e2.location.begin; - } - ); + if (FFlag::LuauAccumulateErrorsInOrder) + { + size_t prevSize = result.size(); + + // Append module errors in reverse order + result.insert(result.end(), module.errors.rbegin(), module.errors.rend()); + + // Sort them in the reverse order as well + std::stable_sort( + result.begin() + prevSize, + result.end(), + [](const TypeError& e1, const TypeError& e2) -> bool + { + return e1.location.begin > e2.location.begin; + } + ); + } + else + { + std::sort( + module.errors.begin(), + module.errors.end(), + [](const TypeError& e1, const TypeError& e2) -> bool + { + return e1.location.begin > e2.location.begin; + } + ); - result.insert(result.end(), module.errors.begin(), module.errors.end()); + result.insert(result.end(), module.errors.begin(), module.errors.end()); + } } + // Now we reverse errors from all modules and since they were inserted and sorted in reverse, it should be in order std::reverse(result.begin(), result.end()); return result; diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 14e74d88..f3b2cc7e 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -24,7 +24,9 @@ LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTFLAGVARIABLE(LuauImproveNormalizeExternTypeCheck) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) +LUAU_FASTFLAGVARIABLE(LuauNormalizerUnionTyvarsTakeMaxSize) namespace Luau { @@ -144,7 +146,8 @@ bool NormalizedFunctionType::isNever() const } NormalizedType::NormalizedType(NotNull builtinTypes) - : tops(builtinTypes->neverType) + : builtinTypes(builtinTypes) + , tops(builtinTypes->neverType) , booleans(builtinTypes->neverType) , errors(builtinTypes->neverType) , nils(builtinTypes->neverType) @@ -170,10 +173,21 @@ bool NormalizedType::isUnknown() const { if (auto ct = get(t)) { - if (ct->name == "class" && disj.empty()) + if (FFlag::LuauImproveNormalizeExternTypeCheck) { - isTopExternType = true; - break; + if (t == builtinTypes->externType && disj.empty()) + { + isTopExternType = true; + break; + } + } + else + { + if (ct->name == "userdata" && disj.empty()) + { + isTopExternType = true; + break; + } } } } @@ -1539,8 +1553,17 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali return NormalizationResult::True; } - if (here.tyvars.size() * there.tyvars.size() >= size_t(FInt::LuauNormalizeUnionLimit)) - return NormalizationResult::HitLimits; + if (FFlag::LuauNormalizerUnionTyvarsTakeMaxSize) + { + auto maxSize = std::max(here.tyvars.size(), there.tyvars.size()); + if (maxSize * maxSize >= size_t(FInt::LuauNormalizeUnionLimit)) + return NormalizationResult::HitLimits; + } + else + { + if (here.tyvars.size() * there.tyvars.size() >= size_t(FInt::LuauNormalizeUnionLimit)) + return NormalizationResult::HitLimits; + } for (auto it = there.tyvars.begin(); it != there.tyvars.end(); it++) { diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 46b84df6..5c6cd78b 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -21,12 +21,12 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8) LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSimplificationIterationLimit, 128) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) -LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAGVARIABLE(LuauMorePreciseExternTableRelation) LUAU_FASTFLAGVARIABLE(LuauSimplifyRefinementOfReadOnlyProperty) LUAU_FASTFLAGVARIABLE(LuauExternTableIndexersIntersect) +LUAU_FASTFLAGVARIABLE(LuauSimplifyMoveTableProps) namespace Luau { @@ -1467,11 +1467,25 @@ std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) if (areDisjoint) { - TableType::Props mergedProps = lt->props; - for (const auto& [name, rightProp] : rt->props) - mergedProps[name] = rightProp; - return arena->addType(TableType{mergedProps, std::nullopt, TypeLevel{}, lt->scope, TableState::Sealed}); + if (FFlag::LuauSimplifyMoveTableProps) + { + TableType merged{TableState::Sealed, TypeLevel{}, lt->scope}; + merged.props = lt->props; + + for (const auto& [name, rightProp] : rt->props) + merged.props[name] = rightProp; + + return arena->addType(std::move(merged)); + } + else + { + TableType::Props mergedProps = lt->props; + for (const auto& [name, rightProp] : rt->props) + mergedProps[name] = rightProp; + + return arena->addType(TableType{mergedProps, std::nullopt, TypeLevel{}, lt->scope, TableState::Sealed}); + } } } @@ -1527,9 +1541,9 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right) return right; if (get(right) && !get(left)) return left; - if (FFlag::LuauSimplifyAnyAndUnion && get(left) && get(right)) + if (get(left) && get(right)) return union_(builtinTypes->errorType, right); - if (FFlag::LuauSimplifyAnyAndUnion && get(left) && get(right)) + if (get(left) && get(right)) return union_(builtinTypes->errorType, left); if (get(left)) return arena->addType(UnionType{{right, builtinTypes->errorType}}); diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index b76f33e0..ed078877 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -14,6 +14,7 @@ #include "Luau/Unifier2.h" LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintIntersection) +LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintSingleton) namespace Luau { @@ -124,6 +125,23 @@ struct BidirectionalTypePusher if (ft && get(ft->lowerBound) && fastIsSubtype(solver->builtinTypes->stringType, ft->upperBound) && fastIsSubtype(ft->lowerBound, solver->builtinTypes->stringType)) { + if (FFlag::LuauPushTypeConstraintSingleton && maybeSingleton(expectedType) && maybeSingleton(ft->lowerBound)) + { + // If we see a pattern like: + // + // local function foo(my_enum: "foo" | "bar" | T) -> T + // return my_enum + // end + // local var = foo("meow") + // + // ... where we are attempting to push a singleton onto any string + // literal, and the lower bound is still a singleton, then snap + // to said lower bound. + emplaceType(asMutable(exprType), ft->lowerBound); + solver->unblock(exprType, expr->location); + return exprType; + } + // if the upper bound is a subtype of the expected type, we can push the expected type in Relation upperBoundRelation = relate(ft->upperBound, expectedType); if (upperBoundRelation == Relation::Subset || upperBoundRelation == Relation::Coincident) diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 65012feb..b17c263f 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -1011,7 +1011,7 @@ BuiltinTypes::BuiltinTypes() , threadType(arena->addType(Type{PrimitiveType{PrimitiveType::Thread}, /*persistent*/ true})) , bufferType(arena->addType(Type{PrimitiveType{PrimitiveType::Buffer}, /*persistent*/ true})) , functionType(arena->addType(Type{PrimitiveType{PrimitiveType::Function}, /*persistent*/ true})) - , externType(arena->addType(Type{ExternType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true})) + , externType(arena->addType(Type{ExternType{"userdata", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true})) , tableType(arena->addType(Type{PrimitiveType{PrimitiveType::Table}, /*persistent*/ true})) , emptyTableType(arena->addType(Type{TableType{TableState::Sealed, TypeLevel{}, nullptr}, /*persistent*/ true})) , trueType(arena->addType(Type{SingletonType{BooleanSingleton{true}}, /*persistent*/ true})) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 3e97769b..aac95f44 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -48,7 +48,7 @@ LUAU_FASTFLAGVARIABLE(LuauRemoveGenericErrorForParams) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAGVARIABLE(LuauAddErrorCaseForIncompatibleTypePacks) LUAU_FASTFLAGVARIABLE(LuauAddConditionalContextForTernary) -LUAU_FASTFLAGVARIABLE(LuauCheckForInWithSubtyping) +LUAU_FASTFLAGVARIABLE(LuauCheckForInWithSubtyping2) LUAU_FASTFLAGVARIABLE(LuauNoOrderingTypeFunctions) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) @@ -1000,11 +1000,11 @@ void TypeChecker2::visit(AstStatForIn* forInStatement) else reportError(GenericError{"next() does not return enough values"}, forInStatement->values.data[0]->location); - if (FFlag::LuauCheckForInWithSubtyping) + if (FFlag::LuauCheckForInWithSubtyping2) return; } - if (!FFlag::LuauCheckForInWithSubtyping) + if (!FFlag::LuauCheckForInWithSubtyping2) { for (size_t i = 0; i < std::min(expectedVariableTypes.head.size(), variableTypes.size()); ++i) testIsSubtype(variableTypes[i], expectedVariableTypes.head[i], forInStatement->vars.data[i]->location); @@ -1039,7 +1039,7 @@ void TypeChecker2::visit(AstStatForIn* forInStatement) else reportError(CountMismatch{2, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->values.data[0]->location); - if (FFlag::LuauCheckForInWithSubtyping) + if (FFlag::LuauCheckForInWithSubtyping2) return; } else if (actualArgCount < minCount) @@ -1049,11 +1049,11 @@ void TypeChecker2::visit(AstStatForIn* forInStatement) else reportError(CountMismatch{2, std::nullopt, firstIterationArgCount, CountMismatch::Arg}, forInStatement->values.data[0]->location); - if (FFlag::LuauCheckForInWithSubtyping) + if (FFlag::LuauCheckForInWithSubtyping2) return; } - if (FFlag::LuauCheckForInWithSubtyping) + if (FFlag::LuauCheckForInWithSubtyping2) { const TypeId iterFunc = follow(iterTys[0]); @@ -3401,7 +3401,7 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location void TypeChecker2::maybeReportSubtypingError(const TypeId subTy, const TypeId superTy, const Location& location) { - LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping); + LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping2); switch (shouldSuppressErrors(NotNull{&normalizer}, subTy).orElse(shouldSuppressErrors(NotNull{&normalizer}, superTy))) { case ErrorSuppression::Suppress: @@ -3420,7 +3420,7 @@ void TypeChecker2::maybeReportSubtypingError(const TypeId subTy, const TypeId su void TypeChecker2::testIsSubtypeForInStat(const TypeId iterFunc, const TypeId prospectiveFunc, const AstStatForIn& forInStat) { - LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping); + LUAU_ASSERT(FFlag::LuauCheckForInWithSubtyping2); LUAU_ASSERT(get(follow(iterFunc))); LUAU_ASSERT(get(follow(prospectiveFunc))); @@ -3481,7 +3481,9 @@ void TypeChecker2::testIsSubtypeForInStat(const TypeId iterFunc, const TypeId pr if (*pf == TypePath::PackField::Arguments) { // The first component of `forInStat.values` is the iterator function itself - Location loc = index->index >= forInStat.values.size ? iterFuncLocation : forInStat.values.data[index->index + 1]->location; + Location loc = index->index + 1 >= forInStat.values.size + ? iterFuncLocation + : forInStat.values.data[index->index + 1]->location; maybeReportSubtypingError(*subLeaf, *superLeaf, loc); } else if (*pf == TypePath::PackField::Returns) diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index 332389bd..12e50fec 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAGVARIABLE(LuauConsiderErrorSuppressionInTypes) // 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 @@ -369,6 +370,7 @@ struct TraversalState // TODO: make NotNull when LuauReturnMappedGenericPacksFromSubtyping3 is clipped TypeArena* arena = nullptr; int steps = 0; + bool encounteredErrorSuppression = false; void updateCurrent(TypeId ty) { @@ -472,26 +474,77 @@ struct TraversalState if (auto currentType = get(current)) { + bool updatedCurrent = false; + + if (FFlag::LuauConsiderErrorSuppressionInTypes && get(*currentType)) + { + encounteredErrorSuppression = true; + return false; + } + if (auto u = get(*currentType)) { auto it = begin(u); - std::advance(it, index.index); - if (it != end(u)) + if (FFlag::LuauConsiderErrorSuppressionInTypes) { - updateCurrent(*it); - return true; + // We want to track the index that updates the current type with `idx` while still iterating through the entire union to check for error types with `it`. + size_t idx = 0; + for (auto it = begin(u); it != end(u); ++it) + { + if (get(*it)) + encounteredErrorSuppression = true; + if (idx == index.index) + { + updateCurrent(*it); + updatedCurrent = true; + } + ++idx; + } + } + else + { + std::advance(it, index.index); + + if (it != end(u)) + { + updateCurrent(*it); + return true; + } } } else if (auto i = get(*currentType)) { auto it = begin(i); - std::advance(it, index.index); - if (it != end(i)) + if (FFlag::LuauConsiderErrorSuppressionInTypes) { - updateCurrent(*it); - return true; + // We want to track the index that updates the current type with `idx` while still iterating through the entire intersection to check for error types with `it`. + size_t idx = 0; + for (auto it = begin(i); it != end(i); ++it) + { + if (get(*it)) + encounteredErrorSuppression = true; + if (idx == index.index) + { + updateCurrent(*it); + updatedCurrent = true; + } + ++idx; + } + } + else + { + std::advance(it, index.index); + + if (it != end(i)) + { + updateCurrent(*it); + return true; + } } } + + if (FFlag::LuauConsiderErrorSuppressionInTypes) + return updatedCurrent; } else { @@ -1198,6 +1251,8 @@ std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorType; auto ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1218,6 +1273,8 @@ std::optional traverseForType_DEPRECATED( TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorType; auto ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1232,6 +1289,9 @@ std::optional traverseForType(const TypeId root, const Path& path, const TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorType; + const TypeId* ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1246,6 +1306,8 @@ std::optional traverseForType_DEPRECATED(TypePackId root, const Path& pa TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorType; auto ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1266,6 +1328,8 @@ std::optional traverseForType_DEPRECATED( TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorType; auto ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1285,6 +1349,8 @@ std::optional traverseForType( TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorType; const TypeId* ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1299,6 +1365,8 @@ std::optional traverseForPack_DEPRECATED(TypeId root, const Path& pa TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorTypePack; auto ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1319,6 +1387,8 @@ std::optional traverseForPack_DEPRECATED( TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorTypePack; auto ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1338,6 +1408,8 @@ std::optional traverseForPack( TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorTypePack; const TypePackId* ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1352,6 +1424,8 @@ std::optional traverseForPack_DEPRECATED(TypePackId root, const Path TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorTypePack; auto ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1372,6 +1446,8 @@ std::optional traverseForPack_DEPRECATED( TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorTypePack; auto ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } @@ -1391,6 +1467,8 @@ std::optional traverseForPack( TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) { + if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) + return builtinTypes->errorTypePack; const TypePackId* ty = get(state.current); return ty ? std::make_optional(*ty) : std::nullopt; } diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index df613199..137cf902 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -236,14 +236,14 @@ enum class IrCmd : uint8_t // Perform a comparison of two tags. Result is an integer register containing 0 or 1 CMP_TAG, // A, B: tag - // C: condition (eq/neq) + // C: condition (eq/not_eq) // Perform tag and value comparison. Result is an integer register containing 0 or 1 CMP_SPLIT_TVALUE, // A: tag - // B: tag (constant: boolean/string) + // B: tag (constant: boolean/number/string) // C, D: value - // E: condition (eq/neq) + // E: condition (eq/not_eq) // Unconditional jump // A: block/vmexit/undef diff --git a/CodeGen/include/Luau/IrVisitUseDef.h b/CodeGen/include/Luau/IrVisitUseDef.h index 7d3f7438..0674641b 100644 --- a/CodeGen/include/Luau/IrVisitUseDef.h +++ b/CodeGen/include/Luau/IrVisitUseDef.h @@ -4,7 +4,7 @@ #include "Luau/Common.h" #include "Luau/IrData.h" -LUAU_FASTFLAG(LuauCodegenDirectCompare) +LUAU_FASTFLAG(LuauCodegenDirectCompare2) namespace Luau { @@ -40,7 +40,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i visitor.use(inst.b); break; case IrCmd::CMP_TAG: - if (FFlag::LuauCodegenDirectCompare) + if (FFlag::LuauCodegenDirectCompare2) visitor.maybeUse(inst.a); break; case IrCmd::JUMP_IF_TRUTHY: @@ -48,7 +48,7 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i visitor.use(inst.a); break; case IrCmd::JUMP_EQ_TAG: - if (FFlag::LuauCodegenDirectCompare) + if (FFlag::LuauCodegenDirectCompare2) visitor.maybeUse(inst.a); break; // A <- B, C diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index 54110aa8..3ea22f3c 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -10,8 +10,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCodeGenBetterBytecodeAnalysis) - namespace Luau { namespace CodeGen @@ -774,26 +772,16 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) } case LOP_GETTABLE: { - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - int ra = LUAU_INSN_A(*pc); - int rb = LUAU_INSN_B(*pc); - int rc = LUAU_INSN_C(*pc); + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int rc = LUAU_INSN_C(*pc); - regTags[ra] = LBC_TYPE_ANY; + regTags[ra] = LBC_TYPE_ANY; - bcType.a = regTags[rb]; - bcType.b = regTags[rc]; + bcType.a = regTags[rb]; + bcType.b = regTags[rc]; - bcType.result = regTags[ra]; - } - else - { - int rb = LUAU_INSN_B(*pc); - int rc = LUAU_INSN_C(*pc); - bcType.a = regTags[rb]; - bcType.b = regTags[rc]; - } + bcType.result = regTags[ra]; break; } case LOP_SETTABLE: @@ -851,25 +839,15 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) } case LOP_GETTABLEN: { - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - int ra = LUAU_INSN_A(*pc); - int rb = LUAU_INSN_B(*pc); + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); - regTags[ra] = LBC_TYPE_ANY; + regTags[ra] = LBC_TYPE_ANY; - bcType.a = regTags[rb]; - bcType.b = LBC_TYPE_NUMBER; + bcType.a = regTags[rb]; + bcType.b = LBC_TYPE_NUMBER; - bcType.result = regTags[ra]; - } - else - { - int rb = LUAU_INSN_B(*pc); - bcType.a = regTags[rb]; - bcType.b = LBC_TYPE_NUMBER; - break; - } + bcType.result = regTags[ra]; break; } case LOP_SETTABLEN: @@ -1157,11 +1135,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) refineRegType(bcTypeInfo, ra, i, bcType.result); - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - // Fastcall failure fallback is skipped from result propagation - i += skip; - } + // Fastcall failure fallback is skipped from result propagation + i += skip; break; } case LOP_FASTCALL1: @@ -1181,11 +1156,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) refineRegType(bcTypeInfo, ra, i, bcType.result); - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - // Fastcall failure fallback is skipped from result propagation - i += skip; - } + // Fastcall failure fallback is skipped from result propagation + i += skip; break; } case LOP_FASTCALL2: @@ -1205,11 +1177,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) refineRegType(bcTypeInfo, ra, i, bcType.result); - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - // Fastcall failure fallback is skipped from result propagation - i += skip; - } + // Fastcall failure fallback is skipped from result propagation + i += skip; break; } case LOP_FASTCALL3: @@ -1231,11 +1200,8 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) refineRegType(bcTypeInfo, ra, i, bcType.result); - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - // Fastcall failure fallback is skipped from result propagation - i += skip; - } + // Fastcall failure fallback is skipped from result propagation + i += skip; break; } case LOP_FORNPREP: @@ -1347,13 +1313,10 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) } case LOP_GETGLOBAL: { - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - int ra = LUAU_INSN_A(*pc); + int ra = LUAU_INSN_A(*pc); - regTags[ra] = LBC_TYPE_ANY; - bcType.result = regTags[ra]; - } + regTags[ra] = LBC_TYPE_ANY; + bcType.result = regTags[ra]; break; } case LOP_SETGLOBAL: @@ -1382,48 +1345,39 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) case LOP_AND: case LOP_OR: { - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - int ra = LUAU_INSN_A(*pc); - int rb = LUAU_INSN_B(*pc); - int rc = LUAU_INSN_C(*pc); + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int rc = LUAU_INSN_C(*pc); - bcType.a = regTags[rb]; - bcType.b = regTags[rc]; + bcType.a = regTags[rb]; + bcType.b = regTags[rc]; - regTags[ra] = LBC_TYPE_ANY; - bcType.result = regTags[ra]; - } + regTags[ra] = LBC_TYPE_ANY; + bcType.result = regTags[ra]; break; } case LOP_ANDK: case LOP_ORK: { - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - int ra = LUAU_INSN_A(*pc); - int rb = LUAU_INSN_B(*pc); - int kc = LUAU_INSN_C(*pc); + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int kc = LUAU_INSN_C(*pc); - bcType.a = regTags[rb]; - bcType.b = getBytecodeConstantTag(proto, kc); + bcType.a = regTags[rb]; + bcType.b = getBytecodeConstantTag(proto, kc); - regTags[ra] = LBC_TYPE_ANY; - bcType.result = regTags[ra]; - } + regTags[ra] = LBC_TYPE_ANY; + bcType.result = regTags[ra]; break; } case LOP_COVERAGE: break; case LOP_GETIMPORT: { - if (FFlag::LuauCodeGenBetterBytecodeAnalysis) - { - int ra = LUAU_INSN_A(*pc); + int ra = LUAU_INSN_A(*pc); - regTags[ra] = LBC_TYPE_ANY; - bcType.result = regTags[ra]; - } + regTags[ra] = LBC_TYPE_ANY; + bcType.result = regTags[ra]; break; } case LOP_CAPTURE: diff --git a/CodeGen/src/IrBuilder.cpp b/CodeGen/src/IrBuilder.cpp index 1b5a238d..69cde9ab 100644 --- a/CodeGen/src/IrBuilder.cpp +++ b/CodeGen/src/IrBuilder.cpp @@ -12,7 +12,7 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCodegenDirectCompare) +LUAU_FASTFLAGVARIABLE(LuauCodegenDirectCompare2) namespace Luau { @@ -381,7 +381,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstJumpX(*this, pc, i); break; case LOP_JUMPXEQKNIL: - if (FFlag::LuauCodegenDirectCompare && isDirectCompare(function.proto, pc, i)) + if (FFlag::LuauCodegenDirectCompare2 && isDirectCompare(function.proto, pc, i)) { translateInstJumpxEqNilShortcut(*this, pc, i); @@ -394,7 +394,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstJumpxEqNil(*this, pc, i); break; case LOP_JUMPXEQKB: - if (FFlag::LuauCodegenDirectCompare && isDirectCompare(function.proto, pc, i)) + if (FFlag::LuauCodegenDirectCompare2 && isDirectCompare(function.proto, pc, i)) { translateInstJumpxEqBShortcut(*this, pc, i); @@ -407,7 +407,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstJumpxEqB(*this, pc, i); break; case LOP_JUMPXEQKN: - if (FFlag::LuauCodegenDirectCompare && isDirectCompare(function.proto, pc, i)) + if (FFlag::LuauCodegenDirectCompare2 && isDirectCompare(function.proto, pc, i)) { translateInstJumpxEqNShortcut(*this, pc, i); @@ -420,7 +420,7 @@ void IrBuilder::translateInst(LuauOpcode op, const Instruction* pc, int i) translateInstJumpxEqN(*this, pc, i); break; case LOP_JUMPXEQKS: - if (FFlag::LuauCodegenDirectCompare && isDirectCompare(function.proto, pc, i)) + if (FFlag::LuauCodegenDirectCompare2 && isDirectCompare(function.proto, pc, i)) { translateInstJumpxEqSShortcut(*this, pc, i); diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index c7f0d42b..7382a2b7 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -13,9 +13,8 @@ #include "lgc.h" LUAU_FASTFLAG(LuauCodeGenUnassignedBcTargetAbort) -LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) -LUAU_FASTFLAG(LuauCodegenDirectCompare) +LUAU_FASTFLAG(LuauCodegenDirectCompare2) namespace Luau { @@ -870,8 +869,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::CMP_INT: { - CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); - inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); IrCondition cond = conditionOp(inst.c); @@ -919,7 +916,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::CMP_TAG: { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); IrCondition cond = conditionOp(inst.c); @@ -976,7 +973,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::CMP_SPLIT_TVALUE: { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); // Second operand of this instruction must be a constant @@ -1086,7 +1083,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { RegisterA64 zr = noreg; - if (FFlag::LuauCodegenDirectCompare) + if (FFlag::LuauCodegenDirectCompare2) { RegisterA64 aReg = noreg; RegisterA64 bReg = noreg; diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index ffe65077..328c2ce4 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -17,10 +17,9 @@ #include "lgc.h" LUAU_FASTFLAG(LuauCodeGenUnassignedBcTargetAbort) -LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTFLAGVARIABLE(LuauCodeGenVBlendpdReorder) LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) -LUAU_FASTFLAG(LuauCodegenDirectCompare) +LUAU_FASTFLAG(LuauCodegenDirectCompare2) namespace Luau { @@ -873,8 +872,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::CMP_INT: { - CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); - // Cannot reuse operand registers as a target because we have to modify it before the comparison inst.regX64 = regs.allocReg(SizeX64::dword, index); @@ -924,7 +921,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::CMP_TAG: { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); // Cannot reuse operand registers as a target because we have to modify it before the comparison inst.regX64 = regs.allocReg(SizeX64::dword, index); @@ -946,7 +943,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::CMP_SPLIT_TVALUE: { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); // Cannot reuse operand registers as a target because we have to modify it before the comparison inst.regX64 = regs.allocReg(SizeX64::dword, index); diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index b4b7b73e..c2e7c101 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -8,7 +8,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCodeGenDirectBtest) LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorLerp) LUAU_FASTFLAGVARIABLE(LuauCodeGenFMA) @@ -460,30 +459,9 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp( if (btest) { - if (FFlag::LuauCodeGenDirectBtest) - { - IrOp value = build.inst(IrCmd::CMP_INT, res, build.constInt(0), build.cond(IrCondition::NotEqual)); - build.inst(IrCmd::STORE_INT, build.vmReg(ra), value); - build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); - } - else - { - IrOp falsey = build.block(IrBlockKind::Internal); - IrOp truthy = build.block(IrBlockKind::Internal); - IrOp exit = build.block(IrBlockKind::Internal); - build.inst(IrCmd::JUMP_CMP_INT, res, build.constInt(0), build.cond(IrCondition::Equal), falsey, truthy); - - build.beginBlock(falsey); - build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(0)); - build.inst(IrCmd::JUMP, exit); - - build.beginBlock(truthy); - build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(1)); - build.inst(IrCmd::JUMP, exit); - - build.beginBlock(exit); - build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); - } + IrOp value = build.inst(IrCmd::CMP_INT, res, build.constInt(0), build.cond(IrCondition::NotEqual)); + build.inst(IrCmd::STORE_INT, build.vmReg(ra), value); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); } else { diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index f981a194..8d4485c5 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -12,7 +12,7 @@ #include "lstate.h" #include "ltm.h" -LUAU_FASTFLAG(LuauCodegenDirectCompare) +LUAU_FASTFLAG(LuauCodegenDirectCompare2) namespace Luau { @@ -272,7 +272,7 @@ void translateInstJumpxEqNil(IrBuilder& build, const Instruction* pc, int pcpos) void translateInstJumpxEqNilShortcut(IrBuilder& build, const Instruction* pc, int pcpos) { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); int rr = LUAU_INSN_A(pc[2]); int ra = LUAU_INSN_A(*pc); @@ -320,7 +320,7 @@ void translateInstJumpxEqB(IrBuilder& build, const Instruction* pc, int pcpos) void translateInstJumpxEqBShortcut(IrBuilder& build, const Instruction* pc, int pcpos) { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); int rr = LUAU_INSN_A(pc[2]); int ra = LUAU_INSN_A(*pc); @@ -377,7 +377,7 @@ void translateInstJumpxEqN(IrBuilder& build, const Instruction* pc, int pcpos) void translateInstJumpxEqNShortcut(IrBuilder& build, const Instruction* pc, int pcpos) { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); int rr = LUAU_INSN_A(pc[2]); int ra = LUAU_INSN_A(*pc); @@ -433,7 +433,7 @@ void translateInstJumpxEqS(IrBuilder& build, const Instruction* pc, int pcpos) void translateInstJumpxEqSShortcut(IrBuilder& build, const Instruction* pc, int pcpos) { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); int rr = LUAU_INSN_A(pc[2]); int ra = LUAU_INSN_A(*pc); diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index 84284327..3f22416c 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -16,8 +16,7 @@ #include #include -LUAU_FASTFLAG(LuauCodeGenDirectBtest) -LUAU_FASTFLAG(LuauCodegenDirectCompare) +LUAU_FASTFLAG(LuauCodegenDirectCompare2) namespace Luau { @@ -799,8 +798,6 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 } break; case IrCmd::CMP_INT: - CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); - if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) { if (compare(function.intOp(inst.a), function.intOp(inst.b), conditionOp(inst.c))) @@ -810,57 +807,82 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 } break; case IrCmd::CMP_TAG: - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) { - substitute(function, inst, build.constInt(function.tagOp(inst.a) == function.tagOp(inst.b) ? 1 : 0)); + IrCondition cond = conditionOp(inst.c); + CODEGEN_ASSERT(cond == IrCondition::Equal || cond == IrCondition::NotEqual); + + if (cond == IrCondition::Equal) + substitute(function, inst, build.constInt(function.tagOp(inst.a) == function.tagOp(inst.b) ? 1 : 0)); + else + substitute(function, inst, build.constInt(function.tagOp(inst.a) != function.tagOp(inst.b) ? 1 : 0)); } break; case IrCmd::CMP_SPLIT_TVALUE: - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + { + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); + CODEGEN_ASSERT(inst.b.kind == IrOpKind::Constant); - if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) - { - IrCondition cond = conditionOp(inst.e); + IrCondition cond = conditionOp(inst.e); + CODEGEN_ASSERT(cond == IrCondition::Equal || cond == IrCondition::NotEqual); - if (cond == IrCondition::Equal) + if (cond == IrCondition::Equal) + { + if (inst.a.kind == IrOpKind::Constant && function.tagOp(inst.a) != function.tagOp(inst.b)) { - if (function.tagOp(inst.a) != function.tagOp(inst.b)) - { - substitute(function, inst, build.constInt(0)); - } - else if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant) - { - if (function.tagOp(inst.a) == LUA_TBOOLEAN) - substitute(function, inst, build.constInt(compare(function.intOp(inst.c), function.intOp(inst.d), cond) ? 1 : 0)); - else if (function.tagOp(inst.a) == LUA_TNUMBER) - substitute(function, inst, build.constInt(compare(function.doubleOp(inst.c), function.doubleOp(inst.d), cond) ? 1 : 0)); - else - CODEGEN_ASSERT(!"unsupported type"); - } + substitute(function, inst, build.constInt(0)); } - else if (cond == IrCondition::NotEqual) + else if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant) { - if (function.tagOp(inst.a) != function.tagOp(inst.b)) - { + // If the tag is a constant, this means previous condition has failed because tags are the same + bool knownSameTag = inst.a.kind == IrOpKind::Constant; + bool sameValue = false; + + if (function.tagOp(inst.b) == LUA_TBOOLEAN) + sameValue = compare(function.intOp(inst.c), function.intOp(inst.d), IrCondition::Equal); + else if (function.tagOp(inst.b) == LUA_TNUMBER) + sameValue = compare(function.doubleOp(inst.c), function.doubleOp(inst.d), IrCondition::Equal); + else + CODEGEN_ASSERT(!"unsupported type"); + + if (knownSameTag && sameValue) substitute(function, inst, build.constInt(1)); - } - else if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant) - { - if (function.tagOp(inst.a) == LUA_TBOOLEAN) - substitute(function, inst, build.constInt(compare(function.intOp(inst.c), function.intOp(inst.d), cond) ? 1 : 0)); - else if (function.tagOp(inst.a) == LUA_TNUMBER) - substitute(function, inst, build.constInt(compare(function.doubleOp(inst.c), function.doubleOp(inst.d), cond) ? 1 : 0)); - else - CODEGEN_ASSERT(!"unsupported type"); - } + else if (sameValue) + replace(function, block, index, {IrCmd::CMP_TAG, inst.a, inst.b, inst.e}); + else + substitute(function, inst, build.constInt(0)); } - else + } + else + { + if (inst.a.kind == IrOpKind::Constant && function.tagOp(inst.a) != function.tagOp(inst.b)) { - CODEGEN_ASSERT(!"unsupported condition"); + substitute(function, inst, build.constInt(1)); + } + else if (inst.c.kind == IrOpKind::Constant && inst.d.kind == IrOpKind::Constant) + { + // If the tag is a constant, this means previous condition has failed because tags are the same + bool knownSameTag = inst.a.kind == IrOpKind::Constant; + bool differentValue = false; + + if (function.tagOp(inst.b) == LUA_TBOOLEAN) + differentValue = compare(function.intOp(inst.c), function.intOp(inst.d), IrCondition::NotEqual); + else if (function.tagOp(inst.b) == LUA_TNUMBER) + differentValue = compare(function.doubleOp(inst.c), function.doubleOp(inst.d), IrCondition::NotEqual); + else + CODEGEN_ASSERT(!"unsupported type"); + + if (differentValue) + substitute(function, inst, build.constInt(1)); + else if (knownSameTag) + substitute(function, inst, build.constInt(0)); + else + replace(function, block, index, {IrCmd::CMP_TAG, inst.a, inst.b, inst.e}); } } + } break; case IrCmd::JUMP_EQ_TAG: if (inst.a.kind == IrOpKind::Constant && inst.b.kind == IrOpKind::Constant) diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index dff7dc81..fde68d95 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -23,8 +23,8 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) -LUAU_FASTFLAG(LuauCodeGenDirectBtest) -LUAU_FASTFLAG(LuauCodegenDirectCompare) +LUAU_FASTFLAG(LuauCodegenDirectCompare2) +LUAU_FASTFLAGVARIABLE(LuauCodegenNilStoreInvalidateValue2) namespace Luau { @@ -64,7 +64,7 @@ struct NumberedInstruction static uint8_t tryGetTagForTypename(std::string_view name, bool forTypeof) { - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); if (name == "nil") return LUA_TNIL; @@ -730,9 +730,19 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& std::tie(activeLoadCmd, activeLoadValue) = state.getPreviousVersionedLoadForTag(value, source); if (state.tryGetTag(source) == value) + { kill(function, inst); + } else + { state.saveTag(source, value); + + // Storing 'nil' implicitly kills the known value in the register + // This is required for dead store elimination to correctly establish tag+value pairs as it treats 'nil' write as a full TValue + // store + if (FFlag::LuauCodegenNilStoreInvalidateValue2 && value == LUA_TNIL) + state.invalidateValue(source); + } } else { @@ -1371,16 +1381,15 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.substituteOrRecord(inst, index); break; case IrCmd::CMP_INT: - CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); break; case IrCmd::CMP_ANY: state.invalidateUserCall(); break; case IrCmd::CMP_TAG: - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); break; case IrCmd::CMP_SPLIT_TVALUE: - CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare); + CODEGEN_ASSERT(FFlag::LuauCodegenDirectCompare2); if (function.proto) { @@ -1419,7 +1428,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::JUMP: break; case IrCmd::JUMP_EQ_POINTER: - if (FFlag::LuauCodegenDirectCompare && function.proto) + if (FFlag::LuauCodegenDirectCompare2 && function.proto) { // Try to find pattern like type(x) == 'tagname' or typeof(x) == 'tagname' if (inst.a.kind == IrOpKind::Inst && inst.b.kind == IrOpKind::Inst) @@ -1890,7 +1899,18 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited constPropInBlock(build, startingBlock, state); // Verify that target hasn't changed - CODEGEN_ASSERT(function.instructions[startingBlock.finish].a.index == targetBlockIdx); + if (FFlag::LuauCodegenNilStoreInvalidateValue2) + { + if (function.instructions[startingBlock.finish].a.index != targetBlockIdx) + { + CODEGEN_ASSERT(!"Running same optimization pass on the linear chain head block changed the jump target"); + return; + } + } + else + { + CODEGEN_ASSERT(function.instructions[startingBlock.finish].a.index == targetBlockIdx); + } // Note: using startingBlock after this line is unsafe as the reference may be reallocated by build.block() below const uint32_t startingSortKey = startingBlock.sortkey; diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 78dde55a..361d977e 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -21,7 +21,6 @@ LUAU_DYNAMIC_FASTINT(LuauSubtypingRecursionLimit) LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauIncludeBreakContinueStatements) LUAU_FASTFLAG(LuauSuggestHotComments) LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) @@ -4724,8 +4723,6 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self") TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_loop") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check(R"(for x in y do @1 if true then @@ -4746,8 +4743,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_loop") TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_outside_loop") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check(R"(@1if true then @2 end)"); @@ -4764,8 +4759,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_outside_loop") TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_function_boundary") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check(R"(for i = 1, 10 do local function helper() @1 @@ -4780,8 +4773,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_function_bound TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_param") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check(R"(while @1 do end)"); @@ -4793,8 +4784,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_param") TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_incomplete_while") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check("while @1"); auto ac = autocomplete('1'); @@ -4805,8 +4794,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_incomplete_whi TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_incomplete_for") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check("for @1 in @2 do"); auto ac = autocomplete('1'); @@ -4822,8 +4809,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_incomplete_for TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_expr_func") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check(R"(while true do local _ = function () @1 @@ -4838,8 +4823,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_expr_func") TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_repeat") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check(R"(repeat @1 until foo())"); @@ -4852,8 +4835,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_repeat") TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_nests") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check(R"(while ((function () while true do @1 @@ -4869,8 +4850,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_nests") TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_incomplete_loop") { - ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; - check(R"(while foo() do @1)"); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 1675cdd2..ce008e94 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -36,14 +36,12 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata); LUAU_DYNAMIC_FASTFLAG(LuauXpcallContErrorHandling) LUAU_FASTFLAG(DebugLuauAbortingChecks) -LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTFLAG(LuauVectorLerp) LUAU_FASTFLAG(LuauCompileVectorLerp) -LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) +LUAU_FASTFLAG(LuauTypeCheckerVectorLerp2) LUAU_FASTFLAG(LuauCodeGenVectorLerp) LUAU_DYNAMIC_FASTFLAG(LuauXpcallContNoYield) -LUAU_FASTFLAG(LuauCodeGenBetterBytecodeAnalysis) LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) LUAU_FASTFLAG(LuauCodeGenRestoreFromSplitStore) LUAU_FASTFLAG(LuauStacklessPcall) @@ -1167,10 +1165,9 @@ TEST_CASE("VectorLibrary") { ScopedFastFlag _[]{ {FFlag::LuauCompileVectorLerp, true}, - {FFlag::LuauTypeCheckerVectorLerp, true}, + {FFlag::LuauTypeCheckerVectorLerp2, true}, {FFlag::LuauVectorLerp, true}, - {FFlag::LuauCodeGenVectorLerp, true}, - {FFlag::LuauCodeGenBetterBytecodeAnalysis, true} + {FFlag::LuauCodeGenVectorLerp, true} }; lua_CompileOptions copts = defaultOptions(); @@ -3246,7 +3243,6 @@ TEST_CASE("SafeEnv") TEST_CASE("Native") { - ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, true}; ScopedFastFlag luauCodeGenRegAutoSpillA64{FFlag::LuauCodeGenRegAutoSpillA64, true}; ScopedFastFlag luauCodeGenRestoreFromSplitStore{FFlag::LuauCodeGenRestoreFromSplitStore, true}; diff --git a/tests/EqSatSimplification.test.cpp b/tests/EqSatSimplification.test.cpp index 64debe81..c7dbd78b 100644 --- a/tests/EqSatSimplification.test.cpp +++ b/tests/EqSatSimplification.test.cpp @@ -228,7 +228,7 @@ TEST_CASE_FIXTURE(ESFixture, "\"hello\" | \"world\" | \"hello\"") ); } -TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | function | table | class | buffer") +TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | function | table | userdata | buffer") { CHECK( "unknown" == simplifyStr(arena->addType( @@ -267,14 +267,14 @@ TEST_CASE_FIXTURE(ESFixture, "Child | Parent") CHECK("Parent" == simplifyStr(arena->addType(UnionType{{childClass, parentClass}}))); } -TEST_CASE_FIXTURE(ESFixture, "class | Child") +TEST_CASE_FIXTURE(ESFixture, "userdata | Child") { - CHECK("class" == simplifyStr(arena->addType(UnionType{{getBuiltins()->externType, childClass}}))); + CHECK("userdata" == simplifyStr(arena->addType(UnionType{{getBuiltins()->externType, childClass}}))); } -TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child") +TEST_CASE_FIXTURE(ESFixture, "Parent | userdata | Child") { - CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, getBuiltins()->externType, childClass}}))); + CHECK("userdata" == simplifyStr(arena->addType(UnionType{{parentClass, getBuiltins()->externType, childClass}}))); } TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated") diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 70be8994..47a5ac2d 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -13,6 +13,8 @@ #include LUAU_FASTFLAG(DebugLuauAbortingChecks) +LUAU_FASTFLAG(LuauCodegenDirectCompare2) +LUAU_FASTFLAG(LuauCodegenNilStoreInvalidateValue2) using namespace Luau::CodeGen; @@ -1770,6 +1772,124 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumericSimplifications") )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "CmpTagSimplification") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; + + IrOp block = build.block(IrBlockKind::Internal); + + build.beginBlock(block); + + IrOp eq = build.cond(IrCondition::Equal); + IrOp neq = build.cond(IrCondition::NotEqual); + + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.inst(IrCmd::CMP_TAG, build.constTag(tnil), build.constTag(tnumber), eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(1), build.inst(IrCmd::CMP_TAG, build.constTag(tnumber), build.constTag(tnumber), eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::CMP_TAG, build.constTag(tnil), build.constTag(tnumber), neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::CMP_TAG, build.constTag(tnumber), build.constTag(tnumber), neq)); + + build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(4)); + + updateUseCounts(build.function); + constPropInBlockChains(build); + + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( +bb_0: + STORE_INT R0, 0i + STORE_INT R1, 1i + STORE_INT R2, 1i + STORE_INT R3, 0i + RETURN R0, 4i + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "CmpSplitTagValueSimplification") +{ + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; + + IrOp block = build.block(IrBlockKind::Internal); + + build.beginBlock(block); + IrOp valueBoolean = build.inst(IrCmd::LOAD_INT, build.vmReg(1)); + IrOp valueNan = build.inst(IrCmd::DIV_NUM, build.constDouble(0.0), build.constDouble(0.0)); + + IrOp opTag = build.inst(IrCmd::LOAD_TAG, build.vmReg(0)); + IrOp opNil = build.constTag(tnil); + IrOp opBool = build.constTag(tboolean); + IrOp opNum = build.constTag(tnumber); + + IrOp eq = build.cond(IrCondition::Equal); + IrOp neq = build.cond(IrCondition::NotEqual); + + build.inst(IrCmd::STORE_INT, build.vmReg(2), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNil, opBool, build.constInt(0), valueBoolean, eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(3), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(0), eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(4), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(1), eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(5), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(0), eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(6), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(1), eq)); + + build.inst(IrCmd::STORE_INT, build.vmReg(7), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNil, opBool, build.constInt(0), valueBoolean, neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(8), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(0), neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(9), build.inst(IrCmd::CMP_SPLIT_TVALUE, opBool, opBool, build.constInt(0), build.constInt(1), neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(10), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(0), neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(11), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, build.constDouble(0), build.constDouble(1), neq)); + + // When we compare known values, but unknown tags, cases can either be fully folded or simplified to a tag check + build.inst(IrCmd::STORE_INT, build.vmReg(12), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(0), eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(13), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(1), eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(14), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(0), eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(15), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(1), eq)); + + build.inst(IrCmd::STORE_INT, build.vmReg(16), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(0), neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(17), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opBool, build.constInt(0), build.constInt(1), neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(18), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(0), neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(19), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, build.constDouble(0), build.constDouble(1), neq)); + + // NaN is always fun to consider + build.inst(IrCmd::STORE_INT, build.vmReg(20), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, valueNan, valueNan, eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(21), build.inst(IrCmd::CMP_SPLIT_TVALUE, opNum, opNum, valueNan, valueNan, neq)); + build.inst(IrCmd::STORE_INT, build.vmReg(22), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, valueNan, valueNan, eq)); + build.inst(IrCmd::STORE_INT, build.vmReg(23), build.inst(IrCmd::CMP_SPLIT_TVALUE, opTag, opNum, valueNan, valueNan, neq)); + + build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(21)); + + updateUseCounts(build.function); + constPropInBlockChains(build); + + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( +bb_0: + %2 = LOAD_TAG R0 + STORE_INT R2, 0i + STORE_INT R3, 1i + STORE_INT R4, 0i + STORE_INT R5, 1i + STORE_INT R6, 0i + STORE_INT R7, 1i + STORE_INT R8, 0i + STORE_INT R9, 1i + STORE_INT R10, 0i + STORE_INT R11, 1i + %23 = CMP_TAG %2, tboolean, eq + STORE_INT R12, %23 + STORE_INT R13, 0i + %27 = CMP_TAG %2, tnumber, eq + STORE_INT R14, %27 + STORE_INT R15, 0i + %31 = CMP_TAG %2, tboolean, not_eq + STORE_INT R16, %31 + STORE_INT R17, 1i + %35 = CMP_TAG %2, tnumber, not_eq + STORE_INT R18, %35 + STORE_INT R19, 1i + STORE_INT R20, 0i + STORE_INT R21, 1i + STORE_INT R22, 0i + STORE_INT R23, 1i + RETURN R2, 21i + +)"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("LinearExecutionFlowExtraction"); @@ -4654,6 +4774,63 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VectorOverCombinedNumber") )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "NilStoreImplicitValueClear1") +{ + ScopedFastFlag luauCodegenNilStoreInvalidateValue2{FFlag::LuauCodegenNilStoreInvalidateValue2, true}; + + IrOp entry = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1)); + build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean)); + build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil)); + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1)); + build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tboolean)); + build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1)); + + updateUseCounts(build.function); + computeCfgInfo(build.function); + constPropInBlockChains(build); + markDeadStoresInBlockChains(build); + + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( +bb_0: + STORE_TAG R0, tnil + STORE_INT R0, 1i + STORE_TAG R0, tboolean + RETURN R0, 1i + +)"); +} + +TEST_CASE_FIXTURE(IrBuilderFixture, "NilStoreImplicitValueClear2") +{ + ScopedFastFlag luauCodegenNilStoreInvalidateValue2{FFlag::LuauCodegenNilStoreInvalidateValue2, true}; + + IrOp entry = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil)); + IrOp value = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)); + build.inst(IrCmd::STORE_TAG, build.vmReg(0), build.constTag(tnil)); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), value); + IrOp tag = build.inst(IrCmd::LOAD_TAG, build.vmReg(1)); + build.inst(IrCmd::CHECK_TAG, tag, build.constTag(tnil), build.vmExit(1)); + build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1)); + + updateUseCounts(build.function); + computeCfgInfo(build.function); + constPropInBlockChains(build); + markDeadStoresInBlockChains(build); + + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( +bb_0: + STORE_TAG R0, tnil + RETURN R0, 1i + +)"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("Dump"); diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index ac5faff9..b70ea6d3 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -16,13 +16,12 @@ #include #include -LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTFLAG(LuauVectorLerp) LUAU_FASTFLAG(LuauCompileVectorLerp) -LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) +LUAU_FASTFLAG(LuauTypeCheckerVectorLerp2) LUAU_FASTFLAG(LuauCodeGenVectorLerp) LUAU_FASTFLAG(LuauCodeGenFMA) -LUAU_FASTFLAG(LuauCodegenDirectCompare) +LUAU_FASTFLAG(LuauCodegenDirectCompare2) static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant) { @@ -470,7 +469,7 @@ TEST_CASE("VectorLerp") { ScopedFastFlag _[]{ {FFlag::LuauCompileVectorLerp, true}, - {FFlag::LuauTypeCheckerVectorLerp, true}, + {FFlag::LuauTypeCheckerVectorLerp2, true}, {FFlag::LuauVectorLerp, true}, {FFlag::LuauCodeGenVectorLerp, true} }; @@ -640,7 +639,7 @@ end TEST_CASE("StringCompare") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -669,7 +668,7 @@ end TEST_CASE("StringCompareAnnotated") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -702,7 +701,7 @@ end TEST_CASE("NilCompare") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -729,7 +728,7 @@ end TEST_CASE("BooleanCompare") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -784,7 +783,7 @@ end TEST_CASE("NumberCompare") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -825,7 +824,7 @@ end TEST_CASE("TypeCompare") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -856,7 +855,7 @@ end TEST_CASE("TypeofCompare") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -886,7 +885,7 @@ end TEST_CASE("TypeofCompareCustom") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -917,7 +916,7 @@ end TEST_CASE("TypeCondition") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -964,7 +963,7 @@ end TEST_CASE("AssertTypeGuard") { - ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare, true}; + ScopedFastFlag luauCodegenDirectCompare{FFlag::LuauCodegenDirectCompare2, true}; CHECK_EQ( "\n" + getCodegenAssembly( @@ -2654,8 +2653,6 @@ end TEST_CASE("Bit32BtestDirect") { - ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, true}; - CHECK_EQ( "\n" + getCodegenAssembly(R"( local function foo(a: number) diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index d833dcd5..e4b97704 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -16,6 +16,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) +LUAU_FASTFLAG(LuauNormalizerUnionTyvarsTakeMaxSize) using namespace Luau; @@ -730,7 +731,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "union_function_and_top_function") TEST_CASE_FIXTURE(NormalizeFixture, "negated_function_is_anything_except_a_function") { - CHECK("(boolean | buffer | class | number | string | table | thread)?" == toString(normal(R"( + CHECK("(boolean | buffer | number | string | table | thread | userdata)?" == toString(normal(R"( Not )"))); } @@ -755,7 +756,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "trivial_intersection_inhabited") TEST_CASE_FIXTURE(NormalizeFixture, "bare_negated_boolean") { - CHECK("(buffer | class | function | number | string | table | thread)?" == toString(normal(R"( + CHECK("(buffer | function | number | string | table | thread | userdata)?" == toString(normal(R"( Not )"))); } @@ -918,9 +919,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_extern_types") { createSomeExternTypes(getFrontend()); CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not) | Unrelated"))); - CHECK("((class & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not"))); + + CHECK("((userdata & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not"))); CHECK("never" == toString(normal("Not & Child"))); - CHECK("((class & ~Parent) | Child | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not | Child"))); + CHECK("((userdata & ~Parent) | Child | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not | Child"))); CHECK("(boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not"))); CHECK( "(Parent | Unrelated | boolean | buffer | function | number | string | table | thread)?" == @@ -951,7 +953,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "top_table_type") TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_tables") { CHECK(nullptr == toNormalizedType("Not<{}>", FFlag::LuauSolverV2 ? 1 : 0)); - CHECK("(boolean | buffer | class | function | number | string | thread)?" == toString(normal("Not"))); + CHECK("(boolean | buffer | function | number | string | thread | userdata)?" == toString(normal("Not"))); CHECK("table" == toString(normal("Not>"))); } @@ -1127,6 +1129,27 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_intersection_ordering") CHECK_EQ("'a & string", toString(typeFromNormal(*normB))); } +TEST_CASE_FIXTURE(NormalizeFixture, "tyvar_limit_one_sided_intersection" * doctest::timeout(0.5)) +{ + ScopedFastFlag _{FFlag::LuauNormalizerUnionTyvarsTakeMaxSize, true}; // Affects stringification of free types. + + std::vector options; + for (auto i = 0; i < 120; i++) + options.push_back(arena.freshType(getBuiltins(), getGlobalScope())); + + TypeId target = arena.addType(IntersectionType{ + { + getBuiltins()->unknownType, + arena.addType(UnionType{std::move(options)}) + }, + }); + + // If we try to normalize 120 free variables against `unknown`, then exit + // early and claim we've hit the limit. + auto norm = normalize(target); + REQUIRE(!norm); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow") { if (!FFlag::LuauSolverV2) diff --git a/tests/OverloadResolver.test.cpp b/tests/OverloadResolver.test.cpp index fec25c42..4806581f 100644 --- a/tests/OverloadResolver.test.cpp +++ b/tests/OverloadResolver.test.cpp @@ -8,6 +8,7 @@ #include "Luau/UnifierSharedState.h" LUAU_FASTFLAG(LuauFilterOverloadsByArity) +LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) using namespace Luau; diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index 38933d88..2f40e177 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -23,7 +23,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauIceLess) -LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) LUAU_FASTFLAG(LuauLimitUnification) @@ -293,11 +292,7 @@ TEST_CASE_FIXTURE(LimitFixture, "typescript_port_of_Result_type") TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(1.0)) { ScopedFastFlag sff[] = { - // These flags are required to surface the problem. {FFlag::LuauSolverV2, true}, - - // And this flag is the one that fixes it. - {FFlag::LuauSimplifyAnyAndUnion, true}, }; constexpr const char* src = R"LUAU( diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index 4d98fb28..dbb304b5 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -9,7 +9,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauSimplifyRefinementOfReadOnlyProperty) LUAU_DYNAMIC_FASTINT(LuauSimplificationComplexityLimit) @@ -624,8 +623,6 @@ TEST_CASE_FIXTURE(SimplifyFixture, "cyclic_never_union_and_string") TEST_CASE_FIXTURE(SimplifyFixture, "any & (error | string)") { - ScopedFastFlag sff{FFlag::LuauSimplifyAnyAndUnion, true}; - TypeId errStringTy = arena->addType(UnionType{{getBuiltins()->errorType, getBuiltins()->stringType}}); auto res = intersect(builtinTypes->anyType, errStringTy); @@ -635,8 +632,6 @@ TEST_CASE_FIXTURE(SimplifyFixture, "any & (error | string)") TEST_CASE_FIXTURE(SimplifyFixture, "(error | string) & any") { - ScopedFastFlag sff{FFlag::LuauSimplifyAnyAndUnion, true}; - TypeId errStringTy = arena->addType(UnionType{{getBuiltins()->errorType, getBuiltins()->stringType}}); auto res = intersect(errStringTy, builtinTypes->anyType); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 633ed1c5..1db63372 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) +LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) using namespace Luau; @@ -218,7 +219,7 @@ struct SubtypeFixture : Fixture TypeId booleanAndTrueType = meet(getBuiltins()->booleanType, getBuiltins()->trueType); /** - * class + * userdata * \- Root * |- Child * | |-GrandchildOne @@ -974,12 +975,12 @@ TEST_IS_NOT_SUBTYPE(getBuiltins()->unknownType, negate(getBuiltins()->stringType TEST_IS_NOT_SUBTYPE(getBuiltins()->unknownType, negate(getBuiltins()->threadType)); TEST_IS_NOT_SUBTYPE(getBuiltins()->unknownType, negate(getBuiltins()->bufferType)); -TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class") +TEST_CASE_FIXTURE(SubtypeFixture, "Root <: userdata") { CHECK_IS_SUBTYPE(rootClass, getBuiltins()->externType); } -TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: class") +TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: userdata") { CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), getBuiltins()->externType); } @@ -994,17 +995,17 @@ TEST_CASE_FIXTURE(SubtypeFixture, "Child | Root <: Root") CHECK_IS_SUBTYPE(join(childClass, rootClass), rootClass); } -TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: class") +TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: userdata") { CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), getBuiltins()->externType); } -TEST_CASE_FIXTURE(SubtypeFixture, "Child & Root <: class") +TEST_CASE_FIXTURE(SubtypeFixture, "Child & Root <: userdata") { CHECK_IS_SUBTYPE(meet(childClass, rootClass), getBuiltins()->externType); } -TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~Root <: class") +TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~Root <: userdata") { CHECK_IS_SUBTYPE(meet(childClass, negate(rootClass)), getBuiltins()->externType); } @@ -1440,6 +1441,62 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type } } +TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_check_for_error_suppression_in_union_type_path") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauConsiderErrorSuppressionInTypes, true}, + {FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}, + }; + + TypeId subTy = arena.addType(UnionType{{getBuiltins()->numberType, getBuiltins()->errorType}}); + TypeId superTy = getBuiltins()->booleanType; + SubtypingResult result = isSubtype(subTy, superTy); + CHECK(!result.isSubtype); + + for (const SubtypingReasoning& reasoning : result.reasoning) + { + if (reasoning.subPath.empty() && reasoning.superPath.empty()) + continue; + + std::optional optSubLeaf = traverseForType(subTy, reasoning.subPath, getBuiltins(), NotNull{&arena}); + std::optional optSuperLeaf = traverseForType(superTy, reasoning.superPath, getBuiltins(), NotNull{&arena}); + + if (!optSubLeaf || !optSuperLeaf) + CHECK(false); + + if (optSubLeaf != getBuiltins()->errorType) + CHECK(false); + } +} + +TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_check_for_error_suppression_in_intersect_type_path") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauConsiderErrorSuppressionInTypes, true}, + {FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}, + }; + + TypeId subTy = getBuiltins()->booleanType; + TypeId superTy = arena.addType(IntersectionType{{getBuiltins()->numberType, getBuiltins()->errorType}}); + SubtypingResult result = isSubtype(subTy, superTy); + CHECK(!result.isSubtype); + + for (const SubtypingReasoning& reasoning : result.reasoning) + { + if (reasoning.subPath.empty() && reasoning.superPath.empty()) + continue; + + std::optional optSubLeaf = traverseForType(subTy, reasoning.subPath, getBuiltins(), NotNull{&arena}); + std::optional optSuperLeaf = traverseForType(superTy, reasoning.superPath, getBuiltins(), NotNull{&arena}); + + if (!optSubLeaf || !optSuperLeaf) + CHECK(false); + + if (optSuperLeaf != getBuiltins()->errorType) + CHECK(false); + } +} + TEST_CASE_FIXTURE(SubtypeFixture, "(() -> number) -> () <: (() -> T) -> ()") { ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index bc6064aa..ad88c573 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -17,6 +17,9 @@ LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauFilterOverloadsByArity) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauVectorLerp) +LUAU_FASTFLAG(LuauCompileVectorLerp) +LUAU_FASTFLAG(LuauTypeCheckerVectorLerp2) TEST_SUITE_BEGIN("BuiltinTests"); @@ -1827,4 +1830,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "pairs_with_refined_any") CHECK_EQ(toString(requireTypeAtPosition(Position{7, 23})), "nil"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "vector_lerp_should_not_crash") +{ + ScopedFastFlag _[]{ + {FFlag::LuauCompileVectorLerp, true}, + {FFlag::LuauTypeCheckerVectorLerp2, true}, + {FFlag::LuauVectorLerp, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function half(x: number, y: number, z: number): vector + return vector.lerp(vector.zero, vector.create(x, y, z), 0.5) + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index fbccbf88..d4492e9e 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -1108,7 +1108,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_intersect_with_table_indexer") end )")); - CHECK_EQ("class & { [any]: any }", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("userdata & { [any]: any }", toString(requireTypeAtPosition({3, 28}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_with_indexer_intersect_table") diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index d24015da..f192df60 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -58,12 +58,13 @@ TEST_CASE_FIXTURE(Fixture, "typeguard_inference_incomplete") end )"; - const std::string expectedWithNewSolver = R"( + const std::string expectedWithNewSolver = + 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 + local a2:{fn:()->(unknown,...unknown)}&(userdata|function|nil|number|string|thread|buffer|table)=a end end )"; diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 56cc85b2..c3ba0873 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAG(LuauAddConditionalContextForTernary) LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) +LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) using namespace Luau; @@ -521,13 +522,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "call_an_incompatible_function_after_using_ty end )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); + if (FFlag::LuauSolverV2 && FFlag::LuauConsiderErrorSuppressionInTypes) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[0])); - CHECK(Location{{7, 18}, {7, 19}} == result.errors[0].location); + CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[0])); + CHECK(Location{{7, 18}, {7, 19}} == result.errors[0].location); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[1])); - CHECK(Location{{13, 18}, {13, 19}} == result.errors[1].location); + CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[0])); + CHECK(Location{{7, 18}, {7, 19}} == result.errors[0].location); + + CHECK("Type 'string' could not be converted into 'number'" == toString(result.errors[1])); + CHECK(Location{{13, 18}, {13, 19}} == result.errors[1].location); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error") diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index a879850d..c6da1abc 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -9,6 +9,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauPushTypeConstraint2) +LUAU_FASTFLAG(LuauPushTypeConstraintSingleton) TEST_SUITE_BEGIN("TypeSingletons"); @@ -727,4 +728,24 @@ TEST_CASE_FIXTURE(Fixture, "cli_163481_any_indexer_pushes_type") )")); } +TEST_CASE_FIXTURE(Fixture, "oss_2010") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintSingleton, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function foo(my_enum: "" | T): T + return my_enum :: T + end + + local var = foo("meow") + )")); + + CHECK_EQ("\"meow\"", toString(requireType("var"))); +} + + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 7034b76c..1a7dd51c 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -35,6 +35,7 @@ LUAU_FASTFLAG(LuauSimplifyIntersectionForLiteralSubtypeCheck) LUAU_FASTFLAG(LuauCacheDuplicateHasPropConstraints) LUAU_FASTFLAG(LuauPushTypeConstraintIntersection) LUAU_FASTFLAG(LuauFilterOverloadsByArity) +LUAU_FASTFLAG(LuauPushTypeConstraintSingleton) TEST_SUITE_BEGIN("TableTests"); @@ -6051,6 +6052,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1924") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintSingleton, true}, }; CheckResult result = check(R"( @@ -6063,7 +6065,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1924") auto err = get(result.errors[0]); REQUIRE(err); CHECK_EQ("\"s\"", toString(err->wantedType)); - CHECK_EQ("string", toString(err->givenType)); + CHECK_EQ("\"t\"", toString(err->givenType)); } TEST_CASE_FIXTURE(BuiltinsFixture, "cli_167052") diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 95ed8afa..fa1e2bd2 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -37,6 +37,7 @@ LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAG(LuauTryToOptimizeSetTypeUnification) LUAU_FASTFLAG(LuauDontReferenceScopePtrFromHashTable) +LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) using namespace Luau; @@ -2740,4 +2741,21 @@ export type t12 = { )"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "any_type_in_function_argument_should_not_error") +{ + ScopedFastFlag sff{FFlag::LuauConsiderErrorSuppressionInTypes, true}; + CheckResult result = check(R"( +--!strict +local function f(u: string) end + +local t: {[any]: any} = {} + +for k in t do + f(k) +end +)"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); From 1f6ff5d6f0072f2fab2adf35d1ffbaae7d848ad0 Mon Sep 17 00:00:00 2001 From: ariel Date: Sat, 11 Oct 2025 15:31:03 -0700 Subject: [PATCH 05/10] Sync to upstream/release/695 (#2043) Hey there friends, apologies for the late release this week! Most of the changes amount to essentially spring (really, autumn) cleaning as we prepare for the next stage of release for the New Type Solver! We've cleaned up lots of flags, enabled regression tests that had been disabled because the New Type Solver did not support them, done some refactoring and otherwise cleaned up bits of code. None of these are really worth capturing beyond the high-level of saying we cleaned things up, but there's also a few small runtime fixes that we'll describe below. ### Runtime - Fix a bug with the argument ordering for the `vblendvps` and `vblendvpd` instructions in our Native Code Generation IR that led to `vector.lerp` having the wrong argument order in some situations when using NCG. `vector.lerp` should now behave correctly under NCG. - Remove an incorrect debug assertion in Native Code Generation's register allocation implementation for A64 assembly (i.e. for ARM's 64-bit architecture). --- Co-authored-by: Andy Friesen Co-authored-by: Annie Tang Co-authored-by: Ariel Weiss Co-authored-by: Hunter Goldstein Co-authored-by: Ilya Rezvov Co-authored-by: Sora Kanosue Co-authored-by: Vyacheslav Egorov --------- Co-authored-by: Hunter Goldstein Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood Co-authored-by: Menarul Alam Co-authored-by: Aviral Goel Co-authored-by: Vighnesh Co-authored-by: Vyacheslav Egorov Co-authored-by: Andy Friesen --- Analysis/include/Luau/Subtyping.h | 18 +- Analysis/include/Luau/TypePack.h | 2 +- Analysis/include/Luau/TypePath.h | 6 +- Analysis/src/Autocomplete.cpp | 35 +- Analysis/src/AutocompleteCore.cpp | 14 +- Analysis/src/BuiltinTypeFunctions.cpp | 5 +- Analysis/src/ConstraintGenerator.cpp | 21 +- Analysis/src/ConstraintSolver.cpp | 46 +- Analysis/src/FragmentAutocomplete.cpp | 81 +- Analysis/src/Frontend.cpp | 73 +- Analysis/src/Module.cpp | 65 +- Analysis/src/NonStrictTypeChecker.cpp | 39 +- Analysis/src/OverloadResolution.cpp | 20 +- Analysis/src/Simplify.cpp | 45 +- Analysis/src/Subtyping.cpp | 589 ++++++------ Analysis/src/TypeChecker2.cpp | 117 +-- Analysis/src/TypePack.cpp | 6 +- Analysis/src/TypePath.cpp | 68 +- Analysis/src/Unifier2.cpp | 75 +- Ast/include/Luau/Cst.h | 1 + .../include/Luau/PrettyPrinter.h | 10 +- .../src/PrettyPrinter.cpp | 22 +- CLI/src/Analyze.cpp | 10 +- CLI/src/Reduce.cpp | 4 +- CodeGen/include/Luau/AssemblyBuilderX64.h | 4 +- CodeGen/src/AssemblyBuilderX64.cpp | 8 +- CodeGen/src/IrLoweringX64.cpp | 6 +- CodeGen/src/IrRegAllocA64.cpp | 1 - CodeGen/src/IrTranslateBuiltins.cpp | 4 +- Common/include/Luau/ScopedSeenSet.h | 29 + Sources.cmake | 6 +- VM/src/lbaselib.cpp | 46 +- VM/src/ldo.cpp | 4 +- fuzz/{transpiler.cpp => prettyprinter.cpp} | 4 +- fuzz/proto.cpp | 8 +- tests/AssemblyBuilderX64.test.cpp | 2 +- tests/Autocomplete.test.cpp | 6 - tests/Conformance.test.cpp | 33 +- tests/Fixture.cpp | 4 +- tests/FragmentAutocomplete.test.cpp | 5 - tests/IrLowering.test.cpp | 4 +- tests/NonStrictTypeChecker.test.cpp | 89 +- tests/NonstrictMode.test.cpp | 3 - tests/Normalize.test.cpp | 12 +- ...spiler.test.cpp => PrettyPrinter.test.cpp} | 910 +++++++++--------- tests/RuntimeLimits.test.cpp | 39 +- tests/Subtyping.test.cpp | 18 +- tests/TopoSort.test.cpp | 1 - tests/TypeFunction.test.cpp | 6 +- tests/TypeInfer.aliases.test.cpp | 4 +- tests/TypeInfer.annotations.test.cpp | 4 +- tests/TypeInfer.classes.test.cpp | 29 +- tests/TypeInfer.functions.test.cpp | 21 +- tests/TypeInfer.generics.test.cpp | 24 +- tests/TypeInfer.intersectionTypes.test.cpp | 10 +- tests/TypeInfer.modules.test.cpp | 5 +- tests/TypeInfer.provisional.test.cpp | 20 + tests/TypeInfer.refinements.test.cpp | 46 +- tests/TypeInfer.singletons.test.cpp | 2 + tests/TypeInfer.tables.test.cpp | 15 +- tests/TypeInfer.test.cpp | 46 +- tests/TypeInfer.typePacks.test.cpp | 7 - tests/TypeInfer.typestates.test.cpp | 2 +- tests/TypePath.test.cpp | 10 +- tests/Unifier2.test.cpp | 16 +- tests/conformance/native.luau | 7 + tests/conformance/vector_library.luau | 1 + 67 files changed, 1261 insertions(+), 1632 deletions(-) rename Analysis/include/Luau/Transpiler.h => Ast/include/Luau/PrettyPrinter.h (63%) rename Analysis/src/Transpiler.cpp => Ast/src/PrettyPrinter.cpp (98%) create mode 100644 Common/include/Luau/ScopedSeenSet.h rename fuzz/{transpiler.cpp => prettyprinter.cpp} (79%) rename tests/{Transpiler.test.cpp => PrettyPrinter.test.cpp} (60%) diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 6bfe5800..a7a4e61c 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -178,8 +178,9 @@ struct SubtypingEnvironment GenericBounds& getMappedTypeBounds(TypeId ty, NotNull iceReporter); // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance GenericBounds_DEPRECATED& getMappedTypeBounds_DEPRECATED(TypeId ty); - // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance + // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 TypePackId* getMappedPackBounds_DEPRECATED(TypePackId tp); + MappedGenericEnvironment::LookupResult lookupGenericPack(TypePackId tp) const; /* * When we encounter a generic over the course of a subtyping test, we need @@ -192,7 +193,7 @@ struct SubtypingEnvironment DenseHashMap mappedGenerics_DEPRECATED{nullptr}; MappedGenericEnvironment mappedGenericPacks; - // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance + // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 DenseHashMap mappedGenericPacks_DEPRECATED{nullptr}; /* @@ -452,8 +453,19 @@ struct Subtyping NotNull scope ); + // Markers to help overload selection. + struct Anything{}; + struct Nothing{}; + + SubtypingResult isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const VariadicTypePack* sub, TypePackId superTp, const VariadicTypePack* super); + SubtypingResult isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const GenericTypePack* sub, TypePackId superTp, const GenericTypePack* super); + SubtypingResult isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const VariadicTypePack* sub, TypePackId superTp, const GenericTypePack* super); + SubtypingResult isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const GenericTypePack* sub, TypePackId superTp, const VariadicTypePack* super); + SubtypingResult isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const GenericTypePack* sub, Nothing); + SubtypingResult isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, Nothing, TypePackId superTp, const GenericTypePack* super); + bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp); - // Clip with LuauSubtypingGenericPacksDoesntUseVariance + // Clip with LuauSubtypingGenericPacksDoesntUseVariance2 bool bindGeneric_DEPRECATED(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) const; template diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index 12c1e6cb..81e23cb6 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -231,7 +231,7 @@ bool isEmpty(TypePackId tp); /// Flattens out a type pack. Also returns a valid TypePackId tail if the type pack's full size is not known std::pair, std::optional> flatten(TypePackId tp); std::pair, std::optional> flatten(TypePackId tp, const TxnLog& log); -// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance +// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 std::pair, std::optional> flatten_DEPRECATED( TypePackId tp, const DenseHashMap& mappedGenericPacks diff --git a/Analysis/include/Luau/TypePath.h b/Analysis/include/Luau/TypePath.h index 1ffd613f..a9f6620f 100644 --- a/Analysis/include/Luau/TypePath.h +++ b/Analysis/include/Luau/TypePath.h @@ -243,13 +243,13 @@ std::string toStringHuman(const TypePath::Path& path); // To keep my head straight when clipping: // LuauReturnMappedGenericPacksFromSubtyping3 expects mappedGenericPacks AND arena -// LuauSubtypingGenericPacksDoesntUseVariance expects just arena. this is the final state +// LuauSubtypingGenericPacksDoesntUseVariance2 expects just arena. this is the final state // TODO: clip below two along with `LuauReturnMappedGenericPacksFromSubtyping3` std::optional traverse_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes); std::optional traverse_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes); std::optional traverse(TypePackId root, const Path& path, NotNull builtinTypes, NotNull arena); -// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance +// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 std::optional traverse_DEPRECATED( TypePackId root, const Path& path, @@ -258,7 +258,7 @@ std::optional traverse_DEPRECATED( NotNull arena ); std::optional traverse(TypeId root, const Path& path, NotNull builtinTypes, NotNull arena); -// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance +// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 std::optional traverse_DEPRECATED( TypeId root, const Path& path, diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 544f19ff..13d9c644 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -10,7 +10,6 @@ #include "AutocompleteCore.h" LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSuggestHotComments) namespace Luau { @@ -41,33 +40,17 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName globalScope = frontend.globalsForAutocomplete.globalScope.get(); TypeArena typeArena; - if (FFlag::LuauSuggestHotComments) - { - bool isInHotComment = isWithinHotComment(*sourceModule, position); - if (isWithinComment(*sourceModule, position) && !isInHotComment) - return {}; - - std::vector ancestry = findAncestryAtPositionForAutocomplete(*sourceModule, position); - LUAU_ASSERT(!ancestry.empty()); - ScopePtr startScope = findScopeAtPosition(*module, position); - - return autocomplete_( - module, builtinTypes, &typeArena, ancestry, globalScope, startScope, position, frontend.fileResolver, std::move(callback), isInHotComment - ); - } - else - { - if (isWithinComment(*sourceModule, position)) - return {}; + bool isInHotComment = isWithinHotComment(*sourceModule, position); + if (isWithinComment(*sourceModule, position) && !isInHotComment) + return {}; - std::vector ancestry = findAncestryAtPositionForAutocomplete(*sourceModule, position); - LUAU_ASSERT(!ancestry.empty()); - ScopePtr startScope = findScopeAtPosition(*module, position); + std::vector ancestry = findAncestryAtPositionForAutocomplete(*sourceModule, position); + LUAU_ASSERT(!ancestry.empty()); + ScopePtr startScope = findScopeAtPosition(*module, position); - return autocomplete_( - module, builtinTypes, &typeArena, ancestry, globalScope, startScope, position, frontend.fileResolver, std::move(callback) - ); - } + return autocomplete_( + module, builtinTypes, &typeArena, ancestry, globalScope, startScope, position, frontend.fileResolver, std::move(callback), isInHotComment + ); } } // namespace Luau diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index 3d8c4c4d..d5661ac8 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -27,7 +27,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) -LUAU_FASTFLAGVARIABLE(LuauSuggestHotComments) LUAU_FASTFLAG(LuauAutocompleteAttributes) static constexpr std::array kStatementStartingKeywords = @@ -1824,16 +1823,13 @@ AutocompleteResult autocomplete_( { LUAU_TIMETRACE_SCOPE("Luau::autocomplete_", "AutocompleteCore"); - if (FFlag::LuauSuggestHotComments) + if (isInHotComment) { - if (isInHotComment) - { - AutocompleteEntryMap result; + AutocompleteEntryMap result; - for (const std::string_view hc : kHotComments) - result.emplace(hc, AutocompleteEntry{AutocompleteEntryKind::HotComment}); - return {std::move(result), ancestry, AutocompleteContext::HotComment}; - } + for (const std::string_view hc : kHotComments) + result.emplace(hc, AutocompleteEntry{AutocompleteEntryKind::HotComment}); + return {std::move(result), ancestry, AutocompleteContext::HotComment}; } AstNode* node = ancestry.back(); diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index a356e018..1cfd5b00 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -20,7 +20,6 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_DYNAMIC_FASTINTVARIABLE(LuauStepRefineRecursionLimit, 64) -LUAU_FASTFLAGVARIABLE(LuauRefineOccursCheckDirectRecursion) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways) @@ -1184,7 +1183,7 @@ struct RefineTypeScrubber : public Substitution return true; } } - return FFlag::LuauRefineOccursCheckDirectRecursion ? ty == needle : false; + return ty == needle; } bool ignoreChildren(TypeId ty) override @@ -1226,7 +1225,7 @@ struct RefineTypeScrubber : public Substitution else return ctx->arena->addType(IntersectionType{newParts.take()}); } - else if (FFlag::LuauRefineOccursCheckDirectRecursion && ty == needle) + else if (ty == needle) return ctx->builtins->unknownType; else return ty; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index c1acb577..2e0727f6 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -37,10 +37,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauConstraintGeneratorRecursionLimit, 300) LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) -LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2) -LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) @@ -2080,13 +2077,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte } ); - if (FFlag::LuauLimitDynamicConstraintSolving3) - { - // If we don't emplace an error type here, then later we'll be - // exposing a blocked type in this file's type interface. This - // is _normally_ harmless. - emplaceType(asMutable(bindingIt->second.type), builtinTypes->errorType); - } + // If we don't emplace an error type here, then later we'll be + // exposing a blocked type in this file's type interface. This + // is _normally_ harmless. + emplaceType(asMutable(bindingIt->second.type), builtinTypes->errorType); return ControlFlow::None; } @@ -3333,10 +3327,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifEls applyRefinements(elseScope, ifElse->falseExpr->location, refinementArena.negation(refinement)); TypeId elseType = check(elseScope, ifElse->falseExpr, expectedType).ty; - if (FFlag::LuauInferActualIfElseExprType2) - return Inference{makeUnion(scope, ifElse->location, thenType, elseType)}; - else - return Inference{expectedType ? *expectedType : makeUnion(scope, ifElse->location, thenType, elseType)}; + return Inference{makeUnion(scope, ifElse->location, thenType, elseType)}; } Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion* typeAssert) @@ -3987,7 +3978,7 @@ TypeId ConstraintGenerator::resolveReferenceType( else return resolveType_(scope, ref->parameters.data[0].type, inTypeArguments); } - else if (FFlag::LuauLimitDynamicConstraintSolving3 && ref->name == "_luau_blocked_type") + else if (ref->name == "_luau_blocked_type") { return arena->addType(BlockedType{}); } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index e7ceefc4..20f165e7 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -14,6 +14,7 @@ #include "Luau/ModuleResolver.h" #include "Luau/OverloadResolution.h" #include "Luau/RecursionCounter.h" +#include "Luau/ScopedSeenSet.h" #include "Luau/Simplify.h" #include "Luau/TableLiteralInference.h" #include "Luau/TimeTrace.h" @@ -40,8 +41,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauTrackUniqueness) LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAGVARIABLE(LuauCollapseShouldNotCrash) -LUAU_FASTFLAGVARIABLE(LuauContainsAnyGenericFollowBeforeChecking) -LUAU_FASTFLAGVARIABLE(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAGVARIABLE(LuauDontDynamicallyCreateRedundantSubtypeConstraints) LUAU_FASTFLAGVARIABLE(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) @@ -57,28 +56,6 @@ LUAU_FASTFLAGVARIABLE(LuauAvoidOverloadSelectionForFunctionType) namespace Luau { -struct SeenScope -{ - Set& seen; - TypeId ty; - - SeenScope(Set& seen, TypeId ty) - : seen(seen) - , ty(ty) - { - seen.insert(ty); - } - - ~SeenScope() - { - seen.erase(ty); - } - - // Delete copy constructor and copy assignment operator to prevent copying - SeenScope(const SeenScope&) = delete; - SeenScope& operator=(const SeenScope&) = delete; -}; - bool SubtypeConstraintRecord::operator==(const SubtypeConstraintRecord& other) const { return (subTy == other.subTy) && (superTy == other.superTy) && (variance == other.variance); @@ -530,7 +507,7 @@ void ConstraintSolver::run() // If we were _given_ a limit, and the current limit has hit zero, ] // then early exit from constraint solving. - if (FFlag::LuauLimitDynamicConstraintSolving3 && FInt::LuauSolverConstraintLimit > 0 && solverConstraintLimit == 0) + if (FInt::LuauSolverConstraintLimit > 0 && solverConstraintLimit == 0) break; std::string saveMe = FFlag::DebugLuauLogSolver ? toString(*c, opts) : std::string{}; @@ -2756,10 +2733,7 @@ struct ContainsAnyGeneric final : public TypeOnceVisitor bool visit(TypePackId ty) override { - if (FFlag::LuauContainsAnyGenericFollowBeforeChecking) - found = found || is(follow(ty)); - else - found = found || is(ty); + found = found || is(follow(ty)); return !found; } @@ -3103,7 +3077,8 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( if (seen.contains(subjectType)) return {}; - std::optional ss; + std::optional, TypeId>> ss; // This won't be needed once LuauScopedSeenSetInLookupTableProp is clipped. + if (FFlag::LuauScopedSeenSetInLookupTableProp) ss.emplace(seen, subjectType); else @@ -3673,15 +3648,12 @@ NotNull ConstraintSolver::pushConstraint(NotNull scope, const solverConstraints.push_back(std::move(c)); unsolvedConstraints.emplace_back(borrow); - if (FFlag::LuauLimitDynamicConstraintSolving3) + if (solverConstraintLimit > 0) { - if (solverConstraintLimit > 0) - { - --solverConstraintLimit; + --solverConstraintLimit; - if (solverConstraintLimit == 0) - reportError(CodeTooComplex{}, location); - } + if (solverConstraintLimit == 0) + reportError(CodeTooComplex{}, location); } return borrow; diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index e67b8c8a..6da10071 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -35,8 +35,6 @@ LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauFragmentRequiresCanBeResolvedToAModule) LUAU_FASTFLAGVARIABLE(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAGVARIABLE(LuauForInProvidesRecommendations) -LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTakesInnermostRefinement) -LUAU_FASTFLAG(LuauSuggestHotComments) LUAU_FASTFLAGVARIABLE(LuauForInRangesConsiderInLocation) namespace Luau @@ -708,8 +706,7 @@ void cloneTypesFromFragment( // end // // We could find another binding for `syms` and then set _that_. - if (FFlag::LuauFragmentAutocompleteTakesInnermostRefinement) - break; + break; } } } @@ -1486,61 +1483,31 @@ FragmentAutocompleteStatusResult tryFragmentAutocomplete( StringCompletionCallback stringCompletionCB ) { - if (FFlag::LuauSuggestHotComments) + bool isInHotComment = isWithinHotComment(context.freshParse.hotcomments, cursorPosition); + if (isWithinComment(context.freshParse.commentLocations, cursorPosition) && !isInHotComment) + return {FragmentAutocompleteStatus::Success, std::nullopt}; + // TODO: we should calculate fragmentEnd position here, by using context.newAstRoot and cursorPosition + try { - bool isInHotComment = isWithinHotComment(context.freshParse.hotcomments, cursorPosition); - if (isWithinComment(context.freshParse.commentLocations, cursorPosition) && !isInHotComment) - return {FragmentAutocompleteStatus::Success, std::nullopt}; - // TODO: we should calculate fragmentEnd position here, by using context.newAstRoot and cursorPosition - try - { - Luau::FragmentAutocompleteResult fragmentAutocomplete = Luau::fragmentAutocomplete( - frontend, - context.newSrc, - moduleName, - cursorPosition, - context.opts, - std::move(stringCompletionCB), - context.DEPRECATED_fragmentEndPosition, - context.freshParse.root, - context.reporter, - isInHotComment - ); - return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)}; - } - catch (const Luau::InternalCompilerError& e) - { - if (FFlag::DebugLogFragmentsFromAutocomplete) - logLuau("tryFragmentAutocomplete exception", e.what()); - return {FragmentAutocompleteStatus::InternalIce, std::nullopt}; - } + Luau::FragmentAutocompleteResult fragmentAutocomplete = Luau::fragmentAutocomplete( + frontend, + context.newSrc, + moduleName, + cursorPosition, + context.opts, + std::move(stringCompletionCB), + context.DEPRECATED_fragmentEndPosition, + context.freshParse.root, + context.reporter, + isInHotComment + ); + return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)}; } - else + catch (const Luau::InternalCompilerError& e) { - 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 - { - Luau::FragmentAutocompleteResult fragmentAutocomplete = Luau::fragmentAutocomplete( - frontend, - context.newSrc, - moduleName, - cursorPosition, - context.opts, - std::move(stringCompletionCB), - context.DEPRECATED_fragmentEndPosition, - context.freshParse.root, - context.reporter - ); - return {FragmentAutocompleteStatus::Success, std::move(fragmentAutocomplete)}; - } - catch (const Luau::InternalCompilerError& e) - { - if (FFlag::DebugLogFragmentsFromAutocomplete) - logLuau("tryFragmentAutocomplete exception", e.what()); - return {FragmentAutocompleteStatus::InternalIce, std::nullopt}; - } + if (FFlag::DebugLogFragmentsFromAutocomplete) + logLuau("tryFragmentAutocomplete exception", e.what()); + return {FragmentAutocompleteStatus::InternalIce, std::nullopt}; } } @@ -1579,7 +1546,7 @@ FragmentAutocompleteResult fragmentAutocomplete( cursorPosition, frontend.fileResolver, std::move(callback), - FFlag::LuauSuggestHotComments && isInHotComment + isInHotComment ); freeze(tcResult.incrementalModule->internalTypes); reportWaypoint(reporter, FragmentAutocompleteWaypoint::AutocompleteEnd); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 20d9a485..c2f273a2 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -42,7 +42,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) -LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) @@ -1873,37 +1872,34 @@ ModulePtr check( // NOTE: This used to be done prior to cloning the public interface, but // we now replace "internal" types with `*error-type*`. - if (FFlag::LuauLimitDynamicConstraintSolving3) + if (FFlag::DebugLuauForbidInternalTypes) { - if (FFlag::DebugLuauForbidInternalTypes) - { - InternalTypeFinder finder; + InternalTypeFinder finder; - // `result->returnType` is not filled in yet, so we - // traverse the return type of the root module. - finder.traverse(module->getModuleScope()->returnType); + // `result->returnType` is not filled in yet, so we + // traverse the return type of the root module. + finder.traverse(module->getModuleScope()->returnType); - for (const auto& [_, binding] : module->exportedTypeBindings) - finder.traverse(binding.type); + for (const auto& [_, binding] : module->exportedTypeBindings) + finder.traverse(binding.type); - for (const auto& [_, ty] : module->astTypes) - finder.traverse(ty); + for (const auto& [_, ty] : module->astTypes) + finder.traverse(ty); - for (const auto& [_, ty] : module->astExpectedTypes) - finder.traverse(ty); + for (const auto& [_, ty] : module->astExpectedTypes) + finder.traverse(ty); - for (const auto& [_, tp] : module->astTypePacks) - finder.traverse(tp); + for (const auto& [_, tp] : module->astTypePacks) + finder.traverse(tp); - for (const auto& [_, ty] : module->astResolvedTypes) - finder.traverse(ty); + for (const auto& [_, ty] : module->astResolvedTypes) + finder.traverse(ty); - for (const auto& [_, ty] : module->astOverloadResolvedTypes) - finder.traverse(ty); + for (const auto& [_, ty] : module->astOverloadResolvedTypes) + finder.traverse(ty); - for (const auto& [_, tp] : module->astResolvedTypePacks) - finder.traverse(tp); - } + for (const auto& [_, tp] : module->astResolvedTypePacks) + finder.traverse(tp); } @@ -1913,37 +1909,6 @@ ModulePtr check( else module->clonePublicInterface_DEPRECATED(builtinTypes, *iceHandler); - if (!FFlag::LuauLimitDynamicConstraintSolving3) - { - if (FFlag::DebugLuauForbidInternalTypes) - { - InternalTypeFinder finder; - - finder.traverse(module->returnType); - - for (const auto& [_, binding] : module->exportedTypeBindings) - finder.traverse(binding.type); - - for (const auto& [_, ty] : module->astTypes) - finder.traverse(ty); - - for (const auto& [_, ty] : module->astExpectedTypes) - finder.traverse(ty); - - for (const auto& [_, tp] : module->astTypePacks) - finder.traverse(tp); - - for (const auto& [_, ty] : module->astResolvedTypes) - finder.traverse(ty); - - for (const auto& [_, ty] : module->astOverloadResolvedTypes) - finder.traverse(ty); - - for (const auto& [_, tp] : module->astResolvedTypePacks) - finder.traverse(tp); - } - } - // It would be nice if we could freeze the arenas before doing type // checking, but we'll have to do some work to get there. // diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 3638f997..01fa02a4 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -16,9 +16,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) -LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAGVARIABLE(LuauEmplaceNotPushBack) -LUAU_FASTFLAG(LuauSuggestHotComments) namespace Luau { @@ -203,41 +201,20 @@ struct ClonePublicInterface : Substitution if (isNewSolver()) { ttv->scope = nullptr; - if (FFlag::LuauLimitDynamicConstraintSolving3) - ttv->state = TableState::Sealed; + ttv->state = TableState::Sealed; } } if (isNewSolver()) { - if (FFlag::LuauLimitDynamicConstraintSolving3) + if (is(ty)) { - if (is(ty)) - { - internalTypeEscaped = true; - result = builtinTypes->errorType; - } - else if (auto genericty = getMutable(result)) - { - genericty->scope = nullptr; - } + internalTypeEscaped = true; + result = builtinTypes->errorType; } - else + else if (auto genericty = getMutable(result)) { - if (auto freety = getMutable(result)) - { - module->errors.emplace_back( - freety->scope->location, - module->name, - InternalError{"Free type is escaping its module; please report this bug at " - "https://github.com/luau-lang/luau/issues"} - ); - result = builtinTypes->errorType; - } - else if (auto genericty = getMutable(result)) - { - genericty->scope = nullptr; - } + genericty->scope = nullptr; } } @@ -248,32 +225,14 @@ struct ClonePublicInterface : Substitution { if (isNewSolver()) { - if (FFlag::LuauLimitDynamicConstraintSolving3) + if (is(tp)) { - if (is(tp)) - { - internalTypeEscaped = true; - return builtinTypes->errorTypePack; - } - - auto clonedTp = clone(tp); - if (auto gtp = getMutable(clonedTp)) - gtp->scope = nullptr; - return clonedTp; + internalTypeEscaped = true; + return builtinTypes->errorTypePack; } auto clonedTp = clone(tp); - if (auto ftp = getMutable(clonedTp)) - { - module->errors.emplace_back( - ftp->scope->location, - module->name, - InternalError{"Free type pack is escaping its module; please report this bug at " - "https://github.com/luau-lang/luau/issues"} - ); - clonedTp = builtinTypes->errorTypePack; - } - else if (auto gtp = getMutable(clonedTp)) + if (auto gtp = getMutable(clonedTp)) gtp->scope = nullptr; return clonedTp; } @@ -393,7 +352,7 @@ void Module::clonePublicInterface_DEPRECATED(NotNull builtinTypes, *tf = clonePublicInterface.cloneTypeFun(*tf); } - if (FFlag::LuauLimitDynamicConstraintSolving3 && clonePublicInterface.internalTypeEscaped) + if (clonePublicInterface.internalTypeEscaped) { errors.emplace_back( Location{}, // Not amazing but the best we can do. @@ -444,7 +403,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr *tf = clonePublicInterface.cloneTypeFun(*tf); } - if (FFlag::LuauLimitDynamicConstraintSolving3 && clonePublicInterface.internalTypeEscaped) + if (clonePublicInterface.internalTypeEscaped) { errors.emplace_back( Location{}, // Not amazing but the best we can do. diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 66c0a5d6..95bfecab 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -20,8 +20,6 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAGVARIABLE(LuauNewNonStrictMoreUnknownSymbols) -LUAU_FASTFLAGVARIABLE(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressesDynamicRequireErrors) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauUnreducedTypeFunctionsDontTriggerWarnings) @@ -350,24 +348,11 @@ struct NonStrictTypeChecker NonStrictContext condB = visit(ifStatement->condition, ValueContext::RValue); NonStrictContext branchContext; - if (FFlag::LuauNewNonStrictMoreUnknownSymbols) + NonStrictContext thenBody = visit(ifStatement->thenbody); + if (ifStatement->elsebody) { - NonStrictContext thenBody = visit(ifStatement->thenbody); - if (ifStatement->elsebody) - { - NonStrictContext elseBody = visit(ifStatement->elsebody); - branchContext = NonStrictContext::conjunction(builtinTypes, arena, thenBody, elseBody); - } - } - else - { - // If there is no else branch, don't bother generating warnings for the then branch - we can't prove there is an error - if (ifStatement->elsebody) - { - NonStrictContext thenBody = visit(ifStatement->thenbody); - NonStrictContext elseBody = visit(ifStatement->elsebody); - branchContext = NonStrictContext::conjunction(builtinTypes, arena, thenBody, elseBody); - } + NonStrictContext elseBody = visit(ifStatement->elsebody); + branchContext = NonStrictContext::conjunction(builtinTypes, arena, thenBody, elseBody); } return NonStrictContext::disjunction(builtinTypes, arena, condB, branchContext); @@ -624,12 +609,9 @@ struct NonStrictTypeChecker NonStrictContext visit(AstExprCall* call) { - if (FFlag::LuauNewNonStrictMoreUnknownSymbols) - { - visit(call->func, ValueContext::RValue); - for (auto arg : call->args) - visit(arg, ValueContext::RValue); - } + visit(call->func, ValueContext::RValue); + for (auto arg : call->args) + visit(arg, ValueContext::RValue); NonStrictContext fresh{}; TypeId* originalCallTy = module->astOriginalCallTypes.find(call->func); @@ -722,12 +704,7 @@ struct NonStrictTypeChecker reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); else { - if (FFlag::LuauNewNonStrictNoErrorsPassingNever) - { - if (!get(follow(*runTimeFailureType))) - reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); - } - else + if (!get(follow(*runTimeFailureType))) reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); } } diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 05987ef2..27a2f471 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -16,7 +16,7 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAGVARIABLE(LuauFilterOverloadsByArity) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) @@ -483,7 +483,7 @@ std::pair OverloadResolver::checkOverload_ if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { const bool subPathArgTail = matchesPrefix(Path({TypePath::PackField::Arguments, TypePath::PackField::Tail}), reason.subPath); const bool superPathArgs = matchesPrefix(Path(TypePath::PackField::Arguments), reason.superPath); @@ -591,20 +591,20 @@ std::pair OverloadResolver::checkOverload_ : argExprs->size() != 0 ? argExprs->back()->location : fnExpr->location; - // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance and + // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance2 and // LuauReturnMappedGenericPacksFromSubtyping3 std::optional failedSubTy; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, arena); else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) failedSubTy = traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); else failedSubTy = traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes); - // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance and + // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance2 and // LuauReturnMappedGenericPacksFromSubtyping3 std::optional failedSuperTy; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena); else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) failedSuperTy = traverseForType_DEPRECATED( @@ -660,11 +660,11 @@ std::pair OverloadResolver::checkOverload_ argLocation = fnExpr->location; } std::optional failedSubTy = - FFlag::LuauSubtypingGenericPacksDoesntUseVariance + FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 ? traverseForType(fnTy, reason.subPath, builtinTypes, arena) : traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); std::optional failedSuperTy = - FFlag::LuauSubtypingGenericPacksDoesntUseVariance + FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 ? traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena) : traverseForType_DEPRECATED( prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena @@ -674,7 +674,7 @@ std::pair OverloadResolver::checkOverload_ } std::optional failedSubPack; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes, arena); else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) failedSubPack = traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); @@ -682,7 +682,7 @@ std::pair OverloadResolver::checkOverload_ failedSubPack = traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes); std::optional failedSuperPack; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, arena); else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) failedSuperPack = diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 5c6cd78b..64c43ed4 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -23,7 +23,6 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSimplificationIterationLimit, 128) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauPushTypeConstraint2) -LUAU_FASTFLAGVARIABLE(LuauMorePreciseExternTableRelation) LUAU_FASTFLAGVARIABLE(LuauSimplifyRefinementOfReadOnlyProperty) LUAU_FASTFLAGVARIABLE(LuauExternTableIndexersIntersect) LUAU_FASTFLAGVARIABLE(LuauSimplifyMoveTableProps) @@ -648,31 +647,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) } if (auto re = get(right)) - { - if (FFlag::LuauMorePreciseExternTableRelation) - return relateTableToExternType(lt, re, seen); - - Relation overall = Relation::Coincident; - - for (auto& [name, prop] : lt->props) - { - if (auto propInExternType = re->props.find(name); propInExternType != re->props.end()) - { - LUAU_ASSERT(prop.readTy && propInExternType->second.readTy); - Relation propRel = relate(*prop.readTy, *propInExternType->second.readTy, seen); - - if (propRel == Relation::Disjoint) - return Relation::Disjoint; - - if (propRel == Relation::Coincident) - continue; - - overall = Relation::Intersects; - } - } - - return overall; - } + return relateTableToExternType(lt, re, seen); // TODO metatables @@ -692,22 +667,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) return Relation::Disjoint; } - if (FFlag::LuauMorePreciseExternTableRelation) - { - if (auto tbl = get(right)) - return flip(relateTableToExternType(tbl, ct, seen)); - } - else - { - if (is(right)) - { - // FIXME: This could be better in that we can say a table only - // intersects with an extern type if they share a property, but - // for now it is within the contract of the function to claim - // the two intersect. - return Relation::Intersects; - } - } + if (auto tbl = get(right)) + return flip(relateTableToExternType(tbl, ct, seen)); return Relation::Disjoint; } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 8d45170d..dceb8210 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -8,6 +8,7 @@ #include "Luau/Normalize.h" #include "Luau/RecursionCounter.h" #include "Luau/Scope.h" +#include "Luau/ScopedSeenSet.h" #include "Luau/Substitution.h" #include "Luau/TxnLog.h" #include "Luau/Type.h" @@ -24,12 +25,11 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauSubtypingRecursionLimit, 100) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAGVARIABLE(LuauTrackUniqueness) -LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAGVARIABLE(LuauSubtypingUnionsAndIntersectionsInGenericBounds) LUAU_FASTFLAGVARIABLE(LuauIndexInMetatableSubtyping) LUAU_FASTFLAGVARIABLE(LuauSubtypingPackRecursionLimits) @@ -82,12 +82,12 @@ MappedGenericEnvironment::MappedGenericFrame::MappedGenericFrame( : mappings(std::move(mappings)) , parentScopeIndex(parentScopeIndex) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); } MappedGenericEnvironment::LookupResult MappedGenericEnvironment::lookupGenericPack(TypePackId genericTp) const { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); genericTp = follow(genericTp); @@ -136,7 +136,7 @@ MappedGenericEnvironment::LookupResult MappedGenericEnvironment::lookupGenericPa void MappedGenericEnvironment::pushFrame(const std::vector& genericTps) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); DenseHashMap> mappings{nullptr}; @@ -155,7 +155,7 @@ void MappedGenericEnvironment::pushFrame(const std::vector& genericT void MappedGenericEnvironment::popFrame() { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); LUAU_ASSERT(currentScopeIndex); if (currentScopeIndex) { @@ -166,7 +166,7 @@ void MappedGenericEnvironment::popFrame() bool MappedGenericEnvironment::bindGeneric(TypePackId genericTp, TypePackId bindeeTp) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); // We shouldn't bind generic type packs to themselves if (genericTp == bindeeTp) return true; @@ -213,7 +213,7 @@ static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& for (const SubtypingReasoning& reasoning : result.reasoning) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes, arena)); LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes, arena)); @@ -561,18 +561,24 @@ struct ApplyMappedGenerics : Substitution TypePackId clean(TypePackId tp) override { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { - const MappedGenericEnvironment::LookupResult result = env.mappedGenericPacks.lookupGenericPack(tp); + const MappedGenericEnvironment::LookupResult result = env.lookupGenericPack(tp); if (const TypePackId* mappedGen = get_if(&result)) return *mappedGen; + // Clean is only called when isDirty found a pack bound + LUAU_ASSERT(!"Unreachable"); + return builtinTypes->anyTypePack; } - else if (auto it = env.getMappedPackBounds_DEPRECATED(tp)) - return *it; + else + { + if (auto it = env.getMappedPackBounds_DEPRECATED(tp)) + return *it; - // Clean is only called when isDirty found a pack bound - LUAU_ASSERT(!"Unreachable"); - return nullptr; + // Clean is only called when isDirty found a pack bound + LUAU_ASSERT(!"Unreachable"); + return nullptr; + } } bool ignoreChildren(TypeId ty) override @@ -673,7 +679,7 @@ bool SubtypingEnvironment::containsMappedType(TypeId ty) const bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { if (const MappedGenericEnvironment::LookupResult lookupResult = mappedGenericPacks.lookupGenericPack(tp); get_if(&lookupResult)) return true; @@ -717,7 +723,7 @@ SubtypingEnvironment::GenericBounds_DEPRECATED& SubtypingEnvironment::getMappedT TypePackId* SubtypingEnvironment::getMappedPackBounds_DEPRECATED(TypePackId tp) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); if (auto it = mappedGenericPacks_DEPRECATED.find(tp)) return it; @@ -729,6 +735,18 @@ TypePackId* SubtypingEnvironment::getMappedPackBounds_DEPRECATED(TypePackId tp) return nullptr; } +MappedGenericEnvironment::LookupResult SubtypingEnvironment::lookupGenericPack(TypePackId tp) const +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); + MappedGenericEnvironment::LookupResult result = mappedGenericPacks.lookupGenericPack(tp); + if (get_if(&result)) + return result; + else if (parent) + return parent->lookupGenericPack(tp); + else + return result; +} + Subtyping::Subtyping( NotNull builtinTypes, NotNull typeArena, @@ -819,7 +837,7 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull p{subTy, superTy}; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) result.mappedGenericPacks_DEPRECATED = env.mappedGenericPacks_DEPRECATED; if (result.isCacheable) @@ -941,50 +959,6 @@ SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult resu return result; } -namespace -{ -struct SeenSetPopper -{ - Subtyping::SeenSet* seenTypes; - std::pair pair; - - SeenSetPopper(Subtyping::SeenSet* seenTypes, std::pair pair) - : seenTypes(seenTypes) - , pair(pair) - { - } - - ~SeenSetPopper() - { - seenTypes->erase(pair); - } -}; - -struct SeenTypePackSetPopper -{ - Subtyping::SeenTypePackSet* seenTypes; - std::pair pair; - - SeenTypePackSetPopper(Subtyping::SeenTypePackSet* seenTypes, std::pair pair) - : seenTypes(seenTypes) - , pair(std::move(pair)) - { - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); - } - - SeenTypePackSetPopper(const SeenTypePackSetPopper&) = delete; - SeenTypePackSetPopper& operator=(const SeenTypePackSetPopper&) = delete; - SeenTypePackSetPopper(SeenTypePackSetPopper&&) = delete; - SeenTypePackSetPopper& operator=(SeenTypePackSetPopper&&) = delete; - - ~SeenTypePackSetPopper() - { - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); - seenTypes->erase(pair); - } -}; -} // namespace - SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull scope) { UnifierCounters& counters = normalizer->sharedState->counters; @@ -1012,7 +986,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub const SubtypingResult* cachedResult = resultCache.find({subTy, superTy}); if (cachedResult) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks_DEPRECATED) env.mappedGenericPacks_DEPRECATED.try_insert(genericTp, boundTp); @@ -1024,7 +998,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub cachedResult = env.tryFindSubtypingResult({subTy, superTy}); if (cachedResult) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks_DEPRECATED) env.mappedGenericPacks_DEPRECATED.try_insert(genericTp, boundTp); @@ -1069,8 +1043,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub return res; } - - SeenSetPopper ssp{&seenTypes, typePair}; + ScopedSeenSet ssp{seenTypes, typePair}; // Within the scope to which a generic belongs, that generic should be // tested as though it were its upper bounds. We do not yet support bounded @@ -1249,37 +1222,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub { result = isCovariantWith(env, subNegation, superTy, scope); if (!result.isSubtype && !result.normalizationTooComplex) - { - if (FFlag::LuauSubtypingNegationsChecksNormalizationComplexity) - result = trySemanticSubtyping(env, subTy, superTy, scope, result); - else - { - SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); - if (semantic.isSubtype) - { - semantic.reasoning.clear(); - result = semantic; - } - } - } + result = trySemanticSubtyping(env, subTy, superTy, scope, result); } else if (auto superNegation = get(superTy)) { result = isCovariantWith(env, subTy, superNegation, scope); if (!result.isSubtype && !result.normalizationTooComplex) - { - if (FFlag::LuauSubtypingNegationsChecksNormalizationComplexity) - result = trySemanticSubtyping(env, subTy, superTy, scope, result); - else - { - SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); - if (semantic.isSubtype) - { - semantic.reasoning.clear(); - result = semantic; - } - } - } + result = trySemanticSubtyping(env, subTy, superTy, scope, result); } else if (auto subTypeFunctionInstance = get(subTy)) { @@ -1378,13 +1327,13 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp = follow(subTp); superTp = follow(superTp); - std::optional popper = std::nullopt; + std::optional>> popper; if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) { std::pair typePair = {subTp, superTp}; if (!seenPacks.insert(typePair)) return SubtypingResult{true, false, false}; - popper.emplace(&seenPacks, std::move(typePair)); + popper.emplace(seenPacks, std::move(typePair)); } auto [subHead, subTail] = flatten(subTp); @@ -1439,155 +1388,19 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId { if (auto p = get2(*subTail, *superTail)) { - // Variadic component is added by the isCovariantWith - // implementation; no need to add it here. - results.push_back(isCovariantWith(env, p, scope).withBothComponent(TypePath::PackField::Tail)); + results.push_back(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second)); } - else if (get2(*subTail, *superTail)) + else if (auto p = get2(*subTail, *superTail)) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) - { - MappedGenericEnvironment::LookupResult subLookupResult = env.mappedGenericPacks.lookupGenericPack(*subTail); - MappedGenericEnvironment::LookupResult superLookupResult = env.mappedGenericPacks.lookupGenericPack(*superTail); - - // match (subLookup, superLookupResult) { - // (TypePackId, _) => do covariant test - // (Unmapped, _) => bind the generic - // (_, TypePackId) => do covariant test - // (_, Unmapped) => bind the generic - // (_, _) => subtyping succeeds if the two generics are pointer-identical - // } - if (const TypePackId* currMapping = get_if(&subLookupResult)) - { - results.push_back(isCovariantWith(env, *currMapping, *superTail, scope) - .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})) - .withSuperComponent(TypePath::PackField::Tail)); - } - else if (get_if(&subLookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(*subTail, *superTail); - results.push_back( - SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail) - ); - } - else if (const TypePackId* currMapping = get_if(&superLookupResult)) - { - results.push_back(isCovariantWith(env, *subTail, *currMapping, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))); - } - else if (get_if(&superLookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(*superTail, *subTail); - results.push_back( - SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail) - ); - } - else - { - // Sometimes, we compare generic packs inside the functions which are quantifying them. They're not bindable, but should still - // subtype against themselves. - results.push_back( - SubtypingResult{*subTail == *superTail, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( - TypePath::PackField::Tail - ) - ); - } - } - else - { - bool ok = bindGeneric_DEPRECATED(env, *subTail, *superTail); - results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail)); - } + results.push_back(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second)); } - else if (get2(*subTail, *superTail)) + else if (auto p = get2(*subTail, *superTail)) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) - { - MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(*superTail); - if (const TypePackId* currMapping = get_if(&lookupResult)) - { - results.push_back(isCovariantWith(env, *subTail, *currMapping, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))); - } - else if (get_if(&lookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(*superTail, *subTail); - results.push_back( - SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail) - ); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - results.push_back( - SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( - TypePath::PackField::Tail - ) - ); - } - } - else if (variance == Variance::Contravariant) - { - // (A...) -> number <: (...number) -> number - bool ok = bindGeneric_DEPRECATED(env, *subTail, *superTail); - - results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail)); - } - else - { - // (number) -> ...number (number) -> A... - results.push_back(SubtypingResult{false}.withBothComponent(TypePath::PackField::Tail)); - } + results.push_back(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second)); } else if (auto p = get2(*subTail, *superTail)) { - if (TypeId t = follow(p.second->ty); get(t) || get(t)) - { - // Extra magic rule: - // T... <: ...any - // T... <: ...unknown - // - // See https://github.com/luau-lang/luau/issues/767 - } - else if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) - { - MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(*subTail); - if (const TypePackId* currMapping = get_if(&lookupResult)) - { - results.push_back(isCovariantWith(env, *currMapping, *superTail, scope) - .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})) - .withSuperComponent(TypePath::PackField::Tail)); - } - else if (get_if(&lookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(*subTail, *superTail); - results.push_back( - SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail) - ); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - results.push_back( - SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( - TypePath::PackField::Tail - ) - ); - } - } - else if (variance == Variance::Contravariant) - { - // (...number) -> number (A...) -> number - results.push_back(SubtypingResult{false}.withBothComponent(TypePath::PackField::Tail)); - } - else - { - // () -> A... <: () -> ...number - bool ok = bindGeneric_DEPRECATED(env, *subTail, *superTail); - results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail)); - } + results.push_back(isTailCovariantWithTail(env, scope, *subTail, p.first, *superTail, p.second)); } else if (get(*subTail) || get(*superTail)) // error type is fine on either side @@ -1604,36 +1417,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId { return SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail); } - else if (get(*subTail)) + else if (auto g = get(*subTail)) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) - { - MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(*subTail); - if (const TypePackId* currMapping = get_if(&lookupResult)) - results.push_back(isCovariantWith(env, *currMapping, builtinTypes->emptyTypePack, scope) - .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))); - else if (get_if(&lookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(*subTail, builtinTypes->emptyTypePack); - results.push_back( - SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent(TypePath::PackField::Tail) - ); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - results.push_back( - SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent( - TypePath::PackField::Tail - ) - ); - } - } - else - { - bool ok = bindGeneric_DEPRECATED(env, *subTail, builtinTypes->emptyTypePack); - return SubtypingResult{ok}.withSubComponent(TypePath::PackField::Tail); - } + return isTailCovariantWithTail(env, scope, *subTail, g, Nothing{}); } else return SubtypingResult{false} @@ -1654,40 +1440,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId * All variadic type packs are therefore supertypes of the empty type pack. */ } - else if (get(*superTail)) + else if (auto g = get(*superTail)) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) - { - MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(*superTail); - if (const TypePackId* currMapping = get_if(&lookupResult)) - results.push_back(isCovariantWith(env, builtinTypes->emptyTypePack, *currMapping, scope) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}}))); - else if (get_if(&lookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(*superTail, builtinTypes->emptyTypePack); - results.push_back( - SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent( - TypePath::PackField::Tail - ) - ); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - results.push_back( - SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent( - TypePath::PackField::Tail - ) - ); - } - } - else if (variance == Variance::Contravariant) - { - bool ok = bindGeneric_DEPRECATED(env, builtinTypes->emptyTypePack, *superTail); - results.push_back(SubtypingResult{ok}.withSuperComponent(TypePath::PackField::Tail)); - } - else - results.push_back(SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail)); + results.push_back(isTailCovariantWithTail(env, scope, Nothing{}, *superTail, g)); } else return SubtypingResult{false} @@ -1742,9 +1497,9 @@ std::optional Subtyping::isSubTailCovariantWith( } else if (get(subTail)) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { - MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(subTail); + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTail); SubtypingResult result; if (get_if(&lookupResult)) result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} @@ -1876,9 +1631,9 @@ std::optional Subtyping::isCovariantWithSuperTail( } else if (get(superTail)) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { - MappedGenericEnvironment::LookupResult lookupResult = env.mappedGenericPacks.lookupGenericPack(superTail); + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTail); SubtypingResult result; if (get_if(&lookupResult)) result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} @@ -1986,6 +1741,224 @@ std::optional Subtyping::isCovariantWithSuperTail( .withError({scope->location, UnexpectedTypePackInSubtyping{superTail}}); } +SubtypingResult Subtyping::isTailCovariantWithTail( + SubtypingEnvironment& env, + NotNull scope, + TypePackId subTp, + const VariadicTypePack* sub, + TypePackId superTp, + const VariadicTypePack* super +) +{ + // A variadic type pack is a subtype of another variadic type pack if their + // respective element types have the same subtyping relationship. + return isCovariantWith(env, sub->ty, super->ty, scope) + .withBothComponent(TypePath::TypeField::Variadic) + .withBothComponent(TypePath::PackField::Tail); +} + +SubtypingResult Subtyping::isTailCovariantWithTail( + SubtypingEnvironment& env, + NotNull scope, + TypePackId subTp, + const GenericTypePack* sub, + TypePackId superTp, + const GenericTypePack* super +) +{ + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + { + MappedGenericEnvironment::LookupResult subLookupResult = env.lookupGenericPack(subTp); + MappedGenericEnvironment::LookupResult superLookupResult = env.lookupGenericPack(superTp); + + // match (subLookup, superLookupResult) { + // (TypePackId, _) => do covariant test + // (Unmapped, _) => bind the generic + // (_, TypePackId) => do covariant test + // (_, Unmapped) => bind the generic + // (_, _) => subtyping succeeds if the two generics are pointer-identical + // } + if (const TypePackId* currMapping = get_if(&subLookupResult)) + { + return isCovariantWith(env, *currMapping, superTp, scope) + .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})) + .withSuperComponent(TypePath::PackField::Tail); + } + else if (get_if(&subLookupResult)) + { + bool ok = env.mappedGenericPacks.bindGeneric(subTp, superTp); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); + } + else if (const TypePackId* currMapping = get_if(&superLookupResult)) + { + return isCovariantWith(env, subTp, *currMapping, scope) + .withSubComponent(TypePath::PackField::Tail) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); + } + else if (get_if(&superLookupResult)) + { + bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); + } + else + { + // Sometimes, we compare generic packs inside the functions which are quantifying them. They're not bindable, but should still + // subtype against themselves. + return SubtypingResult{subTp == superTp, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( + TypePath::PackField::Tail + ); + } + } + else + { + bool ok = bindGeneric_DEPRECATED(env, subTp, superTp); + return SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail); + } +} + +SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const VariadicTypePack* sub, TypePackId superTp, const GenericTypePack* super) +{ + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + { + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp); + if (const TypePackId* currMapping = get_if(&lookupResult)) + { + return isCovariantWith(env, subTp, *currMapping, scope) + .withSubComponent(TypePath::PackField::Tail) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); + } + else if (get_if(&lookupResult)) + { + bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); + } + else + { + LUAU_ASSERT(get_if(&lookupResult)); + return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( + TypePath::PackField::Tail + ); + } + } + else if (variance == Variance::Contravariant) + { + // (A...) -> number <: (...number) -> number + bool ok = bindGeneric_DEPRECATED(env, subTp, superTp); + + return SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail); + } + else + { + // (number) -> ...number (number) -> A... + return SubtypingResult{false}.withBothComponent(TypePath::PackField::Tail); + } +} + +SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const GenericTypePack* sub, TypePackId superTp, const VariadicTypePack* super) +{ + if (TypeId t = follow(super->ty); get(t) || get(t)) + { + // Extra magic rule: + // T... <: ...any + // T... <: ...unknown + // + // See https://github.com/luau-lang/luau/issues/767 + return SubtypingResult{true}; + } + else if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + { + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp); + if (const TypePackId* currMapping = get_if(&lookupResult)) + { + return isCovariantWith(env, *currMapping, superTp, scope) + .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})) + .withSuperComponent(TypePath::PackField::Tail); + } + else if (get_if(&lookupResult)) + { + bool ok = env.mappedGenericPacks.bindGeneric(subTp, superTp); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); + } + else + { + LUAU_ASSERT(get_if(&lookupResult)); + return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( + TypePath::PackField::Tail + ); + } + } + else if (variance == Variance::Contravariant) + { + // (...number) -> number (A...) -> number + return SubtypingResult{false}.withBothComponent(TypePath::PackField::Tail); + } + else + { + // () -> A... <: () -> ...number + bool ok = bindGeneric_DEPRECATED(env, subTp, superTp); + return SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail); + } +} + +SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const GenericTypePack* sub, Nothing) +{ + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + { + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp); + if (const TypePackId* currMapping = get_if(&lookupResult)) + return isCovariantWith(env, *currMapping, builtinTypes->emptyTypePack, scope) + .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); + else if (get_if(&lookupResult)) + { + bool ok = env.mappedGenericPacks.bindGeneric(subTp, builtinTypes->emptyTypePack); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent(TypePath::PackField::Tail); + } + else + { + LUAU_ASSERT(get_if(&lookupResult)); + return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent( + TypePath::PackField::Tail + ); + } + } + else + { + bool ok = bindGeneric_DEPRECATED(env, subTp, builtinTypes->emptyTypePack); + return SubtypingResult{ok}.withSubComponent(TypePath::PackField::Tail); + } +} + +SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, Nothing, TypePackId superTp, const GenericTypePack* super) +{ + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + { + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp); + if (const TypePackId* currMapping = get_if(&lookupResult)) + return isCovariantWith(env, builtinTypes->emptyTypePack, *currMapping, scope) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); + else if (get_if(&lookupResult)) + { + bool ok = env.mappedGenericPacks.bindGeneric(superTp, builtinTypes->emptyTypePack); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent( + TypePath::PackField::Tail + ); + } + else + { + LUAU_ASSERT(get_if(&lookupResult)); + return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent(TypePath::PackField::Tail); + } + } + else if (variance == Variance::Contravariant) + { + bool ok = bindGeneric_DEPRECATED(env, builtinTypes->emptyTypePack, superTp); + return SubtypingResult{ok}.withSuperComponent(TypePath::PackField::Tail); + } + else + return SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail); +} + + template SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope) { @@ -2643,7 +2616,7 @@ SubtypingResult Subtyping::isCovariantWith( } } - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance && !subFunction->genericPacks.empty()) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 && !subFunction->genericPacks.empty()) { std::vector packs; packs.reserve(subFunction->genericPacks.size()); @@ -2711,7 +2684,7 @@ SubtypingResult Subtyping::isCovariantWith( } } - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance && !subFunction->genericPacks.empty()) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 && !subFunction->genericPacks.empty()) { env.mappedGenericPacks.popFrame(); // This result isn't cacheable, because we may need it to populate the generic pack mapping environment again later @@ -3140,7 +3113,7 @@ SubtypingResult Subtyping::isCovariantWith( */ bool Subtyping::bindGeneric_DEPRECATED(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) const { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); if (variance == Variance::Contravariant) std::swap(superTp, subTp); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index aac95f44..1816722a 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -34,17 +34,14 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauTrackUniqueness) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAGVARIABLE(LuauIceLess) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) -LUAU_FASTFLAGVARIABLE(LuauAllowMixedTables) LUAU_FASTFLAGVARIABLE(LuauSimplifyIntersectionForLiteralSubtypeCheck) -LUAU_FASTFLAGVARIABLE(LuauRemoveGenericErrorForParams) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAGVARIABLE(LuauAddErrorCaseForIncompatibleTypePacks) LUAU_FASTFLAGVARIABLE(LuauAddConditionalContextForTernary) @@ -743,10 +740,7 @@ void TypeChecker2::visit(AstStatReturn* ret) { if (idx < head.size()) { - if (FFlag::LuauInferActualIfElseExprType2) - isSubtype &= testLiteralOrAstTypeIsSubtype(ret->list.data[idx], head[idx]); - else - isSubtype &= testPotentialLiteralIsSubtype(ret->list.data[idx], head[idx]); + isSubtype &= testLiteralOrAstTypeIsSubtype(ret->list.data[idx], head[idx]); actualHead.push_back(head[idx]); } else @@ -777,10 +771,7 @@ void TypeChecker2::visit(AstStatReturn* ret) else { auto lastType = head[ret->list.size - 1]; - if (FFlag::LuauInferActualIfElseExprType2) - isSubtype &= testLiteralOrAstTypeIsSubtype(lastExpr, lastType); - else - isSubtype &= testPotentialLiteralIsSubtype(lastExpr, lastType); + isSubtype &= testLiteralOrAstTypeIsSubtype(lastExpr, lastType); actualHead.push_back(lastType); } @@ -2780,35 +2771,6 @@ void TypeChecker2::visit(AstTypeReference* ty) size_t typesRequired = alias->typeParams.size(); size_t packsRequired = alias->typePackParams.size(); - bool hasDefaultTypes = std::any_of( - alias->typeParams.begin(), - alias->typeParams.end(), - [](auto&& el) - { - return el.defaultValue.has_value(); - } - ); - - bool hasDefaultPacks = std::any_of( - alias->typePackParams.begin(), - alias->typePackParams.end(), - [](auto&& el) - { - return el.defaultValue.has_value(); - } - ); - - if (!FFlag::LuauRemoveGenericErrorForParams) - { - if (!ty->hasParameterList) - { - if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks)) - { - reportError(GenericError{"Type parameter list is required"}, ty->location); - } - } - } - size_t typesProvided = 0; size_t extraTypes = 0; size_t packsProvided = 0; @@ -2883,17 +2845,9 @@ void TypeChecker2::visit(AstTypeReference* ty) } } - if (FFlag::LuauRemoveGenericErrorForParams) - { - // If the type parameter list is explicitly provided, allow an empty type pack to satisfy the expected pack count. - if (extraTypes == 0 && packsProvided + 1 == packsRequired && ty->hasParameterList) - packsProvided += 1; - } - else - { - if (extraTypes == 0 && packsProvided + 1 == packsRequired) - packsProvided += 1; - } + // If the type parameter list is explicitly provided, allow an empty type pack to satisfy the expected pack count. + if (extraTypes == 0 && packsProvided + 1 == packsRequired && ty->hasParameterList) + packsProvided += 1; if (typesProvided != typesRequired || packsProvided != packsRequired) { @@ -3035,7 +2989,7 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc continue; std::optional optSubLeaf; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes, subtyping->arena); else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) optSubLeaf = traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks_DEPRECATED}, subtyping->arena); @@ -3043,7 +2997,7 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc optSubLeaf = traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes); std::optional optSuperLeaf; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes, subtyping->arena); else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) optSuperLeaf = @@ -3197,29 +3151,26 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT auto exprType = follow(lookupType(expr)); expectedType = follow(expectedType); - if (FFlag::LuauInferActualIfElseExprType2) + if (auto group = expr->as()) { - if (auto group = expr->as()) - { - return testPotentialLiteralIsSubtype(group->expr, expectedType); - } - else if (auto ifElse = expr->as()) - { - bool passes = testPotentialLiteralIsSubtype(ifElse->trueExpr, expectedType); - passes &= testPotentialLiteralIsSubtype(ifElse->falseExpr, expectedType); - return passes; - } - else if (auto binExpr = expr->as(); binExpr && binExpr->op == AstExprBinary::Or) - { - // In this case: `{ ... } or { ... }` is literal _enough_ that - // we should do this covariant check. - auto relaxedExpectedLhs = module->internalTypes.addType(UnionType{{builtinTypes->falsyType, expectedType}}); - bool passes = testPotentialLiteralIsSubtype(binExpr->left, relaxedExpectedLhs); - passes &= testPotentialLiteralIsSubtype(binExpr->right, expectedType); - return passes; - } - // FIXME: We probably should do a check for `and` here. + return testPotentialLiteralIsSubtype(group->expr, expectedType); } + else if (auto ifElse = expr->as()) + { + bool passes = testPotentialLiteralIsSubtype(ifElse->trueExpr, expectedType); + passes &= testPotentialLiteralIsSubtype(ifElse->falseExpr, expectedType); + return passes; + } + else if (auto binExpr = expr->as(); binExpr && binExpr->op == AstExprBinary::Or) + { + // In this case: `{ ... } or { ... }` is literal _enough_ that + // we should do this covariant check. + auto relaxedExpectedLhs = module->internalTypes.addType(UnionType{{builtinTypes->falsyType, expectedType}}); + bool passes = testPotentialLiteralIsSubtype(binExpr->left, relaxedExpectedLhs); + passes &= testPotentialLiteralIsSubtype(binExpr->right, expectedType); + return passes; + } + // FIXME: We probably should do a check for `and` here. auto exprTable = expr->as(); auto exprTableType = get(exprType); @@ -3274,16 +3225,8 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT { NotNull scope{findInnermostScope(expr->location)}; - if (FFlag::LuauAllowMixedTables) - { - auto result = subtyping->isSubtype(/* subTy */ builtinTypes->numberType, /* superTy */ expectedTableType->indexer->indexType, scope); - isArrayLike = result.isSubtype || isErrorSuppressing(expr->location, expectedTableType->indexer->indexType); - } - else - { - auto result = subtyping->isSubtype(/* subTy */ expectedTableType->indexer->indexType, /* superTy */ builtinTypes->numberType, scope); - isArrayLike = result.isSubtype; - } + auto result = subtyping->isSubtype(/* subTy */ builtinTypes->numberType, /* superTy */ expectedTableType->indexer->indexType, scope); + isArrayLike = result.isSubtype || isErrorSuppressing(expr->location, expectedTableType->indexer->indexType); } bool isSubtype = true; @@ -3466,13 +3409,13 @@ void TypeChecker2::testIsSubtypeForInStat(const TypeId iterFunc, const TypeId pr return; } - std::optional subLeaf = FFlag::LuauSubtypingGenericPacksDoesntUseVariance + std::optional subLeaf = FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 ? traverseForType(iterFunc, reasoning.subPath, builtinTypes, subtyping->arena) : traverseForType_DEPRECATED(iterFunc, reasoning.subPath, builtinTypes); if (!subLeaf) continue; - std::optional superLeaf = FFlag::LuauSubtypingGenericPacksDoesntUseVariance + std::optional superLeaf = FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 ? traverseForType(prospectiveFunc, reasoning.superPath, builtinTypes, subtyping->arena) : traverseForType_DEPRECATED(prospectiveFunc, reasoning.superPath, builtinTypes); if (!superLeaf) diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index 1cb3e36d..ce5725f3 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -6,7 +6,7 @@ #include "Luau/TypeArena.h" LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) namespace Luau { @@ -461,7 +461,7 @@ std::pair, std::optional> flatten_DEPRECATED( ) { LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); tp = mappedGenericPacks.contains(tp) ? *mappedGenericPacks.find(tp) : tp; @@ -553,7 +553,7 @@ TypePackId sliceTypePack( const NotNull arena ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); if (sliceIndex == 0) return toBeSliced; diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index 12e50fec..2ce779c6 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -18,7 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAGVARIABLE(LuauConsiderErrorSuppressionInTypes) // Maximum number of steps to follow when traversing a path. May not always @@ -69,7 +69,7 @@ bool Reduction::operator==(const Reduction& other) const bool GenericPackMapping::operator==(const GenericPackMapping& other) const { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); return mappedType == other.mappedType; } @@ -157,7 +157,7 @@ size_t PathHash::operator()(const Reduction& reduction) const size_t PathHash::operator()(const GenericPackMapping& mapping) const { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); return std::hash()(mapping.mappedType); } @@ -315,7 +315,7 @@ PathBuilder& PathBuilder::packSlice(size_t start_index) PathBuilder& PathBuilder::mappedGenericPack(TypePackId mappedType) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); components.emplace_back(GenericPackMapping{mappedType}); return *this; } @@ -327,7 +327,7 @@ namespace struct TraversalState { - // Clip the below two constructors with LuauSubtypingGenericPacksDoesntUseVariance + // Clip the below two constructors with LuauSubtypingGenericPacksDoesntUseVariance2 TraversalState(TypeId root, NotNull builtinTypes, const DenseHashMap* mappedGenericPacks, TypeArena* arena) : current(root) , builtinTypes(builtinTypes) @@ -365,7 +365,7 @@ struct TraversalState TypeOrPack current; NotNull builtinTypes; - // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance + // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 const DenseHashMap* mappedGenericPacks_DEPRECATED = nullptr; // TODO: make NotNull when LuauReturnMappedGenericPacksFromSubtyping3 is clipped TypeArena* arena = nullptr; @@ -550,7 +550,7 @@ struct TraversalState { auto currentPack = get(current); LUAU_ASSERT(currentPack); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { if (const auto tp = get(*currentPack)) { @@ -709,7 +709,7 @@ struct TraversalState if (auto tail = it.tail()) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance && + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 && mappedGenericPacks_DEPRECATED && mappedGenericPacks_DEPRECATED->contains(*tail)) updateCurrent(*mappedGenericPacks_DEPRECATED->find(*tail)); @@ -730,7 +730,7 @@ struct TraversalState // TODO: clip these checks once LuauReturnMappedGenericPacksFromSubtyping3 is clipped // arena and mappedGenericPacks_DEPRECATED should be NonNull once that happens LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { LUAU_ASSERT(arena); @@ -743,7 +743,7 @@ struct TraversalState // TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping3 is clipped // arena and mappedGenericPacks should be NonNull once that happens - if (!FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + if (!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) { if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) LUAU_ASSERT(arena && mappedGenericPacks_DEPRECATED); @@ -755,7 +755,7 @@ struct TraversalState if (!currentPack) return false; - auto [flatHead, flatTail] = FFlag::LuauSubtypingGenericPacksDoesntUseVariance + auto [flatHead, flatTail] = FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 ? flatten(*currentPack) : flatten_DEPRECATED(*currentPack, *mappedGenericPacks_DEPRECATED); @@ -784,7 +784,7 @@ struct TraversalState bool traverse(const TypePath::GenericPackMapping mapping) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); if (checkInvariants()) return false; @@ -886,7 +886,7 @@ std::string toString(const TypePath::Path& path, bool prefixDot) } else if constexpr (std::is_same_v) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); result << "~"; } else @@ -1107,7 +1107,7 @@ std::string toStringHuman(const TypePath::Path& path) } else if constexpr (std::is_same_v) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); result << "is a generic pack mapped to "; } else @@ -1168,7 +1168,7 @@ static bool traverse(TraversalState& state, const Path& path) std::optional traverse_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) @@ -1179,7 +1179,7 @@ std::optional traverse_DEPRECATED(TypeId root, const Path& path, Not std::optional traverse_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) @@ -1190,7 +1190,7 @@ std::optional traverse_DEPRECATED(TypePackId root, const Path& path, std::optional traverse(const TypePackId root, const Path& path, const NotNull builtinTypes, const NotNull arena) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) @@ -1207,7 +1207,7 @@ std::optional traverse_DEPRECATED( NotNull arena ) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) @@ -1224,7 +1224,7 @@ std::optional traverse_DEPRECATED( NotNull arena ) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) @@ -1235,7 +1235,7 @@ std::optional traverse_DEPRECATED( std::optional traverse(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) @@ -1246,7 +1246,7 @@ std::optional traverse(const TypeId root, const Path& path, const No std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) @@ -1268,7 +1268,7 @@ std::optional traverseForType_DEPRECATED( NotNull arena ) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) @@ -1284,7 +1284,7 @@ std::optional traverseForType_DEPRECATED( std::optional traverseForType(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) @@ -1301,7 +1301,7 @@ std::optional traverseForType(const TypeId root, const Path& path, const std::optional traverseForType_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) @@ -1323,7 +1323,7 @@ std::optional traverseForType_DEPRECATED( NotNull arena ) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) @@ -1344,7 +1344,7 @@ std::optional traverseForType( const NotNull arena ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) @@ -1360,7 +1360,7 @@ std::optional traverseForType( std::optional traverseForPack_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) @@ -1382,7 +1382,7 @@ std::optional traverseForPack_DEPRECATED( NotNull arena ) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) @@ -1403,7 +1403,7 @@ std::optional traverseForPack( const NotNull arena ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) @@ -1419,7 +1419,7 @@ std::optional traverseForPack( std::optional traverseForPack_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) @@ -1441,7 +1441,7 @@ std::optional traverseForPack_DEPRECATED( NotNull arena ) { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) @@ -1462,7 +1462,7 @@ std::optional traverseForPack( const NotNull arena ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) @@ -1505,7 +1505,7 @@ std::optional traverseForIndex(const Path& path) TypePack flattenPackWithPath(TypePackId root, const Path& path) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); std::vector flattened; @@ -1543,7 +1543,7 @@ TypePack flattenPackWithPath(TypePackId root, const Path& path) TypePack traverseForFlattenedPack(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); // Iterate over path's components, and figure out when it turns into Tails and GenericPackMappings // We want to split out the part of the path that contains the generic pack mappings we're interested in, so that we can flatten it diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 71e6a0c4..0aa9790e 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -26,7 +26,6 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauUnifierRecursionLimit, 100) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauLimitUnification) LUAU_FASTFLAGVARIABLE(LuauUnifyShortcircuitSomeIntersectionsAndUnions) -LUAU_FASTFLAGVARIABLE(LuauTryToOptimizeSetTypeUnification) LUAU_FASTFLAGVARIABLE(LuauFixNilRightPad) namespace Luau @@ -214,67 +213,19 @@ UnifyResult Unifier2::unify_(TypeId subTy, TypeId superTy) if (subFn && superFn) return unify_(subTy, superFn); - if (FFlag::LuauTryToOptimizeSetTypeUnification) - { - auto subUnion = get(subTy); - auto superUnion = get(superTy); - - auto subIntersection = get(subTy); - auto superIntersection = get(superTy); - - // This is, effectively, arranged to avoid the following: - // - // 'a & T <: U | V => 'a & T <: U and 'a & T <: V - // - - // For T <: U & V and T | U <: V, these two cases are entirely correct. - - // We decompose T <: U & V above into T <: U and T <: V ... - if (superIntersection) - return unify_(subTy, superIntersection); - - // ... and T | U <: V into T <: V and U <: V. - if (subUnion) - return unify_(subUnion, superTy); - - // This, T & U <: V, erroneously is decomposed into T <: U and T <: V, - // even though technically we only need one of the above to hold. - // However, this ordering means that we avoid ... - if (subIntersection) - return unify_(subIntersection, superTy); - - // T <: U | V decomposing into T <: U and T <: V is incorrect, and - // can result in some really strange user-visible bugs. Consider: - // - // 'a & ~(false?) <: string | number - // - // Intuitively, this should place a constraint of `string | number` - // on the upper bound of `'a`. But if we hit this case, then we - // end up with something like: - // - // 'a & ~(false?) <: string and 'a & ~(false?) <: number - // - // ... which will result in `'a` having `string & number` as its - // upper bound, and being inferred to `never`. - if (superUnion) - return unify_(subTy, superUnion); - } - else - { - auto subUnion = get(subTy); - auto superUnion = get(superTy); - if (subUnion) - return unify_(subUnion, superTy); - else if (superUnion) - return unify_(subTy, superUnion); - - auto subIntersection = get(subTy); - auto superIntersection = get(superTy); - if (subIntersection) - return unify_(subIntersection, superTy); - else if (superIntersection) - return unify_(subTy, superIntersection); - } + auto subUnion = get(subTy); + auto superUnion = get(superTy); + if (subUnion) + return unify_(subUnion, superTy); + else if (superUnion) + return unify_(subTy, superUnion); + + auto subIntersection = get(subTy); + auto superIntersection = get(superTy); + if (subIntersection) + return unify_(subIntersection, superTy); + else if (superIntersection) + return unify_(subTy, superIntersection); auto subNever = get(subTy); auto superNever = get(superTy); diff --git a/Ast/include/Luau/Cst.h b/Ast/include/Luau/Cst.h index b9dcb7fd..223c3ddd 100644 --- a/Ast/include/Luau/Cst.h +++ b/Ast/include/Luau/Cst.h @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/Ast.h" #include "Luau/Location.h" #include diff --git a/Analysis/include/Luau/Transpiler.h b/Ast/include/Luau/PrettyPrinter.h similarity index 63% rename from Analysis/include/Luau/Transpiler.h rename to Ast/include/Luau/PrettyPrinter.h index 586fd92e..6d69bb59 100644 --- a/Analysis/include/Luau/Transpiler.h +++ b/Ast/include/Luau/PrettyPrinter.h @@ -12,7 +12,7 @@ namespace Luau class AstNode; class AstStatBlock; -struct TranspileResult +struct PrettyPrintResult { std::string code; Location errorLocation; @@ -23,11 +23,11 @@ std::string toString(AstNode* node); void dump(AstNode* node); // Never fails on a well-formed AST -std::string transpile(AstStatBlock& ast); -std::string transpileWithTypes(AstStatBlock& block); -std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap); +std::string prettyPrint(AstStatBlock& ast); +std::string prettyPrintWithTypes(AstStatBlock& block); +std::string prettyPrintWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap); // Only fails when parsing fails -TranspileResult transpile(std::string_view source, ParseOptions options = ParseOptions{}, bool withTypes = false); +PrettyPrintResult prettyPrint(std::string_view source, ParseOptions options = ParseOptions{}, bool withTypes = false); } // namespace Luau diff --git a/Analysis/src/Transpiler.cpp b/Ast/src/PrettyPrinter.cpp similarity index 98% rename from Analysis/src/Transpiler.cpp rename to Ast/src/PrettyPrinter.cpp index 17ac51dd..7e89f201 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Ast/src/PrettyPrinter.cpp @@ -1,5 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/Transpiler.h" +#include "Luau/PrettyPrinter.h" #include "Luau/Parser.h" #include "Luau/StringUtils.h" @@ -1865,14 +1865,14 @@ void dump(AstNode* node) printf("%s\n", toString(node).c_str()); } -std::string transpile(AstStatBlock& block, const CstNodeMap& cstNodeMap) +std::string prettyPrint(AstStatBlock& block, const CstNodeMap& cstNodeMap) { StringWriter writer; Printer(writer, cstNodeMap).visualizeBlock(block); return writer.str(); } -std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap) +std::string prettyPrintWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap) { StringWriter writer; Printer printer(writer, cstNodeMap); @@ -1881,12 +1881,12 @@ std::string transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap return writer.str(); } -std::string transpileWithTypes(AstStatBlock& block) +std::string prettyPrintWithTypes(AstStatBlock& block) { - return transpileWithTypes(block, CstNodeMap{nullptr}); + return prettyPrintWithTypes(block, CstNodeMap{nullptr}); } -TranspileResult transpile(std::string_view source, ParseOptions options, bool withTypes) +PrettyPrintResult prettyPrint(std::string_view source, ParseOptions options, bool withTypes) { options.storeCstData = true; @@ -1896,20 +1896,20 @@ TranspileResult transpile(std::string_view source, ParseOptions options, bool wi if (!parseResult.errors.empty()) { - // TranspileResult keeps track of only a single error + // PrettyPrintResult keeps track of only a single error const ParseError& error = parseResult.errors.front(); - return TranspileResult{"", error.getLocation(), error.what()}; + return PrettyPrintResult{"", error.getLocation(), error.what()}; } LUAU_ASSERT(parseResult.root); if (!parseResult.root) - return TranspileResult{"", {}, "Internal error: Parser yielded empty parse tree"}; + return PrettyPrintResult{"", {}, "Internal error: Parser yielded empty parse tree"}; if (withTypes) - return TranspileResult{transpileWithTypes(*parseResult.root, parseResult.cstNodeMap)}; + return PrettyPrintResult{prettyPrintWithTypes(*parseResult.root, parseResult.cstNodeMap)}; - return TranspileResult{transpile(*parseResult.root, parseResult.cstNodeMap)}; + return PrettyPrintResult{prettyPrint(*parseResult.root, parseResult.cstNodeMap)}; } } // namespace Luau diff --git a/CLI/src/Analyze.cpp b/CLI/src/Analyze.cpp index 592e1d0e..ae4e9a20 100644 --- a/CLI/src/Analyze.cpp +++ b/CLI/src/Analyze.cpp @@ -1,11 +1,11 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/Config.h" -#include "Luau/ModuleResolver.h" -#include "Luau/TypeInfer.h" #include "Luau/BuiltinDefinitions.h" +#include "Luau/Config.h" #include "Luau/Frontend.h" +#include "Luau/ModuleResolver.h" +#include "Luau/PrettyPrinter.h" #include "Luau/TypeAttach.h" -#include "Luau/Transpiler.h" +#include "Luau/TypeInfer.h" #include "Luau/AnalyzeRequirer.h" #include "Luau/FileUtils.h" @@ -113,7 +113,7 @@ static bool reportModuleResult(Luau::Frontend& frontend, const Luau::ModuleName& Luau::attachTypeData(*sm, *m); - std::string annotated = Luau::transpileWithTypes(*sm->root); + std::string annotated = Luau::prettyPrintWithTypes(*sm->root); printf("%s", annotated.c_str()); } diff --git a/CLI/src/Reduce.cpp b/CLI/src/Reduce.cpp index 22c72964..49f43164 100644 --- a/CLI/src/Reduce.cpp +++ b/CLI/src/Reduce.cpp @@ -3,7 +3,7 @@ #include "Luau/Ast.h" #include "Luau/Common.h" #include "Luau/Parser.h" -#include "Luau/Transpiler.h" +#include "Luau/PrettyPrinter.h" #include "Luau/FileUtils.h" @@ -85,7 +85,7 @@ struct Reducer void writeTempScript(bool minify = false) { - std::string source = transpileWithTypes(*root, cstNodeMap); + std::string source = prettyPrintWithTypes(*root, cstNodeMap); if (minify) { diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index 53617a1e..529c4f09 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -170,8 +170,8 @@ class AssemblyBuilderX64 void vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vcmpeqps(OperandX64 dst, OperandX64 src1, OperandX64 src2); - void vblendvps(RegisterX64 dst, RegisterX64 src1, RegisterX64 src2, OperandX64 mask); - void vblendvpd(RegisterX64 dst, RegisterX64 src1, RegisterX64 src2, OperandX64 mask); + void vblendvps(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, RegisterX64 mask); + void vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, RegisterX64 mask); void vblendvpd_DEPRECATED(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3); void vpshufps(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t shuffle); diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 929bd167..5542fe05 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -942,10 +942,10 @@ void AssemblyBuilderX64::vcmpeqps(OperandX64 dst, OperandX64 src1, OperandX64 sr placeAvx("vcmpeqps", dst, src1, src2, 0x00, 0xc2, false, AVX_0F, AVX_NP); } -void AssemblyBuilderX64::vblendvps(RegisterX64 dst, RegisterX64 src1, RegisterX64 src2, OperandX64 mask) +void AssemblyBuilderX64::vblendvps(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, RegisterX64 mask) { // bits [7:4] of imm8 are used to select register for operand 4 - placeAvx("vblendvpd", dst, src1, mask, src2.index << 4, 0x4a, false, AVX_0F3A, AVX_66); + placeAvx("vblendvps", dst, src1, src2, mask.index << 4, 0x4a, false, AVX_0F3A, AVX_66); } void AssemblyBuilderX64::vblendvpd_DEPRECATED(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3) @@ -954,10 +954,10 @@ void AssemblyBuilderX64::vblendvpd_DEPRECATED(RegisterX64 dst, RegisterX64 src1, placeAvx("vblendvpd", dst, src1, mask, src3.index << 4, 0x4b, false, AVX_0F3A, AVX_66); } -void AssemblyBuilderX64::vblendvpd(RegisterX64 dst, RegisterX64 src1, RegisterX64 src2, OperandX64 mask) +void AssemblyBuilderX64::vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, RegisterX64 mask) { // bits [7:4] of imm8 are used to select register for operand 4 - placeAvx("vblendvpd", dst, src1, mask, src2.index << 4, 0x4b, false, AVX_0F3A, AVX_66); + placeAvx("vblendvpd", dst, src1, src2, mask.index << 4, 0x4b, false, AVX_0F3A, AVX_66); } void AssemblyBuilderX64::vpshufps(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t shuffle) diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 328c2ce4..99e303c5 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -680,7 +680,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // If arg == 0 then tmp1 is 0 and mask-bit is 0, result is 0 // If arg > 0 then tmp1 is 0 and mask-bit is 1, result is 1 if (FFlag::LuauCodeGenVBlendpdReorder) - build.vblendvpd(inst.regX64, tmp1.reg, inst.regX64, build.f64x2(1, 1)); + build.vblendvpd(inst.regX64, tmp1.reg, build.f64x2(1, 1), inst.regX64); else build.vblendvpd_DEPRECATED(inst.regX64, tmp1.reg, build.f64x2(1, 1), inst.regX64); break; @@ -701,14 +701,14 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) if (inst.a.kind == IrOpKind::Inst) if (FFlag::LuauCodeGenVBlendpdReorder) - build.vblendvpd(inst.regX64, regOp(inst.a), tmp.reg, memRegDoubleOp(inst.b)); + build.vblendvpd(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b), tmp.reg); else build.vblendvpd_DEPRECATED(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b), tmp.reg); else { build.vmovsd(inst.regX64, memRegDoubleOp(inst.a)); if (FFlag::LuauCodeGenVBlendpdReorder) - build.vblendvpd(inst.regX64, inst.regX64, tmp.reg, memRegDoubleOp(inst.b)); + build.vblendvpd(inst.regX64, inst.regX64, memRegDoubleOp(inst.b), tmp.reg); else build.vblendvpd_DEPRECATED(inst.regX64, inst.regX64, memRegDoubleOp(inst.b), tmp.reg); } diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index ec02fdfb..f0637506 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -152,7 +152,6 @@ RegisterA64 IrRegAllocA64::allocReg(KindA64 kind, uint32_t index) // Try to find and spill a register that is not used in the current instruction and has the furthest next use if (uint32_t furthestUseTarget = findInstructionWithFurthestNextUse(set); furthestUseTarget != kInvalidInstIdx) { - CODEGEN_ASSERT(currInstIdx == index); spill(set, index, furthestUseTarget); CODEGEN_ASSERT(set.free != 0); } diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index c2e7c101..a7de2e99 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -8,7 +8,7 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorLerp) +LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorLerp2) LUAU_FASTFLAGVARIABLE(LuauCodeGenFMA) // TODO: when nresults is less than our actual result count, we can skip computing/writing unused results @@ -286,7 +286,7 @@ static BuiltinImplResult translateBuiltinMathClamp( static BuiltinImplResult translateBuiltinVectorLerp(IrBuilder& build, int nparams, int ra, int arg, IrOp args, IrOp arg3, int nresults, int pcpos) { - if (!FFlag::LuauCodeGenVectorLerp || nparams < 3 || nresults > 1) + if (!FFlag::LuauCodeGenVectorLerp2 || nparams < 3 || nresults > 1) return {BuiltinImplType::None, -1}; IrOp arg1 = build.vmReg(arg); diff --git a/Common/include/Luau/ScopedSeenSet.h b/Common/include/Luau/ScopedSeenSet.h new file mode 100644 index 00000000..dcee6af7 --- /dev/null +++ b/Common/include/Luau/ScopedSeenSet.h @@ -0,0 +1,29 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +namespace Luau +{ + +template +struct ScopedSeenSet +{ + SetType& seen; + Key key; + + ScopedSeenSet(SetType& seen, Key key) + : seen(seen) + , key(key) + { + seen.insert(key); + } + + ~ScopedSeenSet() + { + seen.erase(key); + } + + ScopedSeenSet(const ScopedSeenSet&) = delete; + ScopedSeenSet& operator=(const ScopedSeenSet&) = delete; +}; + +} // namespace Luau diff --git a/Sources.cmake b/Sources.cmake index ba2ff48c..d004bbc8 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -24,6 +24,7 @@ target_sources(Luau.Ast PRIVATE Ast/include/Luau/ParseOptions.h Ast/include/Luau/Parser.h Ast/include/Luau/ParseResult.h + Ast/include/Luau/PrettyPrinter.h Ast/include/Luau/StringUtils.h Ast/include/Luau/TimeTrace.h @@ -34,6 +35,7 @@ target_sources(Luau.Ast PRIVATE Ast/src/Lexer.cpp Ast/src/Location.cpp Ast/src/Parser.cpp + Ast/src/PrettyPrinter.cpp Ast/src/StringUtils.cpp Ast/src/TimeTrace.cpp ) @@ -228,7 +230,6 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/ToDot.h Analysis/include/Luau/TopoSortStatements.h Analysis/include/Luau/ToString.h - Analysis/include/Luau/Transpiler.h Analysis/include/Luau/TxnLog.h Analysis/include/Luau/Type.h Analysis/include/Luau/TypeArena.h @@ -303,7 +304,6 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/ToDot.cpp Analysis/src/TopoSortStatements.cpp Analysis/src/ToString.cpp - Analysis/src/Transpiler.cpp Analysis/src/TxnLog.cpp Analysis/src/Type.cpp Analysis/src/TypeArena.cpp @@ -498,6 +498,7 @@ if(TARGET Luau.UnitTest) tests/NotNull.test.cpp tests/OverloadResolver.test.cpp tests/Parser.test.cpp + tests/PrettyPrinter.test.cpp tests/RegisterCallbacks.cpp tests/RegisterCallbacks.h tests/RequireTracer.test.cpp @@ -511,7 +512,6 @@ if(TARGET Luau.UnitTest) tests/ToDot.test.cpp tests/TopoSort.test.cpp tests/ToString.test.cpp - tests/Transpiler.test.cpp tests/TxnLog.test.cpp tests/TypeFunction.test.cpp tests/TypeFunction.user.test.cpp diff --git a/VM/src/lbaselib.cpp b/VM/src/lbaselib.cpp index f1b34bc1..fe722ddc 100644 --- a/VM/src/lbaselib.cpp +++ b/VM/src/lbaselib.cpp @@ -11,8 +11,6 @@ #include #include -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauXpcallContNoYield, false) -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauXpcallContErrorHandling, false) LUAU_FASTFLAG(LuauStacklessPcall) static void writestring(const char* s, size_t l) @@ -395,10 +393,7 @@ static void luaB_xpcallerr(lua_State* L, void* ud) { StkId func = (StkId)ud; - if (DFFlag::LuauXpcallContNoYield) - luaD_callny(L, func, 1); - else - luaD_call(L, func, 1); + luaD_callny(L, func, 1); } static int luaB_xpcallcont(lua_State* L, int status) @@ -417,35 +412,24 @@ static int luaB_xpcallcont(lua_State* L, int status) lua_pushvalue(L, 1); // push error function on top of the stack lua_pushvalue(L, -3); // push error object (that was on top of the stack before) - if (DFFlag::LuauXpcallContErrorHandling) - { - StkId errf = L->top - 2; - ptrdiff_t oldtopoffset = savestack(L, errf); - - int err = luaD_pcall(L, luaB_xpcallerr, errf, oldtopoffset, 0); - - if (err != 0) - { - int errstatus = status; + StkId errf = L->top - 2; + ptrdiff_t oldtopoffset = savestack(L, errf); - // in general we preserve the status, except for cases when the error handler fails - // out of memory is treated specially because it's common for it to be cascading, in which case we preserve the code - if (status == LUA_ERRMEM && err == LUA_ERRMEM) - errstatus = LUA_ERRMEM; - else - errstatus = LUA_ERRERR; + int err = luaD_pcall(L, luaB_xpcallerr, errf, oldtopoffset, 0); - StkId oldtop = restorestack(L, oldtopoffset); - luaD_seterrorobj(L, errstatus, oldtop); - } - } - else + if (err != 0) { - StkId res = L->top - 3; - StkId errf = L->top - 2; + int errstatus = status; + + // in general we preserve the status, except for cases when the error handler fails + // out of memory is treated specially because it's common for it to be cascading, in which case we preserve the code + if (status == LUA_ERRMEM && err == LUA_ERRMEM) + errstatus = LUA_ERRMEM; + else + errstatus = LUA_ERRERR; - // note: we pass res as errfunc as a short cut; if errf generates an error, we'll try to execute res (boolean) and fail - luaD_pcall(L, luaB_xpcallerr, errf, savestack(L, errf), savestack(L, res)); + StkId oldtop = restorestack(L, oldtopoffset); + luaD_seterrorobj(L, errstatus, oldtop); } return 2; diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index d9b5ed2e..21ae3b34 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -17,7 +17,6 @@ #include -LUAU_DYNAMIC_FASTFLAG(LuauXpcallContErrorHandling) LUAU_FASTFLAGVARIABLE(LuauStacklessPcall) LUAU_FASTFLAGVARIABLE(LuauResumeFix) @@ -599,8 +598,7 @@ static void resume_handle(lua_State* L, void* ud) // close eventual pending closures; this means it's now safe to restore stack luaF_close(L, L->ci->base); - if (DFFlag::LuauXpcallContErrorHandling) - restore_stack_limit(L); + restore_stack_limit(L); // finish cont call and restore stack to previous ci top luau_poscall(L, L->top - n); diff --git a/fuzz/transpiler.cpp b/fuzz/prettyprinter.cpp similarity index 79% rename from fuzz/transpiler.cpp rename to fuzz/prettyprinter.cpp index 02872b02..467eee9d 100644 --- a/fuzz/transpiler.cpp +++ b/fuzz/prettyprinter.cpp @@ -1,6 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/Transpiler.h" #include "Luau/Common.h" +#include "Luau/PrettyPrinter.h" #include @@ -14,6 +14,6 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) flag->value = true; } - Luau::transpile(std::string_view(reinterpret_cast(Data), Size)); + Luau::prettyPrint(std::string_view(reinterpret_cast(Data), Size)); return 0; } diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index d6da9d8e..ea864b8a 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -12,8 +12,8 @@ #include "Luau/Linter.h" #include "Luau/ModuleResolver.h" #include "Luau/Parser.h" +#include "Luau/PrettyPrinter.h" #include "Luau/ToString.h" -#include "Luau/Transpiler.h" #include "Luau/TypeInfer.h" #include "lua.h" @@ -36,7 +36,7 @@ const bool kFuzzCompiler = getEnvParam("LUAU_FUZZ_COMPILER", true); const bool kFuzzLinter = getEnvParam("LUAU_FUZZ_LINTER", true); const bool kFuzzTypeck = getEnvParam("LUAU_FUZZ_TYPE_CHECK", true); const bool kFuzzVM = getEnvParam("LUAU_FUZZ_VM", true); -const bool kFuzzTranspile = getEnvParam("LUAU_FUZZ_TRANSPILE", true); +const bool kFuzzPrettyPrint = getEnvParam("LUAU_FUZZ_PRETTY_PRINT", true); const bool kFuzzCodegenVM = getEnvParam("LUAU_FUZZ_CODEGEN_VM", true); const bool kFuzzCodegenAssembly = getEnvParam("LUAU_FUZZ_CODEGEN_ASM", true); const bool kFuzzUseNewSolver = getEnvParam("LUAU_FUZZ_NEW_SOLVER", false); @@ -353,12 +353,12 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message) } } - if (kFuzzTranspile) + if (kFuzzPrettyPrint) { for (Luau::ParseResult& parseResult : parseResults) { if (parseResult.root) - transpileWithTypes(*parseResult.root); + prettyPrintWithTypes(*parseResult.root); } } diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index 6acb348e..5700307c 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -578,7 +578,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "AVXTernaryInstructionForms") vroundsd(xmm8, xmm13, xmmword[r13 + rdx], RoundingModeX64::RoundToPositiveInfinity), 0xc4, 0x43, 0x11, 0x0b, 0x44, 0x15, 0x00, 0x0a ); SINGLE_COMPARE(vroundsd(xmm9, xmm14, xmmword[rcx + r10], RoundingModeX64::RoundToZero), 0xc4, 0x23, 0x09, 0x0b, 0x0c, 0x11, 0x0b); - SINGLE_COMPARE(vblendvpd(xmm7, xmm12, xmm5, xmmword[rcx + r10]), 0xc4, 0xa3, 0x19, 0x4b, 0x3c, 0x11, 0x50); + SINGLE_COMPARE(vblendvpd(xmm7, xmm12, xmmword[rcx + r10], xmm5), 0xc4, 0xa3, 0x19, 0x4b, 0x3c, 0x11, 0x50); SINGLE_COMPARE(vpshufps(xmm7, xmm12, xmmword[rcx + r10], 0b11010100), 0xc4, 0xa1, 0x18, 0xc6, 0x3c, 0x11, 0xd4); SINGLE_COMPARE(vpinsrd(xmm7, xmm12, xmmword[rcx + r10], 2), 0xc4, 0xa3, 0x19, 0x22, 0x3c, 0x11, 0x02); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 361d977e..65c93e81 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -21,7 +21,6 @@ LUAU_DYNAMIC_FASTINT(LuauSubtypingRecursionLimit) LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauSuggestHotComments) LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauAutocompleteAttributes) @@ -1604,9 +1603,6 @@ return target(a.@1 TEST_CASE_FIXTURE(ACFixture, "type_correct_suggestion_in_table") { - if (FFlag::LuauSolverV2) // CLI-116815 Autocomplete cannot suggest keys while autocompleting inside of a table - return; - check(R"( type Foo = { a: number, b: string } local a = { one = 4, two = "hello" } @@ -4862,8 +4858,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_incomplete_ TEST_CASE_FIXTURE(ACFixture, "autocomplete_suggest_hot_comments") { - ScopedFastFlag sff{FFlag::LuauSuggestHotComments, true}; - check("--!@1"); auto ac = autocomplete('1'); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index ce008e94..8d68196a 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -34,14 +34,12 @@ void luaC_validate(lua_State* L); // internal functions, declared in lvm.h - not exposed via lua.h void luau_callhook(lua_State* L, lua_Hook hook, void* userdata); -LUAU_DYNAMIC_FASTFLAG(LuauXpcallContErrorHandling) LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTFLAG(LuauVectorLerp) LUAU_FASTFLAG(LuauCompileVectorLerp) LUAU_FASTFLAG(LuauTypeCheckerVectorLerp2) -LUAU_FASTFLAG(LuauCodeGenVectorLerp) -LUAU_DYNAMIC_FASTFLAG(LuauXpcallContNoYield) +LUAU_FASTFLAG(LuauCodeGenVectorLerp2) LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) LUAU_FASTFLAG(LuauCodeGenRestoreFromSplitStore) LUAU_FASTFLAG(LuauStacklessPcall) @@ -280,11 +278,30 @@ static StateRef runConformance( int result = luau_load(L, chunkname.c_str(), bytecode, bytecodeSize, 0); free(bytecode); + Luau::CodeGen::CompilationOptions nativeOpts = codegenOptions ? *codegenOptions : defaultCodegenOptions(); + if (result == 0 && codegen && !skipCodegen && luau_codegen_supported()) + Luau::CodeGen::compile(L, -1, nativeOpts); + + // Extra test for lowering on both platforms with assembly generation + if (luau_codegen_supported()) { - Luau::CodeGen::CompilationOptions nativeOpts = codegenOptions ? *codegenOptions : defaultCodegenOptions(); - Luau::CodeGen::compile(L, -1, nativeOpts); + Luau::CodeGen::AssemblyOptions assemblyOptions; + assemblyOptions.compilationOptions = nativeOpts; + + assemblyOptions.includeAssembly = true; + assemblyOptions.includeIr = true; + assemblyOptions.includeOutlinedCode = true; + assemblyOptions.includeIrTypes = true; + + assemblyOptions.target = Luau::CodeGen::AssemblyOptions::A64; + std::string a64 = Luau::CodeGen::getAssembly(L, -1, assemblyOptions); + CHECK(!a64.empty()); + + assemblyOptions.target = Luau::CodeGen::AssemblyOptions::X64_SystemV; + std::string x64 = Luau::CodeGen::getAssembly(L, -1, assemblyOptions); + CHECK(!x64.empty()); } int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX; @@ -743,7 +760,6 @@ TEST_CASE("Literals") TEST_CASE("Errors") { - ScopedFastFlag luauXpcallContErrorHandling{DFFlag::LuauXpcallContErrorHandling, true}; ScopedFastFlag luauStacklessPcall{FFlag::LuauStacklessPcall, true}; runConformance("errors.luau"); @@ -828,8 +844,6 @@ TEST_CASE("UTF8") TEST_CASE("Coroutine") { - ScopedFastFlag luauXpcallContNoYield{DFFlag::LuauXpcallContNoYield, true}; - runConformance("coroutine.luau"); } @@ -844,7 +858,6 @@ static int cxxthrow(lua_State* L) TEST_CASE("PCall") { - ScopedFastFlag luauXpcallContErrorHandling{DFFlag::LuauXpcallContErrorHandling, true}; ScopedFastFlag luauStacklessPcall{FFlag::LuauStacklessPcall, true}; runConformance( @@ -1167,7 +1180,7 @@ TEST_CASE("VectorLibrary") {FFlag::LuauCompileVectorLerp, true}, {FFlag::LuauTypeCheckerVectorLerp2, true}, {FFlag::LuauVectorLerp, true}, - {FFlag::LuauCodeGenVectorLerp, true} + {FFlag::LuauCodeGenVectorLerp2, true} }; lua_CompileOptions copts = defaultOptions(); diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index e6bc25f2..f918f0dd 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -9,10 +9,10 @@ #include "Luau/ModuleResolver.h" #include "Luau/NotNull.h" #include "Luau/Parser.h" +#include "Luau/PrettyPrinter.h" #include "Luau/Type.h" #include "Luau/TypeAttach.h" #include "Luau/TypeInfer.h" -#include "Luau/Transpiler.h" #include "doctest.h" @@ -579,7 +579,7 @@ std::string Fixture::decorateWithTypes(const std::string& code) SourceModule* sourceModule = getFrontend().getSourceModule(mainModuleName); attachTypeData(*sourceModule, *getFrontend().moduleResolver.getModule(mainModuleName)); - return transpileWithTypes(*sourceModule->root); + return prettyPrintWithTypes(*sourceModule->root); } void Fixture::dumpErrors(std::ostream& os, const std::vector& errors) diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index a49eb7a5..b08d125c 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -31,8 +31,6 @@ LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule) LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAG(LuauForInProvidesRecommendations) -LUAU_FASTFLAG(LuauFragmentAutocompleteTakesInnermostRefinement) -LUAU_FASTFLAG(LuauSuggestHotComments) LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) LUAU_FASTFLAG(LuauForInRangesConsiderInLocation) @@ -4363,8 +4361,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "correctly_grab_innermost_refinement") { - ScopedFastFlag _{FFlag::LuauFragmentAutocompleteTakesInnermostRefinement, true}; - const std::string source = R"( --!strict type Type1 = { Type: "Type1", CommonKey: string, Type1Key: string } @@ -4407,7 +4403,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "hot_comment_should_rec") { - ScopedFastFlag sff{FFlag::LuauSuggestHotComments, true}; const std::string source = R"(--!@1)"; autocompleteFragmentInBothSolvers( source, diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index b70ea6d3..33752de2 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -19,7 +19,7 @@ LUAU_FASTFLAG(LuauVectorLerp) LUAU_FASTFLAG(LuauCompileVectorLerp) LUAU_FASTFLAG(LuauTypeCheckerVectorLerp2) -LUAU_FASTFLAG(LuauCodeGenVectorLerp) +LUAU_FASTFLAG(LuauCodeGenVectorLerp2) LUAU_FASTFLAG(LuauCodeGenFMA) LUAU_FASTFLAG(LuauCodegenDirectCompare2) @@ -471,7 +471,7 @@ TEST_CASE("VectorLerp") {FFlag::LuauCompileVectorLerp, true}, {FFlag::LuauTypeCheckerVectorLerp2, true}, {FFlag::LuauVectorLerp, true}, - {FFlag::LuauCodeGenVectorLerp, true} + {FFlag::LuauCodeGenVectorLerp2, true} }; if (FFlag::LuauCodeGenFMA) { diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 9a735a14..824407df 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -15,8 +15,6 @@ #include "doctest.h" #include -LUAU_FASTFLAG(LuauNewNonStrictMoreUnknownSymbols) -LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAG(LuauNewNonStrictSuppressesDynamicRequireErrors) LUAU_FASTFLAG(LuauNewNonStrictReportsOneIndexedErrors) LUAU_FASTFLAG(LuauUnreducedTypeFunctionsDontTriggerWarnings) @@ -66,7 +64,7 @@ using namespace Luau; struct NonStrictTypeCheckerFixture : Fixture { - NonStrictTypeCheckerFixture() {} + NonStrictTypeCheckerFixture() = default; CheckResult checkNonStrict(const std::string& code) { @@ -199,18 +197,9 @@ local x abs(lower(x)) )"); - if (FFlag::LuauNewNonStrictMoreUnknownSymbols) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 4), "abs", result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 10), "lower", result); - } - else - { - - LUAU_REQUIRE_ERROR_COUNT(1, result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 4), "abs", result); - } + LUAU_REQUIRE_ERROR_COUNT(2, result); + NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 4), "abs", result); + NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 10), "lower", result); } @@ -224,14 +213,8 @@ else lower(x) end )"); - if (FFlag::LuauNewNonStrictNoErrorsPassingNever) - LUAU_REQUIRE_NO_ERRORS(result); - else - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 10), "lower", result); - } + + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_warns_nil_branches") @@ -274,13 +257,8 @@ if cond() then end )"); - if (FFlag::LuauNewNonStrictMoreUnknownSymbols) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result); - } - else - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_ERROR_COUNT(1, result); + NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 8), "abs", result); } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_no_else_err_in_cond") @@ -313,14 +291,7 @@ local x : never local y = if cond() then abs(x) else lower(x) )"); - if (FFlag::LuauNewNonStrictNoErrorsPassingNever) - LUAU_REQUIRE_NO_ERRORS(result); - else - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 29), "abs", result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 43), "lower", result); - } + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "if_then_else_expr_doesnt_warn_else_branch") @@ -402,19 +373,8 @@ function f(x) end )"); - - if (FFlag::LuauNewNonStrictNoErrorsPassingNever) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result); - } - else - { - LUAU_REQUIRE_ERROR_COUNT(3, result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result); - NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result); - } + LUAU_REQUIRE_ERROR_COUNT(1, result); + NONSTRICT_REQUIRE_FUNC_DEFINITION_ERR(Position(1, 11), "x", result); } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_sequencing_errors_2") @@ -426,18 +386,8 @@ local t = {function(x) end} )"); - if (FFlag::LuauNewNonStrictNoErrorsPassingNever) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == "Argument x with type 'unknown' is used in a way that will run time error"); - } - else - { - LUAU_REQUIRE_ERROR_COUNT(3, result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(2, 8), "abs", result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 10), "lower", result); - CHECK(toString(result.errors[2]) == "Argument x with type 'unknown' is used in a way that will run time error"); - } + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(toString(result.errors[0]) == "Argument x with type 'unknown' is used in a way that will run time error"); } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_fn_produces_error") @@ -475,14 +425,7 @@ function f(x: never) end )"); - if (FFlag::LuauNewNonStrictNoErrorsPassingNever) - LUAU_REQUIRE_NO_ERRORS(result); - else - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(3, 12), "abs", result); - NONSTRICT_REQUIRE_CHECKED_ERR(Position(5, 14), "lower", result); - } + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "function_def_if_no_else") @@ -788,8 +731,6 @@ TEST_CASE_FIXTURE(Fixture, "incomplete_function_annotation") TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_function_calls") { - ScopedFastFlag sff{FFlag::LuauNewNonStrictMoreUnknownSymbols, true}; - CheckResult result = check(Mode::Nonstrict, R"( local function foo() : () bar() @@ -804,8 +745,6 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_function_calls") TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_one_sided_conditionals") { - ScopedFastFlag sff{FFlag::LuauNewNonStrictMoreUnknownSymbols, true}; - CheckResult result = check(Mode::Nonstrict, R"( local function foo(cond) : () if cond then diff --git a/tests/NonstrictMode.test.cpp b/tests/NonstrictMode.test.cpp index b7ea3a0e..62a8f856 100644 --- a/tests/NonstrictMode.test.cpp +++ b/tests/NonstrictMode.test.cpp @@ -60,8 +60,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_the_maximum_number_of_values_the_function_coul REQUIRE_EQ("(any) -> (...any)", toString(t)); } -#if 0 -// Maybe we want this? TEST_CASE_FIXTURE(Fixture, "return_annotation_is_still_checked") { CheckResult result = check(R"( @@ -72,7 +70,6 @@ TEST_CASE_FIXTURE(Fixture, "return_annotation_is_still_checked") REQUIRE_NE(*getBuiltins()->anyType, *requireType("foo")); } -#endif TEST_CASE_FIXTURE(Fixture, "function_parameters_are_any") { diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index e4b97704..baa53f31 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -5,6 +5,7 @@ #include "Luau/AstQuery.h" #include "Luau/Common.h" #include "Luau/Type.h" +#include "ScopedFlags.h" #include "doctest.h" #include "Luau/Normalize.h" @@ -104,9 +105,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "variadic_functions_with_no_head") CHECK(!isSubtype(a, b)); } -#if 0 TEST_CASE_FIXTURE(IsSubtypeFixture, "variadic_function_with_head") { + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + check(R"( local a: (...number) -> () local b: (number, number) -> () @@ -118,7 +120,6 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "variadic_function_with_head") CHECK(!isSubtype(b, a)); CHECK(isSubtype(a, b)); } -#endif TEST_CASE_FIXTURE(IsSubtypeFixture, "union") { @@ -253,9 +254,10 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "tables") CHECK(!isSubtype(b, d)); } -#if 0 TEST_CASE_FIXTURE(IsSubtypeFixture, "table_indexers_are_invariant") { + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + check(R"( local a: {[string]: number} local b: {[string]: any} @@ -275,6 +277,8 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "table_indexers_are_invariant") TEST_CASE_FIXTURE(IsSubtypeFixture, "mismatched_indexers") { + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + check(R"( local a: {x: number} local b: {[string]: number} @@ -292,6 +296,7 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "mismatched_indexers") CHECK(isSubtype(b, c)); } +#if 0 TEST_CASE_FIXTURE(IsSubtypeFixture, "cyclic_table") { check(R"( @@ -1274,6 +1279,7 @@ do end InternalCompilerError ); } + #if 0 TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") { diff --git a/tests/Transpiler.test.cpp b/tests/PrettyPrinter.test.cpp similarity index 60% rename from tests/Transpiler.test.cpp rename to tests/PrettyPrinter.test.cpp index cd47e8ff..ba842386 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/PrettyPrinter.test.cpp @@ -1,9 +1,9 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Parser.h" +#include "Luau/PrettyPrinter.h" #include "Luau/TypeAttach.h" #include "Luau/TypeInfer.h" #include "Luau/Type.h" -#include "Luau/Transpiler.h" #include "Fixture.h" #include "ScopedFlags.h" @@ -12,7 +12,7 @@ using namespace Luau; -TEST_SUITE_BEGIN("TranspilerTests"); +TEST_SUITE_BEGIN("PrettyPrinterTests"); TEST_CASE("test_1") { @@ -26,50 +26,50 @@ local function isPortal(element) end )"; - CHECK_EQ(example, transpile(example).code); + CHECK_EQ(example, prettyPrint(example).code); } TEST_CASE("string_literals") { const std::string code = R"( local S='abcdef\n\f\a\020' )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("string_literals_containing_utf8") { const std::string code = R"( local S='lalala こんにちは' )"; // Konichiwa! - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("if_stmt_spaces_around_tokens") { const std::string one = R"( if This then Once() end)"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( if This then Once() end)"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); const std::string three = R"( if This then Once() end)"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); const std::string four = R"( if This then Once() end)"; - CHECK_EQ(four, transpile(four).code); + CHECK_EQ(four, prettyPrint(four).code); const std::string five = R"( if This then Once() else Other() end)"; - CHECK_EQ(five, transpile(five).code); + CHECK_EQ(five, prettyPrint(five).code); const std::string six = R"( if This then Once() else Other() end)"; - CHECK_EQ(six, transpile(six).code); + CHECK_EQ(six, prettyPrint(six).code); const std::string seven = R"( if This then Once() elseif true then Other() end)"; - CHECK_EQ(seven, transpile(seven).code); + CHECK_EQ(seven, prettyPrint(seven).code); const std::string eight = R"( if This then Once() elseif true then Other() end)"; - CHECK_EQ(eight, transpile(eight).code); + CHECK_EQ(eight, prettyPrint(eight).code); const std::string nine = R"( if This then Once() elseif true then Other() end)"; - CHECK_EQ(nine, transpile(nine).code); + CHECK_EQ(nine, prettyPrint(nine).code); } TEST_CASE("elseif_chains_indent_sensibly") @@ -86,118 +86,118 @@ TEST_CASE("elseif_chains_indent_sensibly") end )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } 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); + CHECK_EQ(expected, prettyPrint(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); + CHECK_EQ(expected, prettyPrint(code).code); } TEST_CASE("function_taking_ellipsis") { const std::string code = R"( function F(...) end )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("omit_decimal_place_for_integers") { const std::string code = R"( local a=5, 6, 7, 3.141, 1.1290000000000002e+45 )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("for_loop") { const std::string one = R"( for i=1,10 do end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string code = R"( for i=5,6,7 do end )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("for_loop_spaces_around_tokens") { const std::string one = R"( for index = 1, 10 do call(index) end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( for index = 1 , 10 do call(index) end )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); const std::string three = R"( for index = 1, 10 , 3 do call(index) end )"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); const std::string four = R"( for index = 1, 10 do call(index) end )"; - CHECK_EQ(four, transpile(four).code); + CHECK_EQ(four, prettyPrint(four).code); const std::string five = R"( for index = 1, 10 do call(index) end )"; - CHECK_EQ(five, transpile(five).code); + CHECK_EQ(five, prettyPrint(five).code); } TEST_CASE("for_in_loop") { const std::string code = R"( for k, v in ipairs(x)do end )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("for_in_loop_spaces_around_tokens") { const std::string one = R"( for k, v in ipairs(x) do end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( for k, v in ipairs(x) do end )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); const std::string three = R"( for k , v in ipairs(x) do end )"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); const std::string four = R"( for k, v in next , t do end )"; - CHECK_EQ(four, transpile(four).code); + CHECK_EQ(four, prettyPrint(four).code); const std::string five = R"( for k, v in ipairs(x) do end )"; - CHECK_EQ(five, transpile(five).code); + CHECK_EQ(five, prettyPrint(five).code); } TEST_CASE("for_in_single_variable") { const std::string one = R"( for key in pairs(x) do end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); } TEST_CASE("while_loop") { const std::string code = R"( while f(x)do print() end )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("while_loop_spaces_around_tokens") { const std::string one = R"( while f(x) do print() end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( while f(x) do print() end )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); const std::string three = R"( while f(x) do print() end )"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); const std::string four = R"( while f(x) do print() end )"; - CHECK_EQ(four, transpile(four).code); + CHECK_EQ(four, prettyPrint(four).code); } TEST_CASE("repeat_until_loop") { const std::string code = R"( repeat print() until f(x) )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("repeat_until_loop_condition_on_new_line") @@ -207,271 +207,271 @@ TEST_CASE("repeat_until_loop_condition_on_new_line") print() until f(x) )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("lambda") { const std::string one = R"( local p=function(o, m, g) return 77 end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( local p=function(o, m, g,...) return 77 end )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); } TEST_CASE("local_assignment") { const std::string one = R"( local x = 1 )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( local x, y, z = 1, 2, 3 )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); const std::string three = R"( local x )"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); } TEST_CASE("local_assignment_spaces_around_tokens") { const std::string one = R"( local x = 1 )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( local x = 1 )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); const std::string three = R"( local x = 1 )"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); const std::string four = R"( local x , y = 1, 2 )"; - CHECK_EQ(four, transpile(four).code); + CHECK_EQ(four, prettyPrint(four).code); const std::string five = R"( local x, y = 1, 2 )"; - CHECK_EQ(five, transpile(five).code); + CHECK_EQ(five, prettyPrint(five).code); const std::string six = R"( local x, y = 1 , 2 )"; - CHECK_EQ(six, transpile(six).code); + CHECK_EQ(six, prettyPrint(six).code); const std::string seven = R"( local x, y = 1, 2 )"; - CHECK_EQ(seven, transpile(seven).code); + CHECK_EQ(seven, prettyPrint(seven).code); } TEST_CASE("local_function") { const std::string one = R"( local function p(o, m, g) return 77 end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( local function p(o, m, g,...) return 77 end )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); } TEST_CASE("local_function_spaces_around_tokens") { const std::string one = R"( local function p(o, m, ...) end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( local function p(o, m, ...) end )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); } TEST_CASE("function") { const std::string one = R"( function p(o, m, g) return 77 end )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( function p(o, m, g,...) return 77 end )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); } TEST_CASE("function_spaces_around_tokens") { const std::string two = R"( function p(o, m, ...) end )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); const std::string three = R"( function p( o, m, ...) end )"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); const std::string four = R"( function p(o , m, ...) end )"; - CHECK_EQ(four, transpile(four).code); + CHECK_EQ(four, prettyPrint(four).code); const std::string five = R"( function p(o, m, ...) end )"; - CHECK_EQ(five, transpile(five).code); + CHECK_EQ(five, prettyPrint(five).code); const std::string six = R"( function p(o, m , ...) end )"; - CHECK_EQ(six, transpile(six).code); + CHECK_EQ(six, prettyPrint(six).code); const std::string seven = R"( function p(o, m, ...) end )"; - CHECK_EQ(seven, transpile(seven).code); + CHECK_EQ(seven, prettyPrint(seven).code); const std::string eight = R"( function p(o, m, ... ) end )"; - CHECK_EQ(eight, transpile(eight).code); + CHECK_EQ(eight, prettyPrint(eight).code); const std::string nine = R"( function p(o, m, ...) end )"; - CHECK_EQ(nine, transpile(nine).code); + CHECK_EQ(nine, prettyPrint(nine).code); } TEST_CASE("function_with_types_spaces_around_tokens") { std::string code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p (o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p (o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o : string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string , m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ... : any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any ): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any) :string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( function p(o: string, m: number, ...: any): string end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE("returns_spaces_around_tokens") { const std::string one = R"( return 1 )"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); const std::string two = R"( return 1 , 2 )"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); const std::string three = R"( return 1, 2 )"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); } TEST_CASE_FIXTURE(Fixture, "type_alias_spaces_around_tokens") { std::string code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( export type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( export type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo< X, Y, Z...> = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "type_alias_with_defaults_spaces_around_tokens") { std::string code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE("table_literals") { const std::string code = R"( local t={1, 2, 3, foo='bar', baz=99,[5.5]='five point five', 'end'} )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("more_table_literals") { const std::string code = R"( local t={['Content-Type']='text/plain'} )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_preserves_record_vs_general") { const std::string code = R"( local t={['foo']='bar',quux=42} )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_with_numeric_key") { const std::string code = R"( local t={[5]='five',[6]='six'} )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_with_keyword_key") { const std::string code = R"( local t={['nil']=nil,['true']=true} )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_closing_brace_at_correct_position") @@ -483,7 +483,7 @@ TEST_CASE("table_literal_closing_brace_at_correct_position") } )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_with_semicolon_separators") @@ -492,7 +492,7 @@ TEST_CASE("table_literal_with_semicolon_separators") local t = { x = 1; y = 2 } )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_with_trailing_separators") @@ -501,7 +501,7 @@ TEST_CASE("table_literal_with_trailing_separators") local t = { x = 1, y = 2, } )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_with_spaces_around_separator") @@ -510,7 +510,7 @@ TEST_CASE("table_literal_with_spaces_around_separator") local t = { x = 1 , y = 2 } )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_with_spaces_around_equals") @@ -519,7 +519,7 @@ TEST_CASE("table_literal_with_spaces_around_equals") local t = { x = 1 } )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("table_literal_multiline_with_indexers") @@ -531,97 +531,97 @@ TEST_CASE("table_literal_multiline_with_indexers") } )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("method_calls") { const std::string code = R"( foo.bar.baz:quux() )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("method_definitions") { const std::string code = R"( function foo.bar.baz:quux() end )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("spaces_between_keywords_even_if_it_pushes_the_line_estimation_off") { const std::string code = R"( if math.abs(raySlope) < .01 then return 0 end )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("numbers") { const std::string code = R"( local a=2510238627 )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("infinity") { const std::string code = R"( local a = 1e500 local b = 1e400 )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("numbers_with_separators") { const std::string code = R"( local a = 123_456_789 )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("hexadecimal_numbers") { const std::string code = R"( local a = 0xFFFF )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("binary_numbers") { const std::string code = R"( local a = 0b0101 )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("single_quoted_strings") { const std::string code = R"( local a = 'hello world' )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("double_quoted_strings") { const std::string code = R"( local a = "hello world" )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("simple_interp_string") { const std::string code = R"( local a = `hello world` )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("raw_strings") { const std::string code = R"( local a = [[ hello world ]] )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("raw_strings_with_blocks") { const std::string code = R"( local a = [==[ hello world ]==] )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("escaped_strings") { const std::string code = R"( local s='\\b\\t\\n\\\\' )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("escaped_strings_2") { const std::string code = R"( local s="\a\b\f\n\r\t\v\'\"\\" )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("escaped_strings_newline") @@ -630,13 +630,13 @@ TEST_CASE("escaped_strings_newline") print("foo \ bar") )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("escaped_strings_raw") { const std::string code = R"( local x = [=[\v<((do|load)file|require)\s*\(?['"]\zs[^'"]+\ze['"]]=] )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("position_correctly_updated_when_writing_multiline_string") @@ -645,91 +645,91 @@ TEST_CASE("position_correctly_updated_when_writing_multiline_string") call([[ testing ]]) )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("need_a_space_between_number_literals_and_dots") { const std::string code = R"( return point and math.ceil(point* 100000* 100)/ 100000 .. '%'or '' )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("binary_keywords") { const std::string code = "local c = a0 ._ or b0 ._"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_parentheses_no_args") { const std::string code = R"( call() )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_parentheses_one_arg") { const std::string code = R"( call(arg) )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_parentheses_multiple_args") { const std::string code = R"( call(arg1, arg3, arg3) )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_parentheses_multiple_args_no_space") { const std::string code = R"( call(arg1,arg3,arg3) )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_parentheses_multiple_args_space_before_commas") { const std::string code = R"( call(arg1 ,arg3 ,arg3) )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_spaces_before_parentheses") { const std::string code = R"( call () )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_spaces_within_parentheses") { const std::string code = R"( call( ) )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_string_double_quotes") { const std::string code = R"( call "string" )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_string_single_quotes") { const std::string code = R"( call 'string' )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_string_no_space") { const std::string code = R"( call'string' )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_table_literal") { const std::string code = R"( call { x = 1 } )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("function_call_table_literal_no_space") { const std::string code = R"( call{x=1} )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("do_blocks") @@ -745,7 +745,7 @@ TEST_CASE("do_blocks") foo2() )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("nested_do_block") @@ -758,7 +758,7 @@ TEST_CASE("nested_do_block") end )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE("emit_a_do_block_in_cases_of_potentially_ambiguous_syntax") @@ -767,7 +767,7 @@ TEST_CASE("emit_a_do_block_in_cases_of_potentially_ambiguous_syntax") f(); (g or f)() )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE_FIXTURE(Fixture, "parentheses_multiline") @@ -778,16 +778,16 @@ local test = ( ) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "stmt_semicolon") { std::string code = R"( local test = 1; )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local test = 1 ; )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon") @@ -797,7 +797,7 @@ TEST_CASE_FIXTURE(Fixture, "do_block_ending_with_semicolon") return; end; )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon") @@ -807,7 +807,7 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon") x = string.sub(x, utf8.offset(x, init)); end; )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2") @@ -815,7 +815,7 @@ TEST_CASE_FIXTURE(Fixture, "if_stmt_semicolon_2") std::string code = R"( if (t < 1) then return c/2*t*t + b end; )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon") @@ -824,7 +824,7 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_stmt_semicolon") for i,v in ... do end; )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "while_do_semicolon") @@ -833,7 +833,7 @@ TEST_CASE_FIXTURE(Fixture, "while_do_semicolon") while true do end; )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon") @@ -842,7 +842,7 @@ TEST_CASE_FIXTURE(Fixture, "function_definition_semicolon") function foo() end; )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE("roundtrip_types") @@ -864,7 +864,7 @@ TEST_CASE("roundtrip_types") ParseResult parseResult = Parser::parse(code.data(), code.size(), names, allocator, options); REQUIRE(parseResult.errors.empty()); - CHECK_EQ(code, transpileWithTypes(*parseResult.root)); + CHECK_EQ(code, prettyPrintWithTypes(*parseResult.root)); } TEST_CASE("roundtrip_generic_types") @@ -880,7 +880,7 @@ TEST_CASE("roundtrip_generic_types") ParseResult parseResult = Parser::parse(code.data(), code.size(), names, allocator, options); REQUIRE(parseResult.errors.empty()); - CHECK_EQ(code, transpileWithTypes(*parseResult.root)); + CHECK_EQ(code, prettyPrintWithTypes(*parseResult.root)); } TEST_CASE_FIXTURE(Fixture, "attach_types") @@ -907,15 +907,15 @@ TEST_CASE("a_table_key_can_be_the_empty_string") { std::string code = "local T = {[''] = true}"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } -// There's a bit of login in the transpiler that always adds a space before a dot if the previous symbol ends in a digit. +// There's a bit of login in the prettyPrintr that always adds a space before a dot if the previous symbol ends in a digit. // This was surfacing an issue where we might not insert a space after the 'local' keyword. 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); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE_FIXTURE(Fixture, "types_should_not_be_considered_cyclic_if_they_are_not_recursive") @@ -989,37 +989,37 @@ TEST_CASE_FIXTURE(Fixture, "function_type_location") CHECK_EQ(expected, actual); } -TEST_CASE_FIXTURE(Fixture, "transpile_type_assertion") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_type_assertion") { std::string code = "local a = 5 :: number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "type_assertion_spaces_around_tokens") { std::string code = "local a = 5 :: number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a = 5 :: number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_if_then_else") { std::string code = "local a = if 1 then 2 else 3"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_if_then_else_multiple_conditions") { std::string code = "local a = if 1 then 2 elseif 3 then 4 else 5"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_if_then_else_multiple_conditions_2") { std::string code = R"( local x = if yes @@ -1031,43 +1031,43 @@ TEST_CASE_FIXTURE(Fixture, "transpile_if_then_else_multiple_conditions_2") else nil )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_around_tokens") { std::string code = "local a = if 1 then 2 else 3"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 else 3"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 else 3"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 else 3"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 else 3"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 elseif 3 then 4 else 5"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 elseif 3 then 4 else 5"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 elseif 3 then 4 else 5"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 elseif 3 then 4 else 5"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 elseif 3 then 4 else 5"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); code = "local a = if 1 then 2 elseif 3 then 4 else 5"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if") @@ -1079,10 +1079,10 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_spaces_between_else_if") if c then "was c" else "was nothing!" )"; - CHECK_EQ(code, transpile(code).code); + CHECK_EQ(code, prettyPrint(code).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_import") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_type_reference_import") { fileResolver.source["game/A"] = R"( export type Type = { a: number } @@ -1094,82 +1094,82 @@ local Import = require(game.A) local a: Import.Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_type_reference_spaces_around_tokens") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_type_reference_spaces_around_tokens") { std::string code = R"( local _: Foo.Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _: Foo .Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _: Foo. Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _: Type <> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _: Type< > )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _: Type< number> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _: Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _: Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_type_annotation_spaces_around_tokens") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_type_annotation_spaces_around_tokens") { std::string code = R"( local _: Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _ : Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local _: Type )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local x: Type, y = 1 )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local x : Type, y = 1 )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_for_loop_annotation_spaces_around_tokens") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_for_loop_annotation_spaces_around_tokens") { std::string code = R"( for i: number = 1, 10 do end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( for i : number = 1, 10 do end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( for i: number = 1, 10 do end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( for x: number, y: number in ... do end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( for x : number, y: number in ... do end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( for x: number, y: number in ... do end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( for x: number, y : number in ... do end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( for x: number, y: number in ... do end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_type_packs") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_type_packs") { std::string code = R"( type Packed = (T...)->(T...) @@ -1177,224 +1177,224 @@ local a: Packed<> local b: Packed<(number, string)> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "type_packs_spaces_around_tokens") { std::string code = R"( type _ = Packed< T...> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed< ...T> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed<... T> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed< ()> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed< (string, number)> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed<( string, number)> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed<(string , number)> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed<(string, number)> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed<(string, number )> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed<(string, number) > )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed<( )> )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type _ = Packed<() > )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_union_type_nested") { std::string code = "local a: ((number)->(string))|((string)->(string))"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_2") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_union_type_nested_2") { std::string code = "local a: (number&string)|(string&boolean)"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_union_type_nested_3") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_union_type_nested_3") { std::string code = "local a: nil | (string & number)"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_intersection_type_nested") { std::string code = "local a: ((number)->(string))&((string)->(string))"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested_2") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_intersection_type_nested_2") { std::string code = "local a: (number|string)&(string|boolean)"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_intersection_type_with_function") { std::string code = "type FnB = () -> U... & T"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_leading_union_pipe") { std::string code = "local a: | string | number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: | string"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_union_spaces_around_tokens") { std::string code = "local a: string | number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string | number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_leading_intersection_ampersand") { std::string code = "local a: & string & number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: & string"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_intersection_spaces_around_tokens") { std::string code = "local a: string & number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string & number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_mixed_union_intersection") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_mixed_union_intersection") { std::string code = "local a: string | (Foo & Bar)"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string | (Foo & Bar)"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string | ( Foo & Bar)"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string | (Foo & Bar )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string & (Foo | Bar)"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string & ( Foo | Bar)"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string & (Foo | Bar )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_preserve_union_optional_style") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_preserve_union_optional_style") { std::string code = "local a: string | nil"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string?"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string???"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string? | nil"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string | nil | number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string | nil | number?"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = "local a: string? | number?"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_varargs") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_varargs") { std::string code = "local function f(...) return ... end"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "index_name_spaces_around_tokens") { std::string one = "local _ = a.name"; - CHECK_EQ(one, transpile(one, {}, true).code); + CHECK_EQ(one, prettyPrint(one, {}, true).code); std::string two = "local _ = a .name"; - CHECK_EQ(two, transpile(two, {}, true).code); + CHECK_EQ(two, prettyPrint(two, {}, true).code); std::string three = "local _ = a. name"; - CHECK_EQ(three, transpile(three, {}, true).code); + CHECK_EQ(three, prettyPrint(three, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "index_name_ends_with_digit") { std::string code = "sparkles.Color = Color3.new()"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_index_expr") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_index_expr") { std::string code = "local a = {1, 2, 3} local b = a[2]"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "index_expr_spaces_around_tokens") { std::string one = "local _ = a[2]"; - CHECK_EQ(one, transpile(one, {}, true).code); + CHECK_EQ(one, prettyPrint(one, {}, true).code); std::string two = "local _ = a [2]"; - CHECK_EQ(two, transpile(two, {}, true).code); + CHECK_EQ(two, prettyPrint(two, {}, true).code); std::string three = "local _ = a[ 2]"; - CHECK_EQ(three, transpile(three, {}, true).code); + CHECK_EQ(three, prettyPrint(three, {}, true).code); std::string four = "local _ = a[2 ]"; - CHECK_EQ(four, transpile(four, {}, true).code); + CHECK_EQ(four, prettyPrint(four, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_unary") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_unary") { std::string code = R"( local a = 1 @@ -1405,7 +1405,7 @@ local e = 'hello' local d = #e )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "unary_spaces_around_tokens") @@ -1419,7 +1419,7 @@ local _ = #e local _ = # e )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "binary_spaces_around_tokens") @@ -1430,10 +1430,10 @@ local _ = 1 +1 local _ = 1+ 1 )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_break_continue") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_break_continue") { std::string code = R"( local a, b, c @@ -1443,10 +1443,10 @@ repeat until c )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_compound_assignment") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_compound_assignment") { std::string code = R"( local a = 1 @@ -1460,73 +1460,73 @@ a ^= 7 a ..= ' - result' )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE_FIXTURE(Fixture, "compound_assignment_spaces_around_tokens") { std::string one = R"( a += 1 )"; - CHECK_EQ(one, transpile(one, {}, true).code); + CHECK_EQ(one, prettyPrint(one, {}, true).code); std::string two = R"( a += 1 )"; - CHECK_EQ(two, transpile(two, {}, true).code); + CHECK_EQ(two, prettyPrint(two, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_assign_multiple") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_assign_multiple") { std::string code = "a, b, c = 1, 2, 3"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_assign_spaces_around_tokens") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_assign_spaces_around_tokens") { std::string one = "a = 1"; - CHECK_EQ(one, transpile(one).code); + CHECK_EQ(one, prettyPrint(one).code); std::string two = "a = 1"; - CHECK_EQ(two, transpile(two).code); + CHECK_EQ(two, prettyPrint(two).code); std::string three = "a = 1"; - CHECK_EQ(three, transpile(three).code); + CHECK_EQ(three, prettyPrint(three).code); std::string four = "a , b = 1, 2"; - CHECK_EQ(four, transpile(four).code); + CHECK_EQ(four, prettyPrint(four).code); std::string five = "a, b = 1, 2"; - CHECK_EQ(five, transpile(five).code); + CHECK_EQ(five, prettyPrint(five).code); std::string six = "a, b = 1 , 2"; - CHECK_EQ(six, transpile(six).code); + CHECK_EQ(six, prettyPrint(six).code); std::string seven = "a, b = 1, 2"; - CHECK_EQ(seven, transpile(seven).code); + CHECK_EQ(seven, prettyPrint(seven).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_generic_function") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_generic_function") { std::string code = R"( local function foo(a: T, ...: S...) return 1 end local f: (T, S...)->(number) = foo )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_union_reverse") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_union_reverse") { std::string code = "local a: nil | number"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_for_in_multiple") { std::string code = "for k,v in next,{}do print(k,v) end"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_error_expr") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_error_expr") { std::string code = "local a = f:-"; @@ -1534,10 +1534,10 @@ TEST_CASE_FIXTURE(Fixture, "transpile_error_expr") auto names = AstNameTable{allocator}; ParseResult parseResult = Parser::parse(code.data(), code.size(), names, allocator, {}); - CHECK_EQ("local a = (error-expr: f:%error-id%)-(error-expr)", transpileWithTypes(*parseResult.root)); + CHECK_EQ("local a = (error-expr: f:%error-id%)-(error-expr)", prettyPrintWithTypes(*parseResult.root)); } -TEST_CASE_FIXTURE(Fixture, "transpile_error_stat") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_error_stat") { std::string code = "-"; @@ -1545,10 +1545,10 @@ TEST_CASE_FIXTURE(Fixture, "transpile_error_stat") auto names = AstNameTable{allocator}; ParseResult parseResult = Parser::parse(code.data(), code.size(), names, allocator, {}); - CHECK_EQ("(error-stat: (error-expr))", transpileWithTypes(*parseResult.root)); + CHECK_EQ("(error-stat: (error-expr))", prettyPrintWithTypes(*parseResult.root)); } -TEST_CASE_FIXTURE(Fixture, "transpile_error_type") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_error_type") { std::string code = "local a: "; @@ -1556,19 +1556,19 @@ TEST_CASE_FIXTURE(Fixture, "transpile_error_type") auto names = AstNameTable{allocator}; ParseResult parseResult = Parser::parse(code.data(), code.size(), names, allocator, {}); - CHECK_EQ("local a:%error-type%", transpileWithTypes(*parseResult.root)); + CHECK_EQ("local a:%error-type%", prettyPrintWithTypes(*parseResult.root)); } -TEST_CASE_FIXTURE(Fixture, "transpile_parse_error") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_parse_error") { std::string code = "local a = -"; - auto result = transpile(code); + auto result = prettyPrint(code); CHECK_EQ("", result.code); CHECK_EQ("Expected identifier when parsing expression, got ", result.parseError); } -TEST_CASE_FIXTURE(Fixture, "transpile_declare_global_stat") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_declare_global_stat") { std::string code = "declare _G: any"; @@ -1579,12 +1579,12 @@ TEST_CASE_FIXTURE(Fixture, "transpile_declare_global_stat") auto names = AstNameTable{allocator}; ParseResult parseResult = Parser::parse(code.data(), code.size(), names, allocator, options); - auto result = transpileWithTypes(*parseResult.root); + auto result = prettyPrintWithTypes(*parseResult.root); CHECK_EQ(result, code); } -TEST_CASE_FIXTURE(Fixture, "transpile_to_string") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_to_string") { std::string code = "local a: string = 'hello'"; @@ -1606,17 +1606,17 @@ TEST_CASE_FIXTURE(Fixture, "transpile_to_string") CHECK_EQ("'hello'", toString(expr)); } -TEST_CASE_FIXTURE(Fixture, "transpile_type_alias_default_type_parameters") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_type_alias_default_type_parameters") { std::string code = R"( type Packed = (T, U, V...)->(W...) local a: Packed )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_singleton_types") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_singleton_types") { std::string code = R"( type t1 = 'hello' @@ -1625,43 +1625,43 @@ type t3 = '' type t4 = false )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_array_types") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_array_types") { std::string code = R"( type t1 = {number} type t2 = {[string]: number} )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_for_in_multiple_types") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_for_in_multiple_types") { std::string code = "for k:string,v:boolean in next,{}do end"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_string_interp") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_string_interp") { std::string code = R"( local _ = `hello {name}` )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_string_interp_multiline") { std::string code = R"( local _ = `hello { name }!` )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_string_interp_on_new_line") { std::string code = R"( error( @@ -1669,92 +1669,92 @@ TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_on_new_line") ) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_string_interp_multiline_escape") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_string_interp_multiline_escape") { std::string code = R"( local _ = `hello \ world!` )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_string_literal_escape") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_string_literal_escape") { std::string code = R"( local _ = ` bracket = \{, backtick = \` = {'ok'} ` )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_type_functions") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_type_functions") { std::string code = R"( type function foo(arg1, arg2) if arg1 == arg2 then return arg1 end return arg2 end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_type_functions_spaces_around_tokens") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_type_functions_spaces_around_tokens") { std::string code = R"( type function foo() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type function foo() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type function foo () end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( export type function foo() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE_FIXTURE(Fixture, "transpile_typeof_spaces_around_tokens") +TEST_CASE_FIXTURE(Fixture, "prettyPrint_typeof_spaces_around_tokens") { std::string code = R"( type X = typeof(x) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type X = typeof(x) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type X = typeof (x) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type X = typeof( x) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type X = typeof(x ) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_single_quoted_string_types") +TEST_CASE("prettyPrint_single_quoted_string_types") { const std::string code = R"( type a = 'hello world' )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_double_quoted_string_types") +TEST_CASE("prettyPrint_double_quoted_string_types") { const std::string code = R"( type a = "hello world" )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_raw_string_types") +TEST_CASE("prettyPrint_raw_string_types") { std::string code = R"( type a = [[ hello world ]] )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type a = [==[ hello world ]==] )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_escaped_string_types") +TEST_CASE("prettyPrint_escaped_string_types") { const std::string code = R"( type a = "\\b\\t\\n\\\\" )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_table_semicolon_separators") +TEST_CASE("prettyPrint_type_table_semicolon_separators") { const std::string code = R"( type Foo = { @@ -1762,10 +1762,10 @@ TEST_CASE("transpile_type_table_semicolon_separators") baz: number; } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_table_access_modifiers") +TEST_CASE("prettyPrint_type_table_access_modifiers") { std::string code = R"( type Foo = { @@ -1773,76 +1773,76 @@ TEST_CASE("transpile_type_table_access_modifiers") write baz: number, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { read string } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { read [string]: number, read ["property"]: number } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_table_spaces_between_tokens") +TEST_CASE("prettyPrint_type_table_spaces_between_tokens") { std::string code = R"( type Foo = { bar: number, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { bar: number, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { bar : number, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { bar: number, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { bar: number , } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { bar: number, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { bar: number } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { [string]: number } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { [string]: number } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { [ string]: number } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { [string ]: number } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { [string] : number } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { [string]: number } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_table_preserve_original_indexer_style") +TEST_CASE("prettyPrint_type_table_preserve_original_indexer_style") { std::string code = R"( type Foo = { [number]: string } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { { number } } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_table_preserve_indexer_location") +TEST_CASE("prettyPrint_type_table_preserve_indexer_location") { std::string code = R"( type Foo = { @@ -1850,7 +1850,7 @@ TEST_CASE("transpile_type_table_preserve_indexer_location") property: number, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { @@ -1858,7 +1858,7 @@ TEST_CASE("transpile_type_table_preserve_indexer_location") [number]: string, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = { @@ -1867,10 +1867,10 @@ TEST_CASE("transpile_type_table_preserve_indexer_location") property2: number, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_table_preserve_property_definition_style") +TEST_CASE("prettyPrint_type_table_preserve_property_definition_style") { std::string code = R"( type Foo = { @@ -1878,10 +1878,10 @@ TEST_CASE("transpile_type_table_preserve_property_definition_style") ['$$typeof2']: string, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens") +TEST_CASE("prettyPrint_type_table_string_properties_spaces_between_tokens") { std::string code = R"( type Foo = { @@ -1889,25 +1889,25 @@ TEST_CASE("transpile_type_table_string_properties_spaces_between_tokens") ['$$typeof2' ]: string, } )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_types_preserve_parentheses_style") +TEST_CASE("prettyPrint_types_preserve_parentheses_style") { std::string code = R"( type Foo = number )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (number) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = ((number)) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = ( (number) ) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("fuzzer_transpile_with_zero_location") +TEST_CASE("fuzzer_prettyPrint_with_zero_location") { const std::string example = R"( if _ then @@ -1926,205 +1926,205 @@ end auto names = std::make_unique(*allocator); ParseResult parseResult = Parser::parse(example.data(), example.size(), *names, *allocator, parseOptions); - transpileWithTypes(*parseResult.root); + prettyPrintWithTypes(*parseResult.root); } -TEST_CASE("transpile_type_function_unnamed_arguments") +TEST_CASE("prettyPrint_type_function_unnamed_arguments") { std::string code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (string) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (string, number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = ( string, number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (string , number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (string, number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (string, number ) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (string, number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (string, number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_function_named_arguments") +TEST_CASE("prettyPrint_type_function_named_arguments") { std::string code = R"( type Foo = (x: string) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (x: string, y: number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = ( x: string, y: number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (x : string, y: number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (x: string, y: number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (x: string, y: number) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (number, info: string) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = (first: string, second: string, ...string) -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_function_generics") +TEST_CASE("prettyPrint_type_function_generics") { std::string code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = < X, Y, Z...>() -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_type_function_return_types") +TEST_CASE("prettyPrint_type_function_return_types") { std::string code = R"( type Foo = () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> ( ) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> string )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> (string) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> (string) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> ...any )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> ...any )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> ... any )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> (...any) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> ( string, number) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> (string , number) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> (string, number) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> (string, number ) )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_chained_function_types") +TEST_CASE("prettyPrint_chained_function_types") { std::string code = R"( type Foo = () -> () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( type Foo = () -> () -> () )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_CASE("fuzzer_nil_optional") { const std::string code = R"( local x: nil? )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } -TEST_CASE("transpile_function_attributes") +TEST_CASE("prettyPrint_function_attributes") { std::string code = R"( @native function foo() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( @native local function foo() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( @checked local function foo() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( local foo = @native function() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( @native function foo:bar() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); code = R"( @native @checked function foo:bar() end )"; - CHECK_EQ(code, transpile(code, {}, true).code); + CHECK_EQ(code, prettyPrint(code, {}, true).code); } TEST_SUITE_END(); diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index 2f40e177..c6d2e080 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -23,7 +23,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauIceLess) -LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) @@ -336,10 +335,7 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(1.0)) TEST_CASE_FIXTURE(Fixture, "limit_number_of_dynamically_created_constraints") { - ScopedFastFlag sff[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauLimitDynamicConstraintSolving3, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; constexpr const char* src = R"( type Array = {T} @@ -370,7 +366,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauLimitDynamicConstraintSolving3, true}, {FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true}, }; @@ -593,4 +588,36 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unification_runs_a_limited_number_of_iterati LUAU_REQUIRE_ERROR(result, UnificationTooComplex); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fusion_normalization_spin" * doctest::timeout(1.0)) +{ + LUAU_REQUIRE_ERRORS(check(R"( +type Task = unknown +type Constructors = unknown +export type Scope = {Task} & Constructors +export type DeriveScopeConstructor = ((Scope) -> Scope) + & ((Scope, A & {}) -> Scope) + & ((Scope, A & {}, B & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}, E & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}, E & {}, F & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}, E & {}, F & {}, G & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}, E & {}, F & {}, G & {}, H & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}, E & {}, F & {}, G & {}, H & {}, I & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}, E & {}, F & {}, G & {}, H & {}, I & {}, J & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}, E & {}, F & {}, G & {}, H & {}, I & {}, J & {}, K & {}) -> Scope) + & ((Scope, A & {}, B & {}, C & {}, D & {}, E & {}, F & {}, G & {}, H & {}, I & {}, J & {}, K & {}, L & {}) -> Scope) + +local deriveScopeImpl : DeriveScopeConstructor = (nil :: any) + +local function innerScope( + existing: Types.Scope, + ...: {[unknown]: unknown} +): any + local new = deriveScopeImpl(existing, ...) +end + + )")); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 1db63372..6724a73a 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -19,7 +19,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) @@ -1407,7 +1407,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "({ x: T }) -> T <: ({ method: ({ x: T } TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance") { ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; TypeId longTy = arena.addType( UnionType{ @@ -1441,11 +1441,19 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type } } +TEST_CASE_FIXTURE(SubtypeFixture, "(number, number...) numberType}, arena.addTypePack(VariadicTypePack{builtinTypes->numberType})); + TypePackId rightTp = arena.addTypePack({builtinTypes->numberType}, arena.addTypePack(VariadicTypePack{builtinTypes->stringType})); + + CHECK_IS_NOT_SUBTYPE(leftTp, rightTp); +} + TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_check_for_error_suppression_in_union_type_path") { ScopedFastFlag sffs[] = { {FFlag::LuauConsiderErrorSuppressionInTypes, true}, - {FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}, + {FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}, }; TypeId subTy = arena.addType(UnionType{{getBuiltins()->numberType, getBuiltins()->errorType}}); @@ -1473,7 +1481,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_check_for_error_suppress { ScopedFastFlag sffs[] = { {FFlag::LuauConsiderErrorSuppressionInTypes, true}, - {FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}, + {FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}, }; TypeId subTy = getBuiltins()->booleanType; @@ -1552,7 +1560,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(x: T, y: T, f: (T, T) -> T) -> T <: (numb TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> ((A...) -> ()) <: (string -> ((number) -> ())") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; // (A...) -> () TypeId asToNothing = arena.addType(FunctionType({}, {genericAs}, genericAs, getBuiltins()->emptyTypePack, std::nullopt, false)); diff --git a/tests/TopoSort.test.cpp b/tests/TopoSort.test.cpp index 02ff6cbe..ad3959c9 100644 --- a/tests/TopoSort.test.cpp +++ b/tests/TopoSort.test.cpp @@ -1,6 +1,5 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/TopoSortStatements.h" -#include "Luau/Transpiler.h" #include "Fixture.h" diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index a18deabd..0dfcd71b 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -15,7 +15,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauRefineOccursCheckDirectRecursion) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauRawGetHandlesNil) @@ -1870,10 +1869,7 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_stuck_tf_is_stuck") // We want to make sure that `t1 where t1 = refine` becomes `unknown`, not a cyclic type. TEST_CASE_FIXTURE(TFFixture, "reduce_degenerate_refinement") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauRefineOccursCheckDirectRecursion, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; TypeId root = arena->addType(BlockedType{}); TypeId refinement = arena->addType( diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 7e063fa1..5a5eaf74 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -340,10 +340,9 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ CHECK_EQ(getBuiltins()->numberType, tm->givenType); } -#if 0 // CLI-169898: temporarily disabled for stack overflow in unoptimized build -// Check that recursive intersection type doesn't generate an OOM TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom") { + // Check that recursive intersection type doesn't generate an OOM CheckResult result = check(R"( function _(l0:(t0)&((t0)&(((t0)&((t0)->()))->(typeof(_),typeof(# _)))),l39,...):any end @@ -351,7 +350,6 @@ TEST_CASE_FIXTURE(Fixture, "cli_38393_recursive_intersection_oom") _(_) )"); } -#endif TEST_CASE_FIXTURE(Fixture, "type_alias_fwd_declaration_is_precise") { diff --git a/tests/TypeInfer.annotations.test.cpp b/tests/TypeInfer.annotations.test.cpp index f3ec6d76..3b81b3d0 100644 --- a/tests/TypeInfer.annotations.test.cpp +++ b/tests/TypeInfer.annotations.test.cpp @@ -885,12 +885,13 @@ TEST_CASE_FIXTURE(Fixture, "instantiate_type_fun_should_not_trip_rbxassert") LUAU_REQUIRE_NO_ERRORS(result); } -#if 0 // This is because, after visiting all nodes in a block, we check if each type alias still points to a FreeType. // Doing it that way is wrong, but I also tried to make typeof(x) return a BoundType, with no luck. // Not important enough to fix today. TEST_CASE_FIXTURE(Fixture, "pulling_a_type_from_value_dont_falsely_create_occurs_check_failed") { + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + CheckResult result = check(R"( function f(x) type T = typeof(x) @@ -899,7 +900,6 @@ TEST_CASE_FIXTURE(Fixture, "pulling_a_type_from_value_dont_falsely_create_occurs LUAU_REQUIRE_NO_ERRORS(result); } -#endif TEST_CASE_FIXTURE(Fixture, "occurs_check_on_cyclic_union_type") { diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index d4492e9e..bb4fd860 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -15,7 +15,6 @@ using namespace Luau; using std::nullopt; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauMorePreciseExternTableRelation) LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAG(LuauExternTableIndexersIntersect) @@ -921,10 +920,7 @@ end TEST_CASE_FIXTURE(Fixture, "extern_type_check_missing_key") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauMorePreciseExternTableRelation, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; loadDefinition(R"( declare extern type Foobar with @@ -957,10 +953,7 @@ TEST_CASE_FIXTURE(Fixture, "extern_type_check_missing_key") TEST_CASE_FIXTURE(Fixture, "extern_type_check_present_key_in_superclass") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauMorePreciseExternTableRelation, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; loadDefinition(R"( declare extern type FoobarParent with @@ -993,10 +986,7 @@ TEST_CASE_FIXTURE(Fixture, "extern_type_check_present_key_in_superclass") TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_becomes_never") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauMorePreciseExternTableRelation, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; loadDefinition(R"( declare extern type Foobar with @@ -1021,10 +1011,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_becomes_never") TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_becomes_intersection") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauMorePreciseExternTableRelation, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; loadDefinition(R"( declare extern type Foobar with @@ -1048,7 +1035,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_superset") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauPushTypeConstraint2, true}, - {FFlag::LuauMorePreciseExternTableRelation, true}, }; loadDefinition(R"( @@ -1070,10 +1056,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_superset") TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_idempotent") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauMorePreciseExternTableRelation, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; loadDefinition(R"( declare extern type Foobar with @@ -1096,7 +1079,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_intersect_with_table_indexer") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauMorePreciseExternTableRelation, true}, {FFlag::LuauExternTableIndexersIntersect, true}, }; @@ -1115,7 +1097,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_with_indexer_intersect_table") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauMorePreciseExternTableRelation, true}, {FFlag::LuauExternTableIndexersIntersect, true}, }; diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 66f8950f..a4ea5cab 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -27,7 +27,7 @@ LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauFixNilRightPad) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) @@ -2394,7 +2394,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}, + {FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}, }; @@ -3345,4 +3345,21 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1854") )")); } +TEST_CASE_FIXTURE(Fixture, "cli_119545_pass_lambda_inside_table") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + type foo1 = { foo: (number) -> () } + type foo2 = { read foo: (number) -> () } + local function bar1(foo: foo1) end + local function bar2(foo: foo2) end + + local baz = { foo = function(number: number) end, } + bar1(baz) + bar2(baz) + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index f2a04e9c..17923dd9 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -12,10 +12,9 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAG(LuauSubtypingUnionsAndIntersectionsInGenericBounds) @@ -1880,7 +1879,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "follow_bound_type_packs_in_generic_type_visitor") { - ScopedFastFlag _{FFlag::LuauContainsAnyGenericFollowBeforeChecking, true}; // Note: we just need this test not to crash check(R"( function (_(_,_,nil)) @@ -1895,7 +1893,7 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: (A) -> ()): () end @@ -1908,7 +1906,7 @@ f(g) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_2") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: (number) -> (number)): () end @@ -1922,7 +1920,7 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_3") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: (B...) -> B...): () end @@ -1937,7 +1935,7 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_4") { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: (A...) -> A...): () end @@ -1951,7 +1949,7 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_5") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: (number) -> number): () end @@ -1965,7 +1963,7 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_6") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: (...number) -> number): () end @@ -1979,7 +1977,7 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_7") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: () -> ()): () end @@ -1993,7 +1991,7 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_8") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: () -> ()): () end @@ -2008,7 +2006,7 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "nested_generic_packs") { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( type T = (A...) -> ((A...) -> ()) @@ -2060,7 +2058,7 @@ TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error_1") TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall_should_work_with_generics") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag _{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( --!strict diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index cf3ed7a4..f57cd4aa 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -11,7 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -851,7 +851,7 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f() @@ -1118,7 +1118,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag _{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f() @@ -1149,7 +1149,7 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") { - ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; ScopedFastFlag sff2{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; CheckResult result = check(R"( @@ -1183,7 +1183,7 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f() diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index f38fe997..fc98c218 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -15,7 +15,6 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTINT(LuauSolverConstraintLimit) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) @@ -847,7 +846,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "internal_types_are_scrubbed_from_module") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, - {FFlag::LuauLimitDynamicConstraintSolving3, true}, }; fileResolver.source["game/A"] = R"( @@ -866,7 +864,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "internal_type_errors_are_only_reported_once" ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, - {FFlag::LuauLimitDynamicConstraintSolving3, true}, }; fileResolver.source["game/A"] = R"( @@ -882,7 +879,7 @@ return function(): { X: _luau_blocked_type, Y: _luau_blocked_type } return nil : TEST_CASE_FIXTURE(BuiltinsFixture, "scrub_unsealed_tables") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauLimitDynamicConstraintSolving3, true}}; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastInt sfi{FInt::LuauSolverConstraintLimit, 5}; diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index f192df60..b1524e43 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -1407,4 +1407,24 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_indexer_satisfies_reading_property" CHECK_EQ("{ read X: number }", toString(err->wantedType)); } +TEST_CASE_FIXTURE(Fixture, "unification_inferring_never_for_refined_param") +{ + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function __remove(__: number?) end + + function __removeItem(self, itemId: number) + local index = self.getItem(itemId) + if index then + __remove(index) + end + end + )")); + + // TODO CLI-168953: This is not correct. We should not be inferring `never` + // for the second return type of `getItem`. + CHECK_EQ("({ read getItem: (number) -> (never, ...unknown) }, number) -> ()", toString(requireType("__removeItem"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index c3ba0873..d555c9dd 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -14,7 +14,6 @@ LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauRefineNoRefineAlways) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) -LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) @@ -1716,9 +1715,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "asserting_non_existent_propertie TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_is_not_instance_or_else_not_part") { - // CLI-117135 - RefinementTests.x_is_not_instance_or_else_not_part not correctly applying refinements to a function parameter - if (FFlag::LuauSolverV2) - return; + ScopedFastFlag sff{FFlag::LuauRefineDistributesOverUnions, true}; CheckResult result = check(R"( local function f(x: Part | Folder | string) @@ -2738,6 +2735,23 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_single") CHECK_EQ("The type function is not precise enough for us to determine the appropriate result type of this call.", toString(result.errors[0])); } +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cli_140033_refine_union_of_extern_types") +{ + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + local function getImageLabel(vars: { Instance }): Folder | Part | nil + for _, item in vars do + if item:IsA("Folder") or item:IsA("Part") then + return item + end + end + return nil + end + )")); + + CHECK_EQ("Folder | Part", toString(requireTypeAtPosition({5, 28}))); +} + TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_union") { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; @@ -3017,4 +3031,28 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "inline_if_conditional_context") )")); } +TEST_CASE_FIXTURE(Fixture, "oss_1517_equality_doesnt_add_nil") +{ + LUAU_REQUIRE_NO_ERRORS(check(R"( + type MyType = { + data: any + } + + local function createMyType(): MyType + local obj = { data = {} } + return obj + end + + local function testTypeInference() + local a: MyType = createMyType() + local b: MyType = createMyType() + + if a == b then + local c: MyType = b + local value = b.data + end + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index c6da1abc..81586bca 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -664,6 +664,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "singletons_stick_around_under_assignment") TEST_CASE_FIXTURE(Fixture, "tagged_union_in_ternary") { + ScopedFastFlag _{FFlag::LuauPushTypeConstraint2, true}; + LUAU_REQUIRE_NO_ERRORS(check(R"( type Result = { type: "ok", value: unknown } | { type: "error" } diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 1a7dd51c..ae95b52a 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -23,11 +23,9 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) -LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) -LUAU_FASTFLAG(LuauAllowMixedTables) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) @@ -5649,20 +5647,15 @@ TEST_CASE_FIXTURE(Fixture, "large_table_inference_does_not_bleed") CHECK(err.location.begin.line == 2); } - -#if 0 - -TEST_CASE_FIXTURE(Fixture, "extremely_large_table" * doctest::timeout(2.0)) +TEST_CASE_FIXTURE(Fixture, "extremely_large_table" * doctest::timeout(1.0)) { ScopedFastFlag _{FFlag::LuauSolverV2, true}; - const std::string source = "local res = {\n" + rep("\"foo\",\n", 100'000) + "}"; + const std::string source = "local res = {\n" + rep("\"foo\",\n", 10'000) + "}"; LUAU_REQUIRE_NO_ERRORS(check(source)); CHECK_EQ("{string}", toString(requireType("res"), {true})); } -#endif - TEST_CASE_FIXTURE(Fixture, "oss_1838") { LUAU_REQUIRE_NO_ERRORS(check(R"( @@ -5976,8 +5969,6 @@ TEST_CASE_FIXTURE(Fixture, "free_types_with_sealed_table_upper_bounds_can_still_ TEST_CASE_FIXTURE(Fixture, "mixed_tables_are_ok_when_explicit") { - ScopedFastFlag _{FFlag::LuauAllowMixedTables, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( local foo: { [number | string]: unknown } = { Key = "sorry", @@ -5989,8 +5980,6 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_are_ok_when_explicit") TEST_CASE_FIXTURE(Fixture, "mixed_tables_are_ok_for_any_key") { - ScopedFastFlag _{FFlag::LuauAllowMixedTables, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( local foo: { [any]: unknown } = { Key = "sorry", diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index fa1e2bd2..dc11ba5e 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -27,7 +27,6 @@ LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) -LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) @@ -300,18 +299,13 @@ TEST_CASE_FIXTURE(Fixture, "occurs_check_does_not_recurse_forever_if_asked_to_tr LUAU_REQUIRE_NO_ERRORS(result); } -#if 0 -// CLI-29798 TEST_CASE_FIXTURE(Fixture, "crazy_complexity") { CheckResult result = check(R"( --!nonstrict A:A():A():A():A():A():A():A():A():A():A():A() )"); - - MESSAGE("OK! Allocated ", typeChecker.types.size(), " types"); } -#endif TEST_CASE_FIXTURE(Fixture, "type_errors_infer_types") { @@ -2092,8 +2086,6 @@ 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[] = { @@ -2111,8 +2103,6 @@ local _ )")); } -#endif - TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_missing_follow_table_freeze") { LUAU_REQUIRE_ERRORS(check(R"( @@ -2437,10 +2427,7 @@ end then _._G else ... TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauInferActualIfElseExprType2, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( --!strict @@ -2469,10 +2456,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim") TEST_CASE_FIXTURE(Fixture, "if_then_else_bidirectional_inference") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauInferActualIfElseExprType2, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( type foo = { @@ -2490,10 +2474,7 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_bidirectional_inference") TEST_CASE_FIXTURE(Fixture, "if_then_else_two_errors") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauInferActualIfElseExprType2, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( type foo = { @@ -2677,27 +2658,6 @@ TEST_CASE_FIXTURE(Fixture, "nested_functions_can_depend_on_outer_generics") CHECK("number" == toString(tm->givenType)); } -TEST_CASE_FIXTURE(Fixture, "avoid_unification_inferring_never_for_refined_param") -{ - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTryToOptimizeSetTypeUnification, true}, - }; - - LUAU_REQUIRE_NO_ERRORS(check(R"( - local function __remove(__: number?) end - - function __removeItem(self, itemId: number) - local index = self.getItem(itemId) - if index then - __remove(index) - end - end - )")); - - CHECK_EQ("({ read getItem: (number) -> (number?, ...unknown) }, number) -> ()", toString(requireType("__removeItem"))); -} - TEST_CASE_FIXTURE(BuiltinsFixture, "unterminated_function_body_causes_constraint_generator_crash") { ScopedFastFlag _{FFlag::LuauDontReferenceScopePtrFromHashTable, true}; diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index d6a95c42..fba8485b 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -12,7 +12,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAG(LuauRemoveGenericErrorForParams) LUAU_FASTFLAG(LuauAddErrorCaseForIncompatibleTypePacks) TEST_SUITE_BEGIN("TypePackTests"); @@ -259,7 +258,6 @@ TEST_CASE_FIXTURE(Fixture, "variadic_pack_syntax") CHECK_EQ(toString(requireType("foo")), "(...number) -> ()"); } -#if 0 TEST_CASE_FIXTURE(Fixture, "type_pack_hidden_free_tail_infinite_growth") { CheckResult result = check(R"( @@ -276,7 +274,6 @@ end LUAU_REQUIRE_ERRORS(result); } -#endif TEST_CASE_FIXTURE(Fixture, "variadic_argument_tail") { @@ -616,8 +613,6 @@ type Other = Packed TEST_CASE_FIXTURE(Fixture, "type_alias_instantiated_but_missing_parameter_list") { - ScopedFastFlag sff{FFlag::LuauRemoveGenericErrorForParams, true}; - CheckResult result = check(R"( type Packed = (T...) -> T... local a: Packed @@ -808,8 +803,6 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors3") TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors4") { - ScopedFastFlag sff{FFlag::LuauRemoveGenericErrorForParams, true}; - CheckResult result = check(R"( type Packed = (T) -> T local a: Packed diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index ded28cdd..6fd2a584 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -122,6 +122,7 @@ TEST_CASE_FIXTURE(TypeStateFixture, "refine_a_local_and_then_assign_it") LUAU_REQUIRE_NO_ERRORS(result); } +#endif TEST_CASE_FIXTURE(TypeStateFixture, "assign_a_local_and_then_refine_it") { @@ -138,7 +139,6 @@ TEST_CASE_FIXTURE(TypeStateFixture, "assign_a_local_and_then_refine_it") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK("Type 'string' could not be converted into 'never'" == toString(result.errors[0])); } -#endif TEST_CASE_FIXTURE(TypeStateFixture, "recursive_local_function") { diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp index 72cd16e2..2e837f82 100644 --- a/tests/TypePath.test.cpp +++ b/tests/TypePath.test.cpp @@ -19,12 +19,12 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps); LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3); -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) struct TypePathFixture : Fixture { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; TypeArena arena; const DenseHashMap emptyMap_DEPRECATED{nullptr}; }; @@ -32,7 +32,7 @@ struct TypePathFixture : Fixture struct TypePathBuiltinsFixture : BuiltinsFixture { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; TypeArena arena; const DenseHashMap emptyMap_DEPRECATED{nullptr}; }; @@ -131,7 +131,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "table_property") TEST_CASE_FIXTURE(ExternTypeFixture, "class_property") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; // Force this here because vector2InstanceType won't get initialized until the frontend has been forced getFrontend(); TypeArena arena; @@ -231,7 +231,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; getFrontend(); TypeArena arena; diff --git a/tests/Unifier2.test.cpp b/tests/Unifier2.test.cpp index 8ae7ce94..a8d7fdfd 100644 --- a/tests/Unifier2.test.cpp +++ b/tests/Unifier2.test.cpp @@ -133,9 +133,8 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "unify_binds_free_supertype_tail_pack") CHECK("(number <: 'a)" == toString(freeAndFree)); } -TEST_CASE_FIXTURE(Unifier2Fixture, "unify_avoid_free_type_intersection_in_ub_from_union") +TEST_CASE_FIXTURE(Unifier2Fixture, "unify_free_type_intersection_in_ub_from_union") { - ScopedFastFlag _{FFlag::LuauTryToOptimizeSetTypeUnification, true}; // 'a TypeId freeTy = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType}); // 'a & ~(false?) @@ -144,12 +143,12 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "unify_avoid_free_type_intersection_in_ub_fro TypeId superTy = arena.addType(UnionType{{builtinTypes.numberType, builtinTypes.nilType}}); u2.unify(subTy, superTy); - CHECK("('a <: number?)" == toString(freeTy)); + // TODO CLI-168953: This is not correct. We should not be unifying to `never` here. + CHECK("('a <: never)" == toString(freeTy)); } -TEST_CASE_FIXTURE(Unifier2Fixture, "unify_unfortunate_free_type_lb_from_intersection") +TEST_CASE_FIXTURE(Unifier2Fixture, "unify_free_type_lb_from_intersection") { - ScopedFastFlag _{FFlag::LuauTryToOptimizeSetTypeUnification, true}; // 'a TypeId freeTy = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType}); // 'a? @@ -158,12 +157,7 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "unify_unfortunate_free_type_lb_from_intersec TypeId subTy = arena.addType(IntersectionType{{builtinTypes.stringType, arena.addType(NegationType{arena.addType(SingletonType{StringSingleton{"foo"}})})}}); u2.unify(subTy, superTy); - - // TODO CLI-168953: This is not correct. The lower bound should be - // string & ~"foo", but we're making this tradeoff for now to avoid - // the more common case where the upper bound becomes `never` (see - // previous test case). - CHECK("(string | ~\"foo\" <: 'a)" == toString(freeTy)); + CHECK("(string & ~\"foo\" <: 'a)" == toString(freeTy)); } TEST_SUITE_END(); diff --git a/tests/conformance/native.luau b/tests/conformance/native.luau index 39be8afc..59d90edb 100644 --- a/tests/conformance/native.luau +++ b/tests/conformance/native.luau @@ -248,6 +248,13 @@ end assert(pcall(fuzzfail24) == false) +local function fuzzfail25(...) + _,_.n0,_,_,_[_ // _],_[_ + _ % _],_,_,_,_,l255,_,_,_,_,_,_,_,_,_[_],_,_,_ = table.insert("") + _,_.n0,_,_,_[_ // _],_[_ + _ % _],_,_,_,_,l255,_,_,_,_,_,_,_,_,_[_],_,_,_ = table.insert("") +end + +assert(pcall(fuzzfail25) == false) + local function arraySizeInv1() local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true} diff --git a/tests/conformance/vector_library.luau b/tests/conformance/vector_library.luau index ea3123a4..8dc4029e 100644 --- a/tests/conformance/vector_library.luau +++ b/tests/conformance/vector_library.luau @@ -178,6 +178,7 @@ assert(vector.lerp(vector.one, vector.zero, 0.5) == vector.create(0.5, 0.5, 0.5) assert(vector.lerp(vector.one, vector.zero, 0.5) == vector.create(0.5, 0.5, 0.5)) assert(vector.lerp(vector.create(1, 2, 3), vector.create(3, 2, 1), 0.5) == vector.create(2, 2, 2)) assert(vector.lerp(vector.create(-1, -1, -3), vector.zero, 0.5) == vector.create(-0.5, -0.5, -1.5)) +assert(vector.lerp(vector.create(10, 8, 3), vector.create(-3, 8, 3), 0.43) == vector.create(4.41, 8, 3)) assert(ecall(function() return vector.lerp(vector.zero, vector.one, vector.one) end) == "invalid argument #3 to 'lerp' (number expected, got vector)") -- validate component access From 6169fa15d85c766c108c90eb68289021b5eee705 Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 17 Oct 2025 10:25:51 -0700 Subject: [PATCH 06/10] Sync to upstream/release/696 (#2050) Hey everyone. In preparation for the next phase of the New Type Solver rollout, we have another round of important bugfixes. We've also got a few improvements to the new require alias machinery and a little optimization to native codegen. ## New Type Solver * Fix a case where Luau would erroneously simplify `{} & { p: number | string }` to `number`. * Fix a crash that could occur within generic substitution. TODO: Sora to help word this. * Fix a case where Luau would infer a spurious union type as the result of a `setmetatable` call. * Fix https://github.com/luau-lang/luau/issues/1803 * Improve bidirectional inference of table literals in the case that the expected type has an indexer. ## Luau.Require * Zero-initialize `luarequire_Configuration` function pointers before user initialization * Error if ambiguity is detected during alias discovery ## Native Codegen * Do not replace known constant value with a load propagation in STORE_TVALUE --------- Co-authored-by: Annie Tang Co-authored-by: Hunter Goldstein Co-authored-by: Sora Kanosue Co-authored-by: Varun Saini Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/BuiltinDefinitions.h | 1 + Analysis/include/Luau/ConstraintSolver.h | 7 +- Analysis/include/Luau/Simplify.h | 7 +- Analysis/include/Luau/Subtyping.h | 4 +- Analysis/include/Luau/TypeIds.h | 2 + Analysis/src/BuiltinDefinitions.cpp | 12 + Analysis/src/BuiltinTypeFunctions.cpp | 3 +- Analysis/src/ConstraintGenerator.cpp | 43 +- Analysis/src/ConstraintSolver.cpp | 119 +++- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 57 +- Analysis/src/Simplify.cpp | 539 ++++++++++++++---- Analysis/src/Subtyping.cpp | 39 +- Analysis/src/TableLiteralInference.cpp | 17 +- Analysis/src/TypeChecker2.cpp | 50 +- Analysis/src/TypeIds.cpp | 17 + CLI/src/ReplRequirer.cpp | 1 - CLI/src/VfsNavigator.cpp | 4 +- CodeGen/src/CodeGenContext.cpp | 30 +- CodeGen/src/IrLoweringA64.cpp | 17 +- CodeGen/src/IrLoweringX64.cpp | 14 +- CodeGen/src/IrRegAllocA64.cpp | 120 +--- CodeGen/src/IrRegAllocX64.cpp | 9 +- CodeGen/src/IrValueLocationTracking.cpp | 11 +- CodeGen/src/OptimizeConstProp.cpp | 3 +- Common/include/Luau/ExperimentalFlags.h | 1 - Require/Navigator/src/RequireNavigator.cpp | 7 +- Require/Runtime/src/Require.cpp | 7 +- tests/Conformance.test.cpp | 5 - tests/IrBuilder.test.cpp | 44 ++ tests/RequireByString.test.cpp | 19 + tests/Simplify.test.cpp | 13 +- tests/TypeFunction.test.cpp | 9 - tests/TypeInfer.provisional.test.cpp | 19 + tests/TypeInfer.refinements.test.cpp | 103 ++++ tests/TypeInfer.singletons.test.cpp | 58 ++ tests/TypeInfer.test.cpp | 12 + .../with_config/parent_ambiguity/.luaurc | 5 + .../with_config/parent_ambiguity/folder.luau | 0 .../parent_ambiguity/folder/requirer.luau | 1 + .../with_config/parent_ambiguity/foo.luau | 1 + 40 files changed, 1012 insertions(+), 418 deletions(-) create mode 100644 tests/require/with_config/parent_ambiguity/.luaurc create mode 100644 tests/require/with_config/parent_ambiguity/folder.luau create mode 100644 tests/require/with_config/parent_ambiguity/folder/requirer.luau create mode 100644 tests/require/with_config/parent_ambiguity/foo.luau diff --git a/Analysis/include/Luau/BuiltinDefinitions.h b/Analysis/include/Luau/BuiltinDefinitions.h index c582d42b..f19e8a2e 100644 --- a/Analysis/include/Luau/BuiltinDefinitions.h +++ b/Analysis/include/Luau/BuiltinDefinitions.h @@ -88,6 +88,7 @@ TypeId getGlobalBinding(GlobalTypes& globals, const std::string& name); bool matchSetMetatable(const AstExprCall& call); bool matchTableFreeze(const AstExprCall& call); bool matchAssert(const AstExprCall& call); +bool matchTypeOf(const AstExprCall& call); // Returns `true` if the function should introduce typestate for its first argument. bool shouldTypestateForFirstArgument(const AstExprCall& call); diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 1b01de4f..77c67331 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -462,7 +462,12 @@ struct ConstraintSolver void reproduceConstraints(NotNull scope, const Location& location, const Substitution& subst); TypeId simplifyIntersection(NotNull scope, Location location, TypeId left, TypeId right); - TypeId simplifyIntersection(NotNull scope, Location location, std::set parts); + + // Clip with LuauSimplifyIntersectionNoTreeSet + TypeId simplifyIntersection_DEPRECATED(NotNull scope, Location location, std::set parts); + + TypeId simplifyIntersection(NotNull scope, Location location, TypeIds parts); + TypeId simplifyUnion(NotNull scope, Location location, TypeId left, TypeId right); TypePackId anyifyModuleReturnTypePackGenerics(TypePackId tp); diff --git a/Analysis/include/Luau/Simplify.h b/Analysis/include/Luau/Simplify.h index ebb14259..579cb835 100644 --- a/Analysis/include/Luau/Simplify.h +++ b/Analysis/include/Luau/Simplify.h @@ -5,6 +5,7 @@ #include "Luau/DenseHash.h" #include "Luau/NotNull.h" #include "Luau/TypeFwd.h" +#include "Luau/TypeIds.h" #include #include @@ -22,7 +23,11 @@ struct SimplifyResult }; SimplifyResult simplifyIntersection(NotNull builtinTypes, NotNull arena, TypeId left, TypeId right); -SimplifyResult simplifyIntersection(NotNull builtinTypes, NotNull arena, std::set parts); + +// Clip with LuauSimplifyIntersectionNoTreeSet +SimplifyResult simplifyIntersection_DEPRECATED(NotNull builtinTypes, NotNull arena, std::set parts); + +SimplifyResult simplifyIntersection(NotNull builtinTypes, NotNull arena, TypeIds parts); SimplifyResult simplifyUnion(NotNull builtinTypes, NotNull arena, TypeId left, TypeId right); diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index a7a4e61c..55ea2420 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -168,7 +168,9 @@ struct SubtypingEnvironment // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance std::optional applyMappedGenerics_DEPRECATED(NotNull builtinTypes, NotNull arena, TypeId ty); - const TypeId* tryFindSubstitution(TypeId ty) const; + // TODO: Clip with LuauTryFindSubstitutionReturnOptional + const TypeId* tryFindSubstitution_DEPRECATED(TypeId ty) const; + std::optional tryFindSubstitution(TypeId ty) const; // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance const SubtypingResult* tryFindSubtypingResult(std::pair subAndSuper) const; diff --git a/Analysis/include/Luau/TypeIds.h b/Analysis/include/Luau/TypeIds.h index 87602d6f..9058f7b4 100644 --- a/Analysis/include/Luau/TypeIds.h +++ b/Analysis/include/Luau/TypeIds.h @@ -40,6 +40,8 @@ class TypeIds void retain(const TypeIds& tys); void clear(); + void clearWithoutRealloc(); + TypeId front() const; iterator begin(); iterator end(); diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index d3106f8a..7376e3cb 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -1880,6 +1880,18 @@ bool matchAssert(const AstExprCall& call) return true; } +bool matchTypeOf(const AstExprCall& call) +{ + if (call.args.size != 1) + return false; + + const AstExprGlobal* funcAsGlobal = call.func->as(); + if (!funcAsGlobal || (funcAsGlobal->name != "typeof" && funcAsGlobal->name != "type")) + return false; + + return true; +} + bool shouldTypestateForFirstArgument(const AstExprCall& call) { // TODO: magic function for setmetatable and assert and then add them diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index 1cfd5b00..2b0e3cc1 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -26,7 +26,6 @@ LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways) LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauEGFixGenericsList) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) -LUAU_FASTFLAG(LuauRawGetHandlesNil) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauBuiltinTypeFunctionsArentGlobal) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) @@ -2326,7 +2325,7 @@ TypeFunctionReductionResult indexFunctionImpl( for (TypeId ty : *typesToFind) if (!tblIndexInto(ty, *tablesIter, properties, ctx, isRaw)) { - if (FFlag::LuauRawGetHandlesNil && isRaw) + if (isRaw) properties.insert(ctx->builtins->nilType); else return {std::nullopt, Reduction::Erroneous, {}, {}}; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 2e0727f6..63eb3d3f 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -54,6 +54,8 @@ LUAU_FASTFLAGVARIABLE(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauDontReferenceScopePtrFromHashTable) LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) +LUAU_FASTFLAGVARIABLE(LuauMetatableAvoidSingletonUnion) +LUAU_FASTFLAGVARIABLE(LuauAddRefinementToAssertions) namespace Luau { @@ -710,7 +712,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat discriminants.clear(); return resultType; } - + }; for (auto& [def, partition] : refinements) @@ -2471,9 +2473,19 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* { expectedType = expectedTypesForCall[i]; } - auto [ty, refinement] = check(scope, arg, expectedType, /*forceSingleton*/ false, /*generalize*/ false); - args.push_back(ty); - argumentRefinements.push_back(refinement); + if (FFlag::LuauAddRefinementToAssertions && i == 0 && matchAssert(*call)) + { + InConditionalContext flipper{&typeContext}; + auto [ty, refinement] = check(scope, arg, expectedType, /*forceSingleton*/ false, /*generalize*/ false); + args.push_back(ty); + argumentRefinements.push_back(refinement); + } + else + { + auto [ty, refinement] = check(scope, arg, expectedType, /*forceSingleton*/ false, /*generalize*/ false); + args.push_back(ty); + argumentRefinements.push_back(refinement); + } } else { @@ -2535,13 +2547,26 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* if (isTableUnion(target)) { - const UnionType* targetUnion = get(target); - std::vector newParts; + if (FFlag::LuauMetatableAvoidSingletonUnion) + { + const UnionType* targetUnion = get(target); + UnionBuilder ub{arena, builtinTypes}; - for (TypeId ty : targetUnion) - newParts.push_back(arena->addType(MetatableType{ty, mt})); + for (TypeId ty : targetUnion) + ub.add(arena->addType(MetatableType{ty, mt})); - resultTy = arena->addType(UnionType{std::move(newParts)}); + resultTy = ub.build(); + } + else + { + const UnionType* targetUnion = get(target); + std::vector newParts; + + for (TypeId ty : targetUnion) + newParts.push_back(arena->addType(MetatableType{ty, mt})); + + resultTy = arena->addType(UnionType{std::move(newParts)}); + } } else resultTy = arena->addType(MetatableType{target, mt}); diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 20f165e7..5e952ee7 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -52,6 +52,7 @@ LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAGVARIABLE(LuauScopedSeenSetInLookupTableProp) LUAU_FASTFLAGVARIABLE(LuauIterableBindNotUnify) LUAU_FASTFLAGVARIABLE(LuauAvoidOverloadSelectionForFunctionType) +LUAU_FASTFLAG(LuauSimplifyIntersectionNoTreeSet) namespace Luau { @@ -2412,46 +2413,95 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull(lhsType)) { - std::set parts; - for (TypeId t : lhsIntersection) + if (FFlag::LuauSimplifyIntersectionNoTreeSet) { - if (auto tbl = getMutable(follow(t))) + + TypeIds parts; + + for (TypeId t : lhsIntersection) { - if (tbl->indexer) + if (auto tbl = getMutable(follow(t))) { - unify(constraint, indexType, tbl->indexer->indexType); - parts.insert(tbl->indexer->indexResultType); - } + if (tbl->indexer) + { + unify(constraint, indexType, tbl->indexer->indexType); + parts.insert(tbl->indexer->indexResultType); + } - if (tbl->state == TableState::Unsealed || tbl->state == TableState::Free) + if (tbl->state == TableState::Unsealed || tbl->state == TableState::Free) + { + tbl->indexer = TableIndexer{indexType, rhsType}; + parts.insert(rhsType); + } + } + else if (auto cls = get(follow(t))) { - tbl->indexer = TableIndexer{indexType, rhsType}; - parts.insert(rhsType); + while (true) + { + if (cls->indexer) + { + unify(constraint, indexType, cls->indexer->indexType); + parts.insert(cls->indexer->indexResultType); + break; + } + + if (cls->parent) + cls = get(cls->parent); + else + break; + } } } - else if (auto cls = get(follow(t))) + + TypeId res = simplifyIntersection(constraint->scope, constraint->location, std::move(parts)); + + unify(constraint, rhsType, res); + } + else + { + + std::set parts; + + for (TypeId t : lhsIntersection) { - while (true) + if (auto tbl = getMutable(follow(t))) { - if (cls->indexer) + if (tbl->indexer) { - unify(constraint, indexType, cls->indexer->indexType); - parts.insert(cls->indexer->indexResultType); - break; + unify(constraint, indexType, tbl->indexer->indexType); + parts.insert(tbl->indexer->indexResultType); } - if (cls->parent) - cls = get(cls->parent); - else - break; + if (tbl->state == TableState::Unsealed || tbl->state == TableState::Free) + { + tbl->indexer = TableIndexer{indexType, rhsType}; + parts.insert(rhsType); + } + } + else if (auto cls = get(follow(t))) + { + while (true) + { + if (cls->indexer) + { + unify(constraint, indexType, cls->indexer->indexType); + parts.insert(cls->indexer->indexResultType); + break; + } + + if (cls->parent) + cls = get(cls->parent); + else + break; + } } } - } - TypeId res = simplifyIntersection(constraint->scope, constraint->location, std::move(parts)); + TypeId res = simplifyIntersection_DEPRECATED(constraint->scope, constraint->location, std::move(parts)); - unify(constraint, rhsType, res); + unify(constraint, rhsType, res); + } } // Other types do not support index assignment. @@ -3805,11 +3855,11 @@ TypeId ConstraintSolver::simplifyIntersection(NotNull scope, Location loc return ::Luau::simplifyIntersection(builtinTypes, arena, left, right).result; } -TypeId ConstraintSolver::simplifyIntersection(NotNull scope, Location location, std::set parts) +TypeId ConstraintSolver::simplifyIntersection(NotNull scope, Location location, TypeIds parts) { if (FFlag::DebugLuauEqSatSimplification) { - TypeId ty = arena->addType(IntersectionType{std::vector(parts.begin(), parts.end())}); + TypeId ty = arena->addType(IntersectionType{parts.take()}); std::optional res = eqSatSimplify(simplifier, ty); if (!res) @@ -3824,6 +3874,25 @@ TypeId ConstraintSolver::simplifyIntersection(NotNull scope, Location loc return ::Luau::simplifyIntersection(builtinTypes, arena, std::move(parts)).result; } +TypeId ConstraintSolver::simplifyIntersection_DEPRECATED(NotNull scope, Location location, std::set parts) +{ + if (FFlag::DebugLuauEqSatSimplification) + { + TypeId ty = arena->addType(IntersectionType{std::vector(parts.begin(), parts.end())}); + + std::optional res = eqSatSimplify(simplifier, ty); + if (!res) + return ty; + + for (TypeId ty : res->newTypeFunctions) + pushConstraint(scope, location, ReduceConstraint{ty}); + + return res->result; + } + else + return ::Luau::simplifyIntersection_DEPRECATED(builtinTypes, arena, std::move(parts)).result; +} + TypeId ConstraintSolver::simplifyUnion(NotNull scope, Location location, TypeId left, TypeId right) { if (FFlag::DebugLuauEqSatSimplification) diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 12b8c8af..4469a084 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,7 +2,6 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp2) -LUAU_FASTFLAGVARIABLE(LuauRawGetHandlesNil) namespace Luau { @@ -60,60 +59,6 @@ declare function unpack(tab: {V}, i: number?, j: number?): ...V )BUILTIN_SRC"; -// Will be removed when LuauRawGetHandlesNil flag gets clipped -static constexpr const char* kBuiltinDefinitionBaseSrc_DEPRECATED = R"BUILTIN_SRC( - -@checked declare function require(target: any): any - -@checked declare function getfenv(target: any): { [string]: any } - -declare _G: any -declare _VERSION: string - -declare function gcinfo(): number - -declare function print(...: T...) - -declare function type(value: T): string -declare function typeof(value: T): string - --- `assert` has a magic function attached that will give more detailed type information -declare function assert(value: T, errorMessage: string?): T -declare function error(message: T, level: number?): never - -declare function tostring(value: T): string -declare function tonumber(value: T, radix: number?): number? - -declare function rawequal(a: T1, b: T2): boolean -declare function rawget(tab: {[K]: V}, k: K): V -declare function rawset(tab: {[K]: V}, k: K, v: V): {[K]: V} -declare function rawlen(obj: {[K]: V} | string): number - -declare function setfenv(target: number | (T...) -> R..., env: {[string]: any}): ((T...) -> R...)? - -declare function ipairs(tab: {V}): (({V}, number) -> (number?, V), {V}, number) - -declare function pcall(f: (A...) -> R..., ...: A...): (boolean, R...) - --- FIXME: The actual type of `xpcall` is: --- (f: (A...) -> R1..., err: (E) -> R2..., A...) -> (true, R1...) | (false, R2...) --- Since we can't represent the return value, we use (boolean, R1...). -declare function xpcall(f: (A...) -> R1..., err: (E) -> R2..., ...: A...): (boolean, R1...) - --- `select` has a magic function attached to provide more detailed type information -declare function select(i: string | number, ...: A...): ...any - --- FIXME: This type is not entirely correct - `loadstring` returns a function or --- (nil, string). -declare function loadstring(src: string, chunkname: string?): (((A...) -> any)?, string?) - -@checked declare function newproxy(mt: boolean?): any - --- Cannot use `typeof` here because it will produce a polytype when we expect a monotype. -declare function unpack(tab: {V}, i: number?, j: number?): ...V - -)BUILTIN_SRC"; - static constexpr const char* kBuiltinDefinitionBit32Src = R"BUILTIN_SRC( declare bit32: { @@ -379,7 +324,7 @@ declare vector: { std::string getBuiltinDefinitionSource() { - std::string result = FFlag::LuauRawGetHandlesNil ? kBuiltinDefinitionBaseSrc : kBuiltinDefinitionBaseSrc_DEPRECATED; + std::string result = kBuiltinDefinitionBaseSrc; result += kBuiltinDefinitionBit32Src; result += kBuiltinDefinitionMathSrc; diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 64c43ed4..4b73eb9e 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -26,6 +26,7 @@ LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAGVARIABLE(LuauSimplifyRefinementOfReadOnlyProperty) LUAU_FASTFLAGVARIABLE(LuauExternTableIndexersIntersect) LUAU_FASTFLAGVARIABLE(LuauSimplifyMoveTableProps) +LUAU_FASTFLAGVARIABLE(LuauSimplifyIntersectionNoTreeSet) namespace Luau { @@ -43,7 +44,10 @@ struct TypeSimplifier TypeId mkNegation(TypeId ty) const; - TypeId intersectFromParts(std::set parts); + // Clip with LuauSimplifyIntersectionNoTreeSet + TypeId intersectFromParts_DEPRECATED(std::set parts); + + TypeId intersectFromParts(TypeIds parts); TypeId intersectUnionWithType(TypeId left, TypeId right); @@ -699,7 +703,7 @@ TypeId TypeSimplifier::mkNegation(TypeId ty) const return result; } -TypeId TypeSimplifier::intersectFromParts(std::set parts) +TypeId TypeSimplifier::intersectFromParts_DEPRECATED(std::set parts) { if (0 == parts.size()) return builtinTypes->neverType; @@ -809,6 +813,129 @@ TypeId TypeSimplifier::intersectFromParts(std::set parts) return arena->addType(IntersectionType{std::vector{begin(newParts), end(newParts)}}); } +namespace { + +enum class Inhabited { + Yes, + No +}; + +Inhabited intersectOneWithIntersection(TypeSimplifier& simplifier, TypeIds& source, TypeIds& dest, TypeId candidate) +{ + if (dest.count(candidate) > 0) + return Inhabited::Yes; + + if (auto itv = get(candidate)) + { + for (auto subPart : itv) + { + if (intersectOneWithIntersection(simplifier, source, dest, subPart) == Inhabited::No) + return Inhabited::No; + } + + return Inhabited::Yes; + } + + if (source.empty()) + { + dest.insert(candidate); + return Inhabited::Yes; + } + + for (TypeId ty : source) + { + // All examples are presented in `candidate & ty` format. + switch (relate(candidate, ty)) + { + case Relation::Disjoint: + // If the candidate and a member of the intersection are + // disjoint, then the entire intersection is uninhabited, for + // example: + // + // boolean & string + return Inhabited::No; + case Relation::Subset: + // If the candidate is a _subset_ of the member of the + // intersection, then replace this entry if we don't already + // have the candidate in the set, e.g.: + // + // true & boolean + dest.insert(candidate); + break; + case Relation::Coincident: + case Relation::Superset: + // If the candidate and a member of the intersection are + // coincident, or the incoming part is a superset, then do + // nothing, e.g.: + // + // boolean & true + // boolean & boolean + dest.insert(ty); + break; + case Relation::Intersects: + { + // If the candidate and a member of the intersection may + // intersect, then attempt to replace the member with + // a simpler type, e.g.: + // + // boolean & ~(false?) + if (std::optional simplified = simplifier.basicIntersect(candidate, ty)) + dest.insert(*simplified); + else + { + dest.insert(candidate); + dest.insert(ty); + } + break; + } + } + } + + return Inhabited::Yes; +} +} + +TypeId TypeSimplifier::intersectFromParts(TypeIds parts) +{ + if (parts.size() == 0) + return builtinTypes->unknownType; + + if (parts.size() == 1) + return *parts.begin(); + + TypeIds source; + TypeIds dest; + + source.reserve(parts.size()); + dest.reserve(parts.size()); + + for (TypeId part : parts) + { + // We use the candidate, part, to construct a new intersection by + // intersecting every element in source against part, and then + // inserting it into dest. + if (intersectOneWithIntersection(*this, source, dest, part) == Inhabited::No) + return builtinTypes->neverType; + + // At this point, source will contain some intersection, and dest will contain + // the intersection we want to retain for the next interation. + + // We swap the two, so that we can use `source` as the basis for the next iteration. + std::swap(source, dest); + + // Then clear the `dest` without reallocating the underlying hashtable, to avoid + // allocating. + dest.clearWithoutRealloc(); + } + + IntersectionBuilder ib(arena, builtinTypes); + for (auto ty : source) + ib.add(ty); + + return ib.build(); + +} + TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right) { const UnionType* leftUnion = get(left); @@ -958,58 +1085,117 @@ TypeId TypeSimplifier::intersectNegatedUnion(TypeId left, TypeId right) const UnionType* negatedUnion = get(negatedTy); LUAU_ASSERT(negatedUnion); - bool changed = false; - std::set newParts; - - for (TypeId part : negatedUnion) + if (FFlag::LuauSimplifyIntersectionNoTreeSet) { - Relation r = relate(part, right); - switch (r) + + bool changed = false; + TypeIds newParts; + + for (TypeId part : negatedUnion) { - case Relation::Disjoint: - // If A is disjoint from B, then ~A & B is just B. - // - // ~(false?) & true - // (~false & true) & (~nil & true) - // true & true - newParts.insert(right); - break; - case Relation::Coincident: - // If A is coincident with or a superset of B, then ~A & B is never. - // - // ~(false?) & false - // (~false & false) & (~nil & false) - // never & false - // - // fallthrough - case Relation::Superset: - // If A is a superset of B, then ~A & B is never. + Relation r = relate(part, right); + switch (r) + { + case Relation::Disjoint: + // If A is disjoint from B, then ~A & B is just B. + // + // ~(false?) & true + // (~false & true) & (~nil & true) + // true & true + newParts.insert(right); + break; + case Relation::Coincident: + // If A is coincident with or a superset of B, then ~A & B is never. // - // ~(boolean | nil) & true - // (~boolean & true) & (~boolean & nil) - // never & nil - return builtinTypes->neverType; - case Relation::Subset: - case Relation::Intersects: - // If A is a subset of B, then ~A & B is a bit more complicated. We need to think harder. + // ~(false?) & false + // (~false & false) & (~nil & false) + // never & false // - // ~(false?) & boolean - // (~false & boolean) & (~nil & boolean) - // true & boolean - TypeId simplified = intersectTypeWithNegation(mkNegation(part), right); - changed |= simplified != right; - if (get(simplified)) - changed = true; - else - newParts.insert(simplified); - break; + // fallthrough + case Relation::Superset: + // If A is a superset of B, then ~A & B is never. + // + // ~(boolean | nil) & true + // (~boolean & true) & (~boolean & nil) + // never & nil + return builtinTypes->neverType; + case Relation::Subset: + case Relation::Intersects: + // If A is a subset of B, then ~A & B is a bit more complicated. We need to think harder. + // + // ~(false?) & boolean + // (~false & boolean) & (~nil & boolean) + // true & boolean + TypeId simplified = intersectTypeWithNegation(mkNegation(part), right); + changed |= simplified != right; + if (get(simplified)) + changed = true; + else + newParts.insert(simplified); + break; + } } - } - if (!changed) - return right; - else + if (!changed) + return right; + return intersectFromParts(std::move(newParts)); + } + else + { + + bool changed = false; + std::set newParts; + + for (TypeId part : negatedUnion) + { + Relation r = relate(part, right); + switch (r) + { + case Relation::Disjoint: + // If A is disjoint from B, then ~A & B is just B. + // + // ~(false?) & true + // (~false & true) & (~nil & true) + // true & true + newParts.insert(right); + break; + case Relation::Coincident: + // If A is coincident with or a superset of B, then ~A & B is never. + // + // ~(false?) & false + // (~false & false) & (~nil & false) + // never & false + // + // fallthrough + case Relation::Superset: + // If A is a superset of B, then ~A & B is never. + // + // ~(boolean | nil) & true + // (~boolean & true) & (~boolean & nil) + // never & nil + return builtinTypes->neverType; + case Relation::Subset: + case Relation::Intersects: + // If A is a subset of B, then ~A & B is a bit more complicated. We need to think harder. + // + // ~(false?) & boolean + // (~false & boolean) & (~nil & boolean) + // true & boolean + TypeId simplified = intersectTypeWithNegation(mkNegation(part), right); + changed |= simplified != right; + if (get(simplified)) + changed = true; + else + newParts.insert(simplified); + break; + } + } + + if (!changed) + return right; + return intersectFromParts_DEPRECATED(std::move(newParts)); + } } std::optional TypeSimplifier::basicIntersectWithTruthy(TypeId target) const @@ -1108,50 +1294,100 @@ TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right) if (auto ut = get(negatedTy)) { - // ~(A | B) & C - // (~A & C) & (~B & C) - bool changed = false; - std::set newParts; - - for (TypeId part : ut) + if (FFlag::LuauSimplifyIntersectionNoTreeSet) { - Relation r = relate(part, right); - switch (r) + // ~(A | B) & C + // (~A & C) & (~B & C) + bool changed = false; + TypeIds newParts; + + for (TypeId part : ut) { - case Relation::Coincident: - // ~(false?) & nil - // (~false & nil) & (~nil & nil) - // nil & never - // - // fallthrough - case Relation::Superset: - // ~(boolean | string) & true - // (~boolean & true) & (~boolean & string) - // never & string + Relation r = relate(part, right); + switch (r) + { + case Relation::Coincident: + // ~(false?) & nil + // (~false & nil) & (~nil & nil) + // nil & never + // + // fallthrough + case Relation::Superset: + // ~(boolean | string) & true + // (~boolean & true) & (~boolean & string) + // never & string - return builtinTypes->neverType; + return builtinTypes->neverType; - case Relation::Disjoint: - // ~nil & boolean - newParts.insert(right); - break; + case Relation::Disjoint: + // ~nil & boolean + newParts.insert(right); + break; - case Relation::Subset: - // ~false & boolean - // fallthrough - case Relation::Intersects: - // FIXME: The mkNegation here is pretty unfortunate. - // Memoizing this will probably be important. - changed = true; - newParts.insert(right); - newParts.insert(mkNegation(part)); + case Relation::Subset: + // ~false & boolean + // fallthrough + case Relation::Intersects: + // FIXME: The mkNegation here is pretty unfortunate. + // Memoizing this will probably be important. + changed = true; + newParts.insert(right); + newParts.insert(mkNegation(part)); + } } - } - if (!changed) - return right; - else + if (!changed) + return right; + return intersectFromParts(std::move(newParts)); + } + else + { + // ~(A | B) & C + // (~A & C) & (~B & C) + bool changed = false; + std::set newParts; + + for (TypeId part : ut) + { + Relation r = relate(part, right); + switch (r) + { + case Relation::Coincident: + // ~(false?) & nil + // (~false & nil) & (~nil & nil) + // nil & never + // + // fallthrough + case Relation::Superset: + // ~(boolean | string) & true + // (~boolean & true) & (~boolean & string) + // never & string + + return builtinTypes->neverType; + + case Relation::Disjoint: + // ~nil & boolean + newParts.insert(right); + break; + + case Relation::Subset: + // ~false & boolean + // fallthrough + case Relation::Intersects: + // FIXME: The mkNegation here is pretty unfortunate. + // Memoizing this will probably be important. + changed = true; + newParts.insert(right); + newParts.insert(mkNegation(part)); + } + } + + if (!changed) + return right; + + return intersectFromParts_DEPRECATED(std::move(newParts)); + } } if (auto rightUnion = get(right)) @@ -1279,49 +1515,101 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right) return arena->addType(IntersectionType{{left, right}}); } - bool changed = false; - std::set newParts; - - for (TypeId part : leftIntersection) + if (FFlag::LuauSimplifyIntersectionNoTreeSet) { - Relation r = relate(part, right); - switch (r) + bool changed = false; + TypeIds newParts; + + for (TypeId part : leftIntersection) { - case Relation::Disjoint: - return builtinTypes->neverType; - case Relation::Coincident: - newParts.insert(part); - continue; - case Relation::Subset: - newParts.insert(part); - continue; - case Relation::Superset: - newParts.insert(right); - changed = true; - continue; - default: - newParts.insert(part); - newParts.insert(right); - changed = true; - continue; + Relation r = relate(part, right); + switch (r) + { + case Relation::Disjoint: + return builtinTypes->neverType; + case Relation::Coincident: + newParts.insert(part); + continue; + case Relation::Subset: + newParts.insert(part); + continue; + case Relation::Superset: + newParts.insert(right); + changed = true; + continue; + default: + newParts.insert(part); + newParts.insert(right); + changed = true; + continue; + } } - } - // It is sometimes the case that an intersection operation will result in - // clipping a free type from the result. - // - // eg (number & 'a) & string --> never - // - // We want to only report the free types that are part of the result. - for (TypeId part : newParts) - { - if (isTypeVariable(part)) - blockedTypes.insert(part); + // It is sometimes the case that an intersection operation will result in + // clipping a free type from the result. + // + // eg (number & 'a) & string --> never + // + // We want to only report the free types that are part of the result. + for (TypeId part : newParts) + { + if (isTypeVariable(part)) + blockedTypes.insert(part); + } + + if (!changed) + return left; + + return intersectFromParts(std::move(newParts)); } + else + { - if (!changed) - return left; - return intersectFromParts(std::move(newParts)); + bool changed = false; + std::set newParts; + + for (TypeId part : leftIntersection) + { + Relation r = relate(part, right); + switch (r) + { + case Relation::Disjoint: + return builtinTypes->neverType; + case Relation::Coincident: + newParts.insert(part); + continue; + case Relation::Subset: + newParts.insert(part); + continue; + case Relation::Superset: + newParts.insert(right); + changed = true; + continue; + default: + newParts.insert(part); + newParts.insert(right); + changed = true; + continue; + } + } + + // It is sometimes the case that an intersection operation will result in + // clipping a free type from the result. + // + // eg (number & 'a) & string --> never + // + // We want to only report the free types that are part of the result. + for (TypeId part : newParts) + { + if (isTypeVariable(part)) + blockedTypes.insert(part); + } + + if (!changed) + return left; + + return intersectFromParts_DEPRECATED(std::move(newParts)); + } } std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) @@ -2072,7 +2360,7 @@ SimplifyResult simplifyIntersection(NotNull builtinTypes, NotNull< return SimplifyResult{res, std::move(s.blockedTypes)}; } -SimplifyResult simplifyIntersection(NotNull builtinTypes, NotNull arena, std::set parts) +SimplifyResult simplifyIntersection(NotNull builtinTypes, NotNull arena, TypeIds parts) { TypeSimplifier s{builtinTypes, arena}; @@ -2081,6 +2369,15 @@ SimplifyResult simplifyIntersection(NotNull builtinTypes, NotNull< return SimplifyResult{res, std::move(s.blockedTypes)}; } +SimplifyResult simplifyIntersection_DEPRECATED(NotNull builtinTypes, NotNull arena, std::set parts) +{ + TypeSimplifier s{builtinTypes, arena}; + + TypeId res = s.intersectFromParts_DEPRECATED(std::move(parts)); + + return SimplifyResult{res, std::move(s.blockedTypes)}; +} + SimplifyResult simplifyUnion(NotNull builtinTypes, NotNull arena, TypeId left, TypeId right) { TypeSimplifier s{builtinTypes, arena}; diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index dceb8210..dd7147bf 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -35,6 +35,7 @@ LUAU_FASTFLAGVARIABLE(LuauIndexInMetatableSubtyping) LUAU_FASTFLAGVARIABLE(LuauSubtypingPackRecursionLimits) LUAU_FASTFLAGVARIABLE(LuauSubtypingPrimitiveAndGenericTableTypes) LUAU_FASTFLAGVARIABLE(LuauPassBindableGenericsByReference) +LUAU_FASTFLAGVARIABLE(LuauTryFindSubstitutionReturnOptional) namespace Luau { @@ -625,17 +626,32 @@ std::optional SubtypingEnvironment::applyMappedGenerics_DEPRECATED(NotNu return amg.substitute(ty); } -const TypeId* SubtypingEnvironment::tryFindSubstitution(TypeId ty) const +const TypeId* SubtypingEnvironment::tryFindSubstitution_DEPRECATED(TypeId ty) const { + LUAU_ASSERT(!FFlag::LuauTryFindSubstitutionReturnOptional); + if (auto it = substitutions.find(ty)) return it; if (parent) - return parent->tryFindSubstitution(ty); + return parent->tryFindSubstitution_DEPRECATED(ty); return nullptr; } +std::optional SubtypingEnvironment::tryFindSubstitution(TypeId ty) const +{ + LUAU_ASSERT(FFlag::LuauTryFindSubstitutionReturnOptional); + + if (const TypeId* it = substitutions.find(ty)) + return *it; + + if (parent) + return parent->tryFindSubstitution(ty); + + return std::nullopt; +} + const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pair subAndSuper) const { if (FFlag::LuauSubtypingGenericsDoesntUseVariance) @@ -977,11 +993,22 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub subTy = follow(subTy); superTy = follow(superTy); - if (const TypeId* subIt = env.tryFindSubstitution(subTy); subIt && *subIt) - subTy = *subIt; + if (FFlag::LuauTryFindSubstitutionReturnOptional) + { + if (std::optional subIt = env.tryFindSubstitution(subTy); subIt && *subIt) + subTy = *subIt; + + if (std::optional superIt = env.tryFindSubstitution(superTy); superIt && *superIt) + subTy = *superIt; + } + else + { + if (const TypeId* subIt = env.tryFindSubstitution_DEPRECATED(subTy); subIt && *subIt) + subTy = *subIt; - if (const TypeId* superIt = env.tryFindSubstitution(superTy); superIt && *superIt) - superTy = *superIt; + if (const TypeId* superIt = env.tryFindSubstitution_DEPRECATED(superTy); superIt && *superIt) + superTy = *superIt; + } const SubtypingResult* cachedResult = resultCache.find({subTy, superTy}); if (cachedResult) diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index ed078877..27d8efdd 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintIntersection) LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintSingleton) +LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintIndexer) namespace Luau { @@ -256,15 +257,23 @@ struct BidirectionalTypePusher { // If we have some type: // - // { [string]: T } + // { [T]: U } // // ... that we're trying to push into ... // // { foo = bar } // - // Then the intent is probably to push `T` into `bar`. - if (expectedTableTy->indexer && fastIsSubtype(solver->builtinTypes->stringType, expectedTableTy->indexer->indexType)) - (void)pushType(expectedTableTy->indexer->indexResultType, item.value); + // Then the intent is probably to push `U` into `bar`. + if (FFlag::LuauPushTypeConstraintIndexer) + { + if (expectedTableTy->indexer) + (void)pushType(expectedTableTy->indexer->indexResultType, item.value); + } + else + { + if (expectedTableTy->indexer && fastIsSubtype(solver->builtinTypes->stringType, expectedTableTy->indexer->indexType)) + (void)pushType(expectedTableTy->indexer->indexResultType, item.value); + } // If it's just an extra property and the expected type // has no indexer, there's no work to do here. diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 1816722a..b9728c30 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -4,6 +4,7 @@ #include "Luau/Ast.h" #include "Luau/AstUtils.h" #include "Luau/AstQuery.h" +#include "Luau/BuiltinDefinitions.h" #include "Luau/Common.h" #include "Luau/DcrLogger.h" #include "Luau/DenseHash.h" @@ -48,6 +49,8 @@ LUAU_FASTFLAGVARIABLE(LuauAddConditionalContextForTernary) LUAU_FASTFLAGVARIABLE(LuauCheckForInWithSubtyping2) LUAU_FASTFLAGVARIABLE(LuauNoOrderingTypeFunctions) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) +LUAU_FASTFLAG(LuauSimplifyIntersectionNoTreeSet) +LUAU_FASTFLAG(LuauAddRefinementToAssertions) namespace Luau { @@ -1765,13 +1768,36 @@ void TypeChecker2::visitCall(AstExprCall* call) void TypeChecker2::visit(AstExprCall* call) { + std::optional flipper; + if (FFlag::LuauAddRefinementToAssertions) + { + // We want to preserve the existing conditional context if we are in a `typeof` call. + if (!matchTypeOf(*call)) + flipper.emplace(&typeContext, TypeContext::Default); + + visit(call->func, ValueContext::RValue); + } + else { InConditionalContext flipper(&typeContext, TypeContext::Default); visit(call->func, ValueContext::RValue); } - for (AstExpr* arg : call->args) - visit(arg, ValueContext::RValue); + if (FFlag::LuauAddRefinementToAssertions && matchAssert(*call) && call->args.size > 0) + { + { + InConditionalContext flipper(&typeContext); + visit(call->args.data[0], ValueContext::RValue); + } + + for (size_t i = 1; i < call->args.size; ++i) + visit(call->args.data[i], ValueContext::RValue); + } + else + { + for (AstExpr* arg : call->args) + visit(arg, ValueContext::RValue); + } visitCall(call); } @@ -3200,10 +3226,22 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT { // If we _happen_ to have an intersection of tables, let's try to // construct it and use it as the input to this algorithm. - std::set parts{begin(itv), end(itv)}; - TypeId simplified = simplifyIntersection(builtinTypes, NotNull{&module->internalTypes}, std::move(parts)).result; - if (is(simplified)) - return testPotentialLiteralIsSubtype(expr, simplified); + if (FFlag::LuauSimplifyIntersectionNoTreeSet) + { + TypeIds parts; + parts.insert(begin(itv), end(itv)); + TypeId simplified = simplifyIntersection(builtinTypes, NotNull{&module->internalTypes}, std::move(parts)).result; + if (is(simplified)) + return testPotentialLiteralIsSubtype(expr, simplified); + } + else + { + + std::set parts{begin(itv), end(itv)}; + TypeId simplified = simplifyIntersection_DEPRECATED(builtinTypes, NotNull{&module->internalTypes}, std::move(parts)).result; + if (is(simplified)) + return testPotentialLiteralIsSubtype(expr, simplified); + } } } diff --git a/Analysis/src/TypeIds.cpp b/Analysis/src/TypeIds.cpp index 1366a35e..56666cd7 100644 --- a/Analysis/src/TypeIds.cpp +++ b/Analysis/src/TypeIds.cpp @@ -35,6 +35,23 @@ void TypeIds::clear() hash = 0; } +void TypeIds::clearWithoutRealloc() +{ + // Our goal for this function is to logically clear the TypeIds without + // resetting either of the underlying allocations, so that we can reuse + // this TypeIds in a loop without paying allocation costs every time. + // + // According to the C++ standard, clear does not affect the result of capacity, + // so we should be able to guarantee that the underlying data is not reallocted. + // https://en.cppreference.com/w/cpp/container/vector/clear.html + order.clear(); + // `DenseHashTable` exposes a "threshold" for clearing the underlying data. + // We just pass in max `size_t` here to ensure that the underlying data is + // never cleared. + types.clear(std::numeric_limits::max()); + hash = 0; +} + TypeId TypeIds::front() const { return order.at(0); diff --git a/CLI/src/ReplRequirer.cpp b/CLI/src/ReplRequirer.cpp index 969f0faf..32da1c84 100644 --- a/CLI/src/ReplRequirer.cpp +++ b/CLI/src/ReplRequirer.cpp @@ -208,7 +208,6 @@ void requireConfigInit(luarequire_Configuration* config) config->get_chunkname = get_chunkname; config->get_loadname = get_loadname; config->get_cache_key = get_cache_key; - config->get_alias = nullptr; config->get_config = get_config; config->load = load; } diff --git a/CLI/src/VfsNavigator.cpp b/CLI/src/VfsNavigator.cpp index 5bea9e71..55daf8e7 100644 --- a/CLI/src/VfsNavigator.cpp +++ b/CLI/src/VfsNavigator.cpp @@ -188,7 +188,9 @@ NavigationStatus VfsNavigator::toParent() modulePath = normalizePath(modulePath + "/.."); absoluteModulePath = normalizePath(absoluteModulePath + "/.."); - return updateRealPaths(); + // There is no ambiguity when navigating up in a tree. + NavigationStatus status = updateRealPaths(); + return status == NavigationStatus::Ambiguous ? NavigationStatus::Success : status; } NavigationStatus VfsNavigator::toChild(const std::string& name) diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp index 678d4f19..75404766 100644 --- a/CodeGen/src/CodeGenContext.cpp +++ b/CodeGen/src/CodeGenContext.cpp @@ -15,7 +15,6 @@ LUAU_FASTINTVARIABLE(LuauCodeGenBlockSize, 4 * 1024 * 1024) LUAU_FASTINTVARIABLE(LuauCodeGenMaxTotalSize, 256 * 1024 * 1024) -LUAU_FASTFLAGVARIABLE(LuauCodeGenUnassignedBcTargetAbort) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodeGenDisableWithNoReentry, false) namespace Luau @@ -447,31 +446,18 @@ void create(lua_State* L, SharedCodeGenContext* codeGenContext) NativeProtoExecDataPtr nativeExecData = createNativeProtoExecData(proto->sizecode); uint32_t instTarget = ir.function.entryLocation; + uint32_t unassignedOffset = ir.function.endLocation - instTarget; - if (FFlag::LuauCodeGenUnassignedBcTargetAbort) + for (int i = 0; i < proto->sizecode; ++i) { - uint32_t unassignedOffset = ir.function.endLocation - instTarget; + const BytecodeMapping& bcMapping = ir.function.bcMapping[i]; - for (int i = 0; i < proto->sizecode; ++i) - { - const BytecodeMapping& bcMapping = ir.function.bcMapping[i]; - - CODEGEN_ASSERT(bcMapping.asmLocation >= instTarget); + CODEGEN_ASSERT(bcMapping.asmLocation >= instTarget); - if (bcMapping.asmLocation != ~0u) - nativeExecData[i] = bcMapping.asmLocation - instTarget; - else - nativeExecData[i] = unassignedOffset; - } - } - else - { - for (int i = 0; i < proto->sizecode; ++i) - { - CODEGEN_ASSERT(ir.function.bcMapping[i].asmLocation >= instTarget); - - nativeExecData[i] = ir.function.bcMapping[i].asmLocation - instTarget; - } + if (bcMapping.asmLocation != ~0u) + nativeExecData[i] = bcMapping.asmLocation - instTarget; + else + nativeExecData[i] = unassignedOffset; } // Set first instruction offset to 0 so that entering this function still diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 7382a2b7..dbf3ac6b 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -12,8 +12,6 @@ #include "lstate.h" #include "lgc.h" -LUAU_FASTFLAG(LuauCodeGenUnassignedBcTargetAbort) -LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) LUAU_FASTFLAG(LuauCodegenDirectCompare2) namespace Luau @@ -273,8 +271,7 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { - if (FFlag::LuauCodeGenRegAutoSpillA64) - regs.currInstIdx = index; + regs.currInstIdx = index; valueTracker.beforeInstLowering(inst); @@ -2726,8 +2723,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) valueTracker.afterInstLowering(inst, index); - if (FFlag::LuauCodeGenRegAutoSpillA64) - regs.currInstIdx = kInvalidInstIdx; + regs.currInstIdx = kInvalidInstIdx; regs.freeLastUseRegs(inst, index); regs.freeTempRegs(); @@ -2772,12 +2768,9 @@ void IrLoweringA64::finishFunction() build.b(helpers.updatePcAndContinueInVm); } - if (FFlag::LuauCodeGenUnassignedBcTargetAbort) - { - // An undefined instruction is placed after the function to be used as an aborting jump offset - function.endLocation = build.setLabel().location; - build.udf(); - } + // An undefined instruction is placed after the function to be used as an aborting jump offset + function.endLocation = build.setLabel().location; + build.udf(); if (stats) { diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 99e303c5..f7253193 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -16,9 +16,7 @@ #include "lstate.h" #include "lgc.h" -LUAU_FASTFLAG(LuauCodeGenUnassignedBcTargetAbort) LUAU_FASTFLAGVARIABLE(LuauCodeGenVBlendpdReorder) -LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) LUAU_FASTFLAG(LuauCodegenDirectCompare2) namespace Luau @@ -2357,8 +2355,7 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) valueTracker.afterInstLowering(inst, index); - if (FFlag::LuauCodeGenRegAutoSpillA64) - regs.currInstIdx = kInvalidInstIdx; + regs.currInstIdx = kInvalidInstIdx; regs.freeLastUseRegs(inst, index); } @@ -2402,12 +2399,9 @@ void IrLoweringX64::finishFunction() build.jmp(helpers.updatePcAndContinueInVm); } - if (FFlag::LuauCodeGenUnassignedBcTargetAbort) - { - // An undefined instruction is placed after the function to be used as an aborting jump offset - function.endLocation = build.setLabel().location; - build.ud2(); - } + // An undefined instruction is placed after the function to be used as an aborting jump offset + function.endLocation = build.setLabel().location; + build.ud2(); if (stats) { diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index f0637506..2ffa90c8 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -11,7 +11,6 @@ #include LUAU_FASTFLAGVARIABLE(DebugCodegenChaosA64) -LUAU_FASTFLAGVARIABLE(LuauCodeGenRegAutoSpillA64) namespace Luau { @@ -147,19 +146,11 @@ RegisterA64 IrRegAllocA64::allocReg(KindA64 kind, uint32_t index) if (set.free == 0) { - if (FFlag::LuauCodeGenRegAutoSpillA64) + // Try to find and spill a register that is not used in the current instruction and has the furthest next use + if (uint32_t furthestUseTarget = findInstructionWithFurthestNextUse(set); furthestUseTarget != kInvalidInstIdx) { - // Try to find and spill a register that is not used in the current instruction and has the furthest next use - if (uint32_t furthestUseTarget = findInstructionWithFurthestNextUse(set); furthestUseTarget != kInvalidInstIdx) - { - spill(set, index, furthestUseTarget); - CODEGEN_ASSERT(set.free != 0); - } - else - { - error = true; - return RegisterA64{kind, 0}; - } + spill(set, index, furthestUseTarget); + CODEGEN_ASSERT(set.free != 0); } else { @@ -185,19 +176,11 @@ RegisterA64 IrRegAllocA64::allocTemp(KindA64 kind) if (set.free == 0) { - if (FFlag::LuauCodeGenRegAutoSpillA64) + // Try to find and spill a register that is not used in the current instruction and has the furthest next use + if (uint32_t furthestUseTarget = findInstructionWithFurthestNextUse(set); furthestUseTarget != kInvalidInstIdx) { - // Try to find and spill a register that is not used in the current instruction and has the furthest next use - if (uint32_t furthestUseTarget = findInstructionWithFurthestNextUse(set); furthestUseTarget != kInvalidInstIdx) - { - spill(set, currInstIdx, furthestUseTarget); - CODEGEN_ASSERT(set.free != 0); - } - else - { - error = true; - return RegisterA64{kind, 0}; - } + spill(set, currInstIdx, furthestUseTarget); + CODEGEN_ASSERT(set.free != 0); } else { @@ -351,79 +334,16 @@ size_t IrRegAllocA64::spill(uint32_t index, std::initializer_list l while (regs) { - if (FFlag::LuauCodeGenRegAutoSpillA64) - { - int reg = 31 - countlz(regs); - - uint32_t targetInstIdx = set.defs[reg]; - - CODEGEN_ASSERT(targetInstIdx != kInvalidInstIdx); - CODEGEN_ASSERT(function.instructions[targetInstIdx].regA64.index == reg); - - spill(set, index, targetInstIdx); - - regs &= ~(1u << reg); - } - else - { - int reg = 31 - countlz(regs); - - uint32_t inst = set.defs[reg]; - CODEGEN_ASSERT(inst != kInvalidInstIdx); - - IrInst& def = function.instructions[inst]; - CODEGEN_ASSERT(def.regA64.index == reg); - CODEGEN_ASSERT(!def.reusedReg); - CODEGEN_ASSERT(!def.spilled); - CODEGEN_ASSERT(!def.needsReload); - - if (def.lastUse == index) - { - // instead of spilling the register to never reload it, we assume the register is not needed anymore - } - else if (getReloadAddress(function, def, /*limitToCurrentBlock*/ true).base != xzr) - { - // instead of spilling the register to stack, we can reload it from VM stack/constants - // we still need to record the spill for restore(start) to work - Spill s = {inst, def.regA64, -1}; - spills.push_back(s); - - def.needsReload = true; - - if (stats) - stats->spillsToRestore++; - } - else - { - int slot = allocSpill(freeSpillSlots, def.regA64.kind); - if (slot < 0) - { - slot = kInvalidSpill; - error = true; - } - - build.str(def.regA64, mem(sp, sSpillArea.data + slot * 8)); - - Spill s = {inst, def.regA64, int8_t(slot)}; - spills.push_back(s); - - def.spilled = true; - - if (stats) - { - stats->spillsToSlot++; - - if (slot != kInvalidSpill && unsigned(slot + 1) > stats->maxSpillSlotsUsed) - stats->maxSpillSlotsUsed = slot + 1; - } - } - - def.regA64 = noreg; - - regs &= ~(1u << reg); - set.free |= 1u << reg; - set.defs[reg] = kInvalidInstIdx; - } + int reg = 31 - countlz(regs); + + uint32_t targetInstIdx = set.defs[reg]; + + CODEGEN_ASSERT(targetInstIdx != kInvalidInstIdx); + CODEGEN_ASSERT(function.instructions[targetInstIdx].regA64.index == reg); + + spill(set, index, targetInstIdx); + + regs &= ~(1u << reg); } CODEGEN_ASSERT(set.free == set.base); @@ -485,8 +405,6 @@ void IrRegAllocA64::restoreReg(IrInst& inst) void IrRegAllocA64::spill(Set& set, uint32_t index, uint32_t targetInstIdx) { - CODEGEN_ASSERT(FFlag::LuauCodeGenRegAutoSpillA64); - IrInst& def = function.instructions[targetInstIdx]; int reg = def.regA64.index; @@ -543,8 +461,6 @@ void IrRegAllocA64::spill(Set& set, uint32_t index, uint32_t targetInstIdx) uint32_t IrRegAllocA64::findInstructionWithFurthestNextUse(Set& set) const { - CODEGEN_ASSERT(FFlag::LuauCodeGenRegAutoSpillA64); - if (currInstIdx == kInvalidInstIdx) return kInvalidInstIdx; diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index ce9bf19d..2aa65524 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -6,8 +6,6 @@ #include "EmitCommonX64.h" -LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) - namespace Luau { namespace CodeGen @@ -395,11 +393,8 @@ OperandX64 IrRegAllocX64::getRestoreAddress(const IrInst& inst, IrOp restoreOp) uint32_t IrRegAllocX64::findInstructionWithFurthestNextUse(const std::array& regInstUsers) const { - if (FFlag::LuauCodeGenRegAutoSpillA64) - { - if (currInstIdx == kInvalidInstIdx) - return kInvalidInstIdx; - } + if (currInstIdx == kInvalidInstIdx) + return kInvalidInstIdx; uint32_t furthestUseTarget = kInvalidInstIdx; uint32_t furthestUseLocation = 0; diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index 94a0151c..3c177416 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -3,8 +3,6 @@ #include "Luau/IrUtils.h" -LUAU_FASTFLAGVARIABLE(LuauCodeGenRestoreFromSplitStore) - namespace Luau { namespace CodeGen @@ -177,12 +175,9 @@ void IrValueLocationTracking::afterInstLowering(IrInst& inst, uint32_t instIdx) recordRestoreOp(inst.b.index, inst.a); break; case IrCmd::STORE_SPLIT_TVALUE: - if (FFlag::LuauCodeGenRestoreFromSplitStore) - { - // If this is not the last use of the stored value, we can restore it from this new location - if (inst.c.kind == IrOpKind::Inst && function.instOp(inst.c).lastUse != instIdx) - recordRestoreOp(inst.c.index, inst.a); - } + // If this is not the last use of the stored value, we can restore it from this new location + if (inst.c.kind == IrOpKind::Inst && function.instOp(inst.c).lastUse != instIdx) + recordRestoreOp(inst.c.index, inst.a); break; default: break; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index fde68d95..10f57bbe 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -25,6 +25,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) LUAU_FASTFLAG(LuauCodegenDirectCompare2) LUAU_FASTFLAGVARIABLE(LuauCodegenNilStoreInvalidateValue2) +LUAU_FASTFLAGVARIABLE(LuauCodegenStorePriority) namespace Luau { @@ -865,7 +866,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& // If we know the tag, we can try extracting the value from a register used by LOAD_TVALUE // To do that, we have to ensure that the register link of the source value is still valid - if (tag != 0xff && state.tryGetRegLink(inst.b) != nullptr) + if (tag != 0xff && (!FFlag::LuauCodegenStorePriority || value.kind == IrOpKind::None) && state.tryGetRegLink(inst.b) != nullptr) { if (IrInst* arg = function.asInstOp(inst.b); arg && arg->cmd == IrCmd::LOAD_TVALUE && arg->a.kind == IrOpKind::VmReg) { diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index c2015ed0..f4c1eb16 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -19,7 +19,6 @@ inline bool isAnalysisFlagExperimental(const char* flag) "LuauSolverV2", "UseNewLuauTypeSolverDefaultEnabled", // This can change the default solver used in cli applications, so it also needs to be disabled. Will // require fixes in lua-apps code - "LuauRawGetHandlesNil", // requires fixes in lua-apps code // makes sure we always have at least one entry nullptr, }; diff --git a/Require/Navigator/src/RequireNavigator.cpp b/Require/Navigator/src/RequireNavigator.cpp index e5a713c9..7fd39ac2 100644 --- a/Require/Navigator/src/RequireNavigator.cpp +++ b/Require/Navigator/src/RequireNavigator.cpp @@ -173,8 +173,11 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias) while (!foundAliasValue) { - if (navigationContext.toParent() != NavigationContext::NavigateResult::Success) - break; + NavigationContext::NavigateResult result = navigationContext.toParent(); + if (result == NavigationContext::NavigateResult::Ambiguous) + return "could not navigate up the ancestry chain during search for alias \"" + desiredAlias + "\" (ambiguous)"; + if (result == NavigationContext::NavigateResult::NotFound) + break; // Not treated as an error: interpreted as reaching the root. if (navigationContext.isConfigPresent()) { diff --git a/Require/Runtime/src/Require.cpp b/Require/Runtime/src/Require.cpp index 97535187..306d0c63 100644 --- a/Require/Runtime/src/Require.cpp +++ b/Require/Runtime/src/Require.cpp @@ -7,6 +7,8 @@ #include "lua.h" #include "lualib.h" +#include + static void validateConfig(lua_State* L, const luarequire_Configuration& config) { if (!config.is_require_allowed) @@ -45,10 +47,11 @@ static int pushrequireclosureinternal( const char* debugname ) { - luarequire_Configuration* config = static_cast(lua_newuserdata(L, sizeof(luarequire_Configuration))); - if (!config) + void* ud = (lua_newuserdata(L, sizeof(luarequire_Configuration))); + if (!ud) luaL_error(L, "failed to allocate memory for require configuration"); + luarequire_Configuration* config = new (ud) luarequire_Configuration{}; config_init(config); validateConfig(L, *config); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 8d68196a..201b37c3 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -40,8 +40,6 @@ LUAU_FASTFLAG(LuauVectorLerp) LUAU_FASTFLAG(LuauCompileVectorLerp) LUAU_FASTFLAG(LuauTypeCheckerVectorLerp2) LUAU_FASTFLAG(LuauCodeGenVectorLerp2) -LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) -LUAU_FASTFLAG(LuauCodeGenRestoreFromSplitStore) LUAU_FASTFLAG(LuauStacklessPcall) LUAU_FASTFLAG(LuauResumeFix) @@ -3256,9 +3254,6 @@ TEST_CASE("SafeEnv") TEST_CASE("Native") { - ScopedFastFlag luauCodeGenRegAutoSpillA64{FFlag::LuauCodeGenRegAutoSpillA64, true}; - ScopedFastFlag luauCodeGenRestoreFromSplitStore{FFlag::LuauCodeGenRestoreFromSplitStore, true}; - // This tests requires code to run natively, otherwise all 'is_native' checks will fail if (!codegen || !luau_codegen_supported()) return; diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 47a5ac2d..d6af8efe 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(LuauCodegenDirectCompare2) LUAU_FASTFLAG(LuauCodegenNilStoreInvalidateValue2) +LUAU_FASTFLAG(LuauCodegenStorePriority) using namespace Luau::CodeGen; @@ -2917,6 +2918,49 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore2") )"); } +TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore3") +{ + ScopedFastFlag luauCodegenStorePriority{FFlag::LuauCodegenStorePriority, true}; + + IrOp entry = build.block(IrBlockKind::Internal); + + build.beginBlock(entry); + + // Obscure the R0 state by only storing the value (tag was established in a previous block not visible here) + build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(2)); + + // In the future, STORE_INT might imply that the tag is LUA_TBOOLEAN, but today it is used for other integer stores too + build.inst(IrCmd::CHECK_TAG, build.inst(IrCmd::LOAD_TAG, build.vmReg(0)), build.constTag(tnumber), build.vmExit(1)); + + // Secondary load for store propagation + build.inst(IrCmd::STORE_TAG, build.vmReg(2), build.constTag(tnumber)); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(2), build.inst(IrCmd::LOAD_DOUBLE, build.vmReg(0))); + + build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0))); + build.inst(IrCmd::STORE_INT, build.vmReg(1), build.constInt(2)); + build.inst(IrCmd::STORE_TAG, build.vmReg(1), build.constTag(tboolean)); + build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1)); + + updateUseCounts(build.function); + computeCfgInfo(build.function); + constPropInBlockChains(build); + + CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( +bb_0: + STORE_INT R0, 2i + %1 = LOAD_TAG R0 + CHECK_TAG %1, tnumber, exit(1) + STORE_TAG R2, tnumber + %4 = LOAD_DOUBLE R0 + STORE_DOUBLE R2, %4 + %6 = LOAD_TVALUE R0 + STORE_TVALUE R1, %6 + STORE_TAG R1, tboolean + RETURN R1, 1i + +)"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("Analysis"); diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index e75d8e8c..71465d0e 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -406,6 +406,25 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithFileAmbiguity") ); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithAmbiguityInAliasDiscovery") +{ + char executable[] = "luau"; + std::vector paths = { + getLuauDirectory(PathType::Relative) + "/tests/require/with_config/parent_ambiguity/folder/requirer.luau", + getLuauDirectory(PathType::Absolute) + "/tests/require/with_config/parent_ambiguity/folder/requirer.luau", + }; + + for (const std::string& path : paths) + { + std::vector pathStr(path.size() + 1); + strncpy(pathStr.data(), path.c_str(), path.size()); + pathStr[path.size()] = '\0'; + + char* argv[2] = {executable, pathStr.data()}; + CHECK_EQ(replMain(2, argv), 0); + } +} + TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithDirectoryAmbiguity") { std::string ambiguousPath = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/ambiguous_directory_requirer"; diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index dbb304b5..714f99d8 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -11,6 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSimplifyRefinementOfReadOnlyProperty) LUAU_DYNAMIC_FASTINT(LuauSimplificationComplexityLimit) +LUAU_FASTFLAG(LuauSimplifyIntersectionNoTreeSet) namespace { @@ -680,12 +681,18 @@ TEST_CASE_FIXTURE(SimplifyFixture, "{ read x: Child } & { x: Parent }") TEST_CASE_FIXTURE(SimplifyFixture, "intersect_parts_empty_table_non_empty") { - TypeId emptyTable = arena->addType(TableType{}); + ScopedFastFlag _{FFlag::LuauSimplifyIntersectionNoTreeSet, true}; + + TableType empty; + empty.state = TableState::Sealed; + TypeId emptyTable = arena->addType(std::move(empty)); + TableType nonEmpty; nonEmpty.props["p"] = arena->addType(UnionType{{getBuiltins()->numberType, getBuiltins()->stringType}}); + nonEmpty.state = TableState::Sealed; TypeId nonEmptyTable = arena->addType(std::move(nonEmpty)); - // FIXME CLI-170522: This is wrong. - CHECK("never" == toString(simplifyIntersection(getBuiltins(), arena, {nonEmptyTable, emptyTable}).result)); + + CHECK("{ p: number | string }" == toString(simplifyIntersection(getBuiltins(), arena, {nonEmptyTable, emptyTable}).result)); } TEST_SUITE_END(); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 0dfcd71b..9a473537 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -17,7 +17,6 @@ LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) -LUAU_FASTFLAG(LuauRawGetHandlesNil) LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) struct TypeFunctionFixture : Fixture @@ -1328,8 +1327,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_union_type_inde if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauRawGetHandlesNil, true}; - CheckResult result = check(R"( type MyObject = {a: string, b: number, c: boolean} type rawType = rawget @@ -1346,8 +1343,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_union_type_inde if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauRawGetHandlesNil, true}; - CheckResult result = check(R"( type MyObject = {a: string, b: number, c: boolean} type MyObject2 = {a: number} @@ -1365,8 +1360,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_index_metatable if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauRawGetHandlesNil, true}; - CheckResult result = check(R"( local exampleClass = { Foo = "text", Bar = true } local exampleClass2 = setmetatable({ Foo = 8 }, { __index = exampleClass }) @@ -1400,8 +1393,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_queried_key_abs if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff{FFlag::LuauRawGetHandlesNil, true}; - CheckResult result = check(R"( type MyObject = {a: string} type T = rawget diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index b1524e43..75765ee3 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) +LUAU_FASTFLAG(LuauAddRefinementToAssertions) TEST_SUITE_BEGIN("ProvisionalTests"); @@ -1427,4 +1428,22 @@ TEST_CASE_FIXTURE(Fixture, "unification_inferring_never_for_refined_param") CHECK_EQ("({ read getItem: (number) -> (never, ...unknown) }, number) -> ()", toString(requireType("__removeItem"))); } +TEST_CASE_FIXTURE(BuiltinsFixture, "assert_and_many_nested_typeof_contexts") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauAddRefinementToAssertions, true}, + }; + + CheckResult result = check(R"( + local foo: unknown = nil :: any + assert(typeof(foo) == "table") + if typeof(typeof(foo.x)) == "string" then + end + )"); + + // TODO CLI-174351: We should expect a TypeError: Type 'table' does not have key 'x' error here. + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index d555c9dd..a620dc2a 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAG(LuauAddConditionalContextForTernary) LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) +LUAU_FASTFLAG(LuauAddRefinementToAssertions) using namespace Luau; @@ -3055,4 +3056,106 @@ TEST_CASE_FIXTURE(Fixture, "oss_1517_equality_doesnt_add_nil") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "typeof_refinement_context") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauAddRefinementToAssertions, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + + local x = {} :: unknown + + if typeof(x) == "table" then + if typeof(x.transform) == "function" then + local y = x.transform + end + end + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "assert_and_typeof_refinement_context") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauAddRefinementToAssertions, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + + local x = {} :: unknown + + if typeof(x) == "table" then + assert(typeof(x.transform) == "function") + end + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "foo_call_should_not_refine") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauAddRefinementToAssertions, true}, + }; + + CheckResult result = check(R"( + --!strict + + local x = {} :: unknown + local function foo(_: boolean) end + + if typeof(x) == "table" then + foo(typeof(x.transform) == "function") + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Type 'table' does not have key 'transform'", toString(result.errors[0])); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "assert_call_should_not_refine_despite_typeof") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauAddRefinementToAssertions, true}, + }; + + CheckResult result = check(R"( + --!strict + local function foo(_: any) end + + local function f(x: unknown) + if typeof(x) == "table" then + assert(foo(typeof(x.bar))) + end + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Type 'table' does not have key 'bar'", toString(result.errors[0])); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "non_conditional_context_in_if_should_not_refine") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauAddRefinementToAssertions, true}, + }; + + CheckResult result = check(R"( + local function bing(_: any) end + local function foobar(x: unknown) + assert(typeof(x) == "table") + if bing(x.foo) then + end + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_EQ("Type 'table' does not have key 'foo'", toString(result.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 81586bca..ff943428 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -10,6 +10,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauPushTypeConstraint2) LUAU_FASTFLAG(LuauPushTypeConstraintSingleton) +LUAU_FASTFLAG(LuauPushTypeConstraintIndexer) TEST_SUITE_BEGIN("TypeSingletons"); @@ -749,5 +750,62 @@ TEST_CASE_FIXTURE(Fixture, "oss_2010") CHECK_EQ("\"meow\"", toString(requireType("var"))); } +TEST_CASE_FIXTURE(Fixture, "oss_1773") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintIndexer, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + + export type T = "foo" | "bar" | "toto" + + local object: T = "foo" + + local getOpposite: {[T]: T} = { + ["foo"] = "bar", + ["bar"] = "toto", + ["toto"] = "foo" + } + + local function hello() + local x = getOpposite[object] + + if x then + object = x + end + end + )")); +} + +TEST_CASE_FIXTURE(Fixture, "bidirectionally_infer_indexers_errored") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintIndexer, true}, + }; + + CheckResult result = check(R"( + --!strict + + export type T = "foo" | "bar" | "toto" + + local getOpposite: { [number]: T } = { + ["foo"] = "bar", + ["bar"] = "toto", + ["toto"] = "foo" + } + )"); + + LUAU_REQUIRE_ERROR_COUNT(3, result); + + for (const auto& e: result.errors) + CHECK(get(e)); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index dc11ba5e..54510452 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -37,6 +37,7 @@ LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAG(LuauTryToOptimizeSetTypeUnification) LUAU_FASTFLAG(LuauDontReferenceScopePtrFromHashTable) LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) +LUAU_FASTFLAG(LuauMetatableAvoidSingletonUnion) using namespace Luau; @@ -2718,4 +2719,15 @@ end LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_avoid_singleton_union") +{ + ScopedFastFlag _{FFlag::LuauMetatableAvoidSingletonUnion, true}; + + LUAU_REQUIRE_ERRORS(check(R"( +_ = if true then _ else {},if (_) then _ elseif "" then {} elseif _ then {} elseif _ then _ else {} +for l0,l2 in setmetatable(_,_),l0,_ do +end + )")); +} + TEST_SUITE_END(); diff --git a/tests/require/with_config/parent_ambiguity/.luaurc b/tests/require/with_config/parent_ambiguity/.luaurc new file mode 100644 index 00000000..24f8e9ff --- /dev/null +++ b/tests/require/with_config/parent_ambiguity/.luaurc @@ -0,0 +1,5 @@ +{ + "aliases": { + "foo": "./foo", + } +} diff --git a/tests/require/with_config/parent_ambiguity/folder.luau b/tests/require/with_config/parent_ambiguity/folder.luau new file mode 100644 index 00000000..e69de29b diff --git a/tests/require/with_config/parent_ambiguity/folder/requirer.luau b/tests/require/with_config/parent_ambiguity/folder/requirer.luau new file mode 100644 index 00000000..2f613057 --- /dev/null +++ b/tests/require/with_config/parent_ambiguity/folder/requirer.luau @@ -0,0 +1 @@ +return require("@foo") diff --git a/tests/require/with_config/parent_ambiguity/foo.luau b/tests/require/with_config/parent_ambiguity/foo.luau new file mode 100644 index 00000000..aa00aca1 --- /dev/null +++ b/tests/require/with_config/parent_ambiguity/foo.luau @@ -0,0 +1 @@ +return { "result from foo" } From 5836a9cf3e9e14ba4e7a6529340ff0e94c56ef39 Mon Sep 17 00:00:00 2001 From: Hunter Goldstein Date: Fri, 24 Oct 2025 13:17:02 -0700 Subject: [PATCH 07/10] Sync to upstream/release/697 (#2058) Another somewhat small release as we work our way up to the next stage of the new type solver! # General * Implement configuration extraction as per the [Luau-syntax configuration files RFC](https://github.com/luau-lang/rfcs/blob/master/docs/config-luauconfig.md): we now expose a low-level `extractConfig` API and a higher level `extractLuauConfig` API for extracting configurations from these files. The runtime and analysis integrations with `require`, for this feature, are coming in a later release. # Analysis * Implemented native stack guards: on Windows and MacOS spots where we attempt to guard against stack overflows by using a counter now _also_ check whether we are close to hitting the runtime stack limit via OS specific APIs. * Fix a performance issue that could occur when trying to reduce type functions for math operations in the new solver, causing the new solver to fail to solve all constraints and take a disproportionate amount of time to finish solving. * Improve the new solver's non-strict checking performance when checking exceptionally large function calls and function definitions. --------- Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Alexander Youngblood Co-authored-by: Menarul Alam Co-authored-by: Aviral Goel Co-authored-by: Vighnesh Co-authored-by: Vyacheslav Egorov Co-authored-by: Ariel Weiss Co-authored-by: Andy Friesen --- Analysis/include/Luau/NativeStackGuard.h | 28 ++ Analysis/include/Luau/RecursionCounter.h | 35 +-- Analysis/include/Luau/TypeFunction.h | 4 + Analysis/src/BuiltinTypeFunctions.cpp | 8 +- Analysis/src/ConstraintGenerator.cpp | 6 +- Analysis/src/ConstraintSolver.cpp | 4 +- Analysis/src/DataFlowGraph.cpp | 10 +- Analysis/src/EqSatSimplification.cpp | 8 +- Analysis/src/Error.cpp | 10 +- Analysis/src/FragmentAutocomplete.cpp | 51 ++- Analysis/src/LValue.cpp | 2 +- Analysis/src/NativeStackGuard.cpp | 123 ++++++++ Analysis/src/NonStrictTypeChecker.cpp | 142 +++++++-- Analysis/src/Normalize.cpp | 6 +- Analysis/src/OverloadResolution.cpp | 124 ++------ Analysis/src/RecursionCounter.cpp | 40 +++ Analysis/src/Simplify.cpp | 12 +- Analysis/src/Subtyping.cpp | 2 +- Analysis/src/ToDot.cpp | 2 +- Analysis/src/TypeFunction.cpp | 14 + Analysis/src/TypeFunctionReductionGuesser.cpp | 10 +- Analysis/src/TypeFunctionRuntime.cpp | 26 +- Analysis/src/TypeFunctionRuntimeBuilder.cpp | 42 +-- Analysis/src/TypeInfer.cpp | 6 +- Analysis/src/TypeUtils.cpp | 15 +- Analysis/src/Unifier.cpp | 4 +- Ast/src/Parser.cpp | 39 +-- CMakeLists.txt | 1 + Config/include/Luau/Config.h | 7 + Config/include/Luau/LuauConfig.h | 102 ++++++ Config/src/Config.cpp | 2 +- Config/src/LuauConfig.cpp | 291 ++++++++++++++++++ Makefile | 2 +- Sources.cmake | 5 + VM/src/lobject.h | 8 +- VM/src/lstate.h | 35 +-- VM/src/ltm.cpp | 3 - VM/src/ltm.h | 3 - tests/Config.test.cpp | 188 +++++++++++ tests/Fixture.cpp | 12 + tests/Fixture.h | 7 + tests/FragmentAutocomplete.test.cpp | 13 - tests/NonStrictTypeChecker.test.cpp | 7 +- tests/OverloadResolver.test.cpp | 3 - tests/Parser.test.cpp | 8 - tests/RuntimeLimits.test.cpp | 45 +++ tests/Subtyping.test.cpp | 2 - tests/TypeInfer.builtins.test.cpp | 2 - tests/TypeInfer.functions.test.cpp | 4 - tests/TypeInfer.refinements.test.cpp | 33 ++ tests/TypeInfer.tables.test.cpp | 5 - 51 files changed, 1184 insertions(+), 377 deletions(-) create mode 100644 Analysis/include/Luau/NativeStackGuard.h create mode 100644 Analysis/src/NativeStackGuard.cpp create mode 100644 Analysis/src/RecursionCounter.cpp create mode 100644 Config/include/Luau/LuauConfig.h create mode 100644 Config/src/LuauConfig.cpp diff --git a/Analysis/include/Luau/NativeStackGuard.h b/Analysis/include/Luau/NativeStackGuard.h new file mode 100644 index 00000000..5ee83f0f --- /dev/null +++ b/Analysis/include/Luau/NativeStackGuard.h @@ -0,0 +1,28 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include + +namespace Luau +{ + +struct NativeStackGuard +{ + NativeStackGuard(); + + // Returns true if we are not dangerously close to overrunning the C stack. + bool isOk() const; + +private: + uintptr_t high; + uintptr_t low; +}; + +} + +namespace Luau +{ + +uintptr_t getStackAddressSpaceSize(); + +} diff --git a/Analysis/include/Luau/RecursionCounter.h b/Analysis/include/Luau/RecursionCounter.h index 962ecd02..fb07d7d7 100644 --- a/Analysis/include/Luau/RecursionCounter.h +++ b/Analysis/include/Luau/RecursionCounter.h @@ -3,6 +3,7 @@ #include "Luau/Common.h" #include "Luau/Error.h" +#include "Luau/NativeStackGuard.h" #include #include @@ -12,25 +13,18 @@ namespace Luau struct RecursionLimitException : public InternalCompilerError { - RecursionLimitException(const std::string system) - : InternalCompilerError("Internal recursion counter limit exceeded in " + system) - { - } + explicit RecursionLimitException(const std::string& system); }; struct RecursionCounter { - RecursionCounter(int* count) - : count(count) - { - ++(*count); - } - - ~RecursionCounter() - { - LUAU_ASSERT(*count > 0); - --(*count); - } + explicit RecursionCounter(int* count); + ~RecursionCounter(); + + RecursionCounter(const RecursionCounter&) = delete; + RecursionCounter& operator=(const RecursionCounter&) = delete; + RecursionCounter(RecursionCounter&&) = delete; + RecursionCounter& operator=(RecursionCounter&&) = delete; protected: int* count; @@ -38,14 +32,9 @@ struct RecursionCounter struct RecursionLimiter : RecursionCounter { - RecursionLimiter(const std::string system, int* count, int limit) - : RecursionCounter(count) - { - if (limit > 0 && *count > limit) - { - throw RecursionLimitException(system); - } - } + NativeStackGuard nativeStackGuard; + + RecursionLimiter(const std::string& system, int* count, int limit); }; } // namespace Luau diff --git a/Analysis/include/Luau/TypeFunction.h b/Analysis/include/Luau/TypeFunction.h index a96b800b..ffeb53dd 100644 --- a/Analysis/include/Luau/TypeFunction.h +++ b/Analysis/include/Luau/TypeFunction.h @@ -108,6 +108,10 @@ struct TypeFunctionReductionResult std::optional error; /// Messages printed out from user-defined type functions std::vector messages; + /// Some type function reduction rules may _create_ type functions (e.g. + /// the numeric type functions can "distribute" over an inner union). If + /// any type functions were created this way, we must add them here. + std::vector freshTypes; }; template diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index 2b0e3cc1..33424a17 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -29,6 +29,7 @@ LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauBuiltinTypeFunctionsArentGlobal) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) +LUAU_FASTFLAG(LuauEnqueueUnionsOfDistributedTypeFunctions) namespace Luau { @@ -122,7 +123,10 @@ std::optional> tryDistributeTypeFunctionApp( if (ctx->solver) ctx->pushConstraint(ReduceConstraint{resultTy}); - return {{resultTy, Reduction::MaybeOk, {}, {}}}; + if (FFlag::LuauEnqueueUnionsOfDistributedTypeFunctions) + return {{resultTy, Reduction::MaybeOk, {}, {}, {}, {}, {resultTy}}}; + else + return {{resultTy, Reduction::MaybeOk, {}, {}}}; } return std::nullopt; @@ -2468,7 +2472,7 @@ static TypeFunctionReductionResult getmetatableHelper(TypeId targetTy, c std::optional result = std::nullopt; bool erroneous = true; - if (auto table = get(targetTy)) + if (get(targetTy)) erroneous = false; if (auto mt = get(targetTy)) diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 63eb3d3f..a7d179e8 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -1563,7 +1563,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f sig.bodyScope->lvalueTypes[def] = sig.signature; updateRValueRefinements(sig.bodyScope, def, sig.signature); } - else if (AstExprIndexName* indexName = function->name->as()) + else if (function->name->is()) { updateRValueRefinements(sig.bodyScope, def, sig.signature); } @@ -1673,7 +1673,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f { visitLValue(scope, indexName, generalizedType); } - else if (AstExprError* err = function->name->as()) + else if (function->name->is()) { generalizedType = builtinTypes->errorType; } @@ -2360,7 +2360,7 @@ InferencePack ConstraintGenerator::checkPack( if (AstExprCall* call = expr->as()) result = checkPack(scope, call); - else if (AstExprVarargs* varargs = expr->as()) + else if (expr->is()) { if (scope->varargPack) result = InferencePack{*scope->varargPack}; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 5e952ee7..0b872b0a 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -1226,7 +1226,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul } // Adding ReduceConstraint on type function for the constraint solver - if (auto typeFn = get(follow(tf->type))) + if (get(follow(tf->type))) pushConstraint(NotNull(constraint->scope.get()), constraint->location, ReduceConstraint{tf->type}); // Due to how pending expansion types and TypeFun's are created @@ -3660,7 +3660,7 @@ bool ConstraintSolver::isBlocked(TypePackId tp) const { tp = follow(tp); - if (auto tfitp = get(tp)) + if (get(tp)) return uninhabitedTypeFunctions.contains(tp) == false; return nullptr != get(tp); diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index 9dbcfd85..567ae624 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -574,7 +574,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatLocal* l) if (i < l->values.size) { AstExpr* e = l->values.data[i]; - if (const AstExprTable* tbl = e->as()) + if (e->is()) { def = defs[i]; } @@ -1165,7 +1165,7 @@ void DataFlowGraphBuilder::visitType(AstType* t) return visitType(f); else if (auto tyof = t->as()) return visitType(tyof); - else if (auto o = t->as()) + else if (t->is()) return; else if (auto u = t->as()) return visitType(u); @@ -1173,9 +1173,9 @@ void DataFlowGraphBuilder::visitType(AstType* t) return visitType(i); else if (auto e = t->as()) return visitType(e); - else if (auto s = t->as()) + else if (t->is()) return; // ok - else if (auto s = t->as()) + else if (t->is()) return; // ok else if (auto g = t->as()) return visitType(g->type); @@ -1243,7 +1243,7 @@ void DataFlowGraphBuilder::visitTypePack(AstTypePack* p) return visitTypePack(e); else if (auto v = p->as()) return visitTypePack(v); - else if (auto g = p->as()) + else if (p->is()) return; // ok else handle->ice("Unknown AstTypePack in DataFlowGraphBuilder::visitTypePack"); diff --git a/Analysis/src/EqSatSimplification.cpp b/Analysis/src/EqSatSimplification.cpp index a933d3ab..beae70f0 100644 --- a/Analysis/src/EqSatSimplification.cpp +++ b/Analysis/src/EqSatSimplification.cpp @@ -286,7 +286,7 @@ Id toId( // First, handle types which do not contain other types. They obviously // cannot participate in cycles, so we don't have to check for that. - if (auto freeTy = get(ty)) + if (get(ty)) return egraph.add(TOpaque{ty}); else if (get(ty)) return egraph.add(TOpaque{ty}); @@ -765,7 +765,7 @@ TypeId fromId( return arena->addType(SingletonType{StringSingleton{strings.asString(s->value())}}); else if (auto fun = node.get()) return fun->value(); - else if (auto tbl = node.get()) + else if (node.get()) { TypeId res = arena->addType(BlockedType{}); seen[rootId] = res; @@ -1310,7 +1310,7 @@ void unionWithType(EGraph& egraph, CanonicalizedType& ct, Id part) if (!ct.functionPart) ct.functionParts.insert(part); } - else if (auto tclass = isTag(egraph, part)) + else if (isTag(egraph, part)) unionClasses(egraph, ct.classParts, part); else if (isTag(egraph, part)) { @@ -1415,7 +1415,7 @@ bool subtract(EGraph& egraph, CanonicalizedType& ct, Id part) ct.tablePart.reset(); else if (etype->get()) ct.classParts.clear(); - else if (auto tclass = etype->get()) + else if (etype->get()) { auto it = std::find(ct.classParts.begin(), ct.classParts.end(), part); if (it != ct.classParts.end()) diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 764e0bfa..1bdb7e05 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -697,7 +697,7 @@ struct ErrorConverter if (tfit->typeArguments.size() != 2) return "Type function instance " + Luau::toString(e.ty) + " is ill-formed, and thus invalid"; - if (auto errType = get(tfit->typeArguments[1])) // Second argument to (index | rawget)<_,_> is not a type + if (get(tfit->typeArguments[1])) // Second argument to (index | rawget)<_,_> is not a type return "Second argument to " + tfit->function->name + "<" + Luau::toString(tfit->typeArguments[0]) + ", _> is not a valid index type"; else // Property `indexer` does not exist on type `indexee` return "Property '" + Luau::toString(tfit->typeArguments[1]) + "' does not exist on type '" + Luau::toString(tfit->typeArguments[0]) + @@ -757,12 +757,8 @@ struct ErrorConverter std::string operator()(const CheckedFunctionCallError& e) const { // TODO: What happens if checkedFunctionName cannot be found?? - if (FFlag::LuauNewNonStrictReportsOneIndexedErrors) - return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + - std::to_string(e.argumentIndex + 1) + ", but got '" + Luau::toString(e.passed) + "'"; - else - return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + std::to_string(e.argumentIndex) + - ", but got '" + Luau::toString(e.passed) + "'"; + return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + + std::to_string(e.argumentIndex + 1) + ", but got '" + Luau::toString(e.passed) + "'"; } std::string operator()(const NonStrictFunctionDefinitionError& e) const diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 6da10071..2f29cbce 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -33,8 +33,6 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauFragmentRequiresCanBeResolvedToAModule) -LUAU_FASTFLAGVARIABLE(LuauPopulateSelfTypesInFragment) -LUAU_FASTFLAGVARIABLE(LuauForInProvidesRecommendations) LUAU_FASTFLAGVARIABLE(LuauForInRangesConsiderInLocation) namespace Luau @@ -119,7 +117,7 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo { Location nonEmpty{nearestStatement->location.begin, cursorPosition}; // If your sibling is a do block, do nothing - if (auto doEnd = nearestStatement->as()) + if (nearestStatement->as()) return empty; // If you're inside the body of the function and this is your sibling, empty fragment @@ -156,27 +154,20 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo if (auto forStat = nearestStatement->as()) { - - if (FFlag::LuauForInProvidesRecommendations) - { - if (forStat->step && forStat->step->location.containsClosed(cursorPosition)) - return {forStat->step->location.begin, cursorPosition}; - if (forStat->to && forStat->to->location.containsClosed(cursorPosition)) - return {forStat->to->location.begin, cursorPosition}; - if (forStat->from && forStat->from->location.containsClosed(cursorPosition)) - return {forStat->from->location.begin, cursorPosition}; - } + if (forStat->step && forStat->step->location.containsClosed(cursorPosition)) + return {forStat->step->location.begin, cursorPosition}; + if (forStat->to && forStat->to->location.containsClosed(cursorPosition)) + return {forStat->to->location.begin, cursorPosition}; + if (forStat->from && forStat->from->location.containsClosed(cursorPosition)) + return {forStat->from->location.begin, cursorPosition}; if (!forStat->hasDo) return nonEmpty; else { - if (FFlag::LuauForInProvidesRecommendations) - { auto completeableExtents = Location{forStat->location.begin, forStat->doLocation.begin}; if (completeableExtents.containsClosed(cursorPosition)) return nonEmpty; - } return empty; } @@ -188,21 +179,18 @@ Location getFragmentLocation(AstStat* nearestStatement, const Position& cursorPo return nonEmpty; else { - if (FFlag::LuauForInProvidesRecommendations) + auto completeableExtents = Location{forIn->location.begin, forIn->doLocation.begin}; + if (completeableExtents.containsClosed(cursorPosition)) { - auto completeableExtents = Location{forIn->location.begin, forIn->doLocation.begin}; - if (completeableExtents.containsClosed(cursorPosition)) + if (!forIn->hasIn) + return nonEmpty; + else { - if (!forIn->hasIn) + // [for ... in ... do] - the cursor can either be between [for ... in] or [in ... do] + if (FFlag::LuauForInRangesConsiderInLocation && cursorPosition < forIn->inLocation.begin) return nonEmpty; else - { - // [for ... in ... do] - the cursor can either be between [for ... in] or [in ... do] - if (FFlag::LuauForInRangesConsiderInLocation && cursorPosition < forIn->inLocation.begin) - return nonEmpty; - else - return Location{forIn->inLocation.begin, cursorPosition}; - } + return Location{forIn->inLocation.begin, cursorPosition}; } } return empty; @@ -429,13 +417,10 @@ FragmentAutocompleteAncestryResult findAncestryForFragmentParse(AstStatBlock* st { if (globFun->location.contains(cursorPos)) { - if (FFlag::LuauPopulateSelfTypesInFragment) + if (auto local = globFun->func->self) { - if (auto local = globFun->func->self) - { - localStack.push_back(local); - localMap[local->name] = local; - } + localStack.push_back(local); + localMap[local->name] = local; } for (AstLocal* loc : globFun->func->args) diff --git a/Analysis/src/LValue.cpp b/Analysis/src/LValue.cpp index 38dfe1ae..daf73ea7 100644 --- a/Analysis/src/LValue.cpp +++ b/Analysis/src/LValue.cpp @@ -80,7 +80,7 @@ std::optional tryGetLValue(const AstExpr& node) Symbol getBaseSymbol(const LValue& lvalue) { const LValue* current = &lvalue; - while (auto field = get(*current)) + while (get(*current)) current = baseof(*current); const Symbol* symbol = get(*current); diff --git a/Analysis/src/NativeStackGuard.cpp b/Analysis/src/NativeStackGuard.cpp new file mode 100644 index 00000000..3bfc40d3 --- /dev/null +++ b/Analysis/src/NativeStackGuard.cpp @@ -0,0 +1,123 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "Luau/NativeStackGuard.h" +#include "Luau/Common.h" +#include + +LUAU_FASTFLAGVARIABLE(LuauUseNativeStackGuard); + +// The minimum number of available bytes in the stack's address space. +// If we have less, we want to fail and exit rather than risking an overflow. +LUAU_FASTINTVARIABLE(LuauStackGuardThreshold, 1024); + +#if defined(_MSC_VER) + +#define WIN32_LEAN_AND_MEAN +#include +#include + +namespace Luau +{ + +NativeStackGuard::NativeStackGuard() + : high(0) + , low(0) +{ + if (!FFlag::LuauUseNativeStackGuard) + return; + + GetCurrentThreadStackLimits((PULONG_PTR)&low, (PULONG_PTR)&high); +} + +bool NativeStackGuard::isOk() const +{ + if (!FFlag::LuauUseNativeStackGuard || FInt::LuauStackGuardThreshold <= 0) + return true; + + const uintptr_t sp = uintptr_t(_AddressOfReturnAddress()); + + const uintptr_t remaining = sp - low; + + return remaining > uintptr_t(FInt::LuauStackGuardThreshold); +} + +uintptr_t getStackAddressSpaceSize() +{ + uintptr_t low = 0; + uintptr_t high = 0; + GetCurrentThreadStackLimits((PULONG_PTR)&low, (PULONG_PTR)&high); + + return high - low; +} + +} + +#elif defined(__APPLE__) + +#include + +namespace Luau +{ + +NativeStackGuard::NativeStackGuard() + : high(0) + , low(0) +{ + if (!FFlag::LuauUseNativeStackGuard) + return; + + pthread_t self = pthread_self(); + char* addr = static_cast(pthread_get_stackaddr_np(self)); + size_t size = pthread_get_stacksize_np(self); + + low = uintptr_t(addr - size); + high = uintptr_t(addr); +} + +bool NativeStackGuard::isOk() const +{ + if (!FFlag::LuauUseNativeStackGuard || FInt::LuauStackGuardThreshold <= 0) + return true; + + const uintptr_t sp = uintptr_t(__builtin_frame_address(0)); + + const uintptr_t remaining = sp - low; + + return remaining > uintptr_t(FInt::LuauStackGuardThreshold); +} + +uintptr_t getStackAddressSpaceSize() +{ + pthread_t self = pthread_self(); + return pthread_get_stacksize_np(self); +} + +} + +#else + + +namespace Luau +{ + +NativeStackGuard::NativeStackGuard() + : high(0) + , low(0) +{ + (void)low; + (void)high; +} + +bool NativeStackGuard::isOk() const +{ + return true; +} + +uintptr_t getStackAddressSpaceSize() +{ + return 0; +} + +} + +#endif diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 95bfecab..a09b28c0 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -20,9 +20,9 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressesDynamicRequireErrors) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauUnreducedTypeFunctionsDontTriggerWarnings) +LUAU_FASTFLAGVARIABLE(LuauNonStrictFetchScopeOnce) namespace Luau { @@ -695,17 +695,38 @@ struct NonStrictTypeChecker } // Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types - for (size_t i = 0; i < arguments.size(); i++) + if (FFlag::LuauNonStrictFetchScopeOnce) { - AstExpr* arg = arguments[i]; - if (auto runTimeFailureType = willRunTimeError(arg, fresh)) + NotNull scope{findInnermostScope(call->location)}; + for (size_t i = 0; i < arguments.size(); i++) { - if (FFlag::LuauUnreducedTypeFunctionsDontTriggerWarnings) - reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); - else + AstExpr* arg = arguments[i]; + if (auto runTimeFailureType = willRunTimeError(arg, fresh, scope)) + { + if (FFlag::LuauUnreducedTypeFunctionsDontTriggerWarnings) + reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); + else + { + if (!get(follow(*runTimeFailureType))) + reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); + } + } + } + } + else + { + for (size_t i = 0; i < arguments.size(); i++) + { + AstExpr* arg = arguments[i]; + if (auto runTimeFailureType = willRunTimeError_DEPRECATED(arg, fresh)) { - if (!get(follow(*runTimeFailureType))) + if (FFlag::LuauUnreducedTypeFunctionsDontTriggerWarnings) reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); + else + { + if (!get(follow(*runTimeFailureType))) + reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); + } } } } @@ -747,18 +768,35 @@ struct NonStrictTypeChecker // TODO: should a function being used as an expression generate a context without the arguments? auto pusher = pushStack(exprFn); NonStrictContext remainder = visit(exprFn->body); - for (AstLocal* local : exprFn->args) + if (FFlag::LuauNonStrictFetchScopeOnce) { - if (std::optional ty = willRunTimeErrorFunctionDefinition(local, remainder)) + auto scope = pusher ? pusher->scope : NotNull{module->getModuleScope().get()}; + for (AstLocal* local : exprFn->args) { - const char* debugname = exprFn->debugname.value; - reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location); - } - remainder.remove(dfg->getDef(local)); + if (std::optional ty = willRunTimeErrorFunctionDefinition(local, scope, remainder)) + { + const char* debugname = exprFn->debugname.value; + reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location); + } + remainder.remove(dfg->getDef(local)); - visit(local->annotation); + visit(local->annotation); + } } + else + { + for (AstLocal* local : exprFn->args) + { + if (std::optional ty = willRunTimeErrorFunctionDefinition_DEPRECATED(local, remainder)) + { + const char* debugname = exprFn->debugname.value; + reportError(NonStrictFunctionDefinitionError{debugname ? debugname : "", local->name.value, *ty}, local->location); + } + remainder.remove(dfg->getDef(local)); + visit(local->annotation); + } + } visitGenerics(exprFn->generics, exprFn->genericPacks); visit(exprFn->returnAnnotation); @@ -1157,8 +1195,33 @@ struct NonStrictTypeChecker // TODO: weave in logger here? } + std::optional willRunTimeError(AstExpr* fragment, const NonStrictContext& context, NotNull scope) + { + DefId def = dfg->getDef(fragment); + std::vector defs; + collectOperands(def, &defs); + for (DefId def : defs) + { + if (std::optional contextTy = context.find(def)) + { + + TypeId actualType = lookupType(fragment); + if (FFlag::LuauUnreducedTypeFunctionsDontTriggerWarnings && shouldSkipRuntimeErrorTesting(actualType)) + continue; + SubtypingResult r = subtyping.isSubtype(actualType, *contextTy, scope); + if (r.normalizationTooComplex) + reportError(NormalizationTooComplex{}, fragment->location); + if (r.isSubtype) + return {actualType}; + } + } + + return {}; + } + // If this fragment of the ast will run time error, return the type that causes this - std::optional willRunTimeError(AstExpr* fragment, const NonStrictContext& context) + // Clip with LuauNonStrictFetchScopeOnce + std::optional willRunTimeError_DEPRECATED(AstExpr* fragment, const NonStrictContext& context) { NotNull scope{Luau::findScopeAtPosition(*module, fragment->location.end).get()}; DefId def = dfg->getDef(fragment); @@ -1183,7 +1246,29 @@ struct NonStrictTypeChecker return {}; } - std::optional willRunTimeErrorFunctionDefinition(AstLocal* fragment, const NonStrictContext& context) + std::optional willRunTimeErrorFunctionDefinition(AstLocal* fragment, NotNull scope, const NonStrictContext& context) + { + DefId def = dfg->getDef(fragment); + std::vector defs; + collectOperands(def, &defs); + for (DefId def : defs) + { + if (std::optional contextTy = context.find(def)) + { + SubtypingResult r1 = subtyping.isSubtype(builtinTypes->unknownType, *contextTy, scope); + SubtypingResult r2 = subtyping.isSubtype(*contextTy, builtinTypes->unknownType, scope); + if (r1.normalizationTooComplex || r2.normalizationTooComplex) + reportError(NormalizationTooComplex{}, fragment->location); + bool isUnknown = r1.isSubtype && r2.isSubtype; + if (isUnknown) + return {builtinTypes->unknownType}; + } + } + return {}; + } + + // Clip with LuauNonStrictFetchScopeOnce + std::optional willRunTimeErrorFunctionDefinition_DEPRECATED(AstLocal* fragment, const NonStrictContext& context) { NotNull scope{Luau::findScopeAtPosition(*module, fragment->location.end).get()}; DefId def = dfg->getDef(fragment); @@ -1241,21 +1326,18 @@ void checkNonStrict( typeChecker.visit(sourceModule.root); unfreeze(module->interfaceTypes); copyErrors(module->errors, module->interfaceTypes, builtinTypes); - - if (FFlag::LuauNewNonStrictSuppressesDynamicRequireErrors) - { - module->errors.erase( - std::remove_if( - module->errors.begin(), - module->errors.end(), - [](auto err) - { - return get(err) != nullptr; - } + + module->errors.erase( + std::remove_if( + module->errors.begin(), + module->errors.end(), + [](auto err) + { + return get(err) != nullptr; + } ), - module->errors.end() + module->errors.end() ); - } freeze(module->interfaceTypes); } diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index f3b2cc7e..634b9985 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -3059,7 +3059,7 @@ NormalizationResult Normalizer::intersectNormalWithTy( NormalizedTyvars tyvars = std::move(here.tyvars); - if (const FunctionType* utv = get(there)) + if (get(there)) { NormalizedFunctionType functions = std::move(here.functions); clearNormal(here); @@ -3150,9 +3150,9 @@ NormalizationResult Normalizer::intersectNormalWithTy( else if (const NegationType* ntv = get(there)) { TypeId t = follow(ntv->ty); - if (const PrimitiveType* ptv = get(t)) + if (get(t)) subtractPrimitive(here, ntv->ty); - else if (const SingletonType* stv = get(t)) + else if (get(t)) subtractSingleton(here, follow(ntv->ty)); else if (get(t)) { diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 27a2f471..8fda8fcc 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -14,10 +14,8 @@ LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) -LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) -LUAU_FASTFLAGVARIABLE(LuauFilterOverloadsByArity) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) namespace Luau @@ -54,88 +52,30 @@ std::pair OverloadResolver::selectOverload( bool useFreeTypeBounds ) { - if (FFlag::LuauFilterOverloadsByArity) - { - TypeId t = follow(ty); - - if (const FunctionType* fn = get(t)) - { - if (testFunctionTypeForOverloadSelection(fn, uniqueTypes, argsPack, useFreeTypeBounds)) - return {Analysis::Ok, ty}; - else - return {Analysis::OverloadIsNonviable, ty}; - } - else if (auto it = get(t)) - { - for (TypeId component : it) - { - const FunctionType* fn = get(follow(component)); - // Only consider function overloads with compatible arities - if (!fn || !isArityCompatible(argsPack, fn->argTypes, builtinTypes)) - continue; - - if (testFunctionTypeForOverloadSelection(fn, uniqueTypes, argsPack, useFreeTypeBounds)) - return {Analysis::Ok, component}; - } - } + TypeId t = follow(ty); - return {Analysis::OverloadIsNonviable, ty}; + if (const FunctionType* fn = get(t)) + { + if (testFunctionTypeForOverloadSelection(fn, uniqueTypes, argsPack, useFreeTypeBounds)) + return {Analysis::Ok, ty}; + else + return {Analysis::OverloadIsNonviable, ty}; } - else + else if (auto it = get(t)) { - auto tryOne = [&](TypeId f) + for (TypeId component : it) { - if (auto ftv = get(f)) - { - Subtyping::Variance variance = subtyping.variance; - subtyping.variance = Subtyping::Variance::Contravariant; - subtyping.uniqueTypes = uniqueTypes; - SubtypingResult r; - if (FFlag::LuauSubtypingGenericsDoesntUseVariance) - { - std::vector generics; - generics.reserve(ftv->generics.size()); - for (TypeId g : ftv->generics) - { - g = follow(g); - if (get(g)) - generics.emplace_back(g); - } - r = FFlag::LuauPassBindableGenericsByReference - ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, generics) - : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope, generics); - } - else - r = FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, {}) - : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope); - subtyping.variance = variance; + const FunctionType* fn = get(follow(component)); + // Only consider function overloads with compatible arities + if (!fn || !isArityCompatible(argsPack, fn->argTypes, builtinTypes)) + continue; - if (!useFreeTypeBounds && !r.assumedConstraints.empty()) - return false; - - if (r.isSubtype) - return true; - } - - return false; - }; - - TypeId t = follow(ty); - - if (tryOne(ty)) - return {Analysis::Ok, ty}; - - if (auto it = get(t)) - { - for (TypeId component : it) - { - if (tryOne(component)) - return {Analysis::Ok, component}; - } + if (testFunctionTypeForOverloadSelection(fn, uniqueTypes, argsPack, useFreeTypeBounds)) + return {Analysis::Ok, component}; } - - return {Analysis::OverloadIsNonviable, ty}; } + + return {Analysis::OverloadIsNonviable, ty}; } void OverloadResolver::resolve( @@ -161,17 +101,14 @@ void OverloadResolver::resolve( if (resolution.find(ty) != resolution.end()) continue; - if (FFlag::LuauFilterOverloadsByArity) + if (const FunctionType* fn = get(follow(ty))) { - if (const FunctionType* fn = get(follow(ty))) + // If the overload isn't arity compatible, report the mismatch and don't do more work + const TypePackId argPack = arena->addTypePack(*args); + if (!isArityCompatible(argPack, fn->argTypes, builtinTypes)) { - // If the overload isn't arity compatible, report the mismatch and don't do more work - const TypePackId argPack = arena->addTypePack(*args); - if (!isArityCompatible(argPack, fn->argTypes, builtinTypes)) - { - add(ArityMismatch, ty, {}); - continue; - } + add(ArityMismatch, ty, {}); + continue; } } @@ -322,8 +259,6 @@ void OverloadResolver::maybeEmplaceError( bool OverloadResolver::isArityCompatible(const TypePackId candidate, const TypePackId desired, NotNull builtinTypes) const { - LUAU_ASSERT(FFlag::LuauFilterOverloadsByArity); - auto [candidateHead, candidateTail] = flatten(candidate); auto [desiredHead, desiredTail] = flatten(desired); @@ -368,8 +303,6 @@ bool OverloadResolver::testFunctionTypeForOverloadSelection( bool useFreeTypeBounds ) { - LUAU_ASSERT(FFlag::LuauFilterOverloadsByArity); - Subtyping::Variance variance = subtyping.variance; subtyping.variance = Subtyping::Variance::Contravariant; subtyping.uniqueTypes = uniqueTypes; @@ -701,12 +634,9 @@ std::pair OverloadResolver::checkOverload_ argLocation = argExprs->at(argExprs->size() - 1)->location; // TODO extract location from the SubtypingResult path and argExprs - if (FFlag::LuauVariadicAnyPackShouldBeErrorSuppressing) - { - auto errorSuppression = shouldSuppressErrors(normalizer, *failedSubPack).orElse(shouldSuppressErrors(normalizer, *failedSuperPack)); - if (errorSuppression == ErrorSuppression::Suppress) - break; - } + auto errorSuppression = shouldSuppressErrors(normalizer, *failedSubPack).orElse(shouldSuppressErrors(normalizer, *failedSuperPack)); + if (errorSuppression == ErrorSuppression::Suppress) + break; switch (reason.variance) { @@ -767,8 +697,6 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors) nonFunctions.push_back(ty); break; case ArityMismatch: - if (!FFlag::LuauFilterOverloadsByArity) - LUAU_ASSERT(!errors.empty()); arityMismatches.emplace_back(ty, std::move(errors)); break; case OverloadIsNonviable: diff --git a/Analysis/src/RecursionCounter.cpp b/Analysis/src/RecursionCounter.cpp new file mode 100644 index 00000000..91203047 --- /dev/null +++ b/Analysis/src/RecursionCounter.cpp @@ -0,0 +1,40 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "Luau/RecursionCounter.h" +#include "Luau/Error.h" + +namespace Luau +{ + +RecursionLimitException::RecursionLimitException(const std::string& system) + : InternalCompilerError("Internal recursion counter limit exceeded in " + system) +{ +} + +RecursionCounter::RecursionCounter(int* count) + : count(count) +{ + ++(*count); +} + +RecursionCounter::~RecursionCounter() +{ + LUAU_ASSERT(*count > 0); + --(*count); +} + +RecursionLimiter::RecursionLimiter(const std::string& system, int* count, int limit) + : RecursionCounter(count) +{ + if (!nativeStackGuard.isOk()) + { + throw InternalCompilerError("Stack overflow in " + system); + } + + if (limit > 0 && *count > limit) + { + throw RecursionLimitException(system); + } +} + +} diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 4b73eb9e..1079cdf2 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -474,9 +474,9 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) else if (get(right)) return flip(relate(right, left, seen)); - if (auto ut = get(left)) + if (get(left)) return Relation::Intersects; - else if (auto ut = get(right)) + else if (get(right)) return Relation::Intersects; if (auto ut = get(left)) @@ -1838,19 +1838,19 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right) return arena->addType(IntersectionType{{left, right}}); } - if (auto ut = get(left)) + if (get(left)) { if (get(right)) return intersectUnions(left, right); else return intersectUnionWithType(left, right); } - else if (auto ut = get(right)) + else if (get(right)) return intersectUnionWithType(right, left); - if (auto it = get(left)) + if (get(left)) return intersectIntersectionWithType(left, right); - else if (auto it = get(right)) + else if (get(right)) return intersectIntersectionWithType(right, left); if (get(left)) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index dd7147bf..bd5d6ba9 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -2770,7 +2770,7 @@ SubtypingResult Subtyping::isCovariantWith( ) { SubtypingResult result{false}; - if (auto stringleton = get(subSingleton)) + if (get(subSingleton)) { if (auto metatable = getMetatable(builtinTypes->stringType, builtinTypes)) { diff --git a/Analysis/src/ToDot.cpp b/Analysis/src/ToDot.cpp index 86f94b9e..de953b23 100644 --- a/Analysis/src/ToDot.cpp +++ b/Analysis/src/ToDot.cpp @@ -439,7 +439,7 @@ void StateDot::visitChildren(TypePackId tp, int index) visitChild(vtp->ty, index); } - else if (const FreeTypePack* ftp = get(tp)) + else if (get(tp)) { formatAppend(result, "FreeTypePack %d", index); finishNodeLabel(tp); diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index daa63b72..0e2fc6db 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -35,6 +35,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAGVARIABLE(LuauEnqueueUnionsOfDistributedTypeFunctions) namespace Luau { @@ -380,9 +381,22 @@ struct TypeFunctionReducer result.messages.emplace_back(location, UserDefinedTypeFunctionError{std::move(message)}); if (reduction.result) + { replace(subject, *reduction.result); + if (FFlag::LuauEnqueueUnionsOfDistributedTypeFunctions) + { + for (auto ty : reduction.freshTypes) + { + if constexpr (std::is_same_v) + queuedTys.push_back(ty); + else if constexpr (std::is_same_v) + queuedTps.push_back(ty); + } + } + } else { + LUAU_ASSERT(reduction.freshTypes.empty()); irreducible.insert(subject); if (reduction.error.has_value()) diff --git a/Analysis/src/TypeFunctionReductionGuesser.cpp b/Analysis/src/TypeFunctionReductionGuesser.cpp index e419ab8a..b8e7fa59 100644 --- a/Analysis/src/TypeFunctionReductionGuesser.cpp +++ b/Analysis/src/TypeFunctionReductionGuesser.cpp @@ -184,7 +184,7 @@ TypeFunctionReductionGuessResult TypeFunctionReductionGuesser::guessTypeFunction recommendedAnnotation = builtins->unknownType; else recommendedAnnotation = follow(*guessedReturnType); - if (auto t = get(recommendedAnnotation)) + if (get(recommendedAnnotation)) recommendedAnnotation = builtins->unknownType; toInfer.clear(); @@ -270,14 +270,14 @@ std::optional TypeFunctionReductionGuesser::tryAssignOperandType(TypeId { // Because we collect innermost instances first, if we see a type function instance as an operand, // We try to check if we guessed a type for it - if (auto tfit = get(ty)) + if (get(ty)) { if (functionReducesTo.contains(ty)) return {functionReducesTo[ty]}; } // If ty is a generic, we need to check if we inferred a substitution - if (auto gt = get(ty)) + if (get(ty)) { if (substitutable.contains(ty)) return {substitutable[ty]}; @@ -338,12 +338,12 @@ void TypeFunctionReductionGuesser::inferTypeFunctionSubstitutions(TypeId ty, con { TypeId arg = follow(instance->typeArguments[i]); TypeId inference = follow(result.operandInference[i]); - if (auto tfit = get(arg)) + if (get(arg)) { if (!functionReducesTo.contains(arg)) functionReducesTo.try_insert(arg, inference); } - else if (auto gt = get(arg)) + else if (get(arg)) substitutable[arg] = inference; } } diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 2dfd21d5..96aa8a47 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -275,7 +275,7 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty) return "never"; else if (get(ty)) return "any"; - else if (auto s = get(ty)) + else if (get(ty)) return "singleton"; else if (get(ty)) return "negation"; @@ -893,7 +893,7 @@ static int setTableIndexer(lua_State* L) TypeFunctionTypeId key = getTypeUserData(L, 2); TypeFunctionTypeId value = getTypeUserData(L, 3); - if (auto tfnt = get(key)) + if (get(key)) { tftt->indexer = std::nullopt; return 0; @@ -2373,11 +2373,11 @@ class TypeFunctionCloner break; } } - else if (auto u = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionUnknownType{}); - else if (auto a = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionNeverType{}); - else if (auto a = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionAnyType{}); else if (auto s = get(ty)) { @@ -2386,20 +2386,20 @@ class TypeFunctionCloner else if (auto ss = get(s)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionSingletonType{TypeFunctionStringSingleton{ss->value}}); } - else if (auto u = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionUnionType{{}}); - else if (auto i = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionIntersectionType{{}}); - else if (auto n = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionNegationType{{}}); - else if (auto t = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionTableType{{}, std::nullopt, std::nullopt}); - else if (auto f = get(ty)) + else if (get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } - else if (auto c = get(ty)) + else if (get(ty)) target = ty; // Don't copy a class since they are immutable else if (auto g = get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name}); @@ -2418,9 +2418,9 @@ class TypeFunctionCloner // Create a shallow serialization TypeFunctionTypePackId target = {}; - if (auto tPack = get(tp)) + if (get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}}); - else if (auto vPack = get(tp)) + else if (get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{}); else if (auto gPack = get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionGenericTypePack{gPack->isNamed, gPack->name}); diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index e9b09d47..a5d5b26c 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -173,11 +173,11 @@ class TypeFunctionSerializer } } } - else if (auto u = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionUnknownType{}); - else if (auto a = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionNeverType{}); - else if (auto a = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionAnyType{}); else if (auto s = get(ty)) { @@ -191,22 +191,22 @@ class TypeFunctionSerializer state->errors.push_back(error); } } - else if (auto u = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionUnionType{{}}); - else if (auto i = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionIntersectionType{{}}); - else if (auto n = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionNegationType{{}}); - else if (auto t = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionTableType{{}, std::nullopt, std::nullopt}); - else if (auto m = get(ty)) + else if (get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionTableType{{}, std::nullopt, std::nullopt}); - else if (auto f = get(ty)) + else if (get(ty)) { TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } - else if (auto c = get(ty)) + else if (get(ty)) { // Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original // class @@ -241,9 +241,9 @@ class TypeFunctionSerializer // Create a shallow serialization TypeFunctionTypePackId target = {}; - if (auto tPack = get(tp)) + if (get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{{}}); - else if (auto vPack = get(tp)) + else if (get(tp)) target = typeFunctionRuntime->typePackArena.allocate(TypeFunctionVariadicTypePack{}); else if (auto gPack = get(tp)) { @@ -683,11 +683,11 @@ class TypeFunctionDeserializer state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); } } - else if (auto u = get(ty)) + else if (get(ty)) target = state->ctx->builtins->unknownType; - else if (auto n = get(ty)) + else if (get(ty)) target = state->ctx->builtins->neverType; - else if (auto a = get(ty)) + else if (get(ty)) target = state->ctx->builtins->anyType; else if (auto s = get(ty)) { @@ -698,11 +698,11 @@ class TypeFunctionDeserializer else state->ctx->ice->ice("Deserializing user defined type function arguments: mysterious type is being deserialized"); } - else if (auto u = get(ty)) + else if (get(ty)) target = state->ctx->arena->addTV(Type(UnionType{{}})); - else if (auto i = get(ty)) + else if (get(ty)) target = state->ctx->arena->addTV(Type(IntersectionType{{}})); - else if (auto n = get(ty)) + else if (get(ty)) target = state->ctx->arena->addType(NegationType{state->ctx->builtins->unknownType}); else if (auto t = get(ty); t && !t->metatable.has_value()) target = state->ctx->arena->addType(TableType{TableType::Props{}, std::nullopt, TypeLevel{}, TableState::Sealed}); @@ -711,7 +711,7 @@ class TypeFunctionDeserializer TypeId emptyTable = state->ctx->arena->addType(TableType{TableType::Props{}, std::nullopt, TypeLevel{}, TableState::Sealed}); target = state->ctx->arena->addType(MetatableType{emptyTable, emptyTable}); } - else if (auto f = get(ty)) + else if (get(ty)) { TypePackId emptyTypePack = state->ctx->arena->addTypePack(TypePack{}); target = state->ctx->arena->addType(FunctionType{emptyTypePack, emptyTypePack, {}, false}); @@ -762,11 +762,11 @@ class TypeFunctionDeserializer // Create a shallow deserialization TypePackId target = {}; - if (auto tPack = get(tp)) + if (get(tp)) { target = state->ctx->arena->addTypePack(TypePack{}); } - else if (auto vPack = get(tp)) + else if (get(tp)) { target = state->ctx->arena->addTypePack(VariadicTypePack{}); } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 5e56abad..c1dd4de1 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -2011,7 +2011,7 @@ WithPredicate TypeChecker::checkExpr(const ScopePtr& scope, const AstExp { return {pack->head.empty() ? nilType : pack->head[0], std::move(result.predicates)}; } - else if (const FreeTypePack* ftp = get(retPack)) + else if (get(retPack)) { TypeId head = freshType(scope->level); TypePackId pack = addTypePack(TypePackVar{TypePack{{head}, freshTypePack(scope->level)}}); @@ -3501,7 +3501,7 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex } } - if (const ExternType* exprExternType = get(exprType)) + if (get(exprType)) { if (isNonstrictMode()) return unknownType; @@ -4541,7 +4541,7 @@ std::unique_ptr> TypeChecker::checkCallOverload( if (get(fn)) return std::make_unique>(uninhabitableTypePack); - if (auto ftv = get(fn)) + if (get(fn)) { // fn is one of the overloads of actualFunctionType, which // has been instantiated, so is a monotype. We can therefore diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 046a6109..7f0b85d3 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -15,9 +15,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils) LUAU_FASTFLAG(LuauEmplaceNotPushBack) -LUAU_FASTFLAGVARIABLE(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauPushTypeConstraint2) -LUAU_FASTFLAG(LuauFilterOverloadsByArity) namespace Luau { @@ -384,7 +382,7 @@ TypePack extendTypePack( return result; } - else if (auto etp = getMutable(pack)) + else if (getMutable(pack)) { while (result.head.size() < length) result.head.push_back(builtinTypes->errorType); @@ -507,13 +505,10 @@ ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypePackId { // Flatten t where t = ...any will produce a type pack [ {}, t] // which trivially fails the tail check below, which is why we need to special case here - if (FFlag::LuauVariadicAnyPackShouldBeErrorSuppressing) + if (auto tpId = get(follow(tp))) { - if (auto tpId = get(follow(tp))) - { - if (get(follow(tpId->ty))) - return ErrorSuppression::Suppress; - } + if (get(follow(tpId->ty))) + return ErrorSuppression::Suppress; } auto [tys, tail] = flatten(tp); @@ -750,8 +745,6 @@ AstExpr* unwrapGroup(AstExpr* expr) bool isOptionalType(TypeId ty, NotNull builtinTypes) { - LUAU_ASSERT(FFlag::LuauFilterOverloadsByArity); - ty = follow(ty); if (ty == builtinTypes->nilType || ty == builtinTypes->anyType || ty == builtinTypes->unknownType) diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index a8da0ef3..18f18354 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -1452,7 +1452,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal else if (isBlocked(log, superTp)) blockedTypePacks.push_back(superTp); - if (auto superFree = log.getMutable(superTp)) + if (log.getMutable(superTp)) { if (!occursCheck(superTp, subTp, /* reversed = */ true)) { @@ -1460,7 +1460,7 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal log.replace(superTp, Unifiable::Bound(widen(subTp))); } } - else if (auto subFree = log.getMutable(subTp)) + else if (log.getMutable(subTp)) { if (!occursCheck(subTp, superTp, /* reversed = */ false)) { diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index d8ee89b7..22a77fa4 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -19,7 +19,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) // See docs/SyntaxChanges.md for an explanation. LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) -LUAU_FASTFLAGVARIABLE(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax) LUAU_FASTFLAGVARIABLE(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAGVARIABLE(LuauAutocompleteAttributes) @@ -4023,33 +4022,27 @@ AstExpr* Parser::parseInterpString() return reportExprError(endLocation, {}, "Double braces are not permitted within interpolated strings; did you mean '\\{'?"); case Lexeme::BrokenString: nextLexeme(); - if (!FFlag::LuauParseIncompleteInterpStringsWithLocation) - return reportExprError(endLocation, {}, "Malformed interpolated string; did you forget to add a '}'?"); LUAU_FALLTHROUGH; case Lexeme::Eof: { - if (FFlag::LuauParseIncompleteInterpStringsWithLocation) + AstArray> stringsArray = copy(strings); + AstArray exprs = copy(expressions); + AstExprInterpString* node = + allocator.alloc(Location{startLocation, lexer.previousLocation()}, stringsArray, exprs); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(copy(sourceStrings), copy(stringPositions)); + if (auto top = lexer.peekBraceStackTop()) { - AstArray> stringsArray = copy(strings); - AstArray exprs = copy(expressions); - AstExprInterpString* node = - allocator.alloc(Location{startLocation, lexer.previousLocation()}, stringsArray, exprs); - if (options.storeCstData) - cstNodeMap[node] = allocator.alloc(copy(sourceStrings), copy(stringPositions)); - if (auto top = lexer.peekBraceStackTop()) - { - // We are in a broken interpolated string, the top of the stack is non empty, we are missing '}' - if (*top == Lexer::BraceType::InterpolatedString) - report(lexer.previousLocation(), "Malformed interpolated string; did you forget to add a '}'?"); - } - else - { - // We are in a broken interpolated string, the top of the stack is empty, we are missing '`'. - report(lexer.previousLocation(), "Malformed interpolated string; did you forget to add a '`'?"); - } - return node; + // We are in a broken interpolated string, the top of the stack is non empty, we are missing '}' + if (*top == Lexer::BraceType::InterpolatedString) + report(lexer.previousLocation(), "Malformed interpolated string; did you forget to add a '}'?"); } - LUAU_FALLTHROUGH; + else + { + // We are in a broken interpolated string, the top of the stack is empty, we are missing '`'. + report(lexer.previousLocation(), "Malformed interpolated string; did you forget to add a '`'?"); + } + return node; } default: return reportExprError(endLocation, {}, "Malformed interpolated string, got %s", lexer.current().toString().c_str()); diff --git a/CMakeLists.txt b/CMakeLists.txt index ff9dc00f..861f7086 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,6 +84,7 @@ target_link_libraries(Luau.Compiler PUBLIC Luau.Ast) target_compile_features(Luau.Config PUBLIC cxx_std_17) target_include_directories(Luau.Config PUBLIC Config/include) target_link_libraries(Luau.Config PUBLIC Luau.Ast) +target_link_libraries(Luau.Config PRIVATE Luau.Compiler Luau.VM) target_compile_features(Luau.Analysis PUBLIC cxx_std_17) target_include_directories(Luau.Analysis PUBLIC Analysis/include) diff --git a/Config/include/Luau/Config.h b/Config/include/Luau/Config.h index 89d018d2..ce5190ca 100644 --- a/Config/include/Luau/Config.h +++ b/Config/include/Luau/Config.h @@ -91,6 +91,13 @@ struct ConfigOptions std::optional aliasOptions = std::nullopt; }; +std::optional parseAlias( + Config& config, + const std::string& aliasKey, + const std::string& aliasValue, + const std::optional& aliasOptions +); + std::optional parseConfig(const std::string& contents, Config& config, const ConfigOptions& options = ConfigOptions{}); } // namespace Luau diff --git a/Config/include/Luau/LuauConfig.h b/Config/include/Luau/LuauConfig.h new file mode 100644 index 00000000..61af99dc --- /dev/null +++ b/Config/include/Luau/LuauConfig.h @@ -0,0 +1,102 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Common.h" +#include "Luau/Config.h" +#include "Luau/DenseHash.h" +#include "Luau/Variant.h" + +#include + +struct lua_State; + +namespace Luau +{ + +constexpr const char* kLuauConfigName = ".config.luau"; + +struct ConfigTableKey : public Variant +{ + ConfigTableKey() = default; + ConfigTableKey(const std::string& s) + : Variant(s) + { + } + ConfigTableKey(std::string&& s) + : Variant(std::move(s)) + { + } + ConfigTableKey(const char* s) + : Variant(std::string(s)) + { + } + ConfigTableKey(double d) + : Variant(d) + { + } + + std::string toString() const + { + if (const double* number = get_if()) + return std::to_string(*number); + else if (const std::string* str = get_if()) + return *str; + + LUAU_UNREACHABLE(); + } +}; + +template +struct VariantHashDefault +{ + size_t operator()(const VariantType& variant) const + { + size_t result = 0; + visit( + [&result](auto&& value) + { + result = detail::DenseHashDefault>{}(value); + }, + variant + ); + return result; + } +}; + +// Forward declaration +struct ConfigValue; + +struct ConfigTable : public DenseHashMap> +{ + ConfigTable() + : DenseHashMap>({}) + { + } +}; + +struct ConfigValue : public Variant +{ + using Variant::Variant; +}; + +struct InterruptCallbacks +{ + std::function initCallback; + void (*interruptCallback)(lua_State* L, int gc); +}; + +// Executes the contents of `source` in a sandboxed Luau VM and extracts the +// configuration table that it returns. If extraction fails, std::nullopt is +// returned and `error` is populated with the error message, if provided. +std::optional extractConfig(const std::string& source, const InterruptCallbacks& callbacks, std::string* error = nullptr); + +// Extracts a Luau::Config object into `config` from the configuration code in +// `source`, returning an error message if extraction fails. +std::optional extractLuauConfig( + const std::string& source, + Config& config, + std::optional aliasOptions, + InterruptCallbacks callbacks +); + +} // namespace Luau diff --git a/Config/src/Config.cpp b/Config/src/Config.cpp index 44cbe2e5..776cb834 100644 --- a/Config/src/Config.cpp +++ b/Config/src/Config.cpp @@ -185,7 +185,7 @@ bool isValidAlias(const std::string& alias) return true; } -static Error parseAlias( +Error parseAlias( Config& config, const std::string& aliasKey, const std::string& aliasValue, diff --git a/Config/src/LuauConfig.cpp b/Config/src/LuauConfig.cpp new file mode 100644 index 00000000..1efcb688 --- /dev/null +++ b/Config/src/LuauConfig.cpp @@ -0,0 +1,291 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/LuauConfig.h" + +#include "Luau/Compiler.h" +#include "Luau/Config.h" + +#include "lua.h" +#include "lualib.h" + +#include +#include +#include +#include + +#define RETURN_WITH_ERROR(msg) \ + do \ + { \ + if (error) \ + *error = msg; \ + return std::nullopt; \ + } while (false) + +namespace Luau +{ + +struct ThreadPopper +{ + explicit ThreadPopper(lua_State* L) + : L(L) + { + LUAU_ASSERT(L); + } + + ThreadPopper(const ThreadPopper&) = delete; + ThreadPopper& operator=(const ThreadPopper&) = delete; + + ~ThreadPopper() + { + lua_pop(L, 1); + } + + lua_State* L = nullptr; +}; + +static std::optional serializeTable(lua_State* L, std::string* error) +{ + ThreadPopper popper(L); // Remove table from stack after processing + ConfigTable table; + + lua_pushnil(L); + while (lua_next(L, -2) != 0) + { + ThreadPopper popper(L); // Remove value from stack after processing + + // Process key + ConfigTableKey key; + switch (lua_type(L, -2)) + { + case LUA_TNUMBER: + key = lua_tonumber(L, -2); + break; + case LUA_TSTRING: + key = std::string{lua_tostring(L, -2)}; + break; + default: + RETURN_WITH_ERROR("configuration table keys must be strings or numbers"); + } + + // Process value + switch (lua_type(L, -1)) + { + case LUA_TNUMBER: + table[key] = lua_tonumber(L, -1); + break; + case LUA_TSTRING: + table[key] = std::string{lua_tostring(L, -1)}; + break; + case LUA_TBOOLEAN: + table[key] = static_cast(lua_toboolean(L, -1)); + break; + case LUA_TTABLE: + { + lua_pushvalue(L, -1); // Copy table for recursive call + if (std::optional nested = serializeTable(L, error)) + table[key] = std::move(*nested); + else + return std::nullopt; // Error already set in recursive call + break; + } + default: + std::string msg = "configuration value for key \"" + key.toString() + "\" must be a string, number, boolean, or nested table"; + RETURN_WITH_ERROR(std::move(msg)); + } + } + + return table; +} + +std::optional extractConfig(const std::string& source, const InterruptCallbacks& callbacks, std::string* error) +{ + // Initialize Luau VM + std::unique_ptr state{luaL_newstate(), lua_close}; + lua_State* L = state.get(); + luaL_openlibs(L); + luaL_sandbox(L); + + // Compile and load source code + std::string bytecode = compile(source); + if (luau_load(L, "=config", bytecode.data(), bytecode.size(), 0) != 0) + RETURN_WITH_ERROR(lua_tostring(L, -1)); + + // Execute configuration + if (callbacks.initCallback) + callbacks.initCallback(L); + lua_callbacks(L)->interrupt = callbacks.interruptCallback; + switch (lua_resume(L, nullptr, 0)) + { + case LUA_OK: + break; + case LUA_BREAK: // debugging not supported, at least for now + case LUA_YIELD: + RETURN_WITH_ERROR("configuration execution cannot yield"); + default: + RETURN_WITH_ERROR(lua_tostring(L, -1)); + } + + // Extract returned table + if (lua_gettop(L) != 1) + RETURN_WITH_ERROR("configuration must return exactly one value"); + + if (lua_type(L, -1) != LUA_TTABLE) + RETURN_WITH_ERROR("configuration did not return a table"); + + return serializeTable(L, error); +} + +static std::optional createLuauConfigFromLuauTable( + Config& config, + const ConfigTable& luauTable, + std::optional aliasOptions +) +{ + for (const auto& [k, v] : luauTable) + { + const std::string* key = k.get_if(); + if (!key) + return "configuration keys in \"luau\" table must be strings"; + + if (*key == "languagemode") + { + const std::string* value = v.get_if(); + if (!value) + return "configuration value for key \"languagemode\" must be a string"; + + if (std::optional errorMessage = parseModeString(config.mode, *value)) + return errorMessage; + } + + if (*key == "lint") + { + const ConfigTable* lint = v.get_if(); + if (!lint) + return "configuration value for key \"lint\" must be a table"; + + // Handle wildcard first to ensure overrides work as expected. + if (const ConfigValue* value = lint->find("*")) + { + const bool* enabled = value->get_if(); + if (!enabled) + return "configuration values in \"lint\" table must be booleans"; + + if (std::optional errorMessage = + parseLintRuleString(config.enabledLint, config.fatalLint, "*", *enabled ? "true" : "false")) + return errorMessage; + } + + for (const auto& [k, v] : *lint) + { + const std::string* warningName = k.get_if(); + if (!warningName) + return "configuration keys in \"lint\" table must be strings"; + + if (*warningName == "*") + continue; // Handled above + + const bool* enabled = v.get_if(); + if (!enabled) + return "configuration values in \"lint\" table must be booleans"; + + if (std::optional errorMessage = + parseLintRuleString(config.enabledLint, config.fatalLint, *warningName, *enabled ? "true" : "false")) + return errorMessage; + } + } + + if (*key == "linterrors") + { + const bool* value = v.get_if(); + if (!value) + return "configuration value for key \"linterrors\" must be a boolean"; + + config.lintErrors = *value; + } + + if (*key == "typeerrors") + { + const bool* value = v.get_if(); + if (!value) + return "configuration value for key \"typeerrors\" must be a boolean"; + + config.typeErrors = *value; + } + + if (*key == "globals") + { + const ConfigTable* globalsTable = v.get_if(); + if (!globalsTable) + return "configuration value for key \"globals\" must be an array of strings"; + + std::vector globals; + globals.resize(globalsTable->size()); + + for (const auto& [k, v] : *globalsTable) + { + const double* key = k.get_if(); + if (!key) + return "configuration array \"globals\" must only have numeric keys"; + + const size_t index = static_cast(*key); + if (index < 1 || globalsTable->size() < index) + return "configuration array \"globals\" contains invalid numeric key"; + + const std::string* global = v.get_if(); + if (!global) + return "configuration value in \"globals\" table must be a string"; + + LUAU_ASSERT(0 <= index - 1 && index - 1 < globalsTable->size()); + globals[index - 1] = *global; + } + + config.globals = std::move(globals); + } + + if (*key == "aliases") + { + const ConfigTable* aliases = v.get_if(); + if (!aliases) + return "configuration value for key \"aliases\" must be a table"; + + for (const auto& [k, v] : *aliases) + { + const std::string* aliasKey = k.get_if(); + if (!aliasKey) + return "configuration keys in \"aliases\" table must be strings"; + + const std::string* aliasValue = v.get_if(); + if (!aliasValue) + return "configuration values in \"aliases\" table must be strings"; + + if (std::optional errorMessage = parseAlias(config, *aliasKey, *aliasValue, aliasOptions)) + return errorMessage; + } + } + } + + return std::nullopt; +} + +std::optional extractLuauConfig( + const std::string& source, + Config& config, + std::optional aliasOptions, + InterruptCallbacks callbacks +) +{ + std::string error; + std::optional configTable = extractConfig(source, callbacks, &error); + if (!configTable) + return error; + + if (!configTable->contains("luau")) + return std::nullopt; + + ConfigTable* luauTable = (*configTable)["luau"].get_if(); + if (!luauTable) + return "configuration value for key \"luau\" must be a table"; + + return createLuauConfigFromLuauTable(config, *luauTable, aliasOptions); +} + +} // namespace Luau diff --git a/Makefile b/Makefile index 50a7f9b8..1b6a4517 100644 --- a/Makefile +++ b/Makefile @@ -152,7 +152,7 @@ endif # target-specific flags $(AST_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include $(COMPILER_OBJECTS): CXXFLAGS+=-std=c++17 -ICompiler/include -ICommon/include -IAst/include -$(CONFIG_OBJECTS): CXXFLAGS+=-std=c++17 -IConfig/include -ICommon/include -IAst/include +$(CONFIG_OBJECTS): CXXFLAGS+=-std=c++17 -IConfig/include -ICommon/include -IAst/include -ICompiler/include -IVM/include $(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IEqSat/include -IConfig/include -ICompiler/include -IVM/include $(EQSAT_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IEqSat/include $(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include -IVM/include -IVM/src # Code generation needs VM internals diff --git a/Sources.cmake b/Sources.cmake index d004bbc8..fa7deb58 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -69,9 +69,11 @@ target_sources(Luau.Compiler PRIVATE target_sources(Luau.Config PRIVATE Config/include/Luau/Config.h Config/include/Luau/LinterConfig.h + Config/include/Luau/LuauConfig.h Config/src/Config.cpp Config/src/LinterConfig.cpp + Config/src/LuauConfig.cpp ) # Luau.CodeGen Sources @@ -185,6 +187,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Constraint.h Analysis/include/Luau/ConstraintGenerator.h Analysis/include/Luau/ConstraintSet.h + Analysis/include/Luau/NativeStackGuard.h Analysis/include/Luau/ConstraintSolver.h Analysis/include/Luau/ControlFlow.h Analysis/include/Luau/DataFlowGraph.h @@ -280,6 +283,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/FragmentAutocomplete.cpp Analysis/src/Frontend.cpp Analysis/src/Generalization.cpp + Analysis/src/NativeStackGuard.cpp Analysis/src/GlobalTypes.cpp Analysis/src/InferPolarity.cpp Analysis/src/Instantiation.cpp @@ -293,6 +297,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/Normalize.cpp Analysis/src/OverloadResolution.cpp Analysis/src/Quantify.cpp + Analysis/src/RecursionCounter.cpp Analysis/src/Refinement.cpp Analysis/src/RequireTracer.cpp Analysis/src/Scope.cpp diff --git a/VM/src/lobject.h b/VM/src/lobject.h index 626d8a85..11db0bf2 100644 --- a/VM/src/lobject.h +++ b/VM/src/lobject.h @@ -287,7 +287,6 @@ typedef struct Proto { CommonHeader; - uint8_t nups; // number of upvalues uint8_t numparams; uint8_t is_vararg; @@ -296,14 +295,12 @@ typedef struct Proto TValue* k; // constants used by the function Instruction* code; // function bytecode - struct Proto** p; // functions defined inside the function const Instruction* codeentry; void* execdata; uintptr_t exectarget; - uint8_t* lineinfo; // for each instruction, line number as a delta from baseline int* abslineinfo; // baseline line info, one entry for each 1< GCthreshold, run GC step size_t totalbytes; // number of bytes currently allocated int gcgoal; // see LUAI_GCGOAL int gcstepmul; // see LUAI_GCSTEPMUL - int gcstepsize; // see LUAI_GCSTEPSIZE + int gcstepsize; // see LUAI_GCSTEPSIZE struct lua_Page* freepages[LUA_SIZECLASSES]; // free page linked list for each size class for non-collectable objects struct lua_Page* freegcopages[LUA_SIZECLASSES]; // free page linked list for each size class for collectable objects @@ -195,12 +190,11 @@ typedef struct global_State size_t memcatbytes[LUA_MEMORY_CATEGORIES]; // total amount of memory used by each memory category - struct lua_State* mainthread; - UpVal uvhead; // head of double-linked list of all open upvalues - struct LuaTable* mt[LUA_T_COUNT]; // metatables for basic types - TString* ttname[LUA_T_COUNT]; // names for basic types - TString* tmname[TM_N]; // array with tag-method names + UpVal uvhead; // head of double-linked list of all open upvalues + struct LuaTable* mt[LUA_T_COUNT]; // metatables for basic types + TString* ttname[LUA_T_COUNT]; // names for basic types + TString* tmname[TM_N]; // array with tag-method names TValue pseudotemp; // storage for temporary values used in pseudo2addr @@ -243,31 +237,26 @@ struct lua_State bool isactive; // thread is currently executing, stack may be mutated without barriers bool singlestep; // call debugstep hook after each instruction - StkId top; // first free slot in the stack StkId base; // base of current function global_State* global; CallInfo* ci; // call info for current function StkId stack_last; // last free slot in the stack - StkId stack; // stack base - + StkId stack; // stack base CallInfo* end_ci; // points after end of ci array - CallInfo* base_ci; // array of CallInfo's - + CallInfo* base_ci; // array of CallInfo's int stacksize; - int size_ci; // size of array `base_ci' - + int size_ci; // size of array `base_ci' unsigned short nCcalls; // number of nested C calls - unsigned short baseCcalls; // nested C calls when resuming coroutine + unsigned short baseCcalls; // nested C calls when resuming coroutine int cachedslot; // when table operations or INDEX/NEWINDEX is invoked from Luau, what is the expected slot for lookup? - LuaTable* gt; // table of globals - UpVal* openupval; // list of open upvalues in this stack + UpVal* openupval; // list of open upvalues in this stack GCObject* gclist; TString* namecall; // when invoked from Luau using NAMECALL, what method do we need to invoke? diff --git a/VM/src/ltm.cpp b/VM/src/ltm.cpp index 58f87260..800c76bc 100644 --- a/VM/src/ltm.cpp +++ b/VM/src/ltm.cpp @@ -31,7 +31,6 @@ const char* const luaT_typenames[] = { const char* const luaT_eventname[] = { // ORDER TM - "__index", "__newindex", "__mode", @@ -42,7 +41,6 @@ const char* const luaT_eventname[] = { "__eq", - "__add", "__sub", "__mul", @@ -52,7 +50,6 @@ const char* const luaT_eventname[] = { "__pow", "__unm", - "__lt", "__le", "__concat", diff --git a/VM/src/ltm.h b/VM/src/ltm.h index f3294b64..169a0b96 100644 --- a/VM/src/ltm.h +++ b/VM/src/ltm.h @@ -11,7 +11,6 @@ // clang-format off typedef enum { - TM_INDEX, TM_NEWINDEX, TM_MODE, @@ -22,7 +21,6 @@ typedef enum TM_EQ, // last tag method with `fast' access - TM_ADD, TM_SUB, TM_MUL, @@ -32,7 +30,6 @@ typedef enum TM_POW, TM_UNM, - TM_LT, TM_LE, TM_CONCAT, diff --git a/tests/Config.test.cpp b/tests/Config.test.cpp index 690c4c37..bc755cd3 100644 --- a/tests/Config.test.cpp +++ b/tests/Config.test.cpp @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Config.h" #include "Luau/Frontend.h" +#include "Luau/LinterConfig.h" +#include "Luau/LuauConfig.h" #include "Fixture.h" #include "ScopedFlags.h" @@ -8,6 +10,11 @@ #include "doctest.h" #include +#include +#include +#include +#include +#include using namespace Luau; @@ -175,3 +182,184 @@ TEST_CASE("lint_rules_compat") } TEST_SUITE_END(); + +TEST_SUITE_BEGIN("ConfigLuauTest"); + +TEST_CASE("extract_configuration") +{ + std::string source = R"( + local config = {} + config.luau = {} + + config.luau.languagemode = "strict" + config.luau.lint = { + ["*"] = true, + LocalUnused = false + } + config.luau.linterrors = true + config.luau.typeerrors = true + config.luau.globals = {"expect"} + config.luau.aliases = { + src = "./src" + } + + return config + )"; + + std::string error; + std::optional configTable = extractConfig(source, InterruptCallbacks{}, &error); + REQUIRE(configTable); + + CHECK(configTable->size() == 1); + REQUIRE(configTable->contains("luau")); + ConfigTable* luau = (*configTable)["luau"].get_if(); + REQUIRE(luau); + CHECK(luau->size() == 6); + + REQUIRE(luau->contains("languagemode")); + std::string* languageMode = (*luau)["languagemode"].get_if(); + REQUIRE(languageMode); + CHECK(*languageMode == "strict"); + + REQUIRE(luau->contains("lint")); + ConfigTable* lint = (*luau)["lint"].get_if(); + REQUIRE(lint); + CHECK(lint->size() == 2); + REQUIRE(lint->contains("*")); + bool* all = (*lint)["*"].get_if(); + REQUIRE(all); + CHECK(*all); + bool* localUnused = (*lint)["LocalUnused"].get_if(); + REQUIRE(localUnused); + CHECK(!(*localUnused)); + + REQUIRE(luau->contains("linterrors")); + bool* lintErrors = (*luau)["linterrors"].get_if(); + REQUIRE(lintErrors); + CHECK(*lintErrors); + + REQUIRE(luau->contains("typeerrors")); + bool* typeErrors = (*luau)["typeerrors"].get_if(); + REQUIRE(typeErrors); + CHECK(*typeErrors); + + REQUIRE(luau->contains("globals")); + ConfigTable* globalsTable = (*luau)["globals"].get_if(); + REQUIRE(globalsTable); + CHECK(globalsTable->size() == 1); + REQUIRE(globalsTable->contains(1)); + std::string* global = (*globalsTable)[1].get_if(); + REQUIRE(global); + CHECK(*global == "expect"); + + REQUIRE(luau->contains("aliases")); + ConfigTable* aliases = (*luau)["aliases"].get_if(); + REQUIRE(aliases); + CHECK(aliases->size() == 1); + REQUIRE(aliases->contains("src")); + std::string* alias = (*aliases)["src"].get_if(); + REQUIRE(alias); + CHECK(*alias == "./src"); +} + +TEST_CASE("extract_luau_configuration") +{ + std::string source = R"( + local config = {} + config.luau = {} + + config.luau.languagemode = "strict" + config.luau.lint = { + ["*"] = true, + LocalUnused = false + } + config.luau.linterrors = true + config.luau.typeerrors = true + config.luau.globals = {"expect"} + config.luau.aliases = { + src = "./src" + } + + return config + )"; + + ConfigOptions::AliasOptions aliasOptions; + aliasOptions.configLocation = "/some/path"; + aliasOptions.overwriteAliases = true; + + Config config; + std::optional error = extractLuauConfig(source, config, std::move(aliasOptions), InterruptCallbacks{}); + REQUIRE(!error); + + CHECK_EQ(config.mode, Mode::Strict); + + for (LintWarning::Code code = static_cast(0); code <= LintWarning::Code::Code__Count; code = LintWarning::Code(int(code) + 1)) + { + if (code == LintWarning::Code_LocalUnused) + CHECK(!config.enabledLint.isEnabled(code)); + else + CHECK(config.enabledLint.isEnabled(code)); + } + + CHECK_EQ(config.lintErrors, true); + CHECK_EQ(config.typeErrors, true); + + CHECK(config.globals.size() == 1); + CHECK(config.globals[0] == "expect"); + + CHECK(config.aliases.size() == 1); + REQUIRE(config.aliases.contains("src")); + CHECK(config.aliases["src"].value == "./src"); +} + +TEST_CASE("yielded_configuration") +{ + std::string source = R"( + coroutine.yield() + )"; + + std::string error; + std::optional configTable = extractConfig(source, InterruptCallbacks{}, &error); + REQUIRE(!configTable); + CHECK(error == "configuration execution cannot yield"); +} + +TEST_CASE("interrupt_execution" * doctest::timeout(2)) +{ + std::string source = R"( + while true do end + )"; + + std::string error; + std::optional configTable = extractConfig( + source, + { + nullptr, + [](lua_State* L, int gc) + { + throw std::runtime_error("interrupted"); + }, + }, + &error + ); + REQUIRE(!configTable); + CHECK(error.find("interrupted") != std::string_view::npos); +} + +TEST_CASE("validate_return_value") +{ + std::vector> testCases; + testCases.emplace_back("", "configuration must return exactly one value"); + testCases.emplace_back("return {}, {}", "configuration must return exactly one value"); + testCases.emplace_back("return 'a string'", "configuration did not return a table"); + + for (const auto& [source, expectedError] : testCases) + { + std::string error; + std::optional configTable = extractConfig(source, InterruptCallbacks{}, &error); + REQUIRE(!configTable); + CHECK(error == expectedError); + } +} + +TEST_SUITE_END(); diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index f918f0dd..3dea764f 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -30,6 +30,7 @@ LUAU_FASTFLAG(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauForceAllNewSolverTests); LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) +LUAU_FASTINT(LuauStackGuardThreshold) extern std::optional randomSeed; // tests/main.cpp @@ -737,6 +738,17 @@ Frontend& Fixture::getFrontend() return *frontend; } +void Fixture::limitStackSize(size_t size) +{ + // The FInt is designed to trip when the amount of available address + // space goes below some threshold, but for this API, the convenient thing + // is to specify how much the test should be allowed to use. We need to + // do a tiny amount of arithmetic to convert. + + uintptr_t addressSpaceSize = getStackAddressSpaceSize(); + + dynamicScopedInts.emplace_back(FInt::LuauStackGuardThreshold, (int)(addressSpaceSize - size)); +} BuiltinsFixture::BuiltinsFixture(bool prepareAutocomplete) : Fixture(prepareAutocomplete) diff --git a/tests/Fixture.h b/tests/Fixture.h index 0163484a..83baaf97 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -189,6 +189,11 @@ struct Fixture const BuiltinTypeFunctions& getBuiltinTypeFunctions(); virtual Frontend& getFrontend(); + // On platforms that support it, adjust our internal stack guard to + // limit how much address space we should use before we blow up. We + // use this to test the stack guard itself. + void limitStackSize(size_t size); + private: bool hasDumpedErrors = false; @@ -199,6 +204,8 @@ struct Fixture TypeArena simplifierArena; SimplifierPtr simplifier{nullptr, nullptr}; + + std::vector dynamicScopedInts; }; struct BuiltinsFixture : Fixture diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index b08d125c..cb7d5248 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -28,9 +28,6 @@ LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauBetterReverseDependencyTracking) LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule) -LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment) -LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) -LUAU_FASTFLAG(LuauForInProvidesRecommendations) LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) LUAU_FASTFLAG(LuauForInRangesConsiderInLocation) @@ -63,9 +60,6 @@ struct FragmentAutocompleteFixtureImpl : BaseType { static_assert(std::is_base_of_v, "BaseType must be a descendant of Fixture"); - ScopedFastFlag sffLuauPopulateSelfTypesInFragment{FFlag::LuauPopulateSelfTypesInFragment, true}; - ScopedFastFlag luauParseIncompleteInterpStringsWithLocation{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; - FragmentAutocompleteFixtureImpl() : BaseType(true) { @@ -764,7 +758,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "partial_for_numeric_in_condition") { - ScopedFastFlag sff{FFlag::LuauForInProvidesRecommendations, true}; auto region = getAutocompleteRegion( R"( for c = 1,3 @@ -4195,7 +4188,6 @@ local s = `{e.@1 }` TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "string_interpolation_format_provides_results_inside_of_function_call") { - ScopedFastFlag _{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; const std::string source = R"( type T = {x : number, y : number, z : number} local e = {x = 1, y = 2, z = 3} @@ -4223,7 +4215,6 @@ print(`{e.x} {e.@1}`) TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_in_should_rec") { - ScopedFastFlag sff{FFlag::LuauForInProvidesRecommendations, true}; const std::string source = R"( type T = { x : {[number] : number}, y: number} local x : T = ({} :: T) @@ -4245,7 +4236,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_expr_in_should_rec_no_do") { - ScopedFastFlag sff{FFlag::LuauForInProvidesRecommendations, true}; const std::string source = R"( type T = { x : {[number] : number}, y: number, z: number} local x : T = ({} :: T) @@ -4274,7 +4264,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_expr_in_should_rec_with_do_in_step") { - ScopedFastFlag sff{FFlag::LuauForInProvidesRecommendations, true}; const std::string source = R"( type T = { x : {[number] : number}, y: number, z: number} local x : T = ({} :: T) @@ -4303,7 +4292,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_expr_in_should_rec_with_do_in_max_delete") { - ScopedFastFlag sff{FFlag::LuauForInProvidesRecommendations, true}; const std::string source = R"( type T = { x : {[number] : number}, y: number, z: number} local x : T = ({} :: T) @@ -4332,7 +4320,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_expr_in_should_rec_with_do_in_max_add") { - ScopedFastFlag sff{FFlag::LuauForInProvidesRecommendations, true}; const std::string source = R"( type T = { x : {[number] : number}, y: number, z: number} local x : T = ({} :: T) diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 824407df..564bc1f5 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -15,8 +15,6 @@ #include "doctest.h" #include -LUAU_FASTFLAG(LuauNewNonStrictSuppressesDynamicRequireErrors) -LUAU_FASTFLAG(LuauNewNonStrictReportsOneIndexedErrors) LUAU_FASTFLAG(LuauUnreducedTypeFunctionsDontTriggerWarnings) using namespace Luau; @@ -761,7 +759,7 @@ TEST_CASE_FIXTURE(Fixture, "unknown_globals_in_one_sided_conditionals") TEST_CASE_FIXTURE(BuiltinsFixture, "new_non_strict_should_suppress_dynamic_require_errors") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNewNonStrictSuppressesDynamicRequireErrors, true}}; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; // Avoid warning about dynamic requires in new nonstrict mode CheckResult result = check(Mode::Nonstrict, R"( function passThrough(module) @@ -784,7 +782,7 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "new_non_strict_should_suppress_unknown_require_errors") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauNewNonStrictSuppressesDynamicRequireErrors, true}}; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; // Avoid warning about dynamic requires in new nonstrict mode CheckResult result = check(Mode::Nonstrict, R"( @@ -808,7 +806,6 @@ require("@self/NonExistent") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "new_non_strict_stringifies_checked_function_errors_as_one_indexed") { - ScopedFastFlag sff = {FFlag::LuauNewNonStrictReportsOneIndexedErrors, true}; CheckResult result = checkNonStrict(R"( getAllTheArgsWrong(3, true, "what") )"); diff --git a/tests/OverloadResolver.test.cpp b/tests/OverloadResolver.test.cpp index 4806581f..94e565a2 100644 --- a/tests/OverloadResolver.test.cpp +++ b/tests/OverloadResolver.test.cpp @@ -7,7 +7,6 @@ #include "Luau/Normalize.h" #include "Luau/UnifierSharedState.h" -LUAU_FASTFLAG(LuauFilterOverloadsByArity) LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) using namespace Luau; @@ -135,8 +134,6 @@ TEST_CASE_FIXTURE(OverloadResolverFixture, "overloads_with_different_arities1") TEST_CASE_FIXTURE(OverloadResolverFixture, "separate_non_viable_overloads_by_arity_mismatch") { - ScopedFastFlag sff{FFlag::LuauFilterOverloadsByArity, true}; - // ty: ((number)->number) & ((number)->string) & ((number, number)->number) // args: (string) OverloadResolver r = mkResolver(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 2ca71e73..a80c97d5 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix) -LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix @@ -952,7 +951,6 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_double_brace_mid") TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_without_end_brace") { - ScopedFastFlag sff{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; auto columnOfEndBraceError = [this](const char* code) { try @@ -4310,7 +4308,6 @@ TEST_CASE_FIXTURE(Fixture, "parsing_string_union_indexers") TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_curly_at_eof") { - ScopedFastFlag _{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; auto parseResult = tryParse(R"(print(`{e.x} {e.a)"); const auto first = parseResult.root->body.data[0]; auto expr = first->as(); @@ -4331,7 +4328,6 @@ TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_curl TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_backtick_at_eof") { - ScopedFastFlag _{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; auto parseResult = tryParse(R"(print(`{e.x} {e.a})"); const auto first = parseResult.root->body.data[0]; auto expr = first->as(); @@ -4352,7 +4348,6 @@ TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_back TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_curly_with_backtick_at_eof") { - ScopedFastFlag _{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; auto parseResult = tryParse(R"(print(`{e.x} {e.a`)"); const auto first = parseResult.root->body.data[0]; auto expr = first->as(); @@ -4373,7 +4368,6 @@ TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_curl TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_curly_broken_string") { - ScopedFastFlag _{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; auto parseResult = tryParse(R"(print(`{e.x} {e.a )"); const auto first = parseResult.root->body.data[0]; @@ -4395,7 +4389,6 @@ TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_curl TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_backtick_broken_string") { - ScopedFastFlag _{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; auto parseResult = tryParse(R"(print(`{e.x} {e.a} )"); const auto first = parseResult.root->body.data[0]; @@ -4417,7 +4410,6 @@ TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_back TEST_CASE_FIXTURE(Fixture, "parsing_incomplete_string_interpolation_missing_curly_with_backtick_broken_string") { - ScopedFastFlag _{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; auto parseResult = tryParse(R"(print(`{e.x} {e.a` )"); const auto first = parseResult.root->body.data[0]; diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index c6d2e080..1533a6bd 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -11,6 +11,7 @@ #include "Fixture.h" +#include "Luau/Error.h" #include "doctest.h" #include @@ -27,6 +28,7 @@ LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTFLAG(LuauUseNativeStackGuard) LUAU_FASTINT(LuauGenericCounterMaxDepth) struct LimitFixture : BuiltinsFixture @@ -588,6 +590,49 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unification_runs_a_limited_number_of_iterati LUAU_REQUIRE_ERROR(result, UnificationTooComplex); } +#if defined(_MSC_VER) || defined(__APPLE__) + +TEST_CASE_FIXTURE(BuiltinsFixture, "native_stack_guard_prevents_stack_overflows" * doctest::timeout(4.0)) +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauLimitUnification, true}, + {FFlag::LuauUseNativeStackGuard, true}, + }; + + // Disable the iteration limit and very tightly constrain the stack. + ScopedFastInt sfi{FInt::LuauTypeInferIterationLimit, 0}; + limitStackSize(38600); + + try + { + (void)check(R"( + local function l0() + for l0=_,_ do + end + end + + _ = if _._ then function(l0) + end elseif _._G then if `` then {n0=_,} else "luauExprConstantSt" elseif _[_][l0] then function() + end elseif _.n0 then if _[_] then if _ then _ else "aeld" elseif false then 0 else "lead" + return _.n0 + )"); + } + catch (InternalCompilerError& err) + { + // HACK: This test doesn't consistently stack overflow in the same subsystem because + // there is some other unrelated source of nondeterminism in the solver. + // For this test, it's aside from the point, so we write it to be a little bit flexible. + const std::string prefix = "Stack overflow in "; + CHECK(prefix == std::string(err.what()).substr(0, prefix.size())); + return; + } + + CHECK_MESSAGE(false, "An expected InternalCompilerError was not thrown!"); +} + +#endif + TEST_CASE_FIXTURE(BuiltinsFixture, "fusion_normalization_spin" * doctest::timeout(1.0)) { LUAU_REQUIRE_ERRORS(check(R"( diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 6724a73a..ec486abc 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -18,7 +18,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) -LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) @@ -1835,7 +1834,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes") TEST_CASE_FIXTURE(Fixture, "variadic_any_pack_should_suppress_errors_during_overload_resolution") { - ScopedFastFlag sff{FFlag::LuauVariadicAnyPackShouldBeErrorSuppressing, true}; auto res = check(R"( type ActionCallback = (string) -> ...any diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index ad88c573..8e471460 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -14,7 +14,6 @@ LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauSubtypingPrimitiveAndGenericTableTypes) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) -LUAU_FASTFLAG(LuauFilterOverloadsByArity) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVectorLerp) @@ -1730,7 +1729,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_requires_all_fields") { ScopedFastFlag _[] = { {FFlag::LuauNoScopeShallNotSubsumeAll, true}, - {FFlag::LuauFilterOverloadsByArity, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} }; diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index a4ea5cab..073aaae5 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -6,7 +6,6 @@ #include "Luau/Scope.h" #include "Luau/TypeInfer.h" #include "Luau/Type.h" -#include "Luau/VisitType.h" #include "ClassFixture.h" #include "Fixture.h" @@ -32,7 +31,6 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauFixNilRightPad) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) -LUAU_FASTFLAG(LuauFilterOverloadsByArity) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -247,8 +245,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified") TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_count") { - ScopedFastFlag _{FFlag::LuauFilterOverloadsByArity, true}; - CheckResult result = check(R"( local multiply: ((number)->number) & ((number)->string) & ((number, number)->number) multiply("") diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index a620dc2a..66687820 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -23,6 +23,8 @@ LUAU_FASTFLAG(LuauAddConditionalContextForTernary) LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) LUAU_FASTFLAG(LuauAddRefinementToAssertions) +LUAU_FASTFLAG(LuauEnqueueUnionsOfDistributedTypeFunctions) +LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) using namespace Luau; @@ -3158,4 +3160,35 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "non_conditional_context_in_if_should_not_ref CHECK_EQ("Type 'table' does not have key 'foo'", toString(result.errors[0])); } +TEST_CASE_FIXTURE(Fixture, "type_function_reduction_with_union_type_application" * doctest::timeout(0.5)) +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::DebugLuauAssertOnForcedConstraint, true}, + {FFlag::LuauEnqueueUnionsOfDistributedTypeFunctions, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local lastTick = 0 + local jumpAnimTime = 0 + local toolAnimTime = 0 + + function move(time, tool, animStringValueObject) + local deltaTime = time - lastTick + lastTick = time + + if jumpAnimTime > 0 then + jumpAnimTime = jumpAnimTime - deltaTime + end + + if animStringValueObject then + toolAnimTime = time + .3 + end + + if time > toolAnimTime then + end + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index ae95b52a..1f6532d0 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -32,7 +32,6 @@ LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSimplifyIntersectionForLiteralSubtypeCheck) LUAU_FASTFLAG(LuauCacheDuplicateHasPropConstraints) LUAU_FASTFLAG(LuauPushTypeConstraintIntersection) -LUAU_FASTFLAG(LuauFilterOverloadsByArity) LUAU_FASTFLAG(LuauPushTypeConstraintSingleton) TEST_SUITE_BEGIN("TableTests"); @@ -2358,7 +2357,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauNoScopeShallNotSubsumeAll, true}, - {FFlag::LuauFilterOverloadsByArity, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} }; @@ -6091,8 +6089,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_any_and_true") { - ScopedFastFlag _{FFlag::LuauFilterOverloadsByArity, true}; - CheckResult result = check(R"( table.insert({} :: any, true) )"); @@ -6116,7 +6112,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "bad_insert_type_mismatch") {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauNoScopeShallNotSubsumeAll, true}, - {FFlag::LuauFilterOverloadsByArity, true}, }; CheckResult result = check(R"( From 6b787963bc2b590f9909bf33aced826c43328444 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 28 Oct 2025 21:24:37 +0100 Subject: [PATCH 08/10] Make enums and structs in Luau.Require header C compatible (#2060) --- Require/Runtime/include/Luau/Require.h | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Require/Runtime/include/Luau/Require.h b/Require/Runtime/include/Luau/Require.h index 386caffc..1503a7f0 100644 --- a/Require/Runtime/include/Luau/Require.h +++ b/Require/Runtime/include/Luau/Require.h @@ -3,6 +3,7 @@ #include "lua.h" +#include #include //////////////////////////////////////////////////////////////////////////////// @@ -46,24 +47,24 @@ // //////////////////////////////////////////////////////////////////////////////// -enum luarequire_NavigateResult +typedef enum luarequire_NavigateResult { NAVIGATE_SUCCESS, NAVIGATE_AMBIGUOUS, NAVIGATE_NOT_FOUND -}; +} luarequire_NavigateResult; // Functions returning WRITE_SUCCESS are expected to set their size_out argument // to the number of bytes written to the buffer. If WRITE_BUFFER_TOO_SMALL is // returned, size_out should be set to the required buffer size. -enum luarequire_WriteResult +typedef enum luarequire_WriteResult { WRITE_SUCCESS, WRITE_BUFFER_TOO_SMALL, WRITE_FAILURE -}; +} luarequire_WriteResult; -struct luarequire_Configuration +typedef struct luarequire_Configuration { // Returns whether requires are permitted from the given chunkname. bool (*is_require_allowed)(lua_State* L, void* ctx, const char* requirer_chunkname); @@ -120,7 +121,7 @@ struct luarequire_Configuration // thread to yield. In this case, this thread should be resumed with the // module result pushed onto its stack. int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname); -}; +} luarequire_Configuration; // Populates function pointers in the given luarequire_Configuration. typedef void (*luarequire_Configuration_init)(luarequire_Configuration* config); From c906d5926190fe60bc2a08e31883d73fbaa836c4 Mon Sep 17 00:00:00 2001 From: Seyed Alireza Fatemi Jahromi Date: Wed, 29 Oct 2025 20:51:49 +0200 Subject: [PATCH 09/10] Remove NBSP from Require.h (#2067) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Well this is a pretty small thing. There is one NBSP in the document in the file that I removed😅 --- Require/Runtime/include/Luau/Require.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Require/Runtime/include/Luau/Require.h b/Require/Runtime/include/Luau/Require.h index 1503a7f0..77745dc5 100644 --- a/Require/Runtime/include/Luau/Require.h +++ b/Require/Runtime/include/Luau/Require.h @@ -20,7 +20,7 @@ // // Without more context, it is impossible to tell which components in a given // path "./foo/bar/baz" are modules and which are directories. To provide this -// context, the require-by-string runtime library must be opened with a +// context, the require-by-string runtime library must be opened with a // luarequire_Configuration object, which defines the navigation behavior of the // context in which Luau is embedded. // From ff6d381e57bcd1799d850d7fabe543c0f0980a5d Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:18:46 -0700 Subject: [PATCH 10/10] Sync to upstream/release/698 (#2070) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # General Another week, another release. Happy Halloween! 🎃 ## Require - Integrate support for `.config.luau` files into `Luau.Require` as part of the [Luau-syntax configuration files RFC](https://rfcs.luau.org/config-luauconfig.html). - `is_config_present` has been replaced with `get_config_status`. - `get_luau_config_timeout` has been added to configure extraction timeouts at runtime. - Enable support for `.config.luau` files in `luau` and `luau-analyze`. - Merge the `Luau.RequireNavigator` static library into `Luau.Require`. ## Analysis - Add fuel-based limits for normalization: normalization will now take a set number of steps (the "fuel") and stop when we hit a certain number of steps ("run out of fuel"). This is in lieu of trying to check static limits on the size of its inputs. This may result in seeing more "code too complex" errors but should dramatically reduce the number of cases where Luau becomes unresponsive and uses several gigabytes of memory. We plan to tune this limit over time to find the right balance between responsiveness and completeness. - Fix a case where refining a variable with the type `any` would cause it to become `unknown` instead due to normalization not preserving `any` in the top type position. - Improve the wording of type errors in the new non-strict mode. - Fix #1910. - Fix #2065. - Fix #1483. - Fix #2018. ## Miscellaneous - Introduce `Luau::overloaded` to facilitate interacting with `Luau::Variant`. - Add type checking support to the [Luau demo](https://luau.org/demo)! --- Co-authored-by: Annie Tang Co-authored-by: Ariel Weiss Co-authored-by: Hunter Goldstein Co-authored-by: Ilya Rezvov Co-authored-by: Sora Kanosue Co-authored-by: Varun Saini --- Analysis/include/Luau/ConstraintSolver.h | 24 +- Analysis/include/Luau/Normalize.h | 37 +- Analysis/include/Luau/Subtyping.h | 24 +- Analysis/include/Luau/ToString.h | 4 + Analysis/include/Luau/TypePack.h | 5 - Analysis/include/Luau/TypePath.h | 113 -- Analysis/include/Luau/TypeUtils.h | 13 + Analysis/src/ConstraintGenerator.cpp | 87 +- Analysis/src/ConstraintSolver.cpp | 218 ++-- Analysis/src/Error.cpp | 44 +- Analysis/src/Instantiation.cpp | 8 +- Analysis/src/Linter.cpp | 9 +- Analysis/src/Normalize.cpp | 366 ++++++- Analysis/src/OverloadResolution.cpp | 215 +--- Analysis/src/RequireTracer.cpp | 13 - Analysis/src/Scope.cpp | 3 - Analysis/src/Subtyping.cpp | 999 +++++------------- Analysis/src/TableLiteralInference.cpp | 91 +- Analysis/src/ToString.cpp | 23 + Analysis/src/TypeChecker2.cpp | 39 +- Analysis/src/TypeInfer.cpp | 41 +- Analysis/src/TypePack.cpp | 40 - Analysis/src/TypePath.cpp | 394 +------ Analysis/src/TypeUtils.cpp | 28 + Analysis/src/Unifier.cpp | 28 +- Analysis/src/Unifier2.cpp | 27 +- Ast/include/Luau/Parser.h | 1 - Ast/src/Ast.cpp | 17 +- Ast/src/Lexer.cpp | 40 +- Ast/src/Parser.cpp | 60 +- CLI/include/Luau/AnalyzeRequirer.h | 3 +- CLI/include/Luau/VfsNavigator.h | 15 +- CLI/src/Analyze.cpp | 51 +- CLI/src/AnalyzeRequirer.cpp | 18 +- CLI/src/ReplRequirer.cpp | 20 +- CLI/src/VfsNavigator.cpp | 41 +- CLI/src/Web.cpp | 97 ++ CMakeLists.txt | 16 +- Common/include/Luau/ExperimentalFlags.h | 1 - Common/include/Luau/Variant.h | 8 + Makefile | 26 +- Require/{Runtime => }/include/Luau/Require.h | 50 +- .../include/Luau/RequireNavigator.h | 19 +- Require/{Runtime => }/src/Navigation.cpp | 47 +- Require/{Runtime => }/src/Navigation.h | 15 +- Require/{Navigator => }/src/PathUtilities.cpp | 2 +- .../include/Luau => src}/PathUtilities.h | 0 Require/{Runtime => }/src/Require.cpp | 4 +- Require/{Runtime => }/src/RequireImpl.cpp | 0 Require/{Runtime => }/src/RequireImpl.h | 0 .../{Navigator => }/src/RequireNavigator.cpp | 49 +- Sources.cmake | 33 +- tests/Autocomplete.test.cpp | 9 +- tests/Generalization.test.cpp | 6 +- tests/Linter.test.cpp | 3 - tests/NonStrictTypeChecker.test.cpp | 27 +- tests/Normalize.test.cpp | 7 +- tests/Parser.test.cpp | 6 - tests/RequireByString.test.cpp | 72 +- tests/RuntimeLimits.test.cpp | 39 +- tests/Subtyping.test.cpp | 30 +- tests/TypeFunction.test.cpp | 9 - tests/TypeInfer.builtins.test.cpp | 12 +- tests/TypeInfer.functions.test.cpp | 46 +- tests/TypeInfer.generics.test.cpp | 45 +- tests/TypeInfer.intersectionTypes.test.cpp | 12 - tests/TypeInfer.modules.test.cpp | 8 +- tests/TypeInfer.provisional.test.cpp | 18 + tests/TypeInfer.refinements.test.cpp | 29 +- tests/TypeInfer.singletons.test.cpp | 9 + tests/TypeInfer.tables.test.cpp | 139 ++- tests/TypeInfer.test.cpp | 8 +- tests/TypePath.test.cpp | 12 - tests/main.cpp | 3 - tests/require/config_tests/README.md | 1 + .../config_ambiguity/.config.luau | 7 + .../config_tests/config_ambiguity/.luaurc | 5 + .../config_ambiguity}/dependency.luau | 0 .../config_ambiguity/requirer.luau} | 0 .../config_cannot_be_required/.config.luau | 1 + .../config_cannot_be_required/requirer.luau | 1 + .../config_luau_timeout/.config.luau | 2 + .../config_luau_timeout/requirer.luau} | 0 .../{ => config_tests}/with_config/.luaurc | 0 .../with_config/parent_ambiguity/.luaurc | 0 .../with_config/parent_ambiguity/folder.luau | 0 .../parent_ambiguity/folder/requirer.luau | 0 .../with_config/parent_ambiguity/foo.luau | 0 .../with_config/src/.luaurc | 0 .../with_config/src/alias_requirer.luau | 1 + .../with_config/src/dependency.luau | 1 + .../src/directory_alias_requirer.luau | 0 .../with_config/src/other_dependency.luau | 0 .../src/parent_alias_requirer.luau | 0 .../subdirectory/subdirectory_dependency.luau | 0 .../with_config/src/submodule/.luaurc | 0 .../with_config/src/submodule/init.luau | 1 + .../with_config_luau/.config.luau | 8 + .../parent_ambiguity/.config.luau | 7 + .../parent_ambiguity/folder.luau | 0 .../parent_ambiguity/folder/requirer.luau | 1 + .../parent_ambiguity/foo.luau | 1 + .../with_config_luau/src/.config.luau | 8 + .../with_config_luau/src/alias_requirer.luau | 1 + .../with_config_luau/src/dependency.luau | 1 + .../src/directory_alias_requirer.luau | 1 + .../src/other_dependency.luau | 1 + .../src/parent_alias_requirer.luau | 1 + .../subdirectory/subdirectory_dependency.luau | 1 + .../src/submodule/.config.luau | 7 + .../with_config_luau/src/submodule/init.luau | 1 + 111 files changed, 1937 insertions(+), 2197 deletions(-) rename Require/{Runtime => }/include/Luau/Require.h (78%) rename Require/{Navigator => }/include/Luau/RequireNavigator.h (89%) rename Require/{Runtime => }/src/Navigation.cpp (74%) rename Require/{Runtime => }/src/Navigation.h (85%) rename Require/{Navigator => }/src/PathUtilities.cpp (96%) rename Require/{Navigator/include/Luau => src}/PathUtilities.h (100%) rename Require/{Runtime => }/src/Require.cpp (97%) rename Require/{Runtime => }/src/RequireImpl.cpp (100%) rename Require/{Runtime => }/src/RequireImpl.h (100%) rename Require/{Navigator => }/src/RequireNavigator.cpp (82%) create mode 100644 tests/require/config_tests/README.md create mode 100644 tests/require/config_tests/config_ambiguity/.config.luau create mode 100644 tests/require/config_tests/config_ambiguity/.luaurc rename tests/require/{with_config/src => config_tests/config_ambiguity}/dependency.luau (100%) rename tests/require/{with_config/src/alias_requirer.luau => config_tests/config_ambiguity/requirer.luau} (100%) create mode 100644 tests/require/config_tests/config_cannot_be_required/.config.luau create mode 100644 tests/require/config_tests/config_cannot_be_required/requirer.luau create mode 100644 tests/require/config_tests/config_luau_timeout/.config.luau rename tests/require/{with_config/src/submodule/init.luau => config_tests/config_luau_timeout/requirer.luau} (100%) rename tests/require/{ => config_tests}/with_config/.luaurc (100%) rename tests/require/{ => config_tests}/with_config/parent_ambiguity/.luaurc (100%) rename tests/require/{ => config_tests}/with_config/parent_ambiguity/folder.luau (100%) rename tests/require/{ => config_tests}/with_config/parent_ambiguity/folder/requirer.luau (100%) rename tests/require/{ => config_tests}/with_config/parent_ambiguity/foo.luau (100%) rename tests/require/{ => config_tests}/with_config/src/.luaurc (100%) create mode 100644 tests/require/config_tests/with_config/src/alias_requirer.luau create mode 100644 tests/require/config_tests/with_config/src/dependency.luau rename tests/require/{ => config_tests}/with_config/src/directory_alias_requirer.luau (100%) rename tests/require/{ => config_tests}/with_config/src/other_dependency.luau (100%) rename tests/require/{ => config_tests}/with_config/src/parent_alias_requirer.luau (100%) rename tests/require/{ => config_tests}/with_config/src/subdirectory/subdirectory_dependency.luau (100%) rename tests/require/{ => config_tests}/with_config/src/submodule/.luaurc (100%) create mode 100644 tests/require/config_tests/with_config/src/submodule/init.luau create mode 100644 tests/require/config_tests/with_config_luau/.config.luau create mode 100644 tests/require/config_tests/with_config_luau/parent_ambiguity/.config.luau create mode 100644 tests/require/config_tests/with_config_luau/parent_ambiguity/folder.luau create mode 100644 tests/require/config_tests/with_config_luau/parent_ambiguity/folder/requirer.luau create mode 100644 tests/require/config_tests/with_config_luau/parent_ambiguity/foo.luau create mode 100644 tests/require/config_tests/with_config_luau/src/.config.luau create mode 100644 tests/require/config_tests/with_config_luau/src/alias_requirer.luau create mode 100644 tests/require/config_tests/with_config_luau/src/dependency.luau create mode 100644 tests/require/config_tests/with_config_luau/src/directory_alias_requirer.luau create mode 100644 tests/require/config_tests/with_config_luau/src/other_dependency.luau create mode 100644 tests/require/config_tests/with_config_luau/src/parent_alias_requirer.luau create mode 100644 tests/require/config_tests/with_config_luau/src/subdirectory/subdirectory_dependency.luau create mode 100644 tests/require/config_tests/with_config_luau/src/submodule/.config.luau create mode 100644 tests/require/config_tests/with_config_luau/src/submodule/init.luau diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 77c67331..2cdfe584 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -218,18 +218,6 @@ struct ConstraintSolver void generalizeOneType(TypeId ty); - /** - * Bind a type variable to another type. - * - * A constraint is required and will validate that blockedTy is owned by this - * constraint. This prevents one constraint from interfering with another's - * blocked types. - * - * Bind will also unblock the type variable for you. - */ - void bind(NotNull constraint, TypeId ty, TypeId boundTo); - void bind(NotNull constraint, TypePackId tp, TypePackId boundTo); - template void emplace(NotNull constraint, TypeId ty, Args&&... args); @@ -404,6 +392,18 @@ struct ConstraintSolver */ void shiftReferences(TypeId source, TypeId target); + /** + * Bind a type variable to another type. + * + * A constraint is required and will validate that blockedTy is owned by this + * constraint. This prevents one constraint from interfering with another's + * blocked types. + * + * Bind will also unblock the type variable for you. + */ + void bind(NotNull constraint, TypeId ty, TypeId boundTo); + void bind(NotNull constraint, TypePackId tp, TypePackId boundTo); + /** * Generalizes the given free type if the reference counting allows it. * @param the scope to generalize in diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index 328ff542..0a45af33 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -317,6 +317,8 @@ class Normalizer DenseHashMap cachedIsInhabited{nullptr}; DenseHashMap, bool, TypeIdPairHash> cachedIsInhabitedIntersection{{nullptr, nullptr}}; + std::optional fuel{std::nullopt}; + bool withinResourceLimits(); bool useNewLuauSolver() const; @@ -342,13 +344,29 @@ class Normalizer // If this returns null, the typechecker should emit a "too complex" error std::shared_ptr normalize(TypeId ty); - void clearNormal(NormalizedType& norm); + void clearCaches(); + + NormalizationResult isIntersectionInhabited(TypeId left, TypeId right); + + // Check for inhabitance + NormalizationResult isInhabited(TypeId ty); + NormalizationResult isInhabited(const NormalizedType* norm); + + // -------- Convert back from a normalized type to a type + TypeId typeFromNormal(const NormalizedType& norm); + + std::optional intersectionOfTypePacks(TypePackId here, TypePackId there); + + // Move to private with LuauNormalizerStepwiseFuel + std::optional intersectionOfTypePacks_INTERNAL(TypePackId here, TypePackId there); + +private: // ------- Cached TypeIds TypeId unionType(TypeId here, TypeId there); TypeId intersectionType(TypeId here, TypeId there); const TypeIds* cacheTypeIds(TypeIds tys); - void clearCaches(); + void clearNormal(NormalizedType& norm); // ------- Normalizing unions void unionTysWithTy(TypeIds& here, TypeId there); @@ -389,7 +407,6 @@ class Normalizer void intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres); void intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there); void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there); - std::optional intersectionOfTypePacks(TypePackId here, TypePackId there); std::optional intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set& seenSet); void intersectTablesWithTable(TypeIds& heres, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set& seenSetTypes); void intersectTables(TypeIds& heres, const TypeIds& theres); @@ -411,18 +428,20 @@ class Normalizer Set& seenSet ); - // Check for inhabitance - NormalizationResult isInhabited(TypeId ty); NormalizationResult isInhabited(TypeId ty, Set& seen); - NormalizationResult isInhabited(const NormalizedType* norm); NormalizationResult isInhabited(const NormalizedType* norm, Set& seen); // Check for intersections being inhabited - NormalizationResult isIntersectionInhabited(TypeId left, TypeId right); NormalizationResult isIntersectionInhabited(TypeId left, TypeId right, SeenTablePropPairs& seenTablePropPairs, Set& seenSet); - // -------- Convert back from a normalized type to a type - TypeId typeFromNormal(const NormalizedType& norm); + + // Fuel setup + + bool initializeFuel(); + void clearFuel(); + void consumeFuel(); + + friend struct FuelInitializer; }; } // namespace Luau diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 55ea2420..3d348f12 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -147,13 +147,6 @@ struct SubtypingEnvironment TypeIds upperBound; }; - // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance - struct GenericBounds_DEPRECATED - { - DenseHashSet lowerBound{nullptr}; - DenseHashSet upperBound{nullptr}; - }; - /* For nested subtyping relationship tests of mapped generic bounds, we keep the outer environment immutable */ SubtypingEnvironment* parent = nullptr; @@ -165,23 +158,16 @@ struct SubtypingEnvironment TypeId ty, NotNull iceReporter ); - // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance - std::optional applyMappedGenerics_DEPRECATED(NotNull builtinTypes, NotNull arena, TypeId ty); // TODO: Clip with LuauTryFindSubstitutionReturnOptional const TypeId* tryFindSubstitution_DEPRECATED(TypeId ty) const; std::optional tryFindSubstitution(TypeId ty) const; - // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance const SubtypingResult* tryFindSubtypingResult(std::pair subAndSuper) const; bool containsMappedType(TypeId ty) const; bool containsMappedPack(TypePackId tp) const; GenericBounds& getMappedTypeBounds(TypeId ty, NotNull iceReporter); - // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance - GenericBounds_DEPRECATED& getMappedTypeBounds_DEPRECATED(TypeId ty); - // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 - TypePackId* getMappedPackBounds_DEPRECATED(TypePackId tp); MappedGenericEnvironment::LookupResult lookupGenericPack(TypePackId tp) const; /* @@ -191,12 +177,8 @@ struct SubtypingEnvironment * of each vector represents the current scope. */ DenseHashMap> mappedGenerics{nullptr}; - // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance - DenseHashMap mappedGenerics_DEPRECATED{nullptr}; MappedGenericEnvironment mappedGenericPacks; - // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 - DenseHashMap mappedGenericPacks_DEPRECATED{nullptr}; /* * See the test cyclic_tables_are_assumed_to_be_compatible_with_extern_types for @@ -206,8 +188,6 @@ struct SubtypingEnvironment */ DenseHashMap substitutions{nullptr}; - // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance - DenseHashMap, SubtypingResult, TypePairHash> ephemeralCache{{}}; // We use this cache to track pairs of subtypes that we tried to subtype, and found them to be in the seen set at the time. // In those situations, we return True, but mark the result as not cacheable, because we don't want to cache broader results which @@ -237,7 +217,7 @@ struct Subtyping Contravariant }; - // TODO: Clip this along with LuauSubtypingGenericsDoesntUseVariance? + // TODO: Clip in CLI-170986 Variance variance = Variance::Covariant; using SeenSet = Set, TypePairHash>; @@ -467,8 +447,6 @@ struct Subtyping SubtypingResult isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, Nothing, TypePackId superTp, const GenericTypePack* super); bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp); - // Clip with LuauSubtypingGenericPacksDoesntUseVariance2 - bool bindGeneric_DEPRECATED(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) const; template TypeId makeAggregateType(const Container& container, TypeId orElse); diff --git a/Analysis/include/Luau/ToString.h b/Analysis/include/Luau/ToString.h index 53b62fbb..dceecca9 100644 --- a/Analysis/include/Luau/ToString.h +++ b/Analysis/include/Luau/ToString.h @@ -127,6 +127,10 @@ inline std::string toStringNamedFunction(const std::string& funcName, const Func return toStringNamedFunction(funcName, ftv, opts); } +// Converts the given number index into a human-readable string for that index to be used in errors. +// e.g. the index `0` becomes `1st`, `1` becomes `2nd`, `11` becomes `12th`, etc. +std::string toHumanReadableIndex(size_t number); + std::optional getFunctionNameAsString(const AstExpr& expr); // It could be useful to see the text representation of a type during a debugging session instead of exploring the content of the class diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index 81e23cb6..b3cc6f8d 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -231,11 +231,6 @@ bool isEmpty(TypePackId tp); /// Flattens out a type pack. Also returns a valid TypePackId tail if the type pack's full size is not known std::pair, std::optional> flatten(TypePackId tp); std::pair, std::optional> flatten(TypePackId tp, const TxnLog& log); -// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 -std::pair, std::optional> flatten_DEPRECATED( - TypePackId tp, - const DenseHashMap& mappedGenericPacks -); /// Returs true if the type pack arose from a function that is declared to be variadic. /// Returns *false* for function argument packs that are inferred to be safe to oversaturate! diff --git a/Analysis/include/Luau/TypePath.h b/Analysis/include/Luau/TypePath.h index a9f6620f..34a3cb82 100644 --- a/Analysis/include/Luau/TypePath.h +++ b/Analysis/include/Luau/TypePath.h @@ -241,54 +241,8 @@ std::string toString(const TypePath::Path& path, bool prefixDot = false); /// Converts a Path to a human readable string for error reporting. std::string toStringHuman(const TypePath::Path& path); -// To keep my head straight when clipping: -// LuauReturnMappedGenericPacksFromSubtyping3 expects mappedGenericPacks AND arena -// LuauSubtypingGenericPacksDoesntUseVariance2 expects just arena. this is the final state - -// TODO: clip below two along with `LuauReturnMappedGenericPacksFromSubtyping3` -std::optional traverse_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes); -std::optional traverse_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes); std::optional traverse(TypePackId root, const Path& path, NotNull builtinTypes, NotNull arena); -// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 -std::optional traverse_DEPRECATED( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -); std::optional traverse(TypeId root, const Path& path, NotNull builtinTypes, NotNull arena); -// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 -std::optional traverse_DEPRECATED( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -); - -/// Traverses a path from a type to its end point, which must be a type. This overload will fail if the path contains a PackSlice component or a -/// mapped generic pack. -/// @param root the entry point of the traversal -/// @param path the path to traverse -/// @param builtinTypes the built-in types in use (used to acquire the string metatable) -/// @returns the TypeId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes); - -/// Traverses a path from a type to its end point, which must be a type. -/// @param root the entry point of the traversal -/// @param path the path to traverse -/// @param builtinTypes the built-in types in use (used to acquire the string metatable) -/// @param mappedGenericPacks the mapping for any encountered generic packs we want to reify -/// @param arena a TypeArena, required if path has a PackSlice component -/// @returns the TypeId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForType_DEPRECATED( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -); /// Traverses a path from a type to its end point, which must be a type. /// @param root the entry point of the traversal @@ -298,28 +252,6 @@ std::optional traverseForType_DEPRECATED( /// @returns the TypeId at the end of the path, or nullopt if the traversal failed. std::optional traverseForType(TypeId root, const Path& path, NotNull builtinTypes, NotNull arena); -/// Traverses a path from a type pack to its end point, which must be a type. -/// @param root the entry point of the traversal -/// @param path the path to traverse -/// @param builtinTypes the built-in types in use (used to acquire the string metatable) -/// @returns the TypeId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForType_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes); - -/// Traverses a path from a type pack to its end point, which must be a type. -/// @param root the entry point of the traversal -/// @param path the path to traverse -/// @param builtinTypes the built-in types in use (used to acquire the string metatable) -/// @param mappedGenericPacks the mapping for any encountered generic packs we want to reify -/// @param arena a TypeArena, required if path has a PackSlice component -/// @returns the TypeId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForType_DEPRECATED( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -); - /// Traverses a path from a type pack to its end point, which must be a type. /// @param root the entry point of the traversal /// @param path the path to traverse @@ -328,29 +260,6 @@ std::optional traverseForType_DEPRECATED( /// @returns the TypeId at the end of the path, or nullopt if the traversal failed. std::optional traverseForType(TypePackId root, const Path& path, NotNull builtinTypes, NotNull arena); -/// Traverses a path from a type to its end point, which must be a type pack. This overload will fail if the path contains a PackSlice component or a -/// mapped generic pack. -/// @param root the entry point of the traversal -/// @param path the path to traverse -/// @param builtinTypes the built-in types in use (used to acquire the string metatable) -/// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForPack_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes); - -/// Traverses a path from a type to its end point, which must be a type pack. -/// @param root the entry point of the traversal -/// @param path the path to traverse -/// @param builtinTypes the built-in types in use (used to acquire the string metatable) -/// @param mappedGenericPacks the mapping for any encountered generic packs we want to reify -/// @param arena a TypeArena, required if path has a PackSlice component -/// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForPack_DEPRECATED( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -); - /// Traverses a path from a type to its end point, which must be a type pack. /// @param root the entry point of the traversal /// @param path the path to traverse @@ -359,28 +268,6 @@ std::optional traverseForPack_DEPRECATED( /// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. std::optional traverseForPack(TypeId root, const Path& path, NotNull builtinTypes, NotNull arena); -/// Traverses a path from a type pack to its end point, which must be a type pack. -/// @param root the entry point of the traversal -/// @param path the path to traverse -/// @param builtinTypes the built-in types in use (used to acquire the string metatable) -/// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForPack_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes); - -/// Traverses a path from a type pack to its end point, which must be a type pack. -/// @param root the entry point of the traversal -/// @param path the path to traverse -/// @param builtinTypes the built-in types in use (used to acquire the string metatable) -/// @param mappedGenericPacks the mapping for any encountered generic packs we want to reify -/// @param arena a TypeArena, required if path has a PackSlice component -/// @returns the TypePackId at the end of the path, or nullopt if the traversal failed. -std::optional traverseForPack_DEPRECATED( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -); - /// Traverses a path from a type pack to its end point, which must be a type pack. /// @param root the entry point of the traversal /// @param path the path to traverse diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 5caad0e6..82e35f03 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -6,6 +6,7 @@ #include "Luau/Type.h" #include "Luau/TypeIds.h" #include "Luau/TypePack.h" +#include "Luau/VisitType.h" #include #include @@ -388,5 +389,17 @@ struct IntersectionBuilder TypeId addIntersection(NotNull arena, NotNull builtinTypes, std::initializer_list list); TypeId addUnion(NotNull arena, NotNull builtinTypes, std::initializer_list list); +struct ContainsAnyGeneric final : public TypeOnceVisitor +{ + bool found = false; + + explicit ContainsAnyGeneric(); + + bool visit(TypeId ty) override; + bool visit(TypePackId ty) override; + + static bool hasAnyGeneric(TypeId ty); + static bool hasAnyGeneric(TypePackId tp); +}; } // namespace Luau diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index a7d179e8..aff1851f 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -40,7 +40,6 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAGVARIABLE(LuauInstantiateResolvedTypeFunctions) @@ -56,6 +55,7 @@ LUAU_FASTFLAGVARIABLE(LuauDontReferenceScopePtrFromHashTable) LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) LUAU_FASTFLAGVARIABLE(LuauMetatableAvoidSingletonUnion) LUAU_FASTFLAGVARIABLE(LuauAddRefinementToAssertions) +LUAU_FASTFLAG(LuauPushTypeConstraintLambdas) namespace Luau { @@ -1459,18 +1459,11 @@ static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstEx { if (GeneralizationConstraint* genConstraint = c.get_if()) { - if (FFlag::LuauParametrizedAttributeSyntax) + AstAttr* deprecatedAttribute = func->getAttribute(AstAttr::Type::Deprecated); + genConstraint->hasDeprecatedAttribute = deprecatedAttribute != nullptr; + if (deprecatedAttribute) { - AstAttr* deprecatedAttribute = func->getAttribute(AstAttr::Type::Deprecated); - genConstraint->hasDeprecatedAttribute = deprecatedAttribute != nullptr; - if (deprecatedAttribute) - { - genConstraint->deprecatedInfo = deprecatedAttribute->deprecatedInfo(); - } - } - else - { - genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated); + genConstraint->deprecatedInfo = deprecatedAttribute->deprecatedInfo(); } } } @@ -2260,18 +2253,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc FunctionType* ftv = getMutable(fnType); ftv->isCheckedFunction = global->isCheckedFunction(); - if (FFlag::LuauParametrizedAttributeSyntax) - { - AstAttr* deprecatedAttr = global->getAttribute(AstAttr::Type::Deprecated); - ftv->isDeprecatedFunction = deprecatedAttr != nullptr; - if (deprecatedAttr) - { - ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); - } - } - else + AstAttr* deprecatedAttr = global->getAttribute(AstAttr::Type::Deprecated); + ftv->isDeprecatedFunction = deprecatedAttr != nullptr; + if (deprecatedAttr) { - ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated); + ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); } ftv->argNames.reserve(global->paramNames.size); @@ -3649,12 +3635,31 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, TypeIds valuesLowerBound; + std::optional start{std::nullopt}; + if (FFlag::LuauPushTypeConstraint2 && FFlag::LuauPushTypeConstraintLambdas) + start = checkpoint(this); + for (const AstExprTable::Item& item : expr->items) { // Expected types are threaded through table literals separately via the // function matchLiteralType. - TypeId itemTy = check(scope, item.value).ty; + // generalize is false here as we want to be able to push types into lambdas in a situation like: + // + // type Callback = (string) -> () + // + // local t: { Callback } = { + // function (s) + // -- s should have type `string` here + // end + // } + TypeId itemTy = check( + scope, + item.value, + /* expectedType */ std::nullopt, + /* forceSingleton */ false, + /* generalize */ !(FFlag::LuauPushTypeConstraint2 && FFlag::LuauPushTypeConstraintLambdas) + ).ty; if (item.key) { @@ -3682,6 +3687,10 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, } } + std::optional end{std::nullopt}; + if (FFlag::LuauPushTypeConstraintLambdas) + end = checkpoint(this); + if (!indexKeyLowerBound.empty()) { LUAU_ASSERT(!indexValueLowerBound.empty()); @@ -3714,7 +3723,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, if (FFlag::LuauPushTypeConstraint2 && expectedType) { - addConstraint( + auto ptc = addConstraint( scope, expr->location, PushTypeConstraint{ @@ -3725,6 +3734,19 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, /* expr */ NotNull{expr}, } ); + if (FFlag::LuauPushTypeConstraintLambdas) + { + LUAU_ASSERT(start && end); + forEachConstraint( + *start, + *end, + this, + [ptc](const ConstraintPtr& c) + { + c->dependencies.emplace_back(ptc.get()); + } + ); + } } if (FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) @@ -4193,18 +4215,11 @@ TypeId ConstraintGenerator::resolveFunctionType( // how to quantify/instantiate it. FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes}; ftv.isCheckedFunction = fn->isCheckedFunction(); - if (FFlag::LuauParametrizedAttributeSyntax) - { - AstAttr* deprecatedAttr = fn->getAttribute(AstAttr::Type::Deprecated); - ftv.isDeprecatedFunction = deprecatedAttr != nullptr; - if (deprecatedAttr) - { - ftv.deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); - } - } - else + AstAttr* deprecatedAttr = fn->getAttribute(AstAttr::Type::Deprecated); + ftv.isDeprecatedFunction = deprecatedAttr != nullptr; + if (deprecatedAttr) { - ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated); + ftv.deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 0b872b0a..80a9c585 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -44,8 +44,6 @@ LUAU_FASTFLAGVARIABLE(LuauCollapseShouldNotCrash) LUAU_FASTFLAGVARIABLE(LuauDontDynamicallyCreateRedundantSubtypeConstraints) LUAU_FASTFLAGVARIABLE(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) -LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAG(LuauPushTypeConstraint2) @@ -53,6 +51,7 @@ LUAU_FASTFLAGVARIABLE(LuauScopedSeenSetInLookupTableProp) LUAU_FASTFLAGVARIABLE(LuauIterableBindNotUnify) LUAU_FASTFLAGVARIABLE(LuauAvoidOverloadSelectionForFunctionType) LUAU_FASTFLAG(LuauSimplifyIntersectionNoTreeSet) +LUAU_FASTFLAG(LuauPushTypeConstraintLambdas) namespace Luau { @@ -939,10 +938,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullisDeprecatedFunction = true; - if (FFlag::LuauParametrizedAttributeSyntax) - { - fty->deprecatedInfo = std::make_shared(c.deprecatedInfo); - } + fty->deprecatedInfo = std::make_shared(c.deprecatedInfo); } } } @@ -1144,27 +1140,24 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNullpersistent || target->owningArena != arena) return true; - if (FFlag::LuauNameConstraintRestrictRecursiveTypes) + if (std::optional tf = constraint->scope->lookupType(c.name)) { - if (std::optional tf = constraint->scope->lookupType(c.name)) - { - // We check to see if this type alias violates the recursion restriction - InstantiationSignature signature{ - *tf, - c.typeParameters, - c.typePackParameters, - }; + // We check to see if this type alias violates the recursion restriction + InstantiationSignature signature{ + *tf, + c.typeParameters, + c.typePackParameters, + }; - InfiniteTypeFinder itf{this, signature, constraint->scope}; - itf.traverse(target); + InfiniteTypeFinder itf{this, signature, constraint->scope}; + itf.traverse(target); - if (itf.foundInfiniteType) - { - constraint->scope->invalidTypeAliasNames.insert(c.name); - shiftReferences(target, builtinTypes->errorType); - emplaceType(asMutable(target), builtinTypes->errorType); - return true; - } + if (itf.foundInfiniteType) + { + constraint->scope->invalidTypeAliasNames.insert(c.name); + shiftReferences(target, builtinTypes->errorType); + emplaceType(asMutable(target), builtinTypes->errorType); + return true; } } @@ -1753,51 +1746,17 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullself ? 1 : 0; - for (size_t i = 0; i < c.callSite->args.size && i + typeOffset < expectedArgs.size() && i + typeOffset < argPackHead.size(); ++i) + if (FFlag::LuauPushTypeConstraintLambdas && FFlag::LuauPushTypeConstraint2) { - TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]); - const TypeId actualArgTy = follow(argPackHead[i + typeOffset]); - AstExpr* expr = unwrapGroup(c.callSite->args.data[i]); - - (*c.astExpectedTypes)[expr] = expectedArgTy; + Subtyping subtyping{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; - const auto lambdaTy = get(actualArgTy); - const auto expectedLambdaTy = get(expectedArgTy); - const auto lambdaExpr = expr->as(); - - if (expectedLambdaTy && lambdaTy && lambdaExpr) + for (size_t i = 0; i < c.callSite->args.size && i + typeOffset < expectedArgs.size() && i + typeOffset < argPackHead.size(); ++i) { - if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) - continue; + TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]); + AstExpr* expr = unwrapGroup(c.callSite->args.data[i]); - const std::vector expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first; - const std::vector lambdaArgTys = flatten(lambdaTy->argTypes).first; - - for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j) - { - if (!lambdaExpr->args.data[j]->annotation && get(follow(lambdaArgTys[j]))) - { - shiftReferences(lambdaArgTys[j], expectedLambdaArgTys[j]); - bind(constraint, lambdaArgTys[j], expectedLambdaArgTys[j]); - } - } - } - else if (expr->is() || expr->is() || expr->is() || - expr->is() || expr->is()) - { - if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) - { - replacer.resetState(TxnLog::empty(), arena); - if (auto res = replacer.substitute(expectedArgTy)) - { - InstantiationQueuer queuer{constraint->scope, constraint->location, this}; - queuer.traverse(*res); - expectedArgTy = *res; - } - } - if (FFlag::LuauPushTypeConstraint2) + if (isLiteral(expr)) { - Subtyping subtyping{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; PushTypeResult result = pushTypeInto(c.astTypes, c.astExpectedTypes, NotNull{this}, constraint, NotNull{&u2}, NotNull{&subtyping}, expectedArgTy, expr); @@ -1832,13 +1791,97 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullargs.size && i + typeOffset < expectedArgs.size() && i + typeOffset < argPackHead.size(); ++i) + { + TypeId expectedArgTy = follow(expectedArgs[i + typeOffset]); + const TypeId actualArgTy = follow(argPackHead[i + typeOffset]); + AstExpr* expr = unwrapGroup(c.callSite->args.data[i]); + + (*c.astExpectedTypes)[expr] = expectedArgTy; + + const auto lambdaTy = get(actualArgTy); + const auto expectedLambdaTy = get(expectedArgTy); + const auto lambdaExpr = expr->as(); + + if (expectedLambdaTy && lambdaTy && lambdaExpr) + { + if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) + continue; + + const std::vector expectedLambdaArgTys = flatten(expectedLambdaTy->argTypes).first; + const std::vector lambdaArgTys = flatten(lambdaTy->argTypes).first; + + for (size_t j = 0; j < expectedLambdaArgTys.size() && j < lambdaArgTys.size() && j < lambdaExpr->args.size; ++j) + { + if (!lambdaExpr->args.data[j]->annotation && get(follow(lambdaArgTys[j]))) + { + shiftReferences(lambdaArgTys[j], expectedLambdaArgTys[j]); + bind(constraint, lambdaArgTys[j], expectedLambdaArgTys[j]); + } + } + } + else if (expr->is() || expr->is() || expr->is() || + expr->is() || expr->is()) { - u2.unify(actualArgTy, expectedArgTy); + if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) + { + replacer.resetState(TxnLog::empty(), arena); + if (auto res = replacer.substitute(expectedArgTy)) + { + InstantiationQueuer queuer{constraint->scope, constraint->location, this}; + queuer.traverse(*res); + expectedArgTy = *res; + } + } + if (FFlag::LuauPushTypeConstraint2) + { + Subtyping subtyping{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; + PushTypeResult result = pushTypeInto( + c.astTypes, c.astExpectedTypes, NotNull{this}, constraint, NotNull{&u2}, NotNull{&subtyping}, expectedArgTy, expr + ); + // Consider: + // + // local Direction = { Left = 1, Right = 2 } + // type Direction = keyof + // + // local function move(dirs: { Direction }) --[[...]] end + // + // move({ "Left", "Right", "Left", "Right" }) + // + // We need `keyof` to reduce prior to inferring that the + // arguments to `move` must generalize to their lower bounds. This + // is how we ensure that ordering. + if (!force && !result.incompleteTypes.empty()) + { + for (const auto& [newExpectedTy, newTargetTy, newExpr] : result.incompleteTypes) + { + auto addition = pushConstraint( + constraint->scope, + constraint->location, + PushTypeConstraint{ + newExpectedTy, + newTargetTy, + /* astTypes */ c.astTypes, + /* astExpectedTypes */ c.astExpectedTypes, + /* expr */ NotNull{newExpr}, + } + ); + inheritBlocks(constraint, addition); + } + } + } + else + { + u2.unify(actualArgTy, expectedArgTy); + } } } } - // Consider: // // local Direction = { Left = 1, Right = 2 } @@ -2763,47 +2806,6 @@ bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNull(ty); - return !found; - } - - bool visit(TypePackId ty) override - { - found = found || is(follow(ty)); - return !found; - } - - static bool hasAnyGeneric(TypeId ty) - { - ContainsAnyGeneric cg; - cg.traverse(ty); - return cg.found; - } - - static bool hasAnyGeneric(TypePackId tp) - { - ContainsAnyGeneric cg; - cg.traverse(tp); - return cg.found; - } -}; - -} // namespace - bool ConstraintSolver::tryDispatch(const PushFunctionTypeConstraint& c, NotNull constraint) { // NOTE: This logic could probably be combined with that of diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 1bdb7e05..e53b82a1 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -20,6 +20,7 @@ LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictReportsOneIndexedErrors) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) +LUAU_FASTFLAGVARIABLE(LuauNewNonStrictBetterCheckedFunctionErrorMessage) static std::string wrongNumberOfArgsString( size_t expectedCount, @@ -756,21 +757,37 @@ struct ErrorConverter std::string operator()(const CheckedFunctionCallError& e) const { - // TODO: What happens if checkedFunctionName cannot be found?? - return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + - std::to_string(e.argumentIndex + 1) + ", but got '" + Luau::toString(e.passed) + "'"; + if (FFlag::LuauNewNonStrictBetterCheckedFunctionErrorMessage) + { + return "the function '" + e.checkedFunctionName + "' expects to get a " + toString(e.expected) + " as its " + + toHumanReadableIndex(e.argumentIndex) + " argument, but is being given a " + toString(e.passed) + ""; + } + else + { + // TODO: What happens if checkedFunctionName cannot be found?? + return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + + std::to_string(e.argumentIndex + 1) + ", but got '" + Luau::toString(e.passed) + "'"; + } } std::string operator()(const NonStrictFunctionDefinitionError& e) const { - if (e.functionName.empty()) + if (FFlag::LuauNewNonStrictBetterCheckedFunctionErrorMessage) { - return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error"; + std::string prefix = e.functionName.empty() ? "" : "in the function '" + e.functionName + "', '"; + return prefix + "the argument '" + e.argument + "' is used in a way that will error at runtime"; } else { - return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName + - "' is used in a way that will run time error"; + if (e.functionName.empty()) + { + return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' is used in a way that will run time error"; + } + else + { + return "Argument " + e.argument + " with type '" + toString(e.argumentType) + "' in function '" + e.functionName + + "' is used in a way that will run time error"; + } } } @@ -791,8 +808,17 @@ struct ErrorConverter std::string operator()(const CheckedFunctionIncorrectArgs& e) const { - return "Checked Function " + e.functionName + " expects " + std::to_string(e.expected) + " arguments, but received " + - std::to_string(e.actual); + + if (FFlag::LuauNewNonStrictBetterCheckedFunctionErrorMessage) + { + return "the function '" + e.functionName + "' will error at runtime if it is not called with " + std::to_string(e.expected) + + " arguments, but we are calling it here with " + std::to_string(e.actual) + " arguments"; + } + else + { + return "Checked Function " + e.functionName + " expects " + std::to_string(e.expected) + " arguments, but received " + + std::to_string(e.actual); + } } std::string operator()(const UnexpectedTypeInSubtyping& e) const diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index 77b5fd63..a6af85aa 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -11,7 +11,6 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) namespace Luau { @@ -65,11 +64,8 @@ TypeId Instantiation::clean(TypeId ty) clone.magic = ftv->magic; clone.tags = ftv->tags; clone.argNames = ftv->argNames; - if (FFlag::LuauParametrizedAttributeSyntax) - { - clone.isDeprecatedFunction = ftv->isDeprecatedFunction; - clone.deprecatedInfo = ftv->deprecatedInfo; - } + clone.isDeprecatedFunction = ftv->isDeprecatedFunction; + clone.deprecatedInfo = ftv->deprecatedInfo; TypeId result = addType(std::move(clone)); // Annoyingly, we have to do this even if there are no generics, diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 5742c09a..16649a96 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -15,7 +15,6 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) namespace Luau { @@ -2295,7 +2294,7 @@ class LintDeprecatedApi : AstVisitor if (shouldReport) { - if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr) + if (fty->deprecatedInfo != nullptr) { report(node->location, node->local->name.value, *fty->deprecatedInfo); } @@ -2315,7 +2314,7 @@ class LintDeprecatedApi : AstVisitor if (shouldReport) { - if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr) + if (fty->deprecatedInfo != nullptr) { report(node->location, node->name.value, *fty->deprecatedInfo); } @@ -2399,7 +2398,7 @@ class LintDeprecatedApi : AstVisitor className = global->name.value; const char* functionName = node->index.value; - if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr) + if (fty->deprecatedInfo != nullptr) { report(node->location, className, functionName, *fty->deprecatedInfo); } @@ -2440,7 +2439,7 @@ class LintDeprecatedApi : AstVisitor const char* functionName = node->index.value; - if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr) + if (fty->deprecatedInfo != nullptr) { report(node->location, className, functionName, *fty->deprecatedInfo); } diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 634b9985..2c994677 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -27,6 +27,9 @@ LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAGVARIABLE(LuauImproveNormalizeExternTypeCheck) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) LUAU_FASTFLAGVARIABLE(LuauNormalizerUnionTyvarsTakeMaxSize) +LUAU_FASTFLAGVARIABLE(LuauNormalizationPreservesAny) +LUAU_FASTFLAGVARIABLE(LuauNormalizerStepwiseFuel) +LUAU_FASTINTVARIABLE(LuauNormalizerInitialFuel, 3000) namespace Luau { @@ -39,6 +42,32 @@ static bool shouldEarlyExit(NormalizationResult res) return false; } +class NormalizerHitLimits : public std::exception +{ +}; + +struct FuelInitializer +{ + NotNull normalizer; + bool initializedFuel; + + explicit FuelInitializer(NotNull normalizer) + : normalizer(normalizer) + , initializedFuel(normalizer->initializeFuel()) + { + } + + FuelInitializer(const FuelInitializer& rhs) = delete; + + FuelInitializer& operator=(const FuelInitializer&) = delete; + + ~FuelInitializer() + { + if (initializedFuel) + normalizer->clearFuel(); + } +}; + NormalizedStringType::NormalizedStringType() {} NormalizedStringType::NormalizedStringType(bool isCofinite, std::map singletons) @@ -342,7 +371,22 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm) { Set seen{nullptr}; - return isInhabited(norm, seen); + if (FFlag::LuauNormalizerStepwiseFuel) + { + try + { + FuelInitializer fi{NotNull{this}}; + return isInhabited(norm, seen); + } + catch (const NormalizerHitLimits&) + { + return NormalizationResult::HitLimits; + } + } + else + { + return isInhabited(norm, seen); + } } NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set& seen) @@ -351,6 +395,9 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set(norm->tops) || !get(norm->booleans) || !get(norm->errors) || !get(norm->nils) || !get(norm->numbers) || !get(norm->threads) || !get(norm->buffers) || !norm->externTypes.isNever() || !norm->strings.isNever() || !norm->functions.isNever()) @@ -382,14 +429,38 @@ NormalizationResult Normalizer::isInhabited(TypeId ty) } Set seen{nullptr}; - NormalizationResult result = isInhabited(ty, seen); - if (cacheInhabitance && result == NormalizationResult::True) - cachedIsInhabited[ty] = true; - else if (cacheInhabitance && result == NormalizationResult::False) - cachedIsInhabited[ty] = false; + if (FFlag::LuauNormalizerStepwiseFuel) + { + try + { + FuelInitializer fi{NotNull{this}}; + NormalizationResult result = isInhabited(ty, seen); - return result; + if (cacheInhabitance && result == NormalizationResult::True) + cachedIsInhabited[ty] = true; + else if (cacheInhabitance && result == NormalizationResult::False) + cachedIsInhabited[ty] = false; + + return result; + } + catch (const NormalizerHitLimits&) + { + return NormalizationResult::HitLimits; + } + } + else + { + + NormalizationResult result = isInhabited(ty, seen); + + if (cacheInhabitance && result == NormalizationResult::True) + cachedIsInhabited[ty] = true; + else if (cacheInhabitance && result == NormalizationResult::False) + cachedIsInhabited[ty] = false; + + return result; + } } NormalizationResult Normalizer::isInhabited(TypeId ty, Set& seen) @@ -398,6 +469,8 @@ NormalizationResult Normalizer::isInhabited(TypeId ty, Set& seen) if (!withinResourceLimits()) return NormalizationResult::HitLimits; + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); // TODO: use log.follow(ty), CLI-64291 ty = follow(ty); @@ -453,11 +526,29 @@ NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId righ { Set seen{nullptr}; SeenTablePropPairs seenTablePropPairs{{nullptr, nullptr}}; - return isIntersectionInhabited(left, right, seenTablePropPairs, seen); + if (FFlag::LuauNormalizerStepwiseFuel) + { + try + { + FuelInitializer fi{NotNull{this}}; + return isIntersectionInhabited(left, right, seenTablePropPairs, seen); + } + catch (const NormalizerHitLimits&) + { + return NormalizationResult::HitLimits; + } + } + else + { + return isIntersectionInhabited(left, right, seenTablePropPairs, seen); + } } NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId right, SeenTablePropPairs& seenTablePropPairs, Set& seenSet) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + left = follow(left); right = follow(right); // We're asking if intersection is inhabited between left and right but we've already seen them .... @@ -828,9 +919,27 @@ std::shared_ptr Normalizer::normalize(TypeId ty) NormalizedType norm{builtinTypes}; Set seenSetTypes{nullptr}; SeenTablePropPairs seenTablePropPairs{{nullptr, nullptr}}; - NormalizationResult res = unionNormalWithTy(norm, ty, seenTablePropPairs, seenSetTypes); - if (res != NormalizationResult::True) - return nullptr; + + if (FFlag::LuauNormalizerStepwiseFuel) + { + try + { + FuelInitializer fi{NotNull{this}}; + NormalizationResult res = unionNormalWithTy(norm, ty, seenTablePropPairs, seenSetTypes); + if (res != NormalizationResult::True) + return nullptr; + } + catch (const NormalizerHitLimits&) + { + return nullptr; + } + } + else + { + NormalizationResult res = unionNormalWithTy(norm, ty, seenTablePropPairs, seenSetTypes); + if (res != NormalizationResult::True) + return nullptr; + } if (norm.isUnknown()) { @@ -855,8 +964,12 @@ NormalizationResult Normalizer::normalizeIntersections( { if (!arena) sharedState->iceHandler->ice("Normalizing types outside a module"); + + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + NormalizedType norm{builtinTypes}; - norm.tops = builtinTypes->anyType; + norm.tops = FFlag::LuauNormalizationPreservesAny ? builtinTypes->unknownType : builtinTypes->anyType; // Now we need to intersect the two types for (auto ty : intersections) { @@ -903,6 +1016,9 @@ const TypeIds* Normalizer::cacheTypeIds(TypeIds tys) TypeId Normalizer::unionType(TypeId here, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + here = follow(here); there = follow(there); @@ -949,6 +1065,9 @@ TypeId Normalizer::unionType(TypeId here, TypeId there) TypeId Normalizer::intersectionType(TypeId here, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + here = follow(here); there = follow(there); @@ -1007,6 +1126,9 @@ void Normalizer::clearCaches() // ------- Normalizing unions TypeId Normalizer::unionOfTops(TypeId here, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (get(here) || get(there)) return there; else @@ -1015,6 +1137,9 @@ TypeId Normalizer::unionOfTops(TypeId here, TypeId there) TypeId Normalizer::unionOfBools(TypeId here, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (get(here)) return there; if (get(there)) @@ -1028,6 +1153,9 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there) void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (heres.count(there)) return; @@ -1050,6 +1178,9 @@ void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there) void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + for (TypeId there : theres) unionExternTypesWithExternType(heres, there); } @@ -1067,6 +1198,9 @@ static bool isSubclass(TypeId test, TypeId parent) void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + for (auto it = heres.ordering.begin(); it != heres.ordering.end();) { TypeId hereTy = *it; @@ -1145,6 +1279,8 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE // have negations to worry about combining. The two aspects combine to make // the tasks this method must perform different enough to warrant a separate // implementation. + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); for (const TypeId thereTy : theres.ordering) { @@ -1234,6 +1370,9 @@ void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedE void Normalizer::unionStrings(NormalizedStringType& here, const NormalizedStringType& there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (there.isString()) here.resetToString(); else if (here.isUnion() && there.isUnion()) @@ -1278,6 +1417,9 @@ void Normalizer::unionStrings(NormalizedStringType& here, const NormalizedString std::optional Normalizer::unionOfTypePacks(TypePackId here, TypePackId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (here == there) return here; @@ -1405,6 +1547,9 @@ std::optional Normalizer::unionOfTypePacks(TypePackId here, TypePack std::optional Normalizer::unionOfFunctions(TypeId here, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (get(here)) return here; @@ -1421,7 +1566,7 @@ std::optional Normalizer::unionOfFunctions(TypeId here, TypeId there) if (hftv->genericPacks != tftv->genericPacks) return std::nullopt; - std::optional argTypes = intersectionOfTypePacks(hftv->argTypes, tftv->argTypes); + std::optional argTypes = intersectionOfTypePacks_INTERNAL(hftv->argTypes, tftv->argTypes); if (!argTypes) return std::nullopt; @@ -1442,6 +1587,9 @@ std::optional Normalizer::unionOfFunctions(TypeId here, TypeId there) void Normalizer::unionFunctions(NormalizedFunctionType& heres, const NormalizedFunctionType& theres) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (heres.isTop) return; if (theres.isTop) @@ -1473,6 +1621,9 @@ void Normalizer::unionFunctions(NormalizedFunctionType& heres, const NormalizedF void Normalizer::unionFunctionsWithFunction(NormalizedFunctionType& heres, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (heres.isNever()) { TypeIds tmps; @@ -1505,6 +1656,9 @@ void Normalizer::unionTablesWithTable(TypeIds& heres, TypeId there) void Normalizer::unionTables(TypeIds& heres, const TypeIds& theres) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + for (TypeId there : theres) { if (there == builtinTypes->tableType) @@ -1541,6 +1695,9 @@ void Normalizer::unionTables(TypeIds& heres, const TypeIds& theres) // That's what you get for having a type system with generics, intersection and union types. NormalizationResult Normalizer::unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + here.isCacheable &= there.isCacheable; TypeId tops = unionOfTops(here.tops, there.tops); @@ -1553,16 +1710,19 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali return NormalizationResult::True; } - if (FFlag::LuauNormalizerUnionTyvarsTakeMaxSize) + if (!FFlag::LuauNormalizerStepwiseFuel) { - auto maxSize = std::max(here.tyvars.size(), there.tyvars.size()); - if (maxSize * maxSize >= size_t(FInt::LuauNormalizeUnionLimit)) - return NormalizationResult::HitLimits; - } - else - { - if (here.tyvars.size() * there.tyvars.size() >= size_t(FInt::LuauNormalizeUnionLimit)) - return NormalizationResult::HitLimits; + if (FFlag::LuauNormalizerUnionTyvarsTakeMaxSize) + { + auto maxSize = std::max(here.tyvars.size(), there.tyvars.size()); + if (maxSize * maxSize >= size_t(FInt::LuauNormalizeUnionLimit)) + return NormalizationResult::HitLimits; + } + else + { + if (here.tyvars.size() * there.tyvars.size() >= size_t(FInt::LuauNormalizeUnionLimit)) + return NormalizationResult::HitLimits; + } } for (auto it = there.tyvars.begin(); it != there.tyvars.end(); it++) @@ -1585,9 +1745,12 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali return res; } - // Limit based on worst-case expansion of the function unions - if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit)) - return NormalizationResult::HitLimits; + if (!FFlag::LuauNormalizerStepwiseFuel) + { + // Limit based on worst-case expansion of the function unions + if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeUnionLimit)) + return NormalizationResult::HitLimits; + } here.booleans = unionOfBools(here.booleans, there.booleans); unionExternTypes(here.externTypes, there.externTypes); @@ -1633,6 +1796,8 @@ bool Normalizer::useNewLuauSolver() const NormalizationResult Normalizer::intersectNormalWithNegationTy(TypeId toNegate, NormalizedType& intersect) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); std::optional negated; @@ -1658,6 +1823,9 @@ NormalizationResult Normalizer::unionNormalWithTy( if (!withinResourceLimits()) return NormalizationResult::HitLimits; + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + there = follow(there); if (get(there) || get(there)) @@ -1702,7 +1870,7 @@ NormalizationResult Normalizer::unionNormalWithTy( seenSetTypes.insert(there); NormalizedType norm{builtinTypes}; - norm.tops = builtinTypes->anyType; + norm.tops = FFlag::LuauNormalizationPreservesAny ? builtinTypes->unknownType : builtinTypes->anyType; for (IntersectionTypeIterator it = begin(itv); it != end(itv); ++it) { NormalizationResult res = intersectNormalWithTy(norm, *it, seenTablePropPairs, seenSetTypes); @@ -1819,6 +1987,9 @@ NormalizationResult Normalizer::unionNormalWithTy( std::optional Normalizer::negateNormal(const NormalizedType& here) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + NormalizedType result{builtinTypes}; result.isCacheable = here.isCacheable; @@ -1915,6 +2086,9 @@ std::optional Normalizer::negateNormal(const NormalizedType& her TypeIds Normalizer::negateAll(const TypeIds& theres) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + TypeIds tys; for (TypeId there : theres) tys.insert(negate(there)); @@ -1923,6 +2097,9 @@ TypeIds Normalizer::negateAll(const TypeIds& theres) TypeId Normalizer::negate(TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + there = follow(there); if (get(there)) return there; @@ -1952,6 +2129,9 @@ TypeId Normalizer::negate(TypeId there) void Normalizer::subtractPrimitive(NormalizedType& here, TypeId ty) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + const PrimitiveType* ptv = get(follow(ty)); LUAU_ASSERT(ptv); switch (ptv->type) @@ -1985,6 +2165,9 @@ void Normalizer::subtractPrimitive(NormalizedType& here, TypeId ty) void Normalizer::subtractSingleton(NormalizedType& here, TypeId ty) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + const SingletonType* stv = get(ty); LUAU_ASSERT(stv); @@ -2028,14 +2211,40 @@ void Normalizer::subtractSingleton(NormalizedType& here, TypeId ty) // ------- Normalizing intersections TypeId Normalizer::intersectionOfTops(TypeId here, TypeId there) { - if (get(here) || get(there)) - return here; + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + + if (FFlag::LuauNormalizationPreservesAny) + { + // NOTE: We need to wrap these in parens as C++'s parser isn't _quite_ + // able to recognize these are generic function calls after macro + // expansion. + LUAU_ASSERT((is(here))); + LUAU_ASSERT((is(there))); + + if (get(here) || get(there)) + return builtinTypes->neverType; + + if (get(here) || get(there)) + return builtinTypes->anyType; + + return builtinTypes->unknownType; + + } else - return there; + { + if (get(here) || get(there)) + return here; + else + return there; + } } TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (get(here)) return here; if (get(there)) @@ -2051,6 +2260,9 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there) void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (theres.isNever()) { heres.resetToNever(); @@ -2176,6 +2388,9 @@ void Normalizer::intersectExternTypes(NormalizedExternType& heres, const Normali void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + for (auto it = heres.ordering.begin(); it != heres.ordering.end();) { TypeId hereTy = *it; @@ -2247,6 +2462,9 @@ void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedStringType& there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + /* There are 9 cases to worry about here Normalized Left | Normalized Right C1 string | string ===> trivial @@ -2313,6 +2531,25 @@ void Normalizer::intersectStrings(NormalizedStringType& here, const NormalizedSt std::optional Normalizer::intersectionOfTypePacks(TypePackId here, TypePackId there) { + LUAU_ASSERT(FFlag::LuauNormalizerStepwiseFuel); + + FuelInitializer fi{NotNull{this}}; + + try + { + return intersectionOfTypePacks_INTERNAL(here, there); + } + catch (const NormalizerHitLimits&) + { + return std::nullopt; + } +} + +std::optional Normalizer::intersectionOfTypePacks_INTERNAL(TypePackId here, TypePackId there) +{ + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (here == there) return here; @@ -2434,6 +2671,9 @@ std::optional Normalizer::intersectionOfTypePacks(TypePackId here, T std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set& seenSet) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (here == there) return here; @@ -2664,6 +2904,9 @@ std::optional Normalizer::intersectionOfTables(TypeId here, TypeId there void Normalizer::intersectTablesWithTable(TypeIds& heres, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set& seenSetTypes) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + TypeIds tmp; for (TypeId here : heres) { @@ -2676,6 +2919,9 @@ void Normalizer::intersectTablesWithTable(TypeIds& heres, TypeId there, SeenTabl void Normalizer::intersectTables(TypeIds& heres, const TypeIds& theres) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + TypeIds tmp; for (TypeId here : heres) { @@ -2694,6 +2940,9 @@ void Normalizer::intersectTables(TypeIds& heres, const TypeIds& theres) std::optional Normalizer::intersectionOfFunctions(TypeId here, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + const FunctionType* hftv = get(here); LUAU_ASSERT(hftv); const FunctionType* tftv = get(there); @@ -2717,7 +2966,7 @@ std::optional Normalizer::intersectionOfFunctions(TypeId here, TypeId th } else if (hftv->argTypes == tftv->argTypes) { - std::optional retTypesOpt = intersectionOfTypePacks(hftv->argTypes, tftv->argTypes); + std::optional retTypesOpt = intersectionOfTypePacks_INTERNAL(hftv->argTypes, tftv->argTypes); if (!retTypesOpt) return std::nullopt; argTypes = hftv->argTypes; @@ -2828,6 +3077,9 @@ std::optional Normalizer::unionSaturatedFunctions(TypeId here, TypeId th // Proc. Principles and practice of declarative programming 2005, pp 198–208 // https://doi.org/10.1145/1069774.1069793 + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + const FunctionType* hftv = get(here); if (!hftv) return std::nullopt; @@ -2855,6 +3107,9 @@ std::optional Normalizer::unionSaturatedFunctions(TypeId here, TypeId th void Normalizer::intersectFunctionsWithFunction(NormalizedFunctionType& heres, TypeId there) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (heres.isNever()) return; @@ -2887,6 +3142,9 @@ void Normalizer::intersectFunctionsWithFunction(NormalizedFunctionType& heres, T void Normalizer::intersectFunctions(NormalizedFunctionType& heres, const NormalizedFunctionType& theres) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (heres.isNever()) return; else if (theres.isNever()) @@ -2908,6 +3166,9 @@ NormalizationResult Normalizer::intersectTyvarsWithTy( Set& seenSetTypes ) { + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + for (auto it = here.begin(); it != here.end();) { NormalizedType& inter = *it->second; @@ -2929,6 +3190,9 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor if (!withinResourceLimits()) return NormalizationResult::HitLimits; + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + if (!get(there.tops)) { here.tops = intersectionOfTops(here.tops, there.tops); @@ -2942,11 +3206,14 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor // Limit based on worst-case expansion of the table/function intersections // This restriction can be relaxed when table intersection simplification is improved - if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) - return NormalizationResult::HitLimits; + if (!FFlag::LuauNormalizerStepwiseFuel) + { + if (here.tables.size() * there.tables.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) + return NormalizationResult::HitLimits; - if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) - return NormalizationResult::HitLimits; + if (here.functions.parts.size() * there.functions.parts.size() >= size_t(FInt::LuauNormalizeIntersectionLimit)) + return NormalizationResult::HitLimits; + } for (auto& [tyvar, inter] : there.tyvars) { @@ -3013,6 +3280,9 @@ NormalizationResult Normalizer::intersectNormalWithTy( if (!withinResourceLimits()) return NormalizationResult::HitLimits; + if (FFlag::LuauNormalizerStepwiseFuel) + consumeFuel(); + there = follow(there); if (get(there) || get(there)) @@ -3381,6 +3651,34 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm) return arena->addType(UnionType{std::move(result)}); } +bool Normalizer::initializeFuel() +{ + LUAU_ASSERT(FFlag::LuauNormalizerStepwiseFuel); + if (fuel) + return false; + + fuel = FInt::LuauNormalizerInitialFuel; + return true; +} + +void Normalizer::clearFuel() +{ + LUAU_ASSERT(FFlag::LuauNormalizerStepwiseFuel); + fuel = std::nullopt; +} + +void Normalizer::consumeFuel() +{ + LUAU_ASSERT(FFlag::LuauNormalizerStepwiseFuel); + if (fuel) + { + (*fuel)--; + if (fuel <= 0) + throw NormalizerHitLimits(); + } +} + + bool isSubtype( TypeId subTy, TypeId superTy, diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 8fda8fcc..c908709f 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -12,10 +12,7 @@ #include "Luau/Unifier2.h" LUAU_FASTFLAG(LuauLimitUnification) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) namespace Luau @@ -306,23 +303,16 @@ bool OverloadResolver::testFunctionTypeForOverloadSelection( Subtyping::Variance variance = subtyping.variance; subtyping.variance = Subtyping::Variance::Contravariant; subtyping.uniqueTypes = uniqueTypes; - SubtypingResult r; - if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + std::vector generics; + generics.reserve(ftv->generics.size()); + for (TypeId g : ftv->generics) { - std::vector generics; - generics.reserve(ftv->generics.size()); - for (TypeId g : ftv->generics) - { - g = follow(g); - if (get(g)) - generics.emplace_back(g); - } - r = FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, generics) - : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope, generics); + g = follow(g); + if (get(g)) + generics.emplace_back(g); } - else - r = FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, {}) - : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope); + SubtypingResult r = FFlag::LuauPassBindableGenericsByReference ? subtyping.isSubtype(argsPack, ftv->argTypes, scope, generics) + : subtyping.isSubtype_DEPRECATED(argsPack, ftv->argTypes, scope, generics); subtyping.variance = variance; if (!useFreeTypeBounds && !r.assumedConstraints.empty()) @@ -414,86 +404,36 @@ std::pair OverloadResolver::checkOverload_ return {Analysis::Ok, {}}; } - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) + const bool subPathArgTail = matchesPrefix(Path({TypePath::PackField::Arguments, TypePath::PackField::Tail}), reason.subPath); + const bool superPathArgs = matchesPrefix(Path(TypePath::PackField::Arguments), reason.superPath); + const TypePath::Component& lastSubComponent = reason.subPath.components.back(); + const bool subEndsInGenericPackMapping = get_if(&lastSubComponent) != nullptr; + + // If the function's argument list ends with a generic pack, and + // the subtype test failed because of that, we need to check the + // pack that the generic was mapped to in order to report an + // accurate CountMismatch error. + if (subPathArgTail && superPathArgs && subEndsInGenericPackMapping) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - const bool subPathArgTail = matchesPrefix(Path({TypePath::PackField::Arguments, TypePath::PackField::Tail}), reason.subPath); - const bool superPathArgs = matchesPrefix(Path(TypePath::PackField::Arguments), reason.superPath); - const TypePath::Component& lastSubComponent = reason.subPath.components.back(); - const bool subEndsInGenericPackMapping = get_if(&lastSubComponent) != nullptr; - - // If the function's argument list ends with a generic pack, and - // the subtype test failed because of that, we need to check the - // pack that the generic was mapped to in order to report an - // accurate CountMismatch error. - if (subPathArgTail && superPathArgs && subEndsInGenericPackMapping) - { - const TypePack requiredMappedArgs = traverseForFlattenedPack(fnTy, reason.subPath, builtinTypes, arena); - const std::vector prospectiveHead = flatten(typ).first; - - const size_t requiredHeadSize = requiredMappedArgs.head.size(); - const size_t prospectiveHeadSize = prospectiveHead.size(); - - if (prospectiveHeadSize != requiredHeadSize) - { - TypeError error{ - fnExpr->location, - CountMismatch{ - requiredHeadSize, - requiredMappedArgs.tail.has_value() ? std::nullopt : std::optional{requiredHeadSize}, - prospectiveHeadSize, - CountMismatch::Arg - } - }; - - return {Analysis::ArityMismatch, {std::move(error)}}; - } - } - } - // If we have an arity mismatch with generic type pack parameters, then subPath matches Args :: Tail :: ... - // and superPath matches Args :: ... - else if (reason.subPath.components.size() >= 2 && reason.subPath.components[0] == TypePath::PackField::Arguments && - reason.subPath.components[1] == TypePath::PackField::Tail && reason.superPath.components.size() >= 1 && - reason.superPath.components[0] == TypePath::PackField::Arguments) + const TypePack requiredMappedArgs = traverseForFlattenedPack(fnTy, reason.subPath, builtinTypes, arena); + const std::vector prospectiveHead = flatten(typ).first; + + const size_t requiredHeadSize = requiredMappedArgs.head.size(); + const size_t prospectiveHeadSize = prospectiveHead.size(); + + if (prospectiveHeadSize != requiredHeadSize) { - if (const auto [requiredHead, requiredTail] = flatten(fn->argTypes); requiredTail) - { - if (const auto genericTail = get(follow(requiredTail)); genericTail) - { - // Get the concrete type pack the generic is mapped to - const auto mappedGenHead = flatten_DEPRECATED(*requiredTail, sr.mappedGenericPacks_DEPRECATED).first; - - const auto prospectiveHead = flatten(typ).first; - - // We're just doing arity checking here - // We've flattened the type packs, so we can check prospectiveHead = requiredHead + mappedGenHead - // Super path reasoning is just args, so we can ignore the tails - const size_t neededHeadSize = requiredHead.size() + mappedGenHead.size(); - const size_t prospectiveHeadSize = prospectiveHead.size(); - if (prospectiveHeadSize != neededHeadSize) - { - TypeError error{fnExpr->location, CountMismatch{neededHeadSize, std::nullopt, prospectiveHeadSize, CountMismatch::Arg}}; - - return {Analysis::ArityMismatch, {error}}; - } + TypeError error{ + fnExpr->location, + CountMismatch{ + requiredHeadSize, + requiredMappedArgs.tail.has_value() ? std::nullopt : std::optional{requiredHeadSize}, + prospectiveHeadSize, + CountMismatch::Arg } - } - } - else if (reason.subPath == TypePath::Path{{TypePath::PackField::Arguments, TypePath::PackField::Tail}} && - reason.superPath == justArguments) - { - // We have an arity mismatch if the argument tail is a generic type pack - if (auto fnArgs = get(fn->argTypes)) - { - if (get(fnArgs->tail)) - { - auto [minParams, optMaxParams] = getParameterExtents(TxnLog::empty(), fn->argTypes); - TypeError error{fnExpr->location, CountMismatch{minParams, optMaxParams, args->head.size(), CountMismatch::Arg}}; + }; - return {Analysis::ArityMismatch, {std::move(error)}}; - } - } + return {Analysis::ArityMismatch, {std::move(error)}}; } } } @@ -524,59 +464,13 @@ std::pair OverloadResolver::checkOverload_ : argExprs->size() != 0 ? argExprs->back()->location : fnExpr->location; - // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance2 and - // LuauReturnMappedGenericPacksFromSubtyping3 - std::optional failedSubTy; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - failedSubTy = traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); - else - failedSubTy = traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes); - - // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance2 and - // LuauReturnMappedGenericPacksFromSubtyping3 - std::optional failedSuperTy; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - failedSuperTy = traverseForType_DEPRECATED( - prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena - ); - else - failedSuperTy = traverseForType_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); + std::optional failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, arena); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); - else if (failedSubTy && failedSuperTy) - { - switch (shouldSuppressErrors(normalizer, *failedSubTy).orElse(shouldSuppressErrors(normalizer, *failedSuperTy))) - { - case ErrorSuppression::Suppress: - break; - case ErrorSuppression::NormalizationFailed: - errors.emplace_back(argLocation, NormalizationTooComplex{}); - // intentionally fallthrough here since we couldn't prove this was error-suppressing - [[fallthrough]]; - case ErrorSuppression::DoNotSuppress: - // TODO extract location from the SubtypingResult path and argExprs - switch (reason.variance) - { - case SubtypingVariance::Covariant: - case SubtypingVariance::Contravariant: - errors.emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::CovariantContext}); - break; - case SubtypingVariance::Invariant: - errors.emplace_back(argLocation, TypeMismatch{*failedSubTy, *failedSuperTy, TypeMismatch::InvariantContext}); - break; - default: - LUAU_ASSERT(0); - break; - } - } - } + std::optional failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena); + + maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); } - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && reason.superPath.components.size() > 1) + else if (reason.superPath.components.size() > 1) { // traverseForIndex only has a value if path is of form [...PackSlice, Index] if (const auto index = @@ -592,36 +486,15 @@ std::pair OverloadResolver::checkOverload_ LUAU_ASSERT(false); argLocation = fnExpr->location; } - std::optional failedSubTy = - FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 - ? traverseForType(fnTy, reason.subPath, builtinTypes, arena) - : traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); - std::optional failedSuperTy = - FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 - ? traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena) - : traverseForType_DEPRECATED( - prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena - ); + std::optional failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, arena); + std::optional failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena); maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); } } - std::optional failedSubPack; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes, arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - failedSubPack = traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); - else - failedSubPack = traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes); - - std::optional failedSuperPack; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - failedSuperPack = - traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); - else - failedSuperPack = traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); + std::optional failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes, arena); + + std::optional failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, arena); if (failedSubPack && failedSuperPack) { diff --git a/Analysis/src/RequireTracer.cpp b/Analysis/src/RequireTracer.cpp index b286ad00..738fb894 100644 --- a/Analysis/src/RequireTracer.cpp +++ b/Analysis/src/RequireTracer.cpp @@ -71,19 +71,6 @@ struct RequireTracer : AstVisitor return true; } - AstExpr* getDependent_DEPRECATED(AstExpr* node) - { - if (AstExprLocal* expr = node->as()) - return locals[expr->local]; - else if (AstExprIndexName* expr = node->as()) - return expr->expr; - else if (AstExprIndexExpr* expr = node->as()) - return expr->expr; - else if (AstExprCall* expr = node->as(); expr && expr->self) - return expr->func->as()->expr; - else - return nullptr; - } AstNode* getDependent(AstNode* node) { if (AstExprLocal* expr = node->as()) diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index fad76f91..cd1a98a7 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -5,7 +5,6 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAGVARIABLE(LuauNoScopeShallNotSubsumeAll) -LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) namespace Luau { @@ -254,8 +253,6 @@ bool Scope::shouldWarnGlobal(std::string name) const bool Scope::isInvalidTypeAliasName(const std::string& name) const { - LUAU_ASSERT(FFlag::LuauNameConstraintRestrictRecursiveTypes); - for (auto scope = this; scope; scope = scope->parent.get()) { if (scope->invalidTypeAliasNames.contains(name)) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index bd5d6ba9..f4b8361a 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -24,12 +24,9 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauSubtypingRecursionLimit, 100) LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) -LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAGVARIABLE(LuauTrackUniqueness) -LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAGVARIABLE(LuauSubtypingUnionsAndIntersectionsInGenericBounds) LUAU_FASTFLAGVARIABLE(LuauIndexInMetatableSubtyping) LUAU_FASTFLAGVARIABLE(LuauSubtypingPackRecursionLimits) @@ -83,13 +80,10 @@ MappedGenericEnvironment::MappedGenericFrame::MappedGenericFrame( : mappings(std::move(mappings)) , parentScopeIndex(parentScopeIndex) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); } MappedGenericEnvironment::LookupResult MappedGenericEnvironment::lookupGenericPack(TypePackId genericTp) const { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - genericTp = follow(genericTp); std::optional currentFrameIndex = currentScopeIndex; @@ -137,8 +131,6 @@ MappedGenericEnvironment::LookupResult MappedGenericEnvironment::lookupGenericPa void MappedGenericEnvironment::pushFrame(const std::vector& genericTps) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - DenseHashMap> mappings{nullptr}; for (TypePackId tp : genericTps) @@ -156,7 +148,6 @@ void MappedGenericEnvironment::pushFrame(const std::vector& genericT void MappedGenericEnvironment::popFrame() { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); LUAU_ASSERT(currentScopeIndex); if (currentScopeIndex) { @@ -167,7 +158,6 @@ void MappedGenericEnvironment::popFrame() bool MappedGenericEnvironment::bindGeneric(TypePackId genericTp, TypePackId bindeeTp) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); // We shouldn't bind generic type packs to themselves if (genericTp == bindeeTp) return true; @@ -207,37 +197,16 @@ static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const Subtyp template static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& result, NotNull builtinTypes, NotNull arena) { - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); - if (!FFlag::DebugLuauSubtypingCheckPathValidity) return; for (const SubtypingReasoning& reasoning : result.reasoning) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes, arena)); - LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes, arena)); - } - else - { - LUAU_ASSERT(traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes, NotNull{&result.mappedGenericPacks_DEPRECATED}, arena)); - LUAU_ASSERT(traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes, NotNull{&result.mappedGenericPacks_DEPRECATED}, arena)); - } + LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes, arena)); + LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes, arena)); } } -template<> -void assertReasoningValid_DEPRECATED( - TableIndexer subIdx, - TableIndexer superIdx, - const SubtypingResult& result, - NotNull builtinTypes -) -{ - // Empty method to satisfy the compiler. -} - template<> void assertReasoningValid( TableIndexer subIdx, @@ -440,20 +409,10 @@ struct ApplyMappedGenerics : Substitution { NotNull builtinTypes; NotNull arena; - // TODO: make this NotNull when LuauSubtypingGenericsDoesntUseVariance is clipped - InternalErrorReporter* iceReporter; + NotNull iceReporter; SubtypingEnvironment& env; - // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance - ApplyMappedGenerics(NotNull builtinTypes, NotNull arena, SubtypingEnvironment& env) - : Substitution(TxnLog::empty(), arena) - , builtinTypes(builtinTypes) - , arena(arena) - , env(env) - { - } - ApplyMappedGenerics( NotNull builtinTypes, NotNull arena, @@ -480,106 +439,79 @@ struct ApplyMappedGenerics : Substitution TypeId clean(TypeId ty) override { - if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + const auto& [lowerBound, upperBound] = env.getMappedTypeBounds(ty, NotNull{iceReporter}); + + if (upperBound.empty() && lowerBound.empty()) + { + // No bounds for the generic we're mapping. + // In this case, unknown vs never is an arbitrary choice: + // ie, does it matter if we map add to add or add in the context of subtyping? + // We choose unknown here, since it's closest to the original behavior. + return builtinTypes->unknownType; + } + else if (!upperBound.empty()) { - const auto& [lowerBound, upperBound] = env.getMappedTypeBounds(ty, NotNull{iceReporter}); + TypeIds boundsToUse; + + for (TypeId ub : upperBound) + { + // quick and dirty check to avoid adding generic types + if (!get(ub)) + boundsToUse.insert(ub); + } - if (upperBound.empty() && lowerBound.empty()) + if (boundsToUse.empty()) { - // No bounds for the generic we're mapping. + // This case happens when we've collected no bounds for the generic we're mapping. // In this case, unknown vs never is an arbitrary choice: // ie, does it matter if we map add to add or add in the context of subtyping? // We choose unknown here, since it's closest to the original behavior. return builtinTypes->unknownType; } - else if (!upperBound.empty()) - { - TypeIds boundsToUse; - - for (TypeId ub : upperBound) - { - // quick and dirty check to avoid adding generic types - if (!get(ub)) - boundsToUse.insert(ub); - } + if (boundsToUse.size() == 1) + return *boundsToUse.begin(); - if (boundsToUse.empty()) - { - // This case happens when we've collected no bounds for the generic we're mapping. - // In this case, unknown vs never is an arbitrary choice: - // ie, does it matter if we map add to add or add in the context of subtyping? - // We choose unknown here, since it's closest to the original behavior. - return builtinTypes->unknownType; - } - if (boundsToUse.size() == 1) - return *boundsToUse.begin(); + return arena->addType(IntersectionType{boundsToUse.take()}); + } + else if (!lowerBound.empty()) + { + TypeIds boundsToUse; - return arena->addType(IntersectionType{boundsToUse.take()}); - } - else if (!lowerBound.empty()) + for (TypeId lb : lowerBound) { - TypeIds boundsToUse; - - for (TypeId lb : lowerBound) - { - // quick and dirty check to avoid adding generic types - if (!get(lb)) - boundsToUse.insert(lb); - } - - if (boundsToUse.empty()) - { - // This case happens when we've collected no bounds for the generic we're mapping. - // In this case, unknown vs never is an arbitrary choice: - // ie, does it matter if we map add to add or add in the context of subtyping? - // We choose unknown here, since it's closest to the original behavior. - return builtinTypes->unknownType; - } - else if (lowerBound.size() == 1) - return *boundsToUse.begin(); - else - return arena->addType(UnionType{boundsToUse.take()}); + // quick and dirty check to avoid adding generic types + if (!get(lb)) + boundsToUse.insert(lb); } - else + + if (boundsToUse.empty()) { - LUAU_ASSERT(!"Unreachable path"); + // This case happens when we've collected no bounds for the generic we're mapping. + // In this case, unknown vs never is an arbitrary choice: + // ie, does it matter if we map add to add or add in the context of subtyping? + // We choose unknown here, since it's closest to the original behavior. return builtinTypes->unknownType; } + else if (lowerBound.size() == 1) + return *boundsToUse.begin(); + else + return arena->addType(UnionType{boundsToUse.take()}); } else { - const auto& bounds = env.getMappedTypeBounds_DEPRECATED(ty); - - if (bounds.upperBound.empty()) - return builtinTypes->unknownType; - - if (bounds.upperBound.size() == 1) - return *begin(bounds.upperBound); - - return arena->addType(IntersectionType{std::vector(begin(bounds.upperBound), end(bounds.upperBound))}); + LUAU_ASSERT(!"Unreachable path"); + return builtinTypes->unknownType; } } TypePackId clean(TypePackId tp) override { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - const MappedGenericEnvironment::LookupResult result = env.lookupGenericPack(tp); - if (const TypePackId* mappedGen = get_if(&result)) - return *mappedGen; - // Clean is only called when isDirty found a pack bound - LUAU_ASSERT(!"Unreachable"); - return builtinTypes->anyTypePack; - } - else - { - if (auto it = env.getMappedPackBounds_DEPRECATED(tp)) - return *it; - - // Clean is only called when isDirty found a pack bound - LUAU_ASSERT(!"Unreachable"); - return nullptr; - } + const MappedGenericEnvironment::LookupResult result = env.lookupGenericPack(tp); + if (const TypePackId* mappedGen = get_if(&result)) + return *mappedGen; + // Clean is only called when isDirty found a pack bound + LUAU_ASSERT(!"Unreachable"); + return builtinTypes->anyTypePack; } bool ignoreChildren(TypeId ty) override @@ -587,16 +519,13 @@ struct ApplyMappedGenerics : Substitution if (get(ty)) return true; - if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + if (const FunctionType* f = get(ty)) { - if (const FunctionType* f = get(ty)) + for (TypeId g : f->generics) { - for (TypeId g : f->generics) - { - if (const std::vector* bounds = env.mappedGenerics.find(g); bounds && !bounds->empty()) - // We don't want to mutate the generics of a function that's being subtyped - return true; - } + if (const std::vector* bounds = env.mappedGenerics.find(g); bounds && !bounds->empty()) + // We don't want to mutate the generics of a function that's being subtyped + return true; } } @@ -615,17 +544,10 @@ std::optional SubtypingEnvironment::applyMappedGenerics( NotNull iceReporter ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); ApplyMappedGenerics amg{builtinTypes, arena, *this, iceReporter}; return amg.substitute(ty); } -std::optional SubtypingEnvironment::applyMappedGenerics_DEPRECATED(NotNull builtinTypes, NotNull arena, TypeId ty) -{ - ApplyMappedGenerics amg{builtinTypes, arena, *this}; - return amg.substitute(ty); -} - const TypeId* SubtypingEnvironment::tryFindSubstitution_DEPRECATED(TypeId ty) const { LUAU_ASSERT(!FFlag::LuauTryFindSubstitutionReturnOptional); @@ -654,12 +576,7 @@ std::optional SubtypingEnvironment::tryFindSubstitution(TypeId ty) const const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pair subAndSuper) const { - if (FFlag::LuauSubtypingGenericsDoesntUseVariance) - { - if (const auto it = seenSetCache.find(subAndSuper)) - return it; - } - else if (auto it = ephemeralCache.find(subAndSuper)) + if (const auto it = seenSetCache.find(subAndSuper)) return it; if (parent) @@ -670,37 +587,19 @@ const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pairempty()) - return true; - - if (parent) - return parent->containsMappedType(ty); - - return false; - } - else - { - if (mappedGenerics_DEPRECATED.contains(ty)) - return true; + ty = follow(ty); + if (const auto bounds = mappedGenerics.find(ty); bounds && !bounds->empty()) + return true; - if (parent) - return parent->containsMappedType(ty); + if (parent) + return parent->containsMappedType(ty); - return false; - } + return false; } bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - if (const MappedGenericEnvironment::LookupResult lookupResult = mappedGenericPacks.lookupGenericPack(tp); get_if(&lookupResult)) - return true; - } - else if (mappedGenericPacks_DEPRECATED.contains(tp)) + if (const MappedGenericEnvironment::LookupResult lookupResult = mappedGenericPacks.lookupGenericPack(tp); get_if(&lookupResult)) return true; if (parent) @@ -711,7 +610,6 @@ bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const SubtypingEnvironment::GenericBounds& SubtypingEnvironment::getMappedTypeBounds(TypeId ty, NotNull iceReporter) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); ty = follow(ty); std::vector* bounds = mappedGenerics.find(ty); if (bounds && !bounds->empty()) @@ -724,36 +622,8 @@ SubtypingEnvironment::GenericBounds& SubtypingEnvironment::getMappedTypeBounds(T iceReporter->ice("Trying to access bounds for a type with no in-scope bounds"); } -SubtypingEnvironment::GenericBounds_DEPRECATED& SubtypingEnvironment::getMappedTypeBounds_DEPRECATED(TypeId ty) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericsDoesntUseVariance); - if (auto it = mappedGenerics_DEPRECATED.find(ty)) - return *it; - - if (parent) - return parent->getMappedTypeBounds_DEPRECATED(ty); - - LUAU_ASSERT(!"Use containsMappedType before asking for bounds!"); - return mappedGenerics_DEPRECATED[ty]; -} - -TypePackId* SubtypingEnvironment::getMappedPackBounds_DEPRECATED(TypePackId tp) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - if (auto it = mappedGenericPacks_DEPRECATED.find(tp)) - return it; - - if (parent) - return parent->getMappedPackBounds_DEPRECATED(tp); - - // This fallback is reachable in valid cases, unlike the final part of getMappedTypeBounds - return nullptr; -} - MappedGenericEnvironment::LookupResult SubtypingEnvironment::lookupGenericPack(TypePackId tp) const { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); MappedGenericEnvironment::LookupResult result = mappedGenericPacks.lookupGenericPack(tp); if (get_if(&result)) return result; @@ -794,52 +664,8 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull(lb, builtinTypes->neverType); - TypeId upperBound = makeAggregateType(ub, builtinTypes->unknownType); - - std::shared_ptr nt = normalizer->normalize(upperBound); - // we say that the result is true if normalization failed because complex types are likely to be inhabited. - NormalizationResult res = nt ? normalizer->isInhabited(nt.get()) : NormalizationResult::True; - - if (!nt || res == NormalizationResult::HitLimits) - result.normalizationTooComplex = true; - else if (res == NormalizationResult::False) - { - /* If the normalized upper bound we're mapping to a generic is - * uninhabited, then we must consider the subtyping relation not to - * hold. - * - * This happens eg in () -> (T, T) <: () -> (string, number) - * - * T appears in covariant position and would have to be both string - * and number at once. - * - * No actual value is both a string and a number, so the test fails. - * - * TODO: We'll need to add explanitory context here. - */ - result.isSubtype = false; - } - - SubtypingEnvironment boundsEnv; - boundsEnv.parent = &env; - SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope); - boundsResult.reasoning.clear(); - - result.andAlso(boundsResult); - } - } + for (const auto& [_, bounds] : env.mappedGenerics) + LUAU_ASSERT(bounds.empty()); /* TODO: We presently don't store subtype test results in the persistent * cache if the left-side type is a generic function. @@ -853,9 +679,6 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull* bounds = env.mappedGenerics.find(bg)) + if (const std::vector* bounds = env.mappedGenerics.find(bg)) + { + // Bounds should have exactly one entry + LUAU_ASSERT(bounds->size() == 1); + if (FFlag::LuauSubtypingReportGenericBoundMismatches2) { - // Bounds should have exactly one entry - LUAU_ASSERT(bounds->size() == 1); - if (FFlag::LuauSubtypingReportGenericBoundMismatches2) - { - if (bounds->empty()) - continue; - if (const GenericType* gen = get(bg)) - result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name)); - } - else if (!bounds->empty()) - result.andAlso(checkGenericBounds_DEPRECATED(bounds->back(), env, scope)); + if (bounds->empty()) + continue; + if (const GenericType* gen = get(bg)) + result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name)); } + else if (!bounds->empty()) + result.andAlso(checkGenericBounds_DEPRECATED(bounds->back(), env, scope)); } } @@ -918,7 +729,7 @@ SubtypingResult Subtyping::isSubtype_DEPRECATED( LUAU_ASSERT(!FFlag::LuauPassBindableGenericsByReference); SubtypingEnvironment env; - if (FFlag::LuauSubtypingGenericsDoesntUseVariance && bindableGenerics) + if (bindableGenerics) { for (TypeId g : *bindableGenerics) env.mappedGenerics[follow(g)] = {SubtypingEnvironment::GenericBounds{}}; @@ -926,13 +737,7 @@ SubtypingResult Subtyping::isSubtype_DEPRECATED( SubtypingResult result = isCovariantWith(env, subTp, superTp, scope); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - if (!env.mappedGenericPacks_DEPRECATED.empty()) - result.mappedGenericPacks_DEPRECATED = std::move(env.mappedGenericPacks_DEPRECATED); - } - - if (FFlag::LuauSubtypingGenericsDoesntUseVariance && bindableGenerics) + if (bindableGenerics) { for (TypeId bg : *bindableGenerics) { @@ -964,13 +769,8 @@ SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult resu { const std::pair p{subTy, superTy}; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - result.mappedGenericPacks_DEPRECATED = env.mappedGenericPacks_DEPRECATED; - if (result.isCacheable) resultCache[p] = result; - else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance) - env.ephemeralCache[p] = result; return result; } @@ -1013,24 +813,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub const SubtypingResult* cachedResult = resultCache.find({subTy, superTy}); if (cachedResult) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks_DEPRECATED) - env.mappedGenericPacks_DEPRECATED.try_insert(genericTp, boundTp); - } - return *cachedResult; } cachedResult = env.tryFindSubtypingResult({subTy, superTy}); if (cachedResult) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks_DEPRECATED) - env.mappedGenericPacks_DEPRECATED.try_insert(genericTp, boundTp); - } - return *cachedResult; } @@ -1065,8 +853,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub res.isSubtype = true; res.isCacheable = false; - if (FFlag::LuauSubtypingGenericsDoesntUseVariance) - env.seenSetCache[typePair] = res; + env.seenSetCache[typePair] = res; return res; } @@ -1128,8 +915,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result = {false}; else if (get(subTy)) result = {true}; - else if (auto subTypeFunctionInstance = get(subTy); - subTypeFunctionInstance && FFlag::LuauSubtypingGenericsDoesntUseVariance) + else if (auto subTypeFunctionInstance = get(subTy)) { bool mappedGenericsApplied = false; if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy, iceReporter)) @@ -1141,8 +927,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope); result.isCacheable = !mappedGenericsApplied; } - else if (auto superTypeFunctionInstance = get(superTy); - superTypeFunctionInstance && FFlag::LuauSubtypingGenericsDoesntUseVariance) + else if (auto superTypeFunctionInstance = get(superTy)) { bool mappedGenericsApplied = false; if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy, iceReporter)) @@ -1154,7 +939,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope); result.isCacheable = !mappedGenericsApplied; } - else if (FFlag::LuauSubtypingGenericsDoesntUseVariance && (get(subTy) || get(superTy))) + else if (get(subTy) || get(superTy)) { if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty()) { @@ -1193,18 +978,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub if (!result.isSubtype && !result.normalizationTooComplex) result = trySemanticSubtyping(env, subTy, superTy, scope, result); } - else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance && get(subTy) && variance == Variance::Covariant) - { - bool ok = bindGeneric(env, subTy, superTy); - result.isSubtype = ok; - result.isCacheable = false; - } - else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance && get(superTy) && variance == Variance::Contravariant) - { - bool ok = bindGeneric(env, subTy, superTy); - result.isSubtype = ok; - result.isCacheable = false; - } else if (auto pair = get2(subTy, superTy)) { // Any two free types are potentially subtypes of one another because @@ -1257,22 +1030,6 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub if (!result.isSubtype && !result.normalizationTooComplex) result = trySemanticSubtyping(env, subTy, superTy, scope, result); } - else if (auto subTypeFunctionInstance = get(subTy)) - { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericsDoesntUseVariance); - if (auto substSubTy = env.applyMappedGenerics_DEPRECATED(builtinTypes, arena, subTy)) - subTypeFunctionInstance = get(*substSubTy); - - result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope); - } - else if (auto superTypeFunctionInstance = get(superTy)) - { - LUAU_ASSERT(!FFlag::LuauSubtypingGenericsDoesntUseVariance); - if (auto substSuperTy = env.applyMappedGenerics_DEPRECATED(builtinTypes, arena, superTy)) - superTypeFunctionInstance = get(*substSuperTy); - - result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope); - } else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) @@ -1311,10 +1068,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - assertReasoningValid(subTy, superTy, result, builtinTypes, arena); - else - assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); + assertReasoningValid(subTy, superTy, result, builtinTypes, arena); return cache(env, std::move(result), subTy, superTy); } @@ -1354,14 +1108,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp = follow(subTp); superTp = follow(superTp); - std::optional>> popper; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - { - std::pair typePair = {subTp, superTp}; - if (!seenPacks.insert(typePair)) - return SubtypingResult{true, false, false}; - popper.emplace(seenPacks, std::move(typePair)); - } + std::pair typePair = {subTp, superTp}; + if (!seenPacks.insert(typePair)) + return SubtypingResult{true, false, false}; + ScopedSeenSet> popper{seenPacks, std::move(typePair)}; auto [subHead, subTail] = flatten(subTp); auto [superHead, superTail] = flatten(superTp); @@ -1479,10 +1229,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId SubtypingResult result = SubtypingResult::all(results); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - assertReasoningValid(subTp, superTp, result, builtinTypes, arena); - else - assertReasoningValid_DEPRECATED(subTp, superTp, result, builtinTypes); + assertReasoningValid(subTp, superTp, result, builtinTypes, arena); return result; } @@ -1524,109 +1271,42 @@ std::optional Subtyping::isSubTailCovariantWith( } else if (get(subTail)) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTail); + SubtypingResult result; + if (get_if(&lookupResult)) + result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} + .withSubComponent(TypePath::PackField::Tail) + .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); + else { - MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTail); - SubtypingResult result; - if (get_if(&lookupResult)) - result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} - .withSubComponent(TypePath::PackField::Tail) - .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); - else - { - TypePackId superTailPack = sliceTypePack(superHeadStartIndex, superTp, superHead, superTail, builtinTypes, arena); + TypePackId superTailPack = sliceTypePack(superHeadStartIndex, superTp, superHead, superTail, builtinTypes, arena); - if (const TypePackId* mappedGen = get_if(&lookupResult)) - { - // Subtype against the mapped generic pack. - TypePackId subTpToCompare = *mappedGen; + if (const TypePackId* mappedGen = get_if(&lookupResult)) + { + // Subtype against the mapped generic pack. + TypePackId subTpToCompare = *mappedGen; - // If mappedGen has a hidden variadic tail, we clip it for better arity mismatch reporting. - const TypePack* tp = get(*mappedGen); - if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) - subTpToCompare = arena->addTypePack(tp->head); + // If mappedGen has a hidden variadic tail, we clip it for better arity mismatch reporting. + const TypePack* tp = get(*mappedGen); + if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) + subTpToCompare = arena->addTypePack(tp->head); - result = isCovariantWith(env, subTpToCompare, superTailPack, scope) - .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}})) - .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - bool ok = env.mappedGenericPacks.bindGeneric(subTail, superTailPack); - result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false} - .withSubComponent(TypePath::PackField::Tail) - .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); - } - } - - outputResults.push_back(result); - return SubtypingResult::all(outputResults); - } - else if (variance == Variance::Covariant) - { - // For any non-generic type T: - // - // (X) -> () <: (T) -> () - - TypePackId superTailPack; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - { - if (superHeadStartIndex == 0) - superTailPack = superTp; - else if (superHeadStartIndex == superHead.size()) - superTailPack = superTail ? *superTail : builtinTypes->emptyTypePack; - else - { - auto superHeadIter = begin(superHead); - for (size_t i = 0; i < superHeadStartIndex; ++i) - ++superHeadIter; - std::vector headSlice(std::move(superHeadIter), end(superHead)); - superTailPack = arena->addTypePack(std::move(headSlice), superTail); - } + result = isCovariantWith(env, subTpToCompare, superTailPack, scope) + .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}})) + .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); } else { - // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice = std::vector(begin(superHead), begin(superHead) + int(superHeadStartIndex)); - superTailPack = arena->addTypePack(std::move(headSlice), superTail); + LUAU_ASSERT(get_if(&lookupResult)); + bool ok = env.mappedGenericPacks.bindGeneric(subTail, superTailPack); + result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false} + .withSubComponent(TypePath::PackField::Tail) + .withSuperComponent(TypePath::PackSlice{superHeadStartIndex}); } - - if (TypePackId* other = env.getMappedPackBounds_DEPRECATED(subTail)) - { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - { - const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) - { - TypePackId taillessTp = arena->addTypePack(tp->head); - outputResults.push_back(isCovariantWith(env, taillessTp, superTailPack, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperComponent(TypePath::PackSlice{superHeadStartIndex})); - } - else - outputResults.push_back(isCovariantWith(env, *other, superTailPack, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperComponent(TypePath::PackSlice{superHeadStartIndex})); - } - else - outputResults.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail)); - } - else - env.mappedGenericPacks_DEPRECATED.try_insert(subTail, superTailPack); - - // FIXME? Not a fan of the early return here. It makes the - // control flow harder to reason about. - return SubtypingResult::all(outputResults); - } - else - { - // For any non-generic type T: - // - // (T) -> () (X) -> () - // - return SubtypingResult{false}.withSubComponent(TypePath::PackField::Tail); } + + outputResults.push_back(result); + return SubtypingResult::all(outputResults); } else if (get(subTail)) return SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail); @@ -1658,107 +1338,41 @@ std::optional Subtyping::isCovariantWithSuperTail( } else if (get(superTail)) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTail); + SubtypingResult result; + if (get_if(&lookupResult)) + result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} + .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) + .withSuperComponent(TypePath::PackField::Tail); + else { - MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTail); - SubtypingResult result; - if (get_if(&lookupResult)) - result = SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false} - .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) - .withSuperComponent(TypePath::PackField::Tail); - else - { - TypePackId subTailPack = sliceTypePack(subHeadStartIndex, subTp, subHead, subTail, builtinTypes, arena); + TypePackId subTailPack = sliceTypePack(subHeadStartIndex, subTp, subHead, subTail, builtinTypes, arena); - if (const TypePackId* mappedGen = get_if(&lookupResult)) - { - TypePackId superTpToCompare = *mappedGen; + if (const TypePackId* mappedGen = get_if(&lookupResult)) + { + TypePackId superTpToCompare = *mappedGen; - // Subtype against the mapped generic pack. - const TypePack* tp = get(*mappedGen); - if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) - superTpToCompare = arena->addTypePack(tp->head); + // Subtype against the mapped generic pack. + const TypePack* tp = get(*mappedGen); + if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) + superTpToCompare = arena->addTypePack(tp->head); - result = isCovariantWith(env, subTailPack, superTpToCompare, scope) - .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}})); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - bool ok = env.mappedGenericPacks.bindGeneric(superTail, subTailPack); - result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false} - .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) - .withSuperComponent(TypePath::PackField::Tail); - } - } - - results.push_back(result); - return SubtypingResult::all(results); - } - else if (variance == Variance::Contravariant) - { - // For any non-generic type T: - // - // (X...) -> () <: (T) -> () - - TypePackId subTailPack; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - { - if (subHeadStartIndex == 0) - subTailPack = subTp; - else if (subHeadStartIndex == subHead.size()) - subTailPack = subTail ? *subTail : builtinTypes->emptyTypePack; - else - { - auto subHeadIter = begin(subHead); - for (size_t i = 0; i < subHeadStartIndex; ++i) - ++subHeadIter; - std::vector headSlice(std::move(subHeadIter), end(subHead)); - subTailPack = arena->addTypePack(std::move(headSlice), subTail); - } + result = isCovariantWith(env, subTailPack, superTpToCompare, scope) + .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*mappedGen}})); } else { - // Possible optimization: If headSize == 0 then we can just use subTp as-is. - std::vector headSlice = std::vector(begin(subHead), begin(subHead) + int(subHeadStartIndex)); - subTailPack = arena->addTypePack(std::move(headSlice), subTail); + LUAU_ASSERT(get_if(&lookupResult)); + bool ok = env.mappedGenericPacks.bindGeneric(superTail, subTailPack); + result = SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false} + .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) + .withSuperComponent(TypePath::PackField::Tail); } - - if (TypePackId* other = env.getMappedPackBounds_DEPRECATED(superTail)) - { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - { - const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = tp ? get(follow(tp->tail)) : nullptr; vtp && vtp->hidden) - { - TypePackId taillessTp = arena->addTypePack(tp->head); - results.push_back(isCovariantWith(env, subTailPack, taillessTp, scope) - .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) - .withSuperComponent(TypePath::PackField::Tail)); - } - else - results.push_back(isCovariantWith(env, subTailPack, *other, scope) - .withSubComponent(TypePath::PackSlice{subHeadStartIndex}) - .withSuperComponent(TypePath::PackField::Tail)); - } - else - results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); - } - else - env.mappedGenericPacks_DEPRECATED.try_insert(superTail, subTailPack); - - // FIXME? Not a fan of the early return here. It makes the - // control flow harder to reason about. - return SubtypingResult::all(results); - } - else - { - // For any non-generic type T: - // - // () -> T () -> X... - return SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail); } + + results.push_back(result); + return SubtypingResult::all(results); } else if (get(superTail)) return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail); @@ -1793,91 +1407,66 @@ SubtypingResult Subtyping::isTailCovariantWithTail( const GenericTypePack* super ) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - MappedGenericEnvironment::LookupResult subLookupResult = env.lookupGenericPack(subTp); - MappedGenericEnvironment::LookupResult superLookupResult = env.lookupGenericPack(superTp); + MappedGenericEnvironment::LookupResult subLookupResult = env.lookupGenericPack(subTp); + MappedGenericEnvironment::LookupResult superLookupResult = env.lookupGenericPack(superTp); - // match (subLookup, superLookupResult) { - // (TypePackId, _) => do covariant test - // (Unmapped, _) => bind the generic - // (_, TypePackId) => do covariant test - // (_, Unmapped) => bind the generic - // (_, _) => subtyping succeeds if the two generics are pointer-identical - // } - if (const TypePackId* currMapping = get_if(&subLookupResult)) - { - return isCovariantWith(env, *currMapping, superTp, scope) - .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})) - .withSuperComponent(TypePath::PackField::Tail); - } - else if (get_if(&subLookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(subTp, superTp); - return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); - } - else if (const TypePackId* currMapping = get_if(&superLookupResult)) - { - return isCovariantWith(env, subTp, *currMapping, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); - } - else if (get_if(&superLookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp); - return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); - } - else - { - // Sometimes, we compare generic packs inside the functions which are quantifying them. They're not bindable, but should still - // subtype against themselves. - return SubtypingResult{subTp == superTp, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( - TypePath::PackField::Tail - ); - } + // match (subLookup, superLookupResult) { + // (TypePackId, _) => do covariant test + // (Unmapped, _) => bind the generic + // (_, TypePackId) => do covariant test + // (_, Unmapped) => bind the generic + // (_, _) => subtyping succeeds if the two generics are pointer-identical + // } + if (const TypePackId* currMapping = get_if(&subLookupResult)) + { + return isCovariantWith(env, *currMapping, superTp, scope) + .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})) + .withSuperComponent(TypePath::PackField::Tail); + } + else if (get_if(&subLookupResult)) + { + bool ok = env.mappedGenericPacks.bindGeneric(subTp, superTp); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); + } + else if (const TypePackId* currMapping = get_if(&superLookupResult)) + { + return isCovariantWith(env, subTp, *currMapping, scope) + .withSubComponent(TypePath::PackField::Tail) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); + } + else if (get_if(&superLookupResult)) + { + bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); } else { - bool ok = bindGeneric_DEPRECATED(env, subTp, superTp); - return SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail); + // Sometimes, we compare generic packs inside the functions which are quantifying them. They're not bindable, but should still + // subtype against themselves. + return SubtypingResult{subTp == superTp, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( + TypePath::PackField::Tail + ); } } SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const VariadicTypePack* sub, TypePackId superTp, const GenericTypePack* super) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp); + if (const TypePackId* currMapping = get_if(&lookupResult)) { - MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp); - if (const TypePackId* currMapping = get_if(&lookupResult)) - { - return isCovariantWith(env, subTp, *currMapping, scope) - .withSubComponent(TypePath::PackField::Tail) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); - } - else if (get_if(&lookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp); - return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent( - TypePath::PackField::Tail - ); - } + return isCovariantWith(env, subTp, *currMapping, scope) + .withSubComponent(TypePath::PackField::Tail) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); } - else if (variance == Variance::Contravariant) + else if (get_if(&lookupResult)) { - // (A...) -> number <: (...number) -> number - bool ok = bindGeneric_DEPRECATED(env, subTp, superTp); - - return SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail); + bool ok = env.mappedGenericPacks.bindGeneric(superTp, subTp); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); } else { - // (number) -> ...number (number) -> A... - return SubtypingResult{false}.withBothComponent(TypePath::PackField::Tail); + LUAU_ASSERT(get_if(&lookupResult)); + return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withBothComponent(TypePath::PackField::Tail); } } @@ -1892,7 +1481,7 @@ SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, No // See https://github.com/luau-lang/luau/issues/767 return SubtypingResult{true}; } - else if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + else { MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp); if (const TypePackId* currMapping = get_if(&lookupResult)) @@ -1914,75 +1503,42 @@ SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, No ); } } - else if (variance == Variance::Contravariant) - { - // (...number) -> number (A...) -> number - return SubtypingResult{false}.withBothComponent(TypePath::PackField::Tail); - } - else - { - // () -> A... <: () -> ...number - bool ok = bindGeneric_DEPRECATED(env, subTp, superTp); - return SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail); - } } SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, TypePackId subTp, const GenericTypePack* sub, Nothing) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp); + if (const TypePackId* currMapping = get_if(&lookupResult)) + return isCovariantWith(env, *currMapping, builtinTypes->emptyTypePack, scope) + .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); + else if (get_if(&lookupResult)) { - MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(subTp); - if (const TypePackId* currMapping = get_if(&lookupResult)) - return isCovariantWith(env, *currMapping, builtinTypes->emptyTypePack, scope) - .withSubPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); - else if (get_if(&lookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(subTp, builtinTypes->emptyTypePack); - return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent(TypePath::PackField::Tail); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent( - TypePath::PackField::Tail - ); - } + bool ok = env.mappedGenericPacks.bindGeneric(subTp, builtinTypes->emptyTypePack); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent(TypePath::PackField::Tail); } else { - bool ok = bindGeneric_DEPRECATED(env, subTp, builtinTypes->emptyTypePack); - return SubtypingResult{ok}.withSubComponent(TypePath::PackField::Tail); + LUAU_ASSERT(get_if(&lookupResult)); + return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSubComponent(TypePath::PackField::Tail); } } SubtypingResult Subtyping::isTailCovariantWithTail(SubtypingEnvironment& env, NotNull scope, Nothing, TypePackId superTp, const GenericTypePack* super) { - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp); + if (const TypePackId* currMapping = get_if(&lookupResult)) + return isCovariantWith(env, builtinTypes->emptyTypePack, *currMapping, scope) + .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); + else if (get_if(&lookupResult)) { - MappedGenericEnvironment::LookupResult lookupResult = env.lookupGenericPack(superTp); - if (const TypePackId* currMapping = get_if(&lookupResult)) - return isCovariantWith(env, builtinTypes->emptyTypePack, *currMapping, scope) - .withSuperPath(Path({TypePath::PackField::Tail, TypePath::GenericPackMapping{*currMapping}})); - else if (get_if(&lookupResult)) - { - bool ok = env.mappedGenericPacks.bindGeneric(superTp, builtinTypes->emptyTypePack); - return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent( - TypePath::PackField::Tail - ); - } - else - { - LUAU_ASSERT(get_if(&lookupResult)); - return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent(TypePath::PackField::Tail); - } + bool ok = env.mappedGenericPacks.bindGeneric(superTp, builtinTypes->emptyTypePack); + return SubtypingResult{ok, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent(TypePath::PackField::Tail); } - else if (variance == Variance::Contravariant) + else { - bool ok = bindGeneric_DEPRECATED(env, builtinTypes->emptyTypePack, superTp); - return SubtypingResult{ok}.withSuperComponent(TypePath::PackField::Tail); + LUAU_ASSERT(get_if(&lookupResult)); + return SubtypingResult{false, /* normalizationTooComplex */ false, /* isCacheable */ false}.withSuperComponent(TypePath::PackField::Tail); } - else - return SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail); } @@ -2013,10 +1569,7 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy& } } - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - assertReasoningValid(subTy, superTy, result, builtinTypes, arena); - else - assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); + assertReasoningValid(subTy, superTy, result, builtinTypes, arena); return result; } @@ -2035,10 +1588,7 @@ SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& su reasoning.variance = SubtypingVariance::Invariant; } - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - assertReasoningValid(subTy, superTy, result, builtinTypes, arena); - else - assertReasoningValid_DEPRECATED(subTy, superTy, result, builtinTypes); + assertReasoningValid(subTy, superTy, result, builtinTypes, arena); return result; } @@ -2627,7 +2177,7 @@ SubtypingResult Subtyping::isCovariantWith( { SubtypingResult result; - if (FFlag::LuauSubtypingGenericsDoesntUseVariance && !subFunction->generics.empty()) + if (!subFunction->generics.empty()) { for (TypeId g : subFunction->generics) { @@ -2643,7 +2193,7 @@ SubtypingResult Subtyping::isCovariantWith( } } - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 && !subFunction->genericPacks.empty()) + if (!subFunction->genericPacks.empty()) { std::vector packs; packs.reserve(subFunction->genericPacks.size()); @@ -2691,7 +2241,7 @@ SubtypingResult Subtyping::isCovariantWith( ); } - if (FFlag::LuauSubtypingGenericsDoesntUseVariance && !subFunction->generics.empty()) + if (!subFunction->generics.empty()) { for (TypeId g : subFunction->generics) { @@ -2711,7 +2261,7 @@ SubtypingResult Subtyping::isCovariantWith( } } - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 && !subFunction->genericPacks.empty()) + if (!subFunction->genericPacks.empty()) { env.mappedGenericPacks.popFrame(); // This result isn't cacheable, because we may need it to populate the generic pack mapping environment again later @@ -3032,77 +2582,51 @@ SubtypingResult Subtyping::isCovariantWith( bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId superTy) { - if (FFlag::LuauSubtypingGenericsDoesntUseVariance) - { - subTy = follow(subTy); - superTy = follow(superTy); - std::optional originalSubTyBounds = std::nullopt; - - if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty()) - { - LUAU_ASSERT(get(subTy)); - - originalSubTyBounds = SubtypingEnvironment::GenericBounds{subBounds->back()}; - - auto& [lowerSubBounds, upperSubBounds] = subBounds->back(); + subTy = follow(subTy); + superTy = follow(superTy); + std::optional originalSubTyBounds = std::nullopt; - if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty()) - { - LUAU_ASSERT(get(superTy)); + if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty()) + { + LUAU_ASSERT(get(subTy)); - const auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back(); + originalSubTyBounds = SubtypingEnvironment::GenericBounds{subBounds->back()}; - maybeUpdateBounds(subTy, superTy, upperSubBounds, lowerSuperBounds, upperSuperBounds); - } - else - upperSubBounds.insert(superTy); - } - else if (env.containsMappedType(subTy)) - iceReporter->ice("attempting to modify bounds of a potentially visited generic"); + auto& [lowerSubBounds, upperSubBounds] = subBounds->back(); if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty()) { LUAU_ASSERT(get(superTy)); - auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back(); + const auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back(); - if (originalSubTyBounds) - { - LUAU_ASSERT(get(subTy)); - - const auto& [originalLowerSubBound, originalUpperSubBound] = *originalSubTyBounds; - - maybeUpdateBounds(superTy, subTy, lowerSuperBounds, originalUpperSubBound, originalLowerSubBound); - } - else - lowerSuperBounds.insert(subTy); + maybeUpdateBounds(subTy, superTy, upperSubBounds, lowerSuperBounds, upperSuperBounds); } - else if (env.containsMappedType(superTy)) - iceReporter->ice("attempting to modify bounds of a potentially visited generic"); + else + upperSubBounds.insert(superTy); } - else + else if (env.containsMappedType(subTy)) + iceReporter->ice("attempting to modify bounds of a potentially visited generic"); + + if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty()) { - if (variance == Variance::Covariant) - { - if (!get(subTy)) - return false; + LUAU_ASSERT(get(superTy)); - if (!env.mappedGenerics_DEPRECATED.find(subTy) && env.containsMappedType(subTy)) - iceReporter->ice("attempting to modify bounds of a potentially visited generic"); + auto& [lowerSuperBounds, upperSuperBounds] = superBounds->back(); - env.mappedGenerics_DEPRECATED[subTy].upperBound.insert(superTy); - } - else + if (originalSubTyBounds) { - if (!get(superTy)) - return false; + LUAU_ASSERT(get(subTy)); - if (!env.mappedGenerics_DEPRECATED.find(superTy) && env.containsMappedType(superTy)) - iceReporter->ice("attempting to modify bounds of a potentially visited generic"); + const auto& [originalLowerSubBound, originalUpperSubBound] = *originalSubTyBounds; - env.mappedGenerics_DEPRECATED[superTy].lowerBound.insert(subTy); + maybeUpdateBounds(superTy, subTy, lowerSuperBounds, originalUpperSubBound, originalLowerSubBound); } + else + lowerSuperBounds.insert(subTy); } + else if (env.containsMappedType(superTy)) + iceReporter->ice("attempting to modify bounds of a potentially visited generic"); return true; } @@ -3133,35 +2657,6 @@ SubtypingResult Subtyping::isCovariantWith( return isCovariantWith(env, subTy, ty, scope).withErrors(errors).withSuperComponent(TypePath::Reduction{ty}); } -/* - * If, when performing a subtyping test, we encounter a generic on the left - * side, it is permissible to tentatively bind that generic to the right side - * type. - */ -bool Subtyping::bindGeneric_DEPRECATED(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) const -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - if (variance == Variance::Contravariant) - std::swap(superTp, subTp); - - if (!get(subTp)) - return false; - - if (TypePackId* m = env.getMappedPackBounds_DEPRECATED(subTp)) - return *m == superTp; - - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - { - // We shouldn't bind generic type packs to themselves - if (subTp == superTp) - return true; - } - - env.mappedGenericPacks_DEPRECATED[subTp] = superTp; - - return true; -} - template TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse) { @@ -3223,7 +2718,6 @@ SubtypingResult Subtyping::checkGenericBounds( std::string_view genericName ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches2); SubtypingResult result{true}; @@ -3342,7 +2836,6 @@ SubtypingResult Subtyping::checkGenericBounds_DEPRECATED( NotNull scope ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); LUAU_ASSERT(!FFlag::LuauSubtypingReportGenericBoundMismatches2); SubtypingResult result{true}; diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index 27d8efdd..4ac04252 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -4,6 +4,7 @@ #include "Luau/Ast.h" #include "Luau/Common.h" +#include "Luau/ConstraintSolver.h" #include "Luau/HashUtil.h" #include "Luau/Simplify.h" #include "Luau/Subtyping.h" @@ -16,6 +17,7 @@ LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintIntersection) LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintSingleton) LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintIndexer) +LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraintLambdas) namespace Luau { @@ -138,8 +140,15 @@ struct BidirectionalTypePusher // ... where we are attempting to push a singleton onto any string // literal, and the lower bound is still a singleton, then snap // to said lower bound. - emplaceType(asMutable(exprType), ft->lowerBound); - solver->unblock(exprType, expr->location); + if (FFlag::LuauPushTypeConstraintLambdas) + { + solver->bind(constraint, exprType, ft->lowerBound); + } + else + { + emplaceType(asMutable(exprType), ft->lowerBound); + solver->unblock(exprType, expr->location); + } return exprType; } @@ -147,8 +156,15 @@ struct BidirectionalTypePusher Relation upperBoundRelation = relate(ft->upperBound, expectedType); if (upperBoundRelation == Relation::Subset || upperBoundRelation == Relation::Coincident) { - emplaceType(asMutable(exprType), expectedType); - solver->unblock(exprType, expr->location); + if (FFlag::LuauPushTypeConstraintLambdas) + { + solver->bind(constraint, exprType, expectedType); + } + else + { + emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); + } return exprType; } @@ -158,8 +174,15 @@ struct BidirectionalTypePusher Relation lowerBoundRelation = relate(ft->lowerBound, expectedType); if (lowerBoundRelation == Relation::Subset || lowerBoundRelation == Relation::Coincident) { - emplaceType(asMutable(exprType), expectedType); - solver->unblock(exprType, expr->location); + if (FFlag::LuauPushTypeConstraintLambdas) + { + solver->bind(constraint, exprType, expectedType); + } + else + { + emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); + } return exprType; } } @@ -174,8 +197,15 @@ struct BidirectionalTypePusher Relation upperBoundRelation = relate(ft->upperBound, expectedType); if (upperBoundRelation == Relation::Subset || upperBoundRelation == Relation::Coincident) { - emplaceType(asMutable(exprType), expectedType); - solver->unblock(exprType, expr->location); + if (FFlag::LuauPushTypeConstraintLambdas) + { + solver->bind(constraint, exprType, expectedType); + } + else + { + emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); + } return exprType; } @@ -185,8 +215,15 @@ struct BidirectionalTypePusher Relation lowerBoundRelation = relate(ft->lowerBound, expectedType); if (lowerBoundRelation == Relation::Subset || lowerBoundRelation == Relation::Coincident) { - emplaceType(asMutable(exprType), expectedType); - solver->unblock(exprType, expr->location); + if (FFlag::LuauPushTypeConstraintLambdas) + { + solver->bind(constraint, exprType, expectedType); + } + else + { + emplaceType(asMutable(exprType), expectedType); + solver->unblock(exprType, expr->location); + } return exprType; } } @@ -209,10 +246,38 @@ struct BidirectionalTypePusher return exprType; } + if (FFlag::LuauPushTypeConstraintLambdas) + { + if (auto exprLambda = expr->as()) + { + const auto lambdaTy = get(exprType); + const auto expectedLambdaTy = get(expectedType); + if (lambdaTy && expectedLambdaTy && !ContainsAnyGeneric::hasAnyGeneric(expectedType)) + { + const auto& [lambdaArgTys, _lambdaTail] = flatten(lambdaTy->argTypes); + const auto& [expectedLambdaArgTys, _expectedLambdaTail] = flatten(expectedLambdaTy->argTypes); + + auto limit = std::min({lambdaArgTys.size(), expectedLambdaArgTys.size(), exprLambda->args.size}); + for (size_t argIndex = 0; argIndex < limit; argIndex++) + { + if (!exprLambda->args.data[argIndex]->annotation && get(follow(lambdaArgTys[argIndex]))) + solver->bind(NotNull{constraint}, lambdaArgTys[argIndex], expectedLambdaArgTys[argIndex]); + } + + if (!exprLambda->returnAnnotation && get(follow(lambdaTy->retTypes))) + solver->bind(NotNull{constraint}, lambdaTy->retTypes, expectedLambdaTy->retTypes); + } + } + } + else + { + if (expr->is()) + { + // TODO: Push argument / return types into the lambda. + return exprType; + } + } - if (expr->is()) - // TODO: Push argument / return types into the lambda. - return exprType; // TODO: CLI-169235: This probably ought to use the same logic as // `index` to determine what the type of a given member is. diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 8b832a39..b7007cc7 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -1954,6 +1954,29 @@ std::string dump(const Constraint& c) return s; } +// Converts the given number index into a human-readable string for that index to be used in errors. +// e.g. the index `0` becomes `1st`, `1` becomes `2nd`, `11` becomes `12th`, etc. +std::string toHumanReadableIndex(size_t number) +{ + size_t humanIndex = number + 1; + size_t finalDigit = humanIndex % 10; + + if (humanIndex > 10 && humanIndex < 20) + return std::to_string(humanIndex) + "th"; + + switch (finalDigit) + { + case 1: + return std::to_string(humanIndex) + "st"; + case 2: + return std::to_string(humanIndex) + "nd"; + case 3: + return std::to_string(humanIndex) + "rd"; + default: + return std::to_string(humanIndex) + "th"; + } +} + std::optional getFunctionNameAsString(const AstExpr& expr) { const AstExpr* curr = &expr; diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index b9728c30..1444f351 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -34,11 +34,8 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauTrackUniqueness) -LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAGVARIABLE(LuauIceLess) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) @@ -1327,13 +1324,10 @@ void TypeChecker2::visit(AstStatTypeAlias* stat) if (!module->astScopes.contains(stat)) return; - if (FFlag::LuauNameConstraintRestrictRecursiveTypes) + if (const Scope* scope = findInnermostScope(stat->location)) { - if (const Scope* scope = findInnermostScope(stat->location)) - { - if (scope->isInvalidTypeAliasName(stat->name.value)) - reportError(RecursiveRestraintViolation{}, stat->location); - } + if (scope->isInvalidTypeAliasName(stat->name.value)) + reportError(RecursiveRestraintViolation{}, stat->location); } visitGenerics(stat->generics, stat->genericPacks); @@ -3014,22 +3008,9 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc if (reasoning.subPath.empty() && reasoning.superPath.empty()) continue; - std::optional optSubLeaf; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes, subtyping->arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - optSubLeaf = traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks_DEPRECATED}, subtyping->arena); - else - optSubLeaf = traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes); - - std::optional optSuperLeaf; - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes, subtyping->arena); - else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - optSuperLeaf = - traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks_DEPRECATED}, subtyping->arena); - else - optSuperLeaf = traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes); + std::optional optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes, subtyping->arena); + + std::optional optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes, subtyping->arena); if (!optSubLeaf || !optSuperLeaf) { @@ -3447,15 +3428,11 @@ void TypeChecker2::testIsSubtypeForInStat(const TypeId iterFunc, const TypeId pr return; } - std::optional subLeaf = FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 - ? traverseForType(iterFunc, reasoning.subPath, builtinTypes, subtyping->arena) - : traverseForType_DEPRECATED(iterFunc, reasoning.subPath, builtinTypes); + std::optional subLeaf = traverseForType(iterFunc, reasoning.subPath, builtinTypes, subtyping->arena); if (!subLeaf) continue; - std::optional superLeaf = FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 - ? traverseForType(prospectiveFunc, reasoning.superPath, builtinTypes, subtyping->arena) - : traverseForType_DEPRECATED(prospectiveFunc, reasoning.superPath, builtinTypes); + std::optional superLeaf = traverseForType(prospectiveFunc, reasoning.superPath, builtinTypes, subtyping->arena); if (!superLeaf) continue; diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index c1dd4de1..fb99401c 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -32,8 +32,6 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) -LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) namespace Luau { @@ -1853,14 +1851,11 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti for (const auto& el : global.paramNames) ftv->argNames.push_back(FunctionArgument{el.first.value, el.second}); - if (FFlag::LuauParametrizedAttributeSyntax) + AstAttr* deprecatedAttr = global.getAttribute(AstAttr::Type::Deprecated); + ftv->isDeprecatedFunction = deprecatedAttr != nullptr; + if (deprecatedAttr) { - AstAttr* deprecatedAttr = global.getAttribute(AstAttr::Type::Deprecated); - ftv->isDeprecatedFunction = deprecatedAttr != nullptr; - if (deprecatedAttr) - { - ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); - } + ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); } Name fnName(global.name.value); @@ -3941,14 +3936,11 @@ std::pair TypeChecker::checkFunctionSignature( for (AstLocal* local : expr.args) ftv->argNames.push_back(FunctionArgument{local->name.value, local->location}); - if (FFlag::LuauParametrizedAttributeSyntax) + AstAttr* deprecatedAttr = expr.getAttribute(AstAttr::Type::Deprecated); + ftv->isDeprecatedFunction = deprecatedAttr != nullptr; + if (deprecatedAttr) { - AstAttr* deprecatedAttr = expr.getAttribute(AstAttr::Type::Deprecated); - ftv->isDeprecatedFunction = deprecatedAttr != nullptr; - if (deprecatedAttr) - { - ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); - } + ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); } return std::make_pair(funTy, funScope); @@ -5795,14 +5787,11 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno ftv->argNames.push_back(std::nullopt); } - if (FFlag::LuauParametrizedAttributeSyntax) + AstAttr* deprecatedAttr = func->getAttribute(AstAttr::Type::Deprecated); + ftv->isDeprecatedFunction = deprecatedAttr != nullptr; + if (deprecatedAttr) { - AstAttr* deprecatedAttr = func->getAttribute(AstAttr::Type::Deprecated); - ftv->isDeprecatedFunction = deprecatedAttr != nullptr; - if (deprecatedAttr) - { - ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); - } + ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); } return fnType; @@ -5950,10 +5939,8 @@ TypeId TypeChecker::instantiateTypeFun( } if (applyTypeFunction.encounteredForwardedType) { - if (FFlag::LuauNameConstraintRestrictRecursiveTypes) - reportError(TypeError{location, RecursiveRestraintViolation{}}); - else - reportError(TypeError{location, GenericError{"Recursive type being used with different parameters"}}); + reportError(TypeError{location, RecursiveRestraintViolation{}}); + return errorRecoveryType(scope); } diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index ce5725f3..d085887c 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -5,9 +5,6 @@ #include "Luau/TxnLog.h" #include "Luau/TypeArena.h" -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) - namespace Luau { @@ -455,41 +452,6 @@ std::pair, std::optional> flatten(TypePackId tp, return {flattened, tail}; } -std::pair, std::optional> flatten_DEPRECATED( - TypePackId tp, - const DenseHashMap& mappedGenericPacks -) -{ - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - tp = mappedGenericPacks.contains(tp) ? *mappedGenericPacks.find(tp) : tp; - - std::vector flattened; - std::optional tail = std::nullopt; - DenseHashSet seenGenericPacks{nullptr}; - - while (tp) - { - TypePackIterator it(tp); - - for (; it != end(tp); ++it) - flattened.push_back(*it); - - if (const auto tpTail = it.tail(); tpTail && !seenGenericPacks.contains(*tpTail) && mappedGenericPacks.contains(*tpTail)) - { - tp = *mappedGenericPacks.find(*tpTail); - seenGenericPacks.insert(*tpTail); - continue; - } - - tail = it.tail(); - break; - } - - return {flattened, tail}; -} - bool isVariadic(TypePackId tp) { return isVariadic(tp, *TxnLog::empty()); @@ -553,8 +515,6 @@ TypePackId sliceTypePack( const NotNull arena ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - if (sliceIndex == 0) return toBeSliced; else if (sliceIndex == head.size()) diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index 2ce779c6..4241e262 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -5,6 +5,7 @@ #include "Luau/Anyification.h" #include "Luau/Common.h" #include "Luau/DenseHash.h" +#include "Luau/ToString.h" #include "Luau/Type.h" #include "Luau/TypeArena.h" #include "Luau/TypeFwd.h" @@ -16,10 +17,9 @@ #include LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAGVARIABLE(LuauConsiderErrorSuppressionInTypes) +LUAU_FASTFLAG(LuauNewNonStrictBetterCheckedFunctionErrorMessage) // 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 @@ -69,7 +69,6 @@ bool Reduction::operator==(const Reduction& other) const bool GenericPackMapping::operator==(const GenericPackMapping& other) const { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); return mappedType == other.mappedType; } @@ -157,7 +156,6 @@ size_t PathHash::operator()(const Reduction& reduction) const size_t PathHash::operator()(const GenericPackMapping& mapping) const { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); return std::hash()(mapping.mappedType); } @@ -315,7 +313,6 @@ PathBuilder& PathBuilder::packSlice(size_t start_index) PathBuilder& PathBuilder::mappedGenericPack(TypePackId mappedType) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); components.emplace_back(GenericPackMapping{mappedType}); return *this; } @@ -327,28 +324,6 @@ namespace struct TraversalState { - // Clip the below two constructors with LuauSubtypingGenericPacksDoesntUseVariance2 - TraversalState(TypeId root, NotNull builtinTypes, const DenseHashMap* mappedGenericPacks, TypeArena* arena) - : current(root) - , builtinTypes(builtinTypes) - , mappedGenericPacks_DEPRECATED(mappedGenericPacks) - , arena(arena) - { - } - - TraversalState( - TypePackId root, - NotNull builtinTypes, - const DenseHashMap* mappedGenericPacks, - TypeArena* arena - ) - : current(root) - , builtinTypes(builtinTypes) - , mappedGenericPacks_DEPRECATED(mappedGenericPacks) - , arena(arena) - { - } - TraversalState(TypeId root, const NotNull builtinTypes, TypeArena* arena) : current(root) , builtinTypes(builtinTypes) @@ -365,10 +340,7 @@ struct TraversalState TypeOrPack current; NotNull builtinTypes; - // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance2 - const DenseHashMap* mappedGenericPacks_DEPRECATED = nullptr; - // TODO: make NotNull when LuauReturnMappedGenericPacksFromSubtyping3 is clipped - TypeArena* arena = nullptr; + NotNull arena; int steps = 0; bool encounteredErrorSuppression = false; @@ -550,43 +522,17 @@ struct TraversalState { auto currentPack = get(current); LUAU_ASSERT(currentPack); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) + if (get(*currentPack)) { - if (const auto tp = get(*currentPack)) - { - auto it = begin(*currentPack); + auto it = begin(*currentPack); - size_t i = 0; - for (; i < index.index && it != end(*currentPack); ++i) - ++it; + for (size_t i = 0; i < index.index && it != end(*currentPack); ++i) + ++it; - if (it != end(*currentPack)) - { - updateCurrent(*it); - return true; - } - else if (tp->tail && mappedGenericPacks_DEPRECATED && mappedGenericPacks_DEPRECATED->contains(*tp->tail)) - { - updateCurrent(*mappedGenericPacks_DEPRECATED->find(*tp->tail)); - LUAU_ASSERT(index.index >= i); - return traverse(TypePath::Index{index.index - i, TypePath::Index::Variant::Pack}); - } - } - } - else - { - if (get(*currentPack)) + if (it != end(*currentPack)) { - auto it = begin(*currentPack); - - for (size_t i = 0; i < index.index && it != end(*currentPack); ++i) - ++it; - - if (it != end(*currentPack)) - { - updateCurrent(*it); - return true; - } + updateCurrent(*it); + return true; } } } @@ -709,12 +655,7 @@ struct TraversalState if (auto tail = it.tail()) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 && - mappedGenericPacks_DEPRECATED && mappedGenericPacks_DEPRECATED->contains(*tail)) - updateCurrent(*mappedGenericPacks_DEPRECATED->find(*tail)); - - else - updateCurrent(*tail); + updateCurrent(*tail); return true; } } @@ -727,37 +668,14 @@ struct TraversalState bool traverse(const TypePath::PackSlice slice) { - // TODO: clip these checks once LuauReturnMappedGenericPacksFromSubtyping3 is clipped - // arena and mappedGenericPacks_DEPRECATED should be NonNull once that happens - LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping3); - if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - LUAU_ASSERT(arena); - - if (!arena) - return false; - } - if (checkInvariants()) return false; - // TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping3 is clipped - // arena and mappedGenericPacks should be NonNull once that happens - if (!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2) - { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping3) - LUAU_ASSERT(arena && mappedGenericPacks_DEPRECATED); - else if (!arena || !mappedGenericPacks_DEPRECATED) - return false; - } - const TypePackId* currentPack = get(current); if (!currentPack) return false; - auto [flatHead, flatTail] = FFlag::LuauSubtypingGenericPacksDoesntUseVariance2 - ? flatten(*currentPack) - : flatten_DEPRECATED(*currentPack, *mappedGenericPacks_DEPRECATED); + auto [flatHead, flatTail] = flatten(*currentPack); if (flatHead.size() <= slice.start_index) return false; @@ -784,8 +702,6 @@ struct TraversalState bool traverse(const TypePath::GenericPackMapping mapping) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - if (checkInvariants()) return false; @@ -885,10 +801,7 @@ std::string toString(const TypePath::Path& path, bool prefixDot) result << "~~>"; } else if constexpr (std::is_same_v) - { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); result << "~"; - } else { static_assert(always_false_v, "Unhandled Component variant"); @@ -950,29 +863,43 @@ std::string toStringHuman(const TypePath::Path& path) } else if constexpr (std::is_same_v) { - size_t humanIndex = c.index + 1; - - if (state == State::Initial && !last) - result << "in" << ' '; - else if (state == State::PendingIs) - result << ' ' << "has" << ' '; - else if (state == State::Property) - result << '`' << ' ' << "has" << ' '; + if (FFlag::LuauNewNonStrictBetterCheckedFunctionErrorMessage) + { + if (state == State::Initial && !last) + result << "in" << ' '; + else if (state == State::PendingIs) + result << ' ' << "has" << ' '; + else if (state == State::Property) + result << '`' << ' ' << "has" << ' '; - result << "the " << humanIndex; - switch (humanIndex) + result << "the " << toHumanReadableIndex(c.index); + } + else { - case 1: - result << "st"; - break; - case 2: - result << "nd"; - break; - case 3: - result << "rd"; - break; - default: - result << "th"; + size_t humanIndex = c.index + 1; + + if (state == State::Initial && !last) + result << "in" << ' '; + else if (state == State::PendingIs) + result << ' ' << "has" << ' '; + else if (state == State::Property) + result << '`' << ' ' << "has" << ' '; + + result << "the " << humanIndex; + switch (humanIndex) + { + case 1: + result << "st"; + break; + case 2: + result << "nd"; + break; + case 3: + result << "rd"; + break; + default: + result << "th"; + } } switch (c.variant) @@ -1106,10 +1033,7 @@ std::string toStringHuman(const TypePath::Path& path) state = State::Normal; } else if constexpr (std::is_same_v) - { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); result << "is a generic pack mapped to "; - } else { static_assert(always_false_v, "Unhandled Component variant"); @@ -1166,32 +1090,8 @@ static bool traverse(TraversalState& state, const Path& path) return true; } -std::optional traverse_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, nullptr, nullptr); - if (traverse(state, path)) - return state.current; - else - return std::nullopt; -} - -std::optional traverse_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, nullptr, nullptr); - if (traverse(state, path)) - return state.current; - else - return std::nullopt; -} - std::optional traverse(const TypePackId root, const Path& path, const NotNull builtinTypes, const NotNull arena) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) return state.current; @@ -1199,44 +1099,8 @@ std::optional traverse(const TypePackId root, const Path& path, cons return std::nullopt; } -std::optional traverse_DEPRECATED( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); - if (traverse(state, path)) - return state.current; - else - return std::nullopt; -} - -std::optional traverse_DEPRECATED( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); - if (traverse(state, path)) - return state.current; - else - return std::nullopt; -} - std::optional traverse(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) return state.current; @@ -1244,48 +1108,8 @@ std::optional traverse(const TypeId root, const Path& path, const No return std::nullopt; } -std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, nullptr, nullptr); - if (traverse(state, path)) - { - if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) - return builtinTypes->errorType; - auto ty = get(state.current); - return ty ? std::make_optional(*ty) : std::nullopt; - } - else - return std::nullopt; -} - -std::optional traverseForType_DEPRECATED( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); - if (traverse(state, path)) - { - if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) - return builtinTypes->errorType; - auto ty = get(state.current); - return ty ? std::make_optional(*ty) : std::nullopt; - } - else - return std::nullopt; -} - std::optional traverseForType(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) { @@ -1299,44 +1123,6 @@ std::optional traverseForType(const TypeId root, const Path& path, const return std::nullopt; } -std::optional traverseForType_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, nullptr, nullptr); - if (traverse(state, path)) - { - if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) - return builtinTypes->errorType; - auto ty = get(state.current); - return ty ? std::make_optional(*ty) : std::nullopt; - } - else - return std::nullopt; -} - -std::optional traverseForType_DEPRECATED( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); - if (traverse(state, path)) - { - if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) - return builtinTypes->errorType; - auto ty = get(state.current); - return ty ? std::make_optional(*ty) : std::nullopt; - } - else - return std::nullopt; -} - std::optional traverseForType( const TypePackId root, const Path& path, @@ -1344,8 +1130,6 @@ std::optional traverseForType( const NotNull arena ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) { @@ -1358,44 +1142,6 @@ std::optional traverseForType( return std::nullopt; } -std::optional traverseForPack_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, nullptr, nullptr); - if (traverse(state, path)) - { - if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) - return builtinTypes->errorTypePack; - auto ty = get(state.current); - return ty ? std::make_optional(*ty) : std::nullopt; - } - else - return std::nullopt; -} - -std::optional traverseForPack_DEPRECATED( - TypeId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); - if (traverse(state, path)) - { - if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) - return builtinTypes->errorTypePack; - auto ty = get(state.current); - return ty ? std::make_optional(*ty) : std::nullopt; - } - else - return std::nullopt; -} - std::optional traverseForPack( const TypeId root, const Path& path, @@ -1403,8 +1149,6 @@ std::optional traverseForPack( const NotNull arena ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) { @@ -1417,44 +1161,6 @@ std::optional traverseForPack( return std::nullopt; } -std::optional traverseForPack_DEPRECATED(TypePackId root, const Path& path, NotNull builtinTypes) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, nullptr, nullptr); - if (traverse(state, path)) - { - if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) - return builtinTypes->errorTypePack; - auto ty = get(state.current); - return ty ? std::make_optional(*ty) : std::nullopt; - } - else - return std::nullopt; -} - -std::optional traverseForPack_DEPRECATED( - TypePackId root, - const Path& path, - NotNull builtinTypes, - NotNull> mappedGenericPacks, - NotNull arena -) -{ - LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - - TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); - if (traverse(state, path)) - { - if (FFlag::LuauConsiderErrorSuppressionInTypes && state.encounteredErrorSuppression) - return builtinTypes->errorTypePack; - auto ty = get(state.current); - return ty ? std::make_optional(*ty) : std::nullopt; - } - else - return std::nullopt; -} - std::optional traverseForPack( const TypePackId root, const Path& path, @@ -1462,8 +1168,6 @@ std::optional traverseForPack( const NotNull arena ) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - TraversalState state(follow(root), builtinTypes, arena); if (traverse(state, path)) { @@ -1505,8 +1209,6 @@ std::optional traverseForIndex(const Path& path) TypePack flattenPackWithPath(TypePackId root, const Path& path) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - std::vector flattened; std::optional curr = root; @@ -1543,8 +1245,6 @@ TypePack flattenPackWithPath(TypePackId root, const Path& path) TypePack traverseForFlattenedPack(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) { - LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance2); - // Iterate over path's components, and figure out when it turns into Tails and GenericPackMappings // We want to split out the part of the path that contains the generic pack mappings we're interested in, so that we can flatten it // path[splitIndex:] will contain only Tails and GenericPackMappings diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 7f0b85d3..64023c2c 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -921,7 +921,35 @@ TypeId addUnion(NotNull arena, NotNull builtinTypes, st return ub.build(); } +ContainsAnyGeneric::ContainsAnyGeneric() + : TypeOnceVisitor("ContainsAnyGeneric", /* skipBoundTypes */ true) +{ +} +bool ContainsAnyGeneric::visit(TypeId ty) +{ + found = found || is(ty); + return !found; +} + +bool ContainsAnyGeneric::visit(TypePackId ty) +{ + found = found || is(follow(ty)); + return !found; +} + +bool ContainsAnyGeneric::hasAnyGeneric(TypeId ty) +{ + ContainsAnyGeneric cg; + cg.traverse(ty); + return cg.found; +} +bool ContainsAnyGeneric::hasAnyGeneric(TypePackId tp) +{ + ContainsAnyGeneric cg; + cg.traverse(tp); + return cg.found; +} } // namespace Luau diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 18f18354..41d85940 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAGVARIABLE(LuauUnifierRecursionOnRestart) +LUAU_FASTFLAG(LuauNormalizerStepwiseFuel) namespace Luau { @@ -1189,13 +1190,26 @@ TypePackId Unifier::tryApplyOverloadedFunction(TypeId function, const Normalized { innerState->log.clear(); innerState->tryUnify_(*result, ftv->retTypes); - if (innerState->errors.empty()) - log.concat(std::move(innerState->log)); - // Annoyingly, since we don't support intersection of generic type packs, - // the intersection may fail. We rather arbitrarily use the first matching overload - // in that case. - else if (std::optional intersect = normalizer->intersectionOfTypePacks(*result, ftv->retTypes)) - result = intersect; + if (FFlag::LuauNormalizerStepwiseFuel) + { + if (innerState->errors.empty()) + log.concat(std::move(innerState->log)); + // Annoyingly, since we don't support intersection of generic type packs, + // the intersection may fail. We rather arbitrarily use the first matching overload + // in that case. + else if (std::optional intersect = normalizer->intersectionOfTypePacks(*result, ftv->retTypes)) + result = intersect; + } + else + { + if (innerState->errors.empty()) + log.concat(std::move(innerState->log)); + // Annoyingly, since we don't support intersection of generic type packs, + // the intersection may fail. We rather arbitrarily use the first matching overload + // in that case. + else if (std::optional intersect = normalizer->intersectionOfTypePacks_INTERNAL(*result, ftv->retTypes)) + result = intersect; + } } else result = ftv->retTypes; diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 0aa9790e..a6b5fc1b 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -25,7 +25,6 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauUnifierRecursionLimit, 100) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauLimitUnification) -LUAU_FASTFLAGVARIABLE(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAGVARIABLE(LuauFixNilRightPad) namespace Luau @@ -400,15 +399,12 @@ UnifyResult Unifier2::unify_(const UnionType* subUnion, TypeId superTy) UnifyResult Unifier2::unify_(TypeId subTy, const UnionType* superUnion) { - if (FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions) + subTy = follow(subTy); + // T <: T | U1 | U2 | ... | Un is trivially true, so we don't gain any information by unifying + for (const auto superOption : superUnion) { - subTy = follow(subTy); - // T <: T | U1 | U2 | ... | Un is trivially true, so we don't gain any information by unifying - for (const auto superOption : superUnion) - { - if (subTy == superOption) - return UnifyResult::Ok; - } + if (subTy == superOption) + return UnifyResult::Ok; } UnifyResult result = UnifyResult::Ok; @@ -425,15 +421,12 @@ UnifyResult Unifier2::unify_(TypeId subTy, const UnionType* superUnion) UnifyResult Unifier2::unify_(const IntersectionType* subIntersection, TypeId superTy) { - if (FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions) + superTy = follow(superTy); + // T & I1 & I2 & ... & In <: T is trivially true, so we don't gain any information by unifying + for (const auto subOption : subIntersection) { - superTy = follow(superTy); - // T & I1 & I2 & ... & In <: T is trivially true, so we don't gain any information by unifying - for (const auto subOption : subIntersection) - { - if (superTy == subOption) - return UnifyResult::Ok; - } + if (superTy == subOption) + return UnifyResult::Ok; } UnifyResult result = UnifyResult::Ok; diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 836708e5..0a31f732 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -136,7 +136,6 @@ class Parser const TempVector& attributes, const AstArray& args ); - std::optional validateAttribute_DEPRECATED(const char* attributeName, const TempVector& attributes); // attribute ::= '@' NAME void parseAttribute(TempVector& attribute); diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index aecd7171..fc5c4a33 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -4,8 +4,6 @@ #include "Luau/Common.h" #include "Luau/StringUtils.h" -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) - namespace Luau { @@ -22,20 +20,7 @@ static AstAttr* findAttributeInArray(const AstArray attributes, AstAtt static bool hasAttributeInArray(const AstArray attributes, AstAttr::Type attributeType) { - if (FFlag::LuauParametrizedAttributeSyntax) - { - return findAttributeInArray(attributes, attributeType) != nullptr; - } - else - { - for (const auto attribute : attributes) - { - if (attribute->type == attributeType) - return true; - } - - return false; - } + return findAttributeInArray(attributes, attributeType) != nullptr; } static void visitTypeList(AstVisitor* visitor, const AstTypeList& list) diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 12e35343..dc31a292 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -8,8 +8,6 @@ #include -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) - namespace Luau { @@ -991,36 +989,28 @@ Lexeme Lexer::readNext() } case '@': { - if (FFlag::LuauParametrizedAttributeSyntax) + if (peekch(1) == '[') { - if (peekch(1) == '[') - { - consume(); - consume(); + consume(); + consume(); + + return Lexeme(Location(start, 2), Lexeme::AttributeOpen); + } + else + { + // consume @ first + consume(); - return Lexeme(Location(start, 2), Lexeme::AttributeOpen); + if (isAlpha(peekch()) || peekch() == '_') + { + std::pair attribute = readName(); + return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value); } else { - // consume @ first - consume(); - - if (isAlpha(peekch()) || peekch() == '_') - { - std::pair attribute = readName(); - return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value); - } - else - { - return Lexeme(Location(start, position()), Lexeme::Attribute, ""); - } + return Lexeme(Location(start, position()), Lexeme::Attribute, ""); } } - else - { - std::pair attribute = readName(); - return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value); - } } default: if (isDigit(peekch())) diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 22a77fa4..cb92bc2a 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -19,7 +19,6 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) // See docs/SyntaxChanges.md for an explanation. LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) -LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax) LUAU_FASTFLAGVARIABLE(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAGVARIABLE(LuauAutocompleteAttributes) @@ -868,60 +867,10 @@ std::optional Parser::validateAttribute( return type; } -std::optional Parser::validateAttribute_DEPRECATED(const char* attributeName, const TempVector& attributes) -{ - // check if the attribute name is valid - std::optional type; - - for (int i = 0; kAttributeEntries_DEPRECATED[i].name; ++i) - { - if (strcmp(attributeName, kAttributeEntries_DEPRECATED[i].name) == 0) - { - type = kAttributeEntries_DEPRECATED[i].type; - break; - } - } - - if (!type) - { - if (strlen(attributeName) == 1) - report(lexer.current().location, "Attribute name is missing"); - else - report(lexer.current().location, "Invalid attribute '%s'", attributeName); - } - else - { - // check that attribute is not duplicated - for (const AstAttr* attr : attributes) - { - if (attr->type == *type) - report(lexer.current().location, "Cannot duplicate attribute '%s'", attributeName); - } - } - - return type; -} - // attribute ::= '@' NAME void Parser::parseAttribute(TempVector& attributes) { AstArray empty; - if (!FFlag::LuauParametrizedAttributeSyntax) - { - LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute); - - Location loc = lexer.current().location; - - const char* name = lexer.current().name; - std::optional type = validateAttribute_DEPRECATED(name, attributes); - - nextLexeme(); - - if (type) - attributes.push_back(allocator.alloc(loc, *type, empty)); - - return; - } LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute || lexer.current().type == Lexeme::Type::AttributeOpen); @@ -1034,7 +983,7 @@ AstArray Parser::parseAttributes() TempVector attributes(scratchAttr); - while (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen)) + while (lexer.current().type == Lexeme::Attribute || lexer.current().type == Lexeme::AttributeOpen) parseAttribute(attributes); return copy(attributes); @@ -1442,8 +1391,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray attributes{nullptr, 0}; - if (lexer.current().type == Lexeme::Attribute || - (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen)) + if (lexer.current().type == Lexeme::Attribute || lexer.current().type == Lexeme::AttributeOpen) { attributes = Parser::parseAttributes(); @@ -2566,7 +2514,7 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) AstArray attributes{nullptr, 0}; - if (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen)) + if (lexer.current().type == Lexeme::Attribute || lexer.current().type == Lexeme::AttributeOpen) { if (!inDeclarationContext) { @@ -3213,7 +3161,7 @@ AstExpr* Parser::parseSimpleExpr() AstArray attributes{nullptr, 0}; - if (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen)) + if (lexer.current().type == Lexeme::Attribute || lexer.current().type == Lexeme::AttributeOpen) { attributes = parseAttributes(); diff --git a/CLI/include/Luau/AnalyzeRequirer.h b/CLI/include/Luau/AnalyzeRequirer.h index 0e1d5ee8..f9d6ed53 100644 --- a/CLI/include/Luau/AnalyzeRequirer.h +++ b/CLI/include/Luau/AnalyzeRequirer.h @@ -7,6 +7,7 @@ struct FileNavigationContext : Luau::Require::NavigationContext { using NavigateResult = Luau::Require::NavigationContext::NavigateResult; + using ConfigStatus = Luau::Require::NavigationContext::ConfigStatus; FileNavigationContext(std::string requirerPath); @@ -19,7 +20,7 @@ struct FileNavigationContext : Luau::Require::NavigationContext NavigateResult toParent() override; NavigateResult toChild(const std::string& component) override; - bool isConfigPresent() const override; + ConfigStatus getConfigStatus() const override; virtual ConfigBehavior getConfigBehavior() const override; virtual std::optional getAlias(const std::string& alias) const override; virtual std::optional getConfig() const override; diff --git a/CLI/include/Luau/VfsNavigator.h b/CLI/include/Luau/VfsNavigator.h index b781b13d..91988d19 100644 --- a/CLI/include/Luau/VfsNavigator.h +++ b/CLI/include/Luau/VfsNavigator.h @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include #include enum class NavigationStatus @@ -21,9 +22,21 @@ class VfsNavigator std::string getFilePath() const; std::string getAbsoluteFilePath() const; - std::string getLuaurcPath() const; + + enum class ConfigStatus + { + Absent, + Ambiguous, + PresentJson, + PresentLuau + }; + + ConfigStatus getConfigStatus() const; + std::optional getConfig() const; private: + std::string getConfigPath(const std::string& filename) const; + NavigationStatus updateRealPaths(); std::string realPath; diff --git a/CLI/src/Analyze.cpp b/CLI/src/Analyze.cpp index ae4e9a20..0a344248 100644 --- a/CLI/src/Analyze.cpp +++ b/CLI/src/Analyze.cpp @@ -2,8 +2,10 @@ #include "Luau/BuiltinDefinitions.h" #include "Luau/Config.h" #include "Luau/Frontend.h" +#include "Luau/LuauConfig.h" #include "Luau/ModuleResolver.h" #include "Luau/PrettyPrinter.h" +#include "Luau/StringUtils.h" #include "Luau/TypeAttach.h" #include "Luau/TypeInfer.h" @@ -174,6 +176,7 @@ struct CliFileResolver : Luau::FileResolver { std::string path{expr->value.data, expr->value.size}; + // TODO (CLI-174536): support interrupt callbacks based on TypeCheckLimits FileNavigationContext navigationContext{context->name}; Luau::Require::ErrorHandler nullErrorHandler{}; @@ -229,20 +232,48 @@ struct CliConfigResolver : Luau::ConfigResolver std::optional parent = getParentPath(path); Luau::Config result = parent ? readConfigRec(*parent) : defaultConfig; - std::string configPath = joinPaths(path, Luau::kConfigName); + std::optional configPath = joinPaths(path, Luau::kConfigName); + if (!isFile(*configPath)) + configPath = std::nullopt; - if (std::optional contents = readFile(configPath)) + std::optional luauConfigPath = joinPaths(path, Luau::kLuauConfigName); + if (!isFile(*luauConfigPath)) + luauConfigPath = std::nullopt; + + if (configPath && luauConfigPath) + { + std::string ambiguousError = Luau::format("Both %s and %s files exist", Luau::kConfigName, Luau::kLuauConfigName); + configErrors.emplace_back(*configPath, std::move(ambiguousError)); + } + else if (configPath) { - Luau::ConfigOptions::AliasOptions aliasOpts; - aliasOpts.configLocation = configPath; - aliasOpts.overwriteAliases = true; + if (std::optional contents = readFile(*configPath)) + { + Luau::ConfigOptions::AliasOptions aliasOpts; + aliasOpts.configLocation = *configPath; + aliasOpts.overwriteAliases = true; - Luau::ConfigOptions opts; - opts.aliasOptions = std::move(aliasOpts); + Luau::ConfigOptions opts; + opts.aliasOptions = std::move(aliasOpts); - std::optional error = Luau::parseConfig(*contents, result, opts); - if (error) - configErrors.push_back({configPath, *error}); + std::optional error = Luau::parseConfig(*contents, result, opts); + if (error) + configErrors.emplace_back(*configPath, *error); + } + } + else if (luauConfigPath) + { + if (std::optional contents = readFile(*luauConfigPath)) + { + Luau::ConfigOptions::AliasOptions aliasOpts; + aliasOpts.configLocation = *configPath; + aliasOpts.overwriteAliases = true; + + // TODO (CLI-174536): support interrupt callbacks based on TypeCheckLimits + std::optional error = Luau::extractLuauConfig(*contents, result, aliasOpts, Luau::InterruptCallbacks{}); + if (error) + configErrors.emplace_back(*luauConfigPath, *error); + } } return configCache[path] = result; diff --git a/CLI/src/AnalyzeRequirer.cpp b/CLI/src/AnalyzeRequirer.cpp index 318a352f..c6543c4a 100644 --- a/CLI/src/AnalyzeRequirer.cpp +++ b/CLI/src/AnalyzeRequirer.cpp @@ -17,6 +17,18 @@ static Luau::Require::NavigationContext::NavigateResult convert(NavigationStatus return Luau::Require::NavigationContext::NavigateResult::NotFound; } +static Luau::Require::NavigationContext::ConfigStatus convert(VfsNavigator::ConfigStatus status) +{ + if (status == VfsNavigator::ConfigStatus::Ambiguous) + return Luau::Require::NavigationContext::ConfigStatus::Ambiguous; + else if (status == VfsNavigator::ConfigStatus::PresentJson) + return Luau::Require::NavigationContext::ConfigStatus::PresentJson; + else if (status == VfsNavigator::ConfigStatus::PresentLuau) + return Luau::Require::NavigationContext::ConfigStatus::PresentLuau; + else + return Luau::Require::NavigationContext::ConfigStatus::Absent; +} + FileNavigationContext::FileNavigationContext(std::string requirerPath) : requirerPath(std::move(requirerPath)) { @@ -63,9 +75,9 @@ std::optional FileNavigationContext::getIdentifier() const return vfs.getAbsoluteFilePath(); } -bool FileNavigationContext::isConfigPresent() const +Luau::Require::NavigationContext::ConfigStatus FileNavigationContext::getConfigStatus() const { - return isFile(vfs.getLuaurcPath()); + return convert(vfs.getConfigStatus()); } Luau::Require::NavigationContext::ConfigBehavior FileNavigationContext::getConfigBehavior() const @@ -80,5 +92,5 @@ std::optional FileNavigationContext::getAlias(const std::string& al std::optional FileNavigationContext::getConfig() const { - return readFile(vfs.getLuaurcPath()); + return vfs.getConfig(); } diff --git a/CLI/src/ReplRequirer.cpp b/CLI/src/ReplRequirer.cpp index 32da1c84..4c46f229 100644 --- a/CLI/src/ReplRequirer.cpp +++ b/CLI/src/ReplRequirer.cpp @@ -42,6 +42,18 @@ static luarequire_NavigateResult convert(NavigationStatus status) return NAVIGATE_NOT_FOUND; } +static luarequire_ConfigStatus convert(VfsNavigator::ConfigStatus status) +{ + if (status == VfsNavigator::ConfigStatus::Ambiguous) + return CONFIG_AMBIGUOUS; + else if (status == VfsNavigator::ConfigStatus::PresentJson) + return CONFIG_PRESENT_JSON; + else if (status == VfsNavigator::ConfigStatus::PresentLuau) + return CONFIG_PRESENT_LUAU; + else + return CONFIG_ABSENT; +} + static bool is_require_allowed(lua_State* L, void* ctx, const char* requirer_chunkname) { std::string_view chunkname = requirer_chunkname; @@ -107,16 +119,16 @@ static luarequire_WriteResult get_cache_key(lua_State* L, void* ctx, char* buffe return write(req->vfs.getAbsoluteFilePath(), buffer, buffer_size, size_out); } -static bool is_config_present(lua_State* L, void* ctx) +static luarequire_ConfigStatus get_config_status(lua_State* L, void* ctx) { ReplRequirer* req = static_cast(ctx); - return isFile(req->vfs.getLuaurcPath()); + return convert(req->vfs.getConfigStatus()); } static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out) { ReplRequirer* req = static_cast(ctx); - return write(readFile(req->vfs.getLuaurcPath()), buffer, buffer_size, size_out); + return write(req->vfs.getConfig(), buffer, buffer_size, size_out); } static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* loadname) @@ -204,7 +216,7 @@ void requireConfigInit(luarequire_Configuration* config) config->to_parent = to_parent; config->to_child = to_child; config->is_module_present = is_module_present; - config->is_config_present = is_config_present; + config->get_config_status = get_config_status; config->get_chunkname = get_chunkname; config->get_loadname = get_loadname; config->get_cache_key = get_cache_key; diff --git a/CLI/src/VfsNavigator.cpp b/CLI/src/VfsNavigator.cpp index 55daf8e7..1351c4ac 100644 --- a/CLI/src/VfsNavigator.cpp +++ b/CLI/src/VfsNavigator.cpp @@ -2,7 +2,9 @@ #include "Luau/VfsNavigator.h" #include "Luau/Common.h" +#include "Luau/Config.h" #include "Luau/FileUtils.h" +#include "Luau/LuauConfig.h" #include #include @@ -195,6 +197,9 @@ NavigationStatus VfsNavigator::toParent() NavigationStatus VfsNavigator::toChild(const std::string& name) { + if (name == ".config") + return NavigationStatus::NotFound; + modulePath = normalizePath(modulePath + "/" + name); absoluteModulePath = normalizePath(absoluteModulePath + "/" + name); @@ -211,7 +216,7 @@ std::string VfsNavigator::getAbsoluteFilePath() const return absoluteRealPath; } -std::string VfsNavigator::getLuaurcPath() const +std::string VfsNavigator::getConfigPath(const std::string& filename) const { std::string_view directory = realPath; @@ -220,7 +225,7 @@ std::string VfsNavigator::getLuaurcPath() const if (hasSuffix(directory, suffix)) { directory.remove_suffix(suffix.size()); - return std::string(directory) + "/.luaurc"; + return std::string(directory) + '/' + filename; } } for (std::string_view suffix : kSuffixes) @@ -228,9 +233,37 @@ std::string VfsNavigator::getLuaurcPath() const if (hasSuffix(directory, suffix)) { directory.remove_suffix(suffix.size()); - return std::string(directory) + "/.luaurc"; + return std::string(directory) + '/' + filename; } } - return std::string(directory) + "/.luaurc"; + return std::string(directory) + '/' + filename; +} + +VfsNavigator::ConfigStatus VfsNavigator::getConfigStatus() const +{ + bool luaurcExists = isFile(getConfigPath(Luau::kConfigName)); + bool luauConfigExists = isFile(getConfigPath(Luau::kLuauConfigName)); + + if (luaurcExists && luauConfigExists) + return ConfigStatus::Ambiguous; + else if (luauConfigExists) + return ConfigStatus::PresentLuau; + else if (luaurcExists) + return ConfigStatus::PresentJson; + else + return ConfigStatus::Absent; +} + +std::optional VfsNavigator::getConfig() const +{ + ConfigStatus status = getConfigStatus(); + LUAU_ASSERT(status == ConfigStatus::PresentJson || status == ConfigStatus::PresentLuau); + + if (status == ConfigStatus::PresentJson) + return readFile(getConfigPath(Luau::kConfigName)); + else if (status == ConfigStatus::PresentLuau) + return readFile(getConfigPath(Luau::kLuauConfigName)); + + LUAU_UNREACHABLE(); } diff --git a/CLI/src/Web.cpp b/CLI/src/Web.cpp index 81156e88..f3323359 100644 --- a/CLI/src/Web.cpp +++ b/CLI/src/Web.cpp @@ -4,12 +4,72 @@ #include "luacode.h" #include "Luau/Common.h" +#include "Luau/Frontend.h" +#include "Luau/BuiltinDefinitions.h" #include #include #include +// Simple FileResolver for type checking on luau.org/demo +struct DemoFileResolver + : Luau::FileResolver +{ + DemoFileResolver() + : Luau::FileResolver(nullptr) + { + } + + std::optional readSource(const Luau::ModuleName& name) + { + auto it = source.find(name); + if (it == source.end()) + return std::nullopt; + + Luau::SourceCode::Type sourceType = Luau::SourceCode::Module; + auto it2 = sourceTypes.find(name); + if (it2 != sourceTypes.end()) + sourceType = it2->second; + + return Luau::SourceCode{it->second, sourceType}; + } + + std::optional resolveModuleInfo( + const Luau::ModuleName& currentModuleName, const Luau::AstExpr& pathExpr) + { + return std::nullopt; + } + + const Luau::ModulePtr getModule(const Luau::ModuleName& moduleName) const + { + return nullptr; + } + + bool moduleExists(const Luau::ModuleName& moduleName) const + { + return false; + } + + std::optional resolveModule(const Luau::ModuleInfo* context, Luau::AstExpr* expr) + { + return std::nullopt; + } + + std::string getHumanReadableModuleName(const Luau::ModuleName& name) const + { + return name; + } + + std::optional getEnvironmentForModule(const Luau::ModuleName& name) const + { + return std::nullopt; + } + + std::unordered_map source; + std::unordered_map sourceTypes; +}; + static void setupState(lua_State* L) { luaL_openlibs(L); @@ -88,6 +148,43 @@ static std::string runCode(lua_State* L, const std::string& source) } } +extern "C" const char* checkScript(const char* source) +{ + static std::string result; + result.clear(); + + try + { + DemoFileResolver fileResolver; + Luau::NullConfigResolver configResolver; + Luau::FrontendOptions options; + + Luau::Frontend frontend(&fileResolver, &configResolver, options); + // Add Luau builtins + Luau::unfreeze(frontend.globals.globalTypes); + Luau::registerBuiltinGlobals(frontend, frontend.globals); + Luau::freeze(frontend.globals.globalTypes); + + fileResolver.source["main"] = source; + + Luau::CheckResult checkResult = frontend.check("main"); + for (const Luau::TypeError& err : checkResult.errors) + { + if (!result.empty()) + result += "\n"; + result += std::to_string(err.location.begin.line + 1); + result += ": "; + result += Luau::toString(err); + } + } + catch (const std::exception& e) + { + result = e.what(); + } + + return result.empty() ? nullptr : result.c_str(); +} + extern "C" const char* executeScript(const char* source) { // setup flags diff --git a/CMakeLists.txt b/CMakeLists.txt index 861f7086..4fb21c3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,6 @@ add_library(Luau.EqSat STATIC) add_library(Luau.CodeGen STATIC) add_library(Luau.VM STATIC) add_library(Luau.Require STATIC) -add_library(Luau.RequireNavigator STATIC) add_library(isocline STATIC) if(LUAU_BUILD_CLI) @@ -105,13 +104,8 @@ target_include_directories(Luau.VM PUBLIC VM/include) target_link_libraries(Luau.VM PUBLIC Luau.Common) target_compile_features(Luau.Require PUBLIC cxx_std_17) -target_include_directories(Luau.Require PUBLIC Require/Runtime/include) -target_link_libraries(Luau.Require PUBLIC Luau.VM) -target_link_libraries(Luau.Require PRIVATE Luau.RequireNavigator) - -target_compile_features(Luau.RequireNavigator PUBLIC cxx_std_17) -target_include_directories(Luau.RequireNavigator PUBLIC Require/Navigator/include) -target_link_libraries(Luau.RequireNavigator PUBLIC Luau.Config) +target_include_directories(Luau.Require PUBLIC Require/include) +target_link_libraries(Luau.Require PUBLIC Luau.Config Luau.VM) target_include_directories(isocline PUBLIC extern/isocline/include) @@ -236,7 +230,7 @@ if(LUAU_BUILD_CLI) target_link_libraries(Luau.Compile.CLI PRIVATE osthreads) target_link_libraries(Luau.Bytecode.CLI PRIVATE osthreads) - target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis Luau.CLI.lib Luau.RequireNavigator) + target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis Luau.CLI.lib Luau.Require) target_link_libraries(Luau.Ast.CLI PRIVATE Luau.Ast Luau.Analysis Luau.CLI.lib) @@ -276,10 +270,10 @@ endif() if(LUAU_BUILD_WEB) target_compile_options(Luau.Web PRIVATE ${LUAU_OPTIONS}) - target_link_libraries(Luau.Web PRIVATE Luau.Compiler Luau.VM) + target_link_libraries(Luau.Web PRIVATE Luau.Compiler Luau.VM Luau.Analysis) # declare exported functions to emscripten - target_link_options(Luau.Web PRIVATE -sEXPORTED_FUNCTIONS=['_executeScript'] -sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']) + target_link_options(Luau.Web PRIVATE -sEXPORTED_FUNCTIONS=["_executeScript","_checkScript"] -sEXPORTED_RUNTIME_METHODS=["ccall","cwrap"]) # add -fexceptions for emscripten to allow exceptions to be caught in C++ target_link_options(Luau.Web PRIVATE -fexceptions) diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index f4c1eb16..307bbb5b 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -14,7 +14,6 @@ inline bool isAnalysisFlagExperimental(const char* flag) static const char* const kList[] = { "LuauInstantiateInSubtyping", // requires some fixes to lua-apps code "LuauFixIndexerSubtypingOrdering", // requires some small fixes to lua-apps code since this fixes a false negative - "StudioReportLuauAny2", // takes telemetry data for usage of any types "LuauTableCloneClonesType3", // requires fixes in lua-apps code, terrifyingly "LuauSolverV2", "UseNewLuauTypeSolverDefaultEnabled", // This can change the default solver used in cli applications, so it also needs to be disabled. Will diff --git a/Common/include/Luau/Variant.h b/Common/include/Luau/Variant.h index 14eb8c4e..5d6542dd 100644 --- a/Common/include/Luau/Variant.h +++ b/Common/include/Luau/Variant.h @@ -290,6 +290,14 @@ auto visit(Visitor&& vis, Variant& var) } } +template +struct overloaded : Ts... +{ + using Ts::operator()...; +}; +template +overloaded(Ts...) -> overloaded; + template inline constexpr bool always_false_v = false; diff --git a/Makefile b/Makefile index 1b6a4517..4c30a42e 100644 --- a/Makefile +++ b/Makefile @@ -38,14 +38,10 @@ VM_SOURCES=$(wildcard VM/src/*.cpp) VM_OBJECTS=$(VM_SOURCES:%=$(BUILD)/%.o) VM_TARGET=$(BUILD)/libluauvm.a -REQUIRE_SOURCES=$(wildcard Require/Runtime/src/*.cpp) +REQUIRE_SOURCES=$(wildcard Require/src/*.cpp) REQUIRE_OBJECTS=$(REQUIRE_SOURCES:%=$(BUILD)/%.o) REQUIRE_TARGET=$(BUILD)/libluaurequire.a -REQUIRENAVIGATOR_SOURCES=$(wildcard Require/Navigator/src/*.cpp) -REQUIRENAVIGATOR_OBJECTS=$(REQUIRENAVIGATOR_SOURCES:%=$(BUILD)/%.o) -REQUIRENAVIGATOR_TARGET=$(BUILD)/libluaurequirenavigator.a - ISOCLINE_SOURCES=extern/isocline/src/isocline.c ISOCLINE_OBJECTS=$(ISOCLINE_SOURCES:%=$(BUILD)/%.o) ISOCLINE_TARGET=$(BUILD)/libisocline.a @@ -81,7 +77,7 @@ ifneq ($(opt),) TESTS_ARGS+=-O$(opt) endif -OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(CONFIG_OBJECTS) $(ANALYSIS_OBJECTS) $(EQSAT_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(REQUIRE_OBJECTS) $(REQUIRENAVIGATOR_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(REPL_CLI_OBJECTS) $(ANALYZE_CLI_OBJECTS) $(COMPILE_CLI_OBJECTS) $(BYTECODE_CLI_OBJECTS) $(FUZZ_OBJECTS) +OBJECTS=$(AST_OBJECTS) $(COMPILER_OBJECTS) $(CONFIG_OBJECTS) $(ANALYSIS_OBJECTS) $(EQSAT_OBJECTS) $(CODEGEN_OBJECTS) $(VM_OBJECTS) $(REQUIRE_OBJECTS) $(ISOCLINE_OBJECTS) $(TESTS_OBJECTS) $(REPL_CLI_OBJECTS) $(ANALYZE_CLI_OBJECTS) $(COMPILE_CLI_OBJECTS) $(BYTECODE_CLI_OBJECTS) $(FUZZ_OBJECTS) EXECUTABLE_ALIASES = luau luau-analyze luau-compile luau-bytecode luau-tests # common flags @@ -157,12 +153,11 @@ $(ANALYSIS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnaly $(EQSAT_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IEqSat/include $(CODEGEN_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -ICodeGen/include -IVM/include -IVM/src # Code generation needs VM internals $(VM_OBJECTS): CXXFLAGS+=-std=c++11 -ICommon/include -IVM/include -$(REQUIRE_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IVM/include -IAst/include -IConfig/include -IRequire/Navigator/include -IRequire/Runtime/include -$(REQUIRENAVIGATOR_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IConfig/include -IRequire/Navigator/include +$(REQUIRE_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IVM/include -IAst/include -IConfig/include -IRequire/include $(ISOCLINE_OBJECTS): CXXFLAGS+=-Wno-unused-function -Iextern/isocline/include -$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IConfig/include -IAnalysis/include -IEqSat/include -ICodeGen/include -IVM/include -IRequire/Runtime/include -ICLI/include -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY -$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -IRequire/Runtime/include -Iextern -Iextern/isocline/include -ICLI/include -$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IEqSat/include -IConfig/include -IRequire/Navigator/include -Iextern -ICLI/include +$(TESTS_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IConfig/include -IAnalysis/include -IEqSat/include -ICodeGen/include -IVM/include -IRequire/include -ICLI/include -Iextern -DDOCTEST_CONFIG_DOUBLE_STRINGIFY +$(REPL_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -IRequire/include -Iextern -Iextern/isocline/include -ICLI/include +$(ANALYZE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -IAnalysis/include -IEqSat/include -IConfig/include -IRequire/include -Iextern -ICLI/include $(COMPILE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -ICLI/include $(BYTECODE_CLI_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IVM/include -ICodeGen/include -ICLI/include $(FUZZ_OBJECTS): CXXFLAGS+=-std=c++17 -ICommon/include -IAst/include -ICompiler/include -IAnalysis/include -IEqSat/include -IVM/include -ICodeGen/include -IConfig/include @@ -238,9 +233,9 @@ luau-tests: $(TESTS_TARGET) ln -fs $^ $@ # executable targets -$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(REQUIRE_TARGET) $(REQUIRENAVIGATOR_TARGET) $(CONFIG_TARGET) $(ISOCLINE_TARGET) -$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(REQUIRE_TARGET) $(REQUIRENAVIGATOR_TARGET) $(CONFIG_TARGET) $(ISOCLINE_TARGET) -$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(AST_TARGET) $(COMPILER_TARGET) $(VM_TARGET) $(REQUIRENAVIGATOR_TARGET) $(CONFIG_TARGET) +$(TESTS_TARGET): $(TESTS_OBJECTS) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(REQUIRE_TARGET) $(CONFIG_TARGET) $(ISOCLINE_TARGET) +$(REPL_CLI_TARGET): $(REPL_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(REQUIRE_TARGET) $(CONFIG_TARGET) $(ISOCLINE_TARGET) +$(ANALYZE_CLI_TARGET): $(ANALYZE_CLI_OBJECTS) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(AST_TARGET) $(COMPILER_TARGET) $(VM_TARGET) $(REQUIRE_TARGET) $(CONFIG_TARGET) $(COMPILE_CLI_TARGET): $(COMPILE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(BYTECODE_CLI_TARGET): $(BYTECODE_CLI_OBJECTS) $(COMPILER_TARGET) $(AST_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) @@ -263,10 +258,9 @@ $(EQSAT_TARGET): $(EQSAT_OBJECTS) $(CODEGEN_TARGET): $(CODEGEN_OBJECTS) $(VM_TARGET): $(VM_OBJECTS) $(REQUIRE_TARGET): $(REQUIRE_OBJECTS) -$(REQUIRENAVIGATOR_TARGET): $(REQUIRENAVIGATOR_OBJECTS) $(ISOCLINE_TARGET): $(ISOCLINE_OBJECTS) -$(AST_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(REQUIRE_TARGET) $(REQUIRENAVIGATOR_TARGET) $(ISOCLINE_TARGET): +$(AST_TARGET) $(COMPILER_TARGET) $(CONFIG_TARGET) $(ANALYSIS_TARGET) $(EQSAT_TARGET) $(CODEGEN_TARGET) $(VM_TARGET) $(REQUIRE_TARGET) $(ISOCLINE_TARGET): ar rcs $@ $^ # object file targets diff --git a/Require/Runtime/include/Luau/Require.h b/Require/include/Luau/Require.h similarity index 78% rename from Require/Runtime/include/Luau/Require.h rename to Require/include/Luau/Require.h index 77745dc5..a4918fff 100644 --- a/Require/Runtime/include/Luau/Require.h +++ b/Require/include/Luau/Require.h @@ -36,14 +36,14 @@ // nestable code unit and organizational unit, respectively. // // Require-by-string's runtime behavior can be additionally be configured in -// configuration files, such as .luaurc files in a filesystem context. The -// presence of a configuration file in the current context is signaled by the -// is_config_present function. Both modules and directories can contain -// configuration files; however, note that a given configuration file's scope is -// limited to the descendants of the module or directory in which it resides. In -// other words, when searching for a relevant configuration file for a given -// module, the search begins at the module's parent context and proceeds up the -// hierarchy from there, resolving to the first configuration file found. +// configuration files, such as .luaurc or .config.luau files in a filesystem +// context. The presence of a configuration file in the current context is +// signaled by the get_config_status function. Both modules and directories can +// contain configuration files; however, note that a given configuration file's +// scope is limited to the descendants of the module or directory in which it +// resides. In other words, when searching for a relevant configuration file for +// a given module, the search begins at the module's parent context and proceeds +// up the hierarchy from there, resolving to the first configuration file found. // //////////////////////////////////////////////////////////////////////////////// @@ -64,6 +64,15 @@ typedef enum luarequire_WriteResult WRITE_FAILURE } luarequire_WriteResult; +// Represents whether a configuration file is present, and if so, its syntax. +typedef enum luarequire_ConfigStatus +{ + CONFIG_ABSENT, + CONFIG_AMBIGUOUS, // Signals the presence of multiple configuration files. + CONFIG_PRESENT_JSON, + CONFIG_PRESENT_LUAU, +} luarequire_ConfigStatus; + typedef struct luarequire_Configuration { // Returns whether requires are permitted from the given chunkname. @@ -97,25 +106,34 @@ typedef struct luarequire_Configuration // only called if is_module_present returns true. luarequire_WriteResult (*get_cache_key)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); - // Returns whether a configuration file is present in the current context. - // If not, require-by-string will call to_parent until either a - // configuration file is present or NAVIGATE_FAILURE is returned (at root). - bool (*is_config_present)(lua_State* L, void* ctx); + // Returns whether a configuration file is present in the current context, + // and if so, its syntax. If not present, require-by-string will call + // to_parent until either a configuration file is present or + // NAVIGATE_FAILURE is returned (at root). + luarequire_ConfigStatus (*get_config_status)(lua_State* L, void* ctx); // Parses the configuration file in the current context for the given alias // and returns its value or WRITE_FAILURE if not found. This function is - // only called if is_config_present returns true. If this function pointer + // only called if get_config_status returns true. If this function pointer // is set, get_config must not be set. Opting in to this function pointer // disables parsing configuration files internally and can be used for finer // control over the configuration file parsing process. luarequire_WriteResult (*get_alias)(lua_State* L, void* ctx, const char* alias, char* buffer, size_t buffer_size, size_t* size_out); // Provides the contents of the configuration file in the current context. - // This function is only called if is_config_present returns true. If this - // function pointer is set, get_alias must not be set. Opting in to this - // function pointer enables parsing configuration files internally. + // This function is only called if get_config_status does not return + // CONFIG_ABSENT. If this function pointer is set, get_alias must not be + // set. Opting in to this function pointer enables parsing configuration + // files internally. luarequire_WriteResult (*get_config)(lua_State* L, void* ctx, char* buffer, size_t buffer_size, size_t* size_out); + // Returns the maximum number of milliseconds to allow for executing a given + // Luau-syntax configuration file. This function is only called if + // get_config_status returns CONFIG_PRESENT_LUAU and can be left undefined + // if support for Luau-syntax configuration files is not needed. A default + // value of 2000ms is used. Negative values are treated as infinite. + int (*get_luau_config_timeout)(lua_State* L, void* ctx); + // Executes the module and places the result on the stack. Returns the // number of results placed on the stack. Returning -1 directs the requiring // thread to yield. In this case, this thread should be resumed with the diff --git a/Require/Navigator/include/Luau/RequireNavigator.h b/Require/include/Luau/RequireNavigator.h similarity index 89% rename from Require/Navigator/include/Luau/RequireNavigator.h rename to Require/include/Luau/RequireNavigator.h index af5646a4..698df295 100644 --- a/Require/Navigator/include/Luau/RequireNavigator.h +++ b/Require/include/Luau/RequireNavigator.h @@ -2,11 +2,15 @@ #pragma once #include "Luau/Config.h" +#include "Luau/LuauConfig.h" +#include #include #include #include +struct lua_State; + //////////////////////////////////////////////////////////////////////////////// // // The RequireNavigator library provides a C++ interface for navigating the @@ -65,10 +69,21 @@ class NavigationContext GetConfig }; - virtual bool isConfigPresent() const = 0; + enum class ConfigStatus + { + Absent, + Ambiguous, + PresentJson, + PresentLuau + }; + + virtual ConfigStatus getConfigStatus() const = 0; + + std::function luauConfigInit = nullptr; + void (*luauConfigInterrupt)(lua_State* L, int gc) = nullptr; // The result of getConfigBehavior determines whether getAlias or getConfig - // is called when isConfigPresent returns true. + // is called when getConfigStatus indicates a configuration is present. virtual ConfigBehavior getConfigBehavior() const = 0; virtual std::optional getAlias(const std::string& alias) const = 0; virtual std::optional getConfig() const = 0; diff --git a/Require/Runtime/src/Navigation.cpp b/Require/src/Navigation.cpp similarity index 74% rename from Require/Runtime/src/Navigation.cpp rename to Require/src/Navigation.cpp index ba679866..9438dca9 100644 --- a/Require/Runtime/src/Navigation.cpp +++ b/Require/src/Navigation.cpp @@ -6,6 +6,7 @@ #include "lua.h" #include "lualib.h" +#include #include static constexpr size_t initalFileBufferSize = 1024; @@ -24,12 +25,38 @@ static NavigationContext::NavigateResult convertNavigateResult(luarequire_Naviga return NavigationContext::NavigateResult::NotFound; } +static NavigationContext::ConfigStatus convertConfigStatus(luarequire_ConfigStatus status) +{ + if (status == CONFIG_PRESENT_JSON) + return NavigationContext::ConfigStatus::PresentJson; + if (status == CONFIG_PRESENT_LUAU) + return NavigationContext::ConfigStatus::PresentLuau; + if (status == CONFIG_AMBIGUOUS) + return NavigationContext::ConfigStatus::Ambiguous; + + return NavigationContext::ConfigStatus::Absent; +} + RuntimeNavigationContext::RuntimeNavigationContext(luarequire_Configuration* config, lua_State* L, void* ctx, std::string requirerChunkname) : config(config) , L(L) , ctx(ctx) , requirerChunkname(std::move(requirerChunkname)) { + luauConfigInit = [config, ctx, this](lua_State* L) + { + int timeout = config->get_luau_config_timeout ? config->get_luau_config_timeout(L, ctx) : 2000; + this->timer.start(timeout); + lua_setthreaddata(L, &this->timer); + }; + + luauConfigInterrupt = [](lua_State* L, int gc) + { + RuntimeLuauConfigTimer* timer = static_cast(lua_getthreaddata(L)); + LUAU_ASSERT(timer); + if (timer->isFinished()) + luaL_errorL(L, "configuration execution timed out"); + }; } std::string RuntimeNavigationContext::getRequirerIdentifier() const @@ -77,9 +104,9 @@ std::optional RuntimeNavigationContext::getCacheKey() const return getStringFromCWriter(config->get_cache_key, initalIdentifierBufferSize); } -bool RuntimeNavigationContext::isConfigPresent() const +NavigationContext::ConfigStatus RuntimeNavigationContext::getConfigStatus() const { - return config->is_config_present(L, ctx); + return convertConfigStatus(config->get_config_status(L, ctx)); } NavigationContext::ConfigBehavior RuntimeNavigationContext::getConfigBehavior() const @@ -165,4 +192,20 @@ const std::string& RuntimeErrorHandler::getReportedError() const return errorMessage; } +void RuntimeLuauConfigTimer::start(int timeoutMs) +{ + startTime = std::chrono::steady_clock::now(); + if (timeoutMs < 0) + timeoutDuration = std::nullopt; // Infinite timeout + else + timeoutDuration = std::chrono::milliseconds(timeoutMs); +} + +bool RuntimeLuauConfigTimer::isFinished() const +{ + if (!timeoutDuration) + return false; + return (std::chrono::steady_clock::now() - startTime) >= *timeoutDuration; +} + } // namespace Luau::Require diff --git a/Require/Runtime/src/Navigation.h b/Require/src/Navigation.h similarity index 85% rename from Require/Runtime/src/Navigation.h rename to Require/src/Navigation.h index 36c30846..ecb90668 100644 --- a/Require/Runtime/src/Navigation.h +++ b/Require/src/Navigation.h @@ -4,6 +4,7 @@ #include "Luau/RequireNavigator.h" #include "Luau/Require.h" +#include #include struct lua_State; @@ -12,6 +13,17 @@ struct luarequire_Configuration; namespace Luau::Require { +class RuntimeLuauConfigTimer +{ +public: + void start(int timeoutMs); + bool isFinished() const; + +private: + std::chrono::steady_clock::time_point startTime; + std::optional timeoutDuration; +}; + class RuntimeNavigationContext : public NavigationContext { public: @@ -26,7 +38,7 @@ class RuntimeNavigationContext : public NavigationContext NavigateResult toParent() override; NavigateResult toChild(const std::string& component) override; - bool isConfigPresent() const override; + NavigationContext::ConfigStatus getConfigStatus() const override; NavigationContext::ConfigBehavior getConfigBehavior() const override; std::optional getAlias(const std::string& alias) const override; std::optional getConfig() const override; @@ -52,6 +64,7 @@ class RuntimeNavigationContext : public NavigationContext lua_State* L; void* ctx; std::string requirerChunkname; + RuntimeLuauConfigTimer timer = RuntimeLuauConfigTimer{}; }; // Non-throwing error reporter diff --git a/Require/Navigator/src/PathUtilities.cpp b/Require/src/PathUtilities.cpp similarity index 96% rename from Require/Navigator/src/PathUtilities.cpp rename to Require/src/PathUtilities.cpp index 6de4f7ad..397a1026 100644 --- a/Require/Navigator/src/PathUtilities.cpp +++ b/Require/src/PathUtilities.cpp @@ -1,6 +1,6 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include "Luau/PathUtilities.h" +#include "PathUtilities.h" #include diff --git a/Require/Navigator/include/Luau/PathUtilities.h b/Require/src/PathUtilities.h similarity index 100% rename from Require/Navigator/include/Luau/PathUtilities.h rename to Require/src/PathUtilities.h diff --git a/Require/Runtime/src/Require.cpp b/Require/src/Require.cpp similarity index 97% rename from Require/Runtime/src/Require.cpp rename to Require/src/Require.cpp index 306d0c63..ff0c7214 100644 --- a/Require/Runtime/src/Require.cpp +++ b/Require/src/Require.cpp @@ -29,8 +29,8 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config) luaL_error(L, "require configuration is missing required function pointer: get_loadname"); if (!config.get_cache_key) luaL_error(L, "require configuration is missing required function pointer: get_cache_key"); - if (!config.is_config_present) - luaL_error(L, "require configuration is missing required function pointer: is_config_present"); + if (!config.get_config_status) + luaL_error(L, "require configuration is missing required function pointer: get_config_status"); if (config.get_alias && config.get_config) luaL_error(L, "require configuration cannot define both get_alias and get_config"); if (!config.get_alias && !config.get_config) diff --git a/Require/Runtime/src/RequireImpl.cpp b/Require/src/RequireImpl.cpp similarity index 100% rename from Require/Runtime/src/RequireImpl.cpp rename to Require/src/RequireImpl.cpp diff --git a/Require/Runtime/src/RequireImpl.h b/Require/src/RequireImpl.h similarity index 100% rename from Require/Runtime/src/RequireImpl.h rename to Require/src/RequireImpl.h diff --git a/Require/Navigator/src/RequireNavigator.cpp b/Require/src/RequireNavigator.cpp similarity index 82% rename from Require/Navigator/src/RequireNavigator.cpp rename to Require/src/RequireNavigator.cpp index 7fd39ac2..9852039d 100644 --- a/Require/Navigator/src/RequireNavigator.cpp +++ b/Require/src/RequireNavigator.cpp @@ -2,9 +2,10 @@ #include "Luau/RequireNavigator.h" -#include "Luau/PathUtilities.h" +#include "PathUtilities.h" #include "Luau/Config.h" +#include "Luau/LuauConfig.h" #include #include @@ -179,30 +180,50 @@ Error Navigator::navigateToAndPopulateConfig(const std::string& desiredAlias) if (result == NavigationContext::NavigateResult::NotFound) break; // Not treated as an error: interpreted as reaching the root. - if (navigationContext.isConfigPresent()) + NavigationContext::ConfigStatus status = navigationContext.getConfigStatus(); + if (status == NavigationContext::ConfigStatus::Absent) + { + continue; + } + else if (status == NavigationContext::ConfigStatus::Ambiguous) + { + return "could not resolve alias \"" + desiredAlias + "\" (ambiguous configuration file)"; + } + else { if (navigationContext.getConfigBehavior() == NavigationContext::ConfigBehavior::GetAlias) { foundAliasValue = navigationContext.getAlias(desiredAlias); + break; } - else - { - std::optional configContents = navigationContext.getConfig(); - if (!configContents) - return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\""; - Luau::ConfigOptions opts; - Luau::ConfigOptions::AliasOptions aliasOpts; - aliasOpts.configLocation = "unused"; - aliasOpts.overwriteAliases = false; - opts.aliasOptions = std::move(aliasOpts); + std::optional configContents = navigationContext.getConfig(); + if (!configContents) + return "could not get configuration file contents to resolve alias \"" + desiredAlias + "\""; + Luau::ConfigOptions opts; + Luau::ConfigOptions::AliasOptions aliasOpts; + aliasOpts.configLocation = "unused"; + aliasOpts.overwriteAliases = false; + opts.aliasOptions = std::move(aliasOpts); + + if (status == NavigationContext::ConfigStatus::PresentJson) + { if (Error error = Luau::parseConfig(*configContents, config, opts)) return error; + } + else if (status == NavigationContext::ConfigStatus::PresentLuau) + { + InterruptCallbacks callbacks; + callbacks.initCallback = navigationContext.luauConfigInit; + callbacks.interruptCallback = navigationContext.luauConfigInterrupt; - if (config.aliases.contains(desiredAlias)) - foundAliasValue = config.aliases[desiredAlias].value; + if (Error error = Luau::extractLuauConfig(*configContents, config, std::move(opts.aliasOptions), std::move(callbacks))) + return error; } + + if (config.aliases.contains(desiredAlias)) + foundAliasValue = config.aliases[desiredAlias].value; } }; diff --git a/Sources.cmake b/Sources.cmake index fa7deb58..5266dcb3 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -590,24 +590,21 @@ endif() if(TARGET Luau.Require) # Luau.Require Sources target_sources(Luau.Require PRIVATE - Require/Runtime/include/Luau/Require.h - - Require/Runtime/src/Navigation.h - Require/Runtime/src/RequireImpl.h - - Require/Runtime/src/Navigation.cpp - Require/Runtime/src/Require.cpp - Require/Runtime/src/RequireImpl.cpp) -endif() - -if(TARGET Luau.RequireNavigator) - # Luau.Require Sources - target_sources(Luau.RequireNavigator PRIVATE - Require/Navigator/include/Luau/PathUtilities.h - Require/Navigator/include/Luau/RequireNavigator.h - - Require/Navigator/src/PathUtilities.cpp - Require/Navigator/src/RequireNavigator.cpp) + # Public headers + Require/include/Luau/Require.h + Require/include/Luau/RequireNavigator.h + + # Internal headers + Require/src/Navigation.h + Require/src/PathUtilities.h + Require/src/RequireImpl.h + + # Source files + Require/src/Navigation.cpp + Require/src/PathUtilities.cpp + Require/src/Require.cpp + Require/src/RequireImpl.cpp + Require/src/RequireNavigator.cpp) endif() if(TARGET Luau.Web) diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 65c93e81..2dca8fc4 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -22,7 +22,6 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauAutocompleteAttributes) using namespace Luau; @@ -4917,7 +4916,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_method_in_unfinished_while_body") TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_empty_attribute") { - ScopedFastFlag sff[]{{FFlag::LuauParametrizedAttributeSyntax, true}, {FFlag::LuauAutocompleteAttributes, true}}; + ScopedFastFlag sff[]{{FFlag::LuauAutocompleteAttributes, true}}; check(R"( \@@1 @@ -4932,7 +4931,7 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_empty_attribute") TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_deprecated_attribute") { - ScopedFastFlag sff[]{{FFlag::LuauParametrizedAttributeSyntax, true}, {FFlag::LuauAutocompleteAttributes, true}}; + ScopedFastFlag sff[]{{FFlag::LuauAutocompleteAttributes, true}}; check(R"( \@dep@1 @@ -4947,7 +4946,7 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_deprecated_attribute") TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_empty_braced_attribute") { - ScopedFastFlag sff[]{{FFlag::LuauParametrizedAttributeSyntax, true}, {FFlag::LuauAutocompleteAttributes, true}}; + ScopedFastFlag sff[]{{FFlag::LuauAutocompleteAttributes, true}}; check(R"( \@[@1] @@ -4962,7 +4961,7 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_empty_braced_attribute") TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_deprecated_braced_attribute") { - ScopedFastFlag sff[]{{FFlag::LuauParametrizedAttributeSyntax, true}, {FFlag::LuauAutocompleteAttributes, true}}; + ScopedFastFlag sff[]{{FFlag::LuauAutocompleteAttributes, true}}; check(R"( \@[dep@1] diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index c435f0ee..40ca347e 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -18,8 +18,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) - TEST_SUITE_BEGIN("Generalization"); struct GeneralizationFixture @@ -394,9 +392,7 @@ TEST_CASE_FIXTURE(Fixture, "generics_dont_leak_into_callback") TEST_CASE_FIXTURE(Fixture, "generics_dont_leak_into_callback_2") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}}; CheckResult result = check(R"( local func: (T, (T) -> ()) -> () = nil :: any diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index c2f44b5b..a686fedc 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -8,7 +8,6 @@ #include "doctest.h" LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) using namespace Luau; @@ -1875,8 +1874,6 @@ end TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeWithParams") { - ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true}; - // @deprecated works on local functions { LintResult result = lint(R"( diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 564bc1f5..5e45c801 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -16,6 +16,7 @@ #include LUAU_FASTFLAG(LuauUnreducedTypeFunctionsDontTriggerWarnings) +LUAU_FASTFLAG(LuauNewNonStrictBetterCheckedFunctionErrorMessage) using namespace Luau; @@ -385,7 +386,10 @@ end} )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == "Argument x with type 'unknown' is used in a way that will run time error"); + if (FFlag::LuauNewNonStrictBetterCheckedFunctionErrorMessage) + CHECK(toString(result.errors[0]) == "the argument 'x' is used in a way that will error at runtime"); + else + CHECK(toString(result.errors[0]) == "Argument x with type 'unknown' is used in a way that will run time error"); } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "local_fn_produces_error") @@ -816,9 +820,24 @@ getAllTheArgsWrong(3, true, "what") CHECK(err1 != nullptr); CHECK(err2 != nullptr); CHECK(err3 != nullptr); - CHECK_EQ("Function 'getAllTheArgsWrong' expects 'string' at argument #1, but got 'number'", toString(result.errors[0])); - CHECK_EQ("Function 'getAllTheArgsWrong' expects 'number' at argument #2, but got 'boolean'", toString(result.errors[1])); - CHECK_EQ("Function 'getAllTheArgsWrong' expects 'boolean' at argument #3, but got 'string'", toString(result.errors[2])); + if (FFlag::LuauNewNonStrictBetterCheckedFunctionErrorMessage) + { + CHECK_EQ( + "the function 'getAllTheArgsWrong' expects to get a string as its 1st argument, but is being given a number", toString(result.errors[0]) + ); + CHECK_EQ( + "the function 'getAllTheArgsWrong' expects to get a number as its 2nd argument, but is being given a boolean", toString(result.errors[1]) + ); + CHECK_EQ( + "the function 'getAllTheArgsWrong' expects to get a boolean as its 3rd argument, but is being given a string", toString(result.errors[2]) + ); + } + else + { + CHECK_EQ("Function 'getAllTheArgsWrong' expects 'string' at argument #1, but got 'number'", toString(result.errors[0])); + CHECK_EQ("Function 'getAllTheArgsWrong' expects 'number' at argument #2, but got 'boolean'", toString(result.errors[1])); + CHECK_EQ("Function 'getAllTheArgsWrong' expects 'boolean' at argument #3, but got 'string'", toString(result.errors[2])); + } } TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "new_non_strict_skips_warnings_on_unreduced_typefunctions") diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index baa53f31..ea298353 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -9,14 +9,12 @@ #include "doctest.h" #include "Luau/Normalize.h" -#include "Luau/BuiltinDefinitions.h" #include LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauNormalizerUnionTyvarsTakeMaxSize) using namespace Luau; @@ -1283,10 +1281,7 @@ do end #if 0 TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") { - ScopedFastFlag sff[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; // FIXME CLI-153131: This is constructing a cyclic type pack diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index a80c97d5..e275a5ab 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -18,7 +18,6 @@ LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix) -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix extern bool luau_telemetry_parsed_return_type_variadic_with_type_suffix; @@ -3729,8 +3728,6 @@ end)"); TEST_CASE_FIXTURE(Fixture, "parse_parametrized_attribute_on_function_stat") { - ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true}; - AstStatBlock* stat = parse(R"( @[deprecated{ use = "greetng", reason = "Using is too causal"}] function hello(x, y) @@ -3751,7 +3748,6 @@ end)"); TEST_CASE_FIXTURE(Fixture, "non_literal_attribute_arguments_is_not_allowed") { - ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true}; ParseResult result = tryParse(R"( @[deprecated{ reason = reasonString }] function hello(x, y) @@ -3765,7 +3761,6 @@ end)"); TEST_CASE_FIXTURE(Fixture, "unknown_arguments_for_depricated_is_not_allowed") { - ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true}; ParseResult result = tryParse(R"( @[deprecated({}, "Very deprecated")] function hello(x, y) @@ -3806,7 +3801,6 @@ end)"); TEST_CASE_FIXTURE(Fixture, "do_not_hang_on_incomplete_attribute_list") { - ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true}; ParseResult result = tryParse(R"( @[] function hello(x, y) diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index 71465d0e..b14e6795 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -410,8 +410,10 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireWithAmbiguityInAliasDiscovery") { char executable[] = "luau"; std::vector paths = { - getLuauDirectory(PathType::Relative) + "/tests/require/with_config/parent_ambiguity/folder/requirer.luau", - getLuauDirectory(PathType::Absolute) + "/tests/require/with_config/parent_ambiguity/folder/requirer.luau", + getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/parent_ambiguity/folder/requirer.luau", + getLuauDirectory(PathType::Absolute) + "/tests/require/config_tests/with_config/parent_ambiguity/folder/requirer.luau", + getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/parent_ambiguity/folder/requirer.luau", + getLuauDirectory(PathType::Absolute) + "/tests/require/config_tests/with_config_luau/parent_ambiguity/folder/requirer.luau", }; for (const std::string& path : paths) @@ -634,23 +636,44 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireUnprefixedPath") TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAlias") { - std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/alias_requirer"; - runProtectedRequire(path); - assertOutputContainsAll({"true", "result from dependency"}); + { + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/src/alias_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from dependency"}); + } + { + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/src/alias_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from dependency"}); + } } TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithParentAlias") { - std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/parent_alias_requirer"; - runProtectedRequire(path); - assertOutputContainsAll({"true", "result from other_dependency"}); + { + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/src/parent_alias_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from other_dependency"}); + } + { + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/src/parent_alias_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from other_dependency"}); + } } TEST_CASE_FIXTURE(ReplWithPathFixture, "RequirePathWithAliasPointingToDirectory") { - std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/directory_alias_requirer"; - runProtectedRequire(path); - assertOutputContainsAll({"true", "result from subdirectory_dependency"}); + { + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/src/directory_alias_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from subdirectory_dependency"}); + } + { + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/src/directory_alias_requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "result from subdirectory_dependency"}); + } } TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireAliasThatDoesNotExist") @@ -685,6 +708,27 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "AliasHasIllegalFormat") assertOutputContainsAll({"false", " is not a valid alias"}); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "AliasNotParsedIfConfigsAmbiguous") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/config_ambiguity/requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"false", "could not resolve alias \"dep\" (ambiguous configuration file)"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "AliasNotParsedIfLuauConfigTimesOut" * doctest::timeout(5)) +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/config_luau_timeout/requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"false", "configuration execution timed out"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "CannotRequireConfigLuau") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/config_cannot_be_required/requirer"; + runProtectedRequire(path); + assertOutputContainsAll({"false", "could not resolve child component \".config\""}); +} + TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireFromLuauBinary") { char executable[] = "luau"; @@ -695,8 +739,10 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireFromLuauBinary") getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/module.luau", getLuauDirectory(PathType::Relative) + "/tests/require/without_config/nested/init.luau", getLuauDirectory(PathType::Absolute) + "/tests/require/without_config/nested/init.luau", - getLuauDirectory(PathType::Relative) + "/tests/require/with_config/src/submodule/init.luau", - getLuauDirectory(PathType::Absolute) + "/tests/require/with_config/src/submodule/init.luau", + getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config/src/submodule/init.luau", + getLuauDirectory(PathType::Absolute) + "/tests/require/config_tests/with_config/src/submodule/init.luau", + getLuauDirectory(PathType::Relative) + "/tests/require/config_tests/with_config_luau/src/submodule/init.luau", + getLuauDirectory(PathType::Absolute) + "/tests/require/config_tests/with_config_luau/src/submodule/init.luau", }; for (const std::string& path : paths) diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index 1533a6bd..ac30ff2e 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -26,10 +26,10 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauIceLess) LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) LUAU_FASTFLAG(LuauLimitUnification) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauUseNativeStackGuard) LUAU_FASTINT(LuauGenericCounterMaxDepth) +LUAU_FASTFLAG(LuauNormalizerStepwiseFuel) struct LimitFixture : BuiltinsFixture { @@ -423,11 +423,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_should_cache_pairs_in_seen_set" * doctest::timeout(1.0)) { - ScopedFastFlag sff[] = { - {FFlag::LuauSolverV2, true}, - // This flags surfaced and solves the problem. (The original PR was reverted) - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; constexpr const char* src = R"LUAU( type DataProxy = any @@ -665,4 +661,35 @@ end )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_" * doctest::timeout(4.0)) +{ + ScopedFastFlag _{FFlag::LuauNormalizerStepwiseFuel, true}; + + LUAU_REQUIRE_ERRORS(check(R"( + _ = if _ then {n0=# _,[_]=_,``,[function(l0,l0,l0) + do end + end]=_,setmetatable,[l0(_ + _)]=_,} else _(),_,_ + _[_](_,_(coroutine,_,_,nil),_(0,_()),function() + end) + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_oom_unions" * doctest::timeout(4.0)) +{ + ScopedFastFlag _{FFlag::LuauNormalizerStepwiseFuel, true}; + + LUAU_REQUIRE_ERRORS(check(R"( + local _ = true,l0 + _ = if _ then _ else _._,if _[_] then nil elseif _ then `` else _._,... + _ = if _ then _ elseif _ then `` else _.n0,true,... + _G = if "" then _ else _.n0,_ + _ = if _[_] then _ elseif _ then _ + n0 else _._,32804,... + _.readstring = _,_ + local l0 = require(module0) + _ = _,l0,_ + do end + _.readstring += _ + )")); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index ec486abc..aeb2bb55 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -16,9 +16,6 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAG(LuauPassBindableGenericsByReference) LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) @@ -1405,9 +1402,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "({ x: T }) -> T <: ({ method: ({ x: T } TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type_function_instance") { - ScopedFastFlag sff{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - TypeId longTy = arena.addType( UnionType{ {getBuiltins()->booleanType, @@ -1450,11 +1444,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(number, number...) numberType, getBuiltins()->errorType}}); TypeId superTy = getBuiltins()->booleanType; SubtypingResult result = isSubtype(subTy, superTy); @@ -1478,10 +1468,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_check_for_error_suppress TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_check_for_error_suppression_in_intersect_type_path") { - ScopedFastFlag sffs[] = { - {FFlag::LuauConsiderErrorSuppressionInTypes, true}, - {FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}, - }; + ScopedFastFlag sff{FFlag::LuauConsiderErrorSuppressionInTypes, true}; TypeId subTy = getBuiltins()->booleanType; TypeId superTy = arena.addType(IntersectionType{{getBuiltins()->numberType, getBuiltins()->errorType}}); @@ -1506,8 +1493,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_check_for_error_suppress TEST_CASE_FIXTURE(SubtypeFixture, "(() -> number) -> () <: (() -> T) -> ()") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; - TypeId f1 = fn({nothingToNumberType}, {}); TypeId f2 = fn({genericNothingToTType}, {}); CHECK_IS_SUBTYPE(f1, f2); @@ -1515,8 +1500,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(() -> number) -> () <: (() -> T) -> ()") TEST_CASE_FIXTURE(SubtypeFixture, "((number) -> ()) -> () <: ((T) -> ()) -> ()") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; - TypeId f1 = fn({numberToNothingType}, {}); TypeId f2 = fn({genericTToNothingType}, {}); CHECK_IS_SUBTYPE(f1, f2); @@ -1524,8 +1507,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "((number) -> ()) -> () <: ((T) -> ()) -> ( TEST_CASE_FIXTURE(SubtypeFixture, "((number) -> number) -> () <: ((T) -> T) -> ()") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; - TypeId f1 = fn({numberToNumberType}, {}); TypeId f2 = fn({genericTToTType}, {}); CHECK_IS_SUBTYPE(f1, f2); @@ -1533,8 +1514,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "((number) -> number) -> () <: ((T) -> T) - TEST_CASE_FIXTURE(SubtypeFixture, "(x: T, y: T, f: (T, T) -> T) -> T <: (number, number, (U, U) -> add) -> number") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; - TypeId f1 = arena.addType(FunctionType( {genericT}, {}, @@ -1559,8 +1538,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(x: T, y: T, f: (T, T) -> T) -> T <: (numb TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> ((A...) -> ()) <: (string -> ((number) -> ())") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - // (A...) -> () TypeId asToNothing = arena.addType(FunctionType({}, {genericAs}, genericAs, getBuiltins()->emptyTypePack, std::nullopt, false)); TypeId f1 = arena.addType(FunctionType({}, {genericAs}, genericAs, pack({asToNothing}), std::nullopt, false)); @@ -1571,10 +1548,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> ((A...) -> ()) <: (stri TEST_CASE_FIXTURE(SubtypeFixture, "no_caching_type_function_instances_with_mapped_generics") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; - // ((U) -> keyof, (U) -> keyof) "a", ({"b" : number}) -> "a") - TypeId keyOfU = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions.keyofFunc, {genericU}}); // (U) -> keyof TypeId uToKeyOfU = arena.addType(FunctionType({genericU}, {}, arena.addTypePack({genericU}), arena.addTypePack({keyOfU}))); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 9a473537..71399e89 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -16,7 +16,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) -LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauBuiltinTypeFunctionsArentGlobal) struct TypeFunctionFixture : Fixture @@ -1893,8 +1892,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_functions_should_not_get_stuck_or") TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation") { - ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; - CheckResult result = check(R"( type a = {a<{T}>} )"); @@ -1905,8 +1902,6 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation") TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation1") { - ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; - CheckResult result = check(R"( type b = {b} )"); @@ -1917,8 +1912,6 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation1") TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation2") { - ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; - CheckResult result = check(R"( type c = {c} )"); @@ -1929,8 +1922,6 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation2") TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation3") { - ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; - CheckResult result = check(R"( type d = (d) -> () )"); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 8e471460..696b8dde 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -13,9 +13,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauSubtypingPrimitiveAndGenericTableTypes) -LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVectorLerp) LUAU_FASTFLAG(LuauCompileVectorLerp) LUAU_FASTFLAG(LuauTypeCheckerVectorLerp2) @@ -1727,11 +1725,7 @@ table.insert(1::any, 2::any) TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_requires_all_fields") { - ScopedFastFlag _[] = { - {FFlag::LuauNoScopeShallNotSubsumeAll, true}, - {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} - }; + ScopedFastFlag _[] = {{FFlag::LuauNoScopeShallNotSubsumeAll, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}}; CheckResult result = check(R"( local function huh(): { { x: number, y: string } } @@ -1785,9 +1779,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_known_ TEST_CASE_FIXTURE(BuiltinsFixture, "next_with_refined_any") { ScopedFastFlag lsv2{FFlag::LuauSolverV2, true}; - ScopedFastFlag sffs[] = { - {FFlag::LuauSubtypingPrimitiveAndGenericTableTypes, true}, {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true} - }; + ScopedFastFlag sff{FFlag::LuauSubtypingPrimitiveAndGenericTableTypes, true}; CheckResult result = check(R"( --!strict diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 073aaae5..f7204b46 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -23,14 +23,14 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauCollapseShouldNotCrash) LUAU_FASTFLAG(LuauFormatUseLastPosition) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) -LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauFixNilRightPad) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNoOrderingTypeFunctions) +LUAU_FASTFLAG(LuauPushTypeConstraint2) +LUAU_FASTFLAG(LuauPushTypeConstraintIntersection) +LUAU_FASTFLAG(LuauPushTypeConstraintSingleton) +LUAU_FASTFLAG(LuauPushTypeConstraintLambdas) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -1442,7 +1442,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_lib_function_function_argument {FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauNoOrderingTypeFunctions, true}, - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauNoScopeShallNotSubsumeAll, true}, }; @@ -1959,8 +1958,8 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_ if (FFlag::LuauSolverV2) { LUAU_CHECK_NO_ERRORS(result); - if (!FFlag::LuauSubtypingGenericsDoesntUseVariance) // FIXME CLI-162439, the below fails on Linux with the flag on - CHECK("({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g"))); + // FIXME CLI-162439, the below fails on Linux with the flag on + // CHECK("({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g"))); } else { @@ -2388,11 +2387,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_wi TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}, - {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( local function apply(f: (a, b...) -> c..., x: a) @@ -3272,8 +3267,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_calls_should_not_crash") TEST_CASE_FIXTURE(BuiltinsFixture, "unnecessary_nil_in_lower_bound_of_generic") { - ScopedFastFlag _{FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}; - CheckResult result = check( Mode::Nonstrict, R"( @@ -3358,4 +3351,29 @@ TEST_CASE_FIXTURE(Fixture, "cli_119545_pass_lambda_inside_table") )")); } +TEST_CASE_FIXTURE(Fixture, "oss_2065_bidirectional_inference_function_call") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintLambdas, true}, + {FFlag::LuauPushTypeConstraintIntersection, true}, + {FFlag::DebugLuauAssertOnForcedConstraint, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function foo(callback: () -> (() -> ())?) + end + + local someCondition: boolean = true + + foo(function() + if someCondition then + return nil + end + return function() end + end) + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 17923dd9..59048485 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -9,12 +9,9 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAG(LuauSubtypingUnionsAndIntersectionsInGenericBounds) @@ -995,8 +992,6 @@ local TheDispatcher: Dispatcher = { TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_few") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - CheckResult result = check(R"( function test(a: number) return 1 @@ -1023,8 +1018,6 @@ wrapper(test) TEST_CASE_FIXTURE(Fixture, "generic_argument_count_too_many") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - CheckResult result = check(R"( function test2(a: number, b: string) return 1 @@ -1067,8 +1060,6 @@ wrapper(test2, 1, "") TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - CheckResult result = check(R"( function test2(a: number) return "hello" @@ -1097,8 +1088,6 @@ wrapper(test2, 1) TEST_CASE_FIXTURE(Fixture, "generic_argument_pack_type_inferred_from_return_no_error") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - CheckResult result = check(R"( function test2(a: number) return "hello" @@ -1115,8 +1104,6 @@ wrapper(test2, "hello") TEST_CASE_FIXTURE(Fixture, "nested_generic_argument_type_packs") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - CheckResult result = check(R"( function test2(a: number) return 3 @@ -1480,10 +1467,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded_ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_overloaded_pt_2") { - ScopedFastFlag _[] = { - {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, - }; + ScopedFastFlag _{FFlag::LuauSubtypingReportGenericBoundMismatches2, true}; CheckResult result = check(R"( local g12: ((T, (T) -> T) -> T) & ((T, T, (T, T) -> T) -> T) @@ -1544,7 +1528,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions_2") { - ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}}; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( type t = (a, a, (a, a) -> a) -> a @@ -1893,8 +1877,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f(foo: (A) -> ()): () end function g(...: B...): () end @@ -1906,8 +1888,6 @@ f(g) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_2") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f(foo: (number) -> (number)): () end type T = (A...) -> A... @@ -1920,8 +1900,6 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_3") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f(foo: (B...) -> B...): () end type T = (A...) -> A... @@ -1935,7 +1913,6 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_4") { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( function f(foo: (A...) -> A...): () end @@ -1949,8 +1926,6 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_5") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f(foo: (number) -> number): () end type T = (A...) -> number @@ -1963,8 +1938,6 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_6") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f(foo: (...number) -> number): () end type T = (A...) -> number @@ -1977,8 +1950,6 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_7") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f(foo: () -> ()): () end type T = () -> A... @@ -1991,8 +1962,6 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_8") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f(foo: () -> ()): () end type T = (A...) -> A... @@ -2006,7 +1975,6 @@ f(t) TEST_CASE_FIXTURE(BuiltinsFixture, "nested_generic_packs") { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; CheckResult result = check(R"( type T = (A...) -> ((A...) -> ()) @@ -2020,7 +1988,7 @@ local u: U = t TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error") { - ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}}; + ScopedFastFlag _{FFlag::LuauSubtypingReportGenericBoundMismatches2, true}; CheckResult res = check(R"( local func: (T, (T) -> ()) -> () = nil :: any local foobar: (number) -> () = nil :: any @@ -2036,7 +2004,7 @@ TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error") TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error_1") { - ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}}; + ScopedFastFlag _{FFlag::LuauSubtypingReportGenericBoundMismatches2, true}; CheckResult res = check(R"( --!strict @@ -2058,8 +2026,6 @@ TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error_1") TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall_should_work_with_generics") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( --!strict local v: (number) -> (number) = nil :: any @@ -2078,7 +2044,6 @@ TEST_CASE_FIXTURE(Fixture, "array_of_singletons_should_subtype_against_generic_a // These flags expose the issue {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::DebugLuauStringSingletonBasedOnQuotes, true}, - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, // And this flag fixes it {FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds, true} }; @@ -2095,7 +2060,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gh1985_array_of_union_for_generic") { ScopedFastFlag _[] = { {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds, true} }; @@ -2114,7 +2078,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gh1985_array_of_union_for_generic_2") { ScopedFastFlag _[] = { {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds, true} }; diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index f57cd4aa..38d938ba 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -10,8 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -851,8 +849,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f() function g(x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))) @@ -1118,8 +1114,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") { - ScopedFastFlag _{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f() function g(x : ((a...) -> ()) & ((b...) -> ())) @@ -1149,9 +1143,6 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") { - ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - ScopedFastFlag sff2{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - CheckResult result = check(R"( function f() function g(x : (() -> a...) & (() -> (number?,a...))) @@ -1182,9 +1173,6 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - CheckResult result = check(R"( function f() function g(x : ((a...) -> ()) & ((number,a...) -> number)) diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index fc98c218..26f24ab2 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -13,10 +13,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTINT(LuauSolverConstraintLimit) -LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) using namespace Luau; @@ -817,7 +815,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any") TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_function_mutation") { - ScopedFastFlag _[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}}; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; fileResolver.source["game/A"] = R"( function test2(a: number, b: string) @@ -909,8 +907,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "scrub_unsealed_tables") TEST_CASE_FIXTURE(BuiltinsFixture, "invalid_local_alias_shouldnt_shadow_imported_type") { - ScopedFastFlag _{FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; - fileResolver.source["game/A"] = R"( export type bad = {T} return {} @@ -937,8 +933,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "invalid_local_alias_shouldnt_shadow_imported TEST_CASE_FIXTURE(BuiltinsFixture, "invalid_alias_should_export_as_error_type") { - ScopedFastFlag _{FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; - fileResolver.source["game/A"] = R"( export type bad = {bad<{T}>} return {} diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 75765ee3..2f6ef2c5 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -1446,4 +1446,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_and_many_nested_typeof_contexts") LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "bidirectional_inference_variadic_type_pack_read_only_prop") +{ + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local foo: { read bar: (...string) -> () } = { + bar = function (foobar) + print(foobar) + end + } + )")); + + // CLI-174314: This should be `string`: we need to flatten and *extend* + // the type packs for function arguments, so that variadic type packs + // fill in. + CHECK_EQ("unknown", toString(requireTypeAtPosition({3, 24}))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 66687820..6614e7a9 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -13,10 +13,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauRefineNoRefineAlways) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) -LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAG(LuauAddConditionalContextForTernary) @@ -25,6 +22,7 @@ LUAU_FASTFLAG(LuauConsiderErrorSuppressionInTypes) LUAU_FASTFLAG(LuauAddRefinementToAssertions) LUAU_FASTFLAG(LuauEnqueueUnionsOfDistributedTypeFunctions) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) +LUAU_FASTFLAG(LuauNormalizationPreservesAny) using namespace Luau; @@ -2282,10 +2280,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "check_refinement_to_primitive_and_compare") TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant") { - ScopedFastFlag _[] = { - {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, - {FFlag::LuauNoOrderingTypeFunctions, true}, - }; + ScopedFastFlag _{FFlag::LuauNoOrderingTypeFunctions, true}; // FIXME CLI-141364: An underlying bug in normalization means the type of // `isIndexKey` is platform dependent. @@ -3191,4 +3186,24 @@ TEST_CASE_FIXTURE(Fixture, "type_function_reduction_with_union_type_application" )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "refine_any_and_unknown_should_still_be_any") +{ + ScopedFastFlag _{FFlag::LuauNormalizationPreservesAny, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local REACT_FRAGMENT_TYPE = (nil :: any) + local function typeOf(object: any) + local __type = object.type + + if __type == REACT_FRAGMENT_TYPE then + return __type + else + return __type + and typeof(__type) == "table" + and __type["$$typeof"] + end + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index ff943428..fbb34bc2 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -807,5 +807,14 @@ TEST_CASE_FIXTURE(Fixture, "bidirectionally_infer_indexers_errored") CHECK(get(e)); } +TEST_CASE_FIXTURE(Fixture, "oss_2018") +{ + ScopedFastFlag _{FFlag::LuauPushTypeConstraint2, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local rule: { rule: "AppendTextComment" } | { rule: "Other" } = { rule = "AppendTextComment" } + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 1f6532d0..fb93ae60 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -25,14 +25,13 @@ LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) -LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauPushTypeConstraint2) -LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSimplifyIntersectionForLiteralSubtypeCheck) LUAU_FASTFLAG(LuauCacheDuplicateHasPropConstraints) LUAU_FASTFLAG(LuauPushTypeConstraintIntersection) LUAU_FASTFLAG(LuauPushTypeConstraintSingleton) +LUAU_FASTFLAG(LuauPushTypeConstraintLambdas) TEST_SUITE_BEGIN("TableTests"); @@ -2313,7 +2312,6 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table // Old Solver Bug: We have to turn off InstantiateInSubtyping in the old solver as we don't invariantly // compare functions inside of table properties ScopedFastFlag sff[] = { - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauInstantiateInSubtyping, FFlag::LuauSolverV2}, }; @@ -2355,10 +2353,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_properties_in_strict") { ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauNoScopeShallNotSubsumeAll, true}, - {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} + {FFlag::LuauSolverV2, true}, {FFlag::LuauNoScopeShallNotSubsumeAll, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true} }; CheckResult result = check(R"( @@ -5806,7 +5801,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, {FFlag::LuauPushTypeConstraint2, true}, }; @@ -6108,10 +6102,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_array_of_any") TEST_CASE_FIXTURE(BuiltinsFixture, "bad_insert_type_mismatch") { ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, - {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, - {FFlag::LuauNoScopeShallNotSubsumeAll, true}, + {FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauNoScopeShallNotSubsumeAll, true} }; CheckResult result = check(R"( @@ -6229,4 +6220,128 @@ TEST_CASE_FIXTURE(Fixture, "oss_1953") CHECK_EQ("kind", err->key); } +TEST_CASE_FIXTURE(Fixture, "array_of_callbacks_bidirectionally_inferred") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintLambdas, true}, + {FFlag::DebugLuauAssertOnForcedConstraint, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local Actions : { [string]: (string?) -> number? } = { + Foo = function (input) + if input then + return 42 + else + return nil + end + end + } + )")); + + CHECK_EQ("string?", toString(requireTypeAtPosition({3, 21}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1483") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintLambdas, true}, + {FFlag::DebugLuauAssertOnForcedConstraint, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + type Form = "do-not-register" | (() -> ()) + + local function observer(register: () -> Form) end + + observer(function() + if math.random() > 0.5 then + return "do-not-register" + end + return function() end + end) + )")); +} + +TEST_CASE_FIXTURE(Fixture, "oss_1910") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintLambdas, true}, + {FFlag::DebugLuauAssertOnForcedConstraint, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + type Implementation = { + on_thing: (something: boolean) -> (), + } + + local a: Implementation = { + on_thing = function(something) + local _ = something + end, + } + )")); + CHECK_EQ("boolean", toString(requireTypeAtPosition({7, 29}))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "bidirectional_inference_variadic_type_pack") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintLambdas, true}, + {FFlag::DebugLuauAssertOnForcedConstraint, true}, + }; + + // As it turns out, you don't strictly need bidirectional inference in + // this specific case: subtyping is enough to constain `foobar` to be + // `string <: 'a <: string`, and generalization takes care of the rest, + // but you need to order the constraints correctly, otherwise we + // generalize the lambda too early. + LUAU_REQUIRE_NO_ERRORS(check(R"( + local foo: { (...string) -> () } = { + function (foobar) + print(foobar) + end + } + )")); + + CHECK_EQ("string", toString(requireTypeAtPosition({3, 24}))); +} + +TEST_CASE_FIXTURE(Fixture, "table_with_intersection_containing_lambda") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint2, true}, + {FFlag::LuauPushTypeConstraintLambdas, true}, + {FFlag::LuauPushTypeConstraintIntersection, true}, + {FFlag::DebugLuauAssertOnForcedConstraint, true}, + }; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + type Arg = { foo: string } + + type TypeA = { foo: string, method: (arg: Arg) -> any } + + type TypeB = TypeA & {} + + local bar: TypeB = { + foo = "wow!", + method = function(arg) + local _ = arg + return nil + end, + } + )")); + + CHECK_EQ("{ foo: string }", toString(requireTypeAtPosition({10, 28}), { /* exhaustive */ true})); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 54510452..f753ecdf 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -28,11 +28,9 @@ LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAG(LuauOccursCheckInCommit) LUAU_FASTFLAG(LuauEGFixGenericsList) -LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAG(LuauTryToOptimizeSetTypeUnification) LUAU_FASTFLAG(LuauDontReferenceScopePtrFromHashTable) @@ -671,7 +669,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ TEST_CASE_FIXTURE(BuiltinsFixture, "invalide_deprecated_attribute_doesn't_chrash_checker") { - ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true}; CheckResult result = check(R"( @[deprecated{ reason = reasonString }] function hello(x: number, y: number): number @@ -2531,10 +2528,7 @@ TEST_CASE_FIXTURE(Fixture, "non_standalone_constraint_solving_incomplete_is_hidd TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_missing_type_pack_follow") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}, - }; + ScopedFastFlag sffs{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_ERRORS(check(R"( local _ = {[0]=_,} diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp index 2e837f82..9ab1be0d 100644 --- a/tests/TypePath.test.cpp +++ b/tests/TypePath.test.cpp @@ -18,13 +18,9 @@ using namespace Luau::TypePath; LUAU_FASTFLAG(LuauSolverV2); LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps); -LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping3); -LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance2) - struct TypePathFixture : Fixture { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; TypeArena arena; const DenseHashMap emptyMap_DEPRECATED{nullptr}; }; @@ -32,7 +28,6 @@ struct TypePathFixture : Fixture struct TypePathBuiltinsFixture : BuiltinsFixture { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; - ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; TypeArena arena; const DenseHashMap emptyMap_DEPRECATED{nullptr}; }; @@ -131,7 +126,6 @@ TEST_CASE_FIXTURE(TypePathFixture, "table_property") TEST_CASE_FIXTURE(ExternTypeFixture, "class_property") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; // Force this here because vector2InstanceType won't get initialized until the frontend has been forced getFrontend(); TypeArena arena; @@ -231,8 +225,6 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance2, true}; - getFrontend(); TypeArena arena; @@ -470,8 +462,6 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail") TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - TypeArena& arena = getFrontend().globals.globalTypes; unfreeze(arena); @@ -487,8 +477,6 @@ TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail") TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_finite_pack") { - ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping3, true}; - TypeArena& arena = getFrontend().globals.globalTypes; unfreeze(arena); diff --git a/tests/main.cpp b/tests/main.cpp index e30d0ed0..f137ebfe 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -56,9 +56,6 @@ static bool skipFastFlag(const char* flagName) if (strncmp(flagName, "Debug", 5) == 0) return true; - if (strcmp(flagName, "StudioReportLuauAny2") == 0) - return true; - return false; } diff --git a/tests/require/config_tests/README.md b/tests/require/config_tests/README.md new file mode 100644 index 00000000..02689969 --- /dev/null +++ b/tests/require/config_tests/README.md @@ -0,0 +1 @@ +The `with_config` and `with_config_luau` directories should be identical, apart from the configuration file type they use. diff --git a/tests/require/config_tests/config_ambiguity/.config.luau b/tests/require/config_tests/config_ambiguity/.config.luau new file mode 100644 index 00000000..27af9236 --- /dev/null +++ b/tests/require/config_tests/config_ambiguity/.config.luau @@ -0,0 +1,7 @@ +return { + luau = { + aliases = { + dep = "./dependency" + } + } +} diff --git a/tests/require/config_tests/config_ambiguity/.luaurc b/tests/require/config_tests/config_ambiguity/.luaurc new file mode 100644 index 00000000..90fe77c2 --- /dev/null +++ b/tests/require/config_tests/config_ambiguity/.luaurc @@ -0,0 +1,5 @@ +{ + "aliases": { + "dep": "./dependency" + } +} diff --git a/tests/require/with_config/src/dependency.luau b/tests/require/config_tests/config_ambiguity/dependency.luau similarity index 100% rename from tests/require/with_config/src/dependency.luau rename to tests/require/config_tests/config_ambiguity/dependency.luau diff --git a/tests/require/with_config/src/alias_requirer.luau b/tests/require/config_tests/config_ambiguity/requirer.luau similarity index 100% rename from tests/require/with_config/src/alias_requirer.luau rename to tests/require/config_tests/config_ambiguity/requirer.luau diff --git a/tests/require/config_tests/config_cannot_be_required/.config.luau b/tests/require/config_tests/config_cannot_be_required/.config.luau new file mode 100644 index 00000000..a3cb3505 --- /dev/null +++ b/tests/require/config_tests/config_cannot_be_required/.config.luau @@ -0,0 +1 @@ +return {"result from .config.luau"} diff --git a/tests/require/config_tests/config_cannot_be_required/requirer.luau b/tests/require/config_tests/config_cannot_be_required/requirer.luau new file mode 100644 index 00000000..38c00431 --- /dev/null +++ b/tests/require/config_tests/config_cannot_be_required/requirer.luau @@ -0,0 +1 @@ +return require("./.config") diff --git a/tests/require/config_tests/config_luau_timeout/.config.luau b/tests/require/config_tests/config_luau_timeout/.config.luau new file mode 100644 index 00000000..9c128d7e --- /dev/null +++ b/tests/require/config_tests/config_luau_timeout/.config.luau @@ -0,0 +1,2 @@ +-- Infinite loop +while true do end diff --git a/tests/require/with_config/src/submodule/init.luau b/tests/require/config_tests/config_luau_timeout/requirer.luau similarity index 100% rename from tests/require/with_config/src/submodule/init.luau rename to tests/require/config_tests/config_luau_timeout/requirer.luau diff --git a/tests/require/with_config/.luaurc b/tests/require/config_tests/with_config/.luaurc similarity index 100% rename from tests/require/with_config/.luaurc rename to tests/require/config_tests/with_config/.luaurc diff --git a/tests/require/with_config/parent_ambiguity/.luaurc b/tests/require/config_tests/with_config/parent_ambiguity/.luaurc similarity index 100% rename from tests/require/with_config/parent_ambiguity/.luaurc rename to tests/require/config_tests/with_config/parent_ambiguity/.luaurc diff --git a/tests/require/with_config/parent_ambiguity/folder.luau b/tests/require/config_tests/with_config/parent_ambiguity/folder.luau similarity index 100% rename from tests/require/with_config/parent_ambiguity/folder.luau rename to tests/require/config_tests/with_config/parent_ambiguity/folder.luau diff --git a/tests/require/with_config/parent_ambiguity/folder/requirer.luau b/tests/require/config_tests/with_config/parent_ambiguity/folder/requirer.luau similarity index 100% rename from tests/require/with_config/parent_ambiguity/folder/requirer.luau rename to tests/require/config_tests/with_config/parent_ambiguity/folder/requirer.luau diff --git a/tests/require/with_config/parent_ambiguity/foo.luau b/tests/require/config_tests/with_config/parent_ambiguity/foo.luau similarity index 100% rename from tests/require/with_config/parent_ambiguity/foo.luau rename to tests/require/config_tests/with_config/parent_ambiguity/foo.luau diff --git a/tests/require/with_config/src/.luaurc b/tests/require/config_tests/with_config/src/.luaurc similarity index 100% rename from tests/require/with_config/src/.luaurc rename to tests/require/config_tests/with_config/src/.luaurc diff --git a/tests/require/config_tests/with_config/src/alias_requirer.luau b/tests/require/config_tests/with_config/src/alias_requirer.luau new file mode 100644 index 00000000..4375a783 --- /dev/null +++ b/tests/require/config_tests/with_config/src/alias_requirer.luau @@ -0,0 +1 @@ +return require("@dep") diff --git a/tests/require/config_tests/with_config/src/dependency.luau b/tests/require/config_tests/with_config/src/dependency.luau new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/require/config_tests/with_config/src/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/require/with_config/src/directory_alias_requirer.luau b/tests/require/config_tests/with_config/src/directory_alias_requirer.luau similarity index 100% rename from tests/require/with_config/src/directory_alias_requirer.luau rename to tests/require/config_tests/with_config/src/directory_alias_requirer.luau diff --git a/tests/require/with_config/src/other_dependency.luau b/tests/require/config_tests/with_config/src/other_dependency.luau similarity index 100% rename from tests/require/with_config/src/other_dependency.luau rename to tests/require/config_tests/with_config/src/other_dependency.luau diff --git a/tests/require/with_config/src/parent_alias_requirer.luau b/tests/require/config_tests/with_config/src/parent_alias_requirer.luau similarity index 100% rename from tests/require/with_config/src/parent_alias_requirer.luau rename to tests/require/config_tests/with_config/src/parent_alias_requirer.luau diff --git a/tests/require/with_config/src/subdirectory/subdirectory_dependency.luau b/tests/require/config_tests/with_config/src/subdirectory/subdirectory_dependency.luau similarity index 100% rename from tests/require/with_config/src/subdirectory/subdirectory_dependency.luau rename to tests/require/config_tests/with_config/src/subdirectory/subdirectory_dependency.luau diff --git a/tests/require/with_config/src/submodule/.luaurc b/tests/require/config_tests/with_config/src/submodule/.luaurc similarity index 100% rename from tests/require/with_config/src/submodule/.luaurc rename to tests/require/config_tests/with_config/src/submodule/.luaurc diff --git a/tests/require/config_tests/with_config/src/submodule/init.luau b/tests/require/config_tests/with_config/src/submodule/init.luau new file mode 100644 index 00000000..4375a783 --- /dev/null +++ b/tests/require/config_tests/with_config/src/submodule/init.luau @@ -0,0 +1 @@ +return require("@dep") diff --git a/tests/require/config_tests/with_config_luau/.config.luau b/tests/require/config_tests/with_config_luau/.config.luau new file mode 100644 index 00000000..b64979de --- /dev/null +++ b/tests/require/config_tests/with_config_luau/.config.luau @@ -0,0 +1,8 @@ +return { + luau = { + aliases = { + dep = "./this_should_be_overwritten_by_child_luaurc", + otherdep = "./src/other_dependency" + } + } +} diff --git a/tests/require/config_tests/with_config_luau/parent_ambiguity/.config.luau b/tests/require/config_tests/with_config_luau/parent_ambiguity/.config.luau new file mode 100644 index 00000000..bc1ec7f0 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/parent_ambiguity/.config.luau @@ -0,0 +1,7 @@ +return { + luau = { + aliases = { + foo = "./foo", + } + } +} diff --git a/tests/require/config_tests/with_config_luau/parent_ambiguity/folder.luau b/tests/require/config_tests/with_config_luau/parent_ambiguity/folder.luau new file mode 100644 index 00000000..e69de29b diff --git a/tests/require/config_tests/with_config_luau/parent_ambiguity/folder/requirer.luau b/tests/require/config_tests/with_config_luau/parent_ambiguity/folder/requirer.luau new file mode 100644 index 00000000..2f613057 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/parent_ambiguity/folder/requirer.luau @@ -0,0 +1 @@ +return require("@foo") diff --git a/tests/require/config_tests/with_config_luau/parent_ambiguity/foo.luau b/tests/require/config_tests/with_config_luau/parent_ambiguity/foo.luau new file mode 100644 index 00000000..aa00aca1 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/parent_ambiguity/foo.luau @@ -0,0 +1 @@ +return { "result from foo" } diff --git a/tests/require/config_tests/with_config_luau/src/.config.luau b/tests/require/config_tests/with_config_luau/src/.config.luau new file mode 100644 index 00000000..63d3bbc8 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/.config.luau @@ -0,0 +1,8 @@ +return { + luau = { + aliases = { + dep = "./dependency", + subdir = "./subdirectory" + } + } +} diff --git a/tests/require/config_tests/with_config_luau/src/alias_requirer.luau b/tests/require/config_tests/with_config_luau/src/alias_requirer.luau new file mode 100644 index 00000000..4375a783 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/alias_requirer.luau @@ -0,0 +1 @@ +return require("@dep") diff --git a/tests/require/config_tests/with_config_luau/src/dependency.luau b/tests/require/config_tests/with_config_luau/src/dependency.luau new file mode 100644 index 00000000..07466f42 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/dependency.luau @@ -0,0 +1 @@ +return {"result from dependency"} diff --git a/tests/require/config_tests/with_config_luau/src/directory_alias_requirer.luau b/tests/require/config_tests/with_config_luau/src/directory_alias_requirer.luau new file mode 100644 index 00000000..3b19d4ff --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/directory_alias_requirer.luau @@ -0,0 +1 @@ +return(require("@subdir/subdirectory_dependency")) diff --git a/tests/require/config_tests/with_config_luau/src/other_dependency.luau b/tests/require/config_tests/with_config_luau/src/other_dependency.luau new file mode 100644 index 00000000..8c582dc2 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/other_dependency.luau @@ -0,0 +1 @@ +return {"result from other_dependency"} diff --git a/tests/require/config_tests/with_config_luau/src/parent_alias_requirer.luau b/tests/require/config_tests/with_config_luau/src/parent_alias_requirer.luau new file mode 100644 index 00000000..a8e8de09 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/parent_alias_requirer.luau @@ -0,0 +1 @@ +return require("@otherdep") diff --git a/tests/require/config_tests/with_config_luau/src/subdirectory/subdirectory_dependency.luau b/tests/require/config_tests/with_config_luau/src/subdirectory/subdirectory_dependency.luau new file mode 100644 index 00000000..8bbd0beb --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/subdirectory/subdirectory_dependency.luau @@ -0,0 +1 @@ +return {"result from subdirectory_dependency"} diff --git a/tests/require/config_tests/with_config_luau/src/submodule/.config.luau b/tests/require/config_tests/with_config_luau/src/submodule/.config.luau new file mode 100644 index 00000000..eb309fd1 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/submodule/.config.luau @@ -0,0 +1,7 @@ +return { + luau = { + aliases = { + dep = "./this_should_not_be_read_by_init_luau", + } + } +} diff --git a/tests/require/config_tests/with_config_luau/src/submodule/init.luau b/tests/require/config_tests/with_config_luau/src/submodule/init.luau new file mode 100644 index 00000000..4375a783 --- /dev/null +++ b/tests/require/config_tests/with_config_luau/src/submodule/init.luau @@ -0,0 +1 @@ +return require("@dep")