From 55f3e0093843c2d3745c5041d2093172f0959bc1 Mon Sep 17 00:00:00 2001 From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com> Date: Fri, 15 Aug 2025 11:48:43 -0700 Subject: [PATCH 1/8] Sync to upstream/release/687 (#1954) ## What's Changed? This week we have an update with an implementation for one of the RFCs we had approved before, an improvement of the new type solver and a small Lua 5.1 C API compatibility improvement. * `@deprecated` attribute can now have a custom suggestion for a replacement and a reason message as described in [deprecated attribute parameters RFC](https://rfcs.luau.org/syntax-attribute-functions-deprecated.html) For example: ```luau @[deprecated {reason = "foo suffers from performance issues", use = "bar"}] local function foo() ... end -- Function 'foo' is deprecated, use 'bar' instead. foo suffers from performance issues foo() ``` * `lua_cpcall` C API function has been restored both for compatibility with Lua 5.1 and as a safe way to enter protected call environment to work with Luau C API functions that may error Instead of ``` if (!lua_checkstack(L, 2)) return -1; lua_pushcfunction(L, test, nullptr); lua_pushlightuserdata(L, context); int status = lua_pcall(L, 1, 0, 0); ``` you can simply do ``` int status = lua_cpcall(L, test, context); ``` * In Luau CLI, required module return values can now have any type ## New Type Solver - Additional improvements on type refinements used with external types should fix some reported false positive errors where types refined to `never` - Fixed an issue in recursive refinement types in a form of `t1 where t1 = refine` getting 'stuck' - Fixed an issue in subtyping of generic functions, it is now possible to assign `(T, (T) -> T) -> T` to `(number, (X) -> X) -> number` - Fixed an ICE caused by recursive types (Fixes #1686) - Added additional iteration and recursion limits to stop the type solver before system resources are used up ## 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: Sora Kanosue Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/Constraint.h | 1 + Analysis/include/Luau/Error.h | 12 +- Analysis/include/Luau/Frontend.h | 4 +- Analysis/include/Luau/Scope.h | 4 + Analysis/include/Luau/Substitution.h | 16 + Analysis/include/Luau/Subtyping.h | 54 +- Analysis/include/Luau/SubtypingVariance.h | 4 +- Analysis/include/Luau/Transpiler.h | 2 + Analysis/include/Luau/Type.h | 1 + Analysis/include/Luau/TypeIds.h | 2 + Analysis/include/Luau/TypeUtils.h | 34 + Analysis/include/Luau/Unifier2.h | 66 +- Analysis/include/Luau/VisitType.h | 34 +- Analysis/src/AutocompleteCore.cpp | 2 +- Analysis/src/BuiltinDefinitions.cpp | 21 +- Analysis/src/BuiltinTypeFunctions.cpp | 467 +++++--------- Analysis/src/ConstraintGenerator.cpp | 314 +++++---- Analysis/src/ConstraintSolver.cpp | 201 +++--- Analysis/src/DataFlowGraph.cpp | 25 +- Analysis/src/Error.cpp | 9 +- Analysis/src/FileResolver.cpp | 1 - Analysis/src/Frontend.cpp | 60 +- Analysis/src/Generalization.cpp | 103 ++- Analysis/src/InferPolarity.cpp | 1 - Analysis/src/Instantiation.cpp | 6 + Analysis/src/IostreamHelpers.cpp | 2 + Analysis/src/Linter.cpp | 79 ++- Analysis/src/Module.cpp | 12 +- Analysis/src/NonStrictTypeChecker.cpp | 8 +- Analysis/src/Normalize.cpp | 12 +- Analysis/src/OverloadResolution.cpp | 41 +- Analysis/src/Scope.cpp | 27 + Analysis/src/Simplify.cpp | 244 +++++-- Analysis/src/Substitution.cpp | 27 +- Analysis/src/Subtyping.cpp | 600 +++++++++++++++--- Analysis/src/TableLiteralInference.cpp | 20 +- Analysis/src/ToString.cpp | 5 +- Analysis/src/Type.cpp | 1 - Analysis/src/TypeChecker2.cpp | 85 ++- Analysis/src/TypeFunction.cpp | 5 - Analysis/src/TypeFunctionReductionGuesser.cpp | 9 +- Analysis/src/TypeFunctionRuntimeBuilder.cpp | 39 +- Analysis/src/TypeIds.cpp | 6 + Analysis/src/TypeInfer.cpp | 38 +- Analysis/src/TypePath.cpp | 71 ++- Analysis/src/TypeUtils.cpp | 146 ++++- Analysis/src/Unifier2.cpp | 289 +++++---- Ast/include/Luau/Ast.h | 19 +- Ast/include/Luau/Lexer.h | 1 + Ast/include/Luau/Parser.h | 9 +- Ast/src/Ast.cpp | 109 +++- Ast/src/Lexer.cpp | 37 +- Ast/src/Parser.cpp | 251 +++++++- CLI/src/Reduce.cpp | 5 +- CLI/src/ReplRequirer.cpp | 2 - CodeGen/include/Luau/OptimizeConstProp.h | 4 +- CodeGen/src/CodeAllocator.cpp | 32 +- CodeGen/src/CodeGen.cpp | 1 - CodeGen/src/CodeGenLower.h | 41 +- CodeGen/src/OptimizeConstProp.cpp | 21 +- Compiler/src/Compiler.cpp | 7 +- VM/include/lua.h | 1 + VM/src/lapi.cpp | 39 +- tests/Conformance.test.cpp | 119 +++- tests/Fixture.h | 9 +- tests/FragmentAutocomplete.test.cpp | 24 +- tests/Frontend.test.cpp | 4 +- tests/Generalization.test.cpp | 10 +- tests/InferPolarity.test.cpp | 7 +- tests/IrBuilder.test.cpp | 162 ++--- tests/Linter.test.cpp | 186 ++++++ tests/NonstrictMode.test.cpp | 3 + tests/Normalize.test.cpp | 2 - tests/Parser.test.cpp | 113 ++++ tests/RuntimeLimits.test.cpp | 184 +++++- tests/Subtyping.test.cpp | 103 ++- tests/TypeFunction.test.cpp | 96 ++- tests/TypeFunction.user.test.cpp | 2 + tests/TypeInfer.builtins.test.cpp | 20 +- tests/TypeInfer.classes.test.cpp | 2 +- tests/TypeInfer.definitions.test.cpp | 6 +- tests/TypeInfer.functions.test.cpp | 37 +- tests/TypeInfer.generics.test.cpp | 33 +- tests/TypeInfer.intersectionTypes.test.cpp | 6 +- tests/TypeInfer.loops.test.cpp | 3 - tests/TypeInfer.modules.test.cpp | 74 ++- tests/TypeInfer.negations.test.cpp | 4 +- tests/TypeInfer.oop.test.cpp | 3 + tests/TypeInfer.operators.test.cpp | 7 +- tests/TypeInfer.provisional.test.cpp | 23 + tests/TypeInfer.refinements.test.cpp | 105 ++- tests/TypeInfer.tables.test.cpp | 88 ++- tests/TypeInfer.test.cpp | 36 +- tests/TypeInfer.typestates.test.cpp | 17 +- tests/TypeInfer.unknownnever.test.cpp | 5 +- tests/Unifier2.test.cpp | 6 +- tests/conformance/native.luau | 16 +- tests/conformance/native_types.luau | 2 +- tests/conformance/vector.luau | 162 ++--- 99 files changed, 4041 insertions(+), 1427 deletions(-) diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 0d5ad81b..a556736b 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -52,6 +52,7 @@ struct GeneralizationConstraint std::vector interiorTypes; bool hasDeprecatedAttribute = false; + AstAttr::DeprecatedInfo deprecatedInfo; /// If true, never introduce generics. Always replace free types by their /// bounds or unknown. Presently used only to generalize the whole module. diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index dbf4c9f5..a8e1c76f 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -504,6 +504,15 @@ struct MultipleNonviableOverloads bool operator==(const MultipleNonviableOverloads& rhs) const; }; +// Error where a type alias violates the recursive restraint, ie when a type alias T has T with different arguments on the RHS. +struct RecursiveRestraintViolation +{ + bool operator==(const RecursiveRestraintViolation& rhs) const + { + return true; + } +}; + using TypeErrorData = Variant< TypeMismatch, UnknownSymbol, @@ -559,7 +568,8 @@ using TypeErrorData = Variant< CannotCheckDynamicStringFormatCalls, GenericTypeCountMismatch, GenericTypePackCountMismatch, - MultipleNonviableOverloads>; + MultipleNonviableOverloads, + RecursiveRestraintViolation>; struct TypeErrorSummary { diff --git a/Analysis/include/Luau/Frontend.h b/Analysis/include/Luau/Frontend.h index 4887583f..0ec7f065 100644 --- a/Analysis/include/Luau/Frontend.h +++ b/Analysis/include/Luau/Frontend.h @@ -176,7 +176,7 @@ struct Frontend Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, const FrontendOptions& options = {}); - void setLuauSolverSelectionFromWorkspace(SolverMode mode); + void setLuauSolverMode(SolverMode mode); SolverMode getLuauSolverMode() const; // The default value assuming there is no workspace setup yet std::atomic useNewLuauSolver{FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old}; @@ -330,7 +330,7 @@ ModulePtr check( NotNull iceHandler, NotNull moduleResolver, NotNull fileResolver, - const ScopePtr& globalScope, + const ScopePtr& parentScope, const ScopePtr& typeFunctionScope, std::function prepareModuleScope, FrontendOptions options, diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index 7dc8bd85..80ff0ddf 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -102,6 +102,10 @@ struct Scope std::optional> interiorFreeTypes; std::optional> interiorFreeTypePacks; + // A set of type alias names that are invalid because they violate the recursion restrictions of type aliases. + DenseHashSet invalidTypeAliasNames{""}; + bool isInvalidTypeAliasName(const std::string& name) const; + NotNull findNarrowestScopeContaining(Location); }; diff --git a/Analysis/include/Luau/Substitution.h b/Analysis/include/Luau/Substitution.h index c67f37e2..65215c10 100644 --- a/Analysis/include/Luau/Substitution.h +++ b/Analysis/include/Luau/Substitution.h @@ -66,6 +66,13 @@ struct TarjanWorklistVertex int index; int currEdge; int lastEdge; + + TarjanWorklistVertex(int index, int currEdge, int lastEdge) + : index(index) + , currEdge(currEdge) + , lastEdge(lastEdge) + { + } }; struct TarjanNode @@ -79,6 +86,15 @@ struct TarjanNode // Tarjan calculates the lowlink for each vertex, // which is the lowest ancestor index reachable from the vertex. int lowlink; + + TarjanNode(TypeId ty, TypePackId tp, bool onStack, bool dirty, int lowlink) + : ty(ty) + , tp(tp) + , onStack(onStack) + , dirty(dirty) + , lowlink(lowlink) + { + } }; // Tarjan's algorithm for finding the SCCs in a cyclic structure. diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 90cbd7e3..648db98c 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -88,6 +88,13 @@ struct SubtypingResult struct SubtypingEnvironment { struct GenericBounds + { + TypeIds lowerBound; + TypeIds upperBound; + }; + + // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance + struct GenericBounds_DEPRECATED { DenseHashSet lowerBound{nullptr}; DenseHashSet upperBound{nullptr}; @@ -98,22 +105,36 @@ struct SubtypingEnvironment /// Applies `mappedGenerics` to the given type. /// This is used specifically to substitute for generics in type function instances. - std::optional applyMappedGenerics(NotNull builtinTypes, NotNull arena, TypeId ty); + std::optional applyMappedGenerics( + NotNull builtinTypes, + NotNull arena, + TypeId ty, + NotNull iceReporter + ); + // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance + std::optional applyMappedGenerics_DEPRECATED(NotNull builtinTypes, NotNull arena, TypeId ty); const TypeId* 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); + GenericBounds& getMappedTypeBounds(TypeId ty, NotNull iceReporter); + // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance + GenericBounds_DEPRECATED& getMappedTypeBounds_DEPRECATED(TypeId ty); TypePackId* getMappedPackBounds(TypePackId tp); /* * When we encounter a generic over the course of a subtyping test, we need - * to tentatively map that generic onto a type on the other side. + * to tentatively map that generic onto a type on the other side. We map to a + * vector of bounds, since generics may be shadowed by nested types. The back + * of each vector represents the current scope. */ - DenseHashMap mappedGenerics{nullptr}; + DenseHashMap> mappedGenerics{nullptr}; + // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance + DenseHashMap mappedGenerics_DEPRECATED{nullptr}; DenseHashMap mappedGenericPacks{nullptr}; /* @@ -124,7 +145,14 @@ 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 + // led to the seen pair. However, those results were previously being cache in the ephemeralCache, and we still want to cache them somewhere + // for performance reasons. + DenseHashMap, SubtypingResult, TypePairHash> seenSetCache{{}}; }; struct Subtyping @@ -144,6 +172,7 @@ struct Subtyping Contravariant }; + // TODO: Clip this along with LuauSubtypingGenericsDoesntUseVariance? Variance variance = Variance::Covariant; using SeenSet = Set, TypePairHash>; @@ -178,7 +207,12 @@ struct Subtyping // TODO recursion limits SubtypingResult isSubtype(TypeId subTy, TypeId superTy, NotNull scope); - SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy, NotNull scope); + SubtypingResult isSubtype( + TypePackId subTp, + TypePackId superTp, + NotNull scope, + std::optional> bindableGenerics = std::nullopt + ); private: DenseHashMap, SubtypingResult, TypePairHash> resultCache{{}}; @@ -323,6 +357,16 @@ struct Subtyping TypeId superTy, NotNull scope, SubtypingResult& original); + + SubtypingResult checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull scope); + + static void maybeUpdateBounds( + TypeId here, + TypeId there, + TypeIds& boundsToUpdate, + const TypeIds& firstBoundsToCheck, + const TypeIds& secondBoundsToCheck + ); }; } // namespace Luau diff --git a/Analysis/include/Luau/SubtypingVariance.h b/Analysis/include/Luau/SubtypingVariance.h index 4fd7cd4b..36f54d7a 100644 --- a/Analysis/include/Luau/SubtypingVariance.h +++ b/Analysis/include/Luau/SubtypingVariance.h @@ -7,11 +7,9 @@ namespace Luau enum class SubtypingVariance { - // Used for an empty key. Should never appear in actual code. + // Useful for an empty hash table key. Should never arise from actual code. Invalid, Covariant, - // This is used to identify cases where we have a covariant + a - // contravariant reason and we need to merge them. Contravariant, Invariant, }; diff --git a/Analysis/include/Luau/Transpiler.h b/Analysis/include/Luau/Transpiler.h index df01008c..b8553a70 100644 --- a/Analysis/include/Luau/Transpiler.h +++ b/Analysis/include/Luau/Transpiler.h @@ -3,6 +3,7 @@ #include "Luau/Location.h" #include "Luau/ParseOptions.h" +#include "Luau/ParseResult.h" #include @@ -24,6 +25,7 @@ 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); // Only fails when parsing fails TranspileResult transpile(std::string_view source, ParseOptions options = ParseOptions{}, bool withTypes = false); diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 24741061..320d87af 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -391,6 +391,7 @@ struct FunctionType bool hasNoFreeOrGenericTypes = false; bool isCheckedFunction = false; bool isDeprecatedFunction = false; + std::shared_ptr deprecatedInfo; }; enum class TableState diff --git a/Analysis/include/Luau/TypeIds.h b/Analysis/include/Luau/TypeIds.h index 88bfbccb..87602d6f 100644 --- a/Analysis/include/Luau/TypeIds.h +++ b/Analysis/include/Luau/TypeIds.h @@ -52,6 +52,8 @@ class TypeIds bool empty() const; size_t count(TypeId ty) const; + void reserve(size_t n); + template void insert(Iterator begin, Iterator end) { diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 26efef1a..9125d629 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -4,6 +4,7 @@ #include "Luau/Error.h" #include "Luau/Location.h" #include "Luau/Type.h" +#include "Luau/TypeIds.h" #include "Luau/TypePack.h" #include @@ -363,5 +364,38 @@ inline constexpr char kLuauForceConstraintSolvingIncomplete[] = "_luau_force_con // not get emplaced by constraint solving. inline constexpr char kLuauBlockedType[] = "_luau_blocked_type"; +struct UnionBuilder +{ + UnionBuilder(NotNull arena, NotNull builtinTypes); + void add(TypeId ty); + TypeId build(); + size_t size() const; + void reserve(size_t size); + +private: + NotNull arena; + NotNull builtinTypes; + TypeIds options; + bool isTop = false; +}; + +struct IntersectionBuilder +{ + IntersectionBuilder(NotNull arena, NotNull builtinTypes); + void add(TypeId ty); + TypeId build(); + size_t size() const; + void reserve(size_t size); + +private: + NotNull arena; + NotNull builtinTypes; + TypeIds parts; + bool isBottom = false; +}; + +TypeId addIntersection(NotNull arena, NotNull builtinTypes, std::initializer_list list); +TypeId addUnion(NotNull arena, NotNull builtinTypes, std::initializer_list list); + } // namespace Luau diff --git a/Analysis/include/Luau/Unifier2.h b/Analysis/include/Luau/Unifier2.h index 6838b67c..4c02f9bc 100644 --- a/Analysis/include/Luau/Unifier2.h +++ b/Analysis/include/Luau/Unifier2.h @@ -26,6 +26,27 @@ enum class OccursCheckResult Fail }; +enum class UnifyResult +{ + Ok, + OccursCheckFailed, + TooComplex +}; + +inline UnifyResult operator &(UnifyResult lhs, UnifyResult rhs) +{ + if (lhs == UnifyResult::Ok) + return rhs; + return lhs; +} + +inline UnifyResult& operator&=(UnifyResult& lhs, UnifyResult rhs) +{ + if (lhs == UnifyResult::Ok) + lhs = rhs; + return lhs; +} + struct Unifier2 { NotNull arena; @@ -50,6 +71,7 @@ struct Unifier2 std::vector newFreshTypes; std::vector newFreshTypePacks; + int iterationCount = 0; int recursionCount = 0; int recursionLimit = 0; @@ -66,6 +88,10 @@ struct Unifier2 DenseHashSet* uninhabitedTypeFunctions ); + UnifyResult unify(TypeId subTy, TypeId superTy); + UnifyResult unify(TypePackId subTp, TypePackId superTp); + +private: /** Attempt to commit the subtype relation subTy <: superTy to the type * graph. * @@ -78,30 +104,28 @@ struct Unifier2 * Presently, the only way unification can fail is if we attempt to bind one * free TypePack to another and encounter an occurs check violation. */ - bool unify(TypeId subTy, TypeId superTy); - bool unifyFreeWithType(TypeId subTy, TypeId superTy); - bool unify(TypeId subTy, const FunctionType* superFn); - bool unify(const UnionType* subUnion, TypeId superTy); - bool unify(TypeId subTy, const UnionType* superUnion); - bool unify(const IntersectionType* subIntersection, TypeId superTy); - bool unify(TypeId subTy, const IntersectionType* superIntersection); - bool unify(TableType* subTable, const TableType* superTable); - bool unify(const MetatableType* subMetatable, const MetatableType* superMetatable); - - bool unify(const AnyType* subAny, const FunctionType* superFn); - bool unify(const FunctionType* subFn, const AnyType* superAny); - bool unify(const AnyType* subAny, const TableType* superTable); - bool unify(const TableType* subTable, const AnyType* superAny); - - bool unify(const MetatableType* subMetatable, const AnyType*); - bool unify(const AnyType*, const MetatableType* superMetatable); - - // TODO think about this one carefully. We don't do unions or intersections of type packs - bool unify(TypePackId subTp, TypePackId superTp); + UnifyResult unify_(TypeId subTy, TypeId superTy); + UnifyResult unifyFreeWithType(TypeId subTy, TypeId superTy); + UnifyResult unify_(TypeId subTy, const FunctionType* superFn); + UnifyResult unify_(const UnionType* subUnion, TypeId superTy); + UnifyResult unify_(TypeId subTy, const UnionType* superUnion); + UnifyResult unify_(const IntersectionType* subIntersection, TypeId superTy); + UnifyResult unify_(TypeId subTy, const IntersectionType* superIntersection); + UnifyResult unify_(TableType* subTable, const TableType* superTable); + UnifyResult unify_(const MetatableType* subMetatable, const MetatableType* superMetatable); + + UnifyResult unify_(const AnyType* subAny, const FunctionType* superFn); + UnifyResult unify_(const FunctionType* subFn, const AnyType* superAny); + UnifyResult unify_(const AnyType* subAny, const TableType* superTable); + UnifyResult unify_(const TableType* subTable, const AnyType* superAny); + + UnifyResult unify_(const MetatableType* subMetatable, const AnyType*); + UnifyResult unify_(const AnyType*, const MetatableType* superMetatable); + + UnifyResult unify_(TypePackId subTp, TypePackId superTp); std::optional generalize(TypeId ty); -private: /** * @returns simplify(left | right) */ diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index 5e0a98d7..6c03fb29 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -12,6 +12,7 @@ LUAU_FASTINT(LuauVisitRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverAgnosticVisitType) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) namespace Luau { @@ -218,6 +219,20 @@ struct GenericTypeVisitor void traverse(TypeId ty) { + // Morally, if `skipBoundTypes` is set, then whenever we + // encounter a bound type we should "skip" ahead to the first + // non-bound type. This helps keep stack pressure in check + // while using bound types instead of mutating types in place + // elsewhere (such as in generalization). + // + // We do this check here such that we now will now treat all + // bound types as if they're direct pointers to some final + // non-bound type. If we do the check later, then we might + // get slightly different behavior depending on the exact + // entry point for cyclic types. + if (FFlag::LuauReduceSetTypeStackPressure && is(ty) && skipBoundTypes) + ty = follow(ty); + RecursionLimiter limiter{visitorName, &recursionCounter, FInt::LuauVisitRecursionLimit}; if (visit_detail::hasSeen(seen, ty)) @@ -228,10 +243,21 @@ struct GenericTypeVisitor if (auto btv = get(ty)) { - if (skipBoundTypes) - traverse(btv->boundTo); - else if (visit(ty, *btv)) - traverse(btv->boundTo); + if (FFlag::LuauReduceSetTypeStackPressure) + { + // At this point, we know that `skipBoundTypes` is false, as + // otherwise we would have hit the above branch. + LUAU_ASSERT(!skipBoundTypes); + if (visit(ty, *btv)) + traverse(btv->boundTo); + } + else + { + if (skipBoundTypes) + traverse(btv->boundTo); + else if (visit(ty, *btv)) + traverse(btv->boundTo); + } } else if (auto ftv = get(ty)) { diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index f04996ad..66908920 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -1331,7 +1331,7 @@ static bool autocompleteIfElseExpression( if (node->is()) { // Don't try to complete when the current node is an if-else expression (i.e. only try to complete when the node is a child of an if-else - // expression. + // expression). return true; } diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index fbf1237a..15c7a528 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -35,8 +35,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature) -LUAU_FASTFLAGVARIABLE(LuauUpdateGetMetatableTypeSignature) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) namespace Luau { @@ -193,18 +193,29 @@ TypeId makeFunction( FunctionType ftv{generics, genericPacks, paramPack, retPack, {}, selfType.has_value()}; if (selfType) - ftv.argNames.push_back(Luau::FunctionArgument{"self", {}}); + { + if (FFlag::LuauEmplaceNotPushBack) + ftv.argNames.emplace_back(Luau::FunctionArgument{"self", {}}); + else + ftv.argNames.push_back(Luau::FunctionArgument{"self", {}}); + } if (paramNames.size() != 0) { for (auto&& p : paramNames) - ftv.argNames.push_back(Luau::FunctionArgument{std::move(p), {}}); + if (FFlag::LuauEmplaceNotPushBack) + ftv.argNames.emplace_back(Luau::FunctionArgument{p, Location{}}); + else + ftv.argNames.push_back(Luau::FunctionArgument{std::move(p), {}}); } else if (selfType) { // If argument names were not provided, but we have already added a name for 'self' argument, we have to fill remaining slots as well for (size_t i = 0; i < paramTypes.size(); i++) - ftv.argNames.push_back(std::nullopt); + if (FFlag::LuauEmplaceNotPushBack) + ftv.argNames.emplace_back(std::nullopt); + else + ftv.argNames.push_back(std::nullopt); } ftv.isCheckedFunction = checked; @@ -384,7 +395,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeId genericT = arena.addType(GenericType{globalScope, "T"}); - if ((frontend.getLuauSolverMode() == SolverMode::New) && FFlag::LuauUpdateGetMetatableTypeSignature) + if (frontend.getLuauSolverMode() == SolverMode::New) { // getmetatable : (T) -> getmetatable TypeId getmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().getmetatableFunc, {genericT}}); diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index 008ae018..e7bba4de 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -17,17 +17,16 @@ #include "Luau/UserDefinedTypeFunction.h" #include "Luau/VisitType.h" -LUAU_FASTFLAG(LuauEmptyStringInKeyOf) -LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) -LUAU_FASTFLAG(LuauRefineTablesWithReadType) -LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) -LUAU_FASTFLAG(LuauOccursCheckForRefinement) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(LuauDoNotBlockOnStuckTypeFunctions) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) +LUAU_DYNAMIC_FASTINTVARIABLE(LuauStepRefineRecursionLimit, 64) +LUAU_FASTFLAGVARIABLE(LuauRefineOccursCheckDirectRecursion) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways) +LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions) namespace Luau { @@ -231,7 +230,7 @@ TypeFunctionReductionResult lenTypeFunction( TypePackId inferredArgPack = ctx->arena->addTypePack({operandTy}); Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice}; - if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) + if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; @@ -314,7 +313,7 @@ TypeFunctionReductionResult unmTypeFunction( TypePackId inferredArgPack = ctx->arena->addTypePack({operandTy}); Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice}; - if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) + if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; @@ -670,7 +669,7 @@ TypeFunctionReductionResult concatTypeFunction( TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs)); Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice}; - if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) + if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; @@ -919,7 +918,7 @@ static TypeFunctionReductionResult comparisonTypeFunction( TypePackId inferredArgPack = ctx->arena->addTypePack({lhsTy, rhsTy}); Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice}; - if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) + if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; @@ -1048,7 +1047,7 @@ TypeFunctionReductionResult eqTypeFunction( TypePackId inferredArgPack = ctx->arena->addTypePack({lhsTy, rhsTy}); Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice}; - if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) + if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) return {std::nullopt, Reduction::Erroneous, {}, {}}; // occurs check failed Subtyping subtyping{ctx->builtins, ctx->arena, ctx->simplifier, ctx->normalizer, ctx->typeFunctionRuntime, ctx->ice}; @@ -1094,7 +1093,6 @@ struct ContainsRefinableType : TypeOnceVisitor { } - bool visit(TypeId ty) override { // Default case: if we find *some* type that's worth refining against, @@ -1189,7 +1187,7 @@ struct RefineTypeScrubber : public Substitution return true; } } - return false; + return FFlag::LuauRefineOccursCheckDirectRecursion ? ty == needle : false; } bool ignoreChildren(TypeId ty) override @@ -1231,7 +1229,10 @@ struct RefineTypeScrubber : public Substitution else return ctx->arena->addType(IntersectionType{newParts.take()}); } - return ty; + else if (FFlag::LuauRefineOccursCheckDirectRecursion && ty == needle) + return ctx->builtins->unknownType; + else + return ty; } }; @@ -1285,25 +1286,22 @@ TypeFunctionReductionResult refineTypeFunction( TypeId targetTy = follow(typeParams.at(0)); - if (FFlag::LuauOccursCheckForRefinement) + // If we end up minting a refine type like: + // + // t1 where t1 = refine + // + // This can create a degenerate set type such as: + // + // t1 where t1 = (T | t1) & Y + // + // Instead, we can clip the recursive part: + // + // t1 where t1 = refine => refine + if (occurs(targetTy, instance)) { - // If we end up minting a refine type like: - // - // t1 where t1 = refine - // - // This can create a degenerate set type such as: - // - // t1 where t1 = (T | t1) & Y - // - // Instead, we can clip the recursive part: - // - // t1 where t1 = refine => refine - if (!FFlag::LuauAvoidExcessiveTypeCopying || occurs(targetTy, instance)) - { - RefineTypeScrubber rts{ctx, instance}; - if (auto result = rts.substitute(targetTy)) - targetTy = *result; - } + RefineTypeScrubber rts{ctx, instance}; + if (auto result = rts.substitute(targetTy)) + targetTy = *result; } std::vector discriminantTypes; @@ -1363,10 +1361,16 @@ TypeFunctionReductionResult refineTypeFunction( if (!frb.found.empty()) return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}}; + int stepRefineCount = 0; + // Refine a target type and a discriminant one at a time. // Returns result : TypeId, toBlockOn : vector - auto stepRefine = [&ctx](TypeId target, TypeId discriminant) -> std::pair> + auto stepRefine = [&stepRefineCount, &ctx](TypeId target, TypeId discriminant) -> std::pair> { + std::optional rl; + if (FFlag::LuauRefineDistributesOverUnions) + rl.emplace("BuiltInTypeFunctions::stepRefine", &stepRefineCount, DFInt::LuauStepRefineRecursionLimit); + std::vector toBlock; // we need a more complex check for blocking on the discriminant in particular FindRefinementBlockers frb; @@ -1406,11 +1410,8 @@ TypeFunctionReductionResult refineTypeFunction( return {target, {}}; } - if (FFlag::LuauRefineTablesWithReadType) - { - if (auto ty = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, target, discriminant)) - return {*ty, {}}; - } + if (auto ty = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, target, discriminant)) + return {*ty, {}}; // NOTE: This block causes us to refine too early in some cases. if (auto negation = get(discriminant)) @@ -1470,7 +1471,12 @@ TypeFunctionReductionResult refineTypeFunction( TypeId resultTy = ctx->normalizer->typeFromNormal(*normIntersection); // include the error type if the target type is error-suppressing and the intersection we computed is not if (normType->shouldSuppressErrors() && !normIntersection->shouldSuppressErrors()) - resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}}); + { + if (FFlag::LuauReduceSetTypeStackPressure) + resultTy = addUnion(ctx->arena, ctx->builtins, {resultTy, ctx->builtins->errorType}); + else + resultTy = ctx->arena->addType(UnionType{{resultTy, ctx->builtins->errorType}}); + } return {resultTy, {}}; } @@ -1483,6 +1489,59 @@ TypeFunctionReductionResult refineTypeFunction( while (!discriminantTypes.empty()) { TypeId discriminant = discriminantTypes.back(); + + if (FFlag::LuauRefineDistributesOverUnions) + { + discriminant = follow(discriminant); + + // first, we'll see if simplifying the discriminant alone will solve our problem... + if (auto ut = get(discriminant)) + { + TypeId workingType = ctx->builtins->neverType; + + for (auto optionAsDiscriminant : ut->options) + { + SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, workingType, optionAsDiscriminant); + + if (!simplified.blockedTypes.empty()) + return {std::nullopt, Reduction::MaybeOk, {simplified.blockedTypes.begin(), simplified.blockedTypes.end()}, {}}; + + workingType = simplified.result; + } + + discriminant = workingType; + } + + // if not, we try distributivity: a & (b | c) <=> (a & b) | (a & c) + if (auto ut = get(discriminant)) + { + TypeId finalRefined = ctx->builtins->neverType; + + for (auto optionAsDiscriminant : ut->options) + { + auto [refined, blocked] = stepRefine(target, follow(optionAsDiscriminant)); + + if (blocked.empty() && refined == nullptr) + return {std::nullopt, Reduction::MaybeOk, {}, {}}; + + if (!blocked.empty()) + return {std::nullopt, Reduction::MaybeOk, blocked, {}}; + + SimplifyResult simplified = simplifyUnion(ctx->builtins, ctx->arena, finalRefined, refined); + + if (!simplified.blockedTypes.empty()) + return {std::nullopt, Reduction::MaybeOk, {simplified.blockedTypes.begin(), simplified.blockedTypes.end()}, {}}; + + finalRefined = simplified.result; + } + + target = finalRefined; + discriminantTypes.pop_back(); + + continue; + } + } + auto [refined, blocked] = stepRefine(target, discriminant); if (blocked.empty() && refined == nullptr) @@ -1672,16 +1731,13 @@ TypeFunctionReductionResult intersectTypeFunction( if (get(ty)) continue; - if (FFlag::LuauRefineTablesWithReadType) + if (auto simpleResult = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, resultTy, ty)) { - if (auto simpleResult = intersectWithSimpleDiscriminant(ctx->builtins, ctx->arena, resultTy, ty)) - { - if (get(*simpleResult)) - unintersectableTypes.insert(follow(ty)); - else - resultTy = *simpleResult; - continue; - } + if (get(*simpleResult)) + unintersectableTypes.insert(follow(ty)); + else + resultTy = *simpleResult; + continue; } SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, resultTy, ty); @@ -1728,84 +1784,6 @@ TypeFunctionReductionResult intersectTypeFunction( return {resultTy, Reduction::MaybeOk, {}, {}}; } -// computes the keys of `ty` into `result` -// `isRaw` parameter indicates whether or not we should follow __index metamethods -// returns `false` if `result` should be ignored because the answer is "all strings" -bool computeKeysOf_DEPRECATED(TypeId ty, Set& result, DenseHashSet& seen, bool isRaw, NotNull ctx) -{ - // if the type is the top table type, the answer is just "all strings" - if (get(ty)) - return false; - - // if we've already seen this type, we can do nothing - if (seen.contains(ty)) - return true; - seen.insert(ty); - - // if we have a particular table type, we can insert the keys - if (auto tableTy = get(ty)) - { - if (tableTy->indexer) - { - // if we have a string indexer, the answer is, again, "all strings" - if (isString(tableTy->indexer->indexType)) - return false; - } - - for (auto [key, _] : tableTy->props) - result.insert(key); - return true; - } - - // otherwise, we have a metatable to deal with - if (auto metatableTy = get(ty)) - { - bool res = true; - - if (!isRaw) - { - // findMetatableEntry demands the ability to emit errors, so we must give it - // the necessary state to do that, even if we intend to just eat the errors. - ErrorVec dummy; - - std::optional mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{}); - if (mmType) - res = res && computeKeysOf_DEPRECATED(*mmType, result, seen, isRaw, ctx); - } - - res = res && computeKeysOf_DEPRECATED(metatableTy->table, result, seen, isRaw, ctx); - - return res; - } - - if (auto classTy = get(ty)) - { - for (auto [key, _] : classTy->props) // NOLINT(performance-for-range-copy) - result.insert(key); - - bool res = true; - if (classTy->metatable && !isRaw) - { - // findMetatableEntry demands the ability to emit errors, so we must give it - // the necessary state to do that, even if we intend to just eat the errors. - ErrorVec dummy; - - std::optional mmType = findMetatableEntry(ctx->builtins, dummy, ty, "__index", Location{}); - if (mmType) - res = res && computeKeysOf_DEPRECATED(*mmType, result, seen, isRaw, ctx); - } - - if (classTy->parent) - res = res && computeKeysOf_DEPRECATED(follow(*classTy->parent), result, seen, isRaw, ctx); - - return res; - } - - // this should not be reachable since the type should be a valid tables or extern types part from normalization. - LUAU_ASSERT(false); - return false; -} - namespace { @@ -1923,201 +1901,102 @@ TypeFunctionReductionResult keyofFunctionImpl( normTy->hasThreads() || normTy->hasBuffers() || normTy->hasFunctions() || normTy->hasTyvars()) return {std::nullopt, Reduction::Erroneous, {}, {}}; - if (FFlag::LuauEmptyStringInKeyOf) - { - // We're going to collect the keys in here, and we use optional strings - // so that we can differentiate between the empty string and _no_ string. - Set> keys{std::nullopt}; - - // computing the keys for extern types - if (normTy->hasExternTypes()) - { - LUAU_ASSERT(!normTy->hasTables()); - - // seen set for key computation for extern types - DenseHashSet seen{{}}; - - auto externTypeIter = normTy->externTypes.ordering.begin(); - auto externTypeIterEnd = normTy->externTypes.ordering.end(); - LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier + // We're going to collect the keys in here, and we use optional strings + // so that we can differentiate between the empty string and _no_ string. + Set> keys{std::nullopt}; - // collect all the properties from the first class type - if (!computeKeysOf(*externTypeIter, keys, seen, isRaw, ctx)) - return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type! + // computing the keys for extern types + if (normTy->hasExternTypes()) + { + LUAU_ASSERT(!normTy->hasTables()); - // we need to look at each class to remove any keys that are not common amongst them all - while (++externTypeIter != externTypeIterEnd) - { - seen.clear(); // we'll reuse the same seen set + // seen set for key computation for extern types + DenseHashSet seen{{}}; - Set> localKeys{std::nullopt}; + auto externTypeIter = normTy->externTypes.ordering.begin(); + auto externTypeIterEnd = normTy->externTypes.ordering.end(); + LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier - // we can skip to the next class if this one is a top type - if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx)) - continue; + // collect all the properties from the first class type + if (!computeKeysOf(*externTypeIter, keys, seen, isRaw, ctx)) + return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type! - for (auto& key : keys) - { - // remove any keys that are not present in each class - if (!localKeys.contains(key)) - keys.erase(key); - } - } - } - - // computing the keys for tables - if (normTy->hasTables()) + // we need to look at each class to remove any keys that are not common amongst them all + while (++externTypeIter != externTypeIterEnd) { - LUAU_ASSERT(!normTy->hasExternTypes()); - - // seen set for key computation for tables - DenseHashSet seen{{}}; + seen.clear(); // we'll reuse the same seen set - auto tablesIter = normTy->tables.begin(); - LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier + Set> localKeys{std::nullopt}; - // collect all the properties from the first table type - if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx)) - return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have the top table type! + // we can skip to the next class if this one is a top type + if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx)) + continue; - // we need to look at each tables to remove any keys that are not common amongst them all - while (++tablesIter != normTy->tables.end()) + for (auto& key : keys) { - seen.clear(); // we'll reuse the same seen set - - Set> localKeys{std::nullopt}; - - // we can skip to the next table if this one is the top table type - if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx)) - continue; - - for (auto& key : keys) - { - // remove any keys that are not present in each table - if (!localKeys.contains(key)) - keys.erase(key); - } + // remove any keys that are not present in each class + if (!localKeys.contains(key)) + keys.erase(key); } } - - // if the set of keys is empty, `keyof` is `never` - if (keys.empty()) - return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; - - // everything is validated, we need only construct our big union of singletons now! - std::vector singletons; - singletons.reserve(keys.size()); - - for (const auto& key : keys) - { - if (key) - singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{*key}})); - } - - // If there's only one entry, we don't need a UnionType. - // We can take straight take it from the first entry - // because it was added into the type arena already. - if (singletons.size() == 1) - return {singletons.front(), Reduction::MaybeOk, {}, {}}; - - return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}}; } - else - { - - // we're going to collect the keys in here - Set keys{{}}; - - // computing the keys for extern types - if (normTy->hasExternTypes()) - { - LUAU_ASSERT(!normTy->hasTables()); - - // seen set for key computation for extern types - DenseHashSet seen{{}}; - - auto externTypeIter = normTy->externTypes.ordering.begin(); - auto externTypeIterEnd = normTy->externTypes.ordering.end(); - LUAU_ASSERT(externTypeIter != externTypeIterEnd); // should be guaranteed by the `hasExternTypes` check earlier - // collect all the properties from the first class type - if (!computeKeysOf_DEPRECATED(*externTypeIter, keys, seen, isRaw, ctx)) - return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type! + // computing the keys for tables + if (normTy->hasTables()) + { + LUAU_ASSERT(!normTy->hasExternTypes()); - // we need to look at each class to remove any keys that are not common amongst them all - while (++externTypeIter != externTypeIterEnd) - { - seen.clear(); // we'll reuse the same seen set + // seen set for key computation for tables + DenseHashSet seen{{}}; - Set localKeys{{}}; + auto tablesIter = normTy->tables.begin(); + LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier - // we can skip to the next class if this one is a top type - if (!computeKeysOf_DEPRECATED(*externTypeIter, localKeys, seen, isRaw, ctx)) - continue; + // collect all the properties from the first table type + if (!computeKeysOf(*tablesIter, keys, seen, isRaw, ctx)) + return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have the top table type! - for (auto& key : keys) - { - // remove any keys that are not present in each class - if (!localKeys.contains(key)) - keys.erase(key); - } - } - } - - // computing the keys for tables - if (normTy->hasTables()) + // we need to look at each tables to remove any keys that are not common amongst them all + while (++tablesIter != normTy->tables.end()) { - LUAU_ASSERT(!normTy->hasExternTypes()); - - // seen set for key computation for tables - DenseHashSet seen{{}}; + seen.clear(); // we'll reuse the same seen set - auto tablesIter = normTy->tables.begin(); - LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier + Set> localKeys{std::nullopt}; - // collect all the properties from the first table type - if (!computeKeysOf_DEPRECATED(*tablesIter, keys, seen, isRaw, ctx)) - return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have the top table type! + // we can skip to the next table if this one is the top table type + if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx)) + continue; - // we need to look at each tables to remove any keys that are not common amongst them all - while (++tablesIter != normTy->tables.end()) + for (auto& key : keys) { - seen.clear(); // we'll reuse the same seen set - - Set localKeys{{}}; - - // we can skip to the next table if this one is the top table type - if (!computeKeysOf_DEPRECATED(*tablesIter, localKeys, seen, isRaw, ctx)) - continue; - - for (auto& key : keys) - { - // remove any keys that are not present in each table - if (!localKeys.contains(key)) - keys.erase(key); - } + // remove any keys that are not present in each table + if (!localKeys.contains(key)) + keys.erase(key); } } + } - // if the set of keys is empty, `keyof` is `never` - if (keys.empty()) - return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; + // if the set of keys is empty, `keyof` is `never` + if (keys.empty()) + return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; - // everything is validated, we need only construct our big union of singletons now! - std::vector singletons; - singletons.reserve(keys.size()); + // everything is validated, we need only construct our big union of singletons now! + std::vector singletons; + singletons.reserve(keys.size()); - for (const std::string& key : keys) - singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{key}})); + for (const auto& key : keys) + { + if (key) + singletons.push_back(ctx->arena->addType(SingletonType{StringSingleton{*key}})); + } - // If there's only one entry, we don't need a UnionType. - // We can take straight take it from the first entry - // because it was added into the type arena already. - if (singletons.size() == 1) - return {singletons.front(), Reduction::MaybeOk, {}, {}}; + // If there's only one entry, we don't need a UnionType. + // We can take straight take it from the first entry + // because it was added into the type arena already. + if (singletons.size() == 1) + return {singletons.front(), Reduction::MaybeOk, {}, {}}; - return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}}; - } + return {ctx->arena->addType(UnionType{std::move(singletons)}), Reduction::MaybeOk, {}, {}}; } TypeFunctionReductionResult keyofTypeFunction( @@ -2618,7 +2497,7 @@ static TypeFunctionReductionResult getmetatableHelper(TypeId targetTy, c erroneous = false; } - if (FFlag::LuauUpdateGetMetatableTypeSignature && get(targetTy)) + if (get(targetTy)) { // getmetatable ~ any result = targetTy; @@ -2695,7 +2574,7 @@ TypeFunctionReductionResult getmetatableTypeFunction( if (!result.result) { // Don't immediately error if part is unknown - if (FFlag::LuauUpdateGetMetatableTypeSignature && get(follow(part))) + if (get(follow(part))) { erroredWithUnknown = true; continue; @@ -2708,10 +2587,10 @@ TypeFunctionReductionResult getmetatableTypeFunction( } // If all parts are unknown, return erroneous reduction - if (FFlag::LuauUpdateGetMetatableTypeSignature && erroredWithUnknown && parts.empty()) + if (erroredWithUnknown && parts.empty()) return {std::nullopt, Reduction::Erroneous, {}, {}}; - if (FFlag::LuauUpdateGetMetatableTypeSignature && parts.size() == 1) + if (parts.size() == 1) return {parts.front(), Reduction::MaybeOk, {}, {}}; return {ctx->arena->addType(IntersectionType{std::move(parts)}), Reduction::MaybeOk, {}, {}}; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index cf369e18..1c1395ea 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -37,15 +37,17 @@ LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) -LUAU_FASTFLAGVARIABLE(LuauSimplifyOutOfLine2) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) -LUAU_FASTFLAGVARIABLE(LuauRefineTablesWithReadType) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2) LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAGVARIABLE(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAGVARIABLE(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) namespace Luau { @@ -232,6 +234,7 @@ ConstraintGenerator::ConstraintGenerator( if (FFlag::LuauEagerGeneralization4) { LUAU_ASSERT(FFlag::LuauTrackFreeInteriorTypePacks); + LUAU_ASSERT(FFlag::LuauResetConditionalContextProperly); } } @@ -293,6 +296,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) moduleFnTy, /*interiorTypes*/ std::vector{}, /*hasDeprecatedAttribute*/ false, + /*deprecatedInfo*/{}, /*noGenerics*/ true } ); @@ -312,7 +316,10 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) this, [genConstraint](const ConstraintPtr& c) { - genConstraint->dependencies.push_back(NotNull{c.get()}); + if (FFlag::LuauEmplaceNotPushBack) + genConstraint->dependencies.emplace_back(c.get()); + else + genConstraint->dependencies.push_back(NotNull{c.get()}); } ); @@ -342,11 +349,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) asMutable(ty)->ty.emplace(domainTy); } - if (FFlag::LuauSimplifyOutOfLine2) - { - for (TypeId ty : unionsToSimplify) - addConstraint(scope, block->location, SimplifyConstraint{ty}); - } + for (TypeId ty : unionsToSimplify) + addConstraint(scope, block->location, SimplifyConstraint{ty}); } void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStatBlock* block) @@ -442,7 +446,10 @@ ScopePtr ConstraintGenerator::childScope(AstNode* node, const ScopePtr& parent) scope->returnType = parent->returnType; scope->varargPack = parent->varargPack; - parent->children.push_back(NotNull{scope.get()}); + if (FFlag::LuauEmplaceNotPushBack) + parent->children.emplace_back(scope.get()); + else + parent->children.push_back(NotNull{scope.get()}); module->astScopes[node] = scope.get(); return scope; @@ -608,18 +615,7 @@ void ConstraintGenerator::computeRefinement( TypeId nextDiscriminantTy = arena->addType(TableType{}); NotNull table{getMutable(nextDiscriminantTy)}; - if (FFlag::LuauRefineTablesWithReadType) - { - table->props[*key->propName] = Property::readonly(discriminantTy); - } - else - { - // When we fully support read-write properties (i.e. when we allow properties with - // completely disparate read and write types), then the following property can be - // set to read-only since refinements only tell us about what we read. This cannot - // be allowed yet though because it causes read and write types to diverge. - table->props[*key->propName] = Property::rw(discriminantTy); - } + table->props[*key->propName] = Property::readonly(discriminantTy); table->scope = scope.get(); table->state = TableState::Sealed; @@ -1133,7 +1129,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat hasAnnotation = true; TypeId annotationTy = resolveType(scope, local->annotation, /* inTypeArguments */ false); annotatedTypes.push_back(annotationTy); - expectedTypes.push_back(annotationTy); + if (FFlag::LuauEmplaceNotPushBack) + expectedTypes.emplace_back(annotationTy); + else + expectedTypes.push_back(annotationTy); scope->bindings[local] = Binding{annotationTy, location}; } @@ -1143,7 +1142,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat // local has no annotation at, assume the most conservative thing. annotatedTypes.push_back(builtinTypes->unknownType); - expectedTypes.push_back(std::nullopt); + if (FFlag::LuauEmplaceNotPushBack) + expectedTypes.emplace_back(std::nullopt); + else + expectedTypes.push_back(std::nullopt); scope->bindings[local] = Binding{builtinTypes->unknownType, location}; inferredBindings[local] = {scope.get(), location, {assignee}}; @@ -1407,7 +1409,19 @@ static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstEx { if (GeneralizationConstraint* genConstraint = c.get_if()) { - genConstraint->hasDeprecatedAttribute = func->hasAttribute(AstAttr::Type::Deprecated); + if (FFlag::LuauParametrizedAttributeSyntax) + { + 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); + } } } @@ -1415,7 +1429,19 @@ static void propagateDeprecatedAttributeToType(TypeId signature, const AstExprFu { FunctionType* fty = getMutable(signature); LUAU_ASSERT(fty); - fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated); + if (FFlag::LuauParametrizedAttributeSyntax) + { + AstAttr* deprecatedAttr = func->getAttribute(AstAttr::Type::Deprecated); + fty->isDeprecatedFunction = deprecatedAttr != nullptr; + if (deprecatedAttr) + { + fty->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); + } + } + else + { + fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated); + } } ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFunction* function) @@ -1470,12 +1496,20 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti this, [&c, &previous](const ConstraintPtr& constraint) { - c->dependencies.push_back(NotNull{constraint.get()}); + if (FFlag::LuauEmplaceNotPushBack) + c->dependencies.emplace_back(constraint.get()); + else + c->dependencies.push_back(NotNull{constraint.get()}); if (auto psc = get(*constraint); psc && psc->returns) { if (previous) - constraint->dependencies.push_back(NotNull{previous}); + { + if (FFlag::LuauEmplaceNotPushBack) + constraint->dependencies.emplace_back(previous); + else + constraint->dependencies.push_back(NotNull{previous}); + } previous = constraint.get(); } @@ -1604,12 +1638,20 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f this, [&c, &previous](const ConstraintPtr& constraint) { - c->dependencies.push_back(NotNull{constraint.get()}); + if (FFlag::LuauEmplaceNotPushBack) + c->dependencies.emplace_back(constraint.get()); + else + c->dependencies.push_back(NotNull{constraint.get()}); if (auto psc = get(*constraint); psc && psc->returns) { if (previous) - constraint->dependencies.push_back(NotNull{previous}); + { + if (FFlag::LuauEmplaceNotPushBack) + constraint->dependencies.emplace_back(previous); + else + constraint->dependencies.push_back(NotNull{previous}); + } previous = constraint.get(); } @@ -1667,7 +1709,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatReturn* ret // conforms to that. std::vector> expectedTypes; for (TypeId ty : scope->returnType) - expectedTypes.push_back(ty); + if (FFlag::LuauEmplaceNotPushBack) + expectedTypes.emplace_back(ty); + else + expectedTypes.push_back(ty); TypePackId exprTypes = checkPack(scope, ret->list, expectedTypes).tp; addConstraint(scope, ret->location, PackSubtypeConstraint{exprTypes, scope->returnType, /*returns*/ true}); @@ -1868,7 +1913,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt); // Place this function as a child of the non-type function scope - scope->children.push_back(NotNull{sig.signatureScope.get()}); + if (FFlag::LuauEmplaceNotPushBack) + scope->children.emplace_back(sig.signatureScope.get()); + else + scope->children.push_back(NotNull{sig.signatureScope.get()}); if (FFlag::LuauTrackFreeInteriorTypePacks) interiorFreeTypes.emplace_back(); @@ -1914,7 +1962,12 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio if (auto psc = get(*constraint); psc && psc->returns) { if (previous) - constraint->dependencies.push_back(NotNull{previous}); + { + if (FFlag::LuauEmplaceNotPushBack) + constraint->dependencies.emplace_back(previous); + else + constraint->dependencies.push_back(NotNull{previous}); + } previous = constraint.get(); } @@ -2171,11 +2224,26 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc FunctionType* ftv = getMutable(fnType); ftv->isCheckedFunction = global->isCheckedFunction(); - ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated); + if (FFlag::LuauParametrizedAttributeSyntax) + { + AstAttr* deprecatedAttr = global->getAttribute(AstAttr::Type::Deprecated); + ftv->isDeprecatedFunction = deprecatedAttr != nullptr; + if (deprecatedAttr) + { + ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); + } + } + else + { + ftv->isDeprecatedFunction = global->hasAttribute(AstAttr::Type::Deprecated); + } ftv->argNames.reserve(global->paramNames.size); for (const auto& el : global->paramNames) - ftv->argNames.push_back(FunctionArgument{el.first.value, el.second}); + if (FFlag::LuauEmplaceNotPushBack) + ftv->argNames.emplace_back(FunctionArgument{el.first.value, el.second}); + else + ftv->argNames.push_back(FunctionArgument{el.first.value, el.second}); Name fnName(global->name.value); @@ -2288,8 +2356,13 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* { TypeId discriminantTy = arena->addType(BlockedType{}); returnRefinements.push_back(refinementArena.implicitProposition(key, discriminantTy)); - discriminantTypes.push_back(discriminantTy); + if (FFlag::LuauEmplaceNotPushBack) + discriminantTypes.emplace_back(discriminantTy); + else + discriminantTypes.push_back(discriminantTy); } + else if (FFlag::LuauEmplaceNotPushBack) + discriminantTypes.emplace_back(std::nullopt); else discriminantTypes.push_back(std::nullopt); } @@ -2302,8 +2375,13 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* { TypeId discriminantTy = arena->addType(BlockedType{}); returnRefinements.push_back(refinementArena.implicitProposition(key, discriminantTy)); - discriminantTypes.push_back(discriminantTy); + if (FFlag::LuauEmplaceNotPushBack) + discriminantTypes.emplace_back(discriminantTy); + else + discriminantTypes.push_back(discriminantTy); } + else if (FFlag::LuauEmplaceNotPushBack) + discriminantTypes.emplace_back(std::nullopt); else discriminantTypes.push_back(std::nullopt); } @@ -2311,7 +2389,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* Checkpoint funcBeginCheckpoint = checkpoint(this); TypeId fnType = nullptr; - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauResetConditionalContextProperly) { InConditionalContext icc2{&typeContext, TypeContext::Default}; fnType = check(scope, call->func).ty; @@ -2828,7 +2906,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* func, std::optional expectedType, bool generalize) { std::optional inContext; - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauResetConditionalContextProperly) inContext.emplace(&typeContext, TypeContext::Default); Checkpoint startCheckpoint = checkpoint(this); @@ -2879,7 +2957,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun if (auto psc = get(*constraint); psc && psc->returns) { if (previous) - constraint->dependencies.push_back(NotNull{previous}); + { + if (FFlag::LuauEmplaceNotPushBack) + constraint->dependencies.emplace_back(previous); + else + constraint->dependencies.push_back(NotNull{previous}); + } previous = constraint.get(); } @@ -2899,7 +2982,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary) { std::optional inContext; - if (FFlag::LuauEagerGeneralization4 && unary->op != AstExprUnary::Op::Not) + if (FFlag::LuauResetConditionalContextProperly && unary->op != AstExprUnary::Op::Not) inContext.emplace(&typeContext, TypeContext::Default); auto [operandType, refinement] = check(scope, unary->expr); @@ -3064,7 +3147,7 @@ Inference ConstraintGenerator::checkAstExprBinary( Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional expectedType) { std::optional inContext; - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauResetConditionalContextProperly) inContext.emplace(&typeContext, TypeContext::Default); RefinementId refinement = [&]() @@ -3097,7 +3180,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprInterpString* interpString) { std::optional inContext; - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauResetConditionalContextProperly) inContext.emplace(&typeContext, TypeContext::Default); for (AstExpr* expr : interpString->expressions) @@ -3115,7 +3198,7 @@ std::tuple ConstraintGenerator::checkBinary( ) { std::optional inContext; - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauResetConditionalContextProperly) { if (op != AstExprBinary::And && op != AstExprBinary::Or && op != AstExprBinary::CompareEq && op != AstExprBinary::CompareNe) inContext.emplace(&typeContext, TypeContext::Default); @@ -3362,7 +3445,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprIndexExpr* e Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, std::optional expectedType) { std::optional inContext; - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauResetConditionalContextProperly) inContext.emplace(&typeContext, TypeContext::Default); TypeId ty = arena->addType(TableType{}); @@ -3430,44 +3513,30 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, { LUAU_ASSERT(!indexValueLowerBound.empty()); - if (FFlag::LuauSimplifyOutOfLine2) - { - TypeId indexKey = nullptr; - TypeId indexValue = nullptr; - - if (indexKeyLowerBound.size() == 1) - { - indexKey = *indexKeyLowerBound.begin(); - } - else - { - indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); - unionsToSimplify.push_back(indexKey); - } + TypeId indexKey = nullptr; + TypeId indexValue = nullptr; - if (indexValueLowerBound.size() == 1) - { - indexValue = *indexValueLowerBound.begin(); - } - else - { - indexValue = arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); - unionsToSimplify.push_back(indexValue); - } + if (indexKeyLowerBound.size() == 1) + { + indexKey = *indexKeyLowerBound.begin(); + } + else + { + indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); + unionsToSimplify.push_back(indexKey); + } - ttv->indexer = TableIndexer{indexKey, indexValue}; + if (indexValueLowerBound.size() == 1) + { + indexValue = *indexValueLowerBound.begin(); } else { - TypeId indexKey = indexKeyLowerBound.size() == 1 - ? *indexKeyLowerBound.begin() - : arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); - - TypeId indexValue = indexValueLowerBound.size() == 1 - ? *indexValueLowerBound.begin() - : arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); - ttv->indexer = TableIndexer{indexKey, indexValue}; + indexValue = arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); + unionsToSimplify.push_back(indexValue); } + + ttv->indexer = TableIndexer{indexKey, indexValue}; } if (FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) @@ -3906,7 +3975,20 @@ TypeId ConstraintGenerator::resolveFunctionType( // how to quantify/instantiate it. FunctionType ftv{TypeLevel{}, {}, {}, argTypes, returnTypes}; ftv.isCheckedFunction = fn->isCheckedFunction(); - ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated); + if (FFlag::LuauParametrizedAttributeSyntax) + { + AstAttr* deprecatedAttr = fn->getAttribute(AstAttr::Type::Deprecated); + ftv.isDeprecatedFunction = deprecatedAttr != nullptr; + if (deprecatedAttr) + { + ftv.deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); + } + } + else + { + ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated); + } + // This replicates the behavior of the appropriate FunctionType // constructors. @@ -3919,12 +4001,15 @@ TypeId ConstraintGenerator::resolveFunctionType( if (el) { const auto& [name, location] = *el; - ftv.argNames.push_back(FunctionArgument{name.value, location}); + if (FFlag::LuauEmplaceNotPushBack) + ftv.argNames.emplace_back(FunctionArgument{name.value, location}); + else + ftv.argNames.push_back(FunctionArgument{name.value, location}); } + else if (FFlag::LuauEmplaceNotPushBack) + ftv.argNames.emplace_back(std::nullopt); else - { ftv.argNames.push_back(std::nullopt); - } } return arena->addType(std::move(ftv)); @@ -4173,7 +4258,10 @@ Inference ConstraintGenerator::flattenPack(const ScopePtr& scope, Location locat void ConstraintGenerator::reportError(Location location, TypeErrorData err) { - errors.push_back(TypeError{location, module->name, std::move(err)}); + if (FFlag::LuauEmplaceNotPushBack) + errors.emplace_back(location, module->name, std::move(err)); + else + errors.push_back(TypeError{location, module->name, std::move(err)}); if (logger) logger->captureGenerationError(errors.back()); @@ -4181,7 +4269,10 @@ void ConstraintGenerator::reportError(Location location, TypeErrorData err) void ConstraintGenerator::reportCodeTooComplex(Location location) { - errors.push_back(TypeError{location, module->name, CodeTooComplex{}}); + if (FFlag::LuauEmplaceNotPushBack) + errors.emplace_back(location, module->name, CodeTooComplex{}); + else + errors.push_back(TypeError{location, module->name, CodeTooComplex{}}); if (logger) logger->captureGenerationError(errors.back()); @@ -4194,23 +4285,30 @@ TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, if (get(follow(rhs))) return lhs; - if (FFlag::LuauSimplifyOutOfLine2) - { - TypeId result = simplifyUnion(scope, location, lhs, rhs); - if (is(follow(result))) - unionsToSimplify.push_back(result); - return result; - } - else - { - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, {lhs, rhs}, {}, scope, location); - return resultType; - } + TypeId result = simplifyUnion(scope, location, lhs, rhs); + if (is(follow(result))) + unionsToSimplify.push_back(result); + return result; } TypeId ConstraintGenerator::makeUnion(std::vector options) { - LUAU_ASSERT(FFlag::LuauSimplifyOutOfLine2); + if (FFlag::LuauReduceSetTypeStackPressure) + { + UnionBuilder ub{arena, builtinTypes}; + ub.reserve(options.size()); + + for (auto option : options) + ub.add(option); + + TypeId unionTy = ub.build(); + + if (is(unionTy)) + unionsToSimplify.push_back(unionTy); + + return unionTy; + } + TypeId result = arena->addType(UnionType{std::move(options)}); unionsToSimplify.push_back(result); return result; @@ -4423,16 +4521,8 @@ void ConstraintGenerator::fillInInferredBindings(const ScopePtr& globalScope, As scope->bindings[symbol] = Binding{tys.front(), location}; else { - if (FFlag::LuauSimplifyOutOfLine2) - { - TypeId ty = makeUnion(std::move(tys)); - scope->bindings[symbol] = Binding{ty, location}; - } - else - { - TypeId ty = createTypeFunctionInstance(builtinTypeFunctions().unionFunc, std::move(tys), {}, globalScope, location); - scope->bindings[symbol] = Binding{ty, location}; - } + TypeId ty = makeUnion(std::move(tys)); + scope->bindings[symbol] = Binding{ty, location}; } } } @@ -4455,7 +4545,12 @@ std::vector> ConstraintGenerator::getExpectedCallTypesForF auto assignOption = [this, &expectedTypes](size_t index, TypeId ty) { if (index == expectedTypes.size()) - expectedTypes.push_back(ty); + { + if (FFlag::LuauEmplaceNotPushBack) + expectedTypes.emplace_back(ty); + else + expectedTypes.push_back(ty); + } else if (ty) { auto& el = expectedTypes[index]; @@ -4470,12 +4565,7 @@ std::vector> ConstraintGenerator::getExpectedCallTypesForF else if (result.size() == 1) el = result[0]; else - { - if (FFlag::LuauSimplifyOutOfLine2) - el = makeUnion(std::move(result)); - else - el = module->internalTypes.addType(UnionType{std::move(result)}); - } + el = makeUnion(std::move(result)); } } }; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 3c032a37..30ab72e0 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -24,6 +24,7 @@ #include "Luau/VisitType.h" #include +#include #include LUAU_FASTINTVARIABLE(LuauSolverConstraintLimit, 1000) @@ -38,11 +39,16 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) +LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2) LUAU_FASTFLAGVARIABLE(LuauCollapseShouldNotCrash) LUAU_FASTFLAGVARIABLE(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAGVARIABLE(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAGVARIABLE(LuauDontDynamicallyCreateRedundantSubtypeConstraints) +LUAU_FASTFLAGVARIABLE(LuauExtendSealedTableUpperBounds) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) +LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes) namespace Luau { @@ -307,7 +313,7 @@ struct InstantiationQueuer : TypeOnceVisitor Location location; explicit InstantiationQueuer(NotNull scope, const Location& location, ConstraintSolver* solver) - : TypeOnceVisitor("InstantiationQueuer") + : TypeOnceVisitor("InstantiationQueuer", FFlag::LuauReduceSetTypeStackPressure) , solver(solver) , scope(scope) , location(location) @@ -332,6 +338,42 @@ struct InstantiationQueuer : TypeOnceVisitor } }; +struct InfiniteTypeFinder : TypeOnceVisitor +{ + NotNull solver; + const InstantiationSignature& signature; + NotNull scope; + bool foundInfiniteType = false; + + explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull scope) + : TypeOnceVisitor("InfiniteTypeFinder") + , solver(solver) + , signature(signature) + , scope(scope) + { + } + + + bool visit(TypeId ty, const PendingExpansionType& petv) override + { + const std::optional tf = + (petv.prefix) ? scope->lookupImportedType(petv.prefix->value, petv.name.value) : scope->lookupType(petv.name.value); + + if (!tf.has_value()) + return true; + + auto [typeArguments, packArguments] = saturateArguments(solver->arena, solver->builtinTypes, *tf, petv.typeArguments, petv.packArguments); + + if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments)) + { + foundInfiniteType = true; + return false; + } + + return true; + } +}; + ConstraintSolver::ConstraintSolver( NotNull normalizer, NotNull simplifier, @@ -911,7 +953,13 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull(follow(generalizedType))) { if (c.hasDeprecatedAttribute) + { fty->isDeprecatedFunction = true; + if (FFlag::LuauParametrizedAttributeSyntax) + { + fty->deprecatedInfo = std::make_shared(c.deprecatedInfo); + } + } } } else @@ -1115,6 +1163,30 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNullpersistent || target->owningArena != arena) return true; + if (FFlag::LuauNameConstraintRestrictRecursiveTypes) + { + 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, + }; + + 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 (TableType* ttv = getMutable(target)) { if (c.synthetic && !ttv->name) @@ -1136,41 +1208,6 @@ bool ConstraintSolver::tryDispatch(const NameConstraint& c, NotNull scope; - bool foundInfiniteType = false; - - explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull scope) - : TypeOnceVisitor("InfiniteTypeFinder") - , solver(solver) - , signature(signature) - , scope(scope) - { - } - - bool visit(TypeId ty, const PendingExpansionType& petv) override - { - std::optional tf = - (petv.prefix) ? scope->lookupImportedType(petv.prefix->value, petv.name.value) : scope->lookupType(petv.name.value); - - if (!tf.has_value()) - return true; - - auto [typeArguments, packArguments] = saturateArguments(solver->arena, solver->builtinTypes, *tf, petv.typeArguments, petv.packArguments); - - if (follow(tf->type) == follow(signature.fn.type) && (signature.arguments != typeArguments || signature.packArguments != packArguments)) - { - foundInfiniteType = true; - return false; - } - - return true; - } -}; - bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNull constraint) { const PendingExpansionType* petv = get(follow(c.target)); @@ -1553,7 +1590,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNulladdType(FunctionType{TypeLevel{}, argsPack, c.result}); Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}}; - const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy); + // TODO: This should probably use ConstraintSolver::unify + const UnifyResult unifyResult = u2.unify(overloadToUse, inferredTy); if (FFlag::LuauEagerGeneralization4) { @@ -1584,10 +1622,27 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNulllocation, addition); } - if (occursCheckPassed && c.callSite) + if (UnifyResult::Ok == unifyResult && c.callSite) (*c.astOverloadResolvedTypes)[c.callSite] = inferredTy; - else if (!occursCheckPassed) - reportError(OccursCheckFailed{}, constraint->location); + else if (UnifyResult::Ok != unifyResult) + { + if (FFlag::LuauLimitUnification) + { + switch (unifyResult) + { + case UnifyResult::Ok: + break; + case UnifyResult::TooComplex: + reportError(UnificationTooComplex{}, constraint->location); + break; + case UnifyResult::OccursCheckFailed: + reportError(OccursCheckFailed{}, constraint->location); + break; + } + } + else + reportError(OccursCheckFailed{}, constraint->location); + } InstantiationQueuer queuer{constraint->scope, constraint->location, this}; queuer.traverse(overloadToUse); @@ -1834,38 +1889,17 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullis() || expr->is() || expr->is() || expr->is() || (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is())) { - if (FFlag::LuauAvoidExcessiveTypeCopying) - { - if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) - { - ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}}; - if (auto res = replacer.substitute(expectedArgTy)) - { - InstantiationQueuer queuer{constraint->scope, constraint->location, this}; - queuer.traverse(*res); - expectedArgTy = *res; - } - } - u2.unify(actualArgTy, expectedArgTy); - } - else + if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) { ReferentialReplacer replacer{arena, NotNull{&replacements}, NotNull{&replacementPacks}}; if (auto res = replacer.substitute(expectedArgTy)) { - if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls) - { - // If we do this replacement and there are type - // functions in the final type, then we need to - // ensure those get reduced. - InstantiationQueuer queuer{constraint->scope, constraint->location, this}; - queuer.traverse(*res); - } - u2.unify(actualArgTy, *res); + InstantiationQueuer queuer{constraint->scope, constraint->location, this}; + queuer.traverse(*res); + expectedArgTy = *res; } - else - u2.unify(actualArgTy, expectedArgTy); } + u2.unify(actualArgTy, expectedArgTy); } else if (!FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is() && !ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) @@ -2446,7 +2480,10 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNullindexer->indexType); unify(constraint, rhsType, lhsTable->indexer->indexResultType); - bind(constraint, c.propType, arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}})); + if (FFlag::LuauReduceSetTypeStackPressure) + bind(constraint, c.propType, addUnion(arena, builtinTypes, {lhsTable->indexer->indexResultType, builtinTypes->nilType})); + else + bind(constraint, c.propType, arena->addType(UnionType{{lhsTable->indexer->indexResultType, builtinTypes->nilType}})); return true; } @@ -3283,13 +3320,14 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( { const TypeId upperBound = follow(ft->upperBound); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauExtendSealedTableUpperBounds) { if (get(upperBound) || get(upperBound)) { TablePropLookupResult res = lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); - // If the upper bound is a table that already has the property, we don't need to extend its bounds. - if (res.propType || get(upperBound)) + // Here, res.propType is empty if res is a sealed table or a primitive that lacks the property. + // When this happens, we still want to add to the upper bound of the type. + if (res.propType) return res; } } @@ -3299,8 +3337,6 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); } - // TODO: The upper bound could be an intersection that contains suitable tables or extern types. - NotNull scope{ft->scope}; const TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, scope}); @@ -3409,7 +3445,7 @@ bool ConstraintSolver::unify(NotNull constraint, TID subTy, TI { Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFunctions}; - const bool ok = u2.unify(subTy, superTy); + const UnifyResult unifyResult = u2.unify(subTy, superTy); for (ConstraintV& c : u2.incompleteSubtypes) { @@ -3417,7 +3453,7 @@ bool ConstraintSolver::unify(NotNull constraint, TID subTy, TI inheritBlocks(constraint, addition); } - if (ok) + if (UnifyResult::Ok == unifyResult) { for (const auto& [expanded, additions] : u2.expandedFreeTypes) { @@ -3427,7 +3463,22 @@ bool ConstraintSolver::unify(NotNull constraint, TID subTy, TI } else { - reportError(OccursCheckFailed{}, constraint->location); + if (FFlag::LuauLimitUnification) + { + switch (unifyResult) + { + case Luau::UnifyResult::Ok: + break; + case Luau::UnifyResult::OccursCheckFailed: + reportError(OccursCheckFailed{}, constraint->location); + break; + case Luau::UnifyResult::TooComplex: + reportError(UnificationTooComplex{}, constraint->location); + break; + } + } + else + reportError(OccursCheckFailed{}, constraint->location); return false; } diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index 1455f033..90ec37b5 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -14,7 +14,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) -LUAU_FASTFLAGVARIABLE(LuauDfgForwardNilFromAndOr) namespace Luau { @@ -1036,23 +1035,13 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprUnary* u) DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprBinary* b) { - if (FFlag::LuauDfgForwardNilFromAndOr) - { - auto left = visitExpr(b->left); - auto right = visitExpr(b->right); - // I think there's some subtlety here. There are probably cases where - // X or Y / X and Y can _never_ "be subscripted." - auto subscripted = (b->op == AstExprBinary::And || b->op == AstExprBinary::Or) && - (containsSubscriptedDefinition(left.def) || containsSubscriptedDefinition(right.def)); - return {defArena->freshCell(Symbol{}, b->location, subscripted), nullptr}; - } - else - { - visitExpr(b->left); - visitExpr(b->right); - - return {defArena->freshCell(Symbol{}, b->location), nullptr}; - } + auto left = visitExpr(b->left); + auto right = visitExpr(b->right); + // I think there's some subtlety here. There are probably cases where + // X or Y / X and Y can _never_ "be subscripted." + auto subscripted = (b->op == AstExprBinary::And || b->op == AstExprBinary::Or) && + (containsSubscriptedDefinition(left.def) || containsSubscriptedDefinition(right.def)); + return {defArena->freshCell(Symbol{}, b->location, subscripted), nullptr}; } DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprTypeAssertion* t) diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index a7a252de..d771b2d4 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -12,7 +12,6 @@ #include "Luau/TypeFunction.h" #include -#include #include #include #include @@ -890,6 +889,11 @@ struct ErrorConverter { return "None of the overloads for function that accept " + std::to_string(e.attemptedArgCount) + " arguments are compatible."; } + + std::string operator()(const RecursiveRestraintViolation& e) const + { + return "Recursive type being used with different parameters."; + } }; struct InvalidNameChecker @@ -1516,6 +1520,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) else if constexpr (std::is_same_v) { } + else if constexpr (std::is_same_v) + { + } else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/FileResolver.cpp b/Analysis/src/FileResolver.cpp index 34befcba..fc36babb 100644 --- a/Analysis/src/FileResolver.cpp +++ b/Analysis/src/FileResolver.cpp @@ -4,7 +4,6 @@ #include "Luau/Common.h" #include "Luau/StringUtils.h" -#include #include #include #include diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 50b8db45..36ed4a71 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -16,12 +16,10 @@ #include "Luau/NotNull.h" #include "Luau/Parser.h" #include "Luau/Scope.h" -#include "Luau/StringUtils.h" #include "Luau/TimeTrace.h" #include "Luau/TypeArena.h" #include "Luau/TypeChecker2.h" #include "Luau/TypeInfer.h" -#include "Luau/Variant.h" #include "Luau/VisitType.h" #include @@ -29,7 +27,6 @@ #include #include #include -#include #include LUAU_FASTINT(LuauTypeInferIterationLimit) @@ -49,6 +46,7 @@ LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) namespace Luau { @@ -127,14 +125,24 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName) { for (auto& [name, prop] : ttv->props) { - prop.documentationSymbol = rootName + "." + name; + std::string n; + n.reserve(rootName.size() + 1 + name.size()); + n += rootName; + n += "."; + n += name; + prop.documentationSymbol = std::move(n); } } else if (ExternType* etv = getMutable(ty)) { for (auto& [name, prop] : etv->props) { - prop.documentationSymbol = rootName + "." + name; + std::string n; + n.reserve(rootName.size() + 1 + name.size()); + n += rootName; + n += "."; + n += name; + prop.documentationSymbol = std::move(n); } } } @@ -168,7 +176,15 @@ static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, S for (const auto& [name, ty] : checkedModule->declaredGlobals) { TypeId globalTy = clone(ty, globals.globalTypes, cloneState); - std::string documentationSymbol = packageName + "/global/" + name; + + static constexpr const char infix[] = "/global/"; + constexpr int infixLength = sizeof(infix) - 1; // exclude the null terminator + std::string documentationSymbol; + documentationSymbol.reserve(packageName.size() + infixLength + name.size()); + documentationSymbol += packageName; + documentationSymbol += infix; + documentationSymbol += name; + generateDocumentationSymbols(globalTy, documentationSymbol); targetScope->bindings[globals.globalNames.names->getOrAdd(name.c_str())] = {globalTy, Location(), false, {}, documentationSymbol}; @@ -178,7 +194,15 @@ static void persistCheckedTypes(ModulePtr checkedModule, GlobalTypes& globals, S for (const auto& [name, ty] : checkedModule->exportedTypeBindings) { TypeFun globalTy = clone(ty, globals.globalTypes, cloneState); - std::string documentationSymbol = packageName + "/globaltype/" + name; + + static constexpr const char infix[] = "/globaltype/"; + constexpr int infixLength = sizeof(infix) - 1; // exclude the null terminator + std::string documentationSymbol; + documentationSymbol.reserve(packageName.size() + infixLength + name.size()); + documentationSymbol += packageName; + documentationSymbol += infix; + documentationSymbol += name; + generateDocumentationSymbols(globalTy.type, documentationSymbol); targetScope->exportedTypeBindings[name] = globalTy; @@ -224,7 +248,7 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile( namespace { -static ErrorVec accumulateErrors( +ErrorVec accumulateErrors( const std::unordered_map>& sourceNodes, ModuleResolver& moduleResolver, const ModuleName& name @@ -277,7 +301,7 @@ static ErrorVec accumulateErrors( return result; } -static void filterLintOptions(LintOptions& lintOptions, const std::vector& hotcomments, Mode mode) +void filterLintOptions(LintOptions& lintOptions, const std::vector& hotcomments, Mode mode) { uint64_t ignoreLints = LintWarning::parseMask(hotcomments); @@ -369,7 +393,10 @@ std::vector getRequireCycles( if (!cycle.empty()) { - result.push_back({depLocation, std::move(cycle)}); + if (FFlag::LuauEmplaceNotPushBack) + result.emplace_back(RequireCycle{depLocation, std::move(cycle)}); + else + result.push_back({depLocation, std::move(cycle)}); // note: if we didn't find a cycle, all nodes that we've seen don't depend [transitively] on start // so it's safe to *only* clear seen vector when we find a cycle @@ -402,7 +429,7 @@ Frontend::Frontend(FileResolver* fileResolver, ConfigResolver* configResolver, c { } -void Frontend::setLuauSolverSelectionFromWorkspace(SolverMode mode) +void Frontend::setLuauSolverMode(SolverMode mode) { useNewLuauSolver.store(mode); } @@ -1029,8 +1056,8 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) applyInternalLimitScaling(sourceNode, module, *item.options.moduleTimeLimitSec); item.stats.timeCheck += duration; - item.stats.filesStrict += mode == Mode::Strict; - item.stats.filesNonstrict += mode == Mode::Nonstrict; + item.stats.filesStrict += (mode == Mode::Strict) ? 1 : 0; + item.stats.filesNonstrict += (mode == Mode::Nonstrict) ? 1 : 0; if (FFlag::LuauTrackTypeAllocations && item.options.collectTypeAllocationStats) { @@ -1103,7 +1130,10 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) ErrorVec parseErrors; for (const ParseError& pe : sourceModule.parseErrors) - parseErrors.push_back(TypeError{pe.getLocation(), item.name, SyntaxError{pe.what()}}); + if (FFlag::LuauEmplaceNotPushBack) + parseErrors.emplace_back(pe.getLocation(), item.name, SyntaxError{pe.what()}); + else + parseErrors.push_back(TypeError{pe.getLocation(), item.name, SyntaxError{pe.what()}}); module->errors.insert(module->errors.begin(), parseErrors.begin(), parseErrors.end()); @@ -1333,7 +1363,7 @@ SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) const { - return const_cast(this)->getSourceModule(moduleName); + return const_cast(this)->getSourceModule(moduleName); // NOLINT(cppcoreguidelines-pro-type-const-cast) } struct InternalTypeFinder : TypeOnceVisitor diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index 2d786715..cd7b73e3 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -16,6 +16,8 @@ #include "Luau/VisitType.h" LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4) +LUAU_FASTFLAGVARIABLE(LuauReduceSetTypeStackPressure) +LUAU_FASTINTVARIABLE(LuauGenericCounterMaxDepth, 15) namespace Luau { @@ -334,7 +336,7 @@ struct FreeTypeSearcher : TypeVisitor NotNull> cachedTypes; explicit FreeTypeSearcher(NotNull scope, NotNull> cachedTypes) - : TypeVisitor("FreeTypeSearcher", /*skipBoundTypes*/ true) + : TypeVisitor("FreeTypeSearcher", /* skipBoundTypes */ true) , scope(scope) , cachedTypes(cachedTypes) { @@ -622,38 +624,56 @@ struct TypeCacher : TypeOnceVisitor DenseHashSet uncacheablePacks{nullptr}; explicit TypeCacher(NotNull> cachedTypes) - : TypeOnceVisitor("TypeCacher", /* skipBoundTypes */ false) + : TypeOnceVisitor("TypeCacher", /* skipBoundTypes */ FFlag::LuauReduceSetTypeStackPressure) , cachedTypes(cachedTypes) { } void cache(TypeId ty) const { - cachedTypes->insert(ty); + if (FFlag::LuauReduceSetTypeStackPressure) + cachedTypes->insert(follow(ty)); + else + cachedTypes->insert(ty); } bool isCached(TypeId ty) const { + if (FFlag::LuauReduceSetTypeStackPressure) + return cachedTypes->contains(follow(ty)); + return cachedTypes->contains(ty); } void markUncacheable(TypeId ty) { - uncacheable.insert(ty); + if (FFlag::LuauReduceSetTypeStackPressure) + uncacheable.insert(follow(ty)); + else + uncacheable.insert(ty); } void markUncacheable(TypePackId tp) { - uncacheablePacks.insert(tp); + if (FFlag::LuauReduceSetTypeStackPressure) + uncacheablePacks.insert(follow(tp)); + else + uncacheablePacks.insert(tp); } bool isUncacheable(TypeId ty) const { + if (FFlag::LuauReduceSetTypeStackPressure) + return uncacheable.contains(follow(ty)); + return uncacheable.contains(ty); } bool isUncacheable(TypePackId tp) const { + if (FFlag::LuauReduceSetTypeStackPressure) + return uncacheablePacks.contains(follow(tp)); + return uncacheablePacks.contains(tp); } @@ -668,6 +688,7 @@ struct TypeCacher : TypeOnceVisitor bool visit(TypeId ty, const BoundType& btv) override { + LUAU_ASSERT(!FFlag::LuauReduceSetTypeStackPressure); traverse(btv.boundTo); if (isUncacheable(btv.boundTo)) markUncacheable(ty); @@ -1381,14 +1402,37 @@ struct GenericCounter : TypeVisitor Polarity polarity = Polarity::Positive; + int depth = 0; + bool hitLimits = false; + explicit GenericCounter(NotNull> cachedTypes) : TypeVisitor("GenericCounter") , cachedTypes(cachedTypes) { } + void checkLimits() + { + if (FFlag::LuauReduceSetTypeStackPressure && depth > FInt::LuauGenericCounterMaxDepth) + hitLimits = true; + } + + bool visit(TypeId ty) override + { + checkLimits(); + return !FFlag::LuauReduceSetTypeStackPressure || !hitLimits; + } + + bool visit(TypeId ty, const FunctionType& ft) override { + std::optional rc{std::nullopt}; + if (FFlag::LuauReduceSetTypeStackPressure) + { + rc.emplace(&depth); + checkLimits(); + } + if (ty->persistent) return false; @@ -1408,6 +1452,13 @@ struct GenericCounter : TypeVisitor bool visit(TypeId ty, const TableType& tt) override { + std::optional rc{std::nullopt}; + if (FFlag::LuauReduceSetTypeStackPressure) + { + rc.emplace(&depth); + checkLimits(); + } + if (ty->persistent) return false; @@ -1542,13 +1593,16 @@ void pruneUnnecessaryGenerics( counter.traverse(ty); - for (const auto& [generic, state] : counter.generics) + if (!FFlag::LuauReduceSetTypeStackPressure || !counter.hitLimits) { - if (state.count == 1 && state.polarity != Polarity::Mixed) + for (const auto& [generic, state] : counter.generics) { - if (arena.get() != generic->owningArena) - continue; - emplaceType(asMutable(generic), builtinTypes->unknownType); + if (state.count == 1 && state.polarity != Polarity::Mixed) + { + if (arena.get() != generic->owningArena) + continue; + emplaceType(asMutable(generic), builtinTypes->unknownType); + } } } @@ -1564,9 +1618,12 @@ void pruneUnnecessaryGenerics( return true; seen.insert(ty); - auto state = counter.generics.find(ty); - if (state && state->count == 0) - return true; + if (!FFlag::LuauReduceSetTypeStackPressure || !counter.hitLimits) + { + auto state = counter.generics.find(ty); + if (state && state->count == 0) + return true; + } return !get(ty); } @@ -1574,12 +1631,17 @@ void pruneUnnecessaryGenerics( functionTy->generics.erase(it, functionTy->generics.end()); - for (const auto& [genericPack, state] : counter.genericPacks) + + if (!FFlag::LuauReduceSetTypeStackPressure || !counter.hitLimits) { - if (state.count == 1) - emplaceTypePack(asMutable(genericPack), builtinTypes->unknownTypePack); + for (const auto& [genericPack, state] : counter.genericPacks) + { + if (state.count == 1) + emplaceTypePack(asMutable(genericPack), builtinTypes->unknownTypePack); + } } + DenseHashSet seen2{nullptr}; auto it2 = std::remove_if( functionTy->genericPacks.begin(), @@ -1591,9 +1653,12 @@ void pruneUnnecessaryGenerics( return true; seen2.insert(tp); - auto state = counter.genericPacks.find(tp); - if (state && state->count == 0) - return true; + if (!FFlag::LuauReduceSetTypeStackPressure || !counter.hitLimits) + { + auto state = counter.genericPacks.find(tp); + if (state && state->count == 0) + return true; + } return !get(tp); } diff --git a/Analysis/src/InferPolarity.cpp b/Analysis/src/InferPolarity.cpp index 2eb650ae..e95d76d9 100644 --- a/Analysis/src/InferPolarity.cpp +++ b/Analysis/src/InferPolarity.cpp @@ -6,7 +6,6 @@ #include "Luau/VisitType.h" LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAGVARIABLE(LuauInferPolarityOfReadWriteProperties) namespace Luau { diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index bd6a4b95..77b5fd63 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -11,6 +11,7 @@ #include LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) namespace Luau { @@ -64,6 +65,11 @@ 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; + } TypeId result = addType(std::move(clone)); // Annoyingly, we have to do this even if there are no generics, diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index a1183d52..fd1435c7 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -267,6 +267,8 @@ static void errorToString(std::ostream& stream, const T& err) } else if constexpr (std::is_same_v) stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }"; + else if constexpr (std::is_same_v) + stream << "RecursiveRestraintViolation"; else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 9a91afad..5742c09a 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -15,6 +15,7 @@ LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4) LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) namespace Luau { @@ -2293,7 +2294,16 @@ class LintDeprecatedApi : AstVisitor bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); if (shouldReport) - report(node->location, node->local->name.value); + { + if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr) + { + report(node->location, node->local->name.value, *fty->deprecatedInfo); + } + else + { + report(node->location, node->local->name.value); + } + } return true; } @@ -2304,7 +2314,16 @@ class LintDeprecatedApi : AstVisitor bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); if (shouldReport) - report(node->location, node->name.value); + { + if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr) + { + report(node->location, node->name.value, *fty->deprecatedInfo); + } + else + { + report(node->location, node->name.value); + } + } return true; } @@ -2380,8 +2399,14 @@ class LintDeprecatedApi : AstVisitor className = global->name.value; const char* functionName = node->index.value; - - report(node->location, className, functionName); + if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr) + { + report(node->location, className, functionName, *fty->deprecatedInfo); + } + else + { + report(node->location, className, functionName); + } } } } @@ -2415,7 +2440,14 @@ class LintDeprecatedApi : AstVisitor const char* functionName = node->index.value; - report(node->location, className, functionName); + if (FFlag::LuauParametrizedAttributeSyntax && fty->deprecatedInfo != nullptr) + { + report(node->location, className, functionName, *fty->deprecatedInfo); + } + else + { + report(node->location, className, functionName); + } } } } @@ -2443,7 +2475,6 @@ class LintDeprecatedApi : AstVisitor const FunctionType* fty = getFunctionType(func); bool isDeprecated = fty && fty->isDeprecatedFunction; - // If a function is deprecated, we don't want to flag its recursive uses. // So we push it on a stack while its body is being analyzed. // When a deprecated function is used, we check the stack to ensure that we are not inside that function. @@ -2474,11 +2505,47 @@ class LintDeprecatedApi : AstVisitor emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Member '%s' is deprecated", functionName); } + void report(const Location& location, const char* tableName, const char* functionName, const AstAttr::DeprecatedInfo& info) + { + std::string usePart = info.use ? format(", use '%s' instead", info.use->c_str()) : ""; + std::string reasonPart = info.reason ? format(". %s", info.reason->c_str()) : ""; + if (tableName) + emitWarning( + *context, + LintWarning::Code_DeprecatedApi, + location, + "Member '%s.%s' is deprecated%s%s", + tableName, + functionName, + usePart.c_str(), + reasonPart.c_str() + ); + else + emitWarning( + *context, + LintWarning::Code_DeprecatedApi, + location, + "Member '%s' is deprecated%s%s", + functionName, + usePart.c_str(), + reasonPart.c_str() + ); + } + void report(const Location& location, const char* functionName) { emitWarning(*context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated", functionName); } + void report(const Location& location, const char* functionName, const AstAttr::DeprecatedInfo& info) + { + std::string usePart = info.use ? format(", use '%s' instead", info.use->c_str()) : ""; + std::string reasonPart = info.reason ? format(". %s", info.reason->c_str()) : ""; + emitWarning( + *context, LintWarning::Code_DeprecatedApi, location, "Function '%s' is deprecated%s%s", functionName, usePart.c_str(), reasonPart.c_str() + ); + } + std::vector functionTypeScopeStack; void pushScope(const FunctionType* fty) diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 4ac40aef..e34fe9bc 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) +LUAU_FASTFLAGVARIABLE(LuauEmplaceNotPushBack) namespace Luau { @@ -270,7 +271,11 @@ struct ClonePublicInterface : Substitution } else { - module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}}); + + if (FFlag::LuauEmplaceNotPushBack) + module->errors.emplace_back(module->scopes[0].first, UnificationTooComplex{}); + else + module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}}); return builtinTypes->errorType; } } @@ -284,7 +289,10 @@ struct ClonePublicInterface : Substitution } else { - module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}}); + if (FFlag::LuauEmplaceNotPushBack) + module->errors.emplace_back(module->scopes[0].first, UnificationTooComplex{}); + else + module->errors.push_back(TypeError{module->scopes[0].first, UnificationTooComplex{}}); return builtinTypes->errorTypePack; } } diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 547a8003..360e0bff 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -6,7 +6,6 @@ #include "Luau/Common.h" #include "Luau/Simplify.h" #include "Luau/Type.h" -#include "Luau/Simplify.h" #include "Luau/Subtyping.h" #include "Luau/Normalize.h" #include "Luau/Error.h" @@ -17,7 +16,6 @@ #include "Luau/ToString.h" #include "Luau/TypeUtils.h" -#include #include LUAU_FASTFLAG(DebugLuauMagicTypes) @@ -25,6 +23,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictMoreUnknownSymbols) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressesDynamicRequireErrors) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) namespace Luau { @@ -42,7 +41,10 @@ struct StackPusher : stack(&stack) , scope(scope) { - stack.push_back(NotNull{scope}); + if (FFlag::LuauEmplaceNotPushBack) + stack.emplace_back(scope); + else + stack.push_back(NotNull{scope}); } ~StackPusher() diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 61314d87..109c8e32 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -13,6 +13,7 @@ #include "Luau/Subtyping.h" #include "Luau/Type.h" #include "Luau/TypeFwd.h" +#include "Luau/TypeUtils.h" #include "Luau/Unifier.h" LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant) @@ -22,9 +23,9 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIntersectionLimit, 200) LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect) -LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauNormalizationLimitTyvarUnionSize) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) namespace Luau { @@ -3346,11 +3347,7 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm) { result.reserve(result.size() + norm.tables.size()); for (auto table : norm.tables) - { - if (!FFlag::LuauRefineTablesWithReadType) - makeTableShared(table); result.push_back(table); - } } else result.insert(result.end(), norm.tables.begin(), norm.tables.end()); @@ -3360,7 +3357,10 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm) if (get(intersect->tops)) { TypeId ty = typeFromNormal(*intersect); - result.push_back(arena->addType(IntersectionType{{tyvar, ty}})); + if (FFlag::LuauReduceSetTypeStackPressure) + result.push_back(addIntersection(NotNull{arena}, builtinTypes, {tyvar, ty})); + else + result.push_back(arena->addType(IntersectionType{{tyvar, ty}})); } else result.push_back(tyvar); diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index dd6da046..b313acc0 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -10,7 +10,9 @@ #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" +LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) namespace Luau { @@ -47,7 +49,23 @@ std::pair OverloadResolver::selectOverload(T { Subtyping::Variance variance = subtyping.variance; subtyping.variance = Subtyping::Variance::Contravariant; - SubtypingResult r = subtyping.isSubtype(argsPack, ftv->argTypes, scope); + 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 = subtyping.isSubtype( + argsPack, ftv->argTypes, scope, !generics.empty() ? std::optional>{generics} : std::nullopt + ); + } + else + r = subtyping.isSubtype(argsPack, ftv->argTypes, scope); subtyping.variance = variance; if (!useFreeTypeBounds && !r.assumedConstraints.empty()) @@ -584,7 +602,7 @@ SolveResult solveFunctionCall( TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, argsPack, resultPack}); Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter}; - const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy); + const UnifyResult unifyResult = u2.unify(*overloadToUse, inferredTy); if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty()) { @@ -598,8 +616,23 @@ SolveResult solveFunctionCall( resultPack = *subst; } - if (!occursCheckPassed) - return {SolveResult::OccursCheckFailed}; + if (FFlag::LuauLimitUnification) + { + switch (unifyResult) + { + case Luau::UnifyResult::Ok: + break; + case Luau::UnifyResult::OccursCheckFailed: + return {SolveResult::CodeTooComplex}; + case Luau::UnifyResult::TooComplex: + return {SolveResult::OccursCheckFailed}; + } + } + else + { + if (unifyResult != UnifyResult::Ok) + return {SolveResult::OccursCheckFailed}; + } SolveResult result; result.result = SolveResult::Ok; diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index c3509cc0..16c2ae3a 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -5,6 +5,8 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAGVARIABLE(LuauScopeMethodsAreSolverAgnostic) +LUAU_FASTFLAGVARIABLE(LuauNoScopeShallNotSubsumeAll) +LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) namespace Luau { @@ -265,6 +267,19 @@ bool Scope::shouldWarnGlobal(std::string name) const return false; } +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)) + return true; + } + + return false; +} + NotNull Scope::findNarrowestScopeContaining(Location location) { Scope* bestScope = this; @@ -290,6 +305,12 @@ NotNull Scope::findNarrowestScopeContaining(Location location) bool subsumesStrict(Scope* left, Scope* right) { + if (FFlag::LuauNoScopeShallNotSubsumeAll) + { + if (!left || !right) + return false; + } + while (right) { if (right->parent.get() == left) @@ -303,6 +324,12 @@ bool subsumesStrict(Scope* left, Scope* right) bool subsumes(Scope* left, Scope* right) { + if (FFlag::LuauNoScopeShallNotSubsumeAll) + { + if (!left || !right) + return false; + } + return left == right || subsumesStrict(left, right); } diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index fc4c8532..bac150f0 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -2,6 +2,7 @@ #include "Luau/Simplify.h" +#include "Luau/BuiltinDefinitions.h" #include "Luau/Clone.h" #include "Luau/Common.h" #include "Luau/DenseHash.h" @@ -18,10 +19,10 @@ LUAU_FASTINT(LuauTypeReductionRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTINTVARIABLE(LuauSimplificationComplexityLimit, 8) -LUAU_FASTFLAGVARIABLE(LuauRelateTablesAreNeverDisjoint) -LUAU_FASTFLAG(LuauRefineTablesWithReadType) -LUAU_FASTFLAGVARIABLE(LuauMissingSeenSetRelate) +LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSimplificationIterationLimit, 128) +LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) namespace Luau { @@ -288,7 +289,7 @@ Relation relateTables(TypeId left, TypeId right, SimplifierSeenSet& seen) ); if (!foundPropFromLeftInRight && !foundPropFromRightInLeft && leftTable->props.size() >= 1 && rightTable->props.size() >= 1) - return FFlag::LuauRelateTablesAreNeverDisjoint ? Relation::Intersects : Relation::Disjoint; + return Relation::Intersects; const auto [propName, rightProp] = *begin(rightTable->props); @@ -587,17 +588,8 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) { if (auto propInExternType = re->props.find(name); propInExternType != re->props.end()) { - Relation propRel; - if (FFlag::LuauMissingSeenSetRelate) - { - LUAU_ASSERT(prop.readTy && propInExternType->second.readTy); - propRel = relate(*prop.readTy, *propInExternType->second.readTy, seen); - } - else - { - LUAU_ASSERT(prop.readTy && propInExternType->second.readTy); - propRel = relate(*prop.readTy, *propInExternType->second.readTy); - } + LUAU_ASSERT(prop.readTy && propInExternType->second.readTy); + Relation propRel = relate(*prop.readTy, *propInExternType->second.readTy, seen); if (propRel == Relation::Disjoint) return Relation::Disjoint; @@ -630,7 +622,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) return Relation::Disjoint; } - if (FFlag::LuauRefineTablesWithReadType && is(right)) + 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 @@ -784,10 +776,42 @@ TypeId TypeSimplifier::intersectUnionWithType(TypeId left, TypeId right) LUAU_ASSERT(leftUnion); bool changed = false; - std::set newParts; - size_t maxSize = DFInt::LuauSimplificationComplexityLimit; + if (FFlag::LuauReduceSetTypeStackPressure) + { + if (leftUnion->options.size() > maxSize) + return addIntersection(arena, builtinTypes, {left, right}); + + UnionBuilder ub(arena, builtinTypes); + ub.reserve(leftUnion->options.size()); + + for (TypeId part : leftUnion) + { + TypeId simplified = intersect(right, part); + changed |= simplified != part; + + if (get(simplified)) + { + changed = true; + continue; + } + + ub.add(simplified); + + // Initial combination size check could not predict nested union iteration + if (ub.size() > maxSize) + return addIntersection(arena, builtinTypes, {left, right}); + } + + if (!changed) + return left; + + return ub.build(); + } + + std::set newParts; + if (leftUnion->options.size() > maxSize) return arena->addType(IntersectionType{{left, right}}); @@ -838,6 +862,26 @@ TypeId TypeSimplifier::intersectUnions(TypeId left, TypeId right) if (optionSize > maxSize) return arena->addType(IntersectionType{{left, right}}); + if (FFlag::LuauReduceSetTypeStackPressure) + { + UnionBuilder ub{arena, builtinTypes}; + for (TypeId leftPart : leftUnion) + { + for (TypeId rightPart : rightUnion) + { + TypeId simplified = intersect(leftPart, rightPart); + + ub.add(simplified); + + // Initial combination size check could not predict nested union iteration + if (ub.size() > maxSize) + return addIntersection(arena, builtinTypes, {left, right}); + } + } + + return ub.build(); + } + for (TypeId leftPart : leftUnion) { for (TypeId rightPart : rightUnion) @@ -933,14 +977,11 @@ std::optional TypeSimplifier::basicIntersectWithTruthy(TypeId target) co { target = follow(target); - if (FFlag::LuauRefineTablesWithReadType) - { - if (isApproximatelyTruthyType(target)) - return target; + if (isApproximatelyTruthyType(target)) + return target; - if (isApproximatelyFalsyType(target)) - return builtinTypes->neverType; - } + if (isApproximatelyFalsyType(target)) + return builtinTypes->neverType; if (is(target)) return builtinTypes->truthyType; @@ -978,14 +1019,11 @@ std::optional TypeSimplifier::basicIntersectWithFalsy(TypeId target) con { target = follow(target); - if (FFlag::LuauRefineTablesWithReadType) - { - if (isApproximatelyTruthyType(target)) - return builtinTypes->neverType; + if (isApproximatelyTruthyType(target)) + return builtinTypes->neverType; - if (isApproximatelyFalsyType(target)) - return target; - } + if (isApproximatelyFalsyType(target)) + return target; if (is(target)) return target; @@ -1033,7 +1071,6 @@ TypeId TypeSimplifier::intersectTypeWithNegation(TypeId left, TypeId right) { // ~(A | B) & C // (~A & C) & (~B & C) - bool changed = false; std::set newParts; @@ -1196,7 +1233,12 @@ TypeId TypeSimplifier::intersectIntersectionWithType(TypeId left, TypeId right) LUAU_ASSERT(leftIntersection); if (leftIntersection->parts.size() > (size_t)DFInt::LuauSimplificationComplexityLimit) - return arena->addType(IntersectionType{{left, right}}); + { + if (FFlag::LuauReduceSetTypeStackPressure) + return addIntersection(arena, builtinTypes, {left, right}); + else + return arena->addType(IntersectionType{{left, right}}); + } bool changed = false; std::set newParts; @@ -1355,42 +1397,21 @@ std::optional TypeSimplifier::basicIntersect(TypeId left, TypeId right) return std::nullopt; } - if (FFlag::LuauRefineTablesWithReadType) - { - if (isApproximatelyTruthyType(left)) - if (auto res = basicIntersectWithTruthy(right)) - return res; - - if (isApproximatelyTruthyType(right)) - if (auto res = basicIntersectWithTruthy(left)) - return res; + if (isApproximatelyTruthyType(left)) + if (auto res = basicIntersectWithTruthy(right)) + return res; - if (isApproximatelyFalsyType(left)) - if (auto res = basicIntersectWithFalsy(right)) - return res; + if (isApproximatelyTruthyType(right)) + if (auto res = basicIntersectWithTruthy(left)) + return res; - if (isApproximatelyFalsyType(right)) - if (auto res = basicIntersectWithFalsy(left)) - return res; - } - else - { - if (isTruthyType_DEPRECATED(left)) - if (auto res = basicIntersectWithTruthy(right)) - return res; + if (isApproximatelyFalsyType(left)) + if (auto res = basicIntersectWithFalsy(right)) + return res; - if (isTruthyType_DEPRECATED(right)) - if (auto res = basicIntersectWithTruthy(left)) - return res; - - if (isFalsyType_DEPRECATED(left)) - if (auto res = basicIntersectWithFalsy(right)) - return res; - - if (isFalsyType_DEPRECATED(right)) - if (auto res = basicIntersectWithFalsy(left)) - return res; - } + if (isApproximatelyFalsyType(right)) + if (auto res = basicIntersectWithFalsy(left)) + return res; Relation relation = relate(left, right); @@ -1458,13 +1479,19 @@ TypeId TypeSimplifier::intersect(TypeId left, TypeId right) if (isTypeVariable(left)) { blockedTypes.insert(left); - return arena->addType(IntersectionType{{left, right}}); + if (FFlag::LuauReduceSetTypeStackPressure) + return addIntersection(arena, builtinTypes, {left, right}); + else + return arena->addType(IntersectionType{{left, right}}); } if (isTypeVariable(right)) { blockedTypes.insert(right); - return arena->addType(IntersectionType{{left, right}}); + if (FFlag::LuauReduceSetTypeStackPressure) + return addIntersection(arena, builtinTypes, {left, right}); + else + return arena->addType(IntersectionType{{left, right}}); } if (auto ut = get(left)) @@ -1514,6 +1541,47 @@ TypeId TypeSimplifier::union_(TypeId left, TypeId right) if (auto leftUnion = get(left)) { bool changed = false; + + if (FFlag::LuauReduceSetTypeStackPressure) + { + UnionBuilder ub(arena, builtinTypes); + ub.reserve(leftUnion->options.size()); + for (TypeId part : leftUnion) + { + if (get(part)) + { + changed = true; + continue; + } + + Relation r = relate(part, right); + switch (r) + { + case Relation::Coincident: + case Relation::Superset: + return left; + case Relation::Subset: + ub.add(right); + changed = true; + break; + default: + ub.add(part); + ub.add(right); + changed = true; + break; + } + } + + if (!changed) + return left; + + // If the left-side is changed but has no parts, then the left-side union is uninhabited. + if (ub.size() == 0) + return right; + + return ub.build(); + } + std::set newParts; for (TypeId part : leftUnion) { @@ -1578,6 +1646,44 @@ TypeId TypeSimplifier::union_(TypeId left, TypeId right) } } + if (FFlag::LuauRefineDistributesOverUnions) + { + if (const auto [lt, rt] = get2(left, right); lt && rt) + { + if (1 == lt->props.size() && 1 == rt->props.size()) + { + const auto [propName, leftProp] = *begin(lt->props); + const auto [rightPropName, rightProp] = *begin(rt->props); + + if (rightPropName != propName) + return arena->addType(UnionType{{left, right}}); + + if (leftProp.readTy && rightProp.readTy) + { + Relation r = relate(*leftProp.readTy, *rightProp.readTy); + + switch (r) + { + case Relation::Disjoint: + { + TableType result; + result.state = TableState::Sealed; + result.props[propName] = union_(*leftProp.readTy, *rightProp.readTy); + return arena->addType(result); + } + case Relation::Superset: + case Relation::Coincident: + return left; + case Relation::Subset: + return right; + default: + break; + } + } + } + } + } + return arena->addType(UnionType{{left, right}}); } diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 2553d1c2..e6f59601 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -10,6 +10,7 @@ LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) namespace Luau { @@ -92,6 +93,7 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) clone.argNames = a.argNames; clone.isCheckedFunction = a.isCheckedFunction; clone.isDeprecatedFunction = a.isDeprecatedFunction; + clone.deprecatedInfo = a.deprecatedInfo; return dest.addType(std::move(clone)); } else if constexpr (std::is_same_v) @@ -299,7 +301,10 @@ std::pair Tarjan::indexify(TypeId ty) if (fresh) { index = int(nodes.size()); - nodes.push_back({ty, nullptr, false, false, index}); + if (FFlag::LuauEmplaceNotPushBack) + nodes.emplace_back(ty, nullptr, false, false, index); + else + nodes.push_back({ty, nullptr, false, false, index}); } return {index, fresh}; @@ -314,7 +319,10 @@ std::pair Tarjan::indexify(TypePackId tp) if (fresh) { index = int(nodes.size()); - nodes.push_back({nullptr, tp, false, false, index}); + if (FFlag::LuauEmplaceNotPushBack) + nodes.emplace_back(nullptr, tp, false, false, index); + else + nodes.push_back({nullptr, tp, false, false, index}); } return {index, fresh}; @@ -384,7 +392,10 @@ TarjanResult Tarjan::loop() { // Original recursion point, update the parent continuation point and start the new element worklist.back() = {index, currEdge + 1, lastEdge}; - worklist.push_back({childIndex, -1, -1}); + if (FFlag::LuauEmplaceNotPushBack) + worklist.emplace_back(childIndex, -1, -1); + else + worklist.push_back({childIndex, -1, -1}); // We need to continue the top-level loop from the start with the new worklist element foundFresh = true; @@ -442,7 +453,10 @@ TarjanResult Tarjan::visitRoot(TypeId ty) ty = log->follow(ty); auto [index, fresh] = indexify(ty); - worklist.push_back({index, -1, -1}); + if (FFlag::LuauEmplaceNotPushBack) + worklist.emplace_back(index, -1, -1); + else + worklist.push_back({index, -1, -1}); return loop(); } @@ -455,7 +469,10 @@ TarjanResult Tarjan::visitRoot(TypePackId tp) tp = log->follow(tp); auto [index, fresh] = indexify(tp); - worklist.push_back({index, -1, -1}); + if (FFlag::LuauEmplaceNotPushBack) + worklist.emplace_back(index, -1, -1); + else + worklist.push_back({index, -1, -1}); return loop(); } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 43f5b564..f2052b28 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -13,17 +13,19 @@ #include "Luau/Type.h" #include "Luau/TypeArena.h" #include "Luau/TypeFunction.h" +#include "Luau/TypeIds.h" #include "Luau/TypePack.h" #include "Luau/TypePath.h" #include "Luau/TypeUtils.h" LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) -LUAU_FASTFLAGVARIABLE(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAGVARIABLE(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity) +LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) namespace Luau { @@ -301,9 +303,12 @@ struct ApplyMappedGenerics : Substitution { NotNull builtinTypes; NotNull arena; + // TODO: make this NotNull when LuauSubtypingGenericsDoesntUseVariance is clipped + InternalErrorReporter* iceReporter; SubtypingEnvironment& env; + // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance ApplyMappedGenerics(NotNull builtinTypes, NotNull arena, SubtypingEnvironment& env) : Substitution(TxnLog::empty(), arena) , builtinTypes(builtinTypes) @@ -312,6 +317,20 @@ struct ApplyMappedGenerics : Substitution { } + ApplyMappedGenerics( + NotNull builtinTypes, + NotNull arena, + SubtypingEnvironment& env, + NotNull iceReporter + ) + : Substitution(TxnLog::empty(), arena) + , builtinTypes(builtinTypes) + , arena(arena) + , iceReporter(iceReporter.get()) + , env(env) + { + } + bool isDirty(TypeId ty) override { return env.containsMappedType(ty); @@ -324,15 +343,84 @@ struct ApplyMappedGenerics : Substitution TypeId clean(TypeId ty) override { - const auto& bounds = env.getMappedTypeBounds(ty); + 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()) + { + TypeIds boundsToUse; + + for (TypeId ub : upperBound) + { + // quick and dirty check to avoid adding generic types + if (!get(ub)) + boundsToUse.insert(ub); + } + + 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; + + 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()}); + } + else + { + LUAU_ASSERT(!"Unreachable path"); + return builtinTypes->unknownType; + } + } + else + { + const auto& bounds = env.getMappedTypeBounds_DEPRECATED(ty); - if (bounds.upperBound.empty()) - return builtinTypes->unknownType; + if (bounds.upperBound.empty()) + return builtinTypes->unknownType; - if (bounds.upperBound.size() == 1) - return *begin(bounds.upperBound); + if (bounds.upperBound.size() == 1) + return *begin(bounds.upperBound); - return arena->addType(IntersectionType{std::vector(begin(bounds.upperBound), end(bounds.upperBound))}); + return arena->addType(IntersectionType{std::vector(begin(bounds.upperBound), end(bounds.upperBound))}); + } } TypePackId clean(TypePackId tp) override @@ -350,6 +438,19 @@ struct ApplyMappedGenerics : Substitution if (get(ty)) return true; + if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + { + if (const FunctionType* f = get(ty)) + { + 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; + } + } + } + return ty->persistent; } bool ignoreChildren(TypePackId ty) override @@ -358,7 +459,19 @@ struct ApplyMappedGenerics : Substitution } }; -std::optional SubtypingEnvironment::applyMappedGenerics(NotNull builtinTypes, NotNull arena, TypeId ty) +std::optional SubtypingEnvironment::applyMappedGenerics( + NotNull builtinTypes, + NotNull arena, + TypeId ty, + 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); @@ -377,7 +490,12 @@ const TypeId* SubtypingEnvironment::tryFindSubstitution(TypeId ty) const const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pair subAndSuper) const { - if (auto it = ephemeralCache.find(subAndSuper)) + if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + { + if (const auto it = seenSetCache.find(subAndSuper)) + return it; + } + else if (auto it = ephemeralCache.find(subAndSuper)) return it; if (parent) @@ -388,13 +506,27 @@ const SubtypingResult* SubtypingEnvironment::tryFindSubtypingResult(std::pairempty()) + return true; - if (parent) - return parent->containsMappedType(ty); + if (parent) + return parent->containsMappedType(ty); - return false; + return false; + } + else + { + if (mappedGenerics_DEPRECATED.contains(ty)) + return true; + + if (parent) + return parent->containsMappedType(ty); + + return false; + } } bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const @@ -408,16 +540,32 @@ bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const return false; } -SubtypingEnvironment::GenericBounds& SubtypingEnvironment::getMappedTypeBounds(TypeId ty) +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()) + return bounds->back(); + + if (parent) + return parent->getMappedTypeBounds(ty, iceReporter); + + LUAU_ASSERT(!"Use containsMappedType before asking for bounds!"); + iceReporter->ice("Trying to access bounds for a type with no in-scope bounds"); +} + +SubtypingEnvironment::GenericBounds_DEPRECATED& SubtypingEnvironment::getMappedTypeBounds_DEPRECATED(TypeId ty) { - if (auto it = mappedGenerics.find(ty)) + LUAU_ASSERT(!FFlag::LuauSubtypingGenericsDoesntUseVariance); + if (auto it = mappedGenerics_DEPRECATED.find(ty)) return *it; if (parent) - return parent->getMappedTypeBounds(ty); + return parent->getMappedTypeBounds_DEPRECATED(ty); LUAU_ASSERT(!"Use containsMappedType before asking for bounds!"); - return mappedGenerics[ty]; + return mappedGenerics_DEPRECATED[ty]; } TypePackId* SubtypingEnvironment::getMappedPackBounds(TypePackId tp) @@ -463,44 +611,51 @@ 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) + for (const auto& [_, bounds] : env.mappedGenerics) + LUAU_ASSERT(bounds.empty()); + } + else + { + for (const auto& [subTy, bounds] : env.mappedGenerics_DEPRECATED) { - /* 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; - } - + const auto& lb = bounds.lowerBound; + const auto& ub = bounds.upperBound; + TypeId lowerBound = makeAggregateType(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(); + SubtypingEnvironment boundsEnv; + boundsEnv.parent = &env; + SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope); + boundsResult.reasoning.clear(); - result.andAlso(boundsResult); + result.andAlso(boundsResult); + } } /* TODO: We presently don't store subtype test results in the persistent @@ -526,9 +681,14 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull scope) +SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNull scope, std::optional> bindableGenerics) { SubtypingEnvironment env; + if (FFlag::LuauSubtypingGenericsDoesntUseVariance && bindableGenerics) + { + for (TypeId g : *bindableGenerics) + env.mappedGenerics[follow(g)] = {SubtypingEnvironment::GenericBounds{}}; + } SubtypingResult result = isCovariantWith(env, subTp, superTp, scope); @@ -538,6 +698,24 @@ SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNu result.mappedGenericPacks = std::move(env.mappedGenericPacks); } + if (FFlag::LuauSubtypingGenericsDoesntUseVariance && bindableGenerics) + { + 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 (!bounds->empty()) + result.andAlso(checkGenericBounds(bounds->back(), env, scope)); + } + } + } + return result; } @@ -550,7 +728,7 @@ SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult resu if (result.isCacheable) resultCache[p] = result; - else + else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance) env.ephemeralCache[p] = result; return result; @@ -668,7 +846,14 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub * For now, we do the conservative thing and refuse to cache anything * that touches a cycle. */ - return SubtypingResult{true, false, false}; + SubtypingResult res; + res.isSubtype = true; + res.isCacheable = false; + + if (FFlag::LuauSubtypingGenericsDoesntUseVariance) + env.seenSetCache[typePair] = res; + + return res; } SeenSetPopper ssp{&seenTypes, typePair}; @@ -728,13 +913,54 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result = {false}; else if (get(subTy)) result = {true}; - else if (auto subGeneric = get(subTy); subGeneric && variance == Variance::Covariant) + else if (auto subTypeFunctionInstance = get(subTy); + subTypeFunctionInstance && FFlag::LuauSubtypingGenericsDoesntUseVariance) + { + bool mappedGenericsApplied = false; + if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy, iceReporter)) + { + mappedGenericsApplied = *substSubTy != subTy; + subTypeFunctionInstance = get(*substSubTy); + } + + result = isCovariantWith(env, subTypeFunctionInstance, superTy, scope); + result.isCacheable = !mappedGenericsApplied; + } + else if (auto superTypeFunctionInstance = get(superTy); + superTypeFunctionInstance && FFlag::LuauSubtypingGenericsDoesntUseVariance) + { + bool mappedGenericsApplied = false; + if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy, iceReporter)) + { + mappedGenericsApplied = *substSuperTy != superTy; + superTypeFunctionInstance = get(*substSuperTy); + } + + result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope); + result.isCacheable = !mappedGenericsApplied; + } + else if (FFlag::LuauSubtypingGenericsDoesntUseVariance && (get(subTy) || get(superTy))) + { + if (const auto subBounds = env.mappedGenerics.find(subTy); subBounds && !subBounds->empty()) + { + bool ok = bindGeneric(env, subTy, superTy); + result.isSubtype = ok; + result.isCacheable = false; + } + else if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty()) + { + bool ok = bindGeneric(env, subTy, superTy); + result.isSubtype = ok; + result.isCacheable = false; + } + } + else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance && get(subTy) && variance == Variance::Covariant) { bool ok = bindGeneric(env, subTy, superTy); result.isSubtype = ok; result.isCacheable = false; } - else if (auto superGeneric = get(superTy); superGeneric && variance == Variance::Contravariant) + else if (!FFlag::LuauSubtypingGenericsDoesntUseVariance && get(superTy) && variance == Variance::Contravariant) { bool ok = bindGeneric(env, subTy, superTy); result.isSubtype = ok; @@ -818,14 +1044,16 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub } else if (auto subTypeFunctionInstance = get(subTy)) { - if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, 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)) { - if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy)) + LUAU_ASSERT(!FFlag::LuauSubtypingGenericsDoesntUseVariance); + if (auto substSuperTy = env.applyMappedGenerics_DEPRECATED(builtinTypes, arena, superTy)) superTypeFunctionInstance = get(*substSuperTy); result = isCovariantWith(env, subTy, superTypeFunctionInstance, scope); @@ -1107,6 +1335,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId { // (A...) -> number <: (...number) -> number bool ok = bindGeneric(env, *subTail, *superTail); + results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail)); } else @@ -1240,7 +1469,8 @@ SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy& template SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope) { - SubtypingResult result = isCovariantWith(env, subTy, superTy, scope).andAlso(isContravariantWith(env, subTy, superTy, scope)); + SubtypingResult result = isCovariantWith(env, subTy, superTy, scope); + result.andAlso(isContravariantWith(env, subTy, superTy, scope)); if (result.reasoning.empty()) result.reasoning.insert(SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invariant}); @@ -1750,6 +1980,23 @@ SubtypingResult Subtyping::isCovariantWith( ) { SubtypingResult result; + + if (FFlag::LuauSubtypingGenericsDoesntUseVariance && !subFunction->generics.empty()) + { + for (TypeId g : subFunction->generics) + { + g = follow(g); + if (get(g)) + { + if (auto bounds = env.mappedGenerics.find(g)) + // g may shadow an existing generic, so push a fresh set of bounds + bounds->emplace_back(); + else + env.mappedGenerics[g] = {SubtypingEnvironment::GenericBounds{}}; + } + } + } + { result.orElse( isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments) @@ -1771,18 +2018,31 @@ SubtypingResult Subtyping::isCovariantWith( result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes, scope).withBothComponent(TypePath::PackField::Returns)); - if (FFlag::LuauSubtypingCheckFunctionGenericCounts) + if (*subFunction->argTypes == *superFunction->argTypes && *subFunction->retTypes == *superFunction->retTypes) + { + if (superFunction->generics.size() != subFunction->generics.size()) + result.andAlso({false}).withError( + TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}} + ); + if (superFunction->genericPacks.size() != subFunction->genericPacks.size()) + result.andAlso({false}).withError( + TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}} + ); + } + if (FFlag::LuauSubtypingGenericsDoesntUseVariance && !subFunction->generics.empty()) { - if (*subFunction->argTypes == *superFunction->argTypes && *subFunction->retTypes == *superFunction->retTypes) + for (TypeId g : subFunction->generics) { - if (superFunction->generics.size() != subFunction->generics.size()) - result.andAlso({false}).withError( - TypeError{scope->location, GenericTypeCountMismatch{superFunction->generics.size(), subFunction->generics.size()}} - ); - if (superFunction->genericPacks.size() != subFunction->genericPacks.size()) - result.andAlso({false}).withError( - TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}} - ); + g = follow(g); + if (get(g)) + { + auto bounds = env.mappedGenerics.find(g); + LUAU_ASSERT(bounds && !bounds->empty()); + // Check the bounds are valid + result.andAlso(checkGenericBounds(bounds->back(), env, scope)); + + bounds->pop_back(); + } } } @@ -2084,25 +2344,76 @@ SubtypingResult Subtyping::isCovariantWith( bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId superTy) { - if (variance == Variance::Covariant) + if (FFlag::LuauSubtypingGenericsDoesntUseVariance) { - if (!get(subTy)) - return false; + subTy = follow(subTy); + superTy = follow(superTy); + std::optional originalSubTyBounds = std::nullopt; - if (!env.mappedGenerics.find(subTy) && env.containsMappedType(subTy)) + 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(); + + if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty()) + { + LUAU_ASSERT(get(superTy)); + + const auto& [lowerSuperBounds, upperSuperBounds] = superBounds->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"); - env.mappedGenerics[subTy].upperBound.insert(superTy); + if (const auto superBounds = env.mappedGenerics.find(superTy); superBounds && !superBounds->empty()) + { + LUAU_ASSERT(get(superTy)); + + 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); + } + else if (env.containsMappedType(superTy)) + iceReporter->ice("attempting to modify bounds of a potentially visited generic"); } else { - if (!get(superTy)) - return false; + if (variance == Variance::Covariant) + { + if (!get(subTy)) + return false; - if (!env.mappedGenerics.find(superTy) && env.containsMappedType(superTy)) - iceReporter->ice("attempting to modify bounds of a potentially visited generic"); + if (!env.mappedGenerics_DEPRECATED.find(subTy) && env.containsMappedType(subTy)) + iceReporter->ice("attempting to modify bounds of a potentially visited generic"); - env.mappedGenerics[superTy].lowerBound.insert(subTy); + env.mappedGenerics_DEPRECATED[subTy].upperBound.insert(superTy); + } + else + { + if (!get(superTy)) + return false; + + if (!env.mappedGenerics_DEPRECATED.find(superTy) && env.containsMappedType(superTy)) + iceReporter->ice("attempting to modify bounds of a potentially visited generic"); + + env.mappedGenerics_DEPRECATED[superTy].lowerBound.insert(subTy); + } } return true; @@ -2181,7 +2492,10 @@ std::pair Subtyping::handleTypeFunctionReductionResult(const T ErrorVec errors; if (result.blockedTypes.size() != 0 || result.blockedPacks.size() != 0) { - errors.push_back(TypeError{{}, UninhabitedTypeFunction{function}}); + if (FFlag::LuauEmplaceNotPushBack) + errors.emplace_back(Location{}, UninhabitedTypeFunction{function}); + else + errors.push_back(TypeError{{}, UninhabitedTypeFunction{function}}); return {builtinTypes->neverType, errors}; } if (result.reducedTypes.contains(function)) @@ -2210,4 +2524,128 @@ SubtypingResult Subtyping::trySemanticSubtyping(SubtypingEnvironment& env, return original; } +SubtypingResult Subtyping::checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull scope) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); + + SubtypingResult result{true}; + + const auto& [lb, ub] = bounds; + + TypeIds lbTypes; + for (TypeId t : lb) + { + t = follow(t); + if (const auto mappedBounds = env.mappedGenerics.find(t)) + { + if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it + continue; + + auto& [lowerBound, upperBound] = mappedBounds->back(); + // We're populating the lower bounds, so we prioritize the upper bounds of a mapped generic + if (!upperBound.empty()) + lbTypes.insert(upperBound.begin(), upperBound.end()); + else if (!lowerBound.empty()) + lbTypes.insert(lowerBound.begin(), lowerBound.end()); + else + lbTypes.insert(builtinTypes->unknownType); + } + else + lbTypes.insert(t); + } + + TypeIds ubTypes; + for (TypeId t : ub) + { + t = follow(t); + if (const auto mappedBounds = env.mappedGenerics.find(t)) + { + if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it + continue; + + auto& [lowerBound, upperBound] = mappedBounds->back(); + // We're populating the upper bounds, so we prioritize the lower bounds of a mapped generic + if (!lowerBound.empty()) + ubTypes.insert(lowerBound.begin(), lowerBound.end()); + else if (!upperBound.empty()) + ubTypes.insert(upperBound.begin(), upperBound.end()); + else + ubTypes.insert(builtinTypes->unknownType); + } + else + ubTypes.insert(t); + } + TypeId lowerBound = makeAggregateType(lbTypes.take(), builtinTypes->neverType); + TypeId upperBound = makeAggregateType(ubTypes.take(), 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); + + return result; +} + +void Subtyping::maybeUpdateBounds( + TypeId here, + TypeId there, + TypeIds& boundsToUpdate, + const TypeIds& firstBoundsToCheck, + const TypeIds& secondBoundsToCheck +) +{ + bool boundsChanged = false; + + if (!firstBoundsToCheck.empty()) + { + for (const TypeId t : firstBoundsToCheck) + { + if (t != here) // We don't want to bound a generic by itself, ie A <: A + { + boundsToUpdate.insert(t); + boundsChanged = true; + } + } + } + if (!boundsChanged && !secondBoundsToCheck.empty()) + { + for (const TypeId t : secondBoundsToCheck) + { + if (t != here) + { + boundsToUpdate.insert(t); + boundsChanged = true; + } + } + } + if (!boundsChanged && here != there) + boundsToUpdate.insert(there); +} + } // namespace Luau diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index 6c26449b..a5e395f5 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -13,8 +13,6 @@ #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" -LUAU_FASTFLAGVARIABLE(LuauWriteOnlyPropertyMangling) - namespace Luau { @@ -195,20 +193,10 @@ TypeId matchLiteralType( Property& prop = it->second; - if (FFlag::LuauWriteOnlyPropertyMangling) - { - // If the property is write-only, do nothing. - if (prop.isWriteOnly()) - continue; - } - else - { - // If we encounter a duplcate property, we may have already - // set it to be read-only. If that's the case, the only thing - // that will definitely crash is trying to access a write - // only property. - LUAU_ASSERT(!prop.isWriteOnly()); - } + // If the property is write-only, do nothing. + if (prop.isWriteOnly()) + continue; + TypeId propTy = *prop.readTy; auto it2 = expectedTableTy->props.find(keyStr); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 2f6a58e8..fb0786c1 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -2012,7 +2012,10 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) } else if constexpr (std::is_same_v) { - return tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\" ctx=" + std::to_string(int(c.context)); + std::string s = tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\" ctx=" + std::to_string(int(c.context)); + if (c.inConditional) + s += " (inConditional)"; + return s; } else if constexpr (std::is_same_v) { diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 397132fa..b48aeb92 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -29,7 +29,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticVisitType) LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticSetType) diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 7500b891..048fdba4 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -30,13 +30,14 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAGVARIABLE(LuauIceLess) @@ -1279,6 +1280,15 @@ void TypeChecker2::visit(AstStatTypeAlias* stat) if (!module->astScopes.contains(stat)) return; + if (FFlag::LuauNameConstraintRestrictRecursiveTypes) + { + if (const Scope* scope = findInnermostScope(stat->location)) + { + if (scope->isInvalidTypeAliasName(stat->name.value)) + reportError(RecursiveRestraintViolation{}, stat->location); + } + } + visitGenerics(stat->generics, stat->genericPacks); visit(stat->type); } @@ -1402,11 +1412,8 @@ void TypeChecker2::visit(AstExprConstantBool* expr) { if (!r.isSubtype) reportError(TypeMismatch{inferredType, bestType}, expr->location); - if (FFlag::LuauSubtypingCheckFunctionGenericCounts) - { - for (auto& e : r.errors) - e.location = expr->location; - } + for (auto& e : r.errors) + e.location = expr->location; reportErrors(r.errors); } } @@ -1436,11 +1443,8 @@ void TypeChecker2::visit(AstExprConstantString* expr) { if (!r.isSubtype) reportError(TypeMismatch{inferredType, bestType}, expr->location); - if (FFlag::LuauSubtypingCheckFunctionGenericCounts) - { - for (auto& e : r.errors) - e.location = expr->location; - } + for (auto& e : r.errors) + e.location = expr->location; reportErrors(r.errors); } } @@ -1511,11 +1515,10 @@ void TypeChecker2::visitCall(AstExprCall* call) fnTy = follow(*selectedOverloadTy); if (!isErrorSuppressing(call->location, *selectedOverloadTy)) - if (FFlag::LuauSubtypingCheckFunctionGenericCounts) - { - for (auto& e : result.errors) - e.location = call->location; - } + { + for (auto& e : result.errors) + e.location = call->location; + } reportErrors(std::move(result.errors)); if (result.normalizationTooComplex) { @@ -1757,7 +1760,11 @@ void TypeChecker2::visitCall(AstExprCall* call) void TypeChecker2::visit(AstExprCall* call) { + std::optional flipper; + if (FFlag::LuauResetConditionalContextProperly) + flipper.emplace(&typeContext, TypeContext::Default); visit(call->func, ValueContext::RValue); + flipper.reset(); for (AstExpr* arg : call->args) visit(arg, ValueContext::RValue); @@ -1917,6 +1924,10 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context) void TypeChecker2::visit(AstExprFunction* fn) { + std::optional flipper; + if (FFlag::LuauResetConditionalContextProperly) + flipper.emplace(&typeContext, TypeContext::Default); + auto StackPusher = pushStack(fn); visitGenerics(fn->generics, fn->genericPacks); @@ -2070,6 +2081,10 @@ void TypeChecker2::visit(AstExprFunction* fn) void TypeChecker2::visit(AstExprTable* expr) { + std::optional inContext; + if (FFlag::LuauResetConditionalContextProperly) + inContext.emplace(&typeContext, TypeContext::Default); + for (const AstExprTable::Item& item : expr->items) { if (item.key) @@ -2080,6 +2095,10 @@ void TypeChecker2::visit(AstExprTable* expr) void TypeChecker2::visit(AstExprUnary* expr) { + std::optional inContext; + if (FFlag::LuauResetConditionalContextProperly && expr->op != AstExprUnary::Op::Not) + inContext.emplace(&typeContext, TypeContext::Default); + visit(expr->expr, ValueContext::RValue); TypeId operandType = lookupType(expr->expr); @@ -2171,6 +2190,13 @@ void TypeChecker2::visit(AstExprUnary* expr) TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) { + std::optional inContext; + if (FFlag::LuauResetConditionalContextProperly) + { + if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or && expr->op != AstExprBinary::CompareEq && expr->op != AstExprBinary::CompareNe) + inContext.emplace(&typeContext, TypeContext::Default); + } + visit(expr->left, ValueContext::RValue); visit(expr->right, ValueContext::RValue); @@ -2550,7 +2576,10 @@ void TypeChecker2::visit(AstExprTypeAssertion* expr) void TypeChecker2::visit(AstExprIfElse* expr) { - // TODO! + std::optional inContext; + if (FFlag::LuauResetConditionalContextProperly) + inContext.emplace(&typeContext, TypeContext::Default); + visit(expr->condition, ValueContext::RValue); visit(expr->trueExpr, ValueContext::RValue); visit(expr->falseExpr, ValueContext::RValue); @@ -2558,6 +2587,10 @@ void TypeChecker2::visit(AstExprIfElse* expr) void TypeChecker2::visit(AstExprInterpString* interpString) { + std::optional inContext; + if (FFlag::LuauResetConditionalContextProperly) + inContext.emplace(&typeContext, TypeContext::Default); + for (AstExpr* expr : interpString->expressions) visit(expr, ValueContext::RValue); } @@ -3206,11 +3239,10 @@ bool TypeChecker2::testIsSubtype(TypeId subTy, TypeId superTy, Location location SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); if (!isErrorSuppressing(location, subTy)) - if (FFlag::LuauSubtypingCheckFunctionGenericCounts) - { - for (auto& e : r.errors) - e.location = location; - } + { + for (auto& e : r.errors) + e.location = location; + } reportErrors(std::move(r.errors)); if (r.normalizationTooComplex) reportError(NormalizationTooComplex{}, location); @@ -3227,11 +3259,10 @@ bool TypeChecker2::testIsSubtype(TypePackId subTy, TypePackId superTy, Location SubtypingResult r = subtyping->isSubtype(subTy, superTy, scope); if (!isErrorSuppressing(location, subTy)) - if (FFlag::LuauSubtypingCheckFunctionGenericCounts) - { - for (auto& e : r.errors) - e.location = location; - } + { + for (auto& e : r.errors) + e.location = location; + } reportErrors(std::move(r.errors)); if (r.normalizationTooComplex) reportError(NormalizationTooComplex{}, location); diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 4cabcc74..c0f0e353 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -35,11 +35,6 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) -LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) -LUAU_FASTFLAGVARIABLE(LuauOccursCheckForRefinement) -LUAU_FASTFLAG(LuauRefineTablesWithReadType) -LUAU_FASTFLAGVARIABLE(LuauEmptyStringInKeyOf) -LUAU_FASTFLAGVARIABLE(LuauAvoidExcessiveTypeCopying) namespace Luau { diff --git a/Analysis/src/TypeFunctionReductionGuesser.cpp b/Analysis/src/TypeFunctionReductionGuesser.cpp index f74249d1..12fcaa16 100644 --- a/Analysis/src/TypeFunctionReductionGuesser.cpp +++ b/Analysis/src/TypeFunctionReductionGuesser.cpp @@ -11,9 +11,9 @@ #include "Luau/VecDeque.h" #include "Luau/VisitType.h" -#include #include -#include + +LUAU_FASTFLAG(LuauEmplaceNotPushBack) namespace Luau { @@ -169,7 +169,10 @@ TypeFunctionReductionGuessResult TypeFunctionReductionGuesser::guessTypeFunction if (get(guess)) continue; - results.push_back({local->name.value, guess}); + if (FFlag::LuauEmplaceNotPushBack) + results.emplace_back(local->name.value, guess); + else + results.push_back({local->name.value, guess}); } // Submit a guess for return types diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index 79d9ff35..e9b09d47 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -19,6 +19,7 @@ // used to control the recursion limit of any operations done by user-defined type functions // currently, controls serialization, deserialization, and `type.copy` LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFunctionSerdeIterationLimit, 100'000); +LUAU_FASTFLAG(LuauEmplaceNotPushBack) namespace Luau { @@ -470,12 +471,35 @@ struct SerializedGeneric bool isNamed = false; std::string name; T type = nullptr; + + explicit SerializedGeneric(std::string name) + : name(std::move(name)) + { + } + + SerializedGeneric(bool isNamed, std::string name, T type) + : isNamed(isNamed) + , name(std::move(name)) + , type(std::move(type)) + { + } }; struct SerializedFunctionScope { size_t oldQueueSize = 0; TypeFunctionFunctionType* function = nullptr; + + explicit SerializedFunctionScope(size_t oldQueueSize) + : oldQueueSize(oldQueueSize) + { + } + + SerializedFunctionScope(size_t oldQueueSize, TypeFunctionFunctionType* function) + : oldQueueSize(oldQueueSize) + , function(function) + { + } }; // Complete inverse of TypeFunctionSerializer @@ -901,7 +925,10 @@ class TypeFunctionDeserializer void deserializeChildren(TypeFunctionFunctionType* f2, FunctionType* f1) { - functionScopes.push_back({queue.size(), f2}); + if (FFlag::LuauEmplaceNotPushBack) + functionScopes.emplace_back(queue.size(), f2); + else + functionScopes.push_back({queue.size(), f2}); std::set> genericNames; @@ -923,7 +950,10 @@ class TypeFunctionDeserializer genericNames.insert(nameKey); TypeId mapping = state->ctx->arena->addTV(Type(gty->isNamed ? GenericType{state->ctx->scope.get(), gty->name} : GenericType{})); - genericTypes.push_back({gty->isNamed, gty->name, mapping}); + if (FFlag::LuauEmplaceNotPushBack) + genericTypes.emplace_back(gty->isNamed, gty->name, mapping); + else + genericTypes.push_back({gty->isNamed, gty->name, mapping}); } for (auto tp : f2->genericPacks) @@ -944,7 +974,10 @@ class TypeFunctionDeserializer TypePackId mapping = state->ctx->arena->addTypePack(TypePackVar(gtp->isNamed ? GenericTypePack{state->ctx->scope.get(), gtp->name} : GenericTypePack{})); - genericPacks.push_back({gtp->isNamed, gtp->name, mapping}); + if (FFlag::LuauEmplaceNotPushBack) + genericPacks.emplace_back(gtp->isNamed, gtp->name, mapping); + else + genericPacks.push_back({gtp->isNamed, gtp->name, mapping}); } f1->generics.reserve(f2->generics.size()); diff --git a/Analysis/src/TypeIds.cpp b/Analysis/src/TypeIds.cpp index 6c379f76..1366a35e 100644 --- a/Analysis/src/TypeIds.cpp +++ b/Analysis/src/TypeIds.cpp @@ -157,4 +157,10 @@ std::vector TypeIds::take() return std::move(order); } +void TypeIds::reserve(size_t n) +{ + order.reserve(n); +} + + } // namespace Luau diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index accd037d..36dc9cb6 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -14,7 +14,6 @@ #include "Luau/TimeTrace.h" #include "Luau/TopoSortStatements.h" #include "Luau/ToString.h" -#include "Luau/ToString.h" #include "Luau/Type.h" #include "Luau/TypePack.h" #include "Luau/TypeUtils.h" @@ -33,6 +32,8 @@ LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) +LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) namespace Luau { @@ -1852,6 +1853,16 @@ 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) + { + ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); + } + } + Name fnName(global.name.value); currentModule->declaredGlobals[fnName] = fnType; @@ -3930,6 +3941,16 @@ 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) + { + ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); + } + } + return std::make_pair(funTy, funScope); } @@ -5768,6 +5789,16 @@ 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) + { + ftv->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); + } + } + return fnType; } else if (auto typeOf = annotation.as()) @@ -5913,7 +5944,10 @@ TypeId TypeChecker::instantiateTypeFun( } if (applyTypeFunction.encounteredForwardedType) { - reportError(TypeError{location, GenericError{"Recursive type being used with different parameters"}}); + if (FFlag::LuauNameConstraintRestrictRecursiveTypes) + reportError(TypeError{location, RecursiveRestraintViolation{}}); + else + reportError(TypeError{location, GenericError{"Recursive type being used with different parameters"}}); return errorRecoveryType(scope); } diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index b1b83d55..54ceed7b 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) // 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 @@ -168,85 +169,127 @@ Path PathBuilder::build() PathBuilder& PathBuilder::readProp(std::string name) { - components.push_back(Property{std::move(name), true}); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(Property{std::move(name), true}); + else + components.push_back(Property{std::move(name), true}); return *this; } PathBuilder& PathBuilder::writeProp(std::string name) { - components.push_back(Property{std::move(name), false}); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(Property{std::move(name), false}); + else + components.push_back(Property{std::move(name), false}); return *this; } PathBuilder& PathBuilder::prop(std::string name) { - components.push_back(Property{std::move(name)}); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(Property{std::move(name)}); + else + components.push_back(Property{std::move(name)}); return *this; } PathBuilder& PathBuilder::index(size_t i) { - components.push_back(Index{i}); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(Index{i}); + else + components.push_back(Index{i}); return *this; } PathBuilder& PathBuilder::mt() { - components.push_back(TypeField::Metatable); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(TypeField::Metatable); + else + components.push_back(TypeField::Metatable); return *this; } PathBuilder& PathBuilder::lb() { - components.push_back(TypeField::LowerBound); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(TypeField::LowerBound); + else + components.push_back(TypeField::LowerBound); return *this; } PathBuilder& PathBuilder::ub() { - components.push_back(TypeField::UpperBound); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(TypeField::UpperBound); + else + components.push_back(TypeField::UpperBound); return *this; } PathBuilder& PathBuilder::indexKey() { - components.push_back(TypeField::IndexLookup); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(TypeField::IndexLookup); + else + components.push_back(TypeField::IndexLookup); return *this; } PathBuilder& PathBuilder::indexValue() { - components.push_back(TypeField::IndexResult); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(TypeField::IndexResult); + else + components.push_back(TypeField::IndexResult); return *this; } PathBuilder& PathBuilder::negated() { - components.push_back(TypeField::Negated); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(TypeField::Negated); + else + components.push_back(TypeField::Negated); return *this; } PathBuilder& PathBuilder::variadic() { - components.push_back(TypeField::Variadic); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(TypeField::Variadic); + else + components.push_back(TypeField::Variadic); return *this; } PathBuilder& PathBuilder::args() { - components.push_back(PackField::Arguments); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(PackField::Arguments); + else + components.push_back(PackField::Arguments); return *this; } PathBuilder& PathBuilder::rets() { - components.push_back(PackField::Returns); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(PackField::Returns); + else + components.push_back(PackField::Returns); return *this; } PathBuilder& PathBuilder::tail() { - components.push_back(PackField::Tail); + if (FFlag::LuauEmplaceNotPushBack) + components.emplace_back(PackField::Tail); + else + components.push_back(PackField::Tail); return *this; } diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index fdba2483..0a6c3340 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -13,6 +13,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) namespace Luau { @@ -96,6 +98,8 @@ std::optional findTableProperty(NotNull builtinTypes, Er } else if (get(index)) return builtinTypes->anyType; + else if (FFlag::LuauEmplaceNotPushBack) + errors.emplace_back(location, GenericError{"__index should either be a function or table. Got " + toString(index)}); else errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}}); @@ -127,14 +131,17 @@ std::optional findMetatableEntry( const TableType* mtt = getTableType(unwrapped); if (!mtt) { - errors.push_back(TypeError{location, GenericError{"Metatable was not a table"}}); + if (FFlag::LuauEmplaceNotPushBack) + errors.emplace_back(location, GenericError{"Metatable was not a table"}); + else + errors.push_back(TypeError{location, GenericError{"Metatable was not a table"}}); return std::nullopt; } auto it = mtt->props.find(entry); if (it != mtt->props.end()) { - if (FFlag::LuauSolverV2) + if (FFlag::LuauTidyTypeUtils || FFlag::LuauSolverV2) { if (it->second.readTy) return it->second.readTy; @@ -176,7 +183,7 @@ std::optional findTablePropertyRespectingMeta( const auto& it = tableType->props.find(name); if (it != tableType->props.end()) { - if (FFlag::LuauSolverV2) + if (FFlag::LuauTidyTypeUtils || FFlag::LuauSolverV2) { switch (context) { @@ -231,6 +238,8 @@ std::optional findTablePropertyRespectingMeta( } else if (get(index)) return builtinTypes->anyType; + else if (FFlag::LuauEmplaceNotPushBack) + errors.emplace_back(location, GenericError{"__index should either be a function or table. Got " + toString(index)}); else errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}}); @@ -338,10 +347,10 @@ TypePack extendTypePack( TypePack newPack; newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauTidyTypeUtils) trackInteriorFreeTypePack(ftp->scope, *newPack.tail); - if (FFlag::LuauSolverV2) + if (FFlag::LuauTidyTypeUtils || FFlag::LuauSolverV2) result.tail = newPack.tail; size_t overridesIndex = 0; while (result.head.size() < length) @@ -353,7 +362,7 @@ TypePack extendTypePack( } else { - if (FFlag::LuauSolverV2) + if (FFlag::LuauTidyTypeUtils || FFlag::LuauSolverV2) { FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity}; t = arena.addType(ft); @@ -760,6 +769,131 @@ bool isApproximatelyTruthyType(TypeId ty) return false; } +UnionBuilder::UnionBuilder(NotNull arena, NotNull builtinTypes) + : arena(arena) + , builtinTypes(builtinTypes) +{ +} + +void UnionBuilder::add(TypeId ty) +{ + ty = follow(ty); + + if (is(ty) || isTop) + return; + + if (is(ty)) + { + isTop = true; + return; + } + + if (auto utv = get(ty)) + { + for (auto option : utv) + options.insert(option); + } + else + options.insert(ty); +} + +TypeId UnionBuilder::build() +{ + if (isTop) + return builtinTypes->unknownType; + + if (options.empty()) + return builtinTypes->neverType; + + if (options.size() == 1) + return options.front(); + + return arena->addType(UnionType{options.take()}); +} + +size_t UnionBuilder::size() const +{ + return options.size(); +} + +void UnionBuilder::reserve(size_t size) +{ + options.reserve(size); +} + +IntersectionBuilder::IntersectionBuilder(NotNull arena, NotNull builtinTypes) + : arena(arena) + , builtinTypes(builtinTypes) +{ +} + +void IntersectionBuilder::add(TypeId ty) +{ + ty = follow(ty); + + if (is(ty)) + { + isBottom = true; + return; + } + + if (is(ty)) + return; + + if (auto itv = get(ty)) + { + for (auto part : itv) + parts.insert(part); + } + else + parts.insert(ty); +} + +TypeId IntersectionBuilder::build() +{ + if (isBottom) + return builtinTypes->neverType; + + if (parts.empty()) + return builtinTypes->unknownType; + + if (parts.size() == 1) + return parts.front(); + + return arena->addType(IntersectionType{parts.take()}); +} + +size_t IntersectionBuilder::size() const +{ + return parts.size(); +} + +void IntersectionBuilder::reserve(size_t size) +{ + parts.reserve(size); +} + + +TypeId addIntersection(NotNull arena, NotNull builtinTypes, std::initializer_list list) +{ + IntersectionBuilder ib(arena, builtinTypes); + ib.reserve(list.size()); + for (TypeId part : list) + ib.add(part); + + return ib.build(); +} + +TypeId addUnion(NotNull arena, NotNull builtinTypes, std::initializer_list list) +{ + UnionBuilder ub(arena, builtinTypes); + ub.reserve(list.size()); + for (TypeId option : list) + ub.add(option); + return ub.build(); +} + + } // namespace Luau diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index 6c6b901a..cd27bdb2 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -17,9 +17,13 @@ #include #include +LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) + LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauRefineTablesWithReadType) +LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAGVARIABLE(LuauLimitUnification) +LUAU_FASTFLAGVARIABLE(LuauUnifyShortcircuitSomeIntersectionsAndUnions) namespace Luau { @@ -131,23 +135,43 @@ Unifier2::Unifier2( { } -bool Unifier2::unify(TypeId subTy, TypeId superTy) +UnifyResult Unifier2::unify(TypeId subTy, TypeId superTy) +{ + iterationCount = 0; + return unify_(subTy, superTy); +} + +UnifyResult Unifier2::unify(TypePackId subTp, TypePackId superTp) +{ + iterationCount = 0; + return unify_(subTp, superTp); +} + +UnifyResult Unifier2::unify_(TypeId subTy, TypeId superTy) { + if (FFlag::LuauLimitUnification) + { + if (FInt::LuauTypeInferIterationLimit > 0 && iterationCount >= FInt::LuauTypeInferIterationLimit) + return UnifyResult::TooComplex; + + ++iterationCount; + } + subTy = follow(subTy); superTy = follow(superTy); if (auto subGen = genericSubstitutions.find(subTy)) - return unify(*subGen, superTy); + return unify_(*subGen, superTy); if (auto superGen = genericSubstitutions.find(superTy)) - return unify(subTy, *superGen); + return unify_(subTy, *superGen); if (seenTypePairings.contains({subTy, superTy})) - return true; + return UnifyResult::Ok; seenTypePairings.insert({subTy, superTy}); if (subTy == superTy) - return true; + return UnifyResult::Ok; // We have potentially done some unifications while dispatching either `SubtypeConstraint` or `PackSubtypeConstraint`, // so rather than implementing backtracking or traversing the entire type graph multiple times, we could push @@ -159,10 +183,13 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) if ((isIrresolvable(subTy) || isIrresolvable(superTy)) && !get(subTy) && !get(superTy)) { if (uninhabitedTypeFunctions && (uninhabitedTypeFunctions->contains(subTy) || uninhabitedTypeFunctions->contains(superTy))) - return true; + return UnifyResult::Ok; - incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy}); - return true; + if (FFlag::LuauEmplaceNotPushBack) + incompleteSubtypes.emplace_back(SubtypeConstraint{subTy, superTy}); + else + incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy}); + return UnifyResult::Ok; } FreeType* subFree = getMutable(subTy); @@ -179,44 +206,44 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) } if (subFree || superFree) - return true; + return UnifyResult::Ok; auto subFn = get(subTy); auto superFn = get(superTy); if (subFn && superFn) - return unify(subTy, superFn); + return unify_(subTy, superFn); auto subUnion = get(subTy); auto superUnion = get(superTy); if (subUnion) - return unify(subUnion, superTy); + return unify_(subUnion, superTy); else if (superUnion) - return unify(subTy, superUnion); + return unify_(subTy, superUnion); auto subIntersection = get(subTy); auto superIntersection = get(superTy); if (subIntersection) - return unify(subIntersection, superTy); + return unify_(subIntersection, superTy); else if (superIntersection) - return unify(subTy, superIntersection); + return unify_(subTy, superIntersection); auto subNever = get(subTy); auto superNever = get(superTy); if (subNever && superNever) - return true; + return UnifyResult::Ok; else if (subNever && superFn) { // If `never` is the subtype, then we can propagate that inward. - bool argResult = unify(superFn->argTypes, builtinTypes->neverTypePack); - bool retResult = unify(builtinTypes->neverTypePack, superFn->retTypes); - return argResult && retResult; + UnifyResult argResult = unify_(superFn->argTypes, builtinTypes->neverTypePack); + UnifyResult retResult = unify_(builtinTypes->neverTypePack, superFn->retTypes); + return argResult & retResult; } else if (subFn && superNever) { // If `never` is the supertype, then we can propagate that inward. - bool argResult = unify(builtinTypes->neverTypePack, subFn->argTypes); - bool retResult = unify(subFn->retTypes, builtinTypes->neverTypePack); - return argResult && retResult; + UnifyResult argResult = unify_(builtinTypes->neverTypePack, subFn->argTypes); + UnifyResult retResult = unify_(subFn->retTypes, builtinTypes->neverTypePack); + return argResult & retResult; } auto subAny = get(subTy); @@ -226,15 +253,15 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) auto superTable = get(superTy); if (subAny && superAny) - return true; + return UnifyResult::Ok; else if (subAny && superFn) - return unify(subAny, superFn); + return unify_(subAny, superFn); else if (subFn && superAny) - return unify(subFn, superAny); + return unify_(subFn, superAny); else if (subAny && superTable) - return unify(subAny, superTable); + return unify_(subAny, superTable); else if (subTable && superAny) - return unify(subTable, superAny); + return unify_(subTable, superAny); if (subTable && superTable) { @@ -245,35 +272,35 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy) LUAU_ASSERT(!subTable->boundTo); LUAU_ASSERT(!superTable->boundTo); - return unify(subTable, superTable); + return unify_(subTable, superTable); } auto subMetatable = get(subTy); auto superMetatable = get(superTy); if (subMetatable && superMetatable) - return unify(subMetatable, superMetatable); + return unify_(subMetatable, superMetatable); else if (subMetatable && superAny) - return unify(subMetatable, superAny); + return unify_(subMetatable, superAny); else if (subAny && superMetatable) - return unify(subAny, superMetatable); + return unify_(subAny, superMetatable); else if (subMetatable) // if we only have one metatable, unify with the inner table - return unify(subMetatable->table, superTy); + return unify_(subMetatable->table, superTy); else if (superMetatable) // if we only have one metatable, unify with the inner table - return unify(subTy, superMetatable->table); + return unify_(subTy, superMetatable->table); auto [subNegation, superNegation] = get2(subTy, superTy); if (subNegation && superNegation) - return unify(subNegation->ty, superNegation->ty); + return unify_(subNegation->ty, superNegation->ty); // The unification failed, but we're not doing type checking. - return true; + return UnifyResult::Ok; } // If superTy is a function and subTy already has a // potentially-compatible function in its upper bound, we assume that // the function is not overloaded and attempt to combine superTy into // subTy's existing function bound. -bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy) +UnifyResult Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy) { FreeType* subFree = getMutable(subTy); LUAU_ASSERT(subFree); @@ -282,13 +309,13 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy) { subFree->upperBound = mkIntersection(subFree->upperBound, superTy); expandedFreeTypes[subTy].push_back(superTy); - return true; + return UnifyResult::Ok; }; TypeId upperBound = follow(subFree->upperBound); if (get(upperBound)) - return unify(subFree->upperBound, superTy); + return unify_(subFree->upperBound, superTy); const FunctionType* superFunction = get(superTy); if (!superFunction) @@ -302,7 +329,7 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy) if (!upperBoundIntersection) return doDefault(); - bool ok = true; + UnifyResult result = UnifyResult::Ok; bool foundOne = false; for (TypeId part : upperBoundIntersection->parts) @@ -316,17 +343,17 @@ bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy) if (!subArgTail && subArgHead.size() == superArgHead.size()) { foundOne = true; - ok &= unify(part, superTy); + result &= unify_(part, superTy); } } if (foundOne) - return ok; + return result; else return doDefault(); } -bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) +UnifyResult Unifier2::unify_(TypeId subTy, const FunctionType* superFn) { const FunctionType* subFn = get(subTy); @@ -359,64 +386,86 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) } } - bool argResult = unify(superFn->argTypes, subFn->argTypes); - bool retResult = unify(subFn->retTypes, superFn->retTypes); - return argResult && retResult; + UnifyResult argResult = unify_(superFn->argTypes, subFn->argTypes); + UnifyResult retResult = unify_(subFn->retTypes, superFn->retTypes); + return argResult & retResult; } -bool Unifier2::unify(const UnionType* subUnion, TypeId superTy) +UnifyResult Unifier2::unify_(const UnionType* subUnion, TypeId superTy) { - bool result = true; + UnifyResult result = UnifyResult::Ok; // if the occurs check fails for any option, it fails overall for (auto subOption : subUnion->options) { if (areCompatible(subOption, superTy)) - result &= unify(subOption, superTy); + result &= unify_(subOption, superTy); } return result; } -bool Unifier2::unify(TypeId subTy, const UnionType* superUnion) +UnifyResult Unifier2::unify_(TypeId subTy, const UnionType* superUnion) { - bool result = true; + 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) + { + if (subTy == superOption) + return UnifyResult::Ok; + } + } + + UnifyResult result = UnifyResult::Ok; // if the occurs check fails for any option, it fails overall for (auto superOption : superUnion->options) { if (areCompatible(subTy, superOption)) - result &= unify(subTy, superOption); + result &= unify_(subTy, superOption); } return result; } -bool Unifier2::unify(const IntersectionType* subIntersection, TypeId superTy) +UnifyResult Unifier2::unify_(const IntersectionType* subIntersection, TypeId superTy) { - bool result = true; + 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) + { + if (superTy == subOption) + return UnifyResult::Ok; + } + } + + UnifyResult result = UnifyResult::Ok; // if the occurs check fails for any part, it fails overall for (auto subPart : subIntersection->parts) - result &= unify(subPart, superTy); + result &= unify_(subPart, superTy); return result; } -bool Unifier2::unify(TypeId subTy, const IntersectionType* superIntersection) +UnifyResult Unifier2::unify_(TypeId subTy, const IntersectionType* superIntersection) { - bool result = true; + UnifyResult result = UnifyResult::Ok; // if the occurs check fails for any part, it fails overall for (auto superPart : superIntersection->parts) - result &= unify(subTy, superPart); + result &= unify_(subTy, superPart); return result; } -bool Unifier2::unify(TableType* subTable, const TableType* superTable) +UnifyResult Unifier2::unify_(TableType* subTable, const TableType* superTable) { - bool result = true; + UnifyResult result = UnifyResult::Ok; // It suffices to only check one direction of properties since we'll only ever have work to do during unification // if the property is present in both table types. @@ -429,10 +478,10 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable) const Property& superProp = superPropOpt->second; if (subProp.readTy && superProp.readTy) - result &= unify(*subProp.readTy, *superProp.readTy); + result &= unify_(*subProp.readTy, *superProp.readTy); if (subProp.writeTy && superProp.writeTy) - result &= unify(*superProp.writeTy, *subProp.writeTy); + result &= unify_(*superProp.writeTy, *subProp.writeTy); } } @@ -441,7 +490,7 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable) while (subTypeParamsIter != subTable->instantiatedTypeParams.end() && superTypeParamsIter != superTable->instantiatedTypeParams.end()) { - result &= unify(*subTypeParamsIter, *superTypeParamsIter); + result &= unify_(*subTypeParamsIter, *superTypeParamsIter); subTypeParamsIter++; superTypeParamsIter++; @@ -453,7 +502,7 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable) while (subTypePackParamsIter != subTable->instantiatedTypePackParams.end() && superTypePackParamsIter != superTable->instantiatedTypePackParams.end()) { - result &= unify(*subTypePackParamsIter, *superTypePackParamsIter); + result &= unify_(*subTypePackParamsIter, *superTypePackParamsIter); subTypePackParamsIter++; superTypePackParamsIter++; @@ -461,13 +510,13 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable) if (subTable->indexer && superTable->indexer) { - result &= unify(subTable->indexer->indexType, superTable->indexer->indexType); - result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType); + result &= unify_(subTable->indexer->indexType, superTable->indexer->indexType); + result &= unify_(subTable->indexer->indexResultType, superTable->indexer->indexResultType); if (FFlag::LuauEagerGeneralization4) { // FIXME: We can probably do something more efficient here. - result &= unify(superTable->indexer->indexType, subTable->indexer->indexType); - result &= unify(superTable->indexer->indexResultType, subTable->indexer->indexResultType); + result &= unify_(superTable->indexer->indexType, subTable->indexer->indexType); + result &= unify_(superTable->indexer->indexResultType, subTable->indexer->indexResultType); } } @@ -498,104 +547,126 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable) return result; } -bool Unifier2::unify(const MetatableType* subMetatable, const MetatableType* superMetatable) +UnifyResult Unifier2::unify_(const MetatableType* subMetatable, const MetatableType* superMetatable) { - return unify(subMetatable->metatable, superMetatable->metatable) && unify(subMetatable->table, superMetatable->table); + UnifyResult metatableResult = unify_(subMetatable->metatable, superMetatable->metatable); + if (metatableResult != UnifyResult::Ok) + return metatableResult; + return unify_(subMetatable->table, superMetatable->table); } -bool Unifier2::unify(const AnyType* subAny, const FunctionType* superFn) +UnifyResult Unifier2::unify_(const AnyType* subAny, const FunctionType* superFn) { // If `any` is the subtype, then we can propagate that inward. - bool argResult = unify(superFn->argTypes, builtinTypes->anyTypePack); - bool retResult = unify(builtinTypes->anyTypePack, superFn->retTypes); - return argResult && retResult; + UnifyResult argResult = unify_(superFn->argTypes, builtinTypes->anyTypePack); + UnifyResult retResult = unify_(builtinTypes->anyTypePack, superFn->retTypes); + return argResult & retResult; } -bool Unifier2::unify(const FunctionType* subFn, const AnyType* superAny) +UnifyResult Unifier2::unify_(const FunctionType* subFn, const AnyType* superAny) { // If `any` is the supertype, then we can propagate that inward. - bool argResult = unify(builtinTypes->anyTypePack, subFn->argTypes); - bool retResult = unify(subFn->retTypes, builtinTypes->anyTypePack); - return argResult && retResult; + UnifyResult argResult = unify_(builtinTypes->anyTypePack, subFn->argTypes); + UnifyResult retResult = unify_(subFn->retTypes, builtinTypes->anyTypePack); + return argResult & retResult; } -bool Unifier2::unify(const AnyType* subAny, const TableType* superTable) +UnifyResult Unifier2::unify_(const AnyType* subAny, const TableType* superTable) { for (const auto& [propName, prop] : superTable->props) { if (prop.readTy) - unify(builtinTypes->anyType, *prop.readTy); + unify_(builtinTypes->anyType, *prop.readTy); if (prop.writeTy) - unify(*prop.writeTy, builtinTypes->anyType); + unify_(*prop.writeTy, builtinTypes->anyType); } if (superTable->indexer) { - unify(builtinTypes->anyType, superTable->indexer->indexType); - unify(builtinTypes->anyType, superTable->indexer->indexResultType); + unify_(builtinTypes->anyType, superTable->indexer->indexType); + unify_(builtinTypes->anyType, superTable->indexer->indexResultType); } - return true; + return UnifyResult::Ok; } -bool Unifier2::unify(const TableType* subTable, const AnyType* superAny) +UnifyResult Unifier2::unify_(const TableType* subTable, const AnyType* superAny) { for (const auto& [propName, prop] : subTable->props) { if (prop.readTy) - unify(*prop.readTy, builtinTypes->anyType); + unify_(*prop.readTy, builtinTypes->anyType); if (prop.writeTy) - unify(builtinTypes->anyType, *prop.writeTy); + unify_(builtinTypes->anyType, *prop.writeTy); } if (subTable->indexer) { - unify(subTable->indexer->indexType, builtinTypes->anyType); - unify(subTable->indexer->indexResultType, builtinTypes->anyType); + unify_(subTable->indexer->indexType, builtinTypes->anyType); + unify_(subTable->indexer->indexResultType, builtinTypes->anyType); } - return true; + return UnifyResult::Ok; } -bool Unifier2::unify(const MetatableType* subMetatable, const AnyType*) +UnifyResult Unifier2::unify_(const MetatableType* subMetatable, const AnyType*) { - return unify(subMetatable->metatable, builtinTypes->anyType) && unify(subMetatable->table, builtinTypes->anyType); + UnifyResult metatableResult = unify_(subMetatable->metatable, builtinTypes->anyType); + if (metatableResult != UnifyResult::Ok) + return metatableResult; + + return unify_(subMetatable->table, builtinTypes->anyType); } -bool Unifier2::unify(const AnyType*, const MetatableType* superMetatable) +UnifyResult Unifier2::unify_(const AnyType*, const MetatableType* superMetatable) { - return unify(builtinTypes->anyType, superMetatable->metatable) && unify(builtinTypes->anyType, superMetatable->table); + UnifyResult metatableResult = unify_(builtinTypes->anyType, superMetatable->metatable); + if (metatableResult != UnifyResult::Ok) + return metatableResult; + + return unify_(builtinTypes->anyType, superMetatable->table); } // FIXME? This should probably return an ErrorVec or an optional // rather than a boolean to signal an occurs check failure. -bool Unifier2::unify(TypePackId subTp, TypePackId superTp) +UnifyResult Unifier2::unify_(TypePackId subTp, TypePackId superTp) { + if (FFlag::LuauLimitUnification) + { + if (FInt::LuauTypeInferIterationLimit > 0 && iterationCount >= FInt::LuauTypeInferIterationLimit) + return UnifyResult::TooComplex; + + ++iterationCount; + } + subTp = follow(subTp); superTp = follow(superTp); if (auto subGen = genericPackSubstitutions.find(subTp)) - return unify(*subGen, superTp); + return unify_(*subGen, superTp); if (auto superGen = genericPackSubstitutions.find(superTp)) - return unify(subTp, *superGen); + return unify_(subTp, *superGen); if (seenTypePackPairings.contains({subTp, superTp})) - return true; + return UnifyResult::Ok; seenTypePackPairings.insert({subTp, superTp}); if (subTp == superTp) - return true; + return UnifyResult::Ok; if (isIrresolvable(subTp) || isIrresolvable(superTp)) { if (uninhabitedTypeFunctions && (uninhabitedTypeFunctions->contains(subTp) || uninhabitedTypeFunctions->contains(superTp))) - return true; + return UnifyResult::Ok; - incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp}); - return true; + if (FFlag::LuauEmplaceNotPushBack) + incompleteSubtypes.emplace_back(PackSubtypeConstraint{subTp, superTp}); + else + incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp}); + return UnifyResult::Ok; } const FreeTypePack* subFree = get(subTp); @@ -607,11 +678,11 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) if (OccursCheckResult::Fail == occursCheck(seen, subTp, superTp)) { emplaceTypePack(asMutable(subTp), builtinTypes->errorTypePack); - return false; + return UnifyResult::OccursCheckFailed; } emplaceTypePack(asMutable(subTp), superTp); - return true; + return UnifyResult::Ok; } if (superFree) @@ -620,11 +691,11 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) if (OccursCheckResult::Fail == occursCheck(seen, superTp, subTp)) { emplaceTypePack(asMutable(superTp), builtinTypes->errorTypePack); - return false; + return UnifyResult::OccursCheckFailed; } emplaceTypePack(asMutable(superTp), subTp); - return true; + return UnifyResult::Ok; } size_t maxLength = std::max(flatten(subTp).first.size(), flatten(superTp).first.size()); @@ -640,10 +711,10 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) } if (subTypes.size() < maxLength || superTypes.size() < maxLength) - return true; + return UnifyResult::Ok; for (size_t i = 0; i < maxLength; ++i) - unify(subTypes[i], superTypes[i]); + unify_(subTypes[i], superTypes[i]); if (subTail && superTail) { @@ -651,7 +722,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) TypePackId followedSuperTail = follow(*superTail); if (get(followedSubTail) || get(followedSuperTail)) - return unify(followedSubTail, followedSuperTail); + return unify_(followedSubTail, followedSuperTail); } else if (subTail) { @@ -666,7 +737,7 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp) emplaceTypePack(asMutable(followedSuperTail), builtinTypes->emptyTypePack); } - return true; + return UnifyResult::Ok; } TypeId Unifier2::mkUnion(TypeId left, TypeId right) diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index 0b6f12de..ad1fa896 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -197,7 +197,14 @@ class AstAttr : public AstNode Deprecated, }; - AstAttr(const Location& location, Type type); + struct DeprecatedInfo + { + bool deprecated = false; + std::optional use; + std::optional reason; + }; + + AstAttr(const Location& location, Type type, AstArray args); AstAttr* asAttr() override { @@ -206,7 +213,10 @@ class AstAttr : public AstNode void visit(AstVisitor* visitor) override; + DeprecatedInfo deprecatedInfo() const; + Type type; + AstArray args; }; class AstExpr : public AstNode @@ -455,6 +465,7 @@ class AstExprFunction : public AstExpr bool hasNativeAttribute() const; bool hasAttribute(AstAttr::Type attributeType) const; + AstAttr* getAttribute(AstAttr::Type attributeType) const; AstArray attributes; AstArray generics; @@ -499,6 +510,8 @@ class AstExprTable : public AstExpr void visit(AstVisitor* visitor) override; + std::optional getRecord(const char* key) const; + AstArray items; }; @@ -960,6 +973,7 @@ class AstStatDeclareFunction : public AstStat bool isCheckedFunction() const; bool hasAttribute(AstAttr::Type attributeType) const; + AstAttr* getAttribute(AstAttr::Type attributeType) const; AstArray attributes; AstName name; @@ -1117,6 +1131,7 @@ class AstTypeFunction : public AstType bool isCheckedFunction() const; bool hasAttribute(AstAttr::Type attributeType) const; + AstAttr* getAttribute(AstAttr::Type attributeType) const; AstArray attributes; AstArray generics; @@ -1561,6 +1576,8 @@ class AstVisitor }; bool isLValue(const AstExpr*); +bool isConstantLiteral(const AstExpr*); +bool isLiteralTable(const AstExpr*); AstName getIdentifier(AstExpr*); Location getLocation(const AstTypeList& typeList); diff --git a/Ast/include/Luau/Lexer.h b/Ast/include/Luau/Lexer.h index fd1c8f32..847094dd 100644 --- a/Ast/include/Luau/Lexer.h +++ b/Ast/include/Luau/Lexer.h @@ -55,6 +55,7 @@ struct Lexeme BlockComment, Attribute, + AttributeOpen, BrokenString, BrokenComment, diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index b3206e25..da070a6b 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -130,7 +130,13 @@ class Parser // function funcname funcbody LUAU_FORCEINLINE AstStat* parseFunctionStat(const AstArray& attributes = {nullptr, 0}); - std::optional validateAttribute(const char* attributeName, const TempVector& attributes); + std::optional validateAttribute( + Location loc, + const char* attributeName, + const TempVector& attributes, + const AstArray& args + ); + std::optional validateAttribute_DEPRECATED(const char* attributeName, const TempVector& attributes); // attribute ::= '@' NAME void parseAttribute(TempVector& attribute); @@ -287,6 +293,7 @@ class Parser // simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor | [attributes] FUNCTION body | primaryexp AstExpr* parseSimpleExpr(); + std::tuple, Location, Location> parseCallList(TempVector* commaPositions); // args ::= `(' [explist] `)' | tableconstructor | String AstExpr* parseFunctionArgs(AstExpr* func, bool self); diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index da825d02..6f42f28c 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -2,19 +2,40 @@ #include "Luau/Ast.h" #include "Luau/Common.h" +#include "Luau/StringUtils.h" + +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) namespace Luau { -static bool hasAttributeInArray(const AstArray attributes, AstAttr::Type attributeType) +static AstAttr* findAttributeInArray(const AstArray attributes, AstAttr::Type attributeType) { for (const auto attribute : attributes) { if (attribute->type == attributeType) - return true; + return attribute; } - return false; + return nullptr; +} + +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; + } } static void visitTypeList(AstVisitor* visitor, const AstTypeList& list) @@ -26,9 +47,10 @@ static void visitTypeList(AstVisitor* visitor, const AstTypeList& list) list.tailType->visit(visitor); } -AstAttr::AstAttr(const Location& location, Type type) +AstAttr::AstAttr(const Location& location, Type type, AstArray args) : AstNode(ClassIndex(), location) , type(type) + , args(args) { } @@ -37,6 +59,29 @@ void AstAttr::visit(AstVisitor* visitor) visitor->visit(this); } +AstAttr::DeprecatedInfo AstAttr::deprecatedInfo() const +{ + AstAttr::DeprecatedInfo info; + info.deprecated = type == AstAttr::Type::Deprecated; + + if (info.deprecated && args.size > 0) + { + AstExprTable* table = args.data[0]->as(); + if (auto useValue = table->getRecord("use")) + { + AstArray use = (*useValue)->as()->value; + info.use = {{use.data, use.size}}; + } + if (auto reasonValue = table->getRecord("reason")) + { + AstArray reason = (*reasonValue)->as()->value; + info.reason = {{reason.data, reason.size}}; + } + } + + return info; +} + int gAstRttiIndex = 0; AstGenericType::AstGenericType(const Location& location, AstName name, AstType* defaultValue) @@ -293,6 +338,11 @@ bool AstExprFunction::hasAttribute(const AstAttr::Type attributeType) const return hasAttributeInArray(attributes, attributeType); } +AstAttr* AstExprFunction::getAttribute(const AstAttr::Type attributeType) const +{ + return findAttributeInArray(attributes, attributeType); +} + AstExprTable::AstExprTable(const Location& location, const AstArray& items) : AstExpr(ClassIndex(), location) , items(items) @@ -313,6 +363,19 @@ void AstExprTable::visit(AstVisitor* visitor) } } +std::optional AstExprTable::getRecord(const char* key) const +{ + for (const AstExprTable::Item& item : items) + { + if (item.kind == AstExprTable::Item::Kind::Record) + { + if (strcmp(item.key->as()->value.data, key) == 0) + return item.value; + } + } + return {}; +} + AstExprUnary::AstExprUnary(const Location& location, Op op, AstExpr* expr) : AstExpr(ClassIndex(), location) , op(op) @@ -917,6 +980,11 @@ bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const return hasAttributeInArray(attributes, attributeType); } +AstAttr* AstStatDeclareFunction::getAttribute(const AstAttr::Type attributeType) const +{ + return findAttributeInArray(attributes, attributeType); +} + AstStatDeclareExternType::AstStatDeclareExternType( const Location& location, const AstName& name, @@ -1085,6 +1153,11 @@ bool AstTypeFunction::hasAttribute(AstAttr::Type attributeType) const return hasAttributeInArray(attributes, attributeType); } +AstAttr* AstTypeFunction::getAttribute(AstAttr::Type attributeType) const +{ + return findAttributeInArray(attributes, attributeType); +} + AstTypeTypeof::AstTypeTypeof(const Location& location, AstExpr* expr) : AstType(ClassIndex(), location) , expr(expr) @@ -1234,6 +1307,34 @@ bool isLValue(const AstExpr* expr) return expr->is() || expr->is() || expr->is() || expr->is(); } +bool isConstantLiteral(const AstExpr* expr) +{ + return expr->is() || expr->is() || expr->is() || + expr->is(); +} + +bool isLiteralTable(const AstExpr* expr) +{ + if (!expr->is()) + return false; + + for (const AstExprTable::Item& item : expr->as()->items) + { + switch (item.kind) + { + case AstExprTable::Item::Kind::General: + return false; + break; + case AstExprTable::Item::Kind::Record: + case AstExprTable::Item::Kind::List: + if (!isConstantLiteral(item.value) && !isLiteralTable(item.value)) + return false; + break; + } + } + return true; +} + AstName getIdentifier(AstExpr* node) { if (AstExprGlobal* expr = node->as()) diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 578bb7b4..950f3661 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -8,6 +8,8 @@ #include +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) + namespace Luau { @@ -147,6 +149,9 @@ std::string Lexeme::toString() const case Attribute: return name ? format("'%s'", name) : "attribute"; + case AttributeOpen: + return "'@['"; + case BrokenString: return "malformed string"; @@ -981,8 +986,36 @@ Lexeme Lexer::readNext() } case '@': { - std::pair attribute = readName(); - return Lexeme(Location(start, position()), Lexeme::Attribute, attribute.first.value); + if (FFlag::LuauParametrizedAttributeSyntax) + { + if (peekch(1) == '[') + { + consume(); + consume(); + + return Lexeme(Location(start, 2), Lexeme::AttributeOpen); + } + 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, ""); + } + } + } + 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 21adf68f..8c7f7d00 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -20,6 +20,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_FASTFLAGVARIABLE(LuauParseIncompleteInterpStringsWithLocation) +LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false; @@ -27,17 +28,65 @@ bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false; namespace Luau { +using AttributeArgumentsValidator = std::function>(Location, const AstArray&)>; + struct AttributeEntry { const char* name; AstAttr::Type type; + std::optional argsValidator; +}; + +std::vector> deprecatedArgsValidator(Location attrLoc, const AstArray& args) +{ + + if (args.size == 0) + return {}; + if (args.size > 1) + return {{attrLoc, "@deprecated can be parametrized only by 1 argument"}}; + + if (!args.data[0]->is()) + return {{args.data[0]->location, "Unknown argument type for @deprecated"}}; + + std::vector> errors; + for (const AstExprTable::Item& item : args.data[0]->as()->items) + { + if (item.kind == AstExprTable::Item::Kind::Record) + { + AstArray keyString = item.key->as()->value; + std::string key(keyString.data, keyString.size); + if (key != "use" && key != "reason") + { + errors.emplace_back( + item.key->location, + format("Unknown argument '%s' for @deprecated. Only string constants for 'use' and 'reason' are allowed", key.c_str()) + ); + } + else if (!item.value->is()) + { + errors.emplace_back(item.value->location, format("Only constant string allowed as value for '%s'", key.c_str())); + } + } + else + { + errors.emplace_back(item.value->location, "Only constants keys 'use' and 'reason' are allowed for @deprecated attribute"); + } + } + return errors; +} + +AttributeEntry kAttributeEntries_DEPRECATED[] = { + {"@checked", AstAttr::Type::Checked, {}}, + {"@native", AstAttr::Type::Native, {}}, + {"@deprecated", AstAttr::Type::Deprecated, {}}, + {nullptr, AstAttr::Type::Checked, {}} }; AttributeEntry kAttributeEntries[] = { - {"@checked", AstAttr::Type::Checked}, - {"@native", AstAttr::Type::Native}, - {"@deprecated", AstAttr::Type::Deprecated}, - {nullptr, AstAttr::Type::Checked} + {"checked", AstAttr::Type::Checked, {}}, + {"native", AstAttr::Type::Native, {}}, + {"deprecated", AstAttr::Type::Deprecated, deprecatedArgsValidator}, + {nullptr, AstAttr::Type::Checked, {}} }; ParseError::ParseError(const Location& location, std::string message) @@ -359,6 +408,7 @@ AstStat* Parser::parseStat() case Lexeme::ReservedBreak: return parseBreak(); case Lexeme::Attribute: + case Lexeme::AttributeOpen: return parseAttributeStat(); default:; } @@ -768,16 +818,65 @@ AstStat* Parser::parseFunctionStat(const AstArray& attributes) return node; } -std::optional Parser::validateAttribute(const char* attributeName, const TempVector& attributes) +std::optional Parser::validateAttribute( + Location loc, + const char* attributeName, + const TempVector& attributes, + const AstArray& args +) { // check if the attribute name is valid std::optional type; + std::optional argsValidator; for (int i = 0; kAttributeEntries[i].name; ++i) { if (strcmp(attributeName, kAttributeEntries[i].name) == 0) { type = kAttributeEntries[i].type; + argsValidator = kAttributeEntries[i].argsValidator; + break; + } + } + + if (!type) + { + if (strlen(attributeName) == 0) + report(loc, "Attribute name is missing"); + else + report(loc, "Invalid attribute '@%s'", attributeName); + } + else + { + // check that attribute is not duplicated + for (const AstAttr* attr : attributes) + { + if (attr->type == *type) + report(loc, "Cannot duplicate attribute '@%s'", attributeName); + } + if (argsValidator) + { + auto errorsToReport = (*argsValidator)(loc, args); + for (const auto& [errorLoc, msg] : errorsToReport) + { + report(errorLoc, "%s", msg.c_str()); + } + } + } + + 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; } } @@ -805,17 +904,93 @@ std::optional Parser::validateAttribute(const char* attributeName // attribute ::= '@' NAME void Parser::parseAttribute(TempVector& attributes) { - LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute); + AstArray empty; + if (!FFlag::LuauParametrizedAttributeSyntax) + { + LUAU_ASSERT(lexer.current().type == Lexeme::Type::Attribute); - Location loc = lexer.current().location; + Location loc = lexer.current().location; - const char* name = lexer.current().name; - std::optional type = validateAttribute(name, attributes); + const char* name = lexer.current().name; + std::optional type = validateAttribute_DEPRECATED(name, attributes); - nextLexeme(); + 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); + + if (lexer.current().type == Lexeme::Type::Attribute) + { + Location loc = lexer.current().location; + + const char* name = lexer.current().name; + std::optional type = validateAttribute(loc, name, attributes, empty); + + nextLexeme(); + + if (type) + attributes.push_back(allocator.alloc(loc, *type, empty)); + } + else + { + Lexeme open = lexer.current(); + nextLexeme(); + + if (lexer.current().type != ']') + { + while (true) + { + Name name = parseName("attribute name"); + + Location nameLoc = name.location; + const char* attrName = name.name.value; + + if (lexer.current().type == Lexeme::RawString || lexer.current().type == Lexeme::QuotedString || lexer.current().type == '{' || + lexer.current().type == '(') + { + + auto [args, argsLocation, _exprLocation] = parseCallList(nullptr); + + for (const AstExpr* arg : args) + { + if (!isConstantLiteral(arg) && !isLiteralTable(arg)) + report(argsLocation, "Only literals can be passed as arguments for attributes"); + } + + std::optional type = validateAttribute(nameLoc, attrName, attributes, args); + + if (type) + attributes.push_back(allocator.alloc(Location(nameLoc, argsLocation), *type, args)); + } + else + { + std::optional type = validateAttribute(nameLoc, attrName, attributes, empty); + if (type) + attributes.push_back(allocator.alloc(nameLoc, *type, empty)); + } - if (type) - attributes.push_back(allocator.alloc(loc, *type)); + if (lexer.current().type == ',') + { + nextLexeme(); + } + else + { + break; + } + } + } + else + { + report(Location(open.location, lexer.current().location), "Attribute list cannot be empty"); + } + + expectMatchAndConsume(']', open); + } } // attributes ::= {attribute} @@ -823,11 +998,11 @@ AstArray Parser::parseAttributes() { Lexeme::Type type = lexer.current().type; - LUAU_ASSERT(type == Lexeme::Attribute); + LUAU_ASSERT(type == Lexeme::Attribute || type == Lexeme::AttributeOpen); TempVector attributes(scratchAttr); - while (lexer.current().type == Lexeme::Attribute) + while (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen)) parseAttribute(attributes); return copy(attributes); @@ -1235,7 +1410,8 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray attributes{nullptr, 0}; - if (lexer.current().type == Lexeme::Attribute) + if (lexer.current().type == Lexeme::Attribute || + (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen)) { attributes = Parser::parseAttributes(); @@ -2350,7 +2526,7 @@ AstTypeOrPack Parser::parseSimpleType(bool allowPack, bool inDeclarationContext) AstArray attributes{nullptr, 0}; - if (lexer.current().type == Lexeme::Attribute) + if (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen)) { if (!inDeclarationContext) { @@ -2997,7 +3173,7 @@ AstExpr* Parser::parseSimpleExpr() AstArray attributes{nullptr, 0}; - if (lexer.current().type == Lexeme::Attribute) + if (lexer.current().type == Lexeme::Attribute || (FFlag::LuauParametrizedAttributeSyntax && lexer.current().type == Lexeme::AttributeOpen)) { attributes = parseAttributes(); @@ -3086,6 +3262,47 @@ AstExpr* Parser::parseSimpleExpr() } } +std::tuple, Location, Location> Parser::parseCallList(TempVector* commaPositions) +{ + LUAU_ASSERT( + lexer.current().type == '(' || lexer.current().type == '{' || lexer.current().type == Lexeme::RawString || + lexer.current().type == Lexeme::QuotedString + ); + if (lexer.current().type == '(') + { + Position argStart = lexer.current().location.end; + + MatchLexeme matchParen = lexer.current(); + nextLexeme(); + + TempVector args(scratchExpr); + + if (lexer.current().type != ')') + parseExprList(args, commaPositions); + + Location end = lexer.current().location; + Position argEnd = end.end; + + expectMatchAndConsume(')', matchParen); + + return {copy(args), Location(argStart, argEnd), Location(matchParen.position, lexer.previousLocation().begin)}; + } + else if (lexer.current().type == '{') + { + Position argStart = lexer.current().location.end; + AstExpr* expr = parseTableConstructor(); + Position argEnd = lexer.previousLocation().end; + + return {copy(&expr, 1), Location(argStart, argEnd), expr->location}; + } + else + { + Location argLocation = lexer.current().location; + AstExpr* expr = parseString(); + return {copy(&expr, 1), argLocation, expr->location}; + } +} + // args ::= `(' [explist] `)' | tableconstructor | String AstExpr* Parser::parseFunctionArgs(AstExpr* func, bool self) { diff --git a/CLI/src/Reduce.cpp b/CLI/src/Reduce.cpp index ff6a65ca..22c72964 100644 --- a/CLI/src/Reduce.cpp +++ b/CLI/src/Reduce.cpp @@ -54,6 +54,7 @@ struct Reducer ParseOptions parseOptions; ParseResult parseResult; + CstNodeMap cstNodeMap{nullptr}; AstStatBlock* root; std::string scriptName; @@ -64,6 +65,7 @@ struct Reducer Reducer() { parseOptions.captureComments = true; + parseOptions.storeCstData = true; } std::string readLine(FILE* f) @@ -83,7 +85,7 @@ struct Reducer void writeTempScript(bool minify = false) { - std::string source = transpileWithTypes(*root); + std::string source = transpileWithTypes(*root, cstNodeMap); if (minify) { @@ -454,6 +456,7 @@ struct Reducer } root = parseResult.root; + cstNodeMap = std::move(parseResult.cstNodeMap); const TestResult initialResult = run(); if (initialResult == TestResult::NoBug) diff --git a/CLI/src/ReplRequirer.cpp b/CLI/src/ReplRequirer.cpp index 14af6455..5a290af7 100644 --- a/CLI/src/ReplRequirer.cpp +++ b/CLI/src/ReplRequirer.cpp @@ -168,8 +168,6 @@ static int load(lua_State* L, void* ctx, const char* path, const char* chunkname { if (lua_gettop(ML) == 0) lua_pushstring(ML, "module must return a value"); - else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1)) - lua_pushstring(ML, "module must return a table or function"); } else if (status == LUA_YIELD) { diff --git a/CodeGen/include/Luau/OptimizeConstProp.h b/CodeGen/include/Luau/OptimizeConstProp.h index 74ae131a..619165d0 100644 --- a/CodeGen/include/Luau/OptimizeConstProp.h +++ b/CodeGen/include/Luau/OptimizeConstProp.h @@ -10,8 +10,8 @@ namespace CodeGen struct IrBuilder; -void constPropInBlockChains(IrBuilder& build, bool useValueNumbering); -void createLinearBlocks(IrBuilder& build, bool useValueNumbering); +void constPropInBlockChains(IrBuilder& build); +void createLinearBlocks(IrBuilder& build); } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/src/CodeAllocator.cpp b/CodeGen/src/CodeAllocator.cpp index 2c91264c..9ab4a729 100644 --- a/CodeGen/src/CodeAllocator.cpp +++ b/CodeGen/src/CodeAllocator.cpp @@ -5,8 +5,6 @@ #include -LUAU_FASTFLAGVARIABLE(LuauCodeGenAllocationCheck) - #if defined(_WIN32) #ifndef WIN32_LEAN_AND_MEAN @@ -54,16 +52,6 @@ static void freePagesImpl(uint8_t* mem, size_t size) CODEGEN_ASSERT(!"failed to deallocate block memory"); } -static void makePagesExecutable_DEPRECATED(uint8_t* mem, size_t size) -{ - CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0); - CODEGEN_ASSERT(size == alignToPageSize(size)); - - DWORD oldProtect; - if (VirtualProtect(mem, size, PAGE_EXECUTE_READ, &oldProtect) == 0) - CODEGEN_ASSERT(!"Failed to change page protection"); -} - [[nodiscard]] static bool makePagesExecutable(uint8_t* mem, size_t size) { CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0); @@ -102,15 +90,6 @@ static void freePagesImpl(uint8_t* mem, size_t size) CODEGEN_ASSERT(!"Failed to deallocate block memory"); } -static void makePagesExecutable_DEPRECATED(uint8_t* mem, size_t size) -{ - CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0); - CODEGEN_ASSERT(size == alignToPageSize(size)); - - if (mprotect(mem, size, PROT_READ | PROT_EXEC) != 0) - CODEGEN_ASSERT(!"Failed to change page protection"); -} - [[nodiscard]] static bool makePagesExecutable(uint8_t* mem, size_t size) { CODEGEN_ASSERT((uintptr_t(mem) & (kPageSize - 1)) == 0); @@ -203,15 +182,8 @@ bool CodeAllocator::allocate( size_t pageAlignedSize = alignToPageSize(startOffset + totalSize); - if (FFlag::LuauCodeGenAllocationCheck) - { - if (!makePagesExecutable(blockPos, pageAlignedSize)) - return false; - } - else - { - makePagesExecutable_DEPRECATED(blockPos, pageAlignedSize); - } + if (!makePagesExecutable(blockPos, pageAlignedSize)) + return false; flushInstructionCache(blockPos + codeOffset, codeSize); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 01e87d3d..2da43707 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -41,7 +41,6 @@ #endif #endif -LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt) LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize) LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering) diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index c2117a12..79a597eb 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -22,9 +22,7 @@ #include #include -LUAU_FASTFLAG(DebugCodegenNoOpt) LUAU_FASTFLAG(DebugCodegenOptSize) -LUAU_FASTFLAG(DebugCodegenSkipNumbering) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_FASTINT(CodegenHeuristicsBlockLimit) LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit) @@ -335,36 +333,31 @@ inline bool lowerFunction( computeCfgInfo(ir.function); - if (!FFlag::DebugCodegenNoOpt) - { - bool useValueNumbering = !FFlag::DebugCodegenSkipNumbering; + constPropInBlockChains(ir); - constPropInBlockChains(ir, useValueNumbering); + if (!FFlag::DebugCodegenOptSize) + { + double startTime = 0.0; + unsigned constPropInstructionCount = 0; - if (!FFlag::DebugCodegenOptSize) + if (stats) { - double startTime = 0.0; - unsigned constPropInstructionCount = 0; - - if (stats) - { - constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE); - startTime = lua_clock(); - } + constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE); + startTime = lua_clock(); + } - createLinearBlocks(ir, useValueNumbering); + createLinearBlocks(ir); - if (stats) - { - stats->blockLinearizationStats.timeSeconds += lua_clock() - startTime; - constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE) - constPropInstructionCount; - stats->blockLinearizationStats.constPropInstructionCount += constPropInstructionCount; - } + if (stats) + { + stats->blockLinearizationStats.timeSeconds += lua_clock() - startTime; + constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE) - constPropInstructionCount; + stats->blockLinearizationStats.constPropInstructionCount += constPropInstructionCount; } - - markDeadStoresInBlockChains(ir); } + markDeadStoresInBlockChains(ir); + std::vector sortedBlocks = getSortedBlockOrder(ir.function); // In order to allocate registers during lowering, we need to know where instruction results are last used diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 4c19edf9..2104e288 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -305,8 +305,6 @@ struct ConstPropState uint32_t* getPreviousInstIndex(const IrInst& inst) { - CODEGEN_ASSERT(useValueNumbering); - if (uint32_t* prevIdx = valueMap.find(inst)) { // Previous load might have been removed as unused @@ -325,7 +323,7 @@ struct ConstPropState std::pair getPreviousVersionedLoadForTag(uint8_t tag, IrOp vmReg) { - if (useValueNumbering && !function.cfg.captured.regs.test(vmRegOp(vmReg))) + if (!function.cfg.captured.regs.test(vmRegOp(vmReg))) { if (tag == LUA_TBOOLEAN) { @@ -350,9 +348,6 @@ struct ConstPropState // Find existing value of the instruction that is exactly the same, or record current on for future lookups void substituteOrRecord(IrInst& inst, uint32_t instIdx) { - if (!useValueNumbering) - return; - if (uint32_t* prevIdx = getPreviousInstIndex(inst)) { substitute(function, inst, IrOp{IrOpKind::Inst, *prevIdx}); @@ -368,9 +363,6 @@ struct ConstPropState { CODEGEN_ASSERT(loadInst.a.kind == IrOpKind::VmReg); - if (!useValueNumbering) - return; - // To avoid captured register invalidation tracking in lowering later, values from loads from captured registers are not propagated // This prevents the case where load value location is linked to memory in case of a spill and is then clobbered in a user call if (function.cfg.captured.regs.test(vmRegOp(loadInst.a))) @@ -405,9 +397,6 @@ struct ConstPropState CODEGEN_ASSERT(storeInst.a.kind == IrOpKind::VmReg); CODEGEN_ASSERT(storeInst.b.kind == IrOpKind::Inst); - if (!useValueNumbering) - return; - // To avoid captured register invalidation tracking in lowering later, values from stores into captured registers are not propagated // This prevents the case where store creates an alternative value location in case of a spill and is then clobbered in a user call if (function.cfg.captured.regs.test(vmRegOp(storeInst.a))) @@ -494,8 +483,6 @@ struct ConstPropState IrFunction& function; - bool useValueNumbering = false; - std::array regs; // For range/full invalidations, we only want to visit a limited number of data that we have recorded @@ -1858,12 +1845,11 @@ static void tryCreateLinearBlock(IrBuilder& build, std::vector& visited constPropInBlock(build, linearBlock, state); } -void constPropInBlockChains(IrBuilder& build, bool useValueNumbering) +void constPropInBlockChains(IrBuilder& build) { IrFunction& function = build.function; ConstPropState state{function}; - state.useValueNumbering = useValueNumbering; std::vector visited(function.blocks.size(), false); @@ -1879,7 +1865,7 @@ void constPropInBlockChains(IrBuilder& build, bool useValueNumbering) } } -void createLinearBlocks(IrBuilder& build, bool useValueNumbering) +void createLinearBlocks(IrBuilder& build) { // Go through internal block chains and outline them into a single new block. // Outlining will be able to linearize the execution, even if there was a jump to a block with multiple users, @@ -1887,7 +1873,6 @@ void createLinearBlocks(IrBuilder& build, bool useValueNumbering) IrFunction& function = build.function; ConstPropState state{function}; - state.useValueNumbering = useValueNumbering; std::vector visited(function.blocks.size(), false); diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 307dbe00..e8a00a0f 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -28,8 +28,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo) -LUAU_FASTFLAGVARIABLE(LuauCompileCli162537) - namespace Luau { @@ -1930,10 +1928,7 @@ struct Compiler LUAU_ASSERT(shape.length < BytecodeBuilder::TableShape::kMaxLength); - if (FFlag::LuauCompileCli162537) - shape.keys[shape.length++] = cid; - else - shape.keys[shape.length++] = int16_t(cid); + shape.keys[shape.length++] = cid; } int32_t tid = bytecode.addConstantTable(shape); diff --git a/VM/include/lua.h b/VM/include/lua.h index 9df40dfb..81b932f2 100644 --- a/VM/include/lua.h +++ b/VM/include/lua.h @@ -227,6 +227,7 @@ LUA_API int lua_setfenv(lua_State* L, int idx); LUA_API int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env); LUA_API void lua_call(lua_State* L, int nargs, int nresults); LUA_API int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc); +LUA_API int lua_cpcall(lua_State* L, lua_CFunction func, void* ud); /* ** coroutine functions diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 03196d03..67888f7d 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -1037,8 +1037,9 @@ void lua_call(lua_State* L, int nargs, int nresults) /* ** Execute a protected call. */ +// data to `f_call' struct CallS -{ // data to `f_call' +{ StkId func; int nresults; }; @@ -1071,6 +1072,42 @@ int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc) return status; } +/* +** Execute a protected C call. +*/ +// data to `f_Ccall' +struct CCallS +{ + lua_CFunction func; + void* ud; +}; + +static void f_Ccall(lua_State* L, void* ud) +{ + struct CCallS* c = cast_to(struct CCallS*, ud); + + if (!lua_checkstack(L, 2)) + luaG_runerror(L, "stack limit"); + + lua_pushcclosurek(L, c->func, nullptr, 0, nullptr); + lua_pushlightuserdata(L, c->ud); + luaD_call(L, L->top - 2, 0); +} + +int lua_cpcall(lua_State* L, lua_CFunction func, void* ud) +{ + api_check(L, L->status == 0); + + struct CCallS c; + c.func = func; + c.ud = ud; + + int status = luaD_pcall(L, f_Ccall, &c, savestack(L, L->top), 0); + + adjustresults(L, 0); + return status; +} + int lua_status(lua_State* L) { return L->status; diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 411d7133..f1a1c91b 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -39,7 +39,6 @@ LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_DYNAMIC_FASTFLAG(LuauErrorYield) LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck) -LUAU_FASTFLAG(LuauCompileCli162537) static lua_CompileOptions defaultOptions() { @@ -48,9 +47,6 @@ static lua_CompileOptions defaultOptions() copts.debugLevel = 1; copts.typeInfoLevel = 1; - copts.vectorCtor = "vector"; - copts.vectorType = "vector"; - return copts; } @@ -108,21 +104,6 @@ static int lua_loadstring(lua_State* L) return 2; // return nil plus error message } -static int lua_vector(lua_State* L) -{ - double x = luaL_checknumber(L, 1); - double y = luaL_checknumber(L, 2); - double z = luaL_checknumber(L, 3); - -#if LUA_VECTOR_SIZE == 4 - double w = luaL_optnumber(L, 4, 0.0); - lua_pushvector(L, float(x), float(y), float(z), float(w)); -#else - lua_pushvector(L, float(x), float(y), float(z)); -#endif - return 1; -} - static int lua_vector_dot(lua_State* L) { const float* a = luaL_checkvector(L, 1); @@ -280,7 +261,6 @@ static StateRef runConformance( // Lua conformance tests treat _G synonymously with getfenv(); for now cater to them lua_pushvalue(L, LUA_GLOBALSINDEX); - lua_pushvalue(L, LUA_GLOBALSINDEX); lua_setfield(L, -1, "_G"); std::string chunkname = "=" + std::string(name); @@ -314,6 +294,7 @@ static StateRef runConformance( { REQUIRE(lua_isstring(L, -1)); CHECK(std::string(lua_tostring(L, -1)) == "OK"); + lua_pop(L, 1); } else { @@ -347,9 +328,6 @@ static void* limitedRealloc(void* ud, void* ptr, size_t osize, size_t nsize) void setupVectorHelpers(lua_State* L) { - lua_pushcfunction(L, lua_vector, "vector"); - lua_setglobal(L, "vector"); - #if LUA_VECTOR_SIZE == 4 lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f); #else @@ -1816,6 +1794,23 @@ TEST_CASE("ApiIter") lua_pop(L, 1); } +static int cpcallTest(lua_State* L) +{ + bool shouldFail = *(bool*)(lua_tolightuserdata(L, 1)); + + if (shouldFail) + { + luaL_error(L, "Failed"); + } + else + { + lua_pushinteger(L, 123); + lua_setglobal(L, "cpcallvalue"); + } + + return 0; +} + TEST_CASE("ApiCalls") { StateRef globalState = runConformance("apicalls.luau", nullptr, nullptr, lua_newstate(limitedRealloc, nullptr)); @@ -1843,6 +1838,49 @@ TEST_CASE("ApiCalls") lua_pop(L, 1); } + // lua_cpcall success + { + bool shouldFail = false; + CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_OK); + CHECK(lua_status(L) == LUA_OK); + + lua_getglobal(L, "cpcallvalue"); + CHECK(luaL_checkinteger(L, -1) == 123); + lua_pop(L, 1); + } + + // lua_cpcall failure + { + bool shouldFail = true; + CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_ERRRUN); + REQUIRE(lua_isstring(L, -1)); + CHECK(std::string(lua_tostring(L, -1)) == "Failed"); + lua_pop(L, 1); + + CHECK(lua_status(L) == LUA_OK); + } + + // lua_cpcall early failure + { + bool shouldFail = false; + + CHECK(lua_gettop(L) == 0); + + luaL_checkstack(L, LUAI_MAXCSTACK - 1, "must succeed"); + + for (int i = 0; i < LUAI_MAXCSTACK - 1; i++) + lua_pushnumber(L, 1.0); + + CHECK(lua_cpcall(L, cpcallTest, &shouldFail) == LUA_ERRRUN); + REQUIRE(lua_isstring(L, -1)); + CHECK(std::string(lua_tostring(L, -1)) == "stack limit"); + lua_pop(L, 1); + + CHECK(lua_status(L) == LUA_OK); + + lua_pop(L, LUAI_MAXCSTACK - 1); + } + // lua_equal with a sleeping thread wake up { lua_State* L2 = lua_newthread(L); @@ -3078,14 +3116,42 @@ TEST_CASE("Native") if (!codegen || !luau_codegen_supported()) return; + lua_CompileOptions copts = defaultOptions(); + SUBCASE("Checked") { FFlag::DebugLuauAbortingChecks.value = true; + + SUBCASE("O0") + { + copts.optimizationLevel = 0; + } + SUBCASE("O1") + { + copts.optimizationLevel = 1; + } + SUBCASE("O2") + { + copts.optimizationLevel = 2; + } } SUBCASE("Regular") { FFlag::DebugLuauAbortingChecks.value = false; + + SUBCASE("O0") + { + copts.optimizationLevel = 0; + } + SUBCASE("O1") + { + copts.optimizationLevel = 1; + } + SUBCASE("O2") + { + copts.optimizationLevel = 2; + } } runConformance( @@ -3093,7 +3159,10 @@ TEST_CASE("Native") [](lua_State* L) { setupNativeHelpers(L); - } + }, + nullptr, + nullptr, + &copts ); } @@ -3329,8 +3398,6 @@ TEST_CASE("HugeFunctionLoadFailure") TEST_CASE("HugeConstantTable") { - ScopedFastFlag luauCompileCli162537{FFlag::LuauCompileCli162537, true}; - std::string source = "function foo(...)\n"; source += " local args = ...\n"; diff --git a/tests/Fixture.h b/tests/Fixture.h index 13ee7a0e..4653e260 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -30,7 +30,8 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature) -LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) +LUAU_FASTFLAG(LuauTidyTypeUtils) +LUAU_FASTFLAG(DebugLuauAlwaysShowConstraintSolvingIncomplete); #define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests}; @@ -153,12 +154,16 @@ struct Fixture // In that case, flag can be forced to 'true' using the example below: // ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true}; ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true}; - ScopedFastFlag sff_LuauUpdateGetMetatableTypeSignature{FFlag::LuauUpdateGetMetatableTypeSignature, true}; + + ScopedFastFlag sff_TypeUtilTidy{FFlag::LuauTidyTypeUtils, true}; // Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it. // This is useful for tracking down violations of Luau's memory model. ScopedFastFlag sff_DebugLuauFreezeArena{FFlag::DebugLuauFreezeArena, true}; + // This makes sure that errant cases of constraint solving failing to complete still pop up in tests. + ScopedFastFlag sff_DebugLuauAlwaysShowConstraintSolvingIncomplete{FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, true}; + TestFileResolver fileResolver; TestConfigResolver configResolver; NullModuleResolver moduleResolver; diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 68512cc3..dbee4f81 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -156,7 +156,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType ) { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); + this->getFrontend().setLuauSolverMode(SolverMode::New); this->check(document, getOptions()); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); @@ -173,7 +173,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType ) { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); + this->getFrontend().setLuauSolverMode(SolverMode::Old); this->check(document, getOptions()); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); @@ -190,7 +190,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType ) { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); + this->getFrontend().setLuauSolverMode(SolverMode::New); this->check(document, getOptions()); FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); @@ -198,7 +198,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType assertions(result); ScopedFastFlag _{FFlag::LuauSolverV2, false}; - this->getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); + this->getFrontend().setLuauSolverMode(SolverMode::Old); this->check(document, getOptions()); result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); @@ -1346,7 +1346,7 @@ t FrontendOptions opts; opts.forAutocomplete = true; - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); getFrontend().check("game/A", opts); CHECK_NE(getFrontend().moduleResolverForAutocomplete.getModule("game/A"), nullptr); CHECK_EQ(getFrontend().moduleResolver.getModule("game/A"), nullptr); @@ -1428,7 +1428,7 @@ TEST_SUITE_BEGIN("MixedModeTests"); TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_append") { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); auto res = checkOldSolver( R"( local x = 4 @@ -1455,7 +1455,7 @@ local z = x + y TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_basic_example_inlined") { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); auto res = checkOldSolver( R"( local x = 4 @@ -1480,7 +1480,7 @@ local y = 5 TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "mixed_mode_can_autocomplete_simple_property_access") { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); auto res = checkOldSolver( R"( local tbl = { abc = 1234} @@ -1595,14 +1595,14 @@ return module)"; { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); + getFrontend().setLuauSolverMode(SolverMode::Old); checkAndExamine(source, "module", "{| |}"); fragmentACAndCheck(updated1, Position{1, 17}, "module", "{| |}", "{| a: (%error-id%: unknown) -> () |}"); fragmentACAndCheck(updated2, Position{1, 18}, "module", "{| |}", "{| ab: (%error-id%: unknown) -> () |}"); } { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); + getFrontend().setLuauSolverMode(SolverMode::New); checkAndExamine(source, "module", "{ }"); // [TODO] CLI-140762 Fragment autocomplete still doesn't return correct result when LuauSolverV2 is on return; @@ -2969,7 +2969,7 @@ return module)"; { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; - getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::Old); + getFrontend().setLuauSolverMode(SolverMode::Old); checkAndExamine(source, "module", "{| |}"); // [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment // early return since the following checking will fail, which it shouldn't! @@ -2979,7 +2979,7 @@ return module)"; { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); + getFrontend().setLuauSolverMode(SolverMode::New); checkAndExamine(source, "module", "{ }"); // [TODO] CLI-140762 we shouldn't mutate stale module in autocompleteFragment // early return since the following checking will fail, which it shouldn't! diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index e6ea2bcd..8736892e 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -1353,7 +1353,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "separate_caches_for_autocomplete") FrontendOptions opts; opts.forAutocomplete = true; - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); getFrontend().check("game/A", opts); CHECK(nullptr == getFrontend().moduleResolver.getModule("game/A")); @@ -1725,7 +1725,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "test_dependents_stored_on_node_as_graph_upda TEST_CASE_FIXTURE(FrontendFixture, "test_invalid_dependency_tracking_per_module_resolver") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, false}; - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); fileResolver.source["game/Gui/Modules/A"] = "return {hello=5, world=true}"; fileResolver.source["game/Gui/Modules/B"] = "return require(game:GetService('Gui').Modules.A)"; diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 77650b6f..647d963a 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -17,6 +17,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) TEST_SUITE_BEGIN("Generalization"); @@ -229,7 +230,8 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, }; TableType tt; @@ -266,7 +268,8 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; auto [aTy, aFree] = freshType(); @@ -429,7 +432,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "avoid_cross_module_mutation_in_bidirectional { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; fileResolver.source["Module/ListFns"] = R"( diff --git a/tests/InferPolarity.test.cpp b/tests/InferPolarity.test.cpp index 944ca934..23901cbe 100644 --- a/tests/InferPolarity.test.cpp +++ b/tests/InferPolarity.test.cpp @@ -9,8 +9,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauEagerGeneralization4); -LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) TEST_SUITE_BEGIN("InferPolarity"); @@ -18,7 +18,8 @@ TEST_CASE_FIXTURE(Fixture, "T where T = { m: (a) -> T }") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; TypeArena arena; @@ -58,7 +59,7 @@ TEST_CASE_FIXTURE(Fixture, "({ read x: a, write x: b }) -> ()") ScopedFastFlag sffs[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauInferPolarityOfReadWriteProperties, true}, + {FFlag::LuauResetConditionalContextProperly, true}, }; TypeArena arena; diff --git a/tests/IrBuilder.test.cpp b/tests/IrBuilder.test.cpp index 629c3696..70be8994 100644 --- a/tests/IrBuilder.test.cpp +++ b/tests/IrBuilder.test.cpp @@ -881,7 +881,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTagsAndValues") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -925,7 +925,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PropagateThroughTvalue") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -954,7 +954,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipCheckTag") build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -981,7 +981,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipOncePerBlockChecks") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1020,7 +1020,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberTableState") build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1066,7 +1066,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RememberNewTableState") build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1098,7 +1098,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SkipUselessBarriers") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1129,7 +1129,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ConcatInvalidation") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1186,7 +1186,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BuiltinFastcallsMayInvalidateMemory") build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1219,7 +1219,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RedundantStoreCheckConstantType") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1249,7 +1249,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagation") build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1281,7 +1281,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagCheckPropagationConflicting") build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1317,7 +1317,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TruthyTestRemoval") build.inst(IrCmd::RETURN, build.constUint(3)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1356,7 +1356,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FalsyTestRemoval") build.inst(IrCmd::RETURN, build.constUint(3)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1391,7 +1391,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagEqRemoval") build.inst(IrCmd::RETURN, build.constUint(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1423,7 +1423,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntEqRemoval") build.inst(IrCmd::RETURN, build.constUint(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1454,7 +1454,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumCmpRemoval") build.inst(IrCmd::RETURN, build.constUint(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1482,7 +1482,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataFlowsThroughDirectJumpToUniqueSuccessor build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1515,7 +1515,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DataDoesNotFlowThroughDirectJumpToNonUnique build.inst(IrCmd::JUMP, block2); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1551,7 +1551,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "EntryBlockUseRemoval") build.inst(IrCmd::JUMP, entry); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1586,7 +1586,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval1") build.inst(IrCmd::JUMP, block); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1628,7 +1628,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RecursiveSccUseRemoval2") build.inst(IrCmd::JUMP, block); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1664,7 +1664,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IntNumIntPeepholes") build.inst(IrCmd::RETURN, build.constUint(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1700,7 +1700,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InvalidateReglinkVersion") build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1744,7 +1744,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NumericSimplifications") build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(9)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1809,8 +1809,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimplePathExtraction") build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); - createLinearBlocks(build, true); + constPropInBlockChains(build); + createLinearBlocks(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1885,8 +1885,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPathExtractionForBlocksWithLiveOutValues" build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); - createLinearBlocks(build, true); + constPropInBlockChains(build); + createLinearBlocks(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1937,8 +1937,8 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InfiniteLoopInPathAnalysis") build.inst(IrCmd::JUMP, block2); updateUseCounts(build.function); - constPropInBlockChains(build, true); - createLinearBlocks(build, true); + constPropInBlockChains(build); + createLinearBlocks(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1967,7 +1967,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PartialStoreInvalidation") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -1995,7 +1995,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "VaridicRegisterRangeInvalidation") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2021,7 +2021,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "LoadPropagatesOnlyRightType") build.inst(IrCmd::RETURN, build.constUint(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2065,7 +2065,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecks") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); // In the future, we might even see duplicate identical TValue loads go away // In the future, we might even see loads of different VM regs with the same value go away @@ -2131,7 +2131,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksAvoidNil") build.inst(IrCmd::RETURN, build.vmReg(1), build.constUint(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2192,7 +2192,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateHashSlotChecksInvalidation") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); // In the future, we might even see duplicate identical TValue loads go away // In the future, we might even see loads of different VM regs with the same value go away @@ -2250,7 +2250,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameIndex") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); // In the future, we might even see duplicate identical TValue loads go away // In the future, we might even see loads of different VM regs with the same value go away @@ -2310,7 +2310,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksSameValue") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); // In the future, we might even see duplicate identical TValue loads go away // In the future, we might even see loads of different VM regs with the same value go away @@ -2368,7 +2368,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksLowerIndex") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2424,7 +2424,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateArrayElemChecksInvalidations") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2480,7 +2480,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ArrayElemChecksNegativeIndex") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2540,7 +2540,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DuplicateBufferLengthChecks") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2584,7 +2584,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "BufferLenghtChecksNegativeIndex") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2618,7 +2618,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagVectorSkipErrorFix") build.inst(IrCmd::RETURN, build.vmReg(0), build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::Yes) == R"( bb_0: ; useCount: 0 @@ -2655,7 +2655,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "ForgprepInvalidation") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2687,7 +2687,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects1") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2710,7 +2710,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FastCallEffects2") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2735,7 +2735,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "InferNumberTagFromLimitedContext") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2757,7 +2757,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore1") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -2783,7 +2783,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore2") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3259,7 +3259,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RemoveDuplicateCalculation") build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3295,7 +3295,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "LateTableStateLink") build.inst(IrCmd::RETURN, build.constUint(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3326,7 +3326,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "RegisterVersioning") build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3355,7 +3355,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SetListIsABlocker") build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3384,7 +3384,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "CallIsABlocker") build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3413,7 +3413,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoPropagationOfCapturedRegs") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( ; captured regs: R0 @@ -3445,7 +3445,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadLoadReuse") build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3472,7 +3472,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "NoDeadValueReuse") build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3514,7 +3514,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TValueLoadToSplitStore") build.inst(IrCmd::RETURN, build.vmReg(2), build.constInt(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3547,7 +3547,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesValueVersion") build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3575,7 +3575,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagStoreUpdatesSetUpval") build.inst(IrCmd::RETURN, build.vmReg(0), build.constInt(0)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3606,7 +3606,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TagSelfEqualityCheckRemoval") build.inst(IrCmd::RETURN, build.constUint(2)); updateUseCounts(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3647,7 +3647,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "TaggedValuePropagationIntoTvalueChecksRegis updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( bb_0: @@ -3705,7 +3705,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SimpleDoubleStore") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( @@ -3743,7 +3743,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturn") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( @@ -3766,7 +3766,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "UnusedAtReturnPartial") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // Partial stores cannot be removed, even if unused @@ -3795,7 +3795,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse1") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( @@ -3826,7 +3826,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse2") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // Stores to pointers can be safely removed at 'return' point, but have to preserved for any GC assist trigger (such as a call) @@ -3856,7 +3856,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse3") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // Stores to pointers can be safely removed if there are no potential implicit uses by any GC assists @@ -3883,7 +3883,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "HiddenPointerUse4") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // It is important for tag overwrite to TNIL to kill not only the previous tag store, but the value as well @@ -3914,7 +3914,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "PartialVsFullStoresWithRecombination") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( @@ -3939,7 +3939,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "IgnoreFastcallAdjustment") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( @@ -3968,7 +3968,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "JumpImplicitLiveOut") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // Even though bb_0 doesn't have R1 as a live out, chain optimization used the knowledge of those writes happening to optimize duplicate stores @@ -4002,7 +4002,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "KeepCapturedRegisterStores") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // Captured registers may be modified from called user functions (plain or hidden in metamethods) @@ -4059,7 +4059,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "StoreCannotBeReplacedWithCheck") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"( @@ -4115,7 +4115,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // Even though R1 is not live in of the fallback, stack state cannot be left in a partial store state @@ -4169,7 +4169,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "FullStoreHasToBeObservableFromFallbacks2") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag @@ -4281,7 +4281,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag @@ -4333,7 +4333,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "SafePartialValueStoresWithPreservedTag2") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // If table tag store at the start is removed, GC assists in the fallback can observe value with a wrong tag @@ -4391,7 +4391,7 @@ TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotReturnWithPartialStores") updateUseCounts(build.function); computeCfgInfo(build.function); - constPropInBlockChains(build, true); + constPropInBlockChains(build); markDeadStoresInBlockChains(build); // Even though R1 is not live out at return, we stored table tag followed by an integer value diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 656e7d7f..96896f23 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -9,6 +9,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauEagerGeneralization4); +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) using namespace Luau; @@ -1873,6 +1874,191 @@ end } } +TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeWithParams") +{ + ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true}; + + // @deprecated works on local functions + { + LintResult result = lint(R"( +@[deprecated{ use = "prodfun", reason = "Too old." }] +local function testfun(x) + return x + 1 +end + +testfun(1) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning( + result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated, use 'prodfun' instead. Too old." + ); + } + + // @deprecated works on globals functions + { + LintResult result = lint(R"( +@[deprecated{ use = "prodfun", reason = "Too old." }] +function testfun(x) + return x + 1 +end + +testfun(1) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning( + result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated, use 'prodfun' instead. Too old." + ); + } + + // @deprecated with only 'use' works on local functions + { + LintResult result = lint(R"( +@[deprecated{ use = "prodfun" }] +local function testfun(x) + return x + 1 +end + +testfun(1) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated, use 'prodfun' instead"); + } + + // @deprecated with only 'use' works on globals functions + { + LintResult result = lint(R"( +@[deprecated{ use = "prodfun" }] +function testfun(x) + return x + 1 +end + +testfun(1) +)"); + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated, use 'prodfun' instead"); + } + + + // @deprecated with only 'reason' works on local functions + { + LintResult result = lint(R"( +@[deprecated{ reason = "Too old." }] +local function testfun(x) + return x + 1 +end + +testfun(1) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated. Too old."); + } + + // @deprecated with only 'reason' works on globals functions + { + LintResult result = lint(R"( +@[deprecated{ reason = "Too old." }] +function testfun(x) + return x + 1 +end + +testfun(1) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(6, 0), Position(6, 7), "Function 'testfun' is deprecated. Too old."); + } + + // @deprecated works for methods with a literal class name + { + LintResult result = lint(R"( +Account = { balance=0 } + +@[deprecated{use = 'credit', reason = 'It sounds cool'}] +function Account:deposit(v) + self.balance = self.balance + v +end + +Account:deposit(200.00) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(8, 0), Position(8, 15), "Member 'Account.deposit' is deprecated, use 'credit' instead. It sounds cool"); + } + + // @deprecated works for methods with a compound expression class name + { + LintResult result = lint(R"( +Account = { balance=0 } + +function getAccount() + return Account +end + +@[deprecated{use = 'credit', reason = 'It sounds cool'}] +function Account:deposit (v) + self.balance = self.balance + v +end + +(getAccount()):deposit(200.00) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(12, 0), Position(12, 22), "Member 'deposit' is deprecated, use 'credit' instead. It sounds cool"); + } + + { + loadDefinition(R"( +@[deprecated{use = 'foo', reason = 'Do better.'}] declare function bar(x: number): string +)"); + + LintResult result = lint(R"( +bar(2) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(1, 0), Position(1, 3), "Function 'bar' is deprecated, use 'foo' instead. Do better."); + } + + { + loadDefinition(R"( +declare Hooty : { + tooty : @[deprecated{use = 'foo', reason = 'bar'}] @checked (number) -> number +} +)"); + LintResult result = lint(R"( +print(Hooty:tooty(2.0)) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(1, 6), Position(1, 17), "Member 'Hooty.tooty' is deprecated, use 'foo' instead. bar"); + } + + { + loadDefinition(R"( +declare class Foo + @[deprecated{use = 'foo', reason = 'baz'}] + function bar(self, value: number) : number +end + +declare Foo: { + new: () -> Foo +} +)"); + + LintResult result = lint(R"( +local foo = Foo.new() +print(foo:bar(2.0)) +)"); + + REQUIRE(1 == result.warnings.size()); + checkDeprecatedWarning(result.warnings[0], Position(2, 6), Position(2, 13), "Member 'bar' is deprecated, use 'foo' instead. baz"); + } +} + TEST_CASE_FIXTURE(Fixture, "DeprecatedAttributeFunctionDeclaration") { ScopedFastFlag _{FFlag::LuauSolverV2, true}; diff --git a/tests/NonstrictMode.test.cpp b/tests/NonstrictMode.test.cpp index bde95018..9fbec8b5 100644 --- a/tests/NonstrictMode.test.cpp +++ b/tests/NonstrictMode.test.cpp @@ -327,6 +327,9 @@ TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden_n {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, + // This debug flag is normally on, but we turn it off as we're testing + // the exact behavior it enables. + {FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, false}, }; CheckResult results = check(R"( diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index b3c2785e..5d93480a 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -14,7 +14,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) -LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) using namespace Luau; @@ -1222,7 +1221,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauSimplifyOutOfLine2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}, }; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index da307ec2..a3a271be 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix) LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix extern bool luau_telemetry_parsed_return_type_variadic_with_type_suffix; @@ -3728,6 +3729,118 @@ end)"); checkAttribute(attributes.data[0], AstAttr::Type::Checked, Location(Position(1, 0), Position(1, 8))); } +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) + return x + y +end)"); + + LUAU_ASSERT(stat != nullptr); + + AstStatFunction* statFun = stat->body.data[0]->as(); + LUAU_ASSERT(statFun != nullptr); + + AstArray attributes = statFun->func->attributes; + + CHECK_EQ(attributes.size, 1); + + checkAttribute(attributes.data[0], AstAttr::Type::Deprecated, Location(Position(1, 2), Position(1, 70))); +} + +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) + return x + y +end)"); + + checkFirstErrorForAttributes( + result.errors, 1, Location(Position(1, 13), Position(1, 37)), "Only literals can be passed as arguments for attributes" + ); +} + +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) + return x + y +end)"); + + checkFirstErrorForAttributes(result.errors, 1, Location(Position(1, 2), Position(1, 12)), "@deprecated can be parametrized only by 1 argument"); + + result = tryParse(R"( +@[deprecated "Very deprecated"] +function hello(x, y) + return x + y +end)"); + + checkFirstErrorForAttributes(result.errors, 1, Location(Position(1, 13), Position(1, 30)), "Unknown argument type for @deprecated"); + + result = tryParse(R"( +@[deprecated{ foo = "bar" }] +function hello(x, y) + return x + y +end)"); + + checkFirstErrorForAttributes( + result.errors, + 1, + Location(Position(1, 14), Position(1, 17)), + "Unknown argument 'foo' for @deprecated. Only string constants for 'use' and 'reason' are allowed" + ); + + result = tryParse(R"( +@[deprecated{ use = 5 }] +function hello(x, y) + return x + y +end)"); + + checkFirstErrorForAttributes(result.errors, 1, Location(Position(1, 20), Position(1, 21)), "Only constant string allowed as value for 'use'"); +} + +TEST_CASE_FIXTURE(Fixture, "do_not_hang_on_incomplete_attribute_list") +{ + ScopedFastFlag sff{FFlag::LuauParametrizedAttributeSyntax, true}; + ParseResult result = tryParse(R"( +@[] +function hello(x, y) + return x + y +end)"); + checkFirstErrorForAttributes( + result.errors, 1, Location(Position(1, 0), Position(1, 3)), "Attribute list cannot be empty" + ); + + result = tryParse(R"(@[)"); + + checkFirstErrorForAttributes( + result.errors, 1, Location(Position(0, 2), Position(0, 2)), "Expected identifier when parsing attribute name, got " + ); + + result = tryParse(R"(@[ + function foo() end + )"); + + checkFirstErrorForAttributes( + result.errors, 1, Location(Position(1, 8), Position(1, 16)), "Expected identifier when parsing attribute name, got 'function'" + ); + + result = tryParse(R"(@[deprecated + local function foo() end + )"); + + checkFirstErrorForAttributes( + result.errors, 1, Location(Position(1, 8), Position(1, 13)), "Expected ']' (to close '@[' at line 1), got 'local'" + ); +} + TEST_CASE_FIXTURE(Fixture, "parse_attribute_for_function_expression") { AstStatBlock* stat1 = parse(R"( diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index 05e33a56..0809ead9 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -18,6 +18,7 @@ using namespace Luau; LUAU_FASTINT(LuauSolverConstraintLimit) +LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) @@ -28,6 +29,11 @@ LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauLimitUnification) +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTINT(LuauGenericCounterMaxDepth) struct LimitFixture : BuiltinsFixture { @@ -295,8 +301,8 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5)) {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, // And this flag is the one that fixes it. {FFlag::LuauSimplifyAnyAndUnion, true}, @@ -380,6 +386,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai {FFlag::LuauLimitDynamicConstraintSolving3, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, {FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true}, }; @@ -434,4 +441,179 @@ 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)) +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + // This flags surfaced and solves the problem. (The original PR was reverted) + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, + }; + + constexpr const char* src = R"LUAU( + type DataProxy = any + + type _Transaction = (c: _ApolloCache) -> () + type _ApolloCache = { + read: (self: _ApolloCache, query: Cache_ReadOptions) -> T | nil, + write: (self: _ApolloCache, write: Cache_WriteOptions) -> Reference | nil, + diff: (self: _ApolloCache, query: Cache_DiffOptions) -> Cache_DiffResult, + watch: (self: _ApolloCache, watch: Cache_WatchOptions>) -> (), + reset: (self: _ApolloCache) -> Promise, + evict: (self: _ApolloCache, options: Cache_EvictOptions) -> boolean, + restore: (self: _ApolloCache, serializedState: TSerialized_) -> _ApolloCache, + extract: (self: _ApolloCache, optimistic: boolean?) -> any, + removeOptimistic: (self: _ApolloCache, id: string) -> (), + batch: (self: _ApolloCache, options: Cache_BatchOptions<_ApolloCache>) -> (), + performTransaction: (self: _ApolloCache, transaction: _Transaction, optimisticId: string) -> (), + recordOptimisticTransaction: (self: _ApolloCache, transaction: _Transaction, optimisticId: string) -> (), + transformDocument: (self: _ApolloCache, document: DocumentNode) -> DocumentNode, + identify: (self: _ApolloCache, object: StoreObject | Reference) -> string | nil, + gc: (self: _ApolloCache) -> Array, + modify: (self: _ApolloCache, options: Cache_ModifyOptions) -> boolean, + transformForLink: (self: _ApolloCache, document: DocumentNode) -> DocumentNode, + readQuery: ( + self: _ApolloCache, + options: Cache_ReadQueryOptions, + optimistic: boolean? + ) -> QueryType | nil, + readFragment: ( + self: _ApolloCache, + options: Cache_ReadFragmentOptions, + optimistic: boolean? + ) -> FragmentType | nil, + writeQuery: (self: _ApolloCache, Cache_WriteQueryOptions) -> Reference | nil, + writeFragment: ( + self: _ApolloCache, + Cache_WriteFragmentOptions + ) -> Reference | nil, + } + + export type ApolloCache = { + -- something here needed + read: (self: ApolloCache, query: Cache_ReadOptions) -> T | nil, + write: ( + self: ApolloCache, + write: Cache_WriteOptions + ) -> Reference | nil, + diff: (self: ApolloCache, query: Cache_DiffOptions) -> Cache_DiffResult, + watch: (self: ApolloCache, watch: Cache_WatchOptions>) -> (() -> ()), + reset: (self: ApolloCache) -> Promise, + evict: (self: ApolloCache, options: Cache_EvictOptions) -> boolean, + restore: (self: ApolloCache, serializedState: TSerialized_) -> _ApolloCache, + extract: (self: ApolloCache, optimistic: boolean?) -> TSerialized, + removeOptimistic: (self: ApolloCache, id: string) -> (), + batch: (self: ApolloCache, options: Cache_BatchOptions<_ApolloCache>) -> (), + performTransaction: (self: ApolloCache, transaction: _Transaction, optimisticId: string) -> (), + -- bottom text + -- TOP + recordOptimisticTransaction: ( + self: ApolloCache, + transaction: _Transaction, + optimisticId: string + ) -> (), + transformDocument: (self: ApolloCache, document: DocumentNode) -> DocumentNode, + identify: (self: ApolloCache, object: StoreObject | Reference) -> string | nil, + gc: (self: ApolloCache) -> Array, + modify: (self: ApolloCache, options: Cache_ModifyOptions) -> boolean, + -- BOTTOM + + transformForLink: (self: ApolloCache, document: DocumentNode) -> DocumentNode, + readQuery: ( + self: ApolloCache, + options: Cache_ReadQueryOptions, + optimistic: boolean? + ) -> QueryType | nil, + readFragment: ( + self: ApolloCache, + options: Cache_ReadFragmentOptions, + optimistic: boolean? + ) -> FragmentType | nil, + writeQuery: ( + self: ApolloCache, + Cache_WriteQueryOptions + ) -> Reference | nil, + writeFragment: ( + self: ApolloCache, + Cache_WriteFragmentOptions + ) -> Reference | nil, + } + + + export type InMemoryCache = ApolloCache & { + performTransaction: ( + self: InMemoryCache, + update: (cache: InMemoryCache) -> () + ) -> () + } + + type InMemoryCachePrivate = InMemoryCache & { + broadcastWatches: (self: InMemoryCachePrivate) -> (), -- ROBLOX NOTE: protected method + } + + local InMemoryCache = {} + InMemoryCache.__index = InMemoryCache + + -- InMemoryCache.batch = nil :: any + function InMemoryCache:batch() + self = self :: InMemoryCachePrivate + + if self.txCount == 0 then + self:broadcastWatches() -- problematic call? + end + end + )LUAU"; + + std::ignore = check(src); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "test_generic_pruning_recursion_limit") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, + {FFlag::LuauReduceSetTypeStackPressure, true}, + }; + + ScopedFastInt sfi{FInt::LuauGenericCounterMaxDepth, 1}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function get(scale) + print(scale.Do.Re.Mi) + end + )")); + CHECK_EQ("({ read Do: { read Re: { read Mi: a } } }) -> ()", toString(requireType("get"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "unification_runs_a_limited_number_of_iterations_before_stopping" * doctest::timeout(2.0)) +{ + ScopedFastFlag sff[] = { + // These are necessary to trigger the bug + {FFlag::LuauSolverV2, true}, + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, + + // This is the fix + {FFlag::LuauLimitUnification, true} + }; + + ScopedFastInt sfi{FInt::LuauTypeInferIterationLimit, 100}; + + CheckResult result = 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 + )"); + + LUAU_REQUIRE_ERROR(result, UnificationTooComplex); +} + TEST_SUITE_END(); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 9f6e2166..8d2b0397 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -18,7 +18,9 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) using namespace Luau; @@ -195,6 +197,11 @@ struct SubtypeFixture : Fixture return subtyping.isSubtype(subTy, superTy, NotNull{rootScope.get()}); } + SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy) + { + return subtyping.isSubtype(subTy, superTy, NotNull{rootScope.get()}); + } + TypeId helloType = arena.addType(SingletonType{StringSingleton{"hello"}}); TypeId helloType2 = arena.addType(SingletonType{StringSingleton{"hello"}}); TypeId worldType = arena.addType(SingletonType{StringSingleton{"world"}}); @@ -724,11 +731,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(T) -> T string") CHECK_IS_NOT_SUBTYPE(genericTToTType, numberToStringType); } -TEST_CASE_FIXTURE(SubtypeFixture, "(T) -> () <: (U) -> ()") -{ - CHECK_IS_SUBTYPE(genericTToNothingType, genericUToNothingType); -} - TEST_CASE_FIXTURE(SubtypeFixture, "(number) -> () (T) -> ()") { CHECK_IS_NOT_SUBTYPE(numberToNothingType, genericTToNothingType); @@ -1430,6 +1432,94 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type } } +TEST_CASE_FIXTURE(SubtypeFixture, "(() -> number) -> () <: (() -> T) -> ()") +{ + ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; + + TypeId f1 = fn({nothingToNumberType}, {}); + TypeId f2 = fn({genericNothingToTType}, {}); + CHECK_IS_SUBTYPE(f1, f2); +} + +TEST_CASE_FIXTURE(SubtypeFixture, "((number) -> ()) -> () <: ((T) -> ()) -> ()") +{ + ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; + + TypeId f1 = fn({numberToNothingType}, {}); + TypeId f2 = fn({genericTToNothingType}, {}); + CHECK_IS_SUBTYPE(f1, f2); +} + +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); +} + +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}, + {}, + arena.addTypePack({genericT, genericT, fn({genericT, genericT}, {genericT})}), + // (T, T, (T, T) -> T) + arena.addTypePack({genericT}), + std::nullopt, + false + )); + TypeId addUToU = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {genericU, genericU}}); + TypeId f2 = fn( + { + builtinTypes->numberType, + builtinTypes->numberType, + arena.addType(FunctionType({genericU}, {}, arena.addTypePack({genericU, genericU}), arena.addTypePack({addUToU}))) + // (U, U) -> add + }, + {builtinTypes->numberType} + ); + CHECK_IS_SUBTYPE(f1, f2); +} + +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}))); + TypePackId subTypePack = arena.addTypePack({uToKeyOfU, uToKeyOfU}); + + TypeId tblA = tbl({{"a", builtinTypes->numberType}}); + TypeId tblB = tbl({{"b", builtinTypes->numberType}}); + TypeId aSingleton = arena.addType(SingletonType{StringSingleton{"a"}}); + TypePackId superTypePack = arena.addTypePack({fn({tblA}, {aSingleton}), fn({tblB}, {aSingleton})}); + + CHECK_IS_NOT_SUBTYPE(subTypePack, superTypePack); +} + +TEST_CASE_FIXTURE(Fixture, "fuzzer_non_generics_in_function_generics") +{ + // This should not crash + check(R"( + local _ = _ + function _(l0) + for _ in _(_) do + end + l0[_]( + _(_()) + _ + ) + end + _(_) + )"); +} + TEST_SUITE_END(); TEST_SUITE_BEGIN("Subtyping.Subpaths"); @@ -1657,7 +1747,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; TypeId argTy = arena.freshType(getBuiltins(), moduleScope.get()); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index e6a3b6c9..96d6d0eb 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -15,12 +15,13 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauSimplifyOutOfLine2) -LUAU_FASTFLAG(LuauEmptyStringInKeyOf) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauDoNotBlockOnStuckTypeFunctions) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauRefineOccursCheckDirectRecursion) +LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) struct TypeFunctionFixture : Fixture { @@ -161,8 +162,6 @@ TEST_CASE_FIXTURE(TypeFunctionFixture, "unsolvable_function") TEST_CASE_FIXTURE(TypeFunctionFixture, "table_internal_functions") { - ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true}; - if (!FFlag::LuauSolverV2) return; @@ -732,6 +731,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -1684,7 +1684,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fully_dispatch_type_function_that_is_paramet ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -1712,6 +1713,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "undefined_add_application") {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -1731,8 +1733,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_should_not_assert_on_empty_string_prop if (!FFlag::LuauSolverV2) return; - ScopedFastFlag _{FFlag::LuauEmptyStringInKeyOf, true}; - loadDefinition(R"( declare class Foobar one: boolean @@ -1770,9 +1770,10 @@ struct TFFixture TypeCheckLimits limits; TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; - const ScopedFastFlag sff[2] = { + const ScopedFastFlag sff[3] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; BuiltinTypeFunctions builtinTypeFunctions; @@ -1837,7 +1838,8 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_solved_tf_is_solved") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; TypeId a = arena->addType(GenericType{"A"}); @@ -1859,7 +1861,8 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_stuck_tf_is_stuck") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.bufferType, builtinTypes_.booleanType}}); @@ -1874,12 +1877,35 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_stuck_tf_is_stuck") CHECK(tfit->state == TypeFunctionInstanceState::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}, + }; + + TypeId root = arena->addType(BlockedType{}); + TypeId refinement = arena->addType(TypeFunctionInstanceType{ + builtinTypeFunctions.refineFunc, + { + root, + builtinTypes_.unknownType, + } + }); + + emplaceType(asMutable(root), refinement); + reduceTypeFunctions(refinement, Location{}, tfc, true); + CHECK_EQ("unknown", toString(refinement)); +} + TEST_CASE_FIXTURE(Fixture, "generic_type_functions_should_not_get_stuck_or") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauForceSimplifyConstraint2, true}, {FFlag::LuauDoNotBlockOnStuckTypeFunctions, true}, }; @@ -1893,4 +1919,52 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_functions_should_not_get_stuck_or") CHECK(get(result.errors[0])); } +TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation") +{ + ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; + + CheckResult result = check(R"( + type a = {a<{T}>} + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); +} + +TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation1") +{ + ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; + + CheckResult result = check(R"( + type b = {b} + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); +} + +TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation2") +{ + ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; + + CheckResult result = check(R"( + type c = {c} + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); +} + +TEST_CASE_FIXTURE(TypeFunctionFixture, "recursive_restraint_violation3") +{ + ScopedFastFlag _ = {FFlag::LuauNameConstraintRestrictRecursiveTypes, true}; + + CheckResult result = check(R"( + type d = (d) -> () + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 626589f3..dabde854 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -11,6 +11,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -2372,6 +2373,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 0cb75ade..3cafe58b 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -12,7 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauWriteOnlyPropertyMangling) +LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) @@ -1715,7 +1715,6 @@ TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauWriteOnlyPropertyMangling, true}, {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, }; @@ -1736,6 +1735,23 @@ table.insert(1::any, 2::any) )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_requires_all_fields") +{ + ScopedFastFlag _{FFlag::LuauNoScopeShallNotSubsumeAll, true}; + + CheckResult result = check(R"( + local function huh(): { { x: number, y: string } } + local ret = {} + while true do + table.insert(ret, { x = 42 }) + end + return ret + end + )"); + + LUAU_REQUIRE_ERRORS(result); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "read_refinements_on_persistent_tables_known_property_identity") { // This will not result in a real refinement, as we refine `bnot`, a function, to be truthy diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 8f5b5ecd..c1c62cbd 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -901,7 +901,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "ice_while_checking_script_due_to_scopes_no // new solver code paths. // This is necessary to repro an ice that can occur in studio ScopedFastFlag luauSolverOff{FFlag::LuauSolverV2, false}; - getFrontend().setLuauSolverSelectionFromWorkspace(SolverMode::New); + getFrontend().setLuauSolverMode(SolverMode::New); ScopedFastFlag sff{FFlag::LuauScopeMethodsAreSolverAgnostic, true}; auto result = check(R"( diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index a3975ea7..059898ba 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -10,7 +10,6 @@ using namespace Luau; LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauSimplifyOutOfLine2) TEST_SUITE_BEGIN("DefinitionTests"); @@ -571,10 +570,7 @@ TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully") TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauSimplifyOutOfLine2, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( local function middle(a: number, b: number): number diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 7d1bf1be..9b5c78ca 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -25,11 +25,13 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauCollapseShouldNotCrash) LUAU_FASTFLAG(LuauFormatUseLastPosition) -LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -1972,7 +1974,8 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_ if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) { LUAU_CHECK_NO_ERRORS(result); - CHECK("({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g"))); + 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"))); } else if (FFlag::LuauSolverV2) { @@ -2596,6 +2599,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_return_type") {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; // CLI-114134: This test: @@ -2623,7 +2627,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -2911,8 +2916,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_ast_stat_fun") TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") { - ScopedFastFlag sff{FFlag::LuauSimplifyOutOfLine2, true}; - CheckResult result = check(R"( function foo(player) local success,result = player:thing() @@ -3327,4 +3330,28 @@ 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"( +function isAnArray(value) + if type(value) == "table" then + for index, _ in next, value do + -- assert index is not nil + math.max(0, index) + end + return true + else + return false + end +end +)" + ); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 7370022f..fafe9ee2 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -11,12 +11,13 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauIntersectNotNil) -LUAU_FASTFLAG(LuauSubtypingCheckFunctionGenericCounts) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) using namespace Luau; @@ -782,8 +783,6 @@ TEST_CASE_FIXTURE(Fixture, "instantiated_function_argument_names_old_solver") TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_types") { - ScopedFastFlag _{FFlag::LuauSubtypingCheckFunctionGenericCounts, true}; - CheckResult result = check(R"( type C = () -> () type D = () -> () @@ -815,8 +814,6 @@ local d: D = c } TEST_CASE_FIXTURE(Fixture, "generic_function_mismatch_with_argument") { - ScopedFastFlag _{FFlag::LuauSubtypingCheckFunctionGenericCounts, true}; - CheckResult result = check(R"( type C = (number) -> () type D = (number) -> () @@ -849,8 +846,6 @@ local d: D = c TEST_CASE_FIXTURE(Fixture, "error_detailed_function_mismatch_generic_pack") { - ScopedFastFlag _{FFlag::LuauSubtypingCheckFunctionGenericCounts, true}; - CheckResult result = check(R"( type C = () -> () type D = () -> () @@ -1414,8 +1409,6 @@ TEST_CASE_FIXTURE(Fixture, "no_stack_overflow_from_quantifying") TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument") { - - if (FFlag::LuauSolverV2) { CheckResult result = check(R"( @@ -1500,14 +1493,14 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded" LUAU_REQUIRE_NO_ERRORS(result); } -// Important FIXME CLI-158432: This test exposes some problems with overload +// Important FIXME CLI-161128: This test exposes some problems with overload // selection and generic type substitution when TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") { ScopedFastFlag _[] = { - {FFlag::LuauSubtypingCheckFunctionGenericCounts, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result; @@ -1546,6 +1539,20 @@ 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}}; + + CheckResult result = check(R"( + type t = (a, a, (a, a) -> a) -> a + type u = (number, number, (X, X) -> X) -> number + + local foo = (nil :: any) :: t + local bar : u = foo + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} TEST_CASE_FIXTURE(Fixture, "substitution_with_bound_table") { @@ -1829,9 +1836,9 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_packs_shouldnt_be_bound_to_themselves") { ScopedFastFlag flags[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauSubtypingCheckFunctionGenericCounts, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 3cc2dd6a..4049ecbf 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -10,7 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) @@ -1468,10 +1467,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_80596_simplify_more_realistic_intersections") TEST_CASE_FIXTURE(BuiltinsFixture, "narrow_intersection_nevers") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauRefineTablesWithReadType, true}, - }; + ScopedFastFlag sffs{FFlag::LuauSolverV2, true}; loadDefinition(R"( declare class Player diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 11a29c63..229050a4 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -15,7 +15,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSimplifyOutOfLine2) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -182,8 +181,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next") } TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next_and_multiple_elements") { - ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true}; - CheckResult result = check(R"( local n local s diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 43bd8137..49b9df97 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -17,6 +17,8 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTINT(LuauSolverConstraintLimit) +LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) +LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) using namespace Luau; @@ -855,7 +857,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "internal_types_are_scrubbed_from_module") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, - {FFlag::LuauLimitDynamicConstraintSolving3, true} + {FFlag::LuauLimitDynamicConstraintSolving3, true}, + {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, }; fileResolver.source["game/A"] = R"( @@ -863,8 +866,9 @@ return function(): _luau_blocked_type return nil :: any end )"; CheckResult result = getFrontend().check("game/A"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors[0])); + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK(get(result.errors[0])); + CHECK(get(result.errors[1])); CHECK("(...any) -> *error-type*" == toString(getFrontend().moduleResolver.getModule("game/A")->returnType)); } @@ -873,7 +877,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "internal_type_errors_are_only_reported_once" ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, - {FFlag::LuauLimitDynamicConstraintSolving3, true} + {FFlag::LuauLimitDynamicConstraintSolving3, true}, + {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, }; fileResolver.source["game/A"] = R"( @@ -881,8 +886,9 @@ return function(): { X: _luau_blocked_type, Y: _luau_blocked_type } return nil : )"; CheckResult result = getFrontend().check("game/A"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors[0])); + LUAU_REQUIRE_ERROR_COUNT(2, result); + CHECK(get(result.errors[0])); + CHECK(get(result.errors[1])); CHECK("(...any) -> { X: *error-type*, Y: *error-type* }" == toString(getFrontend().moduleResolver.getModule("game/A")->returnType)); } @@ -919,4 +925,60 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "scrub_unsealed_tables") LUAU_CHECK_ERROR(result, CannotExtendTable); } +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 {} + )"; + + fileResolver.source["game/B"] = R"( + local a_mod = require(game.A) + type bad = {bad<{T}>} + type fine = a_mod.bad + local f: fine + )"; + + CheckResult result = getFrontend().check("game/B"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + + ModulePtr b = getFrontend().moduleResolver.getModule("game/B"); + REQUIRE(b != nullptr); + std::optional fType = requireType(b, "f"); + REQUIRE(fType); + // The important thing here is that it isn't *error-type*, since that would mean that the local definition of `bad` is shadowing the imported one + CHECK(toString(*fType) == "fine"); +} + +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 {} + )"; + + fileResolver.source["game/B"] = R"( + local a_mod = require(game.A) + local f: a_mod.bad + )"; + + CheckResult result = getFrontend().check("game/B"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + + ModulePtr b = getFrontend().moduleResolver.getModule("game/B"); + REQUIRE(b != nullptr); + std::optional fType = requireType(b, "f"); + REQUIRE(fType); + if (FFlag::LuauSolverV2) + CHECK(toString(*fType) == "*error-type*"); + else + CHECK(toString(*fType) == "bad"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.negations.test.cpp b/tests/TypeInfer.negations.test.cpp index 0e6ee6ce..bb559b35 100644 --- a/tests/TypeInfer.negations.test.cpp +++ b/tests/TypeInfer.negations.test.cpp @@ -11,6 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) namespace { @@ -55,7 +56,8 @@ TEST_CASE_FIXTURE(Fixture, "cofinite_strings_can_be_compared_for_equality") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 3cc84dd0..09fd096a 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -15,6 +15,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) TEST_SUITE_BEGIN("TypeInferOOP"); @@ -556,6 +557,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -589,6 +591,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern_2") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index b68d5d20..f15e137b 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -17,6 +17,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauEagerGeneralization4) TEST_SUITE_BEGIN("TypeInferOperators"); @@ -1443,7 +1444,11 @@ local function foo(arg: {name: string}?) end )"); - LUAU_REQUIRE_NO_ERRORS(result); + // FIXME(CLI-165431): fixing subtyping revealed an overload selection problems + if (FFlag::LuauSolverV2 && FFlag::LuauNoScopeShallNotSubsumeAll) + LUAU_REQUIRE_ERROR_COUNT(2, result); + else + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_is_array_simplified") diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 7a01222a..bf36e22a 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -1352,4 +1352,27 @@ TEST_CASE_FIXTURE(Fixture, "loop_unsoundness") )")); } + +TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_and_test_two_props") +{ + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + + CheckResult result = check(R"( + local function f(x: unknown): string + if typeof(x) == 'table' then + if typeof(x.foo) == 'string' and typeof(x.bar) == 'string' then + return x.foo .. x.bar + end + end + return '' + end + )"); + + // We'd like for this to be 0 + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK_MESSAGE(get(result.errors[0]), "Expected UnknownProperty but got " << result.errors[0]); + CHECK(Position{3, 56} == result.errors[0].location.begin); + CHECK(Position{3, 61} == result.errors[0].location.end); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 296596d8..5e5cc386 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -13,12 +13,14 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) -LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauRefineNoRefineAlways) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) -LUAU_FASTFLAG(LuauSimplifyOutOfLine2) +LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) +LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) using namespace Luau; @@ -141,7 +143,7 @@ struct RefinementExternTypeFixture : BuiltinsFixture for (const auto& [name, ty] : getFrontend().globals.globalScope->exportedTypeBindings) persist(ty.type); - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); freeze(getFrontend().globals.globalTypes); } @@ -464,6 +466,34 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_test_a_tested_n } } +TEST_CASE_FIXTURE(BuiltinsFixture, "call_to_undefined_method_is_not_a_refinement") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauResetConditionalContextProperly, true} + }; + + CheckResult result = check(R"( + local function f(x: unknown) + if typeof(x) == "table" then + if x.foo() then + end + end + return (nil :: never) + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + + auto unknownProp = get(result.errors[0]); + REQUIRE_MESSAGE(unknownProp, "Expected UnknownProperty but got " << result.errors[0]); + CHECK("foo" == unknownProp->key); + CHECK("table" == toString(unknownProp->table)); + + CHECK(Position{3, 19} == result.errors[0].location.begin); + CHECK(Position{3, 24} == result.errors[0].location.end); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "call_an_incompatible_function_after_using_typeguard") { CheckResult result = check(R"( @@ -512,8 +542,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "impossible_type_narrow_is_not_an_error") TEST_CASE_FIXTURE(Fixture, "truthy_constraint_on_properties") { - ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true}; - CheckResult result = check(R"( local t: {x: number?} = {x = 1} @@ -658,6 +686,7 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, }; @@ -1017,8 +1046,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "either_number_or_string") TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t") { - ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true}; - CheckResult result = check(R"( local function f(t: {x: boolean}?) if not t or t.x then @@ -1271,8 +1298,6 @@ TEST_CASE_FIXTURE(Fixture, "apply_refinements_on_astexprindexexpr_whose_subscrip TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") { - ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true}; - CheckResult result = check(R"( type T = {tag: "missing", x: nil} | {tag: "exists", x: string} @@ -1438,7 +1463,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeguard_cast_free_table_to_vec { // CLI-115286 - Refining via type(x) == 'vector' does not work in the new solver DOES_NOT_PASS_NEW_SOLVER_GUARD(); - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( local function f(vec) local X, Y, Z = vec.X, vec.Y, vec.Z @@ -2229,6 +2254,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauForceSimplifyConstraint2, true}, }; @@ -2250,6 +2276,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_variant") { + ScopedFastFlag _[] = { + {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, + {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, + {FFlag::LuauEagerGeneralization4, true}, + {FFlag::LuauResetConditionalContextProperly, true}, + {FFlag::LuauTrackFreeInteriorTypePacks, true} + }; + // FIXME CLI-141364: An underlying bug in normalization means the type of // `isIndexKey` is platform dependent. CheckResult result = check(R"( @@ -2260,7 +2294,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_ and math.floor(k) == k -- no float keys end )"); - LUAU_REQUIRE_NO_ERRORS(result); + 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); } TEST_CASE_FIXTURE(BuiltinsFixture, "ex") @@ -2686,10 +2728,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451") TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_single") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauRefineTablesWithReadType, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( local function invokeDisconnect(d: unknown) @@ -2705,10 +2744,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_single") TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function_union") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauRefineTablesWithReadType, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult result = check(R"( type Disconnectable = { @@ -2876,12 +2912,41 @@ TEST_CASE_FIXTURE(Fixture, "cli_120460_table_access_on_phi_node") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "refinements_from_and_should_not_refine_to_never") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauRefineDistributesOverUnions, true}, + }; + + loadDefinition(R"( + declare extern type Config with + KeyboardEnabled: boolean + MouseEnabled: boolean + end + )"); + + CheckResult results = check(R"( + local config: Config + local function serialize() + if config.KeyboardEnabled and config.MouseEnabled then + return 0 + else + print(config) + return 1 + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(results); + CHECK_EQ("Config", toString(requireTypeAtPosition({6, 24}))); +} + TEST_CASE_FIXTURE(Fixture, "force_simplify_constraint_doesnt_drop_blocked_type") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauForceSimplifyConstraint2, true}, - {FFlag::LuauSimplifyOutOfLine2, true}, }; CheckResult results = check(R"( diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index c1272608..ac6170ad 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(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) -LUAU_FASTFLAG(LuauRelateTablesAreNeverDisjoint) LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) -LUAU_FASTFLAG(LuauRefineTablesWithReadType) LUAU_FASTFLAG(LuauSolverAgnosticStringification) -LUAU_FASTFLAG(LuauDfgForwardNilFromAndOr) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) +LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) TEST_SUITE_BEGIN("TableTests"); @@ -762,8 +762,6 @@ TEST_CASE_FIXTURE(Fixture, "indexers_quantification_2") TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_array_like_table") { - ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true}; - CheckResult result = check(R"( local t = {"one", "two", "three"} )"); @@ -2053,8 +2051,6 @@ TEST_CASE_FIXTURE(Fixture, "explicit_nil_indexer") TEST_CASE_FIXTURE(Fixture, "ok_to_provide_a_subtype_during_construction") { - ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true}; - CheckResult result = check(R"( local a: string | number = 1 local t = {a, 1} @@ -2359,6 +2355,7 @@ local t: { a: {Foo}, b: number } = { // since mutating properties means table properties should be invariant. TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound") { + ScopedFastFlag sff{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; CheckResult result = check(R"( --!strict @@ -2371,24 +2368,12 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table local c : string = t.m("hi") )"); - if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2) { - LUAU_CHECK_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR_COUNT(2, result); LUAU_CHECK_ERROR(result, ExplicitFunctionAnnotationRecommended); + LUAU_CHECK_ERROR(result, TypeMismatch); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - - CHECK(get(result.errors[0])); - - // This is not actually the expected behavior, but the typemismatch we were seeing before was for the wrong reason. - // The behavior of this test is just regressed generally in the new solver, and will need to be consciously addressed. - } - - // TODO: test behavior is wrong with LuauInstantiateInSubtyping until we can re-enable the covariant requirement for instantiation in subtyping - else if (FFlag::LuauInstantiateInSubtyping) - LUAU_REQUIRE_NO_ERRORS(result); else LUAU_REQUIRE_ERRORS(result); } @@ -2416,7 +2401,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_should_cope_with_optional_prope table.insert(buttons, { a = 3 }) )"); - LUAU_REQUIRE_NO_ERRORS(result); + // 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); } TEST_CASE_FIXTURE(Fixture, "error_detailed_prop") @@ -3171,8 +3160,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "dont_quantify_table_that_belongs_to_outer_sc TEST_CASE_FIXTURE(BuiltinsFixture, "instantiate_tables_at_scope_level") { - ScopedFastFlag sff1{FFlag::LuauSimplifyOutOfLine2, true}; - CheckResult result = check(R"( --!strict local Option = {} @@ -3514,7 +3501,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_invalidate_the_properties_iterator_of_free_tabl TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early") { - ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true}; CheckResult result = check(R"( local t: {x: number?}? = {x = nil} @@ -3746,7 +3732,8 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_ { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -4626,6 +4613,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -5017,8 +5005,6 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "indexing_branching_table") { - ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true}; - CheckResult result = check(R"( local test = if true then { "meow", "woof" } else { 4, 81 } local test2 = test[1] @@ -5786,11 +5772,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1859") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1797_intersection_of_tables_arent_disjoint") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauRelateTablesAreNeverDisjoint, true}, - {FFlag::LuauRefineTablesWithReadType, true}, - }; + ScopedFastFlag sffs{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( --!strict @@ -5819,8 +5801,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1797_intersection_of_tables_arent_disjoi TEST_CASE_FIXTURE(Fixture, "oss_1344") { - ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( --!strict type t = { @@ -5843,8 +5823,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1344") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1651") { - ScopedFastFlag _{FFlag::LuauRefineTablesWithReadType, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( --!strict local MyModule = {} @@ -5912,7 +5890,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") {FFlag::LuauSolverV2, true}, {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult results = check(R"( @@ -5949,8 +5928,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") TEST_CASE_FIXTURE(Fixture, "oss_1888_and_or_subscriptable") { - ScopedFastFlag _{FFlag::LuauDfgForwardNilFromAndOr, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( export type CachedValue = { future: any, @@ -6034,10 +6011,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1914_access_after_assignment_with_assert TEST_CASE_FIXTURE(BuiltinsFixture, "cli_162179_avoid_exponential_blowup_in_normalization" * doctest::timeout(1.0)) { - ScopedFastFlag sffs[] = { - {FFlag::LuauSimplifyOutOfLine2, true}, - {FFlag::LuauNormalizationLimitTyvarUnionSize, true}, - }; + ScopedFastFlag sff{FFlag::LuauNormalizationLimitTyvarUnionSize, true}; const std::string source = "local res = {\n" + rep("\"foo\",\n", 100) + "}\n" @@ -6050,5 +6024,27 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_162179_avoid_exponential_blowup_in_norma LUAU_REQUIRE_NO_ERRORS(check(source)); } +TEST_CASE_FIXTURE(Fixture, "free_types_with_sealed_table_upper_bounds_can_still_be_expanded") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauExtendSealedTableUpperBounds, true}, + }; + + CheckResult result = check(R"( + function bar(a: {x: number}) end + + function foo(a) + bar(a) + + -- Here, a : A where A = never <: A <: {x: number} + -- The upper bound of A is a sealed table, but we nevertheless want to extend it. + a.nope() + end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK("({ read nope: () -> (...unknown) } & { x: number }) -> ()" == toString(requireType("foo"))); +} TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 004a9ad1..3f4f3637 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -25,11 +25,7 @@ LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauSimplifyOutOfLine2) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) -LUAU_FASTFLAG(LuauUpdateGetMetatableTypeSignature) -LUAU_FASTFLAG(LuauOccursCheckForRefinement) -LUAU_FASTFLAG(LuauInferPolarityOfReadWriteProperties) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) @@ -38,6 +34,7 @@ LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) using namespace Luau; @@ -615,7 +612,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ { { DOES_NOT_PASS_NEW_SOLVER_GUARD(); - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict local t = { x = 10, y = 20 } @@ -626,7 +623,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ } { - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict export type = number @@ -638,7 +635,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ { DOES_NOT_PASS_NEW_SOLVER_GUARD(); - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict function string.() end @@ -648,7 +645,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ } { - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict local function () end @@ -659,7 +656,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tc_after_error_recovery_no_replacement_name_ } { - getFrontend().setLuauSolverSelectionFromWorkspace(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); CheckResult result = check(R"( --!strict local dm = {} @@ -2019,6 +2016,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert") {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; auto result = check(R"( @@ -2055,6 +2053,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2") {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -2089,6 +2088,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion") {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; LUAU_REQUIRE_ERRORS(check(R"( @@ -2173,8 +2173,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_has_indexer_can_create_cyclic_union") TEST_CASE_FIXTURE(Fixture, "fuzzer_simplify_table_indexer" * doctest::timeout(0.5)) { - ScopedFastFlag _{FFlag::LuauSimplifyOutOfLine2, true}; - LUAU_REQUIRE_ERRORS(check(R"( _[_] += true _ = { @@ -2368,7 +2366,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; LUAU_REQUIRE_ERRORS(check(R"( @@ -2406,7 +2405,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_missing_follow_in_assign_index_constraint") TEST_CASE_FIXTURE(Fixture, "fuzzer_occurs_check_stack_overflow") { - ScopedFastFlag _{FFlag::LuauOccursCheckForRefinement, true}; // We just want this to not stack overflow, it's ok for it to barf errors. LUAU_REQUIRE_ERRORS(check(R"( _ = if _ then _ @@ -2418,10 +2416,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzzer_occurs_check_stack_overflow") TEST_CASE_FIXTURE(Fixture, "fuzzer_infer_divergent_rw_props") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauInferPolarityOfReadWriteProperties, true}, - }; + ScopedFastFlag sffs{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( return function(l0:{_:(any)&(any),write _:any,}) @@ -2445,9 +2440,6 @@ TEST_CASE_FIXTURE(Fixture, "oss_1815_verbatim") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauInferActualIfElseExprType2, true}, - // This is needed so that we don't hide the string literal free types - // behind a `union<_, _>` - {FFlag::LuauSimplifyOutOfLine2, true}, }; CheckResult results = check(R"( @@ -2526,7 +2518,6 @@ TEST_CASE_FIXTURE(Fixture, "simplify_constraint_can_force") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauForceSimplifyConstraint2, true}, - {FFlag::LuauSimplifyOutOfLine2, true}, // NOTE: Feel free to clip this test when this flag is clipped. {FFlag::LuauPushFunctionTypesInFunctionStatement, false}, }; @@ -2558,6 +2549,9 @@ TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden") {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, + // This debug flag is normally on, but we turn it off as we're testing + // the exact behavior it enables. + {FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, false}, }; CheckResult results = check(R"( diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 4ca8d641..2551e2a1 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -5,7 +5,10 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) using namespace Luau; @@ -361,6 +364,12 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type" TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type_2") { + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauRefineDistributesOverUnions, true}, + {FFlag::LuauReduceSetTypeStackPressure, true}, + }; + CheckResult result = check(R"( local t = {x = nil} @@ -375,10 +384,9 @@ TEST_CASE_FIXTURE(TypeStateFixture, "captured_locals_do_not_mutate_upvalue_type_ LUAU_REQUIRE_ERROR_COUNT(1, result); auto err = get(result.errors[0]); - CHECK_EQ("t | { x: number }", toString(err->wantedType)); - CHECK_EQ("{ x: string }", toString(err->givenType)); - - CHECK("{ x: nil } | { x: number }" == toString(requireTypeAtPosition({4, 18}), {true})); + CHECK_EQ("number?", toString(err->wantedType)); + CHECK_EQ("string", toString(err->givenType)); + CHECK("{ x: number? }" == toString(requireTypeAtPosition({4, 18}), {true})); CHECK("number?" == toString(requireTypeAtPosition({4, 20}))); } @@ -403,6 +411,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index 4ef2db6e..6fcbc03b 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -10,6 +10,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauEagerGeneralization4); LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) +LUAU_FASTFLAG(LuauResetConditionalContextProperly) TEST_SUITE_BEGIN("TypeInferUnknownNever"); @@ -333,6 +334,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i ScopedFastFlag sffs[] = { {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauForceSimplifyConstraint2, true}, }; @@ -360,7 +362,8 @@ TEST_CASE_FIXTURE(Fixture, "math_operators_and_never") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} + {FFlag::LuauTrackFreeInteriorTypePacks, true}, + {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/Unifier2.test.cpp b/tests/Unifier2.test.cpp index 65734103..41dc64c8 100644 --- a/tests/Unifier2.test.cpp +++ b/tests/Unifier2.test.cpp @@ -53,7 +53,7 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: number") { auto [left, freeLeft] = freshType(); - CHECK(u2.unify(left, builtinTypes.numberType)); + CHECK(UnifyResult::Ok == u2.unify(left, builtinTypes.numberType)); CHECK("never" == toString(freeLeft->lowerBound)); CHECK("number" == toString(freeLeft->upperBound)); @@ -63,7 +63,7 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "number <: T") { auto [right, freeRight] = freshType(); - CHECK(u2.unify(builtinTypes.numberType, right)); + CHECK(UnifyResult::Ok == u2.unify(builtinTypes.numberType, right)); CHECK("number" == toString(freeRight->lowerBound)); CHECK("unknown" == toString(freeRight->upperBound)); @@ -74,7 +74,7 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U") auto [left, freeLeft] = freshType(); auto [right, freeRight] = freshType(); - CHECK(u2.unify(left, right)); + CHECK(UnifyResult::Ok == u2.unify(left, right)); CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(left)); CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(right)); diff --git a/tests/conformance/native.luau b/tests/conformance/native.luau index 9372c8e9..dae8ddf9 100644 --- a/tests/conformance/native.luau +++ b/tests/conformance/native.luau @@ -379,7 +379,7 @@ local function vec3compsum(a: vector) return a.X + a.Y + a.Z end -assert(vec3compsum(vector(1, 2, 4)) == 7.0) +assert(vec3compsum(vector.create(1, 2, 4)) == 7.0) local function vec3add(a: vector, b: vector) return a + b end local function vec3sub(a: vector, b: vector) return a - b end @@ -387,17 +387,17 @@ local function vec3mul(a: vector, b: vector) return a * b end local function vec3div(a: vector, b: vector) return a / b end local function vec3neg(a: vector) return -a end -assert(vec3add(vector(10, 20, 40), vector(1, 0, 2)) == vector(11, 20, 42)) -assert(vec3sub(vector(10, 20, 40), vector(1, 0, 2)) == vector(9, 20, 38)) -assert(vec3mul(vector(10, 20, 40), vector(1, 0, 2)) == vector(10, 0, 80)) -assert(vec3div(vector(10, 20, 40), vector(1, 0, 2)) == vector(10, math.huge, 20)) -assert(vec3neg(vector(10, 20, 40)) == vector(-10, -20, -40)) +assert(vec3add(vector.create(10, 20, 40), vector.create(1, 0, 2)) == vector.create(11, 20, 42)) +assert(vec3sub(vector.create(10, 20, 40), vector.create(1, 0, 2)) == vector.create(9, 20, 38)) +assert(vec3mul(vector.create(10, 20, 40), vector.create(1, 0, 2)) == vector.create(10, 0, 80)) +assert(vec3div(vector.create(10, 20, 40), vector.create(1, 0, 2)) == vector.create(10, math.huge, 20)) +assert(vec3neg(vector.create(10, 20, 40)) == vector.create(-10, -20, -40)) local function vec3mulnum(a: vector, b: number) return a * b end local function vec3mulconst(a: vector) return a * 4 end -assert(vec3mulnum(vector(10, 20, 40), 4) == vector(40, 80, 160)) -assert(vec3mulconst(vector(10, 20, 40), 4) == vector(40, 80, 160)) +assert(vec3mulnum(vector.create(10, 20, 40), 4) == vector.create(40, 80, 160)) +assert(vec3mulconst(vector.create(10, 20, 40), 4) == vector.create(40, 80, 160)) local function bufferbounds(zero) local b1 = buffer.create(1) diff --git a/tests/conformance/native_types.luau b/tests/conformance/native_types.luau index 639ce80b..03a51237 100644 --- a/tests/conformance/native_types.luau +++ b/tests/conformance/native_types.luau @@ -67,7 +67,7 @@ ecall(checkthread, 2) call(checkuserdata, newproxy()) ecall(checkuserdata, 2) -call(checkvector, vector(1, 2, 3)) +call(checkvector, vector.create(1, 2, 3)) ecall(checkvector, 2) call(checkbuffer, buffer.create(10)) diff --git a/tests/conformance/vector.luau b/tests/conformance/vector.luau index 3c70cfb0..c844d160 100644 --- a/tests/conformance/vector.luau +++ b/tests/conformance/vector.luau @@ -2,7 +2,7 @@ print('testing vectors') -- detect vector size -local vector_size = if pcall(function() return vector(0, 0, 0).w end) then 4 else 3 +local vector_size = if pcall(function() return vector.create(0, 0, 0).w end) then 4 else 3 function ecall(fn, ...) local ok, err = pcall(fn, ...) @@ -11,99 +11,99 @@ function ecall(fn, ...) end -- equality -assert(vector(1, 2, 3) == vector(1, 2, 3)) -assert(vector(0, 1, 2) == vector(-0, 1, 2)) -assert(vector(1, 2, 3) ~= vector(1, 2, 4)) +assert(vector.create(1, 2, 3) == vector.create(1, 2, 3)) +assert(vector.create(0, 1, 2) == vector.create(-0, 1, 2)) +assert(vector.create(1, 2, 3) ~= vector.create(1, 2, 4)) -- rawequal -assert(rawequal(vector(1, 2, 3), vector(1, 2, 3))) -assert(rawequal(vector(0, 1, 2), vector(-0, 1, 2))) -assert(not rawequal(vector(1, 2, 3), vector(1, 2, 4))) +assert(rawequal(vector.create(1, 2, 3), vector.create(1, 2, 3))) +assert(rawequal(vector.create(0, 1, 2), vector.create(-0, 1, 2))) +assert(not rawequal(vector.create(1, 2, 3), vector.create(1, 2, 4))) -- type & tostring -assert(type(vector(1, 2, 3)) == "vector") +assert(type(vector.create(1, 2, 3)) == "vector") if vector_size == 4 then - assert(tostring(vector(1, 2, 3, 4)) == "1, 2, 3, 4") - assert(tostring(vector(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0") + assert(tostring(vector.create(1, 2, 3, 4)) == "1, 2, 3, 4") + assert(tostring(vector.create(-1, 2, 0.5, 0)) == "-1, 2, 0.5, 0") else - assert(tostring(vector(1, 2, 3)) == "1, 2, 3") - assert(tostring(vector(-1, 2, 0.5)) == "-1, 2, 0.5") + assert(tostring(vector.create(1, 2, 3)) == "1, 2, 3") + assert(tostring(vector.create(-1, 2, 0.5)) == "-1, 2, 0.5") end local t = {} -- basic table access -t[vector(1, 2, 3)] = 42 -assert(t[vector(1, 2, 3)] == 42) -assert(t[vector(1, 2, 4)] == nil) +t[vector.create(1, 2, 3)] = 42 +assert(t[vector.create(1, 2, 3)] == 42) +assert(t[vector.create(1, 2, 4)] == nil) -- negative zero should hash the same as zero -assert(t[vector(0, 0, 0)] == nil) -t[vector(0, 0, 0)] = "hello" -assert(t[vector(0, 0, 0)] == "hello") -assert(t[vector(0, -0, 0)] == "hello") +assert(t[vector.create(0, 0, 0)] == nil) +t[vector.create(0, 0, 0)] = "hello" +assert(t[vector.create(0, 0, 0)] == "hello") +assert(t[vector.create(0, -0, 0)] == "hello") -- test arithmetic instructions -assert(vector(1, 2, 4) + vector(8, 16, 24) == vector(9, 18, 28)); -assert(vector(1, 2, 4) - vector(8, 16, 24) == vector(-7, -14, -20)); +assert(vector.create(1, 2, 4) + vector.create(8, 16, 24) == vector.create(9, 18, 28)); +assert(vector.create(1, 2, 4) - vector.create(8, 16, 24) == vector.create(-7, -14, -20)); local val = 1/'8' -assert(vector(1, 2, 4) * vector(8, 16, 24) == vector(8, 32, 96)); -assert(vector(1, 2, 4) * 8 == vector(8, 16, 32)); -assert(vector(1, 2, 4) * (1 / val) == vector(8, 16, 32)); -assert(8 * vector(8, 16, 24) == vector(64, 128, 192)); -assert(vector(1, 2, 4) * '8' == vector(8, 16, 32)); -assert('8' * vector(8, 16, 24) == vector(64, 128, 192)); +assert(vector.create(1, 2, 4) * vector.create(8, 16, 24) == vector.create(8, 32, 96)); +assert(vector.create(1, 2, 4) * 8 == vector.create(8, 16, 32)); +assert(vector.create(1, 2, 4) * (1 / val) == vector.create(8, 16, 32)); +assert(8 * vector.create(8, 16, 24) == vector.create(64, 128, 192)); +assert(vector.create(1, 2, 4) * '8' == vector.create(8, 16, 32)); +assert('8' * vector.create(8, 16, 24) == vector.create(64, 128, 192)); -assert(vector(1, 2, 4) * -0.125 == vector(-0.125, -0.25, -0.5)) -assert(-0.125 * vector(1, 2, 4) == vector(-0.125, -0.25, -0.5)) +assert(vector.create(1, 2, 4) * -0.125 == vector.create(-0.125, -0.25, -0.5)) +assert(-0.125 * vector.create(1, 2, 4) == vector.create(-0.125, -0.25, -0.5)) -assert(vector(1, 2, 4) * 100 == vector(100, 200, 400)) -assert(100 * vector(1, 2, 4) == vector(100, 200, 400)) +assert(vector.create(1, 2, 4) * 100 == vector.create(100, 200, 400)) +assert(100 * vector.create(1, 2, 4) == vector.create(100, 200, 400)) if vector_size == 4 then - assert(vector(1, 2, 4, 8) / vector(8, 16, 24, 32) == vector(1/8, 2/16, 4/24, 8/32)); - assert(8 / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4)); - assert('8' / vector(8, 16, 24, 32) == vector(1, 1/2, 1/3, 1/4)); + assert(vector.create(1, 2, 4, 8) / vector.create(8, 16, 24, 32) == vector.create(1/8, 2/16, 4/24, 8/32)); + assert(8 / vector.create(8, 16, 24, 32) == vector.create(1, 1/2, 1/3, 1/4)); + assert('8' / vector.create(8, 16, 24, 32) == vector.create(1, 1/2, 1/3, 1/4)); else - assert(vector(1, 2, 4) / vector(8, 16, 24, 1) == vector(1/8, 2/16, 4/24)); - assert(8 / vector(8, 16, 24) == vector(1, 1/2, 1/3)); - assert('8' / vector(8, 16, 24) == vector(1, 1/2, 1/3)); + assert(vector.create(1, 2, 4) / vector.create(8, 16, 24, 1) == vector.create(1/8, 2/16, 4/24)); + assert(8 / vector.create(8, 16, 24) == vector.create(1, 1/2, 1/3)); + assert('8' / vector.create(8, 16, 24) == vector.create(1, 1/2, 1/3)); end -assert(vector(1, 2, 4) / 8 == vector(1/8, 1/4, 1/2)); -assert(vector(1, 2, 4) / (1 / val) == vector(1/8, 2/8, 4/8)); -assert(vector(1, 2, 4) / '8' == vector(1/8, 1/4, 1/2)); +assert(vector.create(1, 2, 4) / 8 == vector.create(1/8, 1/4, 1/2)); +assert(vector.create(1, 2, 4) / (1 / val) == vector.create(1/8, 2/8, 4/8)); +assert(vector.create(1, 2, 4) / '8' == vector.create(1/8, 1/4, 1/2)); -assert(-vector(1, 2, 4) == vector(-1, -2, -4)); +assert(-vector.create(1, 2, 4) == vector.create(-1, -2, -4)); -- test floor division -assert(vector(1, 3, 5) // 2 == vector(0, 1, 2)) -assert(vector(1, 3, 5) // val == vector(8, 24, 40)) +assert(vector.create(1, 3, 5) // 2 == vector.create(0, 1, 2)) +assert(vector.create(1, 3, 5) // val == vector.create(8, 24, 40)) if vector_size == 4 then - assert(10 // vector(1, 2, 3, 4) == vector(10, 5, 3, 2)) - assert(vector(10, 9, 8, 7) // vector(1, 2, 3, 4) == vector(10, 4, 2, 1)) + assert(10 // vector.create(1, 2, 3, 4) == vector.create(10, 5, 3, 2)) + assert(vector.create(10, 9, 8, 7) // vector.create(1, 2, 3, 4) == vector.create(10, 4, 2, 1)) else - assert(10 // vector(1, 2, 3) == vector(10, 5, 3)) - assert(vector(10, 9, 8) // vector(1, 2, 3) == vector(10, 4, 2)) + assert(10 // vector.create(1, 2, 3) == vector.create(10, 5, 3)) + assert(vector.create(10, 9, 8) // vector.create(1, 2, 3) == vector.create(10, 4, 2)) end -- test NaN comparison -local nanv = vector(0/0, 0/0, 0/0) +local nanv = vector.create(0/0, 0/0, 0/0) assert(nanv ~= nanv); -- __index -assert(vector(1, 2, 2).Magnitude == 3) -assert(vector(0, 0, 0)['Dot'](vector(1, 2, 4), vector(5, 6, 7)) == 45) -assert(vector(2, 0, 0).Unit == vector(1, 0, 0)) +assert(vector.create(1, 2, 2).Magnitude == 3) +assert(vector.create(0, 0, 0)['Dot'](vector.create(1, 2, 4), vector.create(5, 6, 7)) == 45) +assert(vector.create(2, 0, 0).Unit == vector.create(1, 0, 0)) -- __namecall -assert(vector(1, 2, 4):Dot(vector(5, 6, 7)) == 45) -assert(ecall(function() vector(1, 2, 4):Dot() end) == "missing argument #2 (vector expected)") -assert(ecall(function() vector(1, 2, 4):Dot("a") end) == "invalid argument #2 (vector expected, got string)") +assert(vector.create(1, 2, 4):Dot(vector.create(5, 6, 7)) == 45) +assert(ecall(function() vector.create(1, 2, 4):Dot() end) == "missing argument #2 (vector expected)") +assert(ecall(function() vector.create(1, 2, 4):Dot("a") end) == "invalid argument #2 (vector expected, got string)") local function doDot1(a: vector, b) return a:Dot(b) @@ -113,39 +113,39 @@ local function doDot2(a: vector, b) return (a:Dot(b)) end -local v124 = vector(1, 2, 4) +local v124 = vector.create(1, 2, 4) -assert(doDot1(v124, vector(5, 6, 7)) == 45) -assert(doDot2(v124, vector(5, 6, 7)) == 45) +assert(doDot1(v124, vector.create(5, 6, 7)) == 45) +assert(doDot2(v124, vector.create(5, 6, 7)) == 45) assert(ecall(function() doDot1(v124, "a") end) == "invalid argument #2 (vector expected, got string)") assert(ecall(function() doDot2(v124, "a") end) == "invalid argument #2 (vector expected, got string)") -assert(select("#", doDot1(v124, vector(5, 6, 7))) == 1) -assert(select("#", doDot2(v124, vector(5, 6, 7))) == 1) +assert(select("#", doDot1(v124, vector.create(5, 6, 7))) == 1) +assert(select("#", doDot2(v124, vector.create(5, 6, 7))) == 1) -- can't use vector with NaN components as table key -assert(pcall(function() local t = {} t[vector(0/0, 2, 3)] = 1 end) == false) -assert(pcall(function() local t = {} t[vector(1, 0/0, 3)] = 1 end) == false) -assert(pcall(function() local t = {} t[vector(1, 2, 0/0)] = 1 end) == false) -assert(pcall(function() local t = {} rawset(t, vector(0/0, 2, 3), 1) end) == false) +assert(pcall(function() local t = {} t[vector.create(0/0, 2, 3)] = 1 end) == false) +assert(pcall(function() local t = {} t[vector.create(1, 0/0, 3)] = 1 end) == false) +assert(pcall(function() local t = {} t[vector.create(1, 2, 0/0)] = 1 end) == false) +assert(pcall(function() local t = {} rawset(t, vector.create(0/0, 2, 3), 1) end) == false) -assert(vector(1, 0, 0):Cross(vector(0, 1, 0)) == vector(0, 0, 1)) -assert(vector(0, 1, 0):Cross(vector(1, 0, 0)) == vector(0, 0, -1)) +assert(vector.create(1, 0, 0):Cross(vector.create(0, 1, 0)) == vector.create(0, 0, 1)) +assert(vector.create(0, 1, 0):Cross(vector.create(1, 0, 0)) == vector.create(0, 0, -1)) -- make sure we cover both builtin and C impl -assert(vector(1, 2, 4) == vector("1", "2", "4")) +assert(vector.create(1, 2, 4) == vector.create("1", "2", "4")) -- validate component access (both cases) -assert(vector(1, 2, 3).x == 1) -assert(vector(1, 2, 3).X == 1) -assert(vector(1, 2, 3).y == 2) -assert(vector(1, 2, 3).Y == 2) -assert(vector(1, 2, 3).z == 3) -assert(vector(1, 2, 3).Z == 3) +assert(vector.create(1, 2, 3).x == 1) +assert(vector.create(1, 2, 3).X == 1) +assert(vector.create(1, 2, 3).y == 2) +assert(vector.create(1, 2, 3).Y == 2) +assert(vector.create(1, 2, 3).z == 3) +assert(vector.create(1, 2, 3).Z == 3) -- additional checks for 4-component vectors if vector_size == 4 then - assert(vector(1, 2, 3, 4).w == 4) - assert(vector(1, 2, 3, 4).W == 4) + assert(vector.create(1, 2, 3, 4).w == 4) + assert(vector.create(1, 2, 3, 4).W == 4) end -- negative zero should hash the same as zero @@ -153,15 +153,15 @@ end do local larget = {} for i = 1, 2^14 do - larget[vector(0, 0, i)] = true + larget[vector.create(0, 0, i)] = true end - larget[vector(0, 0, 0)] = 42 + larget[vector.create(0, 0, 0)] = 42 - assert(larget[vector(0, 0, 0)] == 42) - assert(larget[vector(0, 0, -0)] == 42) - assert(larget[vector(0, -0, 0)] == 42) - assert(larget[vector(-0, 0, 0)] == 42) + assert(larget[vector.create(0, 0, 0)] == 42) + assert(larget[vector.create(0, 0, -0)] == 42) + assert(larget[vector.create(0, -0, 0)] == 42) + assert(larget[vector.create(-0, 0, 0)] == 42) end local function numvectemporary() @@ -174,7 +174,7 @@ local function numvectemporary() return tmp, num2 end - local a, b = proptab.vec3compsum(vector(2, 6, 0)) + local a, b = proptab.vec3compsum(vector.create(2, 6, 0)) assert(a.X == 0.25) assert(a.Y == 0.75) From 31725a65216635400130cc51a971c98e3f37c9b7 Mon Sep 17 00:00:00 2001 From: ffrostfall <80861876+ffrostfall@users.noreply.github.com> Date: Thu, 21 Aug 2025 09:51:52 -0400 Subject: [PATCH 2/8] Fix the fuzzer being built even if LUAU_BUILD_TESTS is off (#1830) This PR fixes the fuzzer always being built even when LUAU_BUILD_TESTS is off. This is done by moving the `include_subdirectory` for the fuzzer inside of the if block under LUAU_BUILD_TESTS. --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 03f235ce..12613018 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -266,6 +266,7 @@ if(LUAU_BUILD_TESTS) target_link_libraries(Luau.CLI.Test PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.Require Luau.CLI.lib isocline) target_link_libraries(Luau.CLI.Test PRIVATE osthreads) + add_subdirectory(fuzz) endif() if(LUAU_BUILD_WEB) @@ -282,8 +283,6 @@ if(LUAU_BUILD_WEB) target_link_options(Luau.Web PRIVATE -sSINGLE_FILE=1) endif() -add_subdirectory(fuzz) - # validate dependencies for internal libraries foreach(LIB Luau.Ast Luau.Compiler Luau.Config Luau.Analysis Luau.EqSat Luau.CodeGen Luau.VM) if(TARGET ${LIB}) From 0afee00064945abbeb6e6ef2b87633564c54e176 Mon Sep 17 00:00:00 2001 From: ariel Date: Fri, 22 Aug 2025 15:26:46 -0700 Subject: [PATCH 3/8] Sync to upstream/release/688 (#1968) --- Analysis/include/Luau/Error.h | 18 +- Analysis/include/Luau/Instantiation.h | 4 +- Analysis/include/Luau/Subtyping.h | 13 +- Analysis/include/Luau/TxnLog.h | 9 - Analysis/include/Luau/Unifier.h | 3 - Analysis/include/Luau/VisitType.h | 4 +- Analysis/src/AutocompleteCore.cpp | 39 +- Analysis/src/BuiltinDefinitions.cpp | 35 +- Analysis/src/BuiltinTypeFunctions.cpp | 3 +- Analysis/src/Constraint.cpp | 12 +- Analysis/src/ConstraintGenerator.cpp | 366 ++++++++++-------- Analysis/src/ConstraintSolver.cpp | 65 ++-- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 42 +- Analysis/src/Error.cpp | 40 ++ Analysis/src/Frontend.cpp | 3 +- Analysis/src/Generalization.cpp | 3 +- Analysis/src/InferPolarity.cpp | 3 +- Analysis/src/IostreamHelpers.cpp | 18 + Analysis/src/OverloadResolution.cpp | 15 + Analysis/src/Quantify.cpp | 4 +- Analysis/src/Subtyping.cpp | 127 +++++- Analysis/src/ToString.cpp | 3 +- Analysis/src/TxnLog.cpp | 102 +++-- Analysis/src/TypeChecker2.cpp | 115 +++--- Analysis/src/TypeFunction.cpp | 5 +- Analysis/src/TypeFunctionReductionGuesser.cpp | 4 +- Analysis/src/TypeUtils.cpp | 13 + Analysis/src/Unifier.cpp | 75 +--- Ast/include/Luau/Ast.h | 21 +- Ast/src/Parser.cpp | 43 +- CodeGen/include/Luau/ConditionA64.h | 29 ++ CodeGen/include/Luau/ConditionX64.h | 52 ++- CodeGen/include/Luau/IrData.h | 5 + CodeGen/include/Luau/IrUtils.h | 1 + CodeGen/src/BytecodeAnalysis.cpp | 6 + CodeGen/src/IrDump.cpp | 2 + CodeGen/src/IrLoweringA64.cpp | 26 ++ CodeGen/src/IrLoweringX64.cpp | 32 +- CodeGen/src/IrTranslateBuiltins.cpp | 35 +- CodeGen/src/IrUtils.cpp | 14 + CodeGen/src/OptimizeConstProp.cpp | 5 + Common/include/Luau/Bytecode.h | 2 + Compiler/src/Builtins.cpp | 6 + Compiler/src/Types.cpp | 1 + VM/src/lbuiltins.cpp | 24 ++ VM/src/lnumutils.h | 5 + VM/src/lveclib.cpp | 24 ++ tests/Autocomplete.test.cpp | 166 +++++++- tests/Conformance.test.cpp | 8 + tests/Fixture.h | 2 - tests/FragmentAutocomplete.test.cpp | 6 - tests/Generalization.test.cpp | 32 +- tests/IrLowering.test.cpp | 32 ++ tests/RuntimeLimits.test.cpp | 3 - tests/Subtyping.test.cpp | 16 + tests/ToString.test.cpp | 12 +- tests/TypeFunction.user.test.cpp | 32 ++ tests/TypeInfer.builtins.test.cpp | 6 +- tests/TypeInfer.classes.test.cpp | 3 - tests/TypeInfer.functions.test.cpp | 32 +- tests/TypeInfer.generics.test.cpp | 54 ++- tests/TypeInfer.intersectionTypes.test.cpp | 3 - tests/TypeInfer.refinements.test.cpp | 10 +- tests/TypeInfer.tables.test.cpp | 54 +-- tests/TypeInfer.test.cpp | 78 ++-- tests/TypeVar.test.cpp | 2 +- tests/conformance/native.luau | 11 + tests/conformance/vector_library.luau | 10 + tests/main.cpp | 16 + 69 files changed, 1473 insertions(+), 591 deletions(-) diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index a8e1c76f..d6a4c800 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -1,11 +1,12 @@ // 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 "Luau/NotNull.h" #include "Luau/Type.h" +#include "Luau/TypeIds.h" #include "Luau/Variant.h" -#include "Luau/Ast.h" #include @@ -513,6 +514,18 @@ struct RecursiveRestraintViolation } }; +// Error during subtyping when the inferred bounds of a generic type are incompatible +struct GenericBoundsMismatch +{ + std::string_view genericName; + std::vector lowerBounds; + std::vector upperBounds; + + GenericBoundsMismatch(std::string_view genericName, TypeIds lowerBoundSet, TypeIds upperBoundSet); + + bool operator==(const GenericBoundsMismatch& rhs) const; +}; + using TypeErrorData = Variant< TypeMismatch, UnknownSymbol, @@ -569,7 +582,8 @@ using TypeErrorData = Variant< GenericTypeCountMismatch, GenericTypePackCountMismatch, MultipleNonviableOverloads, - RecursiveRestraintViolation>; + RecursiveRestraintViolation, + GenericBoundsMismatch>; struct TypeErrorSummary { diff --git a/Analysis/include/Luau/Instantiation.h b/Analysis/include/Luau/Instantiation.h index b3482ecd..acca6780 100644 --- a/Analysis/include/Luau/Instantiation.h +++ b/Analysis/include/Luau/Instantiation.h @@ -7,6 +7,8 @@ #include "Luau/Unifiable.h" #include "Luau/VisitType.h" +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) + namespace Luau { @@ -93,7 +95,7 @@ struct GenericTypeFinder : TypeOnceVisitor bool found = false; GenericTypeFinder() - : TypeOnceVisitor("GenericTypeFinder") + : TypeOnceVisitor("GenericTypeFinder", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 648db98c..357c800c 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -68,6 +68,9 @@ struct SubtypingResult // those assumptions are recorded here. std::vector assumedConstraints; + /// If any generic bounds were invalid, report them here + std::vector genericBoundsMismatches; + SubtypingResult& andAlso(const SubtypingResult& other); SubtypingResult& orElse(const SubtypingResult& other); SubtypingResult& withBothComponent(TypePath::Component component); @@ -358,7 +361,15 @@ struct Subtyping NotNull scope, SubtypingResult& original); - SubtypingResult checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull scope); + SubtypingResult checkGenericBounds( + const SubtypingEnvironment::GenericBounds& bounds, + SubtypingEnvironment& env, + NotNull scope, + std::string_view genericName + ); + + // TODO: Clip with LuauSubtypingReportGenericBoundMismatches + SubtypingResult checkGenericBounds_DEPRECATED(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull scope); static void maybeUpdateBounds( TypeId here, diff --git a/Analysis/include/Luau/TxnLog.h b/Analysis/include/Luau/TxnLog.h index 1d5e1a47..1d016cbb 100644 --- a/Analysis/include/Luau/TxnLog.h +++ b/Analysis/include/Luau/TxnLog.h @@ -112,7 +112,6 @@ struct TxnLog // If both logs talk about the same type, pack, or table, the rhs takes // priority. void concat(TxnLog rhs); - void concatAsIntersections(TxnLog rhs, NotNull arena); void concatAsUnion(TxnLog rhs, NotNull arena); // Commits the TxnLog, rebinding all type pointers to their pending states. @@ -269,8 +268,6 @@ struct TxnLog return Luau::get_if(&ty->ty) != nullptr; } - std::pair, std::vector> getChanges() const; - private: // unique_ptr is used to give us stable pointers across insertions into the // map. Otherwise, it would be really easy to accidentally invalidate the @@ -291,12 +288,6 @@ struct TxnLog void popSeen(TypeOrPackId lhs, TypeOrPackId rhs); public: - // There is one spot in the code where TxnLog has to reconcile collisions - // between parallel logs. In that codepath, we have to work out which of two - // FreeTypes subsumes the other. If useScopes is false, the TypeLevel is - // used. Else we use the embedded Scope*. - bool useScopes = false; - // It is sometimes the case under DCR that we speculatively rebind // GenericTypes to other types as though they were free. We mark logs that // contain these kinds of substitutions as radioactive so that we know that diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index eb978fba..7e1ef957 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -80,9 +80,6 @@ struct Unifier bool checkInhabited = true; // Normalize types to check if they are inhabited CountMismatch::Context ctx = CountMismatch::Arg; - // If true, generics act as free types when unifying. - bool hideousFixMeGenericsAreActuallyFree = false; - UnifierSharedState& sharedState; // When the Unifier is forced to unify two blocked types (or packs), they diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index 6c03fb29..3d52f0e7 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -556,7 +556,7 @@ struct GenericTypeVisitor */ struct TypeVisitor : GenericTypeVisitor> { - explicit TypeVisitor(const std::string visitorName, bool skipBoundTypes = false) + explicit TypeVisitor(const std::string visitorName, bool skipBoundTypes) : GenericTypeVisitor{visitorName, {}, skipBoundTypes} { } @@ -565,7 +565,7 @@ struct TypeVisitor : GenericTypeVisitor> /// Visit each type under a given type. Each type will only be checked once even if there are multiple paths to it. struct TypeOnceVisitor : GenericTypeVisitor> { - explicit TypeOnceVisitor(const std::string visitorName, bool skipBoundTypes = false) + explicit TypeOnceVisitor(const std::string visitorName, bool skipBoundTypes) : GenericTypeVisitor{visitorName, DenseHashSet{nullptr}, skipBoundTypes} { } diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index 66908920..d680a31a 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -25,6 +25,7 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) +LUAU_FASTFLAGVARIABLE(LuauIncludeBreakContinueStatements) static const std::unordered_set kStatementStartingKeywords = {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -1210,6 +1211,28 @@ static bool isBindingLegalAtCurrentPosition(const Symbol& symbol, const Binding& return binding.location == Location() || !binding.location.containsClosed(pos); } +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() || + (*it)->is()) + return false; + + if (auto statWhile = (*it)->as(); statWhile && statWhile->body->location.contains(position)) + return true; + else if (auto statFor = (*it)->as(); statFor && statFor->body->location.contains(position)) + return true; + else if (auto statForIn = (*it)->as(); statForIn && statForIn->body->location.contains(position)) + return true; + else if (auto statRepeat = (*it)->as(); statRepeat && statRepeat->body->location.contains(position)) + return true; + } + + return false; +} + static AutocompleteEntryMap autocompleteStatement( const Module& module, const std::vector& ancestry, @@ -1254,8 +1277,20 @@ static AutocompleteEntryMap autocompleteStatement( scope = scope->parent; } - for (const auto& kw : kStatementStartingKeywords) - result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); + if (FFlag::LuauIncludeBreakContinueStatements) + { + bool shouldIncludeBreakAndContinue = isValidBreakContinueContext(ancestry, position); + for (const auto& kw : kStatementStartingKeywords) + { + if ((kw != "break" && kw != "continue") || shouldIncludeBreakAndContinue) + result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); + } + } + else + { + for (const auto& kw : kStatementStartingKeywords) + result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); + } for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it) { diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 15c7a528..de8f9911 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -34,7 +34,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) -LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauEmplaceNotPushBack) @@ -409,32 +408,14 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC if (frontend.getLuauSolverMode() == SolverMode::New) { - TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); - - if (FFlag::LuauUpdateSetMetatableTypeSignature) - { - // setmetatable(T, MT) -> setmetatable - TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}}); - addGlobalBinding( - globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau" - ); - } - else - { - // clang-format off - // setmetatable(T, MT) -> { @metatable MT, T } - addGlobalBinding(globals, "setmetatable", - arena.addType( - FunctionType{ - {genericT, genericMT}, - {}, - arena.addTypePack(TypePack{{genericT, genericMT}}), - arena.addTypePack(TypePack{{tMetaMT}}) - } - ), "@luau" - ); - // clang-format on - } + // setmetatable(T, MT) -> setmetatable + TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}}); + addGlobalBinding( + globals, + "setmetatable", + makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), + "@luau" + ); } else { diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index e7bba4de..8d50d31b 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -27,6 +27,7 @@ LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways) LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -1063,7 +1064,7 @@ struct FindRefinementBlockers : TypeOnceVisitor DenseHashSet found{nullptr}; FindRefinementBlockers() - : TypeOnceVisitor("FindRefinementBlockers") + : TypeOnceVisitor("FindRefinementBlockers", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index 877908d0..a7083e10 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -5,8 +5,8 @@ #include "Luau/VisitType.h" LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) +LUAU_FASTFLAGVARIABLE(LuauExplicitSkipBoundTypes) namespace Luau { @@ -24,7 +24,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor bool traverseIntoTypeFunctions = true; explicit ReferenceCountInitializer(NotNull result) - : TypeOnceVisitor("ReferenceCountInitializer") + : TypeOnceVisitor("ReferenceCountInitializer", FFlag::LuauExplicitSkipBoundTypes) , result(result) { } @@ -178,12 +178,10 @@ TypeIds Constraint::getMaybeMutatedFreeTypes() const rci.traverse(tcc->exprType); } - if (FFlag::LuauPushFunctionTypesInFunctionStatement) + // NOTE: this should probably be in an if-else chain with the above. + if (auto pftc = get(*this)) { - if (auto pftc = get(*this)) - { - rci.traverse(pftc->functionType); - } + rci.traverse(pftc->functionType); } return types; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 1c1395ea..e6dd494d 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -39,15 +39,17 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) -LUAU_FASTFLAGVARIABLE(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2) LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex) +LUAU_FASTFLAGVARIABLE(LuauTypeFunNoScopeMapRef) LUAU_FASTFLAGVARIABLE(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAGVARIABLE(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) namespace Luau { @@ -137,7 +139,7 @@ struct HasFreeType : TypeOnceVisitor bool result = false; HasFreeType() - : TypeOnceVisitor("TypeOnceVisitor") + : TypeOnceVisitor("TypeOnceVisitor", FFlag::LuauExplicitSkipBoundTypes) { } @@ -643,7 +645,7 @@ struct FindSimplificationBlockers : TypeOnceVisitor bool found = false; FindSimplificationBlockers() - : TypeOnceVisitor("FindSimplificationBlockers") + : TypeOnceVisitor("FindSimplificationBlockers", FFlag::LuauExplicitSkipBoundTypes) { } @@ -1565,49 +1567,42 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f sig.bodyScope->rvalueRefinements[def] = sig.signature; } - if (FFlag::LuauPushFunctionTypesInFunctionStatement) + if (auto indexName = function->name->as()) { - if (auto indexName = function->name->as()) - { - auto beginProp = checkpoint(this); - auto [fn, _] = check(scope, indexName); - auto endProp = checkpoint(this); - auto pftc = addConstraint( - sig.signatureScope, - function->func->location, - PushFunctionTypeConstraint{ - fn, - sig.signature, - NotNull{function->func}, - /* isSelf */ indexName->op == ':', - } - ); - forEachConstraint( - beginProp, - endProp, - this, - [pftc](const ConstraintPtr& c) - { - pftc->dependencies.emplace_back(c.get()); - } - ); - auto beginBody = checkpoint(this); - checkFunctionBody(sig.bodyScope, function->func); - auto endBody = checkpoint(this); - forEachConstraint( - beginBody, - endBody, - this, - [pftc](const ConstraintPtr& c) - { - c->dependencies.push_back(pftc); - } - ); - } - else - { - checkFunctionBody(sig.bodyScope, function->func); - } + auto beginProp = checkpoint(this); + auto [fn, _] = check(scope, indexName); + auto endProp = checkpoint(this); + auto pftc = addConstraint( + sig.signatureScope, + function->func->location, + PushFunctionTypeConstraint{ + fn, + sig.signature, + NotNull{function->func}, + /* isSelf */ indexName->op == ':', + } + ); + forEachConstraint( + beginProp, + endProp, + this, + [pftc](const ConstraintPtr& c) + { + pftc->dependencies.emplace_back(c.get()); + } + ); + auto beginBody = checkpoint(this); + checkFunctionBody(sig.bodyScope, function->func); + auto endBody = checkpoint(this); + forEachConstraint( + beginBody, + endBody, + this, + [pftc](const ConstraintPtr& c) + { + c->dependencies.push_back(pftc); + } + ); } else { @@ -1906,85 +1901,172 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio reportError(function->location, ReservedIdentifier{"typeof"}); } - auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); - LUAU_ASSERT(scopePtr); + if (FFlag::LuauTypeFunNoScopeMapRef) + { + auto scopeIt = astTypeFunctionEnvironmentScopes.find(function); + LUAU_ASSERT(scopeIt); - Checkpoint startCheckpoint = checkpoint(this); - FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt); + ScopePtr environmentScope = *scopeIt; - // 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()}); + Checkpoint startCheckpoint = checkpoint(this); + FunctionSignature sig = checkFunctionSignature(environmentScope, function->body, /* expectedType */ std::nullopt); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.emplace_back(); - else - DEPRECATED_interiorTypes.push_back(std::vector{}); - checkFunctionBody(sig.bodyScope, function->body); - Checkpoint endCheckpoint = checkpoint(this); + // 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()}); - TypeId generalizedTy = arena->addType(BlockedType{}); - NotNull gc = addConstraint( - sig.signatureScope, - function->location, - GeneralizationConstraint{ - generalizedTy, - sig.signature, - std::vector{}, + if (FFlag::LuauTrackFreeInteriorTypePacks) + interiorFreeTypes.emplace_back(); + else + DEPRECATED_interiorTypes.push_back(std::vector{}); + 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{}, + } + ); + + if (FFlag::LuauTrackFreeInteriorTypePacks) + { + sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); } - ); + else + sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); - if (FFlag::LuauTrackFreeInteriorTypePacks) - { - sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); - sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); + getMutable(generalizedTy)->setOwner(gc); + if (FFlag::LuauTrackFreeInteriorTypePacks) + interiorFreeTypes.pop_back(); + else + DEPRECATED_interiorTypes.pop_back(); + + 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); + + return ControlFlow::None; } else - sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); + { + auto scopePtr = astTypeFunctionEnvironmentScopes.find(function); + LUAU_ASSERT(scopePtr); - getMutable(generalizedTy)->setOwner(gc); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.pop_back(); - else - DEPRECATED_interiorTypes.pop_back(); + Checkpoint startCheckpoint = checkpoint(this); + FunctionSignature sig = checkFunctionSignature(*scopePtr, function->body, /* expectedType */ std::nullopt); - Constraint* previous = nullptr; - forEachConstraint( - startCheckpoint, - endCheckpoint, - this, - [gc, &previous](const ConstraintPtr& constraint) + // 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()}); + + if (FFlag::LuauTrackFreeInteriorTypePacks) + interiorFreeTypes.emplace_back(); + else + DEPRECATED_interiorTypes.push_back(std::vector{}); + 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{}, + } + ); + + if (FFlag::LuauTrackFreeInteriorTypePacks) { - gc->dependencies.emplace_back(constraint.get()); + sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); + } + else + sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); - if (auto psc = get(*constraint); psc && psc->returns) + getMutable(generalizedTy)->setOwner(gc); + if (FFlag::LuauTrackFreeInteriorTypePacks) + interiorFreeTypes.pop_back(); + else + DEPRECATED_interiorTypes.pop_back(); + + Constraint* previous = nullptr; + forEachConstraint( + startCheckpoint, + endCheckpoint, + this, + [gc, &previous](const ConstraintPtr& constraint) { - if (previous) + gc->dependencies.emplace_back(constraint.get()); + + if (auto psc = get(*constraint); psc && psc->returns) { - if (FFlag::LuauEmplaceNotPushBack) - constraint->dependencies.emplace_back(previous); - else - constraint->dependencies.push_back(NotNull{previous}); - } + if (previous) + { + if (FFlag::LuauEmplaceNotPushBack) + constraint->dependencies.emplace_back(previous); + else + constraint->dependencies.push_back(NotNull{previous}); + } - previous = constraint.get(); + previous = constraint.get(); + } } - } - ); + ); - std::optional existingFunctionTy = (*scopePtr)->lookup(function->name); + std::optional existingFunctionTy = (*scopePtr)->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) @@ -2690,6 +2772,15 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExpr* expr, std:: Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantString* string, std::optional expectedType, bool forceSingleton) { + + if (FFlag::DebugLuauStringSingletonBasedOnQuotes) + { + if (string->quoteStyle == AstExprConstantString::QuotedSingle || forceSingleton) + return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; + + return Inference{builtinTypes->stringType}; + } + if (forceSingleton) return Inference{arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}})}; @@ -2725,6 +2816,16 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* boolExpr, std::optional expectedType, bool forceSingleton) { + if (FFlag::DebugLuauStringSingletonBasedOnQuotes) + { + const TypeId singletonType = boolExpr->value ? builtinTypes->trueType : builtinTypes->falseType; + if (forceSingleton) + return Inference{singletonType}; + + return Inference { builtinTypes->booleanType }; + } + + const TypeId singletonType = boolExpr->value ? builtinTypes->trueType : builtinTypes->falseType; if (forceSingleton) return Inference{singletonType}; @@ -4321,65 +4422,6 @@ TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location locati return resultType; } -struct FragmentTypeCheckGlobalPrepopulator_DEPRECATED : AstVisitor -{ - const NotNull globalScope; - const NotNull currentScope; - const NotNull dfg; - const NotNull arena; - - FragmentTypeCheckGlobalPrepopulator_DEPRECATED( - NotNull globalScope, - NotNull currentScope, - NotNull dfg, - NotNull arena - ) - : globalScope(globalScope) - , currentScope(currentScope) - , dfg(dfg) - , arena(arena) - { - } - - bool visit(AstExprGlobal* global) override - { - if (auto ty = globalScope->lookup(global->name)) - { - DefId def = dfg->getDef(global); - // We only want to write into the current scope the type of the global - currentScope->lvalueTypes[def] = *ty; - } - else if (auto ty = currentScope->lookup(global->name)) - { - // We are trying to create a binding for a brand new function, so we actually do have to write it into the scope. - DefId def = dfg->getDef(global); - // We only want to write into the current scope the type of the global - currentScope->lvalueTypes[def] = *ty; - } - - return true; - } - - bool visit(AstStatFunction* function) override - { - if (AstExprGlobal* g = function->name->as()) - { - if (auto ty = globalScope->lookup(g->name)) - { - currentScope->bindings[g->name] = Binding{*ty}; - } - else - { - // Hasn't existed since a previous typecheck - TypeId bt = arena->addType(BlockedType{}); - currentScope->bindings[g->name] = Binding{bt}; - } - } - - return true; - } -}; - struct GlobalPrepopulator : AstVisitor { const NotNull globalScope; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 30ab72e0..d163b073 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -36,8 +36,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeCheckFunctionCalls) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2) @@ -49,6 +47,8 @@ LUAU_FASTFLAGVARIABLE(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) namespace Luau { @@ -346,7 +346,7 @@ struct InfiniteTypeFinder : TypeOnceVisitor bool foundInfiniteType = false; explicit InfiniteTypeFinder(ConstraintSolver* solver, const InstantiationSignature& signature, NotNull scope) - : TypeOnceVisitor("InfiniteTypeFinder") + : TypeOnceVisitor("InfiniteTypeFinder", FFlag::LuauExplicitSkipBoundTypes) , solver(solver) , signature(signature) , scope(scope) @@ -683,7 +683,7 @@ struct TypeSearcher : TypeVisitor } explicit TypeSearcher(TypeId needle, Polarity initialPolarity) - : TypeVisitor("TypeSearcher") + : TypeVisitor("TypeSearcher", FFlag::LuauExplicitSkipBoundTypes) , needle(needle) , current(initialPolarity) { @@ -1664,7 +1664,7 @@ struct ContainsGenerics_DEPRECATED : public TypeOnceVisitor bool found = false; ContainsGenerics_DEPRECATED() - : TypeOnceVisitor("ContainsGenerics_DEPRECATED") + : TypeOnceVisitor("ContainsGenerics_DEPRECATED", FFlag::LuauExplicitSkipBoundTypes) { } @@ -1748,7 +1748,7 @@ struct ContainsGenerics : public TypeOnceVisitor NotNull> generics; explicit ContainsGenerics(NotNull> generics) - : TypeOnceVisitor("ContainsGenerics") + : TypeOnceVisitor("ContainsGenerics", FFlag::LuauExplicitSkipBoundTypes) , generics{generics} { } @@ -1887,7 +1887,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullis() || expr->is() || expr->is() || - expr->is() || (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls && expr->is())) + expr->is() || expr->is()) { if (ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) { @@ -1901,37 +1901,24 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullis() && - !ContainsGenerics::hasGeneric(expectedArgTy, NotNull{&genericTypesAndPacks})) - { - Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; - std::vector toBlock; - (void)matchLiteralType( - c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, expectedArgTy, actualArgTy, expr, toBlock - ); - LUAU_ASSERT(toBlock.empty()); - } } - if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls) + // 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. + for (auto& c : u2.incompleteSubtypes) { - // 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. - for (auto& c : u2.incompleteSubtypes) - { - NotNull addition = pushConstraint(constraint->scope, constraint->location, std::move(c)); - inheritBlocks(constraint, addition); - } + NotNull addition = pushConstraint(constraint->scope, constraint->location, std::move(c)); + inheritBlocks(constraint, addition); } return true; @@ -1962,6 +1949,7 @@ bool ConstraintSolver::tryDispatch(const TableCheckConstraint& c, NotNull constraint) { + LUAU_ASSERT(!FFlag::DebugLuauStringSingletonBasedOnQuotes); std::optional expectedType = c.expectedType ? std::make_optional(follow(*c.expectedType)) : std::nullopt; if (expectedType && (isBlocked(*expectedType) || get(*expectedType))) return block(*expectedType, constraint); @@ -2213,7 +2201,7 @@ struct BlockedTypeFinder : TypeOnceVisitor std::optional blocked; BlockedTypeFinder() - : TypeOnceVisitor("ContainsGenerics_DEPRECATED") + : TypeOnceVisitor("ContainsGenerics_DEPRECATED", FFlag::LuauExplicitSkipBoundTypes) { } @@ -2444,8 +2432,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNulllocation); + unblock(lhsType, constraint->location); } return true; @@ -3568,7 +3555,7 @@ struct Blocker : TypeOnceVisitor bool blocked = false; explicit Blocker(NotNull solver, NotNull constraint) - : TypeOnceVisitor("Blocker") + : TypeOnceVisitor("Blocker", FFlag::LuauExplicitSkipBoundTypes) , solver(solver) , constraint(constraint) { diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index e255451e..88e5c5ec 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.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/BuiltinDefinitions.h" +LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp) + namespace Luau { @@ -268,6 +270,37 @@ declare extern type vector with z: number end +declare vector: { + create: @checked (x: number, y: number, z: number?) -> vector, + magnitude: @checked (vec: vector) -> number, + normalize: @checked (vec: vector) -> vector, + cross: @checked (vec1: vector, vec2: vector) -> vector, + dot: @checked (vec1: vector, vec2: vector) -> number, + angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number, + floor: @checked (vec: vector) -> vector, + ceil: @checked (vec: vector) -> vector, + abs: @checked (vec: vector) -> vector, + sign: @checked (vec: vector) -> 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, + + zero: vector, + one: vector, +} + +)BUILTIN_SRC"; + +static const char* const kBuiltinDefinitionVectorSrc_DEPRECATED = R"BUILTIN_SRC( + +-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties +declare extern type vector with + x: number + y: number + z: number +end + declare vector: { create: @checked (x: number, y: number, z: number?) -> vector, magnitude: @checked (vec: vector) -> number, @@ -301,7 +334,14 @@ std::string getBuiltinDefinitionSource() result += kBuiltinDefinitionDebugSrc; result += kBuiltinDefinitionUtf8Src; result += kBuiltinDefinitionBufferSrc; - result += kBuiltinDefinitionVectorSrc; + if (FFlag::LuauTypeCheckerVectorLerp) + { + result += kBuiltinDefinitionVectorSrc; + } + else + { + result += kBuiltinDefinitionVectorSrc_DEPRECATED; + } return result; } diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index d771b2d4..be163e44 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -894,6 +894,27 @@ struct ErrorConverter { return "Recursive type being used with different parameters."; } + + std::string operator()(const GenericBoundsMismatch& e) const + { + std::string lowerBounds; + for (size_t i = 0; i < e.lowerBounds.size(); ++i) + { + if (i > 0) + lowerBounds += ", "; + lowerBounds += Luau::toString(e.lowerBounds[i]); + } + std::string upperBounds; + for (size_t i = 0; i < e.upperBounds.size(); ++i) + { + if (i > 0) + upperBounds += ", "; + upperBounds += Luau::toString(e.upperBounds[i]); + } + + return "The generic type parameter " + std::string{e.genericName} + "was found to have invalid bounds. Its lower bounds were [" + + lowerBounds + "], and its upper bounds were [" + upperBounds + "]."; + } }; struct InvalidNameChecker @@ -1297,6 +1318,18 @@ bool MultipleNonviableOverloads::operator==(const MultipleNonviableOverloads& rh return attemptedArgCount == rhs.attemptedArgCount; } +GenericBoundsMismatch::GenericBoundsMismatch(const std::string_view genericName, TypeIds lowerBoundSet, TypeIds upperBoundSet) + : genericName(genericName) + , lowerBounds(lowerBoundSet.take()) + , upperBounds(upperBoundSet.take()) +{ +} + +bool GenericBoundsMismatch::operator==(const GenericBoundsMismatch& rhs) const +{ + return genericName == rhs.genericName && lowerBounds == rhs.lowerBounds && upperBounds == rhs.upperBounds; +} + std::string toString(const TypeError& error) { return toString(error, TypeErrorToStringOptions{}); @@ -1523,6 +1556,13 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) else if constexpr (std::is_same_v) { } + else if constexpr (std::is_same_v) + { + for (auto& lowerBound : e.lowerBounds) + lowerBound = clone(lowerBound); + for (auto& upperBound : e.upperBounds) + upperBound = clone(upperBound); + } else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 36ed4a71..46696040 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -47,6 +47,7 @@ LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -1369,7 +1370,7 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons struct InternalTypeFinder : TypeOnceVisitor { InternalTypeFinder() - : TypeOnceVisitor("InternalTypeFinder") + : TypeOnceVisitor("InternalTypeFinder", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index cd7b73e3..1f246f1f 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -18,6 +18,7 @@ LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauReduceSetTypeStackPressure) LUAU_FASTINTVARIABLE(LuauGenericCounterMaxDepth, 15) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -1406,7 +1407,7 @@ struct GenericCounter : TypeVisitor bool hitLimits = false; explicit GenericCounter(NotNull> cachedTypes) - : TypeVisitor("GenericCounter") + : TypeVisitor("GenericCounter", FFlag::LuauExplicitSkipBoundTypes) , cachedTypes(cachedTypes) { } diff --git a/Analysis/src/InferPolarity.cpp b/Analysis/src/InferPolarity.cpp index e95d76d9..40c1fa5c 100644 --- a/Analysis/src/InferPolarity.cpp +++ b/Analysis/src/InferPolarity.cpp @@ -6,6 +6,7 @@ #include "Luau/VisitType.h" LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -21,7 +22,7 @@ struct InferPolarity : TypeVisitor Polarity polarity = Polarity::Positive; explicit InferPolarity(NotNull arena, NotNull scope) - : TypeVisitor("InferPolarity") + : TypeVisitor("InferPolarity", FFlag::LuauExplicitSkipBoundTypes) , arena(arena) , scope(scope) { diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index fd1435c7..7f245005 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -269,6 +269,24 @@ static void errorToString(std::ostream& stream, const T& err) stream << "MultipleNonviableOverloads { attemptedArgCount = " << err.attemptedArgCount << " }"; else if constexpr (std::is_same_v) stream << "RecursiveRestraintViolation"; + else if constexpr (std::is_same_v) + { + stream << "GenericBoundsMismatch { genericName = " << std::string{err.genericName} << ", lowerBounds = ["; + for (size_t i = 0; i < err.lowerBounds.size(); ++i) + { + if (i > 0) + stream << ", "; + stream << toString(err.lowerBounds[i]); + } + stream << "], upperBounds = ["; + for (size_t i = 0; i < err.upperBounds.size(); ++i) + { + if (i > 0) + stream << ", "; + stream << toString(err.upperBounds[i]); + } + stream << "] }"; + } else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index b313acc0..d9e718bd 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -13,6 +13,8 @@ LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) namespace Luau { @@ -487,6 +489,13 @@ 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; + } + switch (reason.variance) { case SubtypingVariance::Covariant: @@ -505,6 +514,12 @@ std::pair OverloadResolver::checkOverload_ } } + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + { + for (GenericBoundsMismatch& mismatch : sr.genericBoundsMismatches) + errors.emplace_back(fnExpr->location, std::move(mismatch)); + } + return {Analysis::OverloadIsNonviable, std::move(errors)}; } diff --git a/Analysis/src/Quantify.cpp b/Analysis/src/Quantify.cpp index 8d5cfbbf..d02cc00c 100644 --- a/Analysis/src/Quantify.cpp +++ b/Analysis/src/Quantify.cpp @@ -8,6 +8,8 @@ #include "Luau/Type.h" #include "Luau/VisitType.h" +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) + namespace Luau { @@ -21,7 +23,7 @@ struct Quantifier final : TypeOnceVisitor bool seenMutableType = false; explicit Quantifier(TypeLevel level) - : TypeOnceVisitor("Quantifier") + : TypeOnceVisitor("Quantifier", /* skipBoundTypes */ false) , level(level) { } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index f2052b28..b63be0f2 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -26,6 +26,7 @@ LUAU_FASTFLAGVARIABLE(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches) namespace Luau { @@ -174,6 +175,8 @@ SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other) normalizationTooComplex |= other.normalizationTooComplex; isCacheable &= other.isCacheable; errors.insert(errors.end(), other.errors.begin(), other.errors.end()); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end()); return *this; } @@ -196,6 +199,8 @@ SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other) normalizationTooComplex |= other.normalizationTooComplex; isCacheable &= other.isCacheable; errors.insert(errors.end(), other.errors.begin(), other.errors.end()); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end()); return *this; } @@ -710,8 +715,15 @@ SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNu { // Bounds should have exactly one entry LUAU_ASSERT(bounds->size() == 1); - if (!bounds->empty()) - result.andAlso(checkGenericBounds(bounds->back(), env, scope)); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + { + 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)); } } } @@ -2034,12 +2046,15 @@ SubtypingResult Subtyping::isCovariantWith( for (TypeId g : subFunction->generics) { g = follow(g); - if (get(g)) + if (const GenericType* gen = get(g)) { auto bounds = env.mappedGenerics.find(g); LUAU_ASSERT(bounds && !bounds->empty()); // Check the bounds are valid - result.andAlso(checkGenericBounds(bounds->back(), env, scope)); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name)); + else + result.andAlso(checkGenericBounds_DEPRECATED(bounds->back(), env, scope)); bounds->pop_back(); } @@ -2524,9 +2539,16 @@ SubtypingResult Subtyping::trySemanticSubtyping(SubtypingEnvironment& env, return original; } -SubtypingResult Subtyping::checkGenericBounds(const SubtypingEnvironment::GenericBounds& bounds, SubtypingEnvironment& env, NotNull scope) + +SubtypingResult Subtyping::checkGenericBounds( + const SubtypingEnvironment::GenericBounds& bounds, + SubtypingEnvironment& env, + NotNull scope, + std::string_view genericName +) { LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); + LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches); SubtypingResult result{true}; @@ -2607,6 +2629,101 @@ SubtypingResult Subtyping::checkGenericBounds(const SubtypingEnvironment::Generi SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope); boundsResult.reasoning.clear(); + if (res == NormalizationResult::False || !boundsResult.isSubtype) + result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound); + + result.andAlso(boundsResult); + + return result; +} + +SubtypingResult Subtyping::checkGenericBounds_DEPRECATED( + const SubtypingEnvironment::GenericBounds& bounds, + SubtypingEnvironment& env, + NotNull scope +) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); + LUAU_ASSERT(!FFlag::LuauSubtypingReportGenericBoundMismatches); + + SubtypingResult result{true}; + + const auto& [lb, ub] = bounds; + + TypeIds lbTypes; + for (TypeId t : lb) + { + t = follow(t); + if (const auto mappedBounds = env.mappedGenerics.find(t)) + { + if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it + continue; + + auto& [lowerBound, upperBound] = mappedBounds->back(); + // We're populating the lower bounds, so we prioritize the upper bounds of a mapped generic + if (!upperBound.empty()) + lbTypes.insert(upperBound.begin(), upperBound.end()); + else if (!lowerBound.empty()) + lbTypes.insert(lowerBound.begin(), lowerBound.end()); + else + lbTypes.insert(builtinTypes->unknownType); + } + else + lbTypes.insert(t); + } + + TypeIds ubTypes; + for (TypeId t : ub) + { + t = follow(t); + if (const auto mappedBounds = env.mappedGenerics.find(t)) + { + if (mappedBounds->empty()) // If the generic is no longer in scope, we don't have any info about it + continue; + + auto& [lowerBound, upperBound] = mappedBounds->back(); + // We're populating the upper bounds, so we prioritize the lower bounds of a mapped generic + if (!lowerBound.empty()) + ubTypes.insert(lowerBound.begin(), lowerBound.end()); + else if (!upperBound.empty()) + ubTypes.insert(upperBound.begin(), upperBound.end()); + else + ubTypes.insert(builtinTypes->unknownType); + } + else + ubTypes.insert(t); + } + TypeId lowerBound = makeAggregateType(lbTypes.take(), builtinTypes->neverType); + TypeId upperBound = makeAggregateType(ubTypes.take(), 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); return result; diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index fb0786c1..2c2a4503 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -22,6 +22,7 @@ LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) /* * Enables increasing levels of verbosity for Luau type names when stringifying. @@ -51,7 +52,7 @@ namespace struct FindCyclicTypes final : TypeVisitor { FindCyclicTypes() - : TypeVisitor("FindCyclicTypes") + : TypeVisitor("FindCyclicTypes", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/TxnLog.cpp b/Analysis/src/TxnLog.cpp index 572f38a6..cee6427f 100644 --- a/Analysis/src/TxnLog.cpp +++ b/Analysis/src/TxnLog.cpp @@ -9,6 +9,8 @@ #include #include +LUAU_FASTFLAGVARIABLE(LuauOccursCheckInCommit) + namespace Luau { @@ -84,29 +86,6 @@ void TxnLog::concat(TxnLog rhs) radioactive |= rhs.radioactive; } -void TxnLog::concatAsIntersections(TxnLog rhs, NotNull arena) -{ - for (auto& [ty, rightRep] : rhs.typeVarChanges) - { - if (rightRep->dead) - continue; - - if (auto leftRep = typeVarChanges.find(ty); leftRep && !(*leftRep)->dead) - { - TypeId leftTy = arena->addType((*leftRep)->pending.clone()); - TypeId rightTy = arena->addType(rightRep->pending.clone()); - typeVarChanges[ty]->pending.ty = IntersectionType{{leftTy, rightTy}}; - } - else - typeVarChanges[ty] = std::move(rightRep); - } - - for (auto& [tp, rep] : rhs.typePackChanges) - typePackChanges[tp] = std::move(rep); - - radioactive |= rhs.radioactive; -} - void TxnLog::concatAsUnion(TxnLog rhs, NotNull arena) { /* @@ -154,7 +133,7 @@ void TxnLog::concatAsUnion(TxnLog rhs, NotNull arena) // leftTy has been bound to rightTy, but rightTy has also been bound // to leftTy. We find the one that belongs to the more deeply nested // scope and remove it from the log. - const bool discardLeft = useScopes ? subsumes(lf->scope, rf->scope) : lf->level.subsumes(rf->level); + const bool discardLeft = lf->level.subsumes(rf->level); if (discardLeft) (*leftRep)->dead = true; @@ -188,6 +167,57 @@ void TxnLog::concatAsUnion(TxnLog rhs, NotNull arena) radioactive |= rhs.radioactive; } +// Like follow(), but only takes a single step. +// +// This is potentailly performance sensitive, so we use nullptr rather than an +// optional for the return type here. +static TypeId followOnce(TxnLog& log, TypeId ty) +{ + if (auto bound = log.get(ty)) + return bound->boundTo; + if (auto tt = log.get(ty)) + return tt->boundTo.value_or(nullptr); + + return nullptr; +} + +// We must take extra care not to replace a type with a BoundType to itself. We +// check each BoundType along the chain +// +// This function returns true if any of the bound types pointed at by 'needle' +// point at 'haystack'. +static bool occurs(TxnLog& log, TypeId needle, TypeId haystack) +{ + TypeId tortoise = needle; + TypeId hare = needle; + + while (true) + { + if (tortoise == haystack) + return true; + + TypeId g = followOnce(log, tortoise); + if (!g) + return false; + tortoise = g; + + // Cycle detection: The hare steps twice for each step that the tortoise takes. + // If ever the two meet, it can only be because the track is cyclic. + // When we hit the end of the chain, hare becomes nullptr. + if (hare) + { + hare = followOnce(log, hare); + if (hare) + { + hare = followOnce(log, hare); + + if (hare == tortoise) + return true; + } + } + } +} + void TxnLog::commit() { LUAU_ASSERT(!radioactive); @@ -195,7 +225,17 @@ void TxnLog::commit() for (auto& [ty, rep] : typeVarChanges) { if (!rep->dead) - asMutable(ty)->reassign(rep.get()->pending); + { + const TypeId unfollowed = &rep.get()->pending; + + if (FFlag::LuauOccursCheckInCommit) + { + if (!occurs(*this, unfollowed, ty)) + asMutable(ty)->reassign(*unfollowed); + } + else + asMutable(ty)->reassign(*unfollowed); + } } for (auto& [tp, rep] : typePackChanges) @@ -474,16 +514,4 @@ TypePackId TxnLog::follow(TypePackId tp) const ); } -std::pair, std::vector> TxnLog::getChanges() const -{ - std::pair, std::vector> result; - - for (const auto& [typeId, _newState] : typeVarChanges) - result.first.push_back(typeId); - for (const auto& [typePackId, _newState] : typePackChanges) - result.second.push_back(typePackId); - - return result; -} - } // namespace Luau diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 048fdba4..f4a1d3c1 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -30,7 +30,6 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) @@ -40,6 +39,8 @@ LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAGVARIABLE(LuauIceLess) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAGVARIABLE(LuauAllowMixedTables) namespace Luau { @@ -162,7 +163,7 @@ struct TypeFunctionFinder : TypeOnceVisitor DenseHashSet mentionedFunctionPacks{nullptr}; TypeFunctionFinder() - : TypeOnceVisitor("TypeFunctionFinder") + : TypeOnceVisitor("TypeFunctionFinder", FFlag::LuauExplicitSkipBoundTypes) { } @@ -187,7 +188,7 @@ struct InternalTypeFunctionFinder : TypeOnceVisitor DenseHashSet mentionedFunctionPacks{nullptr}; explicit InternalTypeFunctionFinder(std::vector& declStack) - : TypeOnceVisitor("InternalTypeFunctionFinder") + : TypeOnceVisitor("InternalTypeFunctionFinder", FFlag::LuauExplicitSkipBoundTypes) { TypeFunctionFinder f; for (TypeId fn : declStack) @@ -1545,80 +1546,53 @@ void TypeChecker2::visitCall(AstExprCall* call) argExprs.push_back(indexExpr->expr); } - if (FFlag::LuauTableLiteralSubtypeCheckFunctionCalls) + // FIXME: Similar to bidirectional inference prior, this does not support + // overloaded functions nor generic types (yet). + if (auto fty = get(fnTy); fty && fty->generics.empty() && fty->genericPacks.empty() && call->args.size > 0) { - // FIXME: Similar to bidirectional inference prior, this does not support - // overloaded functions nor generic types (yet). - if (auto fty = get(fnTy); fty && fty->generics.empty() && fty->genericPacks.empty() && call->args.size > 0) - { - size_t selfOffset = call->self ? 1 : 0; + size_t selfOffset = call->self ? 1 : 0; - auto [paramsHead, _] = extendTypePack(module->internalTypes, builtinTypes, fty->argTypes, call->args.size + selfOffset); + auto [paramsHead, _] = extendTypePack(module->internalTypes, builtinTypes, fty->argTypes, call->args.size + selfOffset); - for (size_t idx = 0; idx < call->args.size - 1; ++idx) + for (size_t idx = 0; idx < call->args.size - 1; ++idx) + { + auto argExpr = call->args.data[idx]; + auto argExprType = lookupType(argExpr); + argExprs.push_back(argExpr); + if (idx + selfOffset >= paramsHead.size() || isErrorSuppressing(argExpr->location, argExprType)) { - auto argExpr = call->args.data[idx]; - auto argExprType = lookupType(argExpr); - argExprs.push_back(argExpr); - if (idx + selfOffset >= paramsHead.size() || isErrorSuppressing(argExpr->location, argExprType)) - { - args.head.push_back(argExprType); - continue; - } - testLiteralOrAstTypeIsSubtype(argExpr, paramsHead[idx + selfOffset]); - args.head.push_back(paramsHead[idx + selfOffset]); + args.head.push_back(argExprType); + continue; } + testLiteralOrAstTypeIsSubtype(argExpr, paramsHead[idx + selfOffset]); + args.head.push_back(paramsHead[idx + selfOffset]); + } - auto lastExpr = call->args.data[call->args.size - 1]; - argExprs.push_back(lastExpr); + auto lastExpr = call->args.data[call->args.size - 1]; + argExprs.push_back(lastExpr); - if (auto argTail = module->astTypePacks.find(lastExpr)) - { - auto [lastExprHead, lastExprTail] = flatten(*argTail); - args.head.insert(args.head.end(), lastExprHead.begin(), lastExprHead.end()); - args.tail = lastExprTail; - } - else if (paramsHead.size() >= call->args.size + selfOffset) + if (auto argTail = module->astTypePacks.find(lastExpr)) + { + auto [lastExprHead, lastExprTail] = flatten(*argTail); + args.head.insert(args.head.end(), lastExprHead.begin(), lastExprHead.end()); + args.tail = lastExprTail; + } + else if (paramsHead.size() >= call->args.size + selfOffset) + { + auto lastType = paramsHead[call->args.size - 1 + selfOffset]; + auto lastExprType = lookupType(lastExpr); + if (isErrorSuppressing(lastExpr->location, lastExprType)) { - auto lastType = paramsHead[call->args.size - 1 + selfOffset]; - auto lastExprType = lookupType(lastExpr); - if (isErrorSuppressing(lastExpr->location, lastExprType)) - { - args.head.push_back(lastExprType); - } - else - { - testLiteralOrAstTypeIsSubtype(lastExpr, lastType); - args.head.push_back(lastType); - } + args.head.push_back(lastExprType); } else - args.tail = builtinTypes->anyTypePack; - } - else - { - for (size_t i = 0; i < call->args.size; ++i) { - AstExpr* arg = call->args.data[i]; - argExprs.push_back(arg); - TypeId* argTy = module->astTypes.find(arg); - if (argTy) - args.head.push_back(*argTy); - else if (i == call->args.size - 1) - { - if (auto argTail = module->astTypePacks.find(arg)) - { - auto [head, tail] = flatten(*argTail); - args.head.insert(args.head.end(), head.begin(), head.end()); - args.tail = tail; - } - else - args.tail = builtinTypes->anyTypePack; - } - else - args.head.push_back(builtinTypes->anyType); + testLiteralOrAstTypeIsSubtype(lastExpr, lastType); + args.head.push_back(lastType); } } + else + args.tail = builtinTypes->anyTypePack; } else { @@ -3157,8 +3131,17 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT if (expectedTableType->indexer) { NotNull scope{findInnermostScope(expr->location)}; - auto result = subtyping->isSubtype(expectedTableType->indexer->indexType, builtinTypes->numberType, scope); - isArrayLike = result.isSubtype; + + 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; + } } bool isSubtype = true; diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index c0f0e353..728b5c60 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -35,6 +35,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { @@ -53,7 +54,7 @@ struct InstanceCollector : TypeOnceVisitor InstanceCollector() - : TypeOnceVisitor("InstanceCollector") + : TypeOnceVisitor("InstanceCollector", FFlag::LuauExplicitSkipBoundTypes) { } @@ -144,7 +145,7 @@ struct UnscopedGenericFinder : TypeOnceVisitor bool foundUnscoped = false; UnscopedGenericFinder() - : TypeOnceVisitor("UnscopedGenericFinder") + : TypeOnceVisitor("UnscopedGenericFinder", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/TypeFunctionReductionGuesser.cpp b/Analysis/src/TypeFunctionReductionGuesser.cpp index 12fcaa16..e419ab8a 100644 --- a/Analysis/src/TypeFunctionReductionGuesser.cpp +++ b/Analysis/src/TypeFunctionReductionGuesser.cpp @@ -15,6 +15,8 @@ LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) + namespace Luau { struct InstanceCollector2 : TypeOnceVisitor @@ -25,7 +27,7 @@ struct InstanceCollector2 : TypeOnceVisitor DenseHashSet instanceArguments{nullptr}; InstanceCollector2() - : TypeOnceVisitor("InstanceCollector2") + : TypeOnceVisitor("InstanceCollector2", FFlag::LuauExplicitSkipBoundTypes) { } diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 0a6c3340..53233dcb 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -8,6 +8,7 @@ #include "Luau/ToString.h" #include "Luau/Type.h" #include "Luau/TypeInfer.h" +#include "Luau/TypePack.h" #include @@ -15,6 +16,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAGVARIABLE(LuauVariadicAnyPackShouldBeErrorSuppressing) namespace Luau { @@ -502,6 +504,17 @@ ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypeId ty) ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypePackId tp) { + // 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 (get(follow(tpId->ty))) + return ErrorSuppression::Suppress; + } + } + auto [tys, tail] = flatten(tp); // check the head, one type at a time diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index e632b03a..208c13e1 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -33,7 +33,7 @@ struct PromoteTypeLevels final : TypeOnceVisitor TypeLevel minLevel; PromoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLevel) - : TypeOnceVisitor("PromoteTypeLevels") + : TypeOnceVisitor("PromoteTypeLevels", /* skipBoundTypes */ false) , log(log) , typeArena(typeArena) , minLevel(minLevel) @@ -146,7 +146,7 @@ void promoteTypeLevels(TxnLog& log, const TypeArena* typeArena, TypeLevel minLev struct SkipCacheForType final : TypeOnceVisitor { SkipCacheForType(const DenseHashMap& skipCacheForType, const TypeArena* typeArena) - : TypeOnceVisitor("SkipCacheForType") + : TypeOnceVisitor("SkipCacheForType", /* skipBoundTypes */ false) , skipCacheForType(skipCacheForType) , typeArena(typeArena) { @@ -508,49 +508,6 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool return; } - if (hideousFixMeGenericsAreActuallyFree) - { - auto superGeneric = log.getMutable(superTy); - auto subGeneric = log.getMutable(subTy); - - if (superGeneric && subGeneric && subsumes(superGeneric, subGeneric)) - { - if (!occursCheck(subTy, superTy, /* reversed = */ false)) - log.replace(subTy, BoundType(superTy)); - - return; - } - else if (superGeneric && subGeneric) - { - if (!occursCheck(superTy, subTy, /* reversed = */ true)) - log.replace(superTy, BoundType(subTy)); - - return; - } - else if (superGeneric) - { - if (!occursCheck(superTy, subTy, /* reversed = */ true)) - { - Widen widen{types, builtinTypes}; - log.replace(superTy, BoundType(widen(subTy))); - } - - return; - } - else if (subGeneric) - { - // Normally, if the subtype is free, it should not be bound to any, unknown, or error types. - // But for bug compatibility, we'll only apply this rule to unknown. Doing this will silence cascading type errors. - if (log.get(superTy)) - return; - - if (!occursCheck(subTy, superTy, /* reversed = */ false)) - log.replace(subTy, BoundType(superTy)); - - return; - } - } - if (log.get(superTy)) return tryUnifyWithAny(subTy, builtinTypes->anyType); @@ -1510,21 +1467,6 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal log.replace(subTp, Unifiable::Bound(superTp)); } } - else if (hideousFixMeGenericsAreActuallyFree && log.getMutable(superTp)) - { - if (!occursCheck(superTp, subTp, /* reversed = */ true)) - { - Widen widen{types, builtinTypes}; - log.replace(superTp, Unifiable::Bound(widen(subTp))); - } - } - else if (hideousFixMeGenericsAreActuallyFree && log.getMutable(subTp)) - { - if (!occursCheck(subTp, superTp, /* reversed = */ false)) - { - log.replace(subTp, Unifiable::Bound(superTp)); - } - } else if (log.getMutable(superTp)) tryUnifyWithAny(subTp, superTp); else if (log.getMutable(subTp)) @@ -2557,12 +2499,7 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever tryUnify_(vtp->ty, variadicTy); } else if (get(tail)) - { - if (!hideousFixMeGenericsAreActuallyFree) - reportError(location, GenericError{"Cannot unify variadic and generic packs"}); - else - log.replace(tail, BoundTypePack{superTp}); - } + reportError(location, GenericError{"Cannot unify variadic and generic packs"}); else if (get(tail)) { // Nothing to do here. @@ -2755,13 +2692,13 @@ bool Unifier::occursCheck(DenseHashSet& seen, TypeId needle, TypeId hays if (log.getMutable(needle)) return false; - if (!log.getMutable(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is(needle))) + if (!log.getMutable(needle)) ice("Expected needle to be free"); if (needle == haystack) return true; - if (log.getMutable(haystack) || (hideousFixMeGenericsAreActuallyFree && log.is(haystack))) + if (log.getMutable(haystack)) return false; else if (auto a = log.getMutable(haystack)) { @@ -2805,7 +2742,7 @@ bool Unifier::occursCheck(DenseHashSet& seen, TypePackId needle, Typ if (log.getMutable(needle)) return false; - if (!log.getMutable(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is(needle))) + if (!log.getMutable(needle)) ice("Expected needle pack to be free"); RecursionLimiter _ra("Unifier::occursCheck", &sharedState.counters.recursionCount, sharedState.counters.recursionLimit); diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index ad1fa896..917217a4 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -339,9 +339,28 @@ class AstExprConstantString : public AstExpr enum QuoteStyle { + // A string created using double quotes or an interpolated string, + // as in: + // + // "foo", `My name is {protagonist}! / And I'm {antagonist}!` + // QuotedSimple, + // A string created using single quotes, as in: + // + // 'bar' + // + QuotedSingle, + // A string created using `[[ ... ]]` as in: + // + // [[ Gee, this sure is a long string. + // it even has a new line in it! ]] + // QuotedRaw, - Unquoted + // A "string" in the context of a table literal, as in: + // + // { foo = 42 } -- `foo` here is a "constant string" + // + Unquoted, }; AstExprConstantString(const Location& location, const AstArray& value, QuoteStyle quoteStyle); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 8c7f7d00..8ace61b5 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) LUAU_FASTFLAGVARIABLE(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax) +LUAU_FASTFLAGVARIABLE(DebugLuauStringSingletonBasedOnQuotes) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false; @@ -3847,17 +3848,39 @@ AstExpr* Parser::parseString() Location location = lexer.current().location; AstExprConstantString::QuoteStyle style; - switch (lexer.current().type) + if (FFlag::DebugLuauStringSingletonBasedOnQuotes) { - case Lexeme::QuotedString: - case Lexeme::InterpStringSimple: - style = AstExprConstantString::QuotedSimple; - break; - case Lexeme::RawString: - style = AstExprConstantString::QuotedRaw; - break; - default: - LUAU_ASSERT(false && "Invalid string type"); + switch (lexer.current().type) + { + case Lexeme::InterpStringSimple: + style = AstExprConstantString::QuotedSimple; + break; + case Lexeme::RawString: + style = AstExprConstantString::QuotedRaw; + break; + case Lexeme::QuotedString: + style = lexer.current().getQuoteStyle() == Lexeme::QuoteStyle::Single + ? AstExprConstantString::QuotedSingle + : AstExprConstantString::QuotedSimple; + break; + default: + LUAU_ASSERT(false && "Invalid string type"); + } + } + else + { + switch (lexer.current().type) + { + case Lexeme::QuotedString: + case Lexeme::InterpStringSimple: + style = AstExprConstantString::QuotedSimple; + break; + case Lexeme::RawString: + style = AstExprConstantString::QuotedRaw; + break; + default: + LUAU_ASSERT(false && "Invalid string type"); + } } CstExprConstantString::QuoteStyle fullStyle; diff --git a/CodeGen/include/Luau/ConditionA64.h b/CodeGen/include/Luau/ConditionA64.h index 9e7d1fa9..b0bd3fe6 100644 --- a/CodeGen/include/Luau/ConditionA64.h +++ b/CodeGen/include/Luau/ConditionA64.h @@ -52,6 +52,35 @@ enum class ConditionA64 Count }; +// Returns a condition that for 'a op b' will result in 'b op a' +// Only a subset of conditions is allowed +inline ConditionA64 getInverseCondition(ConditionA64 cond) +{ + switch (cond) + { + case ConditionA64::Equal: + return ConditionA64::Equal; + case ConditionA64::NotEqual: + return ConditionA64::NotEqual; + case ConditionA64::UnsignedGreater: + return ConditionA64::CarryClear; + case ConditionA64::UnsignedLessEqual: + return ConditionA64::CarrySet; + case ConditionA64::GreaterEqual: + return ConditionA64::LessEqual; + case ConditionA64::Less: + return ConditionA64::Greater; + case ConditionA64::Greater: + return ConditionA64::Less; + case ConditionA64::LessEqual: + return ConditionA64::GreaterEqual; + default: + CODEGEN_ASSERT(!"invalid ConditionA64 value for getInverseCondition"); + } + + return ConditionA64::Count; +} + } // namespace A64 } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/include/Luau/ConditionX64.h b/CodeGen/include/Luau/ConditionX64.h index 39ee3f02..9c1d48bb 100644 --- a/CodeGen/include/Luau/ConditionX64.h +++ b/CodeGen/include/Luau/ConditionX64.h @@ -45,7 +45,8 @@ enum class ConditionX64 : uint8_t Count }; -inline ConditionX64 getReverseCondition(ConditionX64 cond) +// Returns a condition that for 'a op b' will result in '!(a op b)' +inline ConditionX64 getNegatedCondition(ConditionX64 cond) { switch (cond) { @@ -108,5 +109,54 @@ inline ConditionX64 getReverseCondition(ConditionX64 cond) return ConditionX64::Count; } +// Returns a condition that for 'a op b' will result in 'b op a' +// Only a subset of conditions is allowed +inline ConditionX64 getInverseCondition(ConditionX64 cond) +{ + switch (cond) + { + case ConditionX64::Below: + return ConditionX64::Above; + case ConditionX64::BelowEqual: + return ConditionX64::AboveEqual; + case ConditionX64::Above: + return ConditionX64::Above; + case ConditionX64::AboveEqual: + return ConditionX64::BelowEqual; + case ConditionX64::Equal: + return ConditionX64::Equal; + case ConditionX64::Less: + return ConditionX64::Greater; + case ConditionX64::LessEqual: + return ConditionX64::GreaterEqual; + case ConditionX64::Greater: + return ConditionX64::Less; + case ConditionX64::GreaterEqual: + return ConditionX64::LessEqual; + case ConditionX64::NotBelow: + return ConditionX64::NotAbove; + case ConditionX64::NotBelowEqual: + return ConditionX64::NotAboveEqual; + case ConditionX64::NotAbove: + return ConditionX64::NotBelow; + case ConditionX64::NotAboveEqual: + return ConditionX64::NotBelowEqual; + case ConditionX64::NotEqual: + return ConditionX64::NotEqual; + case ConditionX64::NotLess: + return ConditionX64::NotGreater; + case ConditionX64::NotLessEqual: + return ConditionX64::NotGreaterEqual; + case ConditionX64::NotGreater: + return ConditionX64::NotLess; + case ConditionX64::NotGreaterEqual: + return ConditionX64::NotLessEqual; + default: + CODEGEN_ASSERT(!"invalid ConditionX64 value for getInverseCondition"); + } + + return ConditionX64::Count; +} + } // namespace CodeGen } // namespace Luau diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 2f02f994..4919f3b9 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -217,6 +217,11 @@ enum class IrCmd : uint8_t // C: condition CMP_ANY, + // Perform a comparison of two integer numbers. Result is an integer register containing 0 or 1 + // A, B: int + // C: condition + CMP_INT, + // Unconditional jump // A: block/vmexit/undef JUMP, diff --git a/CodeGen/include/Luau/IrUtils.h b/CodeGen/include/Luau/IrUtils.h index eb1cf079..520aec45 100644 --- a/CodeGen/include/Luau/IrUtils.h +++ b/CodeGen/include/Luau/IrUtils.h @@ -112,6 +112,7 @@ inline bool hasResult(IrCmd cmd) case IrCmd::UNM_VEC: case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: + case IrCmd::CMP_INT: case IrCmd::TABLE_LEN: case IrCmd::TABLE_SETNUM: case IrCmd::STRING_LEN: diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index ddccf9ca..e0ed512e 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -548,6 +548,12 @@ static void applyBuiltinCall(LuauBuiltinFunction bfid, BytecodeTypes& types) types.b = LBC_TYPE_VECTOR; types.c = LBC_TYPE_VECTOR; // We can mark optional arguments break; + case LBF_VECTOR_LERP: + types.result = LBC_TYPE_VECTOR; + types.a = LBC_TYPE_VECTOR; + types.b = LBC_TYPE_VECTOR; + types.c = LBC_TYPE_NUMBER; + break; case LBF_MATH_LERP: types.result = LBC_TYPE_NUMBER; types.a = LBC_TYPE_NUMBER; diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 1427e30e..4544c218 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -185,6 +185,8 @@ const char* getCmdName(IrCmd cmd) return "NOT_ANY"; case IrCmd::CMP_ANY: return "CMP_ANY"; + case IrCmd::CMP_INT: + return "CMP_INT"; case IrCmd::JUMP: return "JUMP"; case IrCmd::JUMP_IF_TRUTHY: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 48c2b9a9..8941d5e6 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -12,6 +12,8 @@ #include "lstate.h" #include "lgc.h" +LUAU_FASTFLAG(LuauCodeGenDirectBtest) + namespace Luau { namespace CodeGen @@ -798,6 +800,30 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } break; } + case IrCmd::CMP_INT: + { + CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); + + inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a, inst.b}); + + IrCondition cond = conditionOp(inst.c); + + if (inst.a.kind == IrOpKind::Constant) + { + build.cmp(regOp(inst.b), intOp(inst.a)); + build.cset(inst.regA64, getInverseCondition(getConditionInt(cond))); + } + else if (inst.a.kind == IrOpKind::Inst) + { + build.cmp(regOp(inst.a), intOp(inst.b)); + build.cset(inst.regA64, getConditionInt(cond)); + } + else + { + CODEGEN_ASSERT(!"Unsupported instruction form"); + } + break; + } case IrCmd::CMP_ANY: { IrCondition cond = conditionOp(inst.c); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 2c4d2837..257419cf 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -16,6 +16,8 @@ #include "lstate.h" #include "lgc.h" +LUAU_FASTFLAG(LuauCodeGenDirectBtest) + namespace Luau { namespace CodeGen @@ -759,6 +761,34 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.setLabel(exit); break; } + 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); + + // 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); + + if (inst.a.kind == IrOpKind::Constant) + { + build.cmp(regOp(inst.b), intOp(inst.a)); + build.setcc(getInverseCondition(getConditionInt(cond)), byteReg(inst.regX64)); + } + else if (inst.a.kind == IrOpKind::Inst) + { + build.cmp(regOp(inst.a), intOp(inst.b)); + build.setcc(getConditionInt(cond), byteReg(inst.regX64)); + } + else + { + CODEGEN_ASSERT(!"Unsupported instruction form"); + } + break; + } case IrCmd::CMP_ANY: { IrCondition cond = conditionOp(inst.c); @@ -2246,7 +2276,7 @@ void IrLoweringX64::jumpOrAbortOnUndef(ConditionX64 cond, IrOp target, const IrB } else { - build.jcc(getReverseCondition(cond), label); + build.jcc(getNegatedCondition(cond), label); build.ud2(); build.setLabel(label); } diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index 04f24378..1a5b8599 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -8,6 +8,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCodeGenDirectBtest) + // TODO: when nresults is less than our actual result count, we can skip computing/writing unused results static const int kMinMaxUnrolledParams = 5; @@ -411,21 +413,30 @@ static BuiltinImplResult translateBuiltinBit32BinaryOp( if (btest) { - 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); + 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(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(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)); + build.beginBlock(exit); + build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN)); + } } else { diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index f77ceb78..c8e1929e 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -16,6 +16,8 @@ #include #include +LUAU_FASTFLAG(LuauCodeGenDirectBtest) + namespace Luau { namespace CodeGen @@ -193,6 +195,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) return IrValueKind::Double; case IrCmd::NOT_ANY: case IrCmd::CMP_ANY: + case IrCmd::CMP_INT: return IrValueKind::Int; case IrCmd::JUMP: case IrCmd::JUMP_IF_TRUTHY: @@ -790,6 +793,17 @@ void foldConstants(IrBuilder& build, IrFunction& function, IrBlock& block, uint3 substitute(function, inst, build.constInt(function.intOp(inst.b) == 1 ? 0 : 1)); } 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))) + substitute(function, inst, build.constInt(1)); + else + substitute(function, inst, build.constInt(0)); + } + 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 2104e288..5e05eb89 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -21,6 +21,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenReuseUdataTagLimit, 64) LUAU_FASTINTVARIABLE(LuauCodeGenLiveSlotReuseLimit, 8) LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks) +LUAU_FASTFLAG(LuauCodeGenDirectBtest) namespace Luau { @@ -607,6 +608,7 @@ static void handleBuiltinEffects(ConstPropState& state, LuauBuiltinFunction bfid case LBF_VECTOR_CLAMP: case LBF_VECTOR_MIN: case LBF_VECTOR_MAX: + case LBF_VECTOR_LERP: case LBF_MATH_LERP: break; case LBF_TABLE_INSERT: @@ -1326,6 +1328,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::NOT_ANY: state.substituteOrRecord(inst, index); break; + case IrCmd::CMP_INT: + CODEGEN_ASSERT(FFlag::LuauCodeGenDirectBtest); + break; case IrCmd::CMP_ANY: state.invalidateUserCall(); break; diff --git a/Common/include/Luau/Bytecode.h b/Common/include/Luau/Bytecode.h index 3716d1f1..7d90d475 100644 --- a/Common/include/Luau/Bytecode.h +++ b/Common/include/Luau/Bytecode.h @@ -618,6 +618,8 @@ enum LuauBuiltinFunction // math.lerp LBF_MATH_LERP, + + LBF_VECTOR_LERP, }; // Capture type, used in LOP_CAPTURE diff --git a/Compiler/src/Builtins.cpp b/Compiler/src/Builtins.cpp index bc342bd3..c6ae9b71 100644 --- a/Compiler/src/Builtins.cpp +++ b/Compiler/src/Builtins.cpp @@ -7,6 +7,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCompileVectorLerp) + namespace Luau { namespace Compile @@ -251,6 +253,8 @@ static int getBuiltinFunctionId(const Builtin& builtin, const CompileOptions& op return LBF_VECTOR_MIN; if (builtin.method == "max") return LBF_VECTOR_MAX; + if (FFlag::LuauCompileVectorLerp && builtin.method == "lerp") + return LBF_VECTOR_LERP; } if (options.vectorCtor) @@ -552,6 +556,8 @@ BuiltinInfo getBuiltinInfo(int bfid) case LBF_VECTOR_MIN: case LBF_VECTOR_MAX: return {-1, 1}; // variadic + case LBF_VECTOR_LERP: + return {3, 1, BuiltinInfo::Flag_NoneSafe}; case LBF_MATH_LERP: return {3, 1, BuiltinInfo::Flag_NoneSafe}; diff --git a/Compiler/src/Types.cpp b/Compiler/src/Types.cpp index e984370e..4fcd984b 100644 --- a/Compiler/src/Types.cpp +++ b/Compiler/src/Types.cpp @@ -771,6 +771,7 @@ struct TypeMapVisitor : AstVisitor case LBF_VECTOR_CLAMP: case LBF_VECTOR_MIN: case LBF_VECTOR_MAX: + case LBF_VECTOR_LERP: recordResolvedType(node, &builtinTypes.vectorType); break; } diff --git a/VM/src/lbuiltins.cpp b/VM/src/lbuiltins.cpp index 3fc687e1..4c1a3c37 100644 --- a/VM/src/lbuiltins.cpp +++ b/VM/src/lbuiltins.cpp @@ -25,6 +25,8 @@ #endif #endif +LUAU_FASTFLAG(LuauVectorLerp) + // luauF functions implement FASTCALL instruction that performs a direct execution of some builtin functions from the VM // The rule of thumb is that FASTCALL functions can not call user code, yield, fail, or reallocate stack. // If types of the arguments mismatch, luauF_* needs to return -1 and the execution will fall back to the usual call path @@ -1701,6 +1703,26 @@ static int luauF_vectormax(lua_State* L, StkId res, TValue* arg0, int nresults, return -1; } +static int luauF_vectorlerp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) +{ + if (FFlag::LuauVectorLerp && nparams >= 3 && nresults <= 1 && ttisvector(arg0) && ttisvector(args) && ttisnumber(args + 1)) + { + const float* a = vvalue(arg0); + const float* b = vvalue(args); + const float t = static_cast(nvalue(args + 1)); + +#if LUA_VECTOR_SIZE == 4 + setvvalue(res, luai_lerpf(a[0], b[0], t), luai_lerpf(a[1], b[1], t), luai_lerpf(a[2], b[2], t), luai_lerpf(a[3], b[3], t)); +#else + setvvalue(res, luai_lerpf(a[0], b[0], t), luai_lerpf(a[1], b[1], t), luai_lerpf(a[2], b[2], t), 0.0f); +#endif + + return 1; + } + + return -1; +} + static int luauF_lerp(lua_State* L, StkId res, TValue* arg0, int nresults, StkId args, int nparams) { if (nparams >= 3 && nresults <= 1 && ttisnumber(arg0) && ttisnumber(args) && ttisnumber(args + 1)) @@ -1915,6 +1937,8 @@ const luau_FastFunction luauF_table[256] = { luauF_lerp, + luauF_vectorlerp, + // When adding builtins, add them above this line; what follows is 64 "dummy" entries with luauF_missing fallback. // This is important so that older versions of the runtime that don't support newer builtins automatically fall back via luauF_missing. // Given the builtin addition velocity this should always provide a larger compatibility window than bytecode versions suggest. diff --git a/VM/src/lnumutils.h b/VM/src/lnumutils.h index de56bb09..53c563e7 100644 --- a/VM/src/lnumutils.h +++ b/VM/src/lnumutils.h @@ -58,6 +58,11 @@ inline double luai_numidiv(double a, double b) } LUAU_FASTMATH_END +inline float luai_lerpf(float a, float b, float t) +{ + return (t == 1.0) ? b : a + (b - a) * t; +} + #define luai_num2int(i, d) ((i) = (int)(d)) // On MSVC in 32-bit, double to unsigned cast compiles into a call to __dtoui3, so we invoke x87->int64 conversion path manually diff --git a/VM/src/lveclib.cpp b/VM/src/lveclib.cpp index f24dc8b6..13cfb847 100644 --- a/VM/src/lveclib.cpp +++ b/VM/src/lveclib.cpp @@ -6,6 +6,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauVectorLerp) + static int vector_create(lua_State* L) { // checking argument count to avoid accepting 'nil' as a valid value @@ -283,6 +285,21 @@ static int vector_index(lua_State* L) luaL_error(L, "attempt to index vector with '%s'", name); } +static int vector_lerp(lua_State* L) +{ + const float* a = luaL_checkvector(L, 1); + const float* b = luaL_checkvector(L, 2); + const float t = static_cast(luaL_checknumber(L, 3)); + +#if LUA_VECTOR_SIZE == 4 + lua_pushvector(L, luai_lerpf(a[0], b[0], t), luai_lerpf(a[1], b[1], t), luai_lerpf(a[2], b[2], t), luai_lerpf(a[3], b[3], t)); +#else + lua_pushvector(L, luai_lerpf(a[0], b[0], t), luai_lerpf(a[1], b[1], t), luai_lerpf(a[2], b[2], t)); +#endif + + return 1; +} + static const luaL_Reg vectorlib[] = { {"create", vector_create}, {"magnitude", vector_magnitude}, @@ -326,6 +343,13 @@ int luaopen_vector(lua_State* L) { luaL_register(L, LUA_VECLIBNAME, vectorlib); + if (FFlag::LuauVectorLerp) + { + // To unflag put {"lerp", vector_lerp} in the `vectorlib` table + lua_pushcfunction(L, vector_lerp, "lerp"); + lua_setfield(L, -2, "lerp"); + } + #if LUA_VECTOR_SIZE == 4 lua_pushvector(L, 0.0f, 0.0f, 0.0f, 0.0f); lua_setfield(L, -2, "zero"); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 7f628abe..ae028d44 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -21,7 +21,7 @@ LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) +LUAU_FASTFLAG(LuauIncludeBreakContinueStatements) using namespace Luau; @@ -4685,10 +4685,7 @@ TEST_CASE_FIXTURE(ACFixture, "bidirectional_autocomplete_in_function_call") TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; check(R"( type IAccount = { @@ -4722,4 +4719,163 @@ TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_via_bidirectional_self") CHECK_EQ(ac.entryMap.count("balance"), 1); } +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 + @2 + end + end)"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("break") > 0); + CHECK(ac.entryMap.count("continue") > 0); + + ac = autocomplete('2'); + + CHECK(ac.entryMap.count("break") > 0); + CHECK(ac.entryMap.count("continue") > 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_outside_loop") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(@1if true then + @2 + end)"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); + + ac = autocomplete('2'); + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +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 + end + end)"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_param") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(while @1 do + end)"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_incomplete_while") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check("while @1"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +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'); + + ac = autocomplete('1'); + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); + + ac = autocomplete('2'); + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_expr_func") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(while true do + local _ = function () + @1 + end + end)"); + + auto ac = autocomplete('1'); + + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_repeat") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(repeat + @1 + until foo())"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("break") > 0); + CHECK(ac.entryMap.count("continue") > 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_include_break_continue_in_nests") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(while ((function () + while true do + @1 + end + end)()) do + end)"); + + auto ac = autocomplete('1'); + + CHECK(ac.entryMap.count("break") > 0); + CHECK(ac.entryMap.count("continue") > 0); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_incomplete_loop") +{ + ScopedFastFlag sff{FFlag::LuauIncludeBreakContinueStatements, true}; + + check(R"(while foo() do + @1)"); + + auto ac = autocomplete('1'); + + // We'd like to include break/continue here but the incomplete loop ends immediately. + CHECK_EQ(ac.entryMap.count("break"), 0); + CHECK_EQ(ac.entryMap.count("continue"), 0); +} + TEST_SUITE_END(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f1a1c91b..c6adc450 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -36,9 +36,13 @@ void luau_callhook(lua_State* L, lua_Hook hook, void* userdata); LUAU_FASTFLAG(LuauHeapDumpStringSizeOverhead) LUAU_FASTFLAG(DebugLuauAbortingChecks) +LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_DYNAMIC_FASTFLAG(LuauErrorYield) LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck) +LUAU_FASTFLAG(LuauVectorLerp) +LUAU_FASTFLAG(LuauCompileVectorLerp) +LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) static lua_CompileOptions defaultOptions() { @@ -1130,6 +1134,8 @@ TEST_CASE("Vector") TEST_CASE("VectorLibrary") { + ScopedFastFlag _[]{{FFlag::LuauCompileVectorLerp, true}, {FFlag::LuauTypeCheckerVectorLerp, true}, {FFlag::LuauVectorLerp, true}}; + lua_CompileOptions copts = defaultOptions(); SUBCASE("O0") @@ -3112,6 +3118,8 @@ TEST_CASE("SafeEnv") TEST_CASE("Native") { + ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, 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/Fixture.h b/tests/Fixture.h index 4653e260..4bd2b891 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -29,7 +29,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) -LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature) LUAU_FASTFLAG(LuauTidyTypeUtils) LUAU_FASTFLAG(DebugLuauAlwaysShowConstraintSolvingIncomplete); @@ -153,7 +152,6 @@ struct Fixture // Most often those are changes related to builtin type definitions. // In that case, flag can be forced to 'true' using the example below: // ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true}; - ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true}; ScopedFastFlag sff_TypeUtilTidy{FFlag::LuauTidyTypeUtils, true}; diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index dbee4f81..0f76d5a2 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -31,7 +31,6 @@ LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule) LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAG(LuauForInProvidesRecommendations) @@ -3948,8 +3947,6 @@ require(script.A). TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_types_provide_rich_autocomplete") { - ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true}; - const std::string source = R"( type Service = { Start: (self: Service) -> (), @@ -3990,7 +3987,6 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatable_setting_new_solver") { - ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true}; const std::string source = R"( type IAccount = { __index: IAccount, @@ -4060,8 +4056,6 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatabl TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_colon_good_recommendations") { - ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true}; - const std::string source = R"( type Service = { Start: (self: Service) -> (), diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 647d963a..6dc69cb4 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -19,6 +19,9 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) + +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) TEST_SUITE_BEGIN("Generalization"); @@ -404,16 +407,27 @@ TEST_CASE_FIXTURE(Fixture, "generics_dont_leak_into_callback") TEST_CASE_FIXTURE(Fixture, "generics_dont_leak_into_callback_2") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} + }; - // FIXME: CLI-156389: this is clearly wrong, but also predates this PR. - LUAU_REQUIRE_NO_ERRORS(check(R"( - local func: (T, (T) -> ()) -> () = nil :: any - local foobar: (number) -> () = nil :: any - func({}, function(obj) - foobar(obj) - end) - )")); + CheckResult result = check(R"( +local func: (T, (T) -> ()) -> () = nil :: any +local foobar: (number) -> () = nil :: any +func({}, function(obj) + foobar(obj) +end) + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + const GenericBoundsMismatch* gbm = get(result.errors[0]); + REQUIRE_MESSAGE(gbm, "Expected GenericBoundsMismatch but got: " << toString(result.errors[0])); + CHECK_EQ(gbm->genericName, "T"); + CHECK_EQ(gbm->lowerBounds.size(), 1); + CHECK_EQ(toString(gbm->lowerBounds[0]), "{ }"); + CHECK_EQ(gbm->upperBounds.size(), 1); + CHECK_EQ(toString(gbm->upperBounds[0]), "number"); + CHECK_EQ(result.errors[0].location, Location{Position{3, 0}, Position{3, 4}}); } TEST_CASE_FIXTURE(Fixture, "generic_argument_with_singleton_oss_1808") diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 59e56c32..25df42aa 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -17,6 +17,7 @@ #include LUAU_FASTFLAG(LuauCodeGenSimplifyImport2) +LUAU_FASTFLAG(LuauCodeGenDirectBtest) static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant) { @@ -2205,4 +2206,35 @@ end ); } +TEST_CASE("Bit32BtestDirect") +{ + ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, true}; + + CHECK_EQ( + "\n" + getCodegenAssembly(R"( +local function foo(a: number) + return bit32.btest(a, 0x1f) +end +)"), + R"( +; function foo($arg0) line 2 +bb_0: + CHECK_TAG R0, tnumber, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + CHECK_SAFE_ENV exit(2) + %7 = LOAD_DOUBLE R0 + %8 = NUM_TO_UINT %7 + %10 = BITAND_UINT %8, 31i + %11 = CMP_INT %10, 0i, not_eq + STORE_INT R1, %11 + STORE_TAG R1, tboolean + INTERRUPT 7u + RETURN R1, 1i +)" + ); +} + TEST_SUITE_END(); diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index 0809ead9..d5634fe5 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -24,7 +24,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauIceLess) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) @@ -302,7 +301,6 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5)) {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauResetConditionalContextProperly, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, // And this flag is the one that fixes it. {FFlag::LuauSimplifyAnyAndUnion, true}, @@ -387,7 +385,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauResetConditionalContextProperly, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, {FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true}, }; diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 8d2b0397..ff970505 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) using namespace Luau; @@ -1762,4 +1763,19 @@ TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes") REQUIRE(1 == result.assumedConstraints.size()); } +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 + +function bindAction(callback: ActionCallback) + local _ = function(...) + callback(...) + end +end +)"); + LUAU_REQUIRE_NO_ERRORS(res); +} + TEST_SUITE_END(); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index ee2d14bf..6fcea56b 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -15,6 +15,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) TEST_SUITE_BEGIN("ToString"); @@ -693,6 +695,11 @@ TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_union" TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_intersection") { + ScopedFastFlag sffs[] = { + {FFlag::LuauExplicitSkipBoundTypes, true}, + {FFlag::LuauReduceSetTypeStackPressure, true}, + }; + CheckResult result = check(R"( function f() return f end local a: ((number) -> ()) & typeof(f) @@ -700,10 +707,7 @@ TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_cyclic_function_type_in_inters LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK("(() -> t1) & ((number) -> ()) where t1 = () -> t1" == toString(requireType("a"))); - else - CHECK_EQ("((number) -> ()) & t1 where t1 = () -> t1", toString(requireType("a"))); + CHECK_EQ("((number) -> ()) & t1 where t1 = () -> t1", toString(requireType("a"))); } TEST_CASE_FIXTURE(Fixture, "self_recursive_instantiated_param") diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index dabde854..62f4b24b 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -12,6 +12,7 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauTypeFunNoScopeMapRef) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -2451,4 +2452,35 @@ end CHECK(toString(result.errors[0]) == R"(Redefinition of type 't0', previously defined at line 2)"); } +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 = ... +type function t255() end +if _ then + type function t1() end + type function t6(l0,...) end + type function t255() end + export type function t0() end +else + type function t1(...) end + type function t66(...) end + type function t255() end + if running then + export type function t255() end + type function t0(l0) end + end +end +type function t0(l0,...) end +export type function t66(...) + export type function t255() end +end + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 3cafe58b..93e2bc14 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -13,7 +13,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) -LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) TEST_SUITE_BEGIN("BuiltinTests"); @@ -1713,10 +1712,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "better_string_format_error_when_format_strin TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local function accept(t: { write foo: number }) diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index c1c62cbd..4b49a583 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(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauScopeMethodsAreSolverAgnostic) TEST_SUITE_BEGIN("TypeInferExternTypes"); @@ -443,8 +442,6 @@ b.X = 2 -- real Vector2.X is also read-only TEST_CASE_FIXTURE(ExternTypeFixture, "detailed_class_unification_error") { - ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}; - CheckResult result = check(R"( local function foo(v) return v.X :: number + string.len(v.Y) diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 9b5c78ca..40081a8c 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -27,11 +27,11 @@ LUAU_FASTFLAG(LuauCollapseShouldNotCrash) LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -1449,7 +1449,14 @@ local a = {{x=4}, {x=7}, {x=1}} table.sort(a, function(x, y) return x.x < y.x end) )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + { + // FIXME CLI-161355 + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + } + else + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "variadic_any_is_compatible_with_a_generic_TypePack") @@ -3184,10 +3191,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack_variadic") TEST_CASE_FIXTURE(Fixture, "table_annotated_explicit_self") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( type MyObject = { @@ -3210,11 +3214,7 @@ TEST_CASE_FIXTURE(Fixture, "table_annotated_explicit_self") TEST_CASE_FIXTURE(Fixture, "oss_1871") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; - + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( export type Test = { @@ -3233,10 +3233,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1871") TEST_CASE_FIXTURE(BuiltinsFixture, "io_manager_oop_ish") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( type IIOManager = { @@ -3268,10 +3265,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "io_manager_oop_ish") TEST_CASE_FIXTURE(BuiltinsFixture, "generic_function_statement") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( type Object = { diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index fafe9ee2..b13c8ee3 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -13,11 +13,11 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) using namespace Luau; @@ -65,7 +65,7 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_local_function2") CHECK_EQ(getBuiltins()->numberType, requireType("y")); } -TEST_CASE_FIXTURE(BuiltinsFixture, "unions_and_generics") +TEST_CASE_FIXTURE(Fixture, "unions_and_generics") { CheckResult result = check(R"( type foo = (T | {T}) -> T @@ -1220,12 +1220,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_table_method") TEST_CASE_FIXTURE(Fixture, "correctly_instantiate_polymorphic_member_functions") { - // Prior to `LuauPushFunctionTypesInFunctionStatement`, we _always_ forced - // a constraint when solving this block. - ScopedFastFlag sffs[] = { - {FFlag::DebugLuauAssertOnForcedConstraint, true}, - {FFlag::LuauPushFunctionTypesInFunctionStatement, true}, - }; + ScopedFastFlag sff{FFlag::DebugLuauAssertOnForcedConstraint, true}; CheckResult result = check(R"( local T = {} @@ -1490,7 +1485,10 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded" g12({x=1}, {x=2}, function(x, y) return {x=x.x + y.x} end) )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + LUAU_REQUIRE_ERROR_COUNT(2, result); // FIXME CLI-161355 + else + LUAU_REQUIRE_NO_ERRORS(result); } // Important FIXME CLI-161128: This test exposes some problems with overload @@ -1892,4 +1890,42 @@ end )"); } +TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error") +{ + ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches, true}}; + CheckResult res = check(R"( + local func: (T, (T) -> ()) -> () = nil :: any + local foobar: (number) -> () = nil :: any + func({}, foobar) + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, res); + if (FFlag::LuauSolverV2) + CHECK(get(res.errors[0])); + else + CHECK(get(res.errors[0])); +} + +TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error_1") +{ + ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches, true}}; + CheckResult res = check(R"( + --!strict + + function insert(arr: {T}, value: T) + return arr + end + + local a: {number} = {} + + local b = insert(a, "five") + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, res); + if (FFlag::LuauSolverV2) + CHECK(get(res.errors[0])); + else + CHECK(get(res.errors[0])); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 4049ecbf..980c6e3e 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(LuauReturnMappedGenericPacksFromSubtyping2) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -334,8 +333,6 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed") TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") { - ScopedFastFlag sff{FFlag::LuauPushFunctionTypesInFunctionStatement, true}; - CheckResult result = check(R"( type X = { x: (number) -> number } type Y = { y: (string) -> string } diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 5e5cc386..242e8934 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -21,6 +21,7 @@ LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) using namespace Luau; @@ -2350,7 +2351,14 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symb end )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauSubtypingReportGenericBoundMismatches) + { + // FIXME CLI-161355 + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors[0])); + } + else + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_t_after_return_references_all_reachable_points") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index ac6170ad..9abc8c80 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -24,17 +24,16 @@ LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) -LUAU_FASTFLAG(LuauTableLiteralSubtypeCheckFunctionCalls) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauAllowMixedTables) TEST_SUITE_BEGIN("TableTests"); @@ -491,8 +490,6 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_1") TEST_CASE_FIXTURE(BuiltinsFixture, "table_param_width_subtyping_2") { - ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}; - CheckResult result = check(R"( --!strict function foo(o) @@ -3879,10 +3876,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_call_is_unsound") { - ScopedFastFlag sff[]{ - {FFlag::LuauInstantiateInSubtyping, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauInstantiateInSubtyping, true}; CheckResult result = check(R"( --!strict @@ -4436,8 +4430,6 @@ TEST_CASE_FIXTURE(Fixture, "new_solver_supports_read_write_properties") TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression") { - ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, 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 @@ -5835,10 +5827,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1651") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local function take(_: { foo: string? }) end @@ -5849,10 +5838,7 @@ TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_incorrect") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( local function take(_: { foo: string?, bing: number }) end @@ -5870,10 +5856,7 @@ TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_incorrect") TEST_CASE_FIXTURE(Fixture, "narrow_table_literal_check_call_singleton") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( local function take(_: { foo: "foo" }) end @@ -5888,7 +5871,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeCheckFunctionCalls, true}, {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauTrackFreeInteriorTypePacks, true}, {FFlag::LuauResetConditionalContextProperly, true} @@ -6047,4 +6029,30 @@ TEST_CASE_FIXTURE(Fixture, "free_types_with_sealed_table_upper_bounds_can_still_ CHECK("({ read nope: () -> (...unknown) } & { x: number }) -> ()" == toString(requireType("foo"))); } +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", + "A", + "B", + } + )")); +} + +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", + "A", + "B", + } + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 3f4f3637..6a071b17 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -28,11 +28,11 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) -LUAU_FASTFLAG(LuauPushFunctionTypesInFunctionStatement) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) +LUAU_FASTFLAG(LuauOccursCheckInCommit) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauResetConditionalContextProperly) @@ -2513,36 +2513,6 @@ TEST_CASE_FIXTURE(Fixture, "if_then_else_two_errors") CHECK_EQ("number", toString(err2->givenType)); } -TEST_CASE_FIXTURE(Fixture, "simplify_constraint_can_force") -{ - ScopedFastFlag sff[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauForceSimplifyConstraint2, true}, - // NOTE: Feel free to clip this test when this flag is clipped. - {FFlag::LuauPushFunctionTypesInFunctionStatement, false}, - }; - - CheckResult result = check(R"( - --!strict - - local foo = nil - - bar(function() - if foo then - foo.baz() - end - end) - - foo = {} - - foo.a = { - foo.b - } - )"); - - LUAU_CHECK_NO_ERROR(result, ConstraintSolvingIncompleteError); -} - TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden") { ScopedFastFlag sffs[] = { @@ -2623,4 +2593,50 @@ do end } +TEST_CASE_FIXTURE(Fixture, "txnlog_checks_for_occurrence_before_self_binding_a_type") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, false}, + {FFlag::LuauOccursCheckInCommit, true} + }; + + + CheckResult result = check(R"( + local any = nil :: any + + function f1(x) + x:m() + local _ = x.A.p.a + end + + function f2(x) + local _ = x.d + end + + function f3(x) + local a = "" + a = x.d.p + local _ = undef[x.a] + end + + function f4(x) + f2(x) + if undef and x and x:m() then + any(x) + return + end + f3(x) + for _, v in any.x do + local a = x[v].p + end + a.b = x + if x.q ~= nil then + f1(x) -- things go bad here + end + end + + return f4 + )"); +} + TEST_SUITE_END(); diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index 7274ed48..bf3b7452 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -366,7 +366,7 @@ struct VisitCountTracker final : TypeOnceVisitor std::unordered_map tpVisits; VisitCountTracker() - : TypeOnceVisitor("VisitCountTracker") + : TypeOnceVisitor("VisitCountTracker", /* skipBoundTypes */ true) { } diff --git a/tests/conformance/native.luau b/tests/conformance/native.luau index dae8ddf9..94d67d04 100644 --- a/tests/conformance/native.luau +++ b/tests/conformance/native.luau @@ -237,6 +237,17 @@ end assert(pcall(fuzzfail23) == false) +local function fuzzfail24(...) + local _ = l4 + repeat + local function l0() + end + until (...)[_][_]:n16((_),_,l4,bit32.btest(0,_,_,_) / bit32.btest(0,_,_,_)(_,_,_,0,((_)),_)) + do end +end + +assert(pcall(fuzzfail24) == 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 dd5f2d1b..09da41b8 100644 --- a/tests/conformance/vector_library.luau +++ b/tests/conformance/vector_library.luau @@ -166,6 +166,16 @@ assert(vector.clamp(vector.create(1, 1, 1), vector.create(0, 1, 2), vector.creat assert(vector.clamp(vector.create(1, 1, 1), vector.create(-1, -1, -1), vector.create(0, 1, 2)) == vector.create(0, 1, 1)) assert(select("#", vector.clamp(vector.zero, vector.zero, vector.one)) == 1) +-- lerp +assert(vector.lerp(vector.zero, vector.one, 0) == vector.zero) +assert(vector.lerp(vector.zero, vector.one, 1) == vector.one) +assert(vector.lerp(vector.zero, vector.one, 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.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(ecall(function() return vector.lerp(vector.zero, vector.one, vector.one) end) == "invalid argument #3 to 'lerp' (number expected, got vector)") + -- validate component access assert(vector.create(1, 2, 3).x == 1) assert(vector.create(1, 2, 3).X == 1) diff --git a/tests/main.cpp b/tests/main.cpp index 005a3e61..8ae42460 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -29,6 +29,8 @@ #include #endif +#include +#include #include #include @@ -426,6 +428,11 @@ int main(int argc, char** argv) doctest::String filter; if (doctest::parseOption(argc, argv, "--run_test", &filter) && filter[0] == '=') { + if (doctest::parseOption(argc, argv, "--run_tests_in_file")) + { + fprintf(stderr, "ERROR: Cannot pass both --run_test and --run_tests_in_file\n"); + return 1; + } const char* f = filter.c_str() + 1; const char* s = strchr(f, '/'); @@ -440,6 +447,15 @@ int main(int argc, char** argv) } } + doctest::String filter_path; + if (doctest::parseOption(argc, argv, "--run_tests_in_file", &filter_path) && filter_path[0] == '=') + { + filter_path = filter_path.substr(1, filter_path.size() - 1); + std::ifstream filter_stream(filter_path.c_str()); + std::string case_list((std::istreambuf_iterator(filter_stream)), std::istreambuf_iterator()); + context.addFilter("test-case", case_list.c_str()); + } + // These callbacks register unit tests that need runtime support to be // correctly set up. Running them here means that all command line flags // have been parsed, fast flags have been set, and we've potentially already From 5189ad927ecae483fc3d26641fc21dc5992ae008 Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 29 Aug 2025 13:05:44 -0700 Subject: [PATCH 4/8] Sync to upstream/release/689 (#1981) # General Improvements * Fix a crash that could occur when the `deprecated` attribute is used in a syntactically incorrect way. * Improve stack utilization in the parser. This should make it harder for deeply nested constructs to exhaust the C stack. * Fixed the `xpcall` continuation to handle errors handling the same way as main execution path. * The fuzzer now enables all Luau flags.. * The fuzzer will now add attributes and create table literal expressions. * The type of `rawget` is now `(tab: {[K]: V}, k: K) -> V?` to reflect the fact that it returns `nil` when the key is not present. * Implement native codegen lowering for `vector.lerp` # Autocomplete * Autocomplete will also now suggest hot comments like `--!strict` and `--!optimize` * Incremental autocomplete had a bug where it wasn't selecting the correct refinement of the autocomplete subject. This is now fixed. # New Solver * Fix an incorrect refinement that caused autocomplete to fail on statements of the following form: `if #some_local.` * Improve the error feedback when a type function is not passed required arguments. (ie no `<>` afterward at all) * Improvements to bidirectional table inference ## Internal Contributors Co-authored-by: Andy Friesen Co-authored-by: Annie Tang Co-authored-by: Hunter Goldstein Co-authored-by: Ilya Rezvov Co-authored-by: Sora Kanosue Co-authored-by: Varun Saini Co-authored-by: Vighnesh Vijay 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: Ariel Weiss --- Analysis/include/Luau/AutocompleteTypes.h | 2 + Analysis/include/Luau/Constraint.h | 28 +- Analysis/include/Luau/ConstraintGenerator.h | 1 - Analysis/include/Luau/ConstraintSolver.h | 4 +- Analysis/include/Luau/Error.h | 19 +- Analysis/include/Luau/FragmentAutocomplete.h | 3 +- Analysis/include/Luau/Module.h | 4 + Analysis/include/Luau/TableLiteralInference.h | 30 +- Analysis/include/Luau/TypeArena.h | 4 +- Analysis/include/Luau/TypeUtils.h | 12 - Analysis/src/Autocomplete.cpp | 33 +- Analysis/src/AutocompleteCore.cpp | 29 +- Analysis/src/AutocompleteCore.h | 3 +- Analysis/src/BuiltinTypeFunctions.cpp | 8 +- Analysis/src/Constraint.cpp | 10 +- Analysis/src/ConstraintGenerator.cpp | 200 +++---- Analysis/src/ConstraintSolver.cpp | 115 +++- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 57 +- Analysis/src/Error.cpp | 13 + Analysis/src/FragmentAutocomplete.cpp | 94 ++- Analysis/src/Frontend.cpp | 10 +- Analysis/src/IostreamHelpers.cpp | 2 + Analysis/src/Module.cpp | 22 + Analysis/src/Simplify.cpp | 12 + Analysis/src/TableLiteralInference.cpp | 563 +++++++----------- Analysis/src/ToString.cpp | 4 +- Analysis/src/TypeArena.cpp | 3 - Analysis/src/TypeChecker2.cpp | 51 +- Analysis/src/TypeUtils.cpp | 12 +- Ast/include/Luau/Parser.h | 3 +- Ast/src/Ast.cpp | 16 +- Ast/src/Parser.cpp | 86 ++- CodeGen/include/Luau/AssemblyBuilderA64.h | 3 + CodeGen/include/Luau/AssemblyBuilderX64.h | 5 +- CodeGen/include/Luau/IrData.h | 5 + CodeGen/src/AssemblyBuilderA64.cpp | 26 + CodeGen/src/AssemblyBuilderX64.cpp | 19 +- CodeGen/src/BytecodeAnalysis.cpp | 130 +++- CodeGen/src/IrDump.cpp | 2 + CodeGen/src/IrLoweringA64.cpp | 20 + CodeGen/src/IrLoweringX64.cpp | 30 +- CodeGen/src/IrTranslateBuiltins.cpp | 28 + CodeGen/src/IrUtils.cpp | 1 + CodeGen/src/OptimizeConstProp.cpp | 1 + Common/include/Luau/ExperimentalFlags.h | 1 + Compiler/src/Compiler.cpp | 2 - Makefile | 1 + VM/src/lapi.cpp | 35 +- VM/src/lbaselib.cpp | 41 +- VM/src/ldo.cpp | 63 +- VM/src/ldo.h | 1 + VM/src/lgcdebug.cpp | 7 +- fuzz/compiler.cpp | 11 +- fuzz/format.cpp | 10 +- fuzz/linter.cpp | 13 +- fuzz/luau.proto | 35 ++ fuzz/parser.cpp | 11 +- fuzz/proto.cpp | 2 + fuzz/protoprint.cpp | 92 ++- fuzz/transpiler.cpp | 11 +- fuzz/typeck.cpp | 13 +- tests/AssemblyBuilderA64.test.cpp | 5 + tests/AssemblyBuilderX64.test.cpp | 2 +- tests/Autocomplete.test.cpp | 57 +- tests/Conformance.test.cpp | 58 +- tests/FragmentAutocomplete.test.cpp | 132 +++- tests/Frontend.test.cpp | 57 +- tests/Generalization.test.cpp | 8 - tests/InferPolarity.test.cpp | 6 - tests/IrLowering.test.cpp | 46 ++ tests/NonStrictTypeChecker.test.cpp | 15 +- tests/Normalize.test.cpp | 151 +++-- tests/RuntimeLimits.test.cpp | 12 +- tests/Subtyping.test.cpp | 4 - tests/ToString.test.cpp | 112 ++-- tests/TypeFunction.test.cpp | 63 +- tests/TypeFunction.user.test.cpp | 109 +++- tests/TypeInfer.aliases.test.cpp | 25 +- tests/TypeInfer.builtins.test.cpp | 44 +- tests/TypeInfer.functions.test.cpp | 42 +- tests/TypeInfer.generics.test.cpp | 29 +- tests/TypeInfer.intersectionTypes.test.cpp | 20 +- tests/TypeInfer.loops.test.cpp | 4 +- tests/TypeInfer.modules.test.cpp | 17 +- tests/TypeInfer.negations.test.cpp | 4 - tests/TypeInfer.oop.test.cpp | 9 +- tests/TypeInfer.operators.test.cpp | 16 +- tests/TypeInfer.provisional.test.cpp | 10 +- tests/TypeInfer.refinements.test.cpp | 149 +++-- tests/TypeInfer.singletons.test.cpp | 27 +- tests/TypeInfer.tables.test.cpp | 338 +++++++---- tests/TypeInfer.test.cpp | 25 +- tests/TypeInfer.tryUnify.test.cpp | 5 +- tests/TypeInfer.typePacks.test.cpp | 82 +-- tests/TypeInfer.typestates.test.cpp | 11 +- tests/TypeInfer.unionTypes.test.cpp | 12 +- tests/TypeInfer.unknownnever.test.cpp | 6 - tests/TypeVar.test.cpp | 8 +- tests/conformance/coroutine.luau | 20 + tests/conformance/pcall.luau | 29 +- tests/conformance/vector.luau | 5 +- tests/conformance/vector_library.luau | 12 +- 102 files changed, 2420 insertions(+), 1438 deletions(-) diff --git a/Analysis/include/Luau/AutocompleteTypes.h b/Analysis/include/Luau/AutocompleteTypes.h index ea934a3a..edc79b7f 100644 --- a/Analysis/include/Luau/AutocompleteTypes.h +++ b/Analysis/include/Luau/AutocompleteTypes.h @@ -18,6 +18,7 @@ enum class AutocompleteContext Type, Keyword, String, + HotComment, }; enum class AutocompleteEntryKind @@ -30,6 +31,7 @@ enum class AutocompleteEntryKind Module, GeneratedFunction, RequirePath, + HotComment, }; enum class ParenthesesRecommendation diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index a556736b..31eaa7e7 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -116,21 +116,6 @@ struct FunctionCheckConstraint NotNull> astExpectedTypes; }; -// table_check expectedType exprType -// -// If `expectedType` is a table type and `exprType` is _also_ a table type, -// propogate the member types of `expectedType` into the types of `exprType`. -// This is used to implement bidirectional inference on table assignment. -// Also see: FunctionCheckConstraint. -struct TableCheckConstraint -{ - TypeId expectedType; - TypeId exprType; - AstExprTable* table = nullptr; - NotNull> astTypes; - NotNull> astExpectedTypes; -}; - // prim FreeType ExpectedType PrimitiveType // // FreeType is bounded below by the singleton type and above by PrimitiveType @@ -303,6 +288,15 @@ struct PushFunctionTypeConstraint bool isSelf; }; +struct PushTypeConstraint +{ + TypeId expectedType; + TypeId targetType; + NotNull> astTypes; + NotNull> astExpectedTypes; + NotNull expr; +}; + using ConstraintV = Variant< SubtypeConstraint, PackSubtypeConstraint, @@ -321,9 +315,9 @@ using ConstraintV = Variant< ReduceConstraint, ReducePackConstraint, EqualityConstraint, - TableCheckConstraint, SimplifyConstraint, - PushFunctionTypeConstraint>; + PushFunctionTypeConstraint, + PushTypeConstraint>; struct Constraint { diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index 2f1d1887..67fad474 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -170,7 +170,6 @@ struct ConstraintGenerator std::vector typePacks; }; - std::vector> DEPRECATED_interiorTypes; std::vector interiorFreeTypes; std::vector unionsToSimplify; diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 64c82033..fcbc41e0 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -249,8 +249,7 @@ struct ConstraintSolver bool tryDispatch(const NameConstraint& c, NotNull constraint); bool tryDispatch(const TypeAliasExpansionConstraint& c, NotNull constraint); bool tryDispatch(const FunctionCallConstraint& c, NotNull constraint, bool force); - bool tryDispatch(const TableCheckConstraint& c, NotNull constraint); - bool tryDispatch(const FunctionCheckConstraint& c, NotNull constraint); + bool tryDispatch(const FunctionCheckConstraint& c, NotNull constraint, bool force); bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull constraint); bool tryDispatch(const HasPropConstraint& c, NotNull constraint); @@ -275,6 +274,7 @@ struct ConstraintSolver bool tryDispatch(const SimplifyConstraint& c, NotNull constraint, bool force); bool tryDispatch(const PushFunctionTypeConstraint& c, NotNull constraint); + bool tryDispatch(const PushTypeConstraint& c, NotNull constraint, bool force); // for a, ... in some_table do // also handles __iter metamethod diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index d6a4c800..f1292833 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -526,6 +526,21 @@ struct GenericBoundsMismatch bool operator==(const GenericBoundsMismatch& rhs) const; }; +// Error when referencing a type function without providing explicit generics. +// +// type function create_table_with_key() +// local tbl = types.newtable() +// tbl:setproperty(types.singleton "key", types.unionof(types.string, types.singleton(nil))) +// return tbl +// end +// local a: create_table_with_key = {} +// ^^^^^^^^^^^^^^^^^^^^^ This should have `<>` at the end. +// +struct UnappliedTypeFunction +{ + bool operator==(const UnappliedTypeFunction& rhs) const; +}; + using TypeErrorData = Variant< TypeMismatch, UnknownSymbol, @@ -583,7 +598,9 @@ using TypeErrorData = Variant< GenericTypePackCountMismatch, MultipleNonviableOverloads, RecursiveRestraintViolation, - GenericBoundsMismatch>; + GenericBoundsMismatch, + UnappliedTypeFunction>; + struct TypeErrorSummary { diff --git a/Analysis/include/Luau/FragmentAutocomplete.h b/Analysis/include/Luau/FragmentAutocomplete.h index d1023ecd..3ed86bd4 100644 --- a/Analysis/include/Luau/FragmentAutocomplete.h +++ b/Analysis/include/Luau/FragmentAutocomplete.h @@ -127,7 +127,8 @@ FragmentAutocompleteResult fragmentAutocomplete( StringCompletionCallback callback, std::optional fragmentEndPosition = std::nullopt, AstStatBlock* recentParse = nullptr, - IFragmentAutocompleteReporter* reporter = nullptr + IFragmentAutocompleteReporter* reporter = nullptr, + bool isInHotComment = false ); enum class FragmentAutocompleteStatus diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index cf61e021..f0ce5202 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -63,6 +63,10 @@ bool isWithinComment(const std::vector& commentLocations, Position pos) bool isWithinComment(const SourceModule& sourceModule, Position pos); bool isWithinComment(const ParseResult& result, Position pos); +bool isWithinHotComment(const std::vector& hotComments, Position pos); +bool isWithinHotComment(const SourceModule& sourceModule, Position pos); +bool isWithinHotComment(const ParseResult& result, Position pos); + struct RequireCycle { Location location; diff --git a/Analysis/include/Luau/TableLiteralInference.h b/Analysis/include/Luau/TableLiteralInference.h index cb4e8786..2da6301d 100644 --- a/Analysis/include/Luau/TableLiteralInference.h +++ b/Analysis/include/Luau/TableLiteralInference.h @@ -2,22 +2,29 @@ #pragma once +#include "Luau/Ast.h" #include "Luau/DenseHash.h" #include "Luau/NotNull.h" #include "Luau/TypeFwd.h" - -#include +#include "Luau/TypeArena.h" +#include "Luau/Unifier2.h" namespace Luau { -struct TypeArena; -struct BuiltinTypes; -struct Unifier2; -struct Subtyping; -class AstExpr; +struct IncompleteInference +{ + TypeId expectedType; + TypeId targetType; + const AstExpr* expr; +}; -TypeId matchLiteralType( +struct PushTypeResult +{ + std::vector incompleteTypes; +}; + +PushTypeResult pushTypeInto( NotNull> astTypes, NotNull> astExpectedTypes, NotNull builtinTypes, @@ -25,8 +32,7 @@ TypeId matchLiteralType( NotNull unifier, NotNull subtyping, TypeId expectedType, - TypeId exprType, - const AstExpr* expr, - std::vector& toBlock + const AstExpr* expr ); -} // namespace Luau + +}; // namespace Luau \ No newline at end of file diff --git a/Analysis/include/Luau/TypeArena.h b/Analysis/include/Luau/TypeArena.h index 203a62d9..adc59aeb 100644 --- a/Analysis/include/Luau/TypeArena.h +++ b/Analysis/include/Luau/TypeArena.h @@ -8,8 +8,6 @@ #include -LUAU_FASTFLAG(LuauTrackTypeAllocations) - namespace Luau { struct Module; @@ -37,7 +35,7 @@ struct TypeArena if constexpr (std::is_same_v) { - if (FFlag::LuauTrackTypeAllocations && collectSingletonStats) + if (collectSingletonStats) recordSingletonStats(NotNull{&tv}); } diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 9125d629..0e0a217b 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -257,18 +257,6 @@ std::optional follow(std::optional ty) */ bool isLiteral(const AstExpr* expr); -/** - * Given a table literal and a mapping from expression to type, determine - * whether any literal expression in this table depends on any blocked types. - * This is used as a precondition for bidirectional inference: be warned that - * the behavior of this algorithm is tightly coupled to that of bidirectional - * inference. - * @param expr Expression to search - * @param astTypes Mapping from AST node to TypeID - * @returns A vector of blocked types - */ -std::vector findBlockedTypesIn(AstExprTable* expr, NotNull> astTypes); - /** * Given a function call and a mapping from expression to type, determine * whether the type of any argument in said call in depends on a blocked types. diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp index 6576fd16..544f19ff 100644 --- a/Analysis/src/Autocomplete.cpp +++ b/Analysis/src/Autocomplete.cpp @@ -10,6 +10,7 @@ #include "AutocompleteCore.h" LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauSuggestHotComments) namespace Luau { @@ -40,13 +41,33 @@ AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName globalScope = frontend.globalsForAutocomplete.globalScope.get(); TypeArena typeArena; - if (isWithinComment(*sourceModule, position)) - return {}; + 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 {}; + + 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) + ); + } } } // namespace Luau diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index d680a31a..14d52dfc 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -17,6 +17,9 @@ #include "Luau/TypePack.h" #include +#include +#include +#include #include #include @@ -26,10 +29,13 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) LUAU_FASTFLAGVARIABLE(LuauIncludeBreakContinueStatements) +LUAU_FASTFLAGVARIABLE(LuauSuggestHotComments) -static const std::unordered_set kStatementStartingKeywords = +static constexpr std::array kStatementStartingKeywords = {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; +static constexpr std::array kHotComments = {"nolint", "nocheck", "nonstrict", "strict", "optimize", "native"}; + namespace Luau { @@ -1280,7 +1286,7 @@ static AutocompleteEntryMap autocompleteStatement( if (FFlag::LuauIncludeBreakContinueStatements) { bool shouldIncludeBreakAndContinue = isValidBreakContinueContext(ancestry, position); - for (const auto& kw : kStatementStartingKeywords) + for (const std::string_view kw : kStatementStartingKeywords) { if ((kw != "break" && kw != "continue") || shouldIncludeBreakAndContinue) result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); @@ -1288,7 +1294,7 @@ static AutocompleteEntryMap autocompleteStatement( } else { - for (const auto& kw : kStatementStartingKeywords) + for (const std::string_view kw : kStatementStartingKeywords) result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword}); } @@ -1838,10 +1844,24 @@ AutocompleteResult autocomplete_( const ScopePtr& scopeAtPosition, Position position, FileResolver* fileResolver, - StringCompletionCallback callback + StringCompletionCallback callback, + bool isInHotComment ) { LUAU_TIMETRACE_SCOPE("Luau::autocomplete_", "AutocompleteCore"); + + if (FFlag::LuauSuggestHotComments) + { + if (isInHotComment) + { + AutocompleteEntryMap result; + + for (const std::string_view hc : kHotComments) + result.emplace(hc, AutocompleteEntry{AutocompleteEntryKind::HotComment}); + return {std::move(result), ancestry, AutocompleteContext::HotComment}; + } + } + AstNode* node = ancestry.back(); AstExprConstantNil dummy{Location{}}; @@ -2140,5 +2160,4 @@ AutocompleteResult autocomplete_( return {}; } - } // namespace Luau diff --git a/Analysis/src/AutocompleteCore.h b/Analysis/src/AutocompleteCore.h index d4264da2..1c33e656 100644 --- a/Analysis/src/AutocompleteCore.h +++ b/Analysis/src/AutocompleteCore.h @@ -21,7 +21,8 @@ AutocompleteResult autocomplete_( const ScopePtr& scopeAtPosition, Position position, FileResolver* fileResolver, - StringCompletionCallback callback + StringCompletionCallback callback, + bool isInHotComment = false ); } // namespace Luau diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index 8d50d31b..512ca155 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -28,6 +28,7 @@ LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways) LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(LuauRawGetHandlesNil) namespace Luau { @@ -2328,7 +2329,12 @@ TypeFunctionReductionResult indexFunctionImpl( { for (TypeId ty : *typesToFind) if (!tblIndexInto(ty, *tablesIter, properties, ctx, isRaw)) - return {std::nullopt, Reduction::Erroneous, {}, {}}; + { + if (FFlag::LuauRawGetHandlesNil && isRaw) + properties.insert(ctx->builtins->nilType); + else + return {std::nullopt, Reduction::Erroneous, {}, {}}; + } } } diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index a7083e10..1ea149b5 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -173,15 +173,13 @@ TypeIds Constraint::getMaybeMutatedFreeTypes() const { rci.traverse(rpc->tp); } - else if (auto tcc = get(*this)) + else if (auto pftc = get(*this)) { - rci.traverse(tcc->exprType); + rci.traverse(pftc->functionType); } - - // NOTE: this should probably be in an if-else chain with the above. - if (auto pftc = get(*this)) + else if (auto ptc = get(*this)) { - rci.traverse(pftc->functionType); + rci.traverse(ptc->targetType); } return types; diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index e6dd494d..2856dda4 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -20,7 +20,6 @@ #include "Luau/Simplify.h" #include "Luau/StringUtils.h" #include "Luau/Subtyping.h" -#include "Luau/TableLiteralInference.h" #include "Luau/TimeTrace.h" #include "Luau/Type.h" #include "Luau/TypeFunction.h" @@ -42,14 +41,15 @@ LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2) LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAGVARIABLE(LuauTypeFunNoScopeMapRef) -LUAU_FASTFLAGVARIABLE(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAGVARIABLE(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) +LUAU_FASTFLAGVARIABLE(LuauInstantiateResolvedTypeFunctions) +LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraint) +LUAU_FASTFLAGVARIABLE(LuauNumericUnaryOpsDontProduceNegationRefinements) namespace Luau { @@ -232,12 +232,6 @@ ConstraintGenerator::ConstraintGenerator( , logger(logger) { LUAU_ASSERT(module); - - if (FFlag::LuauEagerGeneralization4) - { - LUAU_ASSERT(FFlag::LuauTrackFreeInteriorTypePacks); - LUAU_ASSERT(FFlag::LuauResetConditionalContextProperly); - } } ConstraintSet ConstraintGenerator::run(AstStatBlock* block) @@ -266,10 +260,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) rootScope->location = block->location; module->astScopes[block] = NotNull{scope.get()}; - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.emplace_back(); - else - DEPRECATED_interiorTypes.emplace_back(); + interiorFreeTypes.emplace_back(); // Create module-local scope for the type function environment ScopePtr localTypeFunctionScope = std::make_shared(typeFunctionScope); @@ -303,13 +294,8 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) } ); - if (FFlag::LuauTrackFreeInteriorTypePacks) - { - scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); - scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); - } - else - scope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); + scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); getMutable(result)->setOwner(genConstraint); forEachConstraint( @@ -325,10 +311,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) } ); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.pop_back(); - else - DEPRECATED_interiorTypes.pop_back(); + interiorFreeTypes.pop_back(); fillInInferredBindings(scope, block); @@ -360,16 +343,10 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat // We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block); // Pre - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.emplace_back(); - else - DEPRECATED_interiorTypes.emplace_back(); + interiorFreeTypes.emplace_back(); visitBlockWithoutChildScope(resumeScope, block); // Post - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.pop_back(); - else - DEPRECATED_interiorTypes.pop_back(); + interiorFreeTypes.pop_back(); fillInInferredBindings(resumeScope, block); @@ -396,33 +373,23 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) { - if (FFlag::LuauTrackFreeInteriorTypePacks) - { - const TypeId ft = FFlag::LuauEagerGeneralization4 - ? Luau::freshType(arena, builtinTypes, scope.get(), polarity) - : Luau::freshType(arena, builtinTypes, scope.get()); + const TypeId ft = FFlag::LuauEagerGeneralization4 + ? Luau::freshType(arena, builtinTypes, scope.get(), polarity) + : Luau::freshType(arena, builtinTypes, scope.get()); - interiorFreeTypes.back().types.push_back(ft); + interiorFreeTypes.back().types.push_back(ft); - if (FFlag::LuauEagerGeneralization4) - freeTypes.insert(ft); + if (FFlag::LuauEagerGeneralization4) + freeTypes.insert(ft); - return ft; - } - else - { - auto ft = Luau::freshType(arena, builtinTypes, scope.get()); - DEPRECATED_interiorTypes.back().push_back(ft); - return ft; - } + return ft; } TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity polarity) { FreeTypePack f{scope.get(), polarity}; TypePackId result = arena->addTypePack(TypePackVar{std::move(f)}); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.back().typePacks.push_back(result); + interiorFreeTypes.back().typePacks.push_back(result); return result; } @@ -1037,11 +1004,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat) { RecursionLimiter limiter{"ConstraintGenerator", &recursionCount, FInt::LuauCheckRecursionLimit}; - if (FFlag::LuauTrackFreeInteriorTypePacks) - LUAU_ASSERT(DEPRECATED_interiorTypes.empty()); - else - LUAU_ASSERT(interiorFreeTypes.empty()); - if (auto s = stat->as()) return visit(scope, s); else if (auto i = stat->as()) @@ -1917,10 +1879,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio else scope->children.push_back(NotNull{sig.signatureScope.get()}); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.emplace_back(); - else - DEPRECATED_interiorTypes.push_back(std::vector{}); + interiorFreeTypes.emplace_back(); checkFunctionBody(sig.bodyScope, function->body); Checkpoint endCheckpoint = checkpoint(this); @@ -1935,19 +1894,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio } ); - if (FFlag::LuauTrackFreeInteriorTypePacks) - { - sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); - sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); - } - else - sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); + sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); getMutable(generalizedTy)->setOwner(gc); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.pop_back(); - else - DEPRECATED_interiorTypes.pop_back(); + interiorFreeTypes.pop_back(); Constraint* previous = nullptr; forEachConstraint( @@ -1999,10 +1950,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio else scope->children.push_back(NotNull{sig.signatureScope.get()}); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.emplace_back(); - else - DEPRECATED_interiorTypes.push_back(std::vector{}); + interiorFreeTypes.emplace_back(); checkFunctionBody(sig.bodyScope, function->body); Checkpoint endCheckpoint = checkpoint(this); @@ -2017,19 +1965,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio } ); - if (FFlag::LuauTrackFreeInteriorTypePacks) - { - sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); - sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); - } - else - sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); + sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); getMutable(generalizedTy)->setOwner(gc); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.pop_back(); - else - DEPRECATED_interiorTypes.pop_back(); + interiorFreeTypes.pop_back(); Constraint* previous = nullptr; forEachConstraint( @@ -2471,13 +2411,10 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* Checkpoint funcBeginCheckpoint = checkpoint(this); TypeId fnType = nullptr; - if (FFlag::LuauResetConditionalContextProperly) { InConditionalContext icc2{&typeContext, TypeContext::Default}; fnType = check(scope, call->func).ty; } - else - fnType = check(scope, call->func).ty; Checkpoint funcEndCheckpoint = checkpoint(this); @@ -3006,17 +2943,12 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* func, std::optional expectedType, bool generalize) { - std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - inContext.emplace(&typeContext, TypeContext::Default); + InConditionalContext inContext(&typeContext, TypeContext::Default); Checkpoint startCheckpoint = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.emplace_back(); - else - DEPRECATED_interiorTypes.push_back(std::vector{}); + interiorFreeTypes.emplace_back(); checkFunctionBody(sig.bodyScope, func); Checkpoint endCheckpoint = checkpoint(this); @@ -3031,20 +2963,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun } ); - if (FFlag::LuauTrackFreeInteriorTypePacks) - { - sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); - sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); - interiorFreeTypes.pop_back(); + sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); + sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); + interiorFreeTypes.pop_back(); - getMutable(generalizedTy)->setOwner(gc); - } - else - { - sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); - getMutable(generalizedTy)->setOwner(gc); - DEPRECATED_interiorTypes.pop_back(); - } + getMutable(generalizedTy)->setOwner(gc); Constraint* previous = nullptr; forEachConstraint( @@ -3083,7 +3006,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary) { std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly && unary->op != AstExprUnary::Op::Not) + if (unary->op != AstExprUnary::Op::Not) inContext.emplace(&typeContext, TypeContext::Default); auto [operandType, refinement] = check(scope, unary->expr); @@ -3098,12 +3021,18 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary) case AstExprUnary::Op::Len: { TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().lenFunc, {operandType}, {}, scope, unary->location); - return Inference{resultType, refinementArena.negation(refinement)}; + if (FFlag::LuauNumericUnaryOpsDontProduceNegationRefinements) + return Inference{resultType, std::move(refinement)}; + else + return Inference{resultType, refinementArena.negation(refinement)}; } case AstExprUnary::Op::Minus: { TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().unmFunc, {operandType}, {}, scope, unary->location); - return Inference{resultType, refinementArena.negation(refinement)}; + if (FFlag::LuauNumericUnaryOpsDontProduceNegationRefinements) + return Inference{resultType, std::move(refinement)}; + else + return Inference{resultType, refinementArena.negation(refinement)}; } default: // msvc can't prove that this is exhaustive. LUAU_UNREACHABLE(); @@ -3247,9 +3176,7 @@ Inference ConstraintGenerator::checkAstExprBinary( Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIfElse* ifElse, std::optional expectedType) { - std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - inContext.emplace(&typeContext, TypeContext::Default); + InConditionalContext inContext(&typeContext, TypeContext::Default); RefinementId refinement = [&]() { @@ -3280,9 +3207,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTypeAssertion Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprInterpString* interpString) { - std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - inContext.emplace(&typeContext, TypeContext::Default); + InConditionalContext inContext(&typeContext, TypeContext::Default); for (AstExpr* expr : interpString->expressions) check(scope, expr); @@ -3299,11 +3224,8 @@ std::tuple ConstraintGenerator::checkBinary( ) { std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - { - if (op != AstExprBinary::And && op != AstExprBinary::Or && op != AstExprBinary::CompareEq && op != AstExprBinary::CompareNe) - inContext.emplace(&typeContext, TypeContext::Default); - } + if (op != AstExprBinary::And && op != AstExprBinary::Or && op != AstExprBinary::CompareEq && op != AstExprBinary::CompareNe) + inContext.emplace(&typeContext, TypeContext::Default); if (op == AstExprBinary::And) { @@ -3545,9 +3467,7 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprIndexExpr* e Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, std::optional expectedType) { - std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - inContext.emplace(&typeContext, TypeContext::Default); + InConditionalContext inContext(&typeContext, TypeContext::Default); TypeId ty = arena->addType(TableType{}); TableType* ttv = getMutable(ty); @@ -3561,10 +3481,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, if (FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) largeTableDepth++; - if (FFlag::LuauTrackFreeInteriorTypePacks) - interiorFreeTypes.back().types.push_back(ty); - else - DEPRECATED_interiorTypes.back().push_back(ty); + interiorFreeTypes.back().types.push_back(ty); TypeIds indexKeyLowerBound; TypeIds indexValueLowerBound; @@ -3640,6 +3557,21 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, ttv->indexer = TableIndexer{indexKey, indexValue}; } + if (FFlag::LuauPushTypeConstraint && expectedType) + { + addConstraint( + scope, + expr->location, + PushTypeConstraint{ + /* expectedType */ *expectedType, + /* targetType */ ty, + /* astTypes */ NotNull{&module->astTypes}, + /* astExpectedTypes */ NotNull{&module->astExpectedTypes}, + /* expr */ NotNull{expr}, + } + ); + } + if (FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) largeTableDepth--; @@ -3967,6 +3899,16 @@ TypeId ConstraintGenerator::resolveReferenceType( result = freshType(scope, Polarity::Mixed); } + if (FFlag::LuauInstantiateResolvedTypeFunctions) + { + if (is(follow(result))) + { + reportError(ty->location, UnappliedTypeFunction{}); + addConstraint(scope, ty->location, ReduceConstraint{result}); + } + } + + return result; } diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index d163b073..032ded72 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -49,6 +49,7 @@ LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAGVARIABLE(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) +LUAU_FASTFLAG(LuauPushTypeConstraint) namespace Luau { @@ -74,7 +75,6 @@ size_t HashSubtypeConstraintRecord::operator()(const SubtypeConstraintRecord& c) return result; } - static void dump(ConstraintSolver* cs, ToStringOptions& opts); size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const @@ -874,9 +874,7 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo else if (auto fcc = get(*constraint)) success = tryDispatch(*fcc, constraint, force); else if (auto fcc = get(*constraint)) - success = tryDispatch(*fcc, constraint); - else if (auto tcc = get(*constraint)) - success = tryDispatch(*tcc, constraint); + success = tryDispatch(*fcc, constraint, force); else if (auto fcc = get(*constraint)) success = tryDispatch(*fcc, constraint); else if (auto hpc = get(*constraint)) @@ -899,6 +897,8 @@ bool ConstraintSolver::tryDispatch(NotNull constraint, bool fo success = tryDispatch(*sc, constraint, force); else if (auto pftc = get(*constraint)) success = tryDispatch(*pftc, constraint); + else if (auto ptc = get(*constraint)) + success = tryDispatch(*ptc, constraint, force); else LUAU_ASSERT(false); @@ -1787,7 +1787,7 @@ struct ContainsGenerics : public TypeOnceVisitor } // namespace -bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull constraint) +bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull constraint, bool force) { TypeId fn = follow(c.fn); const TypePackId argsPack = follow(c.argsPack); @@ -1899,7 +1899,47 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull + // + // 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); + } } } @@ -1924,29 +1964,6 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull constraint) -{ - // This is expensive as we need to traverse a (potentially large) - // literal up front in order to determine if there are any blocked - // types, otherwise we may run `matchTypeLiteral` multiple times, - // which right now may fail due to being non-idempotent (it - // destructively updates the underlying literal type). - auto blockedTypes = findBlockedTypesIn(c.table, c.astTypes); - for (const auto ty : blockedTypes) - { - block(ty, constraint); - } - if (!blockedTypes.empty()) - return false; - - Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}}; - Subtyping sp{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; - std::vector toBlock; - (void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&sp}, c.expectedType, c.exprType, c.table, toBlock); - LUAU_ASSERT(toBlock.empty()); - return true; -} - bool ConstraintSolver::tryDispatch(const PrimitiveTypeConstraint& c, NotNull constraint) { LUAU_ASSERT(!FFlag::DebugLuauStringSingletonBasedOnQuotes); @@ -2945,6 +2962,46 @@ bool ConstraintSolver::tryDispatch(const PushFunctionTypeConstraint& c, NotNull< return true; } +bool ConstraintSolver::tryDispatch(const PushTypeConstraint& c, NotNull constraint, bool force) +{ + LUAU_ASSERT(FFlag::LuauPushTypeConstraint); + Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}, &uninhabitedTypeFunctions}; + Subtyping subtyping{builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, NotNull{&iceReporter}}; + + // NOTE: If we don't do this check up front, we almost immediately start + // spawning tons of push type constraints. It's pretty important. + if (isBlocked(c.expectedType)) + { + block(c.expectedType, constraint); + return false; + } + + auto result = pushTypeInto(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, NotNull{&subtyping}, c.expectedType, c.expr); + + // If we're forcing this constraint, just early exit: we can continue + // inferring the rest of the file, we might just error when we shouldn't. + if (force || result.incompleteTypes.empty()) + return true; + + for (auto [newExpectedTy, newTargetTy, newExpr] : result.incompleteTypes) + { + auto addition = pushConstraint( + constraint->scope, + constraint->location, + PushTypeConstraint{ + /* expectedType */ newExpectedTy, + /* targetType */ newTargetTy, + /* astTypes */ c.astTypes, + /* astExpectedTypes */ c.astExpectedTypes, + /* expr */ NotNull{newExpr}, + } + ); + inheritBlocks(constraint, addition); + } + + return false; +} + bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull constraint, bool force) { iteratorTy = follow(iteratorTy); diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 88e5c5ec..8bb1a4ca 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -2,6 +2,7 @@ #include "Luau/BuiltinDefinitions.h" LUAU_FASTFLAGVARIABLE(LuauTypeCheckerVectorLerp) +LUAU_FASTFLAGVARIABLE(LuauRawGetHandlesNil) namespace Luau { @@ -29,6 +30,60 @@ 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"; + +// 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} @@ -324,7 +379,7 @@ declare vector: { std::string getBuiltinDefinitionSource() { - std::string result = kBuiltinDefinitionBaseSrc; + std::string result = FFlag::LuauRawGetHandlesNil ? kBuiltinDefinitionBaseSrc : kBuiltinDefinitionBaseSrc_DEPRECATED; result += kBuiltinDefinitionBit32Src; result += kBuiltinDefinitionMathSrc; diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index be163e44..8e738140 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -915,6 +915,11 @@ struct ErrorConverter return "The generic type parameter " + std::string{e.genericName} + "was found to have invalid bounds. Its lower bounds were [" + lowerBounds + "], and its upper bounds were [" + upperBounds + "]."; } + + std::string operator()(const UnappliedTypeFunction&) const + { + return "Type functions always require `<>` when referenced."; + } }; struct InvalidNameChecker @@ -1330,6 +1335,11 @@ bool GenericBoundsMismatch::operator==(const GenericBoundsMismatch& rhs) const return genericName == rhs.genericName && lowerBounds == rhs.lowerBounds && upperBounds == rhs.upperBounds; } +bool UnappliedTypeFunction::operator==(const UnappliedTypeFunction& rhs) const +{ + return true; +} + std::string toString(const TypeError& error) { return toString(error, TypeErrorToStringOptions{}); @@ -1563,6 +1573,9 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) for (auto& upperBound : e.upperBounds) upperBound = clone(upperBound); } + else if constexpr (std::is_same_v) + { + } else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index ee4dc438..13a5ec3c 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -36,6 +36,8 @@ LUAU_FASTFLAGVARIABLE(LuauFragmentRequiresCanBeResolvedToAModule) LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAGVARIABLE(LuauForInProvidesRecommendations) +LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTakesInnermostRefinement) +LUAU_FASTFLAG(LuauSuggestHotComments) namespace Luau { @@ -698,6 +700,20 @@ void cloneTypesFromFragment( if (auto res = stale->refinements.find(syms); res != stale->refinements.end()) { destScope->rvalueRefinements[d] = Luau::cloneIncremental(res->second, *destArena, cloneState, destScope); + // If we've found a refinement, just break, otherwise we might end up doing the wrong thing for: + // + // type TaggedUnion = { tag: 'a', value: number } | { tag: 'b', value: string } + // local function foobar(obj: TaggedUnion?) + // if obj then + // if obj.tag == 'a' + // obj.| -- We want the most "narrow" refinement here. + // end + // end + // end + // + // We could find another binding for `syms` and then set _that_. + if (FFlag::LuauFragmentAutocompleteTakesInnermostRefinement) + break; } } } @@ -1489,29 +1505,61 @@ FragmentAutocompleteStatusResult tryFragmentAutocomplete( StringCompletionCallback stringCompletionCB ) { - 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 + if (FFlag::LuauSuggestHotComments) { - 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)}; + 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}; + } } - catch (const Luau::InternalCompilerError& e) + else { - if (FFlag::DebugLogFragmentsFromAutocomplete) - logLuau("tryFragmentAutocomplete exception", e.what()); - return {FragmentAutocompleteStatus::InternalIce, std::nullopt}; + if (isWithinComment(context.freshParse.commentLocations, cursorPosition)) + return {FragmentAutocompleteStatus::Success, std::nullopt}; + // TODO: we should calculate fragmentEnd position here, by using context.newAstRoot and cursorPosition + try + { + 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}; + } } } @@ -1524,7 +1572,8 @@ FragmentAutocompleteResult fragmentAutocomplete( StringCompletionCallback callback, std::optional fragmentEndPosition, AstStatBlock* recentParse, - IFragmentAutocompleteReporter* reporter + IFragmentAutocompleteReporter* reporter, + bool isInHotComment ) { LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete"); @@ -1548,7 +1597,8 @@ FragmentAutocompleteResult fragmentAutocomplete( tcResult.freshScope, cursorPosition, frontend.fileResolver, - std::move(callback) + std::move(callback), + FFlag::LuauSuggestHotComments && isInHotComment ); freeze(tcResult.incrementalModule->internalTypes); reportWaypoint(reporter, FragmentAutocompleteWaypoint::AutocompleteEnd); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 46696040..9bcc53ea 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -41,7 +41,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) -LUAU_FASTFLAGVARIABLE(LuauTrackTypeAllocations) LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) @@ -1030,7 +1029,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) item.stats.timeCheck += duration; item.stats.filesStrict += 1; - if (FFlag::LuauTrackTypeAllocations && item.options.collectTypeAllocationStats) + if (item.options.collectTypeAllocationStats) { item.stats.typesAllocated += moduleForAutocomplete->internalTypes.types.size(); item.stats.typePacksAllocated += moduleForAutocomplete->internalTypes.typePacks.size(); @@ -1060,7 +1059,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) item.stats.filesStrict += (mode == Mode::Strict) ? 1 : 0; item.stats.filesNonstrict += (mode == Mode::Nonstrict) ? 1 : 0; - if (FFlag::LuauTrackTypeAllocations && item.options.collectTypeAllocationStats) + if (item.options.collectTypeAllocationStats) { item.stats.typesAllocated += module->internalTypes.types.size(); item.stats.typePacksAllocated += module->internalTypes.typePacks.size(); @@ -1194,7 +1193,7 @@ void Frontend::recordItemResult(const BuildQueueItem& item) stats.filesStrict += item.stats.filesStrict; stats.filesNonstrict += item.stats.filesNonstrict; - if (FFlag::LuauTrackTypeAllocations && item.options.collectTypeAllocationStats) + if (item.options.collectTypeAllocationStats) { stats.typesAllocated += item.stats.typesAllocated; stats.typePacksAllocated += item.stats.typePacksAllocated; @@ -1445,8 +1444,7 @@ ModulePtr check( result->mode = mode; result->internalTypes.owningModule = result.get(); result->interfaceTypes.owningModule = result.get(); - if (FFlag::LuauTrackTypeAllocations) - result->internalTypes.collectSingletonStats = options.collectTypeAllocationStats; + result->internalTypes.collectSingletonStats = options.collectTypeAllocationStats; result->allocator = sourceModule.allocator; result->names = sourceModule.names; result->root = sourceModule.root; diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index 7f245005..db039d8c 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -287,6 +287,8 @@ static void errorToString(std::ostream& stream, const T& err) } stream << "] }"; } + else if constexpr (std::is_same_v) + stream << "UnappliedTypeFunction {}"; else static_assert(always_false_v, "Non-exhaustive type switch"); } diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index e34fe9bc..6028eb49 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAGVARIABLE(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauSuggestHotComments) namespace Luau { @@ -94,6 +95,27 @@ bool isWithinComment(const ParseResult& result, Position pos) return isWithinComment(result.commentLocations, pos); } +bool isWithinHotComment(const std::vector& hotComments, Position pos) +{ + for (const HotComment& hotComment : hotComments) + { + if (hotComment.location.containsClosed(pos)) + return true; + } + + return false; +} + +bool isWithinHotComment(const SourceModule& sourceModule, Position pos) +{ + return isWithinHotComment(sourceModule.hotcomments, pos); +} + +bool isWithinHotComment(const ParseResult& result, Position pos) +{ + return isWithinHotComment(result.hotcomments, pos); +} + struct ClonePublicInterface : Substitution { NotNull builtinTypes; diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index bac150f0..31becdc0 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -23,6 +23,7 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeSimplificationIterationLimit, 128) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTFLAG(LuauPushTypeConstraint) namespace Luau { @@ -421,7 +422,18 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) return Relation::Intersects; if (auto ut = get(left)) + { + if (FFlag::LuauPushTypeConstraint) + { + for (TypeId part : ut) + { + Relation r = relate(part, right, seen); + if (r == Relation::Superset || r == Relation::Coincident) + return Relation::Superset; + } + } return Relation::Intersects; + } else if (auto ut = get(right)) { std::vector opts; diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index a5e395f5..a0172846 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -4,7 +4,6 @@ #include "Luau/Ast.h" #include "Luau/Common.h" -#include "Luau/Normalize.h" #include "Luau/Simplify.h" #include "Luau/Subtyping.h" #include "Luau/Type.h" @@ -13,429 +12,291 @@ #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" +LUAU_FASTFLAG(LuauEagerGeneralization4) + namespace Luau { -TypeId matchLiteralType( - NotNull> astTypes, - NotNull> astExpectedTypes, - NotNull builtinTypes, - NotNull arena, - NotNull unifier, - NotNull subtyping, - TypeId expectedType, - TypeId exprType, - const AstExpr* expr, - std::vector& toBlock -) +namespace { - /* - * Table types that arise from literal table expressions have some - * properties that make this algorithm much simpler. - * - * Most importantly, the parts of the type that arise directly from the - * table expression are guaranteed to be acyclic. This means we can do all - * kinds of naive depth first traversal shenanigans and not worry about - * nasty details like aliasing or reentrancy. - * - * We are therefore completely free to mutate these portions of the - * TableType however we choose! We'll take advantage of this property to do - * things like replace explicit named properties with indexers as required - * by the expected type. - */ - - if (!isLiteral(expr)) + +struct BidirectionalTypePusher +{ + + NotNull> astTypes; + NotNull> astExpectedTypes; + + NotNull builtinTypes; + NotNull arena; + NotNull unifier; + NotNull subtyping; + + std::vector incompleteInferences; + + BidirectionalTypePusher( + NotNull> astTypes, + NotNull> astExpectedTypes, + NotNull builtinTypes, + NotNull arena, + NotNull unifier, + NotNull subtyping + ) + : astTypes{astTypes} + , astExpectedTypes{astExpectedTypes} + , builtinTypes{builtinTypes} + , arena{arena} + , unifier{unifier} + , subtyping{subtyping} { - auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); - return result.isSubtype ? expectedType : exprType; } - expectedType = follow(expectedType); - exprType = follow(exprType); - + TypeId pushType(TypeId expectedType, const AstExpr* expr) + { + if (!astTypes->contains(expr)) + { + LUAU_ASSERT(false); + return builtinTypes->errorType; + } - // The intent of `matchLiteralType` is to upcast values when it's safe - // to do so. it's always safe to upcast to `any` or `unknown`, so we - // can unconditionally do so here. - if (is(expectedType)) - return expectedType; + TypeId exprType = *astTypes->find(expr); + expectedType = follow(expectedType); + exprType = follow(exprType); + // NOTE: We cannot block on free types here, as that trivially means + // any recursive function would have a cycle, consider: + // + // local function fact(n) + // return if n < 2 then 1 else n * fact(n - 1) + // end + // + // We'll have a cycle between trying to push `fact`'s type into its + // arguments and generalizing `fact`. - if (expr->is()) - { - auto ft = get(exprType); - if (ft && get(ft->lowerBound) && fastIsSubtype(builtinTypes->stringType, ft->upperBound) && - fastIsSubtype(ft->lowerBound, builtinTypes->stringType)) + if (FFlag::LuauEagerGeneralization4) { - // 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) + if (auto tfit = get(expectedType); tfit && tfit->state == TypeFunctionInstanceState::Unsolved) { - emplaceType(asMutable(exprType), expectedType); + incompleteInferences.push_back(IncompleteInference{expectedType, exprType, expr}); return exprType; } - // likewise, if the lower bound is a subtype, we can force the expected type in - // if this is the case and the previous relation failed, it means that the primitive type - // constraint was going to have to select the lower bound for this type anyway. - Relation lowerBoundRelation = relate(ft->lowerBound, expectedType); - if (lowerBoundRelation == Relation::Subset || lowerBoundRelation == Relation::Coincident) + if (is(expectedType)) { - emplaceType(asMutable(exprType), expectedType); + incompleteInferences.push_back(IncompleteInference{expectedType, exprType, expr}); return exprType; } } - } - else if (expr->is()) - { - auto ft = get(exprType); - if (ft && get(ft->lowerBound) && fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && - fastIsSubtype(ft->lowerBound, builtinTypes->booleanType)) + else { - // 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) + if (is(expectedType)) { - emplaceType(asMutable(exprType), expectedType); - return exprType; - } - - // likewise, if the lower bound is a subtype, we can force the expected type in - // if this is the case and the previous relation failed, it means that the primitive type - // constraint was going to have to select the lower bound for this type anyway. - Relation lowerBoundRelation = relate(ft->lowerBound, expectedType); - if (lowerBoundRelation == Relation::Subset || lowerBoundRelation == Relation::Coincident) - { - emplaceType(asMutable(exprType), expectedType); + incompleteInferences.push_back(IncompleteInference{expectedType, exprType, expr}); return exprType; } } - } - if (expr->is() || expr->is() || expr->is() || expr->is()) - { - if (auto ft = get(exprType); ft && fastIsSubtype(ft->upperBound, expectedType)) - { - emplaceType(asMutable(exprType), expectedType); + if (is(expectedType)) return exprType; - } - - Relation r = relate(exprType, expectedType); - if (r == Relation::Coincident || r == Relation::Subset) - return expectedType; - - return exprType; - } + (*astExpectedTypes)[expr] = expectedType; - if (expr->is()) - { - // TODO: Push argument / return types into the lambda. For now, just do - // the non-literal thing: check for a subtype and upcast if valid. - auto result = subtyping->isSubtype(/*subTy=*/exprType, /*superTy=*/expectedType, unifier->scope); - return result.isSubtype ? expectedType : exprType; - } - - if (auto exprTable = expr->as()) - { - TableType* const tableTy = getMutable(exprType); - - // This can occur if we have an expression like: - // - // { x = {}, x = 42 } - // - // The type of this will be `{ x: number }` - if (!tableTy) + if (auto group = expr->as()) + { + pushType(expectedType, group->expr); return exprType; + } - LUAU_ASSERT(tableTy); - - const TableType* expectedTableTy = get(expectedType); - - if (!expectedTableTy) + if (auto ternary = expr->as()) { - if (auto utv = get(expectedType)) - { - std::vector parts{begin(utv), end(utv)}; - - std::optional tt = extractMatchingTableType(parts, exprType, builtinTypes); - - if (tt) - { - TypeId res = matchLiteralType(astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *tt, exprType, expr, toBlock); - parts.push_back(res); - return arena->addType(UnionType{std::move(parts)}); - } - } - + pushType(expectedType, ternary->trueExpr); + pushType(expectedType, ternary->falseExpr); return exprType; } - DenseHashSet keysToDelete{nullptr}; - - DenseHashSet indexerKeyTypes{nullptr}; - DenseHashSet indexerValueTypes{nullptr}; + if (!isLiteral(expr)) + // NOTE: For now we aren't using the result of this function, so + // just return the original expression type. + return exprType; - for (const AstExprTable::Item& item : exprTable->items) + if (expr->is()) { - if (isRecord(item)) + auto ft = get(exprType); + if (ft && get(ft->lowerBound) && fastIsSubtype(builtinTypes->stringType, ft->upperBound) && + fastIsSubtype(ft->lowerBound, builtinTypes->stringType)) { - const AstArray& s = item.key->as()->value; - std::string keyStr{s.data, s.data + s.size}; - auto it = tableTy->props.find(keyStr); - - // This can occur, potentially, if we are re-entrant. - if (it == tableTy->props.end()) - continue; - - LUAU_ASSERT(it != tableTy->props.end()); - - Property& prop = it->second; - - // If the property is write-only, do nothing. - if (prop.isWriteOnly()) - continue; - - TypeId propTy = *prop.readTy; - - auto it2 = expectedTableTy->props.find(keyStr); - - if (it2 == expectedTableTy->props.end()) + // 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) { - // expectedType may instead have an indexer. This is - // kind of interesting because it means we clip the prop - // from the exprType and fold it into the indexer. - if (expectedTableTy->indexer && isString(expectedTableTy->indexer->indexType)) - { - (*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType; - (*astExpectedTypes)[item.value] = expectedTableTy->indexer->indexResultType; - - TypeId matchedType = matchLiteralType( - astTypes, - astExpectedTypes, - builtinTypes, - arena, - unifier, - subtyping, - expectedTableTy->indexer->indexResultType, - propTy, - item.value, - toBlock - ); - - indexerKeyTypes.insert(arena->addType(SingletonType{StringSingleton{keyStr}})); - indexerValueTypes.insert(matchedType); - - keysToDelete.insert(item.key->as()); - } - - // If it's just an extra property and the expected type - // has no indexer, there's no work to do here. - - continue; + emplaceType(asMutable(exprType), expectedType); + return exprType; } - LUAU_ASSERT(it2 != expectedTableTy->props.end()); - - const Property& expectedProp = it2->second; - - std::optional expectedReadTy = expectedProp.readTy; - std::optional expectedWriteTy = expectedProp.writeTy; - - TypeId matchedType = nullptr; - - // Important optimization: If we traverse into the read and - // write types separately even when they are shared, we go - // quadratic in a hurry. - if (expectedProp.isShared()) - { - matchedType = matchLiteralType( - astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock - ); - prop.readTy = matchedType; - prop.writeTy = matchedType; - } - else if (expectedReadTy) - { - matchedType = matchLiteralType( - astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedReadTy, propTy, item.value, toBlock - ); - prop.readTy = matchedType; - prop.writeTy.reset(); - } - else if (expectedWriteTy) - { - matchedType = matchLiteralType( - astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping, *expectedWriteTy, propTy, item.value, toBlock - ); - prop.readTy.reset(); - prop.writeTy = matchedType; - } - else + // likewise, if the lower bound is a subtype, we can force the expected type in + // if this is the case and the previous relation failed, it means that the primitive type + // constraint was going to have to select the lower bound for this type anyway. + Relation lowerBoundRelation = relate(ft->lowerBound, expectedType); + if (lowerBoundRelation == Relation::Subset || lowerBoundRelation == Relation::Coincident) { - // Also important: It is presently the case that all - // table properties are either read-only, or have the - // same read and write types. - LUAU_ASSERT(!"Should be unreachable"); + emplaceType(asMutable(exprType), expectedType); + return exprType; } - - LUAU_ASSERT(prop.readTy || prop.writeTy); - - LUAU_ASSERT(matchedType); - - (*astExpectedTypes)[item.value] = matchedType; - // NOTE: We do *not* add to the potential indexer types here. - // I think this is correct to support something like: - // - // { [string]: number, foo: boolean } - // } - else if (item.kind == AstExprTable::Item::List) + } + else if (expr->is()) + { + auto ft = get(exprType); + if (ft && get(ft->lowerBound) && fastIsSubtype(builtinTypes->booleanType, ft->upperBound) && + fastIsSubtype(ft->lowerBound, builtinTypes->booleanType)) { - if (expectedTableTy->indexer) + // 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) { - const TypeId* propTy = astTypes->find(item.value); - LUAU_ASSERT(propTy); - - unifier->unify(expectedTableTy->indexer->indexType, builtinTypes->numberType); - TypeId matchedType = matchLiteralType( - astTypes, - astExpectedTypes, - builtinTypes, - arena, - unifier, - subtyping, - expectedTableTy->indexer->indexResultType, - *propTy, - item.value, - toBlock - ); - - indexerKeyTypes.insert(builtinTypes->numberType); - indexerValueTypes.insert(matchedType); + emplaceType(asMutable(exprType), expectedType); + return exprType; } - } - else if (item.kind == AstExprTable::Item::General) - { - // We have { ..., [blocked] : somePropExpr, ...} - // If blocked resolves to a string, we will then take care of this above - // If it resolves to some other kind of expression, we don't have a way of folding this information into indexer - // because there is no named prop to remove - // We should just block here - const TypeId* keyTy = astTypes->find(item.key); - LUAU_ASSERT(keyTy); - TypeId tKey = follow(*keyTy); - LUAU_ASSERT(!is(tKey)); - const TypeId* propTy = astTypes->find(item.value); - LUAU_ASSERT(propTy); - TypeId tProp = follow(*propTy); - LUAU_ASSERT(!is(tProp)); - // Populate expected types for non-string keys declared with [] (the code below will handle the case where they are strings) - if (!item.key->as() && expectedTableTy->indexer) - (*astExpectedTypes)[item.key] = expectedTableTy->indexer->indexType; - - indexerKeyTypes.insert(tKey); - indexerValueTypes.insert(tProp); + // likewise, if the lower bound is a subtype, we can force the expected type in + // if this is the case and the previous relation failed, it means that the primitive type + // constraint was going to have to select the lower bound for this type anyway. + Relation lowerBoundRelation = relate(ft->lowerBound, expectedType); + if (lowerBoundRelation == Relation::Subset || lowerBoundRelation == Relation::Coincident) + { + emplaceType(asMutable(exprType), expectedType); + return exprType; + } } - else - LUAU_ASSERT(!"Unexpected"); } - for (const auto& key : keysToDelete) + if (expr->is() || expr->is() || expr->is() || + expr->is()) { - const AstArray& s = key->value; - std::string keyStr{s.data, s.data + s.size}; - tableTy->props.erase(keyStr); + if (auto ft = get(exprType); ft && fastIsSubtype(ft->upperBound, expectedType)) + { + emplaceType(asMutable(exprType), expectedType); + return exprType; + } + + Relation r = relate(exprType, expectedType); + if (r == Relation::Coincident || r == Relation::Subset) + return expectedType; + + return exprType; } - // Keys that the expectedType says we should have, but that aren't - // specified by the AST fragment. - // - // If any such keys are options, then we'll add them to the expression - // type. - // - // We use std::optional here because the empty string is a - // perfectly reasonable value to insert into the set. We'll use - // std::nullopt as our sentinel value. - Set> missingKeys{{}}; - for (const auto& [name, _] : expectedTableTy->props) - missingKeys.insert(name); - - for (const AstExprTable::Item& item : exprTable->items) + + if (expr->is()) + // TODO: Push argument / return types into the lambda. + return exprType; + + if (auto exprTable = expr->as()) { - if (item.key) + const TableType* expectedTableTy = get(expectedType); + + if (!expectedTableTy) { - if (const auto str = item.key->as()) + if (auto utv = get(expectedType)) { - missingKeys.erase(std::string(str->value.data, str->value.size)); + std::vector parts{begin(utv), end(utv)}; + + std::optional tt = extractMatchingTableType(parts, exprType, builtinTypes); + + if (tt) + (void)pushType(*tt, expr); } - } - } - for (const auto& key : missingKeys) - { - LUAU_ASSERT(key.has_value()); + return exprType; + } - auto it = expectedTableTy->props.find(*key); - LUAU_ASSERT(it != expectedTableTy->props.end()); + for (const AstExprTable::Item& item : exprTable->items) + { + if (isRecord(item)) + { + const AstArray& s = item.key->as()->value; + std::string keyStr{s.data, s.data + s.size}; + auto it = expectedTableTy->props.find(keyStr); - const Property& expectedProp = it->second; + if (it == expectedTableTy->props.end()) + { + // If we have some type: + // + // { [string]: T } + // + // ... that we're trying to push into ... + // + // { foo = bar } + // + // Then the intent is probably to push `T` into `bar`. + if (expectedTableTy->indexer && fastIsSubtype(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. + continue; + } - Property exprProp; + LUAU_ASSERT(it != expectedTableTy->props.end()); - if (expectedProp.readTy && isOptional(*expectedProp.readTy)) - exprProp.readTy = *expectedProp.readTy; - if (expectedProp.writeTy && isOptional(*expectedProp.writeTy)) - exprProp.writeTy = *expectedProp.writeTy; + const Property& expectedProp = it->second; - // If the property isn't actually optional, do nothing. - if (exprProp.readTy || exprProp.writeTy) - tableTy->props[*key] = std::move(exprProp); - } + if (expectedProp.readTy) + (void)pushType(*expectedProp.readTy, item.value); - // If the expected table has an indexer, then the provided table can - // have one too. - // TODO: If the expected table also has an indexer, we might want to - // push the expected indexer's types into it. - if (expectedTableTy->indexer) - { - if (indexerValueTypes.size() > 0 && indexerKeyTypes.size() > 0) - { - TypeId inferredKeyType = builtinTypes->neverType; - TypeId inferredValueType = builtinTypes->neverType; - for (auto kt : indexerKeyTypes) + // NOTE: We do *not* add to the potential indexer types here. + // I think this is correct to support something like: + // + // { [string]: number, foo: boolean } + // + // NOTE: We also do nothing for write properties. + } + else if (item.kind == AstExprTable::Item::List) { - auto simplified = simplifyUnion(builtinTypes, arena, inferredKeyType, kt); - inferredKeyType = simplified.result; + if (expectedTableTy->indexer) + { + unifier->unify(expectedTableTy->indexer->indexType, builtinTypes->numberType); + (void)pushType(expectedTableTy->indexer->indexResultType, item.value); + } } - for (auto vt : indexerValueTypes) + else if (item.kind == AstExprTable::Item::General) { - auto simplified = simplifyUnion(builtinTypes, arena, inferredValueType, vt); - inferredValueType = simplified.result; + + // We have { ..., [blocked] : somePropExpr, ...} + // If blocked resolves to a string, we will then take care of this above + // If it resolves to some other kind of expression, we don't have a way of folding this information into indexer + // because there is no named prop to remove + // We should just block here + if (expectedTableTy->indexer) + { + (void)pushType(expectedTableTy->indexer->indexType, item.key); + (void)pushType(expectedTableTy->indexer->indexResultType, item.value); + } } - tableTy->indexer = TableIndexer{inferredKeyType, inferredValueType}; - auto keyCheck = subtyping->isSubtype(inferredKeyType, expectedTableTy->indexer->indexType, unifier->scope); - if (keyCheck.isSubtype) - tableTy->indexer->indexType = expectedTableTy->indexer->indexType; - auto valueCheck = subtyping->isSubtype(inferredValueType, expectedTableTy->indexer->indexResultType, unifier->scope); - if (valueCheck.isSubtype) - tableTy->indexer->indexResultType = expectedTableTy->indexer->indexResultType; - } - else - LUAU_ASSERT(indexerKeyTypes.empty() && indexerValueTypes.empty()); - } - else - { - if (expectedTableTy->indexer && !tableTy->indexer) - { - tableTy->indexer = expectedTableTy->indexer; + else + LUAU_ASSERT(!"Unexpected"); } } + + return exprType; } +}; +} // namespace - return exprType; +PushTypeResult pushTypeInto( + NotNull> astTypes, + NotNull> astExpectedTypes, + NotNull builtinTypes, + NotNull arena, + NotNull unifier, + NotNull subtyping, + TypeId expectedType, + const AstExpr* expr +) +{ + BidirectionalTypePusher btp{astTypes, astExpectedTypes, builtinTypes, arena, unifier, subtyping}; + (void)btp.pushType(expectedType, expr); + return {std::move(btp.incompleteInferences)}; } } // namespace Luau diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 2c2a4503..2054b18c 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -2036,12 +2036,12 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts) } else if constexpr (std::is_same_v) return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType); - else if constexpr (std::is_same_v) - return "table_check " + tos(c.expectedType) + " :> " + tos(c.exprType); else if constexpr (std::is_same_v) return "simplify " + tos(c.ty); else if constexpr (std::is_same_v) return "push_function_type " + tos(c.expectedFunctionType) + " => " + tos(c.functionType); + else if constexpr (std::is_same_v) + return "push_type " + tos(c.expectedType) + " => " + tos(c.targetType); else static_assert(always_false_v, "Non-exhaustive constraint switch"); }; diff --git a/Analysis/src/TypeArena.cpp b/Analysis/src/TypeArena.cpp index cc0b63d5..3d6324a5 100644 --- a/Analysis/src/TypeArena.cpp +++ b/Analysis/src/TypeArena.cpp @@ -3,7 +3,6 @@ #include "Luau/TypeArena.h" LUAU_FASTFLAGVARIABLE(DebugLuauFreezeArena); -LUAU_FASTFLAG(LuauTrackTypeAllocations) namespace Luau { @@ -117,8 +116,6 @@ TypePackId TypeArena::addTypePackFunction(const TypePackFunction& function, std: void TypeArena::recordSingletonStats(const NotNull singleton) { - LUAU_ASSERT(FFlag::LuauTrackTypeAllocations); - auto record = [this](auto&& s) { using T = std::decay_t; diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index f4a1d3c1..907d04cb 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -28,6 +28,8 @@ #include #include +#include "Luau/Simplify.h" + LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) @@ -35,12 +37,12 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAGVARIABLE(LuauIceLess) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAGVARIABLE(LuauAllowMixedTables) +LUAU_FASTFLAGVARIABLE(LuauSimplifyIntersectionForLiteralSubtypeCheck) namespace Luau { @@ -1734,11 +1736,10 @@ void TypeChecker2::visitCall(AstExprCall* call) void TypeChecker2::visit(AstExprCall* call) { - std::optional flipper; - if (FFlag::LuauResetConditionalContextProperly) - flipper.emplace(&typeContext, TypeContext::Default); - visit(call->func, ValueContext::RValue); - flipper.reset(); + { + InConditionalContext flipper(&typeContext, TypeContext::Default); + visit(call->func, ValueContext::RValue); + } for (AstExpr* arg : call->args) visit(arg, ValueContext::RValue); @@ -1898,9 +1899,7 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context) void TypeChecker2::visit(AstExprFunction* fn) { - std::optional flipper; - if (FFlag::LuauResetConditionalContextProperly) - flipper.emplace(&typeContext, TypeContext::Default); + InConditionalContext flipper(&typeContext, TypeContext::Default); auto StackPusher = pushStack(fn); @@ -2055,9 +2054,7 @@ void TypeChecker2::visit(AstExprFunction* fn) void TypeChecker2::visit(AstExprTable* expr) { - std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - inContext.emplace(&typeContext, TypeContext::Default); + InConditionalContext inContext(&typeContext, TypeContext::Default); for (const AstExprTable::Item& item : expr->items) { @@ -2070,7 +2067,7 @@ void TypeChecker2::visit(AstExprTable* expr) void TypeChecker2::visit(AstExprUnary* expr) { std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly && expr->op != AstExprUnary::Op::Not) + if (expr->op != AstExprUnary::Op::Not) inContext.emplace(&typeContext, TypeContext::Default); visit(expr->expr, ValueContext::RValue); @@ -2165,11 +2162,8 @@ void TypeChecker2::visit(AstExprUnary* expr) TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) { std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - { - if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or && expr->op != AstExprBinary::CompareEq && expr->op != AstExprBinary::CompareNe) - inContext.emplace(&typeContext, TypeContext::Default); - } + if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or && expr->op != AstExprBinary::CompareEq && expr->op != AstExprBinary::CompareNe) + inContext.emplace(&typeContext, TypeContext::Default); visit(expr->left, ValueContext::RValue); visit(expr->right, ValueContext::RValue); @@ -2550,9 +2544,7 @@ void TypeChecker2::visit(AstExprTypeAssertion* expr) void TypeChecker2::visit(AstExprIfElse* expr) { - std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - inContext.emplace(&typeContext, TypeContext::Default); + InConditionalContext inContext(&typeContext, TypeContext::Default); visit(expr->condition, ValueContext::RValue); visit(expr->trueExpr, ValueContext::RValue); @@ -2561,9 +2553,7 @@ void TypeChecker2::visit(AstExprIfElse* expr) void TypeChecker2::visit(AstExprInterpString* interpString) { - std::optional inContext; - if (FFlag::LuauResetConditionalContextProperly) - inContext.emplace(&typeContext, TypeContext::Default); + InConditionalContext inContext(&typeContext, TypeContext::Default); for (AstExpr* expr : interpString->expressions) visit(expr, ValueContext::RValue); @@ -3114,6 +3104,19 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT return testPotentialLiteralIsSubtype(expr, *tt); } + if (FFlag::LuauSimplifyIntersectionForLiteralSubtypeCheck) + { + if (auto itv = get(expectedType)) + { + // 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); + } + } + return testIsSubtype(exprType, expectedType, expr->location); } diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 53233dcb..14b87a59 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauVariadicAnyPackShouldBeErrorSuppressing) +LUAU_FASTFLAG(LuauPushTypeConstraint) namespace Luau { @@ -598,14 +599,6 @@ class BlockedTypeInLiteralVisitor : public AstVisitor NotNull> toBlock_; }; -std::vector findBlockedTypesIn(AstExprTable* expr, NotNull> astTypes) -{ - std::vector toBlock; - BlockedTypeInLiteralVisitor v{astTypes, NotNull{&toBlock}}; - expr->visit(&v); - return toBlock; -} - std::vector findBlockedArgTypesIn(AstExprCall* expr, NotNull> astTypes) { std::vector toBlock; @@ -723,6 +716,9 @@ std::optional extractMatchingTableType(std::vector& tables, Type return ty; } } + + if (FFlag::LuauPushTypeConstraint && fastIsSubtype(propType, expectedType)) + return ty; } } } diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index da070a6b..3e72b9ef 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -347,6 +347,7 @@ class Parser // check that parser is at lexeme/symbol, move to next lexeme/symbol on success, report failure and continue on failure bool expectAndConsume(char value, const char* context = nullptr); bool expectAndConsume(Lexeme::Type type, const char* context = nullptr); + bool expectAndConsumeFailWithLookahead(Lexeme::Type type, const char* context); void expectAndConsumeFail(Lexeme::Type type, const char* context); struct MatchLexeme @@ -366,7 +367,7 @@ class Parser bool expectMatchAndConsumeRecover(char value, const MatchLexeme& begin, bool searchForMissing); bool expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begin); - void expectMatchEndAndConsumeFail(Lexeme::Type type, const MatchLexeme& begin); + bool expectMatchEndAndConsumeFailWithLookahead(Lexeme::Type type, const MatchLexeme& begin); template AstArray copy(const T* data, std::size_t size); diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 6f42f28c..b29ffeaf 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -64,18 +64,24 @@ AstAttr::DeprecatedInfo AstAttr::deprecatedInfo() const AstAttr::DeprecatedInfo info; info.deprecated = type == AstAttr::Type::Deprecated; - if (info.deprecated && args.size > 0) + if (info.deprecated && args.size > 0 && args.data[0]->is()) { AstExprTable* table = args.data[0]->as(); if (auto useValue = table->getRecord("use")) { - AstArray use = (*useValue)->as()->value; - info.use = {{use.data, use.size}}; + if ((*useValue)->is()) + { + AstArray use = (*useValue)->as()->value; + info.use = {{use.data, use.size}}; + } } if (auto reasonValue = table->getRecord("reason")) { - AstArray reason = (*reasonValue)->as()->value; - info.reason = {{reason.data, reason.size}}; + if ((*reasonValue)->is()) + { + AstArray reason = (*reasonValue)->as()->value; + info.reason = {{reason.data, reason.size}}; + } } } diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 8ace61b5..28adc45c 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -4092,28 +4092,29 @@ bool Parser::expectAndConsume(char value, const char* context) bool Parser::expectAndConsume(Lexeme::Type type, const char* context) { if (lexer.current().type != type) - { - expectAndConsumeFail(type, context); + return expectAndConsumeFailWithLookahead(type, context); - // check if this is an extra token and the expected token is next - if (lexer.lookahead().type == type) - { - // skip invalid and consume expected - nextLexeme(); - nextLexeme(); - } + nextLexeme(); + return true; +} - return false; - } - else +// LUAU_NOINLINE is used to limit the stack cost due to std::string objects, and to increase caller performance since this code is cold +LUAU_NOINLINE bool Parser::expectAndConsumeFailWithLookahead(Lexeme::Type type, const char* context) +{ + expectAndConsumeFail(type, context); + + // check if this is an extra token and the expected token is next + if (lexer.lookahead().type == type) { + // skip invalid and consume expected + nextLexeme(); nextLexeme(); - return true; } + + return false; } -// LUAU_NOINLINE is used to limit the stack cost of this function due to std::string objects, and to increase caller performance since this code is -// cold +// LUAU_NOINLINE is used to limit the stack cost due to std::string objects, and to increase caller performance since this code is cold LUAU_NOINLINE void Parser::expectAndConsumeFail(Lexeme::Type type, const char* context) { std::string typeString = Lexeme(Location(Position(0, 0), 0), type).toString(); @@ -4143,6 +4144,7 @@ bool Parser::expectMatchAndConsume(char value, const MatchLexeme& begin, bool se } } +// LUAU_NOINLINE is used to limit the stack cost due to std::string objects, and to increase caller performance since this code is cold LUAU_NOINLINE bool Parser::expectMatchAndConsumeRecover(char value, const MatchLexeme& begin, bool searchForMissing) { Lexeme::Type type = static_cast(static_cast(value)); @@ -4185,8 +4187,7 @@ LUAU_NOINLINE bool Parser::expectMatchAndConsumeRecover(char value, const MatchL return false; } -// LUAU_NOINLINE is used to limit the stack cost of this function due to std::string objects, and to increase caller performance since this code is -// cold +// LUAU_NOINLINE is used to limit the stack cost due to std::string objects, and to increase caller performance since this code is cold LUAU_NOINLINE void Parser::expectMatchAndConsumeFail(Lexeme::Type type, const MatchLexeme& begin, const char* extra) { std::string typeString = Lexeme(Location(Position(0, 0), 0), type).toString(); @@ -4217,40 +4218,23 @@ LUAU_NOINLINE void Parser::expectMatchAndConsumeFail(Lexeme::Type type, const Ma bool Parser::expectMatchEndAndConsume(Lexeme::Type type, const MatchLexeme& begin) { if (lexer.current().type != type) - { - expectMatchEndAndConsumeFail(type, begin); + return expectMatchEndAndConsumeFailWithLookahead(type, begin); - // check if this is an extra token and the expected token is next - if (lexer.lookahead().type == type) - { - // skip invalid and consume expected - nextLexeme(); - nextLexeme(); - - return true; - } - - return false; - } - else + // If the token matches on a different line and a different column, it suggests misleading indentation + // This can be used to pinpoint the problem location for a possible future *actual* mismatch + if (lexer.current().location.begin.line != begin.position.line && lexer.current().location.begin.column != begin.position.column && + endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects { - // If the token matches on a different line and a different column, it suggests misleading indentation - // This can be used to pinpoint the problem location for a possible future *actual* mismatch - if (lexer.current().location.begin.line != begin.position.line && lexer.current().location.begin.column != begin.position.column && - endMismatchSuspect.position.line < begin.position.line) // Only replace the previous suspect with more recent suspects - { - endMismatchSuspect = begin; - } + endMismatchSuspect = begin; + } - nextLexeme(); + nextLexeme(); - return true; - } + return true; } -// LUAU_NOINLINE is used to limit the stack cost of this function due to std::string objects, and to increase caller performance since this code is -// cold -LUAU_NOINLINE void Parser::expectMatchEndAndConsumeFail(Lexeme::Type type, const MatchLexeme& begin) +// LUAU_NOINLINE is used to limit the stack cost due to std::string objects, and to increase caller performance since this code is cold +LUAU_NOINLINE bool Parser::expectMatchEndAndConsumeFailWithLookahead(Lexeme::Type type, const MatchLexeme& begin) { if (endMismatchSuspect.type != Lexeme::Eof && endMismatchSuspect.position.line > begin.position.line) { @@ -4263,6 +4247,18 @@ LUAU_NOINLINE void Parser::expectMatchEndAndConsumeFail(Lexeme::Type type, const { expectMatchAndConsumeFail(type, begin); } + + // check if this is an extra token and the expected token is next + if (lexer.lookahead().type == type) + { + // skip invalid and consume expected + nextLexeme(); + nextLexeme(); + + return true; + } + + return false; } template diff --git a/CodeGen/include/Luau/AssemblyBuilderA64.h b/CodeGen/include/Luau/AssemblyBuilderA64.h index 9d337942..9b7018c7 100644 --- a/CodeGen/include/Luau/AssemblyBuilderA64.h +++ b/CodeGen/include/Luau/AssemblyBuilderA64.h @@ -145,6 +145,9 @@ class AssemblyBuilderA64 void ins_4s(RegisterA64 dst, uint8_t dstIndex, RegisterA64 src, uint8_t srcIndex); void dup_4s(RegisterA64 dst, RegisterA64 src, uint8_t index); + void fcmeq_4s(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2); + void bit(RegisterA64 dst, RegisterA64 src, RegisterA64 mask); + // Floating-point rounding and conversions void frinta(RegisterA64 dst, RegisterA64 src); void frintm(RegisterA64 dst, RegisterA64 src); diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index ca5fa7a9..422cbffa 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -162,8 +162,11 @@ class AssemblyBuilderX64 void vcmpeqsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); void vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vcmpeqps(OperandX64 dst, OperandX64 src1, OperandX64 src2); - void vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3); + void vblendvps(RegisterX64 dst, RegisterX64 src1, RegisterX64 src2, OperandX64 mask); + void vblendvpd(RegisterX64 dst, RegisterX64 src1, RegisterX64 src2, OperandX64 mask); + void vblendvpd_DEPRECATED(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3); void vpshufps(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t shuffle); void vpinsrd(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t offset); diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 4919f3b9..cbc1c41f 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -192,6 +192,11 @@ enum class IrCmd : uint8_t // C, D: double (condition arguments) SELECT_NUM, + // For each lane in the vector, select B if C == D, otherwise select A + // A, B: TValue (endpoints) + // C, D: TValue (condition arguments) + SELECT_VEC, + // Add/Sub/Mul/Div/Idiv two vectors // A, B: TValue ADD_VEC, diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index 23384e57..77c35d54 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -770,6 +770,32 @@ void AssemblyBuilderA64::dup_4s(RegisterA64 dst, RegisterA64 src, uint8_t index) commit(); } +void AssemblyBuilderA64::fcmeq_4s(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2) +{ + if (logText) + logAppend(" %-12sv%d.4s,v%d.4s,v%d.4s\n", "fcmeq", dst.index, src1.index, src2.index); + + // Q U ESz Rm Opcode Rn Rd + uint32_t op = 0b0'1'0'01110001'00000'111001'00000'00000; + + place(dst.index | (src1.index << 5) | (src2.index << 16) | op); + + commit(); +} + +void AssemblyBuilderA64::bit(RegisterA64 dst, RegisterA64 src, RegisterA64 mask) +{ + if (logText) + logAppend(" %-12sv%d.16b,v%d.16b,v%d.16b\n", "bit", dst.index, src.index, mask.index); + + // Q U Rm Opcode Rn Rd + uint32_t op = 0b0'1'1'01110101'00000'000111'00000'00000; + + place(dst.index | (src.index << 5) | (mask.index << 16) | op); + + commit(); +} + void AssemblyBuilderA64::frinta(RegisterA64 dst, RegisterA64 src) { CODEGEN_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d); diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index 80982fa5..f37037a6 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -940,12 +940,29 @@ void AssemblyBuilderX64::vcmpltsd(OperandX64 dst, OperandX64 src1, OperandX64 sr placeAvx("vcmpltsd", dst, src1, src2, 0x01, 0xc2, false, AVX_0F, AVX_F2); } -void AssemblyBuilderX64::vblendvpd(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3) +void AssemblyBuilderX64::vcmpeqps(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vcmpeqps", dst, src1, src2, 0x00, 0xc2, false, AVX_0F, AVX_NP); +} + +void AssemblyBuilderX64::vblendvps(RegisterX64 dst, RegisterX64 src1, RegisterX64 src2, OperandX64 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); +} + +void AssemblyBuilderX64::vblendvpd_DEPRECATED(RegisterX64 dst, RegisterX64 src1, OperandX64 mask, RegisterX64 src3) { // bits [7:4] of imm8 are used to select register for operand 4 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) +{ + // 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); +} + void AssemblyBuilderX64::vpshufps(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t shuffle) { placeAvx("vpshufps", dst, src1, src2, shuffle, 0xc6, false, AVX_0F, AVX_NP); diff --git a/CodeGen/src/BytecodeAnalysis.cpp b/CodeGen/src/BytecodeAnalysis.cpp index e0ed512e..c0fee693 100644 --- a/CodeGen/src/BytecodeAnalysis.cpp +++ b/CodeGen/src/BytecodeAnalysis.cpp @@ -10,6 +10,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCodeGenBetterBytecodeAnalysis) + namespace Luau { namespace CodeGen @@ -689,6 +691,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) for (int i = proto->numparams; i < proto->maxstacksize; ++i) regTags[i] = LBC_TYPE_ANY; + // Namecall instruction has a hook which specifies the result of the next call instruction LuauBytecodeType knownNextCallResult = LBC_TYPE_ANY; for (int i = block.startpc; i <= block.finishpc;) @@ -771,10 +774,26 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) } case LOP_GETTABLE: { - int rb = LUAU_INSN_B(*pc); - int rc = LUAU_INSN_C(*pc); - bcType.a = regTags[rb]; - bcType.b = regTags[rc]; + if (FFlag::LuauCodeGenBetterBytecodeAnalysis) + { + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + int rc = LUAU_INSN_C(*pc); + + regTags[ra] = LBC_TYPE_ANY; + + 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]; + } break; } case LOP_SETTABLE: @@ -825,14 +844,38 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) case LOP_SETTABLEKS: { int rb = LUAU_INSN_B(*pc); + bcType.a = regTags[rb]; bcType.b = LBC_TYPE_STRING; break; } case LOP_GETTABLEN: + { + if (FFlag::LuauCodeGenBetterBytecodeAnalysis) + { + int ra = LUAU_INSN_A(*pc); + int rb = LUAU_INSN_B(*pc); + + regTags[ra] = LBC_TYPE_ANY; + + 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; + } + break; + } case LOP_SETTABLEN: { int rb = LUAU_INSN_B(*pc); + bcType.a = regTags[rb]; bcType.b = LBC_TYPE_NUMBER; break; @@ -1106,12 +1149,19 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) int ra = LUAU_INSN_A(call); applyBuiltinCall(LuauBuiltinFunction(bfid), bcType); + regTags[ra + 1] = bcType.a; regTags[ra + 2] = bcType.b; regTags[ra + 3] = bcType.c; regTags[ra] = bcType.result; refineRegType(bcTypeInfo, ra, i, bcType.result); + + if (FFlag::LuauCodeGenBetterBytecodeAnalysis) + { + // Fastcall failure fallback is skipped from result propagation + i += skip; + } break; } case LOP_FASTCALL1: @@ -1130,6 +1180,12 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) regTags[ra] = bcType.result; refineRegType(bcTypeInfo, ra, i, bcType.result); + + if (FFlag::LuauCodeGenBetterBytecodeAnalysis) + { + // Fastcall failure fallback is skipped from result propagation + i += skip; + } break; } case LOP_FASTCALL2: @@ -1148,6 +1204,12 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) regTags[ra] = bcType.result; refineRegType(bcTypeInfo, ra, i, bcType.result); + + if (FFlag::LuauCodeGenBetterBytecodeAnalysis) + { + // Fastcall failure fallback is skipped from result propagation + i += skip; + } break; } case LOP_FASTCALL3: @@ -1168,6 +1230,12 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) regTags[ra] = bcType.result; refineRegType(bcTypeInfo, ra, i, bcType.result); + + if (FFlag::LuauCodeGenBetterBytecodeAnalysis) + { + // Fastcall failure fallback is skipped from result propagation + i += skip; + } break; } case LOP_FORNPREP: @@ -1278,6 +1346,16 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) break; } case LOP_GETGLOBAL: + { + if (FFlag::LuauCodeGenBetterBytecodeAnalysis) + { + int ra = LUAU_INSN_A(*pc); + + regTags[ra] = LBC_TYPE_ANY; + bcType.result = regTags[ra]; + } + break; + } case LOP_SETGLOBAL: case LOP_RETURN: case LOP_JUMP: @@ -1300,12 +1378,54 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks) case LOP_FORGLOOP: case LOP_FORGPREP_NEXT: case LOP_FORGPREP_INEXT: + break; case LOP_AND: - case LOP_ANDK: case LOP_OR: + { + if (FFlag::LuauCodeGenBetterBytecodeAnalysis) + { + 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]; + + 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); + + bcType.a = regTags[rb]; + bcType.b = getBytecodeConstantTag(proto, kc); + + 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); + + regTags[ra] = LBC_TYPE_ANY; + bcType.result = regTags[ra]; + } + break; + } case LOP_CAPTURE: case LOP_PREPVARARGS: case LOP_GETVARARGS: diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 4544c218..698f145d 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -169,6 +169,8 @@ const char* getCmdName(IrCmd cmd) return "SIGN_NUM"; case IrCmd::SELECT_NUM: return "SELECT_NUM"; + case IrCmd::SELECT_VEC: + return "SELECT_VEC"; case IrCmd::ADD_VEC: return "ADD_VEC"; case IrCmd::SUB_VEC: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 8941d5e6..8ae40af0 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -716,6 +716,26 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.fcsel(inst.regA64, temp2, temp1, getConditionFP(IrCondition::Equal)); break; } + case IrCmd::SELECT_VEC: + { + // `inst.b` cannot be reused for return value, because it can be overwritten with A before the first usage + inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.c, inst.d}); + + RegisterA64 temp1 = regOp(inst.a); + RegisterA64 temp2 = regOp(inst.b); + RegisterA64 temp3 = regOp(inst.c); + RegisterA64 temp4 = regOp(inst.d); + + RegisterA64 mask = regs.allocTemp(KindA64::q); + + // Evaluate predicate and calculate mask. + build.fcmeq_4s(mask, temp3, temp4); + // mov A to res register + build.mov(inst.regA64, temp1); + // If numbers are equal override A with B in res register. + build.bit(inst.regA64, temp2, mask); + break; + } case IrCmd::ADD_VEC: { inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 257419cf..934aa481 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -17,6 +17,7 @@ #include "lgc.h" LUAU_FASTFLAG(LuauCodeGenDirectBtest) +LUAU_FASTFLAGVARIABLE(LuauCodeGenVBlendpdReorder) namespace Luau { @@ -619,7 +620,10 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // If arg < 0 then tmp1 is -1 and mask-bit is 0, result is -1 // 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 - build.vblendvpd(inst.regX64, tmp1.reg, build.f64x2(1, 1), inst.regX64); + if (FFlag::LuauCodeGenVBlendpdReorder) + build.vblendvpd(inst.regX64, tmp1.reg, inst.regX64, build.f64x2(1, 1)); + else + build.vblendvpd_DEPRECATED(inst.regX64, tmp1.reg, build.f64x2(1, 1), inst.regX64); break; } case IrCmd::SELECT_NUM: @@ -637,14 +641,34 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } if (inst.a.kind == IrOpKind::Inst) - build.vblendvpd(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b), tmp.reg); + if (FFlag::LuauCodeGenVBlendpdReorder) + build.vblendvpd(inst.regX64, regOp(inst.a), tmp.reg, memRegDoubleOp(inst.b)); + else + build.vblendvpd_DEPRECATED(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b), tmp.reg); else { build.vmovsd(inst.regX64, memRegDoubleOp(inst.a)); - build.vblendvpd(inst.regX64, inst.regX64, memRegDoubleOp(inst.b), tmp.reg); + if (FFlag::LuauCodeGenVBlendpdReorder) + build.vblendvpd(inst.regX64, inst.regX64, tmp.reg, memRegDoubleOp(inst.b)); + else + build.vblendvpd_DEPRECATED(inst.regX64, inst.regX64, memRegDoubleOp(inst.b), tmp.reg); } break; } + case IrCmd::SELECT_VEC: + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.c, inst.d}); + + ScopedRegX64 tmp1{regs}; + ScopedRegX64 tmp2{regs}; + RegisterX64 tmpc = vecOp(inst.c, tmp1); + RegisterX64 tmpd = vecOp(inst.d, tmp2); + + build.vcmpeqps(inst.regX64, tmpc, tmpd); + build.vblendvps(inst.regX64, vecOp(inst.a, tmp1), vecOp(inst.b, tmp2), inst.regX64); + + break; + } case IrCmd::ADD_VEC: { inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index 1a5b8599..b372a7c9 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -9,6 +9,7 @@ #include LUAU_FASTFLAGVARIABLE(LuauCodeGenDirectBtest) +LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorLerp) // TODO: when nresults is less than our actual result count, we can skip computing/writing unused results @@ -283,6 +284,31 @@ static BuiltinImplResult translateBuiltinMathClamp( return {BuiltinImplType::UsesFallback, 1}; } +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) + return {BuiltinImplType::None, -1}; + + IrOp arg1 = build.vmReg(arg); + build.loadAndCheckTag(arg1, LUA_TVECTOR, build.vmExit(pcpos)); + build.loadAndCheckTag(args, LUA_TVECTOR, build.vmExit(pcpos)); + builtinCheckDouble(build, arg3, pcpos); + + IrOp a = build.inst(IrCmd::LOAD_TVALUE, arg1); + IrOp b = build.inst(IrCmd::LOAD_TVALUE, args); + IrOp t = builtinLoadDouble(build, arg3); + + IrOp tvec = build.inst(IrCmd::NUM_TO_VEC, t); + IrOp one = build.inst(IrCmd::NUM_TO_VEC, build.constDouble(1.0)); + IrOp diff = build.inst(IrCmd::SUB_VEC, b, a); + IrOp incr = build.inst(IrCmd::MUL_VEC, diff, tvec); + IrOp res = build.inst(IrCmd::ADD_VEC, a, incr); + IrOp ret = build.inst(IrCmd::SELECT_VEC, res, b, tvec, one); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), build.inst(IrCmd::TAG_VECTOR, ret)); + + return {BuiltinImplType::Full, 1}; +} + static BuiltinImplResult translateBuiltinMathLerp( IrBuilder& build, int nparams, @@ -1343,6 +1369,8 @@ BuiltinImplResult translateBuiltin( return translateBuiltinVectorMap2(build, IrCmd::MIN_NUM, nparams, ra, arg, args, arg3, nresults, pcpos); case LBF_VECTOR_MAX: return translateBuiltinVectorMap2(build, IrCmd::MAX_NUM, nparams, ra, arg, args, arg3, nresults, pcpos); + case LBF_VECTOR_LERP: + return translateBuiltinVectorLerp(build, nparams, ra, arg, args, arg3, nresults, pcpos); case LBF_MATH_LERP: return translateBuiltinMathLerp(build, nparams, ra, arg, args, arg3, nresults, fallback, pcpos); default: diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index c8e1929e..e24ebe25 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -190,6 +190,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::MUL_VEC: case IrCmd::DIV_VEC: case IrCmd::UNM_VEC: + case IrCmd::SELECT_VEC: return IrValueKind::Tvalue; case IrCmd::DOT_VEC: return IrValueKind::Double; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 5e05eb89..5e438b68 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -1325,6 +1325,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::ABS_NUM: case IrCmd::SIGN_NUM: case IrCmd::SELECT_NUM: + case IrCmd::SELECT_VEC: case IrCmd::NOT_ANY: state.substituteOrRecord(inst, index); break; diff --git a/Common/include/Luau/ExperimentalFlags.h b/Common/include/Luau/ExperimentalFlags.h index c1e9c7d8..e5f55f5c 100644 --- a/Common/include/Luau/ExperimentalFlags.h +++ b/Common/include/Luau/ExperimentalFlags.h @@ -20,6 +20,7 @@ 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/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index e8a00a0f..e79af888 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -26,8 +26,6 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25) LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300) LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) -LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo) - namespace Luau { diff --git a/Makefile b/Makefile index 58ad0db9..50a7f9b8 100644 --- a/Makefile +++ b/Makefile @@ -99,6 +99,7 @@ endif # enabled in CI; we should be warning free on our main compiler versions but don't guarantee being warning free everywhere ifneq ($(werror),) CXXFLAGS+=-Werror + CXXFLAGS+=-Wno-unused-but-set-variable endif # configuration-specific flags diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 67888f7d..73e626dc 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -15,8 +15,6 @@ #include -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauSafeStackCheck, false) - /* * This file contains most implementations of core Lua APIs from lua.h. * @@ -141,34 +139,27 @@ int lua_checkstack(lua_State* L, int size) res = 0; // stack overflow else if (size > 0) { - if (DFFlag::LuauSafeStackCheck) + if (stacklimitreached(L, size)) { - if (stacklimitreached(L, size)) + struct CallContext { - struct CallContext - { - int size; + int size; - static void run(lua_State* L, void* ud) - { - CallContext* ctx = (CallContext*)ud; + static void run(lua_State* L, void* ud) + { + CallContext* ctx = (CallContext*)ud; - luaD_growstack(L, ctx->size); - } - } ctx = {size}; + luaD_growstack(L, ctx->size); + } + } ctx = {size}; - // there could be no memory to extend the stack - if (luaD_rawrunprotected(L, &CallContext::run, &ctx) != LUA_OK) - return 0; - } - else - { - condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0)); - } + // there could be no memory to extend the stack + if (luaD_rawrunprotected(L, &CallContext::run, &ctx) != LUA_OK) + return 0; } else { - luaD_checkstack(L, size); + condhardstacktests(luaD_reallocstack(L, L->stacksize - EXTRA_STACK, 0)); } expandstacklimit(L, L->top + size); diff --git a/VM/src/lbaselib.cpp b/VM/src/lbaselib.cpp index c98c31cb..62d3e553 100644 --- a/VM/src/lbaselib.cpp +++ b/VM/src/lbaselib.cpp @@ -11,6 +11,9 @@ #include #include +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauXpcallContNoYield, false) +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauXpcallContErrorHandling, false) + static void writestring(const char* s, size_t l) { fwrite(s, 1, l, stdout); @@ -365,7 +368,10 @@ static void luaB_xpcallerr(lua_State* L, void* ud) { StkId func = (StkId)ud; - luaD_call(L, func, 1); + if (DFFlag::LuauXpcallContNoYield) + luaD_callny(L, func, 1); + else + luaD_call(L, func, 1); } static int luaB_xpcallcont(lua_State* L, int status) @@ -384,11 +390,36 @@ 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) - StkId res = L->top - 3; - StkId errf = L->top - 2; + 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; + + // 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); + } + } + else + { + StkId res = L->top - 3; + StkId errf = L->top - 2; + + // 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)); + } return 2; } diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index ec326da1..58a7e01a 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -17,7 +17,7 @@ #include -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauErrorYield, false) +LUAU_DYNAMIC_FASTFLAG(LuauXpcallContErrorHandling) // keep max stack allocation request under 1GB #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) @@ -296,26 +296,7 @@ void luaD_call(lua_State* L, StkId func, int nresults) ptrdiff_t funcoffset = savestack(L, func); ptrdiff_t cioffset = saveci(L, L->ci); - if (DFFlag::LuauErrorYield) - { - performcall(L, func, nresults); - } - else - { - if (luau_precall(L, func, nresults) == PCRLUA) - { // is a Lua function? - L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame - - bool oldactive = L->isactive; - L->isactive = true; - luaC_threadbarrier(L); - - luau_execute(L); // call it - - if (!oldactive) - L->isactive = false; - } - } + performcall(L, func, nresults); bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK; @@ -361,7 +342,7 @@ void luaD_callny(lua_State* L, StkId func, int nresults) luaC_checkGC(L); } -static void seterrorobj(lua_State* L, int errcode, StkId oldtop) +void luaD_seterrorobj(lua_State* L, int errcode, StkId oldtop) { switch (errcode) { @@ -480,6 +461,17 @@ static CallInfo* resume_findhandler(lua_State* L) return NULL; } +static void restore_stack_limit(lua_State* L) +{ + LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK); + if (L->size_ci > LUAI_MAXCALLS) + { // there was an overflow? + int inuse = cast_int(L->ci - L->base_ci); + if (inuse + 1 < LUAI_MAXCALLS) // can `undo' overflow? + luaD_reallocCI(L, LUAI_MAXCALLS); + } +} + static void resume_handle(lua_State* L, void* ud) { CallInfo* ci = (CallInfo*)ud; @@ -501,7 +493,7 @@ static void resume_handle(lua_State* L, void* ud) // push error object to stack top if it's not already there if (status != LUA_ERRRUN) - seterrorobj(L, status, L->top); + luaD_seterrorobj(L, status, L->top); // adjust the stack frame for ci to prepare for cont call L->base = ci->base; @@ -519,6 +511,9 @@ 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); + // finish cont call and restore stack to previous ci top luau_poscall(L, L->top - n); @@ -542,7 +537,7 @@ static void resume_finish(lua_State* L, int status) if (status != 0) { // error? L->status = cast_byte(status); // mark thread as `dead' - seterrorobj(L, status, L->top); + luaD_seterrorobj(L, status, L->top); L->ci->top = L->top; } else if (L->status == 0) @@ -643,21 +638,7 @@ static void callerrfunc(lua_State* L, void* ud) setobj2s(L, L->top - 1, errfunc); incr_top(L); - if (DFFlag::LuauErrorYield) - luaD_callny(L, L->top - 2, 1); - else - luaD_call(L, L->top - 2, 1); -} - -static void restore_stack_limit(lua_State* L) -{ - LUAU_ASSERT(L->stack_last - L->stack == L->stacksize - EXTRA_STACK); - if (L->size_ci > LUAI_MAXCALLS) - { // there was an overflow? - int inuse = cast_int(L->ci - L->base_ci); - if (inuse + 1 < LUAI_MAXCALLS) // can `undo' overflow? - luaD_reallocCI(L, LUAI_MAXCALLS); - } + luaD_callny(L, L->top - 2, 1); } int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef) @@ -676,7 +657,7 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e { // push error object to stack top if it's not already there if (status != LUA_ERRRUN) - seterrorobj(L, status, L->top); + luaD_seterrorobj(L, status, L->top); // if errfunc fails, we fail with "error in error handling" or "not enough memory" int err = luaD_rawrunprotected(L, callerrfunc, restorestack(L, ef)); @@ -713,7 +694,7 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e StkId oldtop = restorestack(L, old_top); luaF_close(L, oldtop); // close eventual pending closures - seterrorobj(L, errstatus, oldtop); + luaD_seterrorobj(L, errstatus, oldtop); L->ci = restoreci(L, old_ci); L->base = L->ci->base; restore_stack_limit(L); diff --git a/VM/src/ldo.h b/VM/src/ldo.h index 0a7203b5..dce1c64f 100644 --- a/VM/src/ldo.h +++ b/VM/src/ldo.h @@ -62,6 +62,7 @@ LUAI_FUNC void luaD_reallocCI(lua_State* L, int newsize); LUAI_FUNC void luaD_reallocstack(lua_State* L, int newsize, int fornewci); LUAI_FUNC void luaD_growstack(lua_State* L, int n); LUAI_FUNC void luaD_checkCstack(lua_State* L); +LUAI_FUNC void luaD_seterrorobj(lua_State* L, int errcode, StkId oldtop); LUAI_FUNC l_noret luaD_throw(lua_State* L, int errcode); LUAI_FUNC int luaD_rawrunprotected(lua_State* L, Pfunc f, void* ud); diff --git a/VM/src/lgcdebug.cpp b/VM/src/lgcdebug.cpp index 2270bad2..f695188f 100644 --- a/VM/src/lgcdebug.cpp +++ b/VM/src/lgcdebug.cpp @@ -14,8 +14,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauHeapDumpStringSizeOverhead) - static void validateobjref(global_State* g, GCObject* f, GCObject* t) { LUAU_ASSERT(!isdead(g, t)); @@ -653,10 +651,7 @@ static void enumedges(EnumContext* ctx, GCObject* from, TValue* data, size_t siz static void enumstring(EnumContext* ctx, TString* ts) { - if (FFlag::LuauHeapDumpStringSizeOverhead) - enumnode(ctx, obj2gco(ts), sizestring(ts->len), NULL); - else - enumnode(ctx, obj2gco(ts), ts->len, NULL); + enumnode(ctx, obj2gco(ts), sizestring(ts->len), NULL); } static void enumtable(EnumContext* ctx, LuaTable* h) diff --git a/fuzz/compiler.cpp b/fuzz/compiler.cpp index ae915b11..137c3ec3 100644 --- a/fuzz/compiler.cpp +++ b/fuzz/compiler.cpp @@ -1,10 +1,19 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include #include "Luau/Compiler.h" #include "Luau/Common.h" +#include + +#include + extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = true; + } + Luau::compile(std::string(reinterpret_cast(Data), Size)); return 0; } diff --git a/fuzz/format.cpp b/fuzz/format.cpp index 4b943bf1..937a370b 100644 --- a/fuzz/format.cpp +++ b/fuzz/format.cpp @@ -1,9 +1,11 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Common.h" +#include + #include #include -#include +#include namespace Luau { @@ -12,6 +14,12 @@ void fuzzFormatString(const char* data, size_t size); extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = true; + } + // copy data to heap to make sure ASAN can catch out of bounds access std::vector str(Data, Data + Size); diff --git a/fuzz/linter.cpp b/fuzz/linter.cpp index 8efd4246..3210bd32 100644 --- a/fuzz/linter.cpp +++ b/fuzz/linter.cpp @@ -1,15 +1,24 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include - #include "Luau/BuiltinDefinitions.h" #include "Luau/Common.h" #include "Luau/Frontend.h" #include "Luau/Linter.h" #include "Luau/ModuleResolver.h" #include "Luau/Parser.h" +#include "Luau/TypeInfer.h" + +#include + +#include extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = true; + } + Luau::ParseOptions options; Luau::Allocator allocator; diff --git a/fuzz/luau.proto b/fuzz/luau.proto index 148eb1d6..d8013550 100644 --- a/fuzz/luau.proto +++ b/fuzz/luau.proto @@ -120,6 +120,7 @@ message ExprFunction { required StatBlock body = 5; repeated Type types = 6; repeated Type rettypes = 7; + repeated ExprAttr attributes = 8; } message TableItem { @@ -376,6 +377,7 @@ message TypeFunction { repeated GenericTypeName genericpacks = 2; repeated Type args = 3; repeated Type rets = 4; + repeated ExprAttr attributes = 5; // TODO: vararg? } @@ -414,3 +416,36 @@ message ModuleSet { optional StatBlock module = 1; required StatBlock program = 2; } + +message ExprLiteral { + oneof expr_oneof { + ExprConstantNil nil = 1; + ExprConstantBool bool = 2; + ExprConstantNumber number = 3; + ExprConstantString string = 4; + ExprLiteralTable table = 5; + } +} + +message LiteralTableItem { + optional Name key_name = 1; + required ExprLiteral value = 2; +} + +message ExprLiteralTable { + repeated LiteralTableItem items = 1; +} + +enum AttrType { + Checked = 1; + Native = 2; + Deprecated = 3; + Unknown = 4; +} + +message ExprAttr { + required AttrType type = 1; + optional Name name = 2; + repeated ExprLiteral args = 3; + optional bool braced = 4; +} diff --git a/fuzz/parser.cpp b/fuzz/parser.cpp index ef0dc0c3..586733fd 100644 --- a/fuzz/parser.cpp +++ b/fuzz/parser.cpp @@ -1,10 +1,19 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include #include "Luau/Parser.h" #include "Luau/Common.h" +#include + +#include + extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = true; + } + Luau::ParseOptions options; Luau::Allocator allocator; diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index 48ff2cdb..d6da9d8e 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -258,8 +258,10 @@ DEFINE_PROTO_FUZZER(const luau::ModuleSet& message) FInt::LuauTableTypeMaximumStringifierLength.value = 100; for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { if (strncmp(flag->name, "Luau", 4) == 0) flag->value = true; + } FFlag::DebugLuauFreezeArena.value = true; FFlag::DebugLuauAbortingChecks.value = true; diff --git a/fuzz/protoprint.cpp b/fuzz/protoprint.cpp index 58812e07..b22563b0 100644 --- a/fuzz/protoprint.cpp +++ b/fuzz/protoprint.cpp @@ -241,24 +241,28 @@ struct ProtoToLuau functions.push_back(top); } - void ident(const luau::Name& name) + std::string displayName(const luau::Name& name) { if (name.has_builtin()) { size_t index = size_t(name.builtin()) % std::size(kNames); - source += kNames[index]; + return kNames[index]; } else if (name.has_custom()) { - source += 'n'; - source += std::to_string(name.custom() & 0xff); + return 'n' + std::to_string(name.custom() & 0xff); } else { - source += '_'; + return "_"; } } + void ident(const luau::Name& name) + { + source += displayName(name); + } + void ident(const luau::RegularTypeName& name) { source += 't'; @@ -513,6 +517,11 @@ struct ProtoToLuau void print(const luau::ExprFunction& expr) { + for (size_t i = 0; i < expr.attributes_size(); ++i) + { + print(expr.attributes(i)); + source += "\n"; + } source += "function"; function(expr); } @@ -975,6 +984,12 @@ struct ProtoToLuau void print(const luau::StatTypeFunction& stat) { + for (size_t i = 0; i < stat.func().attributes_size(); ++i) + { + print(stat.func().attributes(i)); + source += "\n"; + } + if (stat.export_()) source += "export "; @@ -1166,6 +1181,73 @@ struct ProtoToLuau source += ch; source += '"'; } + + void print(const luau::ExprLiteralTable& table) + { + source += '{'; + for (int i = 0; i < table.items_size(); ++i) + { + ident(table.items(i).key_name()); + source += '='; + + print(table.items(i).value()); + source += ','; + } + source += '}'; + } + + void print(const luau::ExprLiteral& lit) + { + if (lit.has_table()) + print(lit.table()); + else if (lit.has_nil()) + print(lit.nil()); + else if (lit.has_bool_()) + print(lit.bool_()); + else if (lit.has_number()) + print(lit.number()); + else if (lit.has_string()) + print(lit.string()); + } + + void print(const luau::ExprAttr& attr) + { + std::string name; + if (attr.type() == luau::AttrType::Checked) + name = "checked"; + if (attr.type() == luau::AttrType::Native) + name = "native"; + if (attr.type() == luau::AttrType::Deprecated) + name = "deprecated"; + else + name = displayName(attr.name()); + + if (attr.braced()) + { + source += "@[" + name; + if (attr.args_size() == 1) + { + source += " "; + print(attr.args(0)); + } + else if (attr.args_size() > 1) + { + source += "("; + for (int i = 0; i < attr.args_size(); ++i) + { + if (i != 0) + source += ','; + print(attr.args(i)); + } + source += ")"; + } + source += "]"; + } + else + { + source += "@" + name; + } + } }; std::vector protoprint(const luau::ModuleSet& stat, bool types) diff --git a/fuzz/transpiler.cpp b/fuzz/transpiler.cpp index ccc1a4f0..02872b02 100644 --- a/fuzz/transpiler.cpp +++ b/fuzz/transpiler.cpp @@ -1,10 +1,19 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include #include "Luau/Transpiler.h" #include "Luau/Common.h" +#include + +#include + extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) { + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = true; + } + Luau::transpile(std::string_view(reinterpret_cast(Data), Size)); return 0; } diff --git a/fuzz/typeck.cpp b/fuzz/typeck.cpp index 87a88271..daed2c94 100644 --- a/fuzz/typeck.cpp +++ b/fuzz/typeck.cpp @@ -1,11 +1,14 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details -#include - #include "Luau/BuiltinDefinitions.h" #include "Luau/Common.h" #include "Luau/Frontend.h" #include "Luau/ModuleResolver.h" #include "Luau/Parser.h" +#include "Luau/TypeInfer.h" + +#include + +#include LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) @@ -15,6 +18,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) FInt::LuauTypeInferRecursionLimit.value = 100; FInt::LuauTypeInferTypePackLoopLimit.value = 100; + for (Luau::FValue* flag = Luau::FValue::list; flag; flag = flag->next) + { + if (strncmp(flag->name, "Luau", 4) == 0) + flag->value = true; + } + Luau::ParseOptions options; Luau::Allocator allocator; diff --git a/tests/AssemblyBuilderA64.test.cpp b/tests/AssemblyBuilderA64.test.cpp index ee319a5f..f2107fa8 100644 --- a/tests/AssemblyBuilderA64.test.cpp +++ b/tests/AssemblyBuilderA64.test.cpp @@ -598,6 +598,9 @@ TEST_CASE("LogTest") build.dup_4s(q29, q30, 0); build.fmul(q0, q1, q2); + build.fcmeq_4s(q2, q0, q1); + build.bit(q1, q0, q2); + build.setLabel(l); build.ret(); @@ -640,6 +643,8 @@ TEST_CASE("LogTest") dup s29,v31.s[2] dup v29.4s,v30.s[0] fmul v0.4s,v1.4s,v2.4s + fcmeq v2.4s,v0.4s,v1.4s + bit v1.16b,v0.16b,v2.16b .L1: ret )"; diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index a3100366..d90041f6 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -590,7 +590,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, xmmword[rcx + r10], xmm5), 0xc4, 0xa3, 0x19, 0x4b, 0x3c, 0x11, 0x50); + SINGLE_COMPARE(vblendvpd(xmm7, xmm12, xmm5, xmmword[rcx + r10]), 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 ae028d44..f5a2f0e2 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -22,6 +22,8 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) LUAU_FASTFLAG(LuauIncludeBreakContinueStatements) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauSuggestHotComments) using namespace Luau; @@ -79,6 +81,7 @@ struct ACFixtureImpl : BaseType CheckResult check(const std::string& source) { + this->getFrontend(); markerPosition.clear(); std::string filteredSource; filteredSource.reserve(source.size()); @@ -157,11 +160,20 @@ struct ACFixture : ACFixtureImpl ACFixture() : ACFixtureImpl() { + } + + Frontend& getFrontend() override + { + if (frontend) + return *frontend; + + Frontend& f = Fixture::getFrontend(); // TODO - move this into its own consructor - addGlobalBinding(getFrontend().globals, "table", Binding{getBuiltins()->anyType}); - addGlobalBinding(getFrontend().globals, "math", Binding{getBuiltins()->anyType}); - addGlobalBinding(getFrontend().globalsForAutocomplete, "table", Binding{getBuiltins()->anyType}); - addGlobalBinding(getFrontend().globalsForAutocomplete, "math", Binding{getBuiltins()->anyType}); + addGlobalBinding(f.globals, "table", Binding{getBuiltins()->anyType}); + addGlobalBinding(f.globals, "math", Binding{getBuiltins()->anyType}); + addGlobalBinding(f.globalsForAutocomplete, "table", Binding{getBuiltins()->anyType}); + addGlobalBinding(f.globalsForAutocomplete, "math", Binding{getBuiltins()->anyType}); + return *frontend; } }; @@ -606,18 +618,6 @@ TEST_CASE_FIXTURE(ACFixture, "dont_offer_any_suggestions_from_within_a_comment") CHECK_EQ(ac.context, AutocompleteContext::Unknown); } -TEST_CASE_FIXTURE(ACFixture, "dont_offer_any_suggestions_from_the_end_of_a_comment") -{ - check(R"( - --!strict@1 - )"); - - auto ac = autocomplete('1'); - - CHECK_EQ(0, ac.entryMap.size()); - CHECK_EQ(ac.context, AutocompleteContext::Unknown); -} - TEST_CASE_FIXTURE(ACFixture, "dont_offer_any_suggestions_from_within_a_broken_comment") { check(R"( @@ -2195,6 +2195,8 @@ 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 local fp: @1= f @@ -2207,7 +2209,7 @@ local fp: @1= f else { // NOTE: All autocomplete tests occur under no-check mode. - REQUIRE_EQ("({| x: number, y: number |}) -> (...any)", toString(requireType("f"))); + REQUIRE_EQ("({ x: number, y: number }) -> (...any)", toString(requireType("f"))); } CHECK(ac.entryMap.count("({ x: number, y: number }) -> number")); } @@ -2462,9 +2464,9 @@ local a: aaa.do TEST_CASE_FIXTURE(ACFixture, "comments") { - fileResolver.source["Comments"] = "--!str"; + fileResolver.source["Comments"] = "--foo"; - auto ac = autocomplete("Comments", Position{0, 6}); + auto ac = autocomplete("Comments", Position{0, 5}); CHECK_EQ(0, ac.entryMap.size()); } @@ -4878,4 +4880,21 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_exclude_break_continue_in_incomplete_ CHECK_EQ(ac.entryMap.count("continue"), 0); } +TEST_CASE_FIXTURE(ACFixture, "autocomplete_suggest_hot_comments") +{ + ScopedFastFlag sff{FFlag::LuauSuggestHotComments, true}; + + check("--!@1"); + + auto ac = autocomplete('1'); + + CHECK(!ac.entryMap.empty()); + CHECK(ac.entryMap.count("strict")); + CHECK(ac.entryMap.count("nonstrict")); + CHECK(ac.entryMap.count("nocheck")); + CHECK(ac.entryMap.count("native")); + CHECK(ac.entryMap.count("nolint")); + CHECK(ac.entryMap.count("optimize")); +} + TEST_SUITE_END(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index c6adc450..58bf4b5f 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -34,15 +34,16 @@ 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_FASTFLAG(LuauHeapDumpStringSizeOverhead) +LUAU_DYNAMIC_FASTFLAG(LuauXpcallContErrorHandling) LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) -LUAU_DYNAMIC_FASTFLAG(LuauErrorYield) -LUAU_DYNAMIC_FASTFLAG(LuauSafeStackCheck) LUAU_FASTFLAG(LuauVectorLerp) LUAU_FASTFLAG(LuauCompileVectorLerp) LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) +LUAU_FASTFLAG(LuauCodeGenVectorLerp) +LUAU_DYNAMIC_FASTFLAG(LuauXpcallContNoYield) +LUAU_FASTFLAG(LuauCodeGenBetterBytecodeAnalysis) static lua_CompileOptions defaultOptions() { @@ -576,12 +577,12 @@ void setupUserdataHelpers(lua_State* L) static void setupNativeHelpers(lua_State* L) { + extern int luaG_isnative(lua_State * L, int level); + lua_pushcclosurek( L, [](lua_State* L) -> int { - extern int luaG_isnative(lua_State * L, int level); - lua_pushboolean(L, luaG_isnative(L, 1)); return 1; }, @@ -590,6 +591,23 @@ static void setupNativeHelpers(lua_State* L) nullptr ); lua_setglobal(L, "is_native"); + + lua_pushcclosurek( + L, + [](lua_State* L) -> int + { + if (!codegen || !luau_codegen_supported()) + lua_pushboolean(L, 1); + else + lua_pushboolean(L, luaG_isnative(L, 1)); + + return 1; + }, + "is_native_if_supported", + 0, + nullptr + ); + lua_setglobal(L, "is_native_if_supported"); } static std::vector analyzeFile(const char* source, const unsigned nestingLimit) @@ -739,8 +757,6 @@ TEST_CASE("Closure") TEST_CASE("Calls") { - ScopedFastFlag luauSafeStackCheck{DFFlag::LuauSafeStackCheck, true}; - runConformance("calls.luau"); } @@ -803,7 +819,7 @@ TEST_CASE("UTF8") TEST_CASE("Coroutine") { - ScopedFastFlag luauErrorYield{DFFlag::LuauErrorYield, true}; + ScopedFastFlag luauXpcallContNoYield{DFFlag::LuauXpcallContNoYield, true}; runConformance("coroutine.luau"); } @@ -819,6 +835,8 @@ static int cxxthrow(lua_State* L) TEST_CASE("PCall") { + ScopedFastFlag luauXpcallContErrorHandling{DFFlag::LuauXpcallContErrorHandling, true}; + runConformance( "pcall.luau", [](lua_State* L) @@ -1123,6 +1141,7 @@ TEST_CASE("Vector") [](lua_State* L) { setupVectorHelpers(L); + setupNativeHelpers(L); }, nullptr, nullptr, @@ -1134,7 +1153,13 @@ TEST_CASE("Vector") TEST_CASE("VectorLibrary") { - ScopedFastFlag _[]{{FFlag::LuauCompileVectorLerp, true}, {FFlag::LuauTypeCheckerVectorLerp, true}, {FFlag::LuauVectorLerp, true}}; + ScopedFastFlag _[]{ + {FFlag::LuauCompileVectorLerp, true}, + {FFlag::LuauTypeCheckerVectorLerp, true}, + {FFlag::LuauVectorLerp, true}, + {FFlag::LuauCodeGenVectorLerp, true}, + {FFlag::LuauCodeGenBetterBytecodeAnalysis, true} + }; lua_CompileOptions copts = defaultOptions(); @@ -1151,7 +1176,16 @@ TEST_CASE("VectorLibrary") copts.optimizationLevel = 2; } - runConformance("vector_library.luau", [](lua_State* L) {}, nullptr, nullptr, &copts); + runConformance( + "vector_library.luau", + [](lua_State* L) + { + setupNativeHelpers(L); + }, + nullptr, + nullptr, + &copts + ); } static void populateRTTI(lua_State* L, Luau::TypeId type) @@ -2140,8 +2174,6 @@ int slowlyOverflowStack(lua_State* L) TEST_CASE("ApiStack") { - ScopedFastFlag luauSafeStackCheck{DFFlag::LuauSafeStackCheck, true}; - StateRef globalState(lua_newstate(blockableRealloc, nullptr), lua_close); lua_State* GL = globalState.get(); @@ -2381,8 +2413,6 @@ TEST_CASE("StringConversion") TEST_CASE("GCDump") { - ScopedFastFlag luauHeapDumpStringSizeOverhead{FFlag::LuauHeapDumpStringSizeOverhead, true}; - // internal function, declared in lgc.h - not exposed via lua.h extern void luaC_dump(lua_State * L, void* file, const char* (*categoryName)(lua_State* L, uint8_t memcat)); extern void luaC_enumheap( diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 0f76d5a2..cea88e62 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -33,6 +33,9 @@ LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAG(LuauForInProvidesRecommendations) +LUAU_FASTFLAG(LuauFragmentAutocompleteTakesInnermostRefinement) +LUAU_FASTFLAG(LuauSuggestHotComments) +LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { @@ -863,7 +866,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if") auto region = getAutocompleteRegion( R"( if true then -elseif +elseif end )", @@ -4288,6 +4291,133 @@ 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 } +type Type2 = { Type: "Type2", CommonKey: string, Type2Key: string } +type UnionType = Type1 | Type2 + +local foo: UnionType? = nil +if foo then + if foo.Type == "Type2" then + end +end + )"; + + const std::string dest = R"( +--!strict +type Type1 = { Type: "Type1", CommonKey: string, Type1Key: string } +type Type2 = { Type: "Type2", CommonKey: string, Type2Key: string } +type UnionType = Type1 | Type2 + +local foo: UnionType? = nil +if foo then + if foo.Type == "Type2" then + foo. + end +end + )"; + + autocompleteFragmentInBothSolvers( + source, + dest, + Position{9, 12}, + [](FragmentAutocompleteStatusResult& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("Type2Key") > 0); + CHECK(result.result->acResults.entryMap.count("Type1Key") == 0); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "hot_comment_should_rec") +{ + ScopedFastFlag sff{FFlag::LuauSuggestHotComments, true}; + const std::string source = R"(--!)"; + autocompleteFragmentInBothSolvers( + source, + source, + Position{0, 3}, + [](auto& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("strict")); + CHECK(result.result->acResults.entryMap.count("nonstrict")); + CHECK(result.result->acResults.entryMap.count("nocheck")); + CHECK(result.result->acResults.entryMap.count("native")); + CHECK(result.result->acResults.entryMap.count("nolint")); + CHECK(result.result->acResults.entryMap.count("optimize")); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "len_operator_needs_to_provide_autocomplete_results") +{ + ScopedFastFlag sff{FFlag::LuauNumericUnaryOpsDontProduceNegationRefinements, true}; + std::string source = R"( +type Pool = { numbers: { number }} + +local function foobar(p) + local pool = p :: Pool + if #pool +end +)"; + std::string dest = R"( +type Pool = { numbers: { number }} + +local function foobar(p) + local pool = p :: Pool + if #pool. +end +)"; + autocompleteFragmentInBothSolvers( + source, + dest, + Position{5, 13}, + [](auto& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("numbers")); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "unary_minus_operator_needs_to_provide_autocomplete_results") +{ + ScopedFastFlag sff{FFlag::LuauNumericUnaryOpsDontProduceNegationRefinements, true}; + std::string source = R"( +type Pool = { x : number } + +local function foobar(p) + local pool = p :: Pool + if -pool +end +)"; + std::string dest = R"( +type Pool = { x : number } + +local function foobar(p) + local pool = p :: Pool + if -pool. +end +)"; + autocompleteFragmentInBothSolvers( + source, + dest, + Position{5, 13}, + [](auto& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("x")); + } + ); +} + // NOLINTEND(bugprone-unchecked-optional-access) TEST_SUITE_END(); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 8736892e..83d493e5 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -16,6 +16,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauMagicTypes) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) namespace { @@ -55,10 +56,17 @@ struct NaiveFileResolver : NullFileResolver struct FrontendFixture : BuiltinsFixture { - FrontendFixture() + FrontendFixture() = default; + + Frontend& getFrontend() override { - addGlobalBinding(getFrontend().globals, "game", getBuiltins()->anyType, "@test"); - addGlobalBinding(getFrontend().globals, "script", getBuiltins()->anyType, "@test"); + if (frontend) + return *frontend; + + Frontend& f = BuiltinsFixture::getFrontend(); + addGlobalBinding(f.globals, "game", f.builtinTypes->anyType, "@test"); + addGlobalBinding(f.globals, "script", f.builtinTypes->anyType, "@test"); + return *frontend; } }; @@ -119,6 +127,7 @@ 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 @@ -136,10 +145,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_dependent_scripts") auto bExports = first(bModule->returnType); REQUIRE(!!bExports); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ b_value: number }", toString(*bExports)); - else - CHECK_EQ("{| b_value: number |}", toString(*bExports)); + CHECK_EQ("{ b_value: number }", toString(*bExports)); } TEST_CASE_FIXTURE(FrontendFixture, "automatically_check_cyclically_dependent_scripts") @@ -254,6 +260,7 @@ 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 @@ -283,10 +290,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "nocheck_cycle_used_by_checked") std::optional cExports = first(cModule->returnType); REQUIRE(bool(cExports)); - if (FFlag::LuauSolverV2) - CHECK("{ a: { hello: any }, b: { hello: any } }" == toString(*cExports)); - else - CHECK("{| a: {| hello: any |}, b: {| hello: any |} |}" == toString(*cExports)); + CHECK("{ a: { hello: any }, b: { hello: any } }" == toString(*cExports)); } TEST_CASE_FIXTURE(FrontendFixture, "cycle_detection_disabled_in_nocheck") @@ -370,6 +374,7 @@ 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} )"; @@ -430,6 +435,7 @@ 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 } @@ -457,32 +463,20 @@ return {mod_b = 2} LUAU_REQUIRE_ERRORS(resultB); TypeId tyB = requireExportedType("game/B", "btype"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(tyB, opts), "{ x: number }"); - else - CHECK_EQ(toString(tyB, opts), "{| x: number |}"); + CHECK_EQ(toString(tyB, opts), "{ x: number }"); TypeId tyA = requireExportedType("game/A", "atype"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(tyA, opts), "{ x: any }"); - else - CHECK_EQ(toString(tyA, opts), "{| x: any |}"); + CHECK_EQ(toString(tyA, opts), "{ x: any }"); getFrontend().markDirty("game/B"); resultB = getFrontend().check("game/B"); LUAU_REQUIRE_ERRORS(resultB); tyB = requireExportedType("game/B", "btype"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(tyB, opts), "{ x: number }"); - else - CHECK_EQ(toString(tyB, opts), "{| x: number |}"); + CHECK_EQ(toString(tyB, opts), "{ x: number }"); tyA = requireExportedType("game/A", "atype"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(tyA, opts), "{ x: any }"); - else - CHECK_EQ(toString(tyA, opts), "{| x: any |}"); + CHECK_EQ(toString(tyA, opts), "{ x: any }"); } TEST_CASE_FIXTURE(FrontendFixture, "dont_reparse_clean_file_when_linting") @@ -534,6 +528,7 @@ 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 @@ -555,10 +550,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "recheck_if_dependent_script_is_dirty") auto bExports = first(bModule->returnType); REQUIRE(!!bExports); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ b_value: string }", toString(*bExports)); - else - CHECK_EQ("{| b_value: string |}", toString(*bExports)); + CHECK_EQ("{ b_value: string }", toString(*bExports)); } TEST_CASE_FIXTURE(FrontendFixture, "mark_non_immediate_reverse_deps_as_dirty") @@ -876,6 +868,7 @@ 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"( @@ -899,7 +892,7 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f } else REQUIRE_EQ( - "Table type 'a' not compatible with type '{| Count: number |}' because the former is missing field 'Count'", toString(result.errors[0]) + "Table type 'a' not compatible with type '{ Count: number }' because the former is missing field 'Count'", toString(result.errors[0]) ); } diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 6dc69cb4..045539f1 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -16,8 +16,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) @@ -233,8 +231,6 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, }; TableType tt; @@ -271,8 +267,6 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; auto [aTy, aFree] = freshType(); @@ -446,8 +440,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "avoid_cross_module_mutation_in_bidirectional { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; fileResolver.source["Module/ListFns"] = R"( diff --git a/tests/InferPolarity.test.cpp b/tests/InferPolarity.test.cpp index 23901cbe..325a9538 100644 --- a/tests/InferPolarity.test.cpp +++ b/tests/InferPolarity.test.cpp @@ -9,8 +9,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauEagerGeneralization4); -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) TEST_SUITE_BEGIN("InferPolarity"); @@ -18,8 +16,6 @@ TEST_CASE_FIXTURE(Fixture, "T where T = { m: (a) -> T }") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; TypeArena arena; @@ -58,8 +54,6 @@ TEST_CASE_FIXTURE(Fixture, "({ read x: a, write x: b }) -> ()") { ScopedFastFlag sffs[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, }; TypeArena arena; diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index 25df42aa..d8a3bfbe 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -18,6 +18,10 @@ LUAU_FASTFLAG(LuauCodeGenSimplifyImport2) LUAU_FASTFLAG(LuauCodeGenDirectBtest) +LUAU_FASTFLAG(LuauVectorLerp) +LUAU_FASTFLAG(LuauCompileVectorLerp) +LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) +LUAU_FASTFLAG(LuauCodeGenVectorLerp) static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant) { @@ -461,6 +465,48 @@ end ); } +TEST_CASE("VectorLerp") +{ + ScopedFastFlag _[]{ + {FFlag::LuauCompileVectorLerp, true}, + {FFlag::LuauTypeCheckerVectorLerp, true}, + {FFlag::LuauVectorLerp, true}, + {FFlag::LuauCodeGenVectorLerp, true} + }; + CHECK_EQ( + "\n" + getCodegenAssembly(R"( +local function vec3lerp(a: vector, b: vector, t: number) + return vector.lerp(a, b, t) +end +)"), + R"( +; function vec3lerp($arg0, $arg1, $arg2) line 2 +bb_0: + CHECK_TAG R0, tvector, exit(entry) + CHECK_TAG R1, tvector, exit(entry) + CHECK_TAG R2, tnumber, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + CHECK_SAFE_ENV exit(2) + %15 = LOAD_TVALUE R0 + %16 = LOAD_TVALUE R1 + %17 = LOAD_DOUBLE R2 + %18 = NUM_TO_VEC %17 + %19 = NUM_TO_VEC 1 + %20 = SUB_VEC %16, %15 + %21 = MUL_VEC %20, %18 + %22 = ADD_VEC %15, %21 + SELECT_VEC %22, %16, %18, %19 + %24 = TAG_VECTOR %23 + STORE_TVALUE R3, %24 + INTERRUPT 8u + RETURN R3, 1i +)" + ); +} + TEST_CASE("ExtraMathMemoryOperands") { CHECK_EQ( diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 8d44acc7..45bfc0e4 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -67,10 +67,6 @@ struct NonStrictTypeCheckerFixture : Fixture NonStrictTypeCheckerFixture() { - // Force the frontend - getFrontend(); - registerHiddenTypes(getFrontend()); - registerTestTypes(); } CheckResult checkNonStrict(const std::string& code) @@ -93,6 +89,17 @@ struct NonStrictTypeCheckerFixture : Fixture return getFrontend().check(moduleName); } + Frontend& getFrontend() override + { + if (frontend) + return *frontend; + + Frontend& f = Fixture::getFrontend(); + registerHiddenTypes(f); + registerTestTypes(); + return *frontend; + } + std::string definitions = R"BUILTIN_SRC( @checked declare function abs(n: number): number @checked declare function lower(s: string): string diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 5d93480a..b9c32966 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -9,6 +9,7 @@ #include "Luau/Normalize.h" #include "Luau/BuiltinDefinitions.h" +#include LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferRecursionLimit) @@ -16,6 +17,8 @@ LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) + using namespace Luau; namespace @@ -292,6 +295,7 @@ 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 @@ -453,17 +457,15 @@ struct NormalizeFixture : Fixture TypeArena arena; InternalErrorReporter iceHandler; UnifierSharedState unifierState{&iceHandler}; - Normalizer normalizer{&arena, getBuiltins(), NotNull{&unifierState}, FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old}; - Scope globalScope{getBuiltins()->anyTypePack}; NormalizeFixture() { - registerHiddenTypes(getFrontend()); } std::shared_ptr toNormalizedType(const std::string& annotation, int expectedErrors = 0) { - normalizer.clearCaches(); + getFrontend(); + normalizer->clearCaches(); CheckResult result = check("type _Res = " + annotation); LUAU_REQUIRE_ERROR_COUNT(expectedErrors, result); @@ -477,22 +479,65 @@ struct NormalizeFixture : Fixture REQUIRE(alias); TypeId* originalTy = getMainModule()->astResolvedTypes.find(alias->type); REQUIRE(originalTy); - return normalizer.normalize(*originalTy); + return normalizer->normalize(*originalTy); } else { std::optional ty = lookupType("_Res"); REQUIRE(ty); - return normalizer.normalize(*ty); + return normalizer->normalize(*ty); } } + std::shared_ptr normalize(TypeId ty) + { + // Force the frontend; + getFrontend(); + return normalizer->normalize(ty); + } + + TypeId typeFromNormal(const NormalizedType& norm) + { + // Force the fontend + getFrontend(); + return normalizer->typeFromNormal(norm); + } + + bool isInhabited(const NormalizedType* norm) + { + return normalizer->isInhabited(norm) == NormalizationResult::True; + } + TypeId normal(const std::string& annotation) { + // Force the frontend; + getFrontend(); std::shared_ptr norm = toNormalizedType(annotation); REQUIRE(norm); - return normalizer.typeFromNormal(*norm); + return normalizer->typeFromNormal(*norm); } + + Frontend& getFrontend() override + { + if (frontend) + return *frontend; + + Frontend& f = Fixture::getFrontend(); + globalScope = std::make_unique(f.builtinTypes->anyTypePack); + normalizer = std::make_unique(&arena, f.builtinTypes, NotNull{&unifierState}, f.getLuauSolverMode()); + registerHiddenTypes(f); + + return *frontend; + } + + Scope* getGlobalScope() + { + return globalScope.get(); + } + +private: + std::unique_ptr normalizer = nullptr; + std::unique_ptr globalScope = nullptr; }; TEST_SUITE_BEGIN("Normalize"); @@ -608,14 +653,14 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersect_error") { std::shared_ptr norm = toNormalizedType(R"(string & AAA)", 1); REQUIRE(norm); - CHECK("*error-type*" == toString(normalizer.typeFromNormal(*norm))); + CHECK("*error-type*" == toString(typeFromNormal(*norm))); } TEST_CASE_FIXTURE(NormalizeFixture, "intersect_not_error") { std::shared_ptr norm = toNormalizedType(R"(string & Not<)", 1); REQUIRE(norm); - CHECK("*error-type*" == toString(normalizer.typeFromNormal(*norm))); + CHECK("*error-type*" == toString(typeFromNormal(*norm))); } TEST_CASE_FIXTURE(NormalizeFixture, "union_of_union") @@ -707,10 +752,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "trivial_intersection_inhabited") TypeId a = arena.addType(FunctionType{getBuiltins()->emptyTypePack, getBuiltins()->anyTypePack, std::nullopt, false}); TypeId c = arena.addType(IntersectionType{{a, a}}); - std::shared_ptr n = normalizer.normalize(c); + std::shared_ptr n = normalize(c); REQUIRE(n); - CHECK(normalizer.isInhabited(n.get()) == NormalizationResult::True); + CHECK(isInhabited(n.get())); } TEST_CASE_FIXTURE(NormalizeFixture, "bare_negated_boolean") @@ -751,6 +796,7 @@ 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() @@ -761,7 +807,10 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_normalizes_sensibly") LUAU_REQUIRE_NO_ERRORS(result); TypeId ty = requireType("Cyclic"); - CHECK_EQ("t1 where t1 = { get: () -> t1 }", toString(ty, {true})); + if (FFlag::LuauSolverV2) + CHECK_EQ("t1 where t1 = { get: () -> t1 }", toString(ty, {true})); + else + CHECK_EQ("t1 where t1 = {| get: () -> t1 |}", toString(ty, {true})); } TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types") @@ -808,10 +857,8 @@ 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") { - if (FFlag::LuauSolverV2) - CHECK("{ @metatable *error-type*, { } }" == toString(normal("Mt<{}, any> & Mt<{}, err>"))); - else - CHECK("{ @metatable *error-type*, {| |} }" == toString(normal("Mt<{}, any> & Mt<{}, err>"))); + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; + CHECK("{ @metatable *error-type*, { } }" == toString(normal("Mt<{}, any> & Mt<{}, err>"))); } TEST_CASE_FIXTURE(NormalizeFixture, "recurring_intersection") @@ -824,10 +871,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "recurring_intersection") std::optional t = lookupType("B"); REQUIRE(t); - std::shared_ptr nt = normalizer.normalize(*t); + std::shared_ptr nt = normalize(*t); REQUIRE(nt); - CHECK("any" == toString(normalizer.typeFromNormal(*nt))); + CHECK("any" == toString(typeFromNormal(*nt))); } TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union") @@ -837,10 +884,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union") TypeId u = arena.addType(UnionType{{getBuiltins()->numberType, t}}); asMutable(t)->ty.emplace(IntersectionType{{getBuiltins()->anyType, u}}); - std::shared_ptr nt = normalizer.normalize(t); + std::shared_ptr nt = normalize(t); REQUIRE(nt); - CHECK("number" == toString(normalizer.typeFromNormal(*nt))); + CHECK("number" == toString(typeFromNormal(*nt))); } TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union_of_intersection") @@ -851,9 +898,9 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_union_of_intersection") TypeId unionTy = arena.addType(UnionType{{getBuiltins()->stringType, intersectTy}}); asMutable(boundTy)->reassign(Type{BoundType{unionTy}}); - std::shared_ptr nt = normalizer.normalize(unionTy); + std::shared_ptr nt = normalize(unionTy); - CHECK("string" == toString(normalizer.typeFromNormal(*nt))); + CHECK("string" == toString(typeFromNormal(*nt))); } TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_intersection_of_unions") @@ -864,9 +911,9 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_intersection_of_unions") TypeId intersectionTy = arena.addType(IntersectionType{{getBuiltins()->stringType, unionTy}}); asMutable(boundTy)->reassign(Type{BoundType{intersectionTy}}); - std::shared_ptr nt = normalizer.normalize(intersectionTy); + std::shared_ptr nt = normalize(intersectionTy); - CHECK("string" == toString(normalizer.typeFromNormal(*nt))); + CHECK("string" == toString(typeFromNormal(*nt))); } TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable") @@ -903,11 +950,9 @@ 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"))); - if (FFlag::LuauSolverV2) - CHECK("{ }" == toString(normal("{} & tbl"))); - else - CHECK("{| |}" == toString(normal("{} & tbl"))); + CHECK("{ }" == toString(normal("{} & tbl"))); CHECK("never" == toString(normal("number & tbl"))); } @@ -922,25 +967,25 @@ TEST_CASE_FIXTURE(NormalizeFixture, "normalize_blocked_types") { Type blocked{BlockedType{}}; - std::shared_ptr norm = normalizer.normalize(&blocked); + std::shared_ptr norm = normalize(&blocked); - CHECK_EQ(normalizer.typeFromNormal(*norm), &blocked); + CHECK_EQ(typeFromNormal(*norm), &blocked); } TEST_CASE_FIXTURE(NormalizeFixture, "normalize_is_exactly_number") { - std::shared_ptr number = normalizer.normalize(getBuiltins()->numberType); + std::shared_ptr number = normalize(getBuiltins()->numberType); // 1. all types for which Types::number say true for, NormalizedType::isExactlyNumber should say true as well CHECK(Luau::isNumber(getBuiltins()->numberType) == number->isExactlyNumber()); // 2. isExactlyNumber should handle cases like `number & number` TypeId intersection = arena.addType(IntersectionType{{getBuiltins()->numberType, getBuiltins()->numberType}}); - std::shared_ptr normIntersection = normalizer.normalize(intersection); + std::shared_ptr normIntersection = normalize(intersection); CHECK(normIntersection->isExactlyNumber()); // 3. isExactlyNumber should reject things that are definitely not precisely numbers `number | any` TypeId yoonion = arena.addType(UnionType{{getBuiltins()->anyType, getBuiltins()->numberType}}); - std::shared_ptr unionIntersection = normalizer.normalize(yoonion); + std::shared_ptr unionIntersection = normalize(yoonion); CHECK(!unionIntersection->isExactlyNumber()); } @@ -949,7 +994,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "normalize_unknown") auto nt = toNormalizedType("Not | Not"); CHECK(nt); CHECK(nt->isUnknown()); - CHECK(toString(normalizer.typeFromNormal(*nt)) == "unknown"); + CHECK(toString(typeFromNormal(*nt)) == "unknown"); } TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props") @@ -978,18 +1023,18 @@ TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props_3") TEST_CASE_FIXTURE(NormalizeFixture, "final_types_are_cached") { - std::shared_ptr na1 = normalizer.normalize(getBuiltins()->numberType); - std::shared_ptr na2 = normalizer.normalize(getBuiltins()->numberType); + std::shared_ptr na1 = normalize(getBuiltins()->numberType); + std::shared_ptr na2 = normalize(getBuiltins()->numberType); CHECK(na1 == na2); } TEST_CASE_FIXTURE(NormalizeFixture, "non_final_types_can_be_normalized_but_are_not_cached") { - TypeId a = arena.freshType(getBuiltins(), &globalScope); + TypeId a = arena.freshType(getBuiltins(), getGlobalScope()); - std::shared_ptr na1 = normalizer.normalize(a); - std::shared_ptr na2 = normalizer.normalize(a); + std::shared_ptr na1 = normalize(a); + std::shared_ptr na2 = normalize(a); CHECK(na1 != na2); } @@ -998,9 +1043,9 @@ TEST_CASE_FIXTURE(NormalizeFixture, "intersect_with_not_unknown") { TypeId notUnknown = arena.addType(NegationType{getBuiltins()->unknownType}); TypeId type = arena.addType(IntersectionType{{getBuiltins()->numberType, notUnknown}}); - std::shared_ptr normalized = normalizer.normalize(type); + std::shared_ptr normalized = normalize(type); - CHECK("never" == toString(normalizer.typeFromNormal(*normalized.get()))); + CHECK("never" == toString(typeFromNormal(*normalized.get()))); } TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_stack_overflow_1") @@ -1013,7 +1058,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_stack_overflow_1") asMutable(t1)->ty.get_if()->props = {{"foo", Property::readonly(t2)}}; asMutable(t2)->ty.get_if()->props = {{"foo", Property::readonly(t1)}}; - std::shared_ptr normalized = normalizer.normalize(t3); + std::shared_ptr normalized = normalize(t3); CHECK(normalized); } @@ -1027,7 +1072,7 @@ TEST_CASE_FIXTURE(NormalizeFixture, "cyclic_stack_overflow_2") asMutable(t1)->ty.get_if()->props = {{"foo", Property::readonly(t3)}}; asMutable(t2)->ty.get_if()->props = {{"foo", Property::readonly(t1)}}; - std::shared_ptr normalized = normalizer.normalize(t3); + std::shared_ptr normalized = normalize(t3); CHECK(normalized); } @@ -1046,10 +1091,10 @@ TEST_CASE_FIXTURE(NormalizeFixture, "truthy_table_property_and_optional_table_wi TypeId intersection = arena.addType(IntersectionType{{t2, t1}}); - auto norm = normalizer.normalize(intersection); + auto norm = normalize(intersection); REQUIRE(norm); - TypeId ty = normalizer.typeFromNormal(*norm); + TypeId ty = typeFromNormal(*norm); CHECK("{ x: number }" == toString(ty)); } @@ -1059,15 +1104,15 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_and_not_truthy") {FFlag::LuauSolverV2, true}, // Only because it affects the stringification of free types }; - TypeId freeTy = arena.freshType(getBuiltins(), &globalScope); + TypeId freeTy = arena.freshType(getBuiltins(), getGlobalScope()); TypeId notTruthy = arena.addType(NegationType{getBuiltins()->truthyType}); // ~~(false?) TypeId intersectionTy = arena.addType(IntersectionType{{freeTy, notTruthy}}); // 'a & ~~(false?) - auto norm = normalizer.normalize(intersectionTy); + auto norm = normalize(intersectionTy); REQUIRE(norm); - TypeId result = normalizer.typeFromNormal(*norm); + TypeId result = typeFromNormal(*norm); CHECK("'a & (false?)" == toString(result)); } @@ -1079,17 +1124,17 @@ TEST_CASE_FIXTURE(NormalizeFixture, "free_type_intersection_ordering") {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, }; - TypeId freeTy = arena.freshType(getBuiltins(), &globalScope); + TypeId freeTy = arena.freshType(getBuiltins(), getGlobalScope()); TypeId orderA = arena.addType(IntersectionType{{freeTy, getBuiltins()->stringType}}); - auto normA = normalizer.normalize(orderA); + auto normA = normalize(orderA); REQUIRE(normA); - CHECK_EQ("'a & string", toString(normalizer.typeFromNormal(*normA))); + CHECK_EQ("'a & string", toString(typeFromNormal(*normA))); TypeId orderB = arena.addType(IntersectionType{{getBuiltins()->stringType, freeTy}}); - auto normB = normalizer.normalize(orderB); + auto normB = normalize(orderB); REQUIRE(normB); // Prior to LuauNormalizationReorderFreeTypeIntersect this became `never` :skull: - CHECK_EQ("'a & string", toString(normalizer.typeFromNormal(*normB))); + CHECK_EQ("'a & string", toString(typeFromNormal(*normB))); } TEST_CASE_FIXTURE(BuiltinsFixture, "normalizer_should_be_able_to_detect_cyclic_tables_and_not_stack_overflow") diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index d5634fe5..ada4a52c 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -27,8 +27,6 @@ LUAU_FASTFLAG(LuauIceLess) LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauDontDynamicallyCreateRedundantSubtypeConstraints) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) @@ -299,8 +297,6 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5)) // These flags are required to surface the problem. {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, // And this flag is the one that fixes it. {FFlag::LuauSimplifyAnyAndUnion, true}, @@ -383,8 +379,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai {FFlag::LuauSolverV2, true}, {FFlag::LuauLimitDynamicConstraintSolving3, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true}, }; @@ -568,8 +562,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "test_generic_pruning_recursion_limit") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauReduceSetTypeStackPressure, true}, }; @@ -583,14 +575,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "test_generic_pruning_recursion_limit") CHECK_EQ("({ read Do: { read Re: { read Mi: a } } }) -> ()", toString(requireType("get"))); } -TEST_CASE_FIXTURE(BuiltinsFixture, "unification_runs_a_limited_number_of_iterations_before_stopping" * doctest::timeout(2.0)) +TEST_CASE_FIXTURE(BuiltinsFixture, "unification_runs_a_limited_number_of_iterations_before_stopping" * doctest::timeout(4.0)) { ScopedFastFlag sff[] = { // These are necessary to trigger the bug {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, // This is the fix {FFlag::LuauLimitUnification, true} diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index ff970505..c4bfef3e 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -17,8 +17,6 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) @@ -1748,8 +1746,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; TypeId argTy = arena.freshType(getBuiltins(), moduleScope.get()); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index 6fcea56b..f4184f38 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTFLAG(LuauSolverAgnosticSetType) TEST_SUITE_BEGIN("ToString"); @@ -69,14 +70,12 @@ 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}; - if (FFlag::LuauSolverV2) - CHECK_EQ("t1 where t1 = {| self: t1 |}", toString(&cyclicTable)); - else - CHECK_EQ("t1 where t1 = { self: t1 }", toString(&cyclicTable)); + CHECK_EQ("t1 where t1 = {| self: t1 |}", toString(&cyclicTable)); } TEST_CASE_FIXTURE(Fixture, "named_table") @@ -90,26 +89,22 @@ TEST_CASE_FIXTURE(Fixture, "named_table") TEST_CASE_FIXTURE(Fixture, "empty_table") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local a: {} )"); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ }", toString(requireType("a"))); - else - CHECK_EQ("{| |}", toString(requireType("a"))); + CHECK_EQ("{ }", toString(requireType("a"))); // Should stay the same with useLineBreaks enabled ToStringOptions opts; opts.useLineBreaks = true; - if (FFlag::LuauSolverV2) - CHECK_EQ("{ }", toString(requireType("a"), opts)); - else - CHECK_EQ("{| |}", toString(requireType("a"), opts)); + CHECK_EQ("{ }", toString(requireType("a"), opts)); } 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 } )"); @@ -117,24 +112,14 @@ TEST_CASE_FIXTURE(Fixture, "table_respects_use_line_break") ToStringOptions opts; opts.useLineBreaks = true; - if (FFlag::LuauSolverV2) - CHECK_EQ( - "{\n" - " anotherProp: number,\n" - " prop: string,\n" - " thirdProp: boolean\n" - "}", - toString(requireType("a"), opts) - ); - else - CHECK_EQ( - "{|\n" - " anotherProp: number,\n" - " prop: string,\n" - " thirdProp: boolean\n" - "|}", - toString(requireType("a"), opts) - ); + CHECK_EQ( + "{\n" + " anotherProp: number,\n" + " prop: string,\n" + " thirdProp: boolean\n" + "}", + toString(requireType("a"), opts) + ); } TEST_CASE_FIXTURE(Fixture, "nil_or_nil_is_nil_not_question_mark") @@ -161,13 +146,11 @@ 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})}; - if (FFlag::LuauSolverV2) - CHECK_EQ("{ @metatable {| |}, {| |} }", toString(&mtv)); - else - CHECK_EQ("{ @metatable { }, { } }", toString(&mtv)); + CHECK_EQ("{ @metatable {| |}, {| |} }", toString(&mtv)); } TEST_CASE_FIXTURE(Fixture, "named_metatable") @@ -197,6 +180,10 @@ 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 = {} @@ -233,8 +220,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "exhaustive_toString_of_cyclic_table") { CHECK_EQ( "t2 where " - "t1 = { __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 } ; " - "t2 = { @metatable t1, {| x: number, y: number, z: number |} }", + "t1 = {| __index: t1, __mul: ((t2, number) -> t2) & ((t2, t2) -> t2), new: () -> t2 |} ; " + "t2 = { @metatable t1, { x: number, y: number, z: number } }", a ); } @@ -350,6 +337,7 @@ 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}; @@ -359,14 +347,12 @@ TEST_CASE_FIXTURE(Fixture, "quit_stringifying_table_type_when_length_is_exceeded ToStringOptions o; o.exhaustive = false; o.maxTableLength = 40; - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 10 more ... |}"); - else - CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 10 more ... }"); + CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 10 more ... |}"); } 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}; @@ -376,14 +362,12 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_is_still_capped_when_exhaust ToStringOptions o; o.exhaustive = true; o.maxTableLength = 40; - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 2 more ... |}"); - else - CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 2 more ... }"); + CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 2 more ... |}"); } 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 @@ -453,6 +437,7 @@ 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}; @@ -461,10 +446,7 @@ TEST_CASE_FIXTURE(Fixture, "stringifying_table_type_correctly_use_matching_table ToStringOptions o; o.maxTableLength = 40; - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 5 more ... }"); - else - CHECK_EQ(toString(&tv, o), "{| a: number, b: number, c: number, d: number, e: number, ... 5 more ... |}"); + CHECK_EQ(toString(&tv, o), "{ a: number, b: number, c: number, d: number, e: number, ... 5 more ... }"); } TEST_CASE_FIXTURE(Fixture, "stringifying_cyclic_union_type_bails_early") @@ -489,16 +471,14 @@ 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}; CHECK_EQ("{string}", toString(Type{ttv})); ttv.props["A"] = {getBuiltins()->numberType}; - if (FFlag::LuauSolverV2) - CHECK_EQ("{ [number]: string, A: number }", toString(Type{ttv})); - else - CHECK_EQ("{| [number]: string, A: number |}", toString(Type{ttv})); + CHECK_EQ("{ [number]: string, A: number }", toString(Type{ttv})); ttv.props.clear(); ttv.state = TableState::Unsealed; @@ -638,6 +618,7 @@ 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; @@ -655,16 +636,8 @@ TEST_CASE_FIXTURE(Fixture, "toString_the_boundTo_table_type_contained_within_a_T TypePackVar tpv2{TypePack{{&tv2}}}; - if (FFlag::LuauSolverV2) - { - CHECK_EQ("{ hello: number, world: number }", toString(&tpv1)); - CHECK_EQ("{ hello: number, world: number }", toString(&tpv2)); - } - else - { - CHECK_EQ("{| hello: number, world: number |}", toString(&tpv1)); - CHECK_EQ("{| hello: number, world: number |}", toString(&tpv2)); - } + CHECK_EQ("{ hello: number, world: number }", toString(&tpv1)); + CHECK_EQ("{ hello: number, world: number }", toString(&tpv2)); } TEST_CASE_FIXTURE(Fixture, "no_parentheses_around_return_type_if_pack_has_an_empty_head_link") @@ -889,6 +862,7 @@ 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}} @@ -906,15 +880,15 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") "type, and `string` is not exactly `number`"; else expected = "Type\n\t" - "'{| a: number, b: string, c: {| d: string |} |}'\n" + "'{ a: number, b: string, c: { d: string } }'\n" "could not be converted into\n\t" - "'{| a: number, b: string, c: {| d: number |} |}'\n" + "'{ a: number, b: string, c: { d: number } }'\n" "caused by:\n " "Property 'c' is not compatible.\n" "Type\n\t" - "'{| d: string |}'\n" + "'{ d: string }'\n" "could not be converted into\n\t" - "'{| d: number |}'\n" + "'{ d: number }'\n" "caused by:\n " "Property 'd' is not compatible.\n" "Type 'string' could not be converted into 'number' in an invariant context"; @@ -961,6 +935,7 @@ 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}); @@ -976,10 +951,7 @@ TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack") packPtr->head[0] = theTable; - if (FFlag::LuauSolverV2) - CHECK("tp1 where tp1 = { read BaseField: unknown, read BaseMethod: (tp1) -> () }, number" == toString(thePack)); - else - CHECK("tp1 where tp1 = {| BaseField: unknown, BaseMethod: (tp1) -> () |}, number" == toString(thePack)); + CHECK("tp1 where tp1 = { read BaseField: unknown, read BaseMethod: (tp1) -> () }, number" == toString(thePack)); } TEST_CASE_FIXTURE(Fixture, "correct_stringification_user_defined_type_functions") diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 96d6d0eb..4b464a31 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -18,10 +18,9 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauDoNotBlockOnStuckTypeFunctions) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauRefineOccursCheckDirectRecursion) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) +LUAU_FASTFLAG(LuauRawGetHandlesNil) struct TypeFunctionFixture : Fixture { @@ -730,8 +729,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -1338,15 +1335,17 @@ 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 local function ok(idx: rawType): string | number return idx end - type errType = rawget + type stringType = rawget )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == "Property '\"a\" | \"d\"' does not exist on type 'MyObject'"); + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireTypeAlias("stringType")) == "string?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_union_type_indexee") @@ -1354,16 +1353,18 @@ 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} type rawTypeA = rawget local function ok(idx: rawTypeA): string | number return idx end - type errType = rawget + type numberType = rawget )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject | MyObject2'"); + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireTypeAlias("numberType")) == "number?"); } TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_index_metatables") @@ -1371,19 +1372,21 @@ 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 }) type exampleTy2 = rawget local function ok(idx: exampleTy2): number return idx end local exampleClass3 = setmetatable({ Bar = 5 }, { __index = exampleClass }) - type errType = rawget - type errType2 = rawget + type nilType = rawget + type numberType = rawget )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK(toString(result.errors[0]) == "Property '\"Foo\"' does not exist on type 'exampleClass3'"); - CHECK(toString(result.errors[1]) == "Property '\"Bar\" | \"Foo\"' does not exist on type 'exampleClass3'"); + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireTypeAlias("nilType")) == "nil"); + CHECK(toString(requireTypeAlias("numberType")) == "number?"); } TEST_CASE_FIXTURE(ExternTypeFixture, "rawget_type_function_errors_w_extern_types") @@ -1399,6 +1402,22 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "rawget_type_function_errors_w_extern_types CHECK(toString(result.errors[0]) == "Property '\"BaseField\"' does not exist on type 'BaseClass'"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_queried_key_absent") +{ + if (!FFlag::LuauSolverV2) + return; + + ScopedFastFlag sff{FFlag::LuauRawGetHandlesNil, true}; + + CheckResult result = check(R"( + type MyObject = {a: string} + type T = rawget + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + CHECK(toString(requireTypeAlias("T")) == "nil"); +} + TEST_CASE_FIXTURE(Fixture, "fuzz_len_type_function_follow") { // Should not fail assertions @@ -1684,8 +1703,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fully_dispatch_type_function_that_is_paramet ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -1712,8 +1729,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "undefined_add_application") ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -1770,10 +1785,8 @@ struct TFFixture TypeCheckLimits limits; TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; - const ScopedFastFlag sff[3] = { + const ScopedFastFlag sff[1] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; BuiltinTypeFunctions builtinTypeFunctions; @@ -1838,8 +1851,6 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_solved_tf_is_solved") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; TypeId a = arena->addType(GenericType{"A"}); @@ -1861,8 +1872,6 @@ TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_stuck_tf_is_stuck") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.bufferType, builtinTypes_.booleanType}}); @@ -1904,8 +1913,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_functions_should_not_get_stuck_or") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauForceSimplifyConstraint2, true}, {FFlag::LuauDoNotBlockOnStuckTypeFunctions, true}, }; diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 62f4b24b..5b697bd0 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -10,9 +10,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauTypeFunNoScopeMapRef) +LUAU_FASTFLAG(LuauInstantiateResolvedTypeFunctions) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -2373,8 +2372,6 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -2483,4 +2480,108 @@ end LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1887_udtf_with_optional_missing") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInstantiateResolvedTypeFunctions, true}, + }; + + CheckResult results = check(R"( + type function create_table_with_key() + local tbl = types.newtable() + tbl:setproperty(types.singleton "key", types.unionof(types.string, types.singleton(nil))) + return tbl + end + local a: create_table_with_key = {} + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + LUAU_REQUIRE_ERROR(results, UnappliedTypeFunction); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1887_udtf_with_optional_present") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInstantiateResolvedTypeFunctions, true}, + }; + + CheckResult results = check(R"( + type function create_table_with_key() + local tbl = types.newtable() + tbl:setproperty(types.singleton "key", types.unionof(types.string, types.singleton(nil))) + return tbl + end + local a: create_table_with_key = { key = "123" } + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + LUAU_REQUIRE_ERROR(results, UnappliedTypeFunction); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1887_udtf_table_mismatch") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInstantiateResolvedTypeFunctions, true}, + }; + + CheckResult results = check(R"( + type function create_table_with_key() + local tbl = types.newtable() + tbl:setproperty(types.singleton "key", types.optional(types.number)) + return tbl + end + local my_tbl: create_table_with_key = {key = "123"} + )"); + + LUAU_REQUIRE_ERROR_COUNT(2, results); + CHECK(get(results.errors[0])); + auto err = get(results.errors[1]); + REQUIRE(err); + CHECK_EQ("string", toString(err->givenType)); + CHECK_EQ("number?", toString(err->wantedType)); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1887_basic_mismatch") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInstantiateResolvedTypeFunctions, true}, + }; + + CheckResult results = check(R"( + type function foo() + return types.number + end + local f: foo = "123" + )"); + + LUAU_REQUIRE_ERROR_COUNT(2, results); + CHECK(get(results.errors[0])); + auto err = get(results.errors[1]); + REQUIRE(err); + CHECK_EQ("string", toString(err->givenType)); + CHECK_EQ("number", toString(err->wantedType)); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1887_basic_match") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInstantiateResolvedTypeFunctions, true}, + }; + + CheckResult results = check(R"( + type function foo() + return types.string + end + local f: foo = "123" + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + LUAU_REQUIRE_ERROR(results, UnappliedTypeFunction); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 746c9ed3..a578c809 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -10,6 +10,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TypeAliases"); @@ -308,6 +309,8 @@ 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 } type Wrapped = Table @@ -324,6 +327,8 @@ 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 } type Wrapped = (Table) -> string @@ -334,10 +339,7 @@ TEST_CASE_FIXTURE(Fixture, "stringify_type_alias_of_recursive_template_table_typ TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); - if (FFlag::LuauSolverV2) - CHECK_EQ("t1 where t1 = ({ a: t1 }) -> string", toString(tm->wantedType)); - else - CHECK_EQ("t1 where t1 = ({| a: t1 |}) -> string", toString(tm->wantedType)); + CHECK_EQ("t1 where t1 = ({ a: t1 }) -> string", toString(tm->wantedType)); CHECK_EQ(getBuiltins()->numberType, tm->givenType); } @@ -587,6 +589,8 @@ 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? } return {} @@ -612,6 +616,8 @@ 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? } return {} @@ -653,8 +659,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_of_an_imported_recursive_generic_ } else { - CHECK_EQ(toString(*ty1, {true}), "t1 where t1 = {| C: t1?, a: T, b: U |}"); - CHECK_EQ(toString(*ty2, {true}), "{| C: t1, a: U, b: T |} where t1 = {| C: t1, a: U, b: T |}?"); + CHECK_EQ(toString(*ty1, {true}), "t1 where t1 = { C: t1?, a: T, b: U }"); + CHECK_EQ(toString(*ty2, {true}), "{ C: t1, a: U, b: T } where t1 = { C: t1, a: U, b: T }?"); } } @@ -832,6 +838,8 @@ 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() local y: FutureType = {}::any @@ -841,10 +849,7 @@ TEST_CASE_FIXTURE(Fixture, "forward_declared_alias_is_not_clobbered_by_prior_uni local d: FutureType = { smth = true } -- missing error, 'd' is resolved to 'any' )"); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ foo: number }", toString(requireType("d"), {true})); - else - CHECK_EQ("{| foo: number |}", toString(requireType("d"), {true})); + CHECK_EQ("{ foo: number }", toString(requireType("d"), {true})); LUAU_REQUIRE_ERROR_COUNT(1, result); } diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 93e2bc14..748be8ed 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -14,6 +14,8 @@ LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauSolverAgnosticSetType) TEST_SUITE_BEGIN("BuiltinTests"); @@ -372,6 +374,7 @@ 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} @@ -384,8 +387,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables") )"); LUAU_REQUIRE_NO_ERRORS(result); - - CHECK("{ @metatable { }, A } | { @metatable { }, B }" == toString(requireTypeAlias("X"))); + if (FFlag::LuauSolverV2) + CHECK("{ @metatable { }, A } | { @metatable { }, B }" == toString(requireTypeAlias("X"))); + else + CHECK("{ @metatable {| |}, A } | { @metatable {| |}, B }" == toString(requireTypeAlias("X"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_2_args_overload") @@ -414,19 +419,18 @@ 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) )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ [number]: boolean | number | string, n: number }", toString(requireType("t"))); - else - CHECK_EQ("{| [number]: boolean | number | string, n: number |}", toString(requireType("t"))); + CHECK_EQ("{ [number]: boolean | number | string, n: number }", toString(requireType("t"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_variadic") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( --!strict function f(): (string, ...number) @@ -437,33 +441,25 @@ local t = table.pack(f()) )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ [number]: number | string, n: number }", toString(requireType("t"))); - else - CHECK_EQ("{| [number]: number | string, n: number |}", toString(requireType("t"))); + CHECK_EQ("{ [number]: number | string, n: number }", toString(requireType("t"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce") { + ScopedFastFlag sff{FFlag::LuauSolverAgnosticStringification, true}; CheckResult result = check(R"( local t = table.pack(1, 2, true) )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ [number]: boolean | number, n: number }", toString(requireType("t"))); - else - CHECK_EQ("{| [number]: boolean | number, n: number |}", toString(requireType("t"))); + CHECK_EQ("{ [number]: boolean | number, n: number }", toString(requireType("t"))); result = check(R"( local t = table.pack("a", "b", "c") )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t"))); - else - CHECK_EQ("{| [number]: string, n: number |}", toString(requireType("t"))); + CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t"))); } TEST_CASE_FIXTURE(BuiltinsFixture, "gcinfo") @@ -1112,6 +1108,7 @@ 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"} @@ -1136,7 +1133,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_freeze_is_generic") if (FFlag::LuauSolverV2) CHECK("Key 'b' not found in table '{ read a: number }'" == toString(result.errors[0])); else - CHECK_EQ("Key 'b' not found in table '{| a: number |}'", toString(result.errors[0])); + CHECK_EQ("Key 'b' not found in table '{ a: number }'", toString(result.errors[0])); CHECK(Location({13, 18}, {13, 23}) == result.errors[0].location); if (FFlag::LuauSolverV2) @@ -1592,6 +1589,9 @@ 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} + }; CheckResult result = check(R"( local t1 = {} t1.x = 5 @@ -1602,15 +1602,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_dot_clone_type_states") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauTableCloneClonesType3) + if (FFlag::LuauSolverV2) { CHECK_EQ(toString(requireType("t1"), {true}), "{ x: number, z: number }"); CHECK_EQ(toString(requireType("t2"), {true}), "{ x: number, y: number }"); } else { - CHECK_EQ(toString(requireType("t1"), {true}), "{ x: number, y: number, z: number }"); - CHECK_EQ(toString(requireType("t2"), {true}), "{ x: number, y: number, z: number }"); + CHECK_EQ(toString(requireType("t1"), {true}), "{| x: number, z: number |}"); + CHECK_EQ(toString(requireType("t2"), {true}), "{| x: number, y: number |}"); } } diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 40081a8c..a32f72c9 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -27,11 +27,10 @@ LUAU_FASTFLAG(LuauCollapseShouldNotCrash) LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -1686,6 +1685,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite") TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite_2") { + ScopedFastFlag _[] = { + {FFlag::LuauEagerGeneralization4, true}, + }; CheckResult result = check(R"( local t: { f: ((x: number) -> number)? } = {} @@ -1700,23 +1702,11 @@ t.f = function(x) end )"); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauSolverV2) { LUAU_CHECK_ERROR_COUNT(2, result); LUAU_CHECK_ERROR(result, WhereClauseNeeded); // x2 } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ( - toString(result.errors[0]), - R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)" - ); - CHECK_EQ( - toString(result.errors[1]), - R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)" - ); - } else { LUAU_REQUIRE_ERROR_COUNT(2, result); @@ -2270,6 +2260,7 @@ 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 @@ -2287,12 +2278,13 @@ TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_no else { // note that b is not in the generic list; it is free, the unconstrained type of `bar`. - CHECK(toString(requireType("foo")) == "(a) -> b"); + CHECK(toString(requireType("foo")) == "(a) -> 'b"); } } 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 @@ -2346,10 +2338,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu LUAU_REQUIRE_ERROR_COUNT(2, result); - const std::string expected = R"(Type '{ x: number }' could not be converted into 'vec2?' + const std::string expected = R"(Type '{| x: number |}' could not be converted into 'vec2?' caused by: None of the union options are compatible. For example: -Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')"; +Table type '{| x: number |}' not compatible with type 'vec2' because the former is missing field 'y')"; CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ("Type 'vec2' could not be converted into 'number'", toString(result.errors[1])); } @@ -2385,6 +2377,7 @@ 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() @@ -2396,7 +2389,7 @@ TEST_CASE_FIXTURE(Fixture, "attempt_to_call_an_intersection_of_tables") if (FFlag::LuauSolverV2) CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number } & { y: string }"); else - CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type {| x: number |}"); + CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type { x: number }"); } TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_with_call_metamethod") @@ -2605,8 +2598,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_return_type") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; // CLI-114134: This test: @@ -2634,8 +2625,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -2940,7 +2929,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") { // The new solver should ideally be able to do better here, but this is no worse than the old solver. - if (FFlag::LuauTrackFreeInteriorTypePacks) + if (FFlag::LuauEagerGeneralization4) { LUAU_REQUIRE_ERROR_COUNT(2, result); @@ -2953,10 +2942,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") REQUIRE(tm2); CHECK(toString(tm2->wantedType) == "string"); - if (FFlag::LuauEagerGeneralization4) - CHECK(toString(tm2->givenType) == "unknown & ~(false?)"); - else - CHECK(toString(tm2->givenType) == "~(false?)"); + CHECK(toString(tm2->givenType) == "unknown & ~(false?)"); } else { diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index b13c8ee3..970db638 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -14,10 +14,9 @@ LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) using namespace Luau; @@ -67,6 +66,7 @@ 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 @@ -80,7 +80,7 @@ TEST_CASE_FIXTURE(Fixture, "unions_and_generics") if (FFlag::LuauSolverV2) CHECK_EQ("number | {number}", toString(requireType("res"))); else // in the old solver, this just totally falls apart - CHECK_EQ("a", toString(requireType("res"))); + CHECK_EQ("'a", toString(requireType("res"))); } TEST_CASE_FIXTURE(Fixture, "check_generic_typepack_function") @@ -901,6 +901,7 @@ 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 @@ -932,7 +933,7 @@ y.a.c = y const std::string expected = R"(Type 'y' could not be converted into 'T' caused by: Property 'a' is not compatible. -Type '{ c: T?, d: number }' could not be converted into 'U' +Type '{| c: T?, d: number |}' could not be converted into 'U' caused by: Property 'd' is not compatible. Type 'number' could not be converted into 'string' in an invariant context)"; @@ -1448,6 +1449,7 @@ 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 @@ -1464,10 +1466,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "infer_generic_function_function_argument_3") if (FFlag::LuauSolverV2) REQUIRE_EQ("{ c: number, s: number } | { c: number, s: number }", toString(requireType("r"))); else - REQUIRE_EQ("{ c: number, s: number }", toString(requireType("r"))); + REQUIRE_EQ("{| c: number, s: number |}", toString(requireType("r"))); } -TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded") +TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded_pt_1") { CheckResult result = check(R"( local g12: ((T, (T) -> T) -> T) & ((T, T, (T, T) -> T) -> T) @@ -1477,15 +1479,22 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded" )"); LUAU_REQUIRE_NO_ERRORS(result); +} - result = check(R"( +TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_overloaded_pt_2") +{ + ScopedFastFlag _[] = { + {FFlag::LuauSubtypingReportGenericBoundMismatches, true}, + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, + }; + CheckResult result = check(R"( local g12: ((T, (T) -> T) -> T) & ((T, T, (T, T) -> T) -> T) g12({x=1}, function(x) return {x=-x.x} end) g12({x=1}, {x=2}, function(x, y) return {x=x.x + y.x} end) )"); - if (FFlag::LuauSubtypingReportGenericBoundMismatches) + if (FFlag::LuauSolverV2) LUAU_REQUIRE_ERROR_COUNT(2, result); // FIXME CLI-161355 else LUAU_REQUIRE_NO_ERRORS(result); @@ -1497,8 +1506,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") { ScopedFastFlag _[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result; @@ -1835,8 +1842,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_packs_shouldnt_be_bound_to_themselves") ScopedFastFlag flags[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 980c6e3e..46bb106f 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -11,6 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -162,6 +163,7 @@ 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}} @@ -176,7 +178,7 @@ TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_with_property_guarante if (FFlag::LuauSolverV2) CHECK("(A & B) -> { y: number }" == toString(requireType("f"))); else - CHECK("(A & B) -> {| y: number |} & {| y: number |}" == toString(requireType("f"))); + CHECK("(A & B) -> { y: number } & { y: number }" == toString(requireType("f"))); } TEST_CASE_FIXTURE(Fixture, "index_on_an_intersection_type_works_at_arbitrary_depth") @@ -646,6 +648,7 @@ 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 @@ -671,16 +674,14 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") else { const std::string expected = - R"(Type - '{| p: number?, q: number?, r: number? |} & {| p: number?, q: string? |}' -could not be converted into - '{| p: nil |}'; none of the intersection parts are compatible)"; + R"(Type '{ p: number?, q: number?, r: number? } & { p: number?, q: string? }' could not be converted into '{ p: nil }'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } } 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 @@ -713,9 +714,9 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") { LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type - '{| p: number?, q: any |} & {| p: unknown, q: string? |}' + '{ p: number?, q: any } & { p: unknown, q: string? }' could not be converted into - '{| p: string?, q: number? |}'; none of the intersection parts are compatible)"; + '{ p: string?, q: number? }'; none of the intersection parts are compatible)"; CHECK_EQ(expected, toString(result.errors[0])); } } @@ -734,6 +735,7 @@ 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 @@ -791,9 +793,9 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_returning_intersections") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ( R"(Type - '((number?) -> {| p: number |} & {| q: number |}) & ((string?) -> {| p: number |} & {| r: number |})' + '((number?) -> { p: number } & { q: number }) & ((string?) -> { p: number } & { r: number })' could not be converted into - '(number?) -> {| p: number, q: number, r: number |}'; none of the intersection parts are compatible)", + '(number?) -> { p: number, q: number, r: number }'; none of the intersection parts are compatible)", toString(result.errors[0]) ); } diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 229050a4..efa72260 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -15,6 +15,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -942,6 +943,7 @@ 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; @@ -959,7 +961,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_68448_iterators_need_not_accept_nil") LUAU_REQUIRE_NO_ERRORS(result); // HACK (CLI-68453): We name this inner table `enum`. For now, use the // exhaustive switch to see past it. - CHECK(toString(requireType("makeEnum"), {true}) == "({a}) -> {| [a]: a |}"); + CHECK(toString(requireType("makeEnum"), {true}) == "({a}) -> { [a]: a }"); } TEST_CASE_FIXTURE(Fixture, "iterate_over_free_table") diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 49b9df97..d6e07a6a 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -8,6 +8,7 @@ #include "Fixture.h" +#include "Luau/VisitType.h" #include "doctest.h" LUAU_FASTFLAG(LuauInstantiateInSubtyping) @@ -19,6 +20,8 @@ LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTINT(LuauSolverConstraintLimit) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauSolverAgnosticSetType) using namespace Luau; @@ -161,6 +164,7 @@ 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 { @@ -182,10 +186,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_table_freeze") ModulePtr a = getFrontend().moduleResolver.getModule("game/A"); REQUIRE(a != nullptr); // confirm that no cross-module mutation happened here! - if (FFlag::LuauSolverV2) - CHECK(toString(a->returnType) == "{ a: number }"); - else - CHECK(toString(a->returnType) == "{| a: number |}"); + CHECK(toString(a->returnType) == "{ a: number }"); ModulePtr b = getFrontend().moduleResolver.getModule("game/B"); REQUIRE(b != nullptr); @@ -193,7 +194,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cross_module_table_freeze") if (FFlag::LuauSolverV2) CHECK(toString(b->returnType) == "{ read a: number }"); else - CHECK(toString(b->returnType) == "{| a: number |}"); + CHECK(toString(b->returnType) == "{ a: number }"); } TEST_CASE_FIXTURE(Fixture, "type_error_of_unknown_qualified_type") @@ -271,6 +272,7 @@ 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 } )"; @@ -281,10 +283,7 @@ local tbl: string = require(game.A) CheckResult result = getFrontend().check("game/B"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2) - CHECK_EQ("Type '{ def: number }' could not be converted into 'string'", toString(result.errors[0])); - else - CHECK_EQ("Type '{| def: number |}' could not be converted into 'string'", toString(result.errors[0])); + CHECK_EQ("Type '{ def: number }' could not be converted into 'string'", toString(result.errors[0])); } TEST_CASE_FIXTURE(Fixture, "bound_free_table_export_is_ok") diff --git a/tests/TypeInfer.negations.test.cpp b/tests/TypeInfer.negations.test.cpp index bb559b35..b68930a9 100644 --- a/tests/TypeInfer.negations.test.cpp +++ b/tests/TypeInfer.negations.test.cpp @@ -10,8 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) namespace { @@ -56,8 +54,6 @@ TEST_CASE_FIXTURE(Fixture, "cofinite_strings_can_be_compared_for_equality") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 09fd096a..2ed60f79 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -15,7 +15,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TypeInferOOP"); @@ -333,6 +333,7 @@ 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} @@ -346,7 +347,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "augmenting_an_unsealed_table_with_a_metatabl if (FFlag::LuauSolverV2) CHECK("{ @metatable { number: number }, { method: (unknown) -> string } }" == toString(requireType("B"), {true})); else - CHECK("{ @metatable { number: number }, { method: (a) -> string } }" == toString(requireType("B"), {true})); + CHECK("{ @metatable {| number: number |}, {| method: (a) -> string |} }" == toString(requireType("B"), {true})); } TEST_CASE_FIXTURE(BuiltinsFixture, "react_style_oo") @@ -556,8 +557,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -590,8 +589,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern_2") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index f15e137b..6ea9d68b 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -19,18 +19,22 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TypeInferOperators"); TEST_CASE_FIXTURE(Fixture, "or_joins_types") { + ScopedFastFlag _[] = { + {FFlag::LuauEagerGeneralization4, true}, + }; CheckResult result = check(R"( local s = "a" or 10 local x:string|number = s )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauSolverV2) { // FIXME: Regression CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); @@ -45,6 +49,9 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types") TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") { + ScopedFastFlag _[] = { + {FFlag::LuauEagerGeneralization4, true}, + }; CheckResult result = check(R"( local s = "a" or 10 local x:number|string = s @@ -52,7 +59,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauSolverV2) { // FIXME: Regression. CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); @@ -67,13 +74,16 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union") { + ScopedFastFlag _[] = { + {FFlag::LuauEagerGeneralization4, true}, + }; CheckResult result = check(R"( local s = "a" or "b" local x:string = s )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauSolverV2) { // FIXME: Regression CHECK("(string & ~(false?)) | string" == toString(requireType("s"))); diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index bf36e22a..72a542c8 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -278,6 +278,7 @@ 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} @@ -299,10 +300,10 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_x_not_equal_to_nil") } else { - CHECK_EQ("{| x: string, y: number |}", toString(requireTypeAtPosition({5, 28}))); + CHECK_EQ("{ x: string, y: number }", toString(requireTypeAtPosition({5, 28}))); // Should be {| x: nil, y: nil |} - CHECK_EQ("{| x: nil, y: nil |} | {| x: string, y: number |}", toString(requireTypeAtPosition({7, 28}))); + CHECK_EQ("{ x: nil, y: nil } | { x: string, y: number }", toString(requireTypeAtPosition({7, 28}))); } } @@ -838,6 +839,7 @@ 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} @@ -853,9 +855,9 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = R"(Type - '{| x: number? |}' + '{ x: number? }' could not be converted into - '{| x: number |}' + '{ x: number }' caused by: Property 'x' is not compatible. Type 'number?' could not be converted into 'number' in an invariant context)"; diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 242e8934..e6d470c2 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -17,11 +17,13 @@ LUAU_FASTFLAG(LuauRefineNoRefineAlways) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) using namespace Luau; @@ -87,47 +89,53 @@ struct MagicInstanceIsA final : MagicFunction struct RefinementExternTypeFixture : BuiltinsFixture { - RefinementExternTypeFixture() + RefinementExternTypeFixture() = default; + + Frontend& getFrontend() override { + if (frontend) + return *frontend; + + Frontend& f = BuiltinsFixture::getFrontend(); TypeArena& arena = getFrontend().globals.globalTypes; NotNull scope{getFrontend().globals.globalScope.get()}; - std::optional rootSuper = std::make_optional(getBuiltins()->externType); + std::optional rootSuper = std::make_optional(f.builtinTypes->externType); unfreeze(arena); TypeId vec3 = arena.addType(ExternType{"Vector3", {}, rootSuper, std::nullopt, {}, nullptr, "Test", {}}); getMutable(vec3)->props = { - {"X", Property{getBuiltins()->numberType}}, - {"Y", Property{getBuiltins()->numberType}}, - {"Z", Property{getBuiltins()->numberType}}, + {"X", Property{f.builtinTypes->numberType}}, + {"Y", Property{f.builtinTypes->numberType}}, + {"Z", Property{f.builtinTypes->numberType}}, }; TypeId inst = arena.addType(ExternType{"Instance", {}, rootSuper, std::nullopt, {}, nullptr, "Test", {}}); - TypePackId isAParams = arena.addTypePack({inst, getBuiltins()->stringType}); - TypePackId isARets = arena.addTypePack({getBuiltins()->booleanType}); + TypePackId isAParams = arena.addTypePack({inst, f.builtinTypes->stringType}); + TypePackId isARets = arena.addTypePack({f.builtinTypes->booleanType}); TypeId isA = arena.addType(FunctionType{isAParams, isARets}); getMutable(isA)->magic = std::make_shared(); getMutable(inst)->props = { - {"Name", Property{getBuiltins()->stringType}}, + {"Name", Property{f.builtinTypes->stringType}}, {"IsA", Property{isA}}, }; TypeId scriptConnection = arena.addType(ExternType("ExternScriptConnection", {}, inst, std::nullopt, {}, nullptr, "Test", {})); TypePackId disconnectArgs = arena.addTypePack({scriptConnection}); - TypeId disconnect = arena.addType(FunctionType{disconnectArgs, getBuiltins()->emptyTypePack}); + TypeId disconnect = arena.addType(FunctionType{disconnectArgs, f.builtinTypes->emptyTypePack}); getMutable(scriptConnection)->props = { {"Disconnect", Property{disconnect}}, }; - TypeId folder = getFrontend().globals.globalTypes.addType(ExternType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); - TypeId part = getFrontend().globals.globalTypes.addType(ExternType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); + TypeId folder = f.globals.globalTypes.addType(ExternType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); + TypeId part = f.globals.globalTypes.addType(ExternType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); getMutable(part)->props = { {"Position", Property{vec3}}, }; - TypeId optionalPart = arena.addType(UnionType{{part, getBuiltins()->nilType}}); + TypeId optionalPart = arena.addType(UnionType{{part, f.builtinTypes->nilType}}); TypeId weldConstraint = getFrontend().globals.globalTypes.addType(ExternType{"WeldConstraint", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); getMutable(weldConstraint)->props = { @@ -135,18 +143,19 @@ struct RefinementExternTypeFixture : BuiltinsFixture {"Part1", Property{optionalPart}}, }; - getFrontend().globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3}; - getFrontend().globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst}; - getFrontend().globals.globalScope->exportedTypeBindings["ExternScriptConnection"] = TypeFun{{}, scriptConnection}; - getFrontend().globals.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder}; - getFrontend().globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part}; - getFrontend().globals.globalScope->exportedTypeBindings["WeldConstraint"] = TypeFun{{}, weldConstraint}; + f.globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vec3}; + f.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, inst}; + f.globals.globalScope->exportedTypeBindings["ExternScriptConnection"] = TypeFun{{}, scriptConnection}; + f.globals.globalScope->exportedTypeBindings["Folder"] = TypeFun{{}, folder}; + f.globals.globalScope->exportedTypeBindings["Part"] = TypeFun{{}, part}; + f.globals.globalScope->exportedTypeBindings["WeldConstraint"] = TypeFun{{}, weldConstraint}; - for (const auto& [name, ty] : getFrontend().globals.globalScope->exportedTypeBindings) + for (const auto& [name, ty] : f.globals.globalScope->exportedTypeBindings) persist(ty.type); - getFrontend().setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); + f.setLuauSolverMode(FFlag::LuauSolverV2 ? SolverMode::New : SolverMode::Old); freeze(getFrontend().globals.globalTypes); + return *frontend; } }; } // namespace @@ -471,7 +480,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "call_to_undefined_method_is_not_a_refinement { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -686,8 +694,6 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, }; @@ -720,6 +726,7 @@ 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 @@ -731,10 +738,7 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{ x: number }?"); // a ~= b - else - CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{| x: number |}?"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{ x: number }?"); // a ~= b } TEST_CASE_FIXTURE(Fixture, "string_not_equal_to_string_or_nil") @@ -795,6 +799,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_narrow_to_vector") TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true") { + ScopedFastFlag _[] = { + {FFlag::LuauEagerGeneralization4, true}, + }; CheckResult result = check(R"( local t = {"hello"} local v = t[2] @@ -813,7 +820,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_ LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauSolverV2) { CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil" @@ -851,6 +858,7 @@ 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 @@ -863,10 +871,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ x: number } | { y: boolean }", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table" - else - CHECK_EQ("{| x: number |} | {| y: boolean |}", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table" + CHECK_EQ("{ x: number } | { y: boolean }", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table" CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table" } @@ -890,6 +895,7 @@ 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?) @@ -905,10 +911,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_guard_can_filter_for_intersection_of_ta ToStringOptions opts; opts.exhaustive = true; - if (FFlag::LuauSolverV2) - CHECK_EQ("{ x: number } & { y: number }", toString(requireTypeAtPosition({4, 28}), opts)); - else - CHECK_EQ("{| x: number |} & {| y: number |}", toString(requireTypeAtPosition({4, 28}), opts)); + CHECK_EQ("{ x: number } & { y: number }", toString(requireTypeAtPosition({4, 28}), opts)); CHECK_EQ("nil", toString(requireTypeAtPosition({6, 28}))); } @@ -1047,6 +1050,7 @@ 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 @@ -1069,7 +1073,7 @@ TEST_CASE_FIXTURE(Fixture, "not_t_or_some_prop_of_t") CHECK_EQ("({ read x: ~(false?) } & { x: boolean })?", toString(requireTypeAtPosition({3, 28}))); } else - CHECK_EQ("{| x: boolean |}?", toString(requireTypeAtPosition({3, 28}))); + CHECK_EQ("{ x: boolean }?", toString(requireTypeAtPosition({3, 28}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "assert_a_to_be_truthy_then_assert_a_to_be_number") @@ -1299,6 +1303,7 @@ 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} @@ -1320,8 +1325,8 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") } else { - CHECK_EQ(R"({| tag: "exists", x: string |})", toString(requireTypeAtPosition({5, 28}))); - CHECK_EQ(R"({| tag: "exists", x: string |} | {| tag: "missing", x: nil |})", toString(requireTypeAtPosition({7, 28}))); + CHECK_EQ(R"({ tag: "exists", x: string })", toString(requireTypeAtPosition({5, 28}))); + CHECK_EQ(R"({ tag: "exists", x: string } | { tag: "missing", x: nil })", toString(requireTypeAtPosition({7, 28}))); } } @@ -1433,6 +1438,7 @@ 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} @@ -1448,16 +1454,8 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "discriminate_from_isa_of_x") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - { - CHECK(R"({ tag: "Part", x: Part })" == toString(requireTypeAtPosition({5, 28}))); - CHECK(R"({ tag: "Folder", x: Folder })" == toString(requireTypeAtPosition({7, 28}))); - } - else - { - CHECK_EQ(R"({| tag: "Part", x: Part |})", toString(requireTypeAtPosition({5, 28}))); - CHECK_EQ(R"({| tag: "Folder", x: Folder |})", toString(requireTypeAtPosition({7, 28}))); - } + CHECK(R"({ tag: "Part", x: Part })" == toString(requireTypeAtPosition({5, 28}))); + CHECK(R"({ tag: "Folder", x: Folder })" == toString(requireTypeAtPosition({7, 28}))); } TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeguard_cast_free_table_to_vector") @@ -2254,8 +2252,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauForceSimplifyConstraint2, true}, }; @@ -2281,8 +2277,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_ {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauResetConditionalContextProperly, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true} }; // FIXME CLI-141364: An underlying bug in normalization means the type of @@ -2337,6 +2331,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symb TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symbol_2") { + // TODO: This test is written weirdly and doesn't pass CheckResult result = check(R"( type Result = never | { tag: "ok", value: T } @@ -2351,7 +2346,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symb end )"); - if (FFlag::LuauSubtypingReportGenericBoundMismatches) + if (FFlag::LuauSubtypingReportGenericBoundMismatches && FFlag::LuauSolverV2) { // FIXME CLI-161355 LUAU_REQUIRE_ERROR_COUNT(1, result); @@ -2363,6 +2358,7 @@ 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 = {} @@ -2377,7 +2373,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_t_after_return_references_all_reachab LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("{ [string]: number }", toString(requireTypeAtPosition({8, 12}), {true})); + if (FFlag::LuauSolverV2) + CHECK_EQ("{ [string]: number }", toString(requireTypeAtPosition({8, 12}), {true})); + else + CHECK_EQ("{| [string]: number |}", toString(requireTypeAtPosition({8, 12}), {true})); } TEST_CASE_FIXTURE(Fixture, "long_disjunction_of_refinements_should_not_trip_recursion_counter") @@ -2975,4 +2974,40 @@ TEST_CASE_FIXTURE(Fixture, "force_simplify_constraint_doesnt_drop_blocked_type") REQUIRE(get(results.errors[0])); } +TEST_CASE_FIXTURE(Fixture, "len_operator_in_if_is_just_a_proposition") +{ + ScopedFastFlag _[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauNumericUnaryOpsDontProduceNegationRefinements, true}, + }; + + CheckResult result = check(R"( +type Pool = { x : number } +local pool = p :: Pool +if #pool then + local y = pool +end +)"); + TypeId ty = requireTypeAtPosition({4, 14}); + CHECK(toString(ty) != "never"); +} + +TEST_CASE_FIXTURE(Fixture, "unm_operator_is_just_a_proposition") +{ + ScopedFastFlag _[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauNumericUnaryOpsDontProduceNegationRefinements, true}, + }; + + CheckResult result = check(R"( +type Pool = { x : number } +local pool = p :: Pool +if -pool then + local y = pool +end +)"); + TypeId ty = requireTypeAtPosition({4, 14}); + CHECK(toString(ty) != "never"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 96bbc15e..6485370b 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -7,6 +7,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauPushTypeConstraint) TEST_SUITE_BEGIN("TypeSingletons"); @@ -289,11 +291,15 @@ 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} )"); - CHECK("{ a: number, b: boolean }" == toString(requireType("t"), {true})); + if (FFlag::LuauSolverV2) + CHECK("{ a: number, b: boolean }" == toString(requireType("t"), {true})); + else + CHECK("{| a: number, b: boolean |}" == toString(requireType("t"), {true})); } TEST_CASE_FIXTURE(Fixture, "table_properties_singleton_strings") @@ -392,6 +398,8 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") TEST_CASE_FIXTURE(Fixture, "error_detailed_tagged_union_mismatch_string") { + ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + CheckResult result = check(R"( type Cat = { tag: 'cat', catfood: string } type Dog = { tag: 'dog', dogfood: string } @@ -402,9 +410,7 @@ local a: Animal = { tag = 'cat', cafood = 'something' } LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::LuauSolverV2) - // NOTE: This error is not great, it might be more helpful to indicate - // that tag _could_ have type 'cat'. - CHECK("Type '{ cafood: string, tag: string }' could not be converted into 'Cat | Dog'" == 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' @@ -653,4 +659,17 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "singletons_stick_around_under_assignment") LUAU_REQUIRE_ERROR_COUNT(1, result); } +TEST_CASE_FIXTURE(Fixture, "tagged_union_in_ternary") +{ + LUAU_REQUIRE_NO_ERRORS(check(R"( + type Result = { type: "ok", value: unknown } | { type: "error" } + + local function coinflip(): boolean return true end + + local function readFromDB(): Result + return if coinflip() then { type = "ok", value = 42 } else { type = "error" } + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 9abc8c80..f5ffe511 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -29,11 +29,14 @@ LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauAllowMixedTables) +LUAU_FASTFLAG(LuauSolverAgnosticSetType) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) +LUAU_FASTFLAG(LuauSimplifyIntersectionForLiteralSubtypeCheck) TEST_SUITE_BEGIN("TableTests"); @@ -99,6 +102,7 @@ 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' @@ -108,11 +112,15 @@ TEST_CASE_FIXTURE(Fixture, "augment_table") const TableType* tType = get(requireType("t")); REQUIRE(tType != nullptr); - CHECK("{ foo: string }" == toString(requireType("t"), {true})); + if (FFlag::LuauSolverV2) + CHECK("{ foo: string }" == toString(requireType("t"), {true})); + else + CHECK("{| foo: string |}" == toString(requireType("t"), {true})); } 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' @@ -129,7 +137,10 @@ TEST_CASE_FIXTURE(Fixture, "augment_nested_table") const TableType* pType = get(p.readTy); REQUIRE(pType != nullptr); - CHECK("{ p: { foo: string } }" == toString(requireType("t"), {true})); + if (FFlag::LuauSolverV2) + CHECK("{ p: { foo: string } }" == toString(requireType("t"), {true})); + else + CHECK("{| p: {| foo: string |} |}" == toString(requireType("t"), {true})); } TEST_CASE_FIXTURE(Fixture, "assign_key_at_index_expr") @@ -163,6 +174,7 @@ 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} @@ -182,10 +194,7 @@ TEST_CASE_FIXTURE(Fixture, "cannot_augment_sealed_table") // TODO: better, more robust comparison of type vars auto s = toString(error->tableType, ToStringOptions{/*exhaustive*/ true}); - if (FFlag::LuauSolverV2) - CHECK_EQ(s, "{ prop: number }"); - else - CHECK_EQ(s, "{| prop: number |}"); + CHECK_EQ(s, "{ prop: number }"); CHECK_EQ(error->prop, "foo"); CHECK_EQ(error->context, CannotExtendTable::Property); } @@ -777,6 +786,7 @@ 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 } @@ -805,10 +815,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_value_property_in_literal") CHECK(bool(retType->indexer)); const TableIndexer& indexer = *retType->indexer; - if (FFlag::LuauSolverV2) - CHECK_EQ("{ __name: string }", toString(indexer.indexType)); - else - CHECK_EQ("{| __name: string |}", toString(indexer.indexType)); + CHECK_EQ("{ __name: string }", toString(indexer.indexType)); } TEST_CASE_FIXTURE(Fixture, "infer_indexer_from_its_variable_type_and_unifiable") @@ -839,6 +846,7 @@ 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 } = {} @@ -854,10 +862,7 @@ TEST_CASE_FIXTURE(Fixture, "indexer_mismatch") TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm != nullptr); CHECK(toString(tm->wantedType) == "{number}"); - if (FFlag::LuauSolverV2) - CHECK(toString(tm->givenType) == "{ [string]: string }"); - else - CHECK(toString(tm->givenType) == "{| [string]: string |}"); + CHECK(toString(tm->givenType) == "{ [string]: string }"); CHECK_NE(*t1, *t2); } @@ -1691,6 +1696,7 @@ 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 } @@ -1710,16 +1716,17 @@ TEST_CASE_FIXTURE(Fixture, "casting_unsealed_tables_with_props_into_table_with_i } else { - CHECK_EQ("{| [string]: string |}", toString(tm->wantedType, o)); + CHECK_EQ("{ [string]: string }", toString(tm->wantedType, o)); // Should t now have an indexer? // It would if the assignment to rt was correctly typed. - CHECK_EQ("{ [string]: string, foo: number }", toString(tm->givenType, o)); + CHECK_EQ("{| [string]: string, foo: number |}", toString(tm->givenType, o)); } } 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 @@ -1731,16 +1738,8 @@ TEST_CASE_FIXTURE(Fixture, "casting_sealed_tables_with_props_into_table_with_ind ToStringOptions o{/* exhaustive= */ true}; TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); - if (FFlag::LuauSolverV2) - { - CHECK_EQ("{ [string]: string }", toString(tm->wantedType, o)); - CHECK_EQ("{ foo: number }", toString(tm->givenType, o)); - } - else - { - CHECK_EQ("{| [string]: string |}", toString(tm->wantedType, o)); - CHECK_EQ("{| foo: number |}", toString(tm->givenType, o)); - } + CHECK_EQ("{ [string]: string }", toString(tm->wantedType, o)); + CHECK_EQ("{ foo: number }", toString(tm->givenType, o)); } TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer2") @@ -1755,6 +1754,7 @@ 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 }) @@ -1773,8 +1773,8 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer3") } else { - CHECK_EQ("{| [string]: number, a: string |}", toString(tm->wantedType, o)); - CHECK_EQ("{ [string]: number, a: number }", toString(tm->givenType, o)); + CHECK_EQ("{ [string]: number, a: string }", toString(tm->wantedType, o)); + CHECK_EQ("{| [string]: number, a: number |}", toString(tm->givenType, o)); } } @@ -1793,6 +1793,7 @@ 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 @@ -1818,8 +1819,8 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi CHECK_EQ(mp->properties[0], "y"); CHECK_EQ(mp->properties[1], "z"); - CHECK_EQ("{| x: number, y: number, z: number |}", toString(mp->superType)); - CHECK_EQ("{| x: number |}", toString(mp->subType)); + CHECK_EQ("{ x: number, y: number, z: number }", toString(mp->superType)); + CHECK_EQ("{ x: number }", toString(mp->subType)); } } @@ -1850,6 +1851,8 @@ 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 @@ -1865,16 +1868,8 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_dont_report_multipl TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); - if (FFlag::LuauSolverV2) - { - CHECK_EQ("{{ x: number }}", toString(tm->wantedType)); - CHECK_EQ("{{ x: number, y: number, z: number }}", toString(tm->givenType)); - } - else - { - CHECK_EQ("{{| x: number |}}", toString(tm->wantedType)); - CHECK_EQ("{{| x: number, y: number, z: number |}}", toString(tm->givenType)); - } + CHECK_EQ("{{ x: number }}", toString(tm->wantedType)); + CHECK_EQ("{{ x: number, y: number, z: number }}", toString(tm->givenType)); } TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_extra_props_is_ok") @@ -1892,6 +1887,8 @@ 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"( local t: {a: number,b: number, c: number, d: number, e: number, f: number} = nil :: any @@ -1903,26 +1900,13 @@ TEST_CASE_FIXTURE(Fixture, "type_mismatch_on_massive_table_is_cut_short") TypeMismatch* tm = get(result.errors[0]); REQUIRE(tm); - if (FFlag::LuauSolverV2) - { - CHECK("{ a: number, b: number, c: number, d: number, e: number, ... 1 more ... }" == toString(requireType("t"))); - CHECK_EQ("number", toString(tm->givenType)); - - CHECK_EQ( - "Type 'number' could not be converted into '{ a: number, b: number, c: number, d: number, e: number, ... 1 more ... }'", - toString(result.errors[0]) - ); - } - else - { - CHECK("{| a: number, b: number, c: number, d: number, e: number, ... 1 more ... |}" == toString(requireType("t"))); - CHECK_EQ("number", toString(tm->givenType)); + CHECK("{ a: number, b: number, c: number, d: number, e: number, ... 1 more ... }" == toString(requireType("t"))); + CHECK_EQ("number", toString(tm->givenType)); - CHECK_EQ( - "Type 'number' could not be converted into '{| a: number, b: number, c: number, d: number, e: number, ... 1 more ... |}'", - toString(result.errors[0]) - ); - } + CHECK_EQ( + "Type 'number' could not be converted into '{ a: number, b: number, c: number, d: number, e: number, ... 1 more ... }'", + toString(result.errors[0]) + ); } TEST_CASE_FIXTURE(Fixture, "ok_to_set_nil_even_on_non_lvalue_base_expr") @@ -2120,6 +2104,8 @@ 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 @@ -2136,16 +2122,8 @@ TEST_CASE_FIXTURE(Fixture, "hide_table_error_properties") LUAU_REQUIRE_ERROR_COUNT(2, result); - if (FFlag::LuauSolverV2) - { - CHECK_EQ("Cannot add property 'a' to table '{ x: number }'", toString(result.errors[0])); - CHECK_EQ("Cannot add property 'b' to table '{ x: number }'", toString(result.errors[1])); - } - else - { - CHECK_EQ("Cannot add property 'a' to table '{| x: number |}'", toString(result.errors[0])); - CHECK_EQ("Cannot add property 'b' to table '{| x: number |}'", toString(result.errors[1])); - } + CHECK_EQ("Cannot add property 'a' to table '{ x: number }'", toString(result.errors[0])); + CHECK_EQ("Cannot add property 'b' to table '{ x: number }'", toString(result.errors[1])); } TEST_CASE_FIXTURE(BuiltinsFixture, "builtin_table_names") @@ -2352,7 +2330,13 @@ local t: { a: {Foo}, b: number } = { // since mutating properties means table properties should be invariant. TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound") { - ScopedFastFlag sff{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; + // 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::LuauSubtypingReportGenericBoundMismatches, true}, + {FFlag::LuauInstantiateInSubtyping, FFlag::LuauSolverV2}, + }; CheckResult result = check(R"( --!strict @@ -2473,6 +2457,10 @@ 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"( local a1 = setmetatable({ x = 2, y = 3 }, { __call = function(s) end }); local b1 = setmetatable({ x = 2, y = "hello" }, { __call = function(s) end }); @@ -2487,9 +2475,9 @@ local c2: typeof(a2) = b2 R"(Type 'b1' could not be converted into 'a1' caused by: Type - '{ x: number, y: string }' + '{| x: number, y: string |}' could not be converted into - '{ x: number, y: number }' + '{| x: number, y: number |}' caused by: Property 'y' is not compatible. Type 'string' could not be converted into 'number' in an invariant context)"; @@ -2497,9 +2485,9 @@ Type 'string' could not be converted into 'number' in an invariant context)"; R"(Type 'b2' could not be converted into 'a2' caused by: Type - '{ __call: (a, b) -> () }' + '{| __call: (a, b) -> () |}' could not be converted into - '{ __call: (a) -> () }' + '{| __call: (a) -> () |}' caused by: Property '__call' is not compatible. Type @@ -2531,9 +2519,9 @@ could not be converted into const std::string expected3 = R"(Type 'b2' could not be converted into 'a2' caused by: Type - '{ __call: (a, b) -> () }' + '{| __call: (a, b) -> () |}' could not be converted into - '{ __call: (a) -> () }' + '{| __call: (a) -> () |}' caused by: Property '__call' is not compatible. Type @@ -2643,6 +2631,7 @@ 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 } @@ -2669,7 +2658,7 @@ local y: number = tmp.p.y const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper' caused by: Property 'p' is not compatible. -Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')"; +Table type '{| x: number, y: number |}' not compatible with type 'Super' because the former has extra field 'y')"; CHECK_EQ(expected, toString(result.errors[0])); } } @@ -3437,6 +3426,7 @@ 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 = {} @@ -3453,10 +3443,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "access_index_metamethod_that_returns_variadi ToStringOptions o; o.exhaustive = true; - if (FFlag::LuauSolverV2) - CHECK_EQ("{ x: string }", toString(requireType("foo"), o)); - else - CHECK_EQ("{| x: string |}", toString(requireType("foo"), o)); + CHECK_EQ("{ x: string }", toString(requireType("foo"), o)); } TEST_CASE_FIXTURE(Fixture, "dont_invalidate_the_properties_iterator_of_free_table_when_rolled_back") @@ -3498,7 +3485,7 @@ 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 @@ -3513,23 +3500,21 @@ TEST_CASE_FIXTURE(Fixture, "checked_prop_too_early") } else { - CHECK_EQ("Value of type '{| x: number? |}?' could be nil", toString(result.errors[0])); - CHECK_EQ("number | {| x: number? |}", toString(requireType("u"))); + CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0])); + CHECK_EQ("number | { x: number? }", toString(requireType("u"))); } } 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 )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2) - CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0])); - else - CHECK_EQ("Value of type '{| x: number? |}?' could be nil", toString(result.errors[0])); + CHECK_EQ("Value of type '{ x: number? }?' could be nil", toString(result.errors[0])); CHECK_EQ("boolean", toString(requireType("u"))); } @@ -3683,16 +3668,14 @@ 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 )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - if (FFlag::LuauSolverV2) - CHECK_EQ("Type '{ [boolean]: number } | {number}' does not have key 'x'", toString(result.errors[0])); - else - CHECK_EQ("Type '{number} | {| [boolean]: number |}' does not have key 'x'", toString(result.errors[0])); + CHECK_EQ("Type '{ [boolean]: number } | {number}' does not have key 'x'", toString(result.errors[0])); } TEST_CASE_FIXTURE(BuiltinsFixture, "leaking_bad_metatable_errors") @@ -3729,8 +3712,6 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_ { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -3856,9 +3837,6 @@ Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutel TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly") { - // We need egraphs to simplify the type of `out` here. CLI-114134 - DOES_NOT_PASS_NEW_SOLVER_GUARD(); - CheckResult result = check(R"( local function stringByteList(str) local out = {} @@ -3871,12 +3849,39 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly local x = stringByteList("xoo") )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauSolverV2) + { + LUAU_REQUIRE_ERRORS(result); + if (FFlag::LuauEagerGeneralization4) + { + /* + * string.byte returns ...number, which cannot be passed to + * table.insert. + * + * Intuitively, Luau has no way to guarantee that string.byte() will + * always return at least 1 number and there is no table.insert overload + * that takes just 1 argument. + */ + LUAU_REQUIRE_ERROR(result, MultipleNonviableOverloads); + } + else + { + const GenericError* err = findError(result); + REQUIRE(err); + CHECK(err->message == "None of the overloads for function that accept 1 arguments are compatible."); + LUAU_REQUIRE_ERROR(result, GenericError); + } + } + else + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_tables_in_call_is_unsound") { - ScopedFastFlag sff{FFlag::LuauInstantiateInSubtyping, true}; + ScopedFastFlag sff[] = { + {FFlag::LuauInstantiateInSubtyping, true}, + {FFlag::LuauSolverAgnosticStringification, true}, + }; CheckResult result = check(R"( --!strict @@ -3912,7 +3917,6 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table // parameters)"); // // this error message is not great since the underlying issue is that the context is invariant, // and `(number) -> number` cannot be a subtype of `(a) -> a`. - LUAU_REQUIRE_NO_ERRORS(result); } } @@ -3971,6 +3975,7 @@ 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, @@ -3982,7 +3987,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tables_should_be_fully_populated") ToStringOptions opts; opts.exhaustive = true; - CHECK_EQ("{ x: *error-type*, y: number }", toString(requireType("t"), opts)); + if (FFlag::LuauSolverV2) + CHECK_EQ("{ x: *error-type*, y: number }", toString(requireType("t"), opts)); + else + CHECK_EQ("{| x: *error-type*, y: number |}", toString(requireType("t"), opts)); } TEST_CASE_FIXTURE(Fixture, "fuzz_table_indexer_unification_can_bound_owner_to_string") @@ -4302,6 +4310,7 @@ 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 = {} @@ -4317,7 +4326,7 @@ TEST_CASE_FIXTURE(Fixture, "simple_method_definition") if (FFlag::LuauSolverV2) CHECK_EQ("{ m: (unknown) -> number }", toString(getMainModule()->returnType, ToStringOptions{true})); else - CHECK_EQ("{| m: (a) -> number |}", toString(getMainModule()->returnType, ToStringOptions{true})); + CHECK_EQ("{ m: (a) -> number }", toString(getMainModule()->returnType, ToStringOptions{true})); } TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields") @@ -4430,6 +4439,7 @@ 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 @@ -4447,16 +4457,8 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression") // the new solver reports specifically the inner mismatch, rather than the whole table // honestly not sure which of these is a better developer experience. - if (FFlag::LuauSolverV2) - { - CHECK_EQ("{ x: any, y: string }", toString(tm->wantedType)); - CHECK_EQ("{ x: string, y: number }", toString(tm->givenType)); - } - else - { - CHECK_EQ("{| x: any, y: string |}", toString(tm->wantedType)); - CHECK_EQ("{| x: string, y: number |}", toString(tm->givenType)); - } + CHECK_EQ("{ x: any, y: string }", toString(tm->wantedType)); + CHECK_EQ("{ x: string, y: number }", toString(tm->givenType)); } TEST_CASE_FIXTURE(Fixture, "write_to_read_only_property") @@ -4604,8 +4606,6 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -4868,6 +4868,7 @@ 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 @@ -4880,7 +4881,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "ipairs_adds_an_unbounded_indexer") if (FFlag::LuauSolverV2) CHECK("{unknown}" == toString(requireType("a"), {true})); else - CHECK("{a}" == toString(requireType("a"), {true})); + CHECK("{'a}" == toString(requireType("a"), {true})); } TEST_CASE_FIXTURE(BuiltinsFixture, "index_results_compare_to_nil") @@ -5299,16 +5300,14 @@ 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 } return t end )")); - if (FFlag::LuauSolverV2) - CHECK_EQ("(number) -> { x: number }", toString(requireType("new"))); - else - CHECK_EQ("(number) -> {| x: number |}", toString(requireType("new"))); + CHECK_EQ("(number) -> { x: number }", toString(requireType("new"))); } TEST_CASE_FIXTURE(Fixture, "returning_optional_in_table") @@ -5485,7 +5484,7 @@ TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference") TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error") { - ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauPushTypeConstraint, true}}; auto result = check(R"( type File = { @@ -5519,8 +5518,9 @@ TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error") LUAU_REQUIRE_ERROR_COUNT(1, result); std::string expected = - R"(Type '{ children: {{ path: string, type: string }}, name: string, type: "dir" }' could not be converted into 'Dir | File')"; + R"(Table type '{ path: string, type: "file" }' not compatible with type 'File' because the former is missing field 'name')"; CHECK_EQ(expected, toString(result.errors[0])); + CHECK_EQ(result.errors[0].location, Location{{21, 20}, {24, 21}}); } TEST_CASE_FIXTURE(Fixture, "unsafe_bidirectional_mutation") @@ -5872,8 +5872,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} + {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, + {FFlag::LuauPushTypeConstraint, true}, }; CheckResult results = check(R"( @@ -5904,8 +5904,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") // NOTE: Single line with just `"Ctrl"` CHECK_EQ(results.errors[0].location, Location{{17, 16}, {17, 22}}); CHECK_EQ(R"("Alt" | "Space" | "Tab")", toString(err->wantedType)); - // FIXME: CLI-157899 - CHECK_EQ(R"("Alt" | "Ctrl" | "Space" | "Tab")", toString(err->givenType)); + CHECK_EQ(R"("Ctrl")", toString(err->givenType)); } TEST_CASE_FIXTURE(Fixture, "oss_1888_and_or_subscriptable") @@ -6055,4 +6054,89 @@ TEST_CASE_FIXTURE(Fixture, "mixed_tables_are_ok_for_any_key") )")); } +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1935") +{ + ScopedFastFlag _{FFlag::LuauSimplifyIntersectionForLiteralSubtypeCheck, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + type Drawing = { + update: (() -> boolean)?, + } + + type Counter = { + count: number, + } + + function update(): boolean + return true + end + + return function(): Drawing & Counter + return { + count = 34, + update = update, + } + end + )")); +} + +TEST_CASE_FIXTURE(Fixture, "result_like_tagged_union") +{ + ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( +--!strict +local function retry(func: (...any) -> ...any): { type: "ok", value: any } | { type: "failed" } + local success: boolean, result: any = func() + + if success then + return { type = "ok", value = result } + else + return { type = "failed" } + end +end + +return retry + )")); +} + +TEST_CASE_FIXTURE(Fixture, "oss_1924") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint, true}, + }; + + CheckResult result = check(R"( + local t: { [string]: "s" } = { + key = "s", + other_key = "t", + } + )"); + LUAU_REQUIRE_ERROR_COUNT(1, result); + auto err = get(result.errors[0]); + REQUIRE(err); + CHECK_EQ("\"s\"", toString(err->wantedType)); + CHECK_EQ("string", toString(err->givenType)); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "cli_167052") +{ + ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local Children = newproxy() + local Macro: { [ string | typeof(Children) ]: true } = { + ["_exec"] = true; + ["_run"] = true; + ["_init"] = true; + ["_base"] = true; + ["Class"] = true; + ["_count"] = true; + [Children] = true; + } + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index 6a071b17..e7112ad8 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -33,8 +33,7 @@ LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAG(LuauOccursCheckInCommit) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) using namespace Luau; @@ -668,6 +667,18 @@ 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 + return x + y +end)"); + + LUAU_REQUIRE_ERROR_COUNT(2, result); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "index_expr_should_be_checked") { CheckResult result = check(R"( @@ -2015,8 +2026,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; auto result = check(R"( @@ -2052,8 +2061,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -2087,8 +2094,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; LUAU_REQUIRE_ERRORS(check(R"( @@ -2288,7 +2293,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "config_reader_example") // test suite starts, which will cause an assert if we try to eagerly // generalize _after_ the test is set up. Additionally, this code block // crashes under the new solver without flags. - if (!FFlag::LuauEagerGeneralization4) + if (!(FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)) return; fileResolver.source["game/ConfigReader"] = R"( @@ -2366,8 +2371,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; LUAU_REQUIRE_ERRORS(check(R"( diff --git a/tests/TypeInfer.tryUnify.test.cpp b/tests/TypeInfer.tryUnify.test.cpp index 25d33b4b..1f08273c 100644 --- a/tests/TypeInfer.tryUnify.test.cpp +++ b/tests/TypeInfer.tryUnify.test.cpp @@ -13,6 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauUnifierRecursionOnRestart); +LUAU_FASTFLAG(LuauSolverAgnosticStringification) struct TryUnifyFixture : Fixture { @@ -275,6 +276,8 @@ 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 table.insert() @@ -285,7 +288,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_41095_concat_log_in_sealed_table_unifica if (FFlag::LuauSolverV2) CHECK_EQ(toString(result.errors[1]), "Available overloads: ({V}, V) -> (); and ({V}, number, V) -> ()"); else - CHECK_EQ(toString(result.errors[1]), "Available overloads: ({a}, a) -> (); and ({a}, number, a) -> ()"); + CHECK_EQ(toString(result.errors[1]), "Available overloads: ({'a}, 'a) -> (); and ({'a}, number, 'a) -> ()"); } TEST_CASE_FIXTURE(TryUnifyFixture, "free_tail_is_grown_properly") diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index a3cd50db..3a36d94a 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -13,6 +13,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TypePackTests"); @@ -88,6 +89,9 @@ TEST_CASE_FIXTURE(Fixture, "last_element_of_return_statement_can_itself_be_a_pac TEST_CASE_FIXTURE(Fixture, "higher_order_function") { + ScopedFastFlag _[] = { + {FFlag::LuauEagerGeneralization4, true}, + }; CheckResult result = check(R"( function apply(f, g, x) return f(g(x)) @@ -96,7 +100,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauSolverV2) CHECK_EQ("((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply"))); else CHECK_EQ("((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply"))); @@ -292,6 +296,8 @@ 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<> @@ -321,18 +327,13 @@ local c: Packed tf = lookupType("Packed"); REQUIRE(tf); CHECK_EQ(toString(*tf), "Packed"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(*tf, {true}), "{ f: (T, U...) -> (T, U...) }"); - else - CHECK_EQ(toString(*tf, {true}), "{| f: (T, U...) -> (T, U...) |}"); + CHECK_EQ(toString(*tf, {true}), "{ f: (T, U...) -> (T, U...) }"); auto ttvA = get(requireType("a")); REQUIRE(ttvA); CHECK_EQ(toString(requireType("a")), "Packed"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(requireType("a"), {true}), "{ f: (number) -> number }"); - else - CHECK_EQ(toString(requireType("a"), {true}), "{| f: (number) -> number |}"); + CHECK_EQ(toString(requireType("a"), {true}), "{ f: (number) -> number }"); + REQUIRE(ttvA->instantiatedTypeParams.size() == 1); REQUIRE(ttvA->instantiatedTypePackParams.size() == 1); CHECK_EQ(toString(ttvA->instantiatedTypeParams[0], {true}), "number"); @@ -341,10 +342,8 @@ local c: Packed auto ttvB = get(requireType("b")); REQUIRE(ttvB); CHECK_EQ(toString(requireType("b")), "Packed"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(requireType("b"), {true}), "{ f: (string, number) -> (string, number) }"); - else - CHECK_EQ(toString(requireType("b"), {true}), "{| f: (string, number) -> (string, number) |}"); + CHECK_EQ(toString(requireType("b"), {true}), "{ f: (string, number) -> (string, number) }"); + REQUIRE(ttvB->instantiatedTypeParams.size() == 1); REQUIRE(ttvB->instantiatedTypePackParams.size() == 1); CHECK_EQ(toString(ttvB->instantiatedTypeParams[0], {true}), "string"); @@ -353,10 +352,8 @@ local c: Packed auto ttvC = get(requireType("c")); REQUIRE(ttvC); CHECK_EQ(toString(requireType("c")), "Packed"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(requireType("c"), {true}), "{ f: (string, number, boolean) -> (string, number, boolean) }"); - else - CHECK_EQ(toString(requireType("c"), {true}), "{| f: (string, number, boolean) -> (string, number, boolean) |}"); + CHECK_EQ(toString(requireType("c"), {true}), "{ f: (string, number, boolean) -> (string, number, boolean) }"); + REQUIRE(ttvC->instantiatedTypeParams.size() == 1); REQUIRE(ttvC->instantiatedTypePackParams.size() == 1); CHECK_EQ(toString(ttvC->instantiatedTypeParams[0], {true}), "string"); @@ -365,6 +362,8 @@ 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 {} @@ -386,28 +385,17 @@ local d: { a: typeof(c) } REQUIRE(tf); CHECK_EQ(toString(*tf), "Packed"); - if (FFlag::LuauSolverV2) - { - CHECK_EQ(toString(*tf, {true}), "{ a: T, b: (U...) -> () }"); - - CHECK_EQ(toString(requireType("a"), {true}), "{ a: number, b: () -> () }"); - CHECK_EQ(toString(requireType("b"), {true}), "{ a: string, b: (number) -> () }"); - CHECK_EQ(toString(requireType("c"), {true}), "{ a: string, b: (number, boolean) -> () }"); - CHECK_EQ(toString(requireType("d")), "{ a: Packed }"); - } - else - { - CHECK_EQ(toString(*tf, {true}), "{| a: T, b: (U...) -> () |}"); + CHECK_EQ(toString(*tf, {true}), "{ a: T, b: (U...) -> () }"); - CHECK_EQ(toString(requireType("a"), {true}), "{| a: number, b: () -> () |}"); - CHECK_EQ(toString(requireType("b"), {true}), "{| a: string, b: (number) -> () |}"); - CHECK_EQ(toString(requireType("c"), {true}), "{| a: string, b: (number, boolean) -> () |}"); - CHECK_EQ(toString(requireType("d")), "{| a: Packed |}"); - } + CHECK_EQ(toString(requireType("a"), {true}), "{ a: number, b: () -> () }"); + CHECK_EQ(toString(requireType("b"), {true}), "{ a: string, b: (number) -> () }"); + CHECK_EQ(toString(requireType("c"), {true}), "{ a: string, b: (number, boolean) -> () }"); + CHECK_EQ(toString(requireType("d")), "{ a: Packed }"); } 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 {} @@ -426,31 +414,19 @@ type C = Import.Packed auto tf = lookupType("Alias"); REQUIRE(tf); CHECK_EQ(toString(*tf), "Alias"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(*tf, {true}), "{ a: S, b: (T, R...) -> () }"); - else - CHECK_EQ(toString(*tf, {true}), "{| a: S, b: (T, R...) -> () |}"); + CHECK_EQ(toString(*tf, {true}), "{ a: S, b: (T, R...) -> () }"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(requireType("a"), {true}), "{ a: string, b: (number, boolean) -> () }"); - else - CHECK_EQ(toString(requireType("a"), {true}), "{| a: string, b: (number, boolean) -> () |}"); + CHECK_EQ(toString(requireType("a"), {true}), "{ a: string, b: (number, boolean) -> () }"); tf = lookupType("B"); REQUIRE(tf); CHECK_EQ(toString(*tf), "B"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (X...) -> () }"); - else - CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (X...) -> () |}"); + CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (X...) -> () }"); tf = lookupType("C"); REQUIRE(tf); CHECK_EQ(toString(*tf), "C"); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (number, X...) -> () }"); - else - CHECK_EQ(toString(*tf, {true}), "{| a: string, b: (number, X...) -> () |}"); + CHECK_EQ(toString(*tf, {true}), "{ a: string, b: (number, X...) -> () }"); } TEST_CASE_FIXTURE(Fixture, "type_alias_type_packs_nested") @@ -933,6 +909,7 @@ 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 } @@ -940,10 +917,7 @@ type R = { m: F } LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2) - CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = { m: (t1) -> (t1) -> () }"); - else - CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = {| m: (t1) -> (t1) -> () |}"); + CHECK_EQ(toString(*lookupType("R"), {true}), "t1 where t1 = { m: (t1) -> (t1) -> () }"); } TEST_CASE_FIXTURE(Fixture, "pack_tail_unification_check") diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 2551e2a1..3fffacf3 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -6,9 +6,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) using namespace Luau; @@ -410,8 +409,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( @@ -589,6 +586,7 @@ 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() @@ -598,7 +596,10 @@ TEST_CASE_FIXTURE(Fixture, "modify_captured_table_field") auto randTy = getType("state"); REQUIRE(randTy); - CHECK_EQ("{ x: number }", toString(*randTy, {true})); + if (FFlag::LuauSolverV2) + CHECK_EQ("{ x: number }", toString(*randTy, {true})); + else + CHECK_EQ("{| x: number |}", toString(*randTy, {true})); } TEST_CASE_FIXTURE(Fixture, "oss_1561") diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index c1de47a7..d2f63b04 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -11,6 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("UnionTypes"); @@ -403,6 +404,7 @@ 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?) @@ -412,10 +414,7 @@ TEST_CASE_FIXTURE(Fixture, "optional_assignment_errors_2") LUAU_REQUIRE_ERROR_COUNT(1, result); auto s = toString(result.errors[0]); - if (FFlag::LuauSolverV2) - CHECK_EQ("Value of type '({ x: number } & { y: number })?' could be nil", s); - else - CHECK_EQ("Value of type '({| x: number |} & {| y: number |})?' could be nil", s); + CHECK_EQ("Value of type '({ x: number } & { y: number })?' could be nil", s); } TEST_CASE_FIXTURE(Fixture, "optional_length_error") @@ -526,6 +525,7 @@ 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 } @@ -554,10 +554,10 @@ end } else { - CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{| w: number |}' + CHECK_EQ(toString(result.errors[0]), R"(Type 'X | Y | Z' could not be converted into '{ w: number }' caused by: Not all union options are compatible. -Table type 'X' not compatible with type '{| w: number |}' because the former is missing field 'w')"); +Table type 'X' not compatible with type '{ w: number }' because the former is missing field 'w')"); } } diff --git a/tests/TypeInfer.unknownnever.test.cpp b/tests/TypeInfer.unknownnever.test.cpp index 6fcbc03b..71c88c20 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -9,8 +9,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauEagerGeneralization4); LUAU_FASTFLAG(LuauForceSimplifyConstraint2) -LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) -LUAU_FASTFLAG(LuauResetConditionalContextProperly) TEST_SUITE_BEGIN("TypeInferUnknownNever"); @@ -333,8 +331,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i { ScopedFastFlag sffs[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true}, {FFlag::LuauForceSimplifyConstraint2, true}, }; @@ -362,8 +358,6 @@ TEST_CASE_FIXTURE(Fixture, "math_operators_and_never") { ScopedFastFlag sff[] = { {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauTrackFreeInteriorTypePacks, true}, - {FFlag::LuauResetConditionalContextProperly, true} }; CheckResult result = check(R"( diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index bf3b7452..220dc749 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -11,6 +11,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauSolverAgnosticStringification) + TEST_SUITE_BEGIN("TypeTests"); TEST_CASE_FIXTURE(Fixture, "primitives_are_equal") @@ -219,6 +221,7 @@ 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}}}; @@ -304,10 +307,7 @@ TEST_CASE_FIXTURE(Fixture, "substitution_skip_failure") REQUIRE(!anyification.normalizationTooComplex); REQUIRE(any.has_value()); - if (FFlag::LuauSolverV2) - CHECK_EQ("{ f: t1 } where t1 = () -> { f: () -> { f: ({ f: t1 }) -> (), signal: { f: (any) -> () } } }", toString(*any)); - else - CHECK_EQ("{| f: t1 |} where t1 = () -> {| f: () -> {| f: ({| f: t1 |}) -> (), signal: {| f: (any) -> () |} |} |}", toString(*any)); + CHECK_EQ("{ f: t1 } where t1 = () -> { f: () -> { f: ({ f: t1 }) -> (), signal: { f: (any) -> () } } }", toString(*any)); } TEST_CASE("tagging_tables") diff --git a/tests/conformance/coroutine.luau b/tests/conformance/coroutine.luau index 4c5534d7..94026319 100644 --- a/tests/conformance/coroutine.luau +++ b/tests/conformance/coroutine.luau @@ -382,6 +382,7 @@ do assert(st and msg == nil) end +-- xpcall is non-yieldable do local co = coroutine.wrap(xpcall) @@ -391,4 +392,23 @@ do assert(err == "cannot resume dead coroutine") end +-- xpcall continuation is non-yieldable +do + local function test() + coroutine.yield() + xpcall(test, nil) -- argument error + end + + local co = coroutine.wrap(xpcall) + co(test, test) + + local status, result = co() + assert(status == false) + assert(result == "error in error handling") + + local status, result = pcall(co) + assert(status == false) + assert(result == "cannot resume dead coroutine") +end + return 'OK' diff --git a/tests/conformance/pcall.luau b/tests/conformance/pcall.luau index abb242d3..9e691e2e 100644 --- a/tests/conformance/pcall.luau +++ b/tests/conformance/pcall.luau @@ -146,7 +146,14 @@ checkresults({ true, false, "fail" }, coroutine.resume(co)) -- stack overflow needs to happen at the call limit local calllimit = 20000 -function recurse(n) return n <= 1 and 1 or recurse(n-1) + 1 end +function recurse(n) + return n <= 1 and 1 or recurse(n-1) + 1 +end + +function yieldandrecurse(n) + coroutine.yield() + recurse(n) +end -- we use one frame for top-level function and one frame is the service frame for coroutines assert(recurse(calllimit - 2) == calllimit - 2) @@ -168,6 +175,26 @@ checkresults({ false, "oops" }, xpcall(function() table.create(1e6) end, functio checkresults({ false, "error in error handling" }, xpcall(function() error("oops") end, function(e) table.create(1e6) end)) checkresults({ false, "not enough memory" }, xpcall(function() table.create(1e6) end, function(e) table.create(1e6) end)) +-- Same tests as above are performed using the pcall continuation +local yieldwithsameresult = coroutine.wrap(function() + -- xpcall handler runs in context of the stack frame, but this works just fine since we allow extra stack consumption past stack overflow + checkresults({ false, "ok" }, xpcall(yieldandrecurse, function() return string.reverse("ko") end, calllimit - 2)) + + -- however, if xpcall handler itself runs out of extra stack space, we get "error in error handling" + checkresults({ false, "error in error handling" }, xpcall(yieldandrecurse, function() return recurse(calllimit) end, calllimit - 2)) + + -- simulate OOM and make sure we can catch it with pcall or xpcall + checkresults({ false, "not enough memory" }, pcall(function() coroutine.yield(); table.create(1e6) end)) + checkresults({ false, "not enough memory" }, xpcall(function() coroutine.yield(); table.create(1e6) end, function(e) return e end)) + checkresults({ false, "oops" }, xpcall(function() coroutine.yield(); table.create(1e6) end, function(e) return "oops" end)) + checkresults({ false, "error in error handling" }, xpcall(function() coroutine.yield(); error("oops") end, function(e) table.create(1e6) end)) + checkresults({ false, "not enough memory" }, xpcall(function() coroutine.yield(); table.create(1e6) end, function(e) table.create(1e6) end)) + + return 1 +end) + +while yieldwithsameresult() ~= 1 do end + co = coroutine.create(function() table.create(1e6) end) coroutine.resume(co) checkresults({ false, "not enough memory" }, coroutine.close(co)) diff --git a/tests/conformance/vector.luau b/tests/conformance/vector.luau index c844d160..12d0f4b4 100644 --- a/tests/conformance/vector.luau +++ b/tests/conformance/vector.luau @@ -131,9 +131,6 @@ assert(pcall(function() local t = {} rawset(t, vector.create(0/0, 2, 3), 1) end) assert(vector.create(1, 0, 0):Cross(vector.create(0, 1, 0)) == vector.create(0, 0, 1)) assert(vector.create(0, 1, 0):Cross(vector.create(1, 0, 0)) == vector.create(0, 0, -1)) --- make sure we cover both builtin and C impl -assert(vector.create(1, 2, 4) == vector.create("1", "2", "4")) - -- validate component access (both cases) assert(vector.create(1, 2, 3).x == 1) assert(vector.create(1, 2, 3).X == 1) @@ -183,4 +180,6 @@ end numvectemporary() +assert(is_native_if_supported()) + return 'OK' diff --git a/tests/conformance/vector_library.luau b/tests/conformance/vector_library.luau index 09da41b8..ea3123a4 100644 --- a/tests/conformance/vector_library.luau +++ b/tests/conformance/vector_library.luau @@ -10,9 +10,13 @@ function ecall(fn, ...) return err:sub((err:find(": ") or -1) + 2, #err) end --- make sure we cover both builtin and C impl -assert(vector.create(1, 2) == vector.create("1", "2")) -assert(vector.create(1, 2, 4) == vector.create("1", "2", "4")) +-- in a different thread to not deoptimize native execution +pcall(function() + -- make sure we cover both builtin and C impl + assert(vector.create(1, 2) == vector.create("1", "2")) + assert(vector.create(1, 2, 4) == vector.create("1", "2", "4")) + assert(not is_native_if_supported()) +end) -- 'create' local v12 = vector.create(1, 2) @@ -204,4 +208,6 @@ if vector_size == 4 then assert(vector.create(1, 2, 3, 4)['W'] == 4) end +assert(is_native_if_supported()) + return 'OK' From fb63edcceadafc7144d3de7a6ccb0fee83978cf0 Mon Sep 17 00:00:00 2001 From: Hunter Goldstein Date: Fri, 5 Sep 2025 11:47:07 -0700 Subject: [PATCH 5/8] Sync to upstream/release/690 (#1992) # New Type Solver * Improve the error message for an uninstantiated type alias, as in: ```luau type Packed = (T...) -> T... local a: Packed -- Now has a single, specific, error ``` * Improve refinements of extern types against table types, ensuring the extern part is dropped less often, as in: ```luau local function update(foo: Instance) assert(foo.Name == "bubbles") -- Previously might have been `{ read Name: "bubbles" }`, -- will now be `Instance & { read Name: "bubbles" }` return foo end ``` * Resolve an internal complier exception when attempting to use `typeof` inside the generic type default of an alias. Fixes #1462. * No longer throw an internal compiler exception for exceptionally large ASTs, though a `CodeTooComplex` error will still be raised. * Broadly rework generic pack subtyping to allow code like the following to type check without errors: ```luau function f(foo: (number) -> number): () end type T = (A...) -> number local t: T -- OK, as a function that takes some generic pack of arguments can receive a `number` as well. f(t) ``` * Report an error when a type pack is provided to a generic alias that can receive a type _and_ a type pack, as in: ```luau type Y = { a: (T) -> U... } -- Now errors as you must provide a type before the pack. local a: Y<...number> ``` * Fix an instance of generics leaking when returning a function from another function with explicitly defined generics. Fixes #1971. * Cache `HasPropConstraint`s such that for every pair of subject type and desired property, we only emit a single constraint and blocked type; this incidentally fixes some bugs around inferring the types of properties on `self`. * Table types associated with table literals are now tracked specially to make type checking more permissive, such as in: ```luau type Foo = { name: string?, flag: boolean? } local arr: {Foo} = {} local function foo(arg: {name: string}?) local name = if arg and arg.name then arg.name else nil -- Previously would error as bidirectional inference would not -- kick in as often. table.insert(arr, { name = name or "", flag = name ~= nil and name ~= "", }) end ``` # Native Codegen * Lowering to arm64 for some local heavy functions will now succeed whereas prior they failed due to running out of registers for live values. --- Co-authored-by: Andy Friesen Co-authored-by: Annie Tang Co-authored-by: Hunter Goldstein Co-authored-by: Sora Kanosue Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/AstUtils.h | 19 + Analysis/include/Luau/Constraint.h | 4 + Analysis/include/Luau/ConstraintGenerator.h | 7 +- Analysis/include/Luau/ConstraintSolver.h | 6 +- Analysis/include/Luau/GlobalTypes.h | 2 +- Analysis/include/Luau/Module.h | 2 + Analysis/include/Luau/OverloadResolution.h | 19 +- Analysis/include/Luau/Subtyping.h | 103 +++- Analysis/include/Luau/Transpiler.h | 2 +- Analysis/include/Luau/Type.h | 16 +- Analysis/include/Luau/TypePack.h | 18 +- Analysis/include/Luau/TypePath.h | 100 +++- Analysis/include/Luau/Unifier2.h | 2 +- Analysis/src/AstUtils.cpp | 64 +++ Analysis/src/BuiltinDefinitions.cpp | 264 +++++---- Analysis/src/BuiltinTypeFunctions.cpp | 27 +- Analysis/src/ConstraintGenerator.cpp | 176 +++++- Analysis/src/ConstraintSolver.cpp | 41 +- Analysis/src/DcrLogger.cpp | 20 +- Analysis/src/EqSatSimplification.cpp | 26 +- Analysis/src/Error.cpp | 7 +- Analysis/src/FileResolver.cpp | 6 +- Analysis/src/FragmentAutocomplete.cpp | 28 +- Analysis/src/Frontend.cpp | 139 ++--- Analysis/src/Module.cpp | 5 +- Analysis/src/OverloadResolution.cpp | 142 +++-- Analysis/src/Simplify.cpp | 99 +++- Analysis/src/Subtyping.cpp | 577 +++++++++++++++++--- Analysis/src/TypeChecker2.cpp | 85 ++- Analysis/src/TypeInfer.cpp | 36 +- Analysis/src/TypePack.cpp | 33 +- Analysis/src/TypePath.cpp | 336 +++++++++++- Analysis/src/Unifier.cpp | 10 +- Ast/include/Luau/Parser.h | 8 +- Ast/src/Parser.cpp | 77 +-- CMakeLists.txt | 1 + CodeGen/include/Luau/IrData.h | 1 + CodeGen/src/CodeGenContext.cpp | 29 +- CodeGen/src/CodeGenContext.h | 12 +- CodeGen/src/CodeGenLower.h | 2 + CodeGen/src/IrLoweringA64.cpp | 119 ++-- CodeGen/src/IrLoweringX64.cpp | 12 + CodeGen/src/IrRegAllocA64.cpp | 246 +++++++-- CodeGen/src/IrRegAllocA64.h | 22 +- CodeGen/src/IrRegAllocX64.cpp | 8 + CodeGen/src/OptimizeConstProp.cpp | 2 +- Common/include/Luau/DenseHash.h | 12 +- Common/include/Luau/HashUtil.h | 54 ++ EqSat/include/Luau/LanguageHash.h | 9 +- Sources.cmake | 3 + VM/src/lstrlib.cpp | 4 +- VM/src/lutf8lib.cpp | 6 +- extern/doctest.h | 6 +- tests/AstJsonEncoder.test.cpp | 3 +- tests/Autocomplete.test.cpp | 4 +- tests/ClassFixture.cpp | 10 +- tests/CodeAllocator.test.cpp | 3 +- tests/Compiler.test.cpp | 6 +- tests/Conformance.test.cpp | 2 + tests/ConstraintGeneratorFixture.cpp | 2 +- tests/EqSatSimplification.test.cpp | 93 ++-- tests/Generalization.test.cpp | 10 +- tests/InferPolarity.test.cpp | 32 +- tests/Instantiation2.test.cpp | 12 +- tests/Linter.test.cpp | 8 +- tests/NonStrictTypeChecker.test.cpp | 4 +- tests/Normalize.test.cpp | 14 +- tests/Parser.test.cpp | 8 +- tests/Subtyping.test.cpp | 146 ++--- tests/TypeFunction.test.cpp | 69 +-- tests/TypeInfer.aliases.test.cpp | 112 +++- tests/TypeInfer.annotations.test.cpp | 17 +- tests/TypeInfer.classes.test.cpp | 197 ++++++- tests/TypeInfer.functions.test.cpp | 34 +- tests/TypeInfer.generics.test.cpp | 168 +++++- tests/TypeInfer.intersectionTypes.test.cpp | 64 ++- tests/TypeInfer.loops.test.cpp | 16 +- tests/TypeInfer.modules.test.cpp | 5 +- tests/TypeInfer.operators.test.cpp | 7 +- tests/TypeInfer.refinements.test.cpp | 8 +- tests/TypeInfer.tables.test.cpp | 48 +- tests/TypeInfer.test.cpp | 57 +- tests/TypeInfer.typePacks.test.cpp | 40 +- tests/TypeInfer.unionTypes.test.cpp | 16 +- tests/TypePath.test.cpp | 129 ++--- tests/conformance/native.luau | 38 +- tests/main.cpp | 34 +- 87 files changed, 3373 insertions(+), 1097 deletions(-) create mode 100644 Analysis/include/Luau/AstUtils.h create mode 100644 Analysis/src/AstUtils.cpp create mode 100644 Common/include/Luau/HashUtil.h diff --git a/Analysis/include/Luau/AstUtils.h b/Analysis/include/Luau/AstUtils.h new file mode 100644 index 00000000..01ed15a3 --- /dev/null +++ b/Analysis/include/Luau/AstUtils.h @@ -0,0 +1,19 @@ +// 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/DenseHash.h" +#include "Luau/NotNull.h" +#include "Luau/TypeFwd.h" + +namespace Luau +{ + +// Search through the expression 'expr' for types that are known to represent +// 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); + +} diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h index 31eaa7e7..5b129dd9 100644 --- a/Analysis/include/Luau/Constraint.h +++ b/Analysis/include/Luau/Constraint.h @@ -92,6 +92,10 @@ struct FunctionCallConstraint TypeId fn; TypePackId argsPack; TypePackId result; + + // callSite can be nullptr in the case that this constraint was + // synthetically generated from some other constraint. eg + // IterableConstraint. class AstExprCall* callSite = nullptr; std::vector> discriminantTypes; diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index 67fad474..8394de6c 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -7,6 +7,7 @@ #include "Luau/ControlFlow.h" #include "Luau/DataFlowGraph.h" #include "Luau/EqSatSimplification.h" +#include "Luau/HashUtil.h" #include "Luau/InsertionOrderedMap.h" #include "Luau/Module.h" #include "Luau/ModuleResolver.h" @@ -135,6 +136,8 @@ struct ConstraintGenerator DcrLogger* logger; + bool recursionLimitMet = false; + ConstraintGenerator( ModulePtr module, NotNull normalizer, @@ -174,6 +177,8 @@ struct ConstraintGenerator std::vector unionsToSimplify; + DenseHashMap, TypeId, PairHash> propIndexPairsSeen{{nullptr, ""}}; + // Used to keep track of when we are inside a large table and should // opt *not* to do type inference for singletons. size_t largeTableDepth = 0; @@ -259,7 +264,6 @@ struct ConstraintGenerator LUAU_NOINLINE void checkAliases(const ScopePtr& scope, AstStatBlock* block); ControlFlow visitBlockWithoutChildScope(const ScopePtr& scope, AstStatBlock* block); - ControlFlow visitBlockWithoutChildScope_DEPRECATED(const ScopePtr& scope, AstStatBlock* block); ControlFlow visit(const ScopePtr& scope, AstStat* stat); ControlFlow visit(const ScopePtr& scope, AstStatBlock* block); @@ -496,6 +500,7 @@ struct ConstraintGenerator void updateRValueRefinements(const ScopePtr& scope, DefId def, TypeId ty) const; void updateRValueRefinements(Scope* scope, DefId def, TypeId ty) const; + void resolveGenericDefaultParameters(const ScopePtr& defnScope, AstStatTypeAlias* alias, const TypeFun& fun); }; } // namespace Luau diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index fcbc41e0..1d1aacf4 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -108,7 +108,7 @@ struct ConstraintSolver std::vector> constraints; NotNull> scopeToFunction; NotNull rootScope; - ModuleName currentModuleName; + ModulePtr module; // The dataflow graph of the program, used in constraint generation and for magic functions. NotNull dfg; @@ -169,7 +169,7 @@ struct ConstraintSolver NotNull normalizer, NotNull simplifier, NotNull typeFunctionRuntime, - ModuleName moduleName, + ModulePtr module, NotNull moduleResolver, std::vector requireCycles, DcrLogger* logger, @@ -185,7 +185,7 @@ struct ConstraintSolver NotNull rootScope, std::vector> constraints, NotNull> scopeToFunction, - ModuleName moduleName, + ModulePtr module, NotNull moduleResolver, std::vector requireCycles, DcrLogger* logger, diff --git a/Analysis/include/Luau/GlobalTypes.h b/Analysis/include/Luau/GlobalTypes.h index 023667a5..7b7b0f2f 100644 --- a/Analysis/include/Luau/GlobalTypes.h +++ b/Analysis/include/Luau/GlobalTypes.h @@ -20,7 +20,7 @@ struct GlobalTypes TypeArena globalTypes; SourceModule globalNames; // names for symbols entered into globalScope - ScopePtr globalScope; // shared by all modules + ScopePtr globalScope; // shared by all modules ScopePtr globalTypeFunctionScope; // shared by all modules SolverMode mode = SolverMode::Old; diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index f0ce5202..518ae49e 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -159,6 +159,8 @@ struct Module void clonePublicInterface_DEPRECATED(NotNull builtinTypes, InternalErrorReporter& ice); void clonePublicInterface(NotNull builtinTypes, InternalErrorReporter& ice, SolverMode mode); + + bool constraintGenerationDidNotComplete = true; }; } // namespace Luau diff --git a/Analysis/include/Luau/OverloadResolution.h b/Analysis/include/Luau/OverloadResolution.h index 8a186e7f..dbe4c06d 100644 --- a/Analysis/include/Luau/OverloadResolution.h +++ b/Analysis/include/Luau/OverloadResolution.h @@ -62,9 +62,20 @@ struct OverloadResolver std::vector> nonviableOverloads; InsertionOrderedMap> resolution; + std::pair selectOverload( + TypeId ty, + TypePackId args, + NotNull> uniqueTypes, + bool useFreeTypeBounds + ); - std::pair selectOverload(TypeId ty, TypePackId args, bool useFreeTypeBounds); - void resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector* argExprs); + void resolve( + TypeId fnTy, + const TypePack* args, + AstExpr* selfExpr, + const std::vector* argExprs, + NotNull> uniqueTypes + ); private: std::optional testIsSubtype(const Location& location, TypeId subTy, TypeId superTy); @@ -74,6 +85,7 @@ struct OverloadResolver const TypePack* args, AstExpr* fnLoc, const std::vector* argExprs, + NotNull> uniqueTypes, bool callMetamethodOk = true ); static bool isLiteral(AstExpr* expr); @@ -83,7 +95,8 @@ struct OverloadResolver const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, - const std::vector* argExprs + const std::vector* argExprs, + NotNull> uniqueTypes ); size_t indexof(Analysis analysis); void add(Analysis analysis, TypeId ty, ErrorVec&& errors); diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 357c800c..ca2e2334 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -8,6 +8,7 @@ #include "Luau/TypeCheckLimits.h" #include "Luau/TypeFunction.h" #include "Luau/TypeFwd.h" +#include "Luau/TypeIds.h" #include "Luau/TypePairHash.h" #include "Luau/TypePath.h" @@ -52,6 +53,56 @@ struct SubtypingReasoningHash using SubtypingReasonings = DenseHashSet; inline const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid}; +/* + * When we encounter a generic pack over the course of a subtyping test, we need + * to tentatively map that generic pack onto a type pack on the other side. This endeavor is complicated by the facts that the scope of generic packs + * 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. + */ + +struct MappedGenericEnvironment +{ + struct MappedGenericFrame + { + DenseHashMap> mappings; + std::optional parentScopeIndex; // nullopt if this is the root frame + DenseHashSet children{0}; + + MappedGenericFrame(DenseHashMap> mappings, std::optional parentScopeIndex); + }; + + std::vector frames; + std::optional currentScopeIndex = std::nullopt; // nullopt if we are in the global scope + + struct Unmapped + { + // The index of the scope where the generic pack was quantified + size_t scopeIndex; + }; + + struct NotBindable + { + }; + + using LookupResult = Luau::Variant; + + // Looks up the given generic pack starting from the innermost scope and working outwards. + // Returns Unmapped if the pack is not mapped in the current scope, and NotBindable if it is not bindable in the current or any enclosing scopes. + LookupResult lookupGenericPack(TypePackId genericTp) const; + + // Pushes a new scope onto the stack of frames. The new scope will contain the generic packs which are being quantified bound to nullopt. + // Also updates currentScopeIndex to point to the new frame. + void pushFrame(const std::vector& genericTps); + + // Restores the current scope to the parent of the current frame. Doesn't actually discard any mappings, since we may need them later. + void popFrame(); + + bool bindGeneric(TypePackId genericTp, TypePackId bindeeTp); +}; + struct SubtypingResult { bool isSubtype = false; @@ -61,7 +112,7 @@ struct SubtypingResult /// The reason for isSubtype to be false. May not be present even if /// isSubtype is false, depending on the input types. SubtypingReasonings reasoning{kEmptyReasoning}; - DenseHashMap mappedGenericPacks{nullptr}; + DenseHashMap mappedGenericPacks_DEPRECATED{nullptr}; // If this subtype result required testing free types, we might be making // assumptions about what the free type eventually resolves to. If so, @@ -127,7 +178,8 @@ struct SubtypingEnvironment GenericBounds& getMappedTypeBounds(TypeId ty, NotNull iceReporter); // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance GenericBounds_DEPRECATED& getMappedTypeBounds_DEPRECATED(TypeId ty); - TypePackId* getMappedPackBounds(TypePackId tp); + // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance + TypePackId* getMappedPackBounds_DEPRECATED(TypePackId tp); /* * When we encounter a generic over the course of a subtyping test, we need @@ -138,7 +190,10 @@ struct SubtypingEnvironment DenseHashMap> mappedGenerics{nullptr}; // TODO: Clip with LuauSubtypingGenericsDoesntUseVariance DenseHashMap mappedGenerics_DEPRECATED{nullptr}; - DenseHashMap mappedGenericPacks{nullptr}; + + MappedGenericEnvironment mappedGenericPacks; + // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance + DenseHashMap mappedGenericPacks_DEPRECATED{nullptr}; /* * See the test cyclic_tables_are_assumed_to_be_compatible_with_extern_types for @@ -169,6 +224,10 @@ struct Subtyping TypeCheckLimits limits; + // If a type is known to have a single unique reference, then we can perform + // a covariant test where an invariant test would otherwise be required. + const DenseHashSet* uniqueTypes = nullptr; + enum class Variance { Covariant, @@ -262,6 +321,7 @@ 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 MetatableType* subMt, const MetatableType* superMt, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull scope); SubtypingResult isCovariantWith( @@ -270,8 +330,14 @@ struct Subtyping const ExternType* superExternType, NotNull scope ); - SubtypingResult - isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + TypeId subTy, + const ExternType* subExternType, + TypeId superTy, + const TableType* superTable, + NotNull + ); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const FunctionType* subFunction, @@ -288,8 +354,14 @@ struct Subtyping const TableIndexer& superIndexer, NotNull scope ); - SubtypingResult - isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name, NotNull); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const Property& subProperty, + const Property& superProperty, + const std::string& name, + bool forceCovariantTest, + NotNull scope + ); SubtypingResult isCovariantWith( SubtypingEnvironment& env, @@ -321,8 +393,12 @@ struct Subtyping const TypeIds& superTables, NotNull scope ); - SubtypingResult - isCovariantWith(SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction, NotNull); + SubtypingResult isCovariantWith( + SubtypingEnvironment& env, + const NormalizedFunctionType& subFunction, + const NormalizedFunctionType& superFunction, + NotNull + ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull scope); SubtypingResult isCovariantWith( @@ -345,7 +421,8 @@ struct Subtyping ); bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp); - bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp); + // Clip with LuauSubtypingGenericPacksDoesntUseVariance + bool bindGeneric_DEPRECATED(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) const; template TypeId makeAggregateType(const Container& container, TypeId orElse); @@ -355,11 +432,7 @@ struct Subtyping [[noreturn]] void unexpected(TypeId ty); [[noreturn]] void unexpected(TypePackId tp); - SubtypingResult trySemanticSubtyping(SubtypingEnvironment& env, - TypeId subTy, - TypeId superTy, - NotNull scope, - SubtypingResult& original); + SubtypingResult trySemanticSubtyping(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull scope, SubtypingResult& original); SubtypingResult checkGenericBounds( const SubtypingEnvironment::GenericBounds& bounds, diff --git a/Analysis/include/Luau/Transpiler.h b/Analysis/include/Luau/Transpiler.h index b8553a70..586fd92e 100644 --- a/Analysis/include/Luau/Transpiler.h +++ b/Analysis/include/Luau/Transpiler.h @@ -25,7 +25,7 @@ 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 transpileWithTypes(AstStatBlock& block, const CstNodeMap& cstNodeMap); // Only fails when parsing fails TranspileResult transpile(std::string_view source, ParseOptions options = ParseOptions{}, bool withTypes = false); diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 320d87af..cc184c53 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -317,8 +317,12 @@ struct MagicFunctionTypeCheckContext struct MagicFunction { - virtual std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) = 0; + virtual std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) = 0; // Callback to allow custom typechecking of builtin function calls whose argument types // will only be resolved after constraint solving. For example, the arguments to string.format @@ -355,13 +359,7 @@ struct FunctionType ); // Local monomorphic function - FunctionType( - TypeLevel level, - TypePackId argTypes, - TypePackId retTypes, - std::optional defn = {}, - bool hasSelf = false - ); + FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional defn = {}, bool hasSelf = false); // Local polymorphic function FunctionType( diff --git a/Analysis/include/Luau/TypePack.h b/Analysis/include/Luau/TypePack.h index cd917686..5dcced9a 100644 --- a/Analysis/include/Luau/TypePack.h +++ b/Analysis/include/Luau/TypePack.h @@ -231,7 +231,11 @@ 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); -std::pair, std::optional> flatten(TypePackId tp, const DenseHashMap& mappedGenericPacks); +// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance +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! @@ -257,4 +261,16 @@ LUAU_NOINLINE T* emplaceTypePack(TypePackVar* ty, Args&&... args) template<> LUAU_NOINLINE Unifiable::Bound* emplaceTypePack(TypePackVar* ty, TypePackId& tyArg); +/* + * Takes a slice of a TypePack, starting at sliceIndex, and up to and including the tail. toBeSliced should be already decomposed into head and tail. + */ +TypePackId sliceTypePack( + size_t sliceIndex, + TypePackId toBeSliced, + std::vector& head, + std::optional tail, + NotNull builtinTypes, + NotNull arena +); + } // namespace Luau diff --git a/Analysis/include/Luau/TypePath.h b/Analysis/include/Luau/TypePath.h index 785f8884..1dc959d4 100644 --- a/Analysis/include/Luau/TypePath.h +++ b/Analysis/include/Luau/TypePath.h @@ -111,9 +111,17 @@ struct Reduction bool operator==(const Reduction& other) const; }; +// Component representing a mapped generic pack. Allows traversal into the pack that a generic pack was mapped to. +struct GenericPackMapping +{ + TypePackId mappedType; + + bool operator==(const GenericPackMapping& other) const; +}; + /// A single component of a path, representing one inner type or type pack to /// traverse into. -using Component = Luau::Variant; +using Component = Luau::Variant; /// A path through a type or type pack accessing a particular type or type pack /// contained within. @@ -190,6 +198,7 @@ struct PathHash size_t operator()(const PackField& field) const; size_t operator()(const PackSlice& slice) const; size_t operator()(const Reduction& reduction) const; + size_t operator()(const GenericPackMapping& mapping) const; size_t operator()(const Component& component) const; size_t operator()(const Path& path) const; }; @@ -218,6 +227,7 @@ struct PathBuilder PathBuilder& rets(); PathBuilder& tail(); PathBuilder& packSlice(size_t start_index); + PathBuilder& mappedGenericPack(TypePackId mappedType); }; } // namespace TypePath @@ -231,10 +241,21 @@ 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); -// TODO: clip traverse_DEPRECATED along with `LuauReturnMappedGenericPacksFromSubtyping2` +// To keep my head straight when clipping: +// LuauReturnMappedGenericPacksFromSubtyping2 expects mappedGenericPacks AND arena +// LuauSubtypingGenericPacksDoesntUseVariance expects just arena. this is the final state + +// TODO: clip below two along with `LuauReturnMappedGenericPacksFromSubtyping2` 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 +std::optional traverse_DEPRECATED( TypePackId root, const Path& path, NotNull builtinTypes, @@ -242,6 +263,13 @@ std::optional traverse( NotNull arena ); std::optional traverse( + TypeId root, + const Path& path, + NotNull builtinTypes, + NotNull arena +); +// TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance +std::optional traverse_DEPRECATED( TypeId root, const Path& path, NotNull builtinTypes, @@ -264,7 +292,7 @@ std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, /// @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( +std::optional traverseForType_DEPRECATED( TypeId root, const Path& path, NotNull builtinTypes, @@ -272,6 +300,19 @@ std::optional traverseForType( 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 +/// @param path the path to traverse +/// @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 +); + /// 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 @@ -286,7 +327,7 @@ std::optional traverseForType_DEPRECATED(TypePackId root, const Path& pa /// @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( +std::optional traverseForType_DEPRECATED( TypePackId root, const Path& path, NotNull builtinTypes, @@ -294,6 +335,19 @@ std::optional traverseForType( 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) +/// @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 +); + /// 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 @@ -309,7 +363,7 @@ std::optional traverseForPack_DEPRECATED(TypeId root, const Path& pa /// @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( +std::optional traverseForPack_DEPRECATED( TypeId root, const Path& path, NotNull builtinTypes, @@ -317,6 +371,19 @@ std::optional traverseForPack( 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 +/// @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 +); + /// 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 @@ -331,7 +398,7 @@ std::optional traverseForPack_DEPRECATED(TypePackId root, const Path /// @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( +std::optional traverseForPack_DEPRECATED( TypePackId root, const Path& path, NotNull builtinTypes, @@ -339,8 +406,29 @@ std::optional traverseForPack( 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) +/// @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 +); + /// 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 std::optional traverseForIndex(const Path& path); +// Flattens a type pack with generic packs into a type pack without generic packs, using the generics mapping encoded in path. +// Path is assumed to contain only PackField::Tail and GenericPackMapping components. +TypePack flattenPackWithPath(TypePackId root, const Path& path); + +TypePack traverseForFlattenedPack(TypeId root, const Path& path, NotNull builtinTypes, NotNull arena); + +bool matchesPrefix(const Path& prefix, const Path& full); + } // namespace Luau diff --git a/Analysis/include/Luau/Unifier2.h b/Analysis/include/Luau/Unifier2.h index 4c02f9bc..0117ee82 100644 --- a/Analysis/include/Luau/Unifier2.h +++ b/Analysis/include/Luau/Unifier2.h @@ -33,7 +33,7 @@ enum class UnifyResult TooComplex }; -inline UnifyResult operator &(UnifyResult lhs, UnifyResult rhs) +inline UnifyResult operator&(UnifyResult lhs, UnifyResult rhs) { if (lhs == UnifyResult::Ok) return rhs; diff --git a/Analysis/src/AstUtils.cpp b/Analysis/src/AstUtils.cpp new file mode 100644 index 00000000..c72eab19 --- /dev/null +++ b/Analysis/src/AstUtils.cpp @@ -0,0 +1,64 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "Luau/Ast.h" +#include "Luau/Type.h" + +namespace Luau +{ + +struct AstExprTableFinder : AstVisitor +{ + NotNull> result; + NotNull> astTypes; + + explicit AstExprTableFinder(NotNull> result, NotNull> astTypes) + : result(result) + , astTypes(astTypes) + {} + + bool visit(AstExpr* expr) override + { + return false; + } + + bool visit(AstExprTable* tbl) override + { + const TypeId* ty = astTypes->find(tbl); + LUAU_ASSERT(ty); + if (ty) + result->insert(*ty); + + return true; + } +}; + +void findUniqueTypes(NotNull> uniqueTypes, AstExpr* expr, NotNull> astTypes) +{ + AstExprTableFinder finder{uniqueTypes, astTypes}; + expr->visit(&finder); +} + +template +void findUniqueTypes(NotNull> uniqueTypes, Iter startIt, Iter endIt, NotNull> astTypes) +{ + while (startIt != endIt) + { + AstExpr* expr = *startIt; + if (expr->is()) + findUniqueTypes(uniqueTypes, expr, astTypes); + ++startIt; + } +} + + +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) +{ + findUniqueTypes(uniqueTypes, exprs.begin(), exprs.end(), astTypes); +} + +} diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index de8f9911..9cd15da9 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -42,79 +42,123 @@ namespace Luau struct MagicSelect final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicSetMetatable final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicAssert final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicPack final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicRequire final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicClone final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicFreeze final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicFormat final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; bool typeCheck(const MagicFunctionTypeCheckContext& ctx) override; }; struct MagicMatch final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicGmatch final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; struct MagicFind final : MagicFunction { - std::optional> - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) override; + std::optional> handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate + ) override; bool infer(const MagicFunctionCallContext& ctx) override; }; @@ -411,10 +455,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC // setmetatable(T, MT) -> setmetatable TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}}); addGlobalBinding( - globals, - "setmetatable", - makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), - "@luau" + globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau" ); } else @@ -442,13 +483,17 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC { // declare function assert(value: T, errorMessage: string?): intersect TypeId genericT = arena.addType(GenericType{globalScope, "T"}); - TypeId refinedTy = arena.addType(TypeFunctionInstanceType{ - NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {} - }); + TypeId refinedTy = arena.addType( + TypeFunctionInstanceType{ + NotNull{&builtinTypeFunctions().intersectFunc}, {genericT, arena.addType(NegationType{builtinTypes->falsyType})}, {} + } + ); - TypeId assertTy = arena.addType(FunctionType{ - {genericT}, {}, arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}), arena.addTypePack(TypePack{{refinedTy}}) - }); + TypeId assertTy = arena.addType( + FunctionType{ + {genericT}, {}, arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}), arena.addTypePack(TypePack{{refinedTy}}) + } + ); addGlobalBinding(globals, "assert", assertTy, "@luau"); } @@ -699,83 +744,80 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context) if (iter == end(context.arguments)) { - context.typechecker->reportError( - CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location - ); - return true; - } + context.typechecker->reportError(CountMismatch{1, std::nullopt, 0, CountMismatch::Arg, true, "string.format"}, context.callSite->location); + return true; + } - // we'll suppress any errors for `string.format` if the format string is error suppressing. - if (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, follow(*iter)) == ErrorSuppression::Suppress) - { - return true; - } + // we'll suppress any errors for `string.format` if the format string is error suppressing. + if (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, follow(*iter)) == ErrorSuppression::Suppress) + { + return true; + } - AstExprConstantString* fmt = nullptr; - if (auto index = context.callSite->func->as(); index && context.callSite->self) - fmt = unwrapGroup(index->expr)->as(); + AstExprConstantString* fmt = nullptr; + if (auto index = context.callSite->func->as(); index && context.callSite->self) + fmt = unwrapGroup(index->expr)->as(); - if (!context.callSite->self && context.callSite->args.size > 0) - fmt = context.callSite->args.data[0]->as(); + if (!context.callSite->self && context.callSite->args.size > 0) + fmt = context.callSite->args.data[0]->as(); - std::optional formatString; - if (fmt) - formatString = {fmt->value.data, fmt->value.size}; - else if (auto singleton = get(follow(*iter))) - { - if (auto stringSingleton = get(singleton)) - formatString = {stringSingleton->value}; - } + std::optional formatString; + if (fmt) + formatString = {fmt->value.data, fmt->value.size}; + else if (auto singleton = get(follow(*iter))) + { + if (auto stringSingleton = get(singleton)) + formatString = {stringSingleton->value}; + } - if (!formatString) - { - context.typechecker->reportError(CannotCheckDynamicStringFormatCalls{}, context.callSite->location); - return true; - } + if (!formatString) + { + context.typechecker->reportError(CannotCheckDynamicStringFormatCalls{}, context.callSite->location); + return true; + } - // CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter. - // This does _not_ handle cases like: - // - // local foo : () -> (...string) = (nil :: any) - // print(string.format("%s %d %s", foo())) - // - // ... which should be disallowed. + // CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter. + // This does _not_ handle cases like: + // + // local foo : () -> (...string) = (nil :: any) + // print(string.format("%s %d %s", foo())) + // + // ... which should be disallowed. - std::vector expected = parseFormatString(context.builtinTypes, formatString->data(), formatString->size()); + std::vector expected = parseFormatString(context.builtinTypes, formatString->data(), formatString->size()); - const auto& [params, tail] = flatten(context.arguments); + const auto& [params, tail] = flatten(context.arguments); - size_t paramOffset = 1; - // Compare the expressions passed with the types the function expects to determine whether this function was called with : or . - bool calledWithSelf = expected.size() == context.callSite->args.size; - // unify the prefix one argument at a time - for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i) + size_t paramOffset = 1; + // Compare the expressions passed with the types the function expects to determine whether this function was called with : or . + bool calledWithSelf = expected.size() == context.callSite->args.size; + // unify the prefix one argument at a time + for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i) + { + TypeId actualTy = params[i + paramOffset]; + TypeId expectedTy = expected[i]; + Location location = context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location; + // use subtyping instead here + SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); + + if (!result.isSubtype) { - TypeId actualTy = params[i + paramOffset]; - TypeId expectedTy = expected[i]; - Location location = - context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location; - // use subtyping instead here - SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); - - if (!result.isSubtype) + switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) { - switch (shouldSuppressErrors(NotNull{&context.typechecker->normalizer}, actualTy)) - { - case ErrorSuppression::Suppress: - break; - case ErrorSuppression::NormalizationFailed: - break; - case ErrorSuppression::DoNotSuppress: - Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); - - if (!reasonings.suppressed) - context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); - } + case ErrorSuppression::Suppress: + break; + case ErrorSuppression::NormalizationFailed: + break; + case ErrorSuppression::DoNotSuppress: + Reasonings reasonings = context.typechecker->explainReasonings(actualTy, expectedTy, location, result); + + if (!reasonings.suppressed) + context.typechecker->reportError(TypeMismatch{expectedTy, actualTy, reasonings.toString()}, location); } } + } - return true; + return true; } static std::vector parsePatternString(NotNull builtinTypes, const char* data, size_t size) @@ -1127,11 +1169,13 @@ TypeId makeStringMetatable(NotNull builtinTypes, SolverMode mode) const TypeId stringToStringType = makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ true); - const TypeId replArgType = arena->addType(UnionType{ - {stringType, - arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), - makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)} - }); + const TypeId replArgType = arena->addType( + UnionType{ + {stringType, + arena->addType(TableType({}, TableIndexer(stringType, stringType), TypeLevel{}, TableState::Generic)), + makeFunction(*arena, std::nullopt, {}, {}, {stringType}, {}, {stringType}, /* checked */ false)} + } + ); const TypeId gsubFunc = makeFunction(*arena, stringType, {}, {}, {stringType, replArgType, optionalNumber}, {}, {stringType, numberType}, /* checked */ false); const TypeId gmatchFunc = @@ -1194,10 +1238,12 @@ TypeId makeStringMetatable(NotNull builtinTypes, SolverMode mode) /* checked */ true )}}, {"pack", - {arena->addType(FunctionType{ - arena->addTypePack(TypePack{{stringType}, variadicTailPack}), - oneStringPack, - })}}, + {arena->addType( + FunctionType{ + arena->addTypePack(TypePack{{stringType}, variadicTailPack}), + oneStringPack, + } + )}}, {"packsize", {makeFunction(*arena, stringType, {}, {}, {}, {}, {numberType}, /* checked */ true)}}, {"unpack", {arena->addType(std::move(stringDotUnpack))}}, }; @@ -1622,8 +1668,12 @@ static std::optional freezeTable(TypeId inputType, const MagicFunctionCa return std::nullopt; } -std::optional> MagicFreeze:: - handleOldSolver(struct TypeChecker&, const std::shared_ptr&, const class AstExprCall&, WithPredicate) +std::optional> MagicFreeze::handleOldSolver( + struct TypeChecker&, + const std::shared_ptr&, + const class AstExprCall&, + WithPredicate +) { return std::nullopt; } @@ -1754,7 +1804,7 @@ bool MagicRequire::infer(const MagicFunctionCallContext& context) if (!checkRequirePathDcr(context.solver, context.callSite->args.data[0])) return false; - if (auto moduleInfo = context.solver->moduleResolver->resolveModuleInfo(context.solver->currentModuleName, *context.callSite)) + if (auto moduleInfo = context.solver->moduleResolver->resolveModuleInfo(context.solver->module->name, *context.callSite)) { TypeId moduleType = context.solver->resolveModule(*moduleInfo, context.callSite->location); TypePackId moduleResult = context.solver->arena->addTypePack({moduleType}); diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index 512ca155..7ec9c5de 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -27,6 +27,7 @@ LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAGVARIABLE(LuauRefineNoRefineAlways) LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions) +LUAU_FASTFLAG(LuauEGFixGenericsList) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauRawGetHandlesNil) @@ -108,11 +109,13 @@ std::optional> tryDistributeTypeFunctionApp( if (results.size() == 1) return {{results[0], Reduction::MaybeOk, {}, {}}}; - TypeId resultTy = ctx->arena->addType(TypeFunctionInstanceType{ - NotNull{&builtinTypeFunctions().unionFunc}, - std::move(results), - {}, - }); + TypeId resultTy = ctx->arena->addType( + TypeFunctionInstanceType{ + NotNull{&builtinTypeFunctions().unionFunc}, + std::move(results), + {}, + } + ); if (ctx->solver) ctx->pushConstraint(ReduceConstraint{resultTy}); @@ -318,9 +321,12 @@ TypeFunctionReductionResult unmTypeFunction( if (UnifyResult::Ok != u2.unify(inferredArgPack, instantiatedMmFtv->argTypes)) 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? - return {std::nullopt, Reduction::Erroneous, {}, {}}; + 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? + return {std::nullopt, Reduction::Erroneous, {}, {}}; + } if (std::optional ret = first(instantiatedMmFtv->retTypes)) return {ret, Reduction::MaybeOk, {}, {}}; @@ -1334,9 +1340,8 @@ TypeFunctionReductionResult refineTypeFunction( if (FFlag::LuauEagerGeneralization4) { - targetIsPending = FFlag::LuauDoNotBlockOnStuckTypeFunctions - ? isBlockedOrUnsolvedType(targetTy) - : is(targetTy); + targetIsPending = FFlag::LuauDoNotBlockOnStuckTypeFunctions ? isBlockedOrUnsolvedType(targetTy) + : is(targetTy); } else { diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 2856dda4..dc91001f 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -49,7 +49,11 @@ LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) LUAU_FASTFLAGVARIABLE(LuauInstantiateResolvedTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauPushTypeConstraint) +LUAU_FASTFLAGVARIABLE(LuauEGFixGenericsList) LUAU_FASTFLAGVARIABLE(LuauNumericUnaryOpsDontProduceNegationRefinements) +LUAU_FASTFLAGVARIABLE(LuauInitializeDefaultGenericParamsAtProgramPoint) +LUAU_FASTFLAGVARIABLE(LuauNoConstraintGenRecursionLimitIce) +LUAU_FASTFLAGVARIABLE(LuauCacheDuplicateHasPropConstraints) namespace Luau { @@ -289,7 +293,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) moduleFnTy, /*interiorTypes*/ std::vector{}, /*hasDeprecatedAttribute*/ false, - /*deprecatedInfo*/{}, + /*deprecatedInfo*/ {}, /*noGenerics*/ true } ); @@ -373,9 +377,8 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) { - const TypeId ft = FFlag::LuauEagerGeneralization4 - ? Luau::freshType(arena, builtinTypes, scope.get(), polarity) - : Luau::freshType(arena, builtinTypes, scope.get()); + const TypeId ft = FFlag::LuauEagerGeneralization4 ? Luau::freshType(arena, builtinTypes, scope.get(), polarity) + : Luau::freshType(arena, builtinTypes, scope.get()); interiorFreeTypes.back().types.push_back(ft); @@ -790,16 +793,27 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc TypeId initialType = arena->addType(BlockedType{}); TypeFun initialFun{initialType}; - for (const auto& [name, gen] : createGenerics(defnScope, alias->generics, /* useCache */ true)) + /* The boolean toggle `addTypes` decides whether or not to introduce the generic type/pack param into the privateType/Pack bindings. + This map is used by resolveType(Pack) to determine whether or not to produce an error for `F`. Because we are delaying + the the initialization of the generic default to the point at which we check the type alias, we need to ensure that we don't + prematurely add `T` as this will cause us to allow the above example (T is in the bindings so we return that as the resolved type). + Done this way, we can evaluate the default safely and then introduce the variable into the map again once the default has been + evaluated. Note, only generic type aliases support default generic parameters. + */ + + for (const auto& [name, gen] : createGenerics( + defnScope, alias->generics, /* useCache */ true, /* addTypes */ !FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint + )) { initialFun.typeParams.push_back(gen); } - for (const auto& [name, genPack] : createGenericPacks(defnScope, alias->genericPacks, /* useCache */ true)) + for (const auto& [name, genPack] : createGenericPacks( + defnScope, alias->genericPacks, /* useCache */ true, /* addTypes */ !FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint + )) { initialFun.typePackParams.push_back(genPack); } - initialFun.definitionLocation = alias->location; if (alias->exported) @@ -1002,7 +1016,20 @@ ControlFlow ConstraintGenerator::visitBlockWithoutChildScope(const ScopePtr& sco ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat) { - RecursionLimiter limiter{"ConstraintGenerator", &recursionCount, FInt::LuauCheckRecursionLimit}; + std::optional counter; + std::optional limiter; + if (FFlag::LuauNoConstraintGenRecursionLimitIce) + { + counter.emplace(&recursionCount); + + if (recursionCount >= FInt::LuauCheckRecursionLimit) + { + reportCodeTooComplex(stat->location); + return ControlFlow::None; + } + } + else + limiter.emplace("ConstraintGenerator", &recursionCount, FInt::LuauCheckRecursionLimit); if (auto s = stat->as()) return visit(scope, s); @@ -1793,6 +1820,39 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifState return ControlFlow::None; } +void ConstraintGenerator::resolveGenericDefaultParameters(const ScopePtr& defnScope, AstStatTypeAlias* alias, const TypeFun& fun) +{ + LUAU_ASSERT(FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint); + + LUAU_ASSERT(alias->generics.size == fun.typeParams.size()); + for (size_t i = 0; i < alias->generics.size; i++) + { + auto astTy = alias->generics.data[i]; + auto param = fun.typeParams[i]; + if (param.defaultValue && astTy->defaultValue != nullptr) + { + auto resolvesTo = astTy->defaultValue; + auto toUnblock = *param.defaultValue; + emplaceType(asMutable(toUnblock), resolveType(defnScope, resolvesTo, /* inTypeArguments */ false)); + } + defnScope->privateTypeBindings[astTy->name.value] = TypeFun{param.ty}; + } + + LUAU_ASSERT(alias->genericPacks.size == fun.typePackParams.size()); + for (size_t i = 0; i < alias->genericPacks.size; i++) + { + auto astPack = alias->genericPacks.data[i]; + auto param = fun.typePackParams[i]; + if (param.defaultValue && astPack->defaultValue != nullptr) + { + auto resolvesTo = astPack->defaultValue; + auto toUnblock = *param.defaultValue; + emplaceTypePack(asMutable(toUnblock), resolveTypePack(defnScope, resolvesTo, /* inTypeArguments */ false)); + } + defnScope->privateTypePackBindings[astPack->name.value] = param.tp; + } +} + ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* alias) { if (alias->name == kParseNameError) @@ -1821,6 +1881,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* if (bindingIt == typeBindings->end() || defnScope == nullptr) return ControlFlow::None; + if (FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint) + resolveGenericDefaultParameters(*defnScope, alias, bindingIt->second); + TypeId ty = resolveType(*defnScope, alias->type, /* inTypeArguments */ false, /* replaceErrorWithFresh */ false); TypeId aliasTy = bindingIt->second.type; @@ -2064,7 +2127,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte { reportError( declaredExternType->location, - GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value) + GenericError{ + format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value) } ); @@ -2095,7 +2159,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte if (declaredExternType->indexer) { - RecursionCounter counter{&recursionCount}; + std::optional counter; + if (!FFlag::LuauNoConstraintGenRecursionLimitIce) + counter.emplace(&recursionCount); if (recursionCount >= FInt::LuauCheckRecursionLimit) { @@ -2531,7 +2597,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 if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) updateRValueRefinements(scope, def, resultTy); // TODO: typestates: track this as an assignment else @@ -2759,7 +2825,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* if (forceSingleton) return Inference{singletonType}; - return Inference { builtinTypes->booleanType }; + return Inference{builtinTypes->booleanType}; } @@ -2876,12 +2942,20 @@ Inference ConstraintGenerator::checkIndexName( result = *it->second.readTy; } + if (FFlag::LuauCacheDuplicateHasPropConstraints) + { + if (auto cachedHasPropResult = propIndexPairsSeen.find({obj,index})) + result = *cachedHasPropResult; + } + if (!result) { result = arena->addType(BlockedType{}); auto c = addConstraint(scope, indexee->location, HasPropConstraint{result, obj, index, ValueContext::RValue, inConditional(typeContext)}); getMutable(result)->setOwner(c); + if (FFlag::LuauCacheDuplicateHasPropConstraints) + propIndexPairsSeen[{obj, index}] = result; } if (key) @@ -3400,7 +3474,6 @@ void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local if (annotatedTy) addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy}); - } void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId rhsType) @@ -3732,16 +3805,46 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu if (FFlag::LuauEagerGeneralization4) { - // Some of the types in argTypes will eventually be generics, and some - // will not. The ones that are not generic will be pruned when - // GeneralizationConstraint dispatches. - genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end()); - varargPack = follow(varargPack); - returnType = follow(returnType); - if (varargPack == returnType) - genericTypePacks = {varargPack}; + if (FFlag::LuauEGFixGenericsList) + { + // Some of the unannotated parameters in argTypes will eventually be + // generics, and some will not. The ones that are not generic will be + // pruned when GeneralizationConstraint dispatches. + + // The self parameter never has an annotation and so could always become generic. + if (fn->self) + genericTypes.push_back(argTypes[0]); + + size_t typeIndex = fn->self ? 1 : 0; + for (auto astArg : fn->args) + { + TypeId argTy = argTypes.at(typeIndex); + if (!astArg->annotation) + genericTypes.push_back(argTy); + + ++typeIndex; + } + + varargPack = follow(varargPack); + returnType = follow(returnType); + if (varargPack == returnType) + genericTypePacks = {varargPack}; + else + genericTypePacks = {varargPack, returnType}; + } else - genericTypePacks = {varargPack, returnType}; + { + // Some of the types in argTypes will eventually be generics, and some + // will not. The ones that are not generic will be pruned when + // GeneralizationConstraint dispatches. + genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end()); + varargPack = follow(varargPack); + returnType = follow(returnType); + if (varargPack == returnType) + genericTypePacks = {varargPack}; + else + genericTypePacks = {varargPack, returnType}; + } } // If there is both an annotation and an expected type, the annotation wins. @@ -4031,7 +4134,7 @@ TypeId ConstraintGenerator::resolveFunctionType( { ftv.isDeprecatedFunction = fn->hasAttribute(AstAttr::Type::Deprecated); } - + // This replicates the behavior of the appropriate FunctionType // constructors. @@ -4235,8 +4338,16 @@ std::vector> ConstraintGenerator::createG std::optional defaultTy = std::nullopt; - if (generic->defaultValue) - defaultTy = resolveType(scope, generic->defaultValue, /* inTypeArguments */ false); + if (FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint) + { + if (generic->defaultValue) + defaultTy = arena->addType(BlockedType{}); + } + else + { + if (generic->defaultValue) + defaultTy = resolveType(scope, generic->defaultValue, /* inTypeArguments */ false); + } if (addTypes) scope->privateTypeBindings[generic->name.value] = TypeFun{genericTy}; @@ -4270,8 +4381,16 @@ std::vector> ConstraintGenerator::cre std::optional defaultTy = std::nullopt; - if (generic->defaultValue) - defaultTy = resolveTypePack(scope, generic->defaultValue, /* inTypeArguments */ false); + if (FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint) + { + if (generic->defaultValue) + defaultTy = arena->addTypePack(BlockedTypePack{}); + } + else + { + if (generic->defaultValue) + defaultTy = resolveTypePack(scope, generic->defaultValue, /* inTypeArguments */ false); + } if (addTypes) scope->privateTypePackBindings[generic->name.value] = genericTy; @@ -4319,6 +4438,9 @@ void ConstraintGenerator::reportCodeTooComplex(Location location) if (logger) logger->captureGenerationError(errors.back()); + + if (FFlag::LuauNoConstraintGenRecursionLimitIce) + recursionLimitMet = true; } TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs) diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 032ded72..04ac5d06 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -3,9 +3,11 @@ #include "Luau/Anyification.h" #include "Luau/ApplyTypeFunction.h" +#include "Luau/AstUtils.h" #include "Luau/Common.h" #include "Luau/DcrLogger.h" #include "Luau/Generalization.h" +#include "Luau/HashUtil.h" #include "Luau/Instantiation.h" #include "Luau/Instantiation2.h" #include "Luau/Location.h" @@ -36,6 +38,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauTrackUniqueness) LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2) @@ -54,13 +57,6 @@ LUAU_FASTFLAG(LuauPushTypeConstraint) namespace Luau { -static void hashCombine(size_t& seed, size_t hash) -{ - // Golden Ratio constant used for better hash scattering - // See https://softwareengineering.stackexchange.com/a/402543 - seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2); -} - bool SubtypeConstraintRecord::operator==(const SubtypeConstraintRecord& other) const { return (subTy == other.subTy) && (superTy == other.superTy) && (variance == other.variance); @@ -378,7 +374,7 @@ ConstraintSolver::ConstraintSolver( NotNull normalizer, NotNull simplifier, NotNull typeFunctionRuntime, - ModuleName moduleName, + ModulePtr module, NotNull moduleResolver, std::vector requireCycles, DcrLogger* logger, @@ -395,7 +391,7 @@ ConstraintSolver::ConstraintSolver( , constraints(borrowConstraints(constraintSet.constraints)) , scopeToFunction(&constraintSet.scopeToFunction) , rootScope(constraintSet.rootScope) - , currentModuleName(std::move(moduleName)) + , module(std::move(module)) , dfg(dfg) , solverConstraintLimit(FInt::LuauSolverConstraintLimit) , moduleResolver(moduleResolver) @@ -414,7 +410,7 @@ ConstraintSolver::ConstraintSolver( NotNull rootScope, std::vector> constraints, NotNull> scopeToFunction, - ModuleName moduleName, + ModulePtr module, NotNull moduleResolver, std::vector requireCycles, DcrLogger* logger, @@ -430,7 +426,7 @@ ConstraintSolver::ConstraintSolver( , constraints(std::move(constraints)) , scopeToFunction(scopeToFunction) , rootScope(rootScope) - , currentModuleName(std::move(moduleName)) + , module(std::move(module)) , dfg(dfg) , solverConstraintLimit(FInt::LuauSolverConstraintLimit) , moduleResolver(moduleResolver) @@ -472,7 +468,7 @@ void ConstraintSolver::run() if (FFlag::DebugLuauLogSolver) { printf( - "Starting solver for module %s (%s)\n", moduleResolver->getHumanReadableModuleName(currentModuleName).c_str(), currentModuleName.c_str() + "Starting solver for module %s (%s)\n", module->humanReadableName.c_str(), module->name.c_str() ); dump(this, opts); printf("Bindings:\n"); @@ -1414,7 +1410,7 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // This is a new type - redefine the location. ttv->definitionLocation = constraint->location; - ttv->definitionModuleName = currentModuleName; + ttv->definitionModuleName = module->name; ttv->instantiatedTypeParams = typeArguments; ttv->instantiatedTypePackParams = packArguments; @@ -1582,7 +1578,12 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNulllocation }; - auto [status, overload] = resolver.selectOverload(fn, argsPack, /*useFreeTypeBounds*/ force); + + DenseHashSet uniqueTypes{nullptr}; + if (FFlag::LuauTrackUniqueness && c.callSite) + findUniqueTypes(NotNull{&uniqueTypes}, c.callSite->args, NotNull{&module->astTypes}); + + auto [status, overload] = resolver.selectOverload(fn, argsPack, NotNull{&uniqueTypes}, /*useFreeTypeBounds*/ force); TypeId overloadToUse = fn; if (status == OverloadResolver::Analysis::Ok) overloadToUse = overload; @@ -1804,7 +1805,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullname; } void ConstraintSolver::reportError(TypeError e) { errors.emplace_back(std::move(e)); - errors.back().moduleName = currentModuleName; + errors.back().moduleName = module->name; } void ConstraintSolver::shiftReferences(TypeId source, TypeId target) @@ -4028,12 +4029,12 @@ TypePackId ConstraintSolver::anyifyModuleReturnTypePackGenerics(TypePackId tp) LUAU_NOINLINE void ConstraintSolver::throwTimeLimitError() const { - throw TimeLimitError(currentModuleName); + throw TimeLimitError(module->name); } LUAU_NOINLINE void ConstraintSolver::throwUserCancelError() const { - throw UserCancelError(currentModuleName); + throw UserCancelError(module->name); } // Instantiate private template implementations for external callers diff --git a/Analysis/src/DcrLogger.cpp b/Analysis/src/DcrLogger.cpp index 3d706fe1..8138fec8 100644 --- a/Analysis/src/DcrLogger.cpp +++ b/Analysis/src/DcrLogger.cpp @@ -306,10 +306,12 @@ void DcrLogger::captureGenerationModule(const ModulePtr& module) void DcrLogger::captureGenerationError(const TypeError& error) { std::string stringifiedError = toString(error); - generationLog.errors.push_back(ErrorSnapshot{ - /* message */ std::move(stringifiedError), - /* location */ error.location, - }); + generationLog.errors.push_back( + ErrorSnapshot{ + /* message */ std::move(stringifiedError), + /* location */ error.location, + } + ); } void DcrLogger::pushBlock(NotNull constraint, TypeId block) @@ -442,10 +444,12 @@ void DcrLogger::captureFinalSolverState(const Scope* rootScope, const std::vecto void DcrLogger::captureTypeCheckError(const TypeError& error) { std::string stringifiedError = toString(error); - checkLog.errors.push_back(ErrorSnapshot{ - /* message */ std::move(stringifiedError), - /* location */ error.location, - }); + checkLog.errors.push_back( + ErrorSnapshot{ + /* message */ std::move(stringifiedError), + /* location */ error.location, + } + ); } std::vector DcrLogger::snapshotBlocks(NotNull c) diff --git a/Analysis/src/EqSatSimplification.cpp b/Analysis/src/EqSatSimplification.cpp index 914f8b3c..a933d3ab 100644 --- a/Analysis/src/EqSatSimplification.cpp +++ b/Analysis/src/EqSatSimplification.cpp @@ -4,6 +4,7 @@ #include "Luau/EqSatSimplificationImpl.h" #include "Luau/EGraph.h" +#include "Luau/HashUtil.h" #include "Luau/Id.h" #include "Luau/Language.h" @@ -84,9 +85,9 @@ size_t TTable::Hash::operator()(const TTable& value) const // We're using pointers here, which does mean platform divergence. I think // it's okay? (famous last words, I know) for (StringId s : value.propNames) - EqSat::hashCombine(hash, EqSat::languageHash(s)); + hashCombine(hash, EqSat::languageHash(s)); - EqSat::hashCombine(hash, EqSat::languageHash(value.storage)); + hashCombine(hash, EqSat::languageHash(value.storage)); return hash; } @@ -415,12 +416,14 @@ Id toId( // `TypeFunctionInstanceType` outside of the provided arena so that // we can access the members without fear of the specific TFIT being // overwritten with a bound type. - return cache(egraph.add(TTypeFun{ - std::make_shared( - tfun->function, tfun->typeArguments, tfun->packArguments, tfun->userFuncName, tfun->userFuncData - ), - std::move(parts) - })); + return cache(egraph.add( + TTypeFun{ + std::make_shared( + tfun->function, tfun->typeArguments, tfun->packArguments, tfun->userFuncName, tfun->userFuncData + ), + std::move(parts) + } + )); } else if (get(ty)) return egraph.add(TNoRefine{}); @@ -2368,11 +2371,12 @@ void Simplifier::intersectTableProperty(Id id) newIntersectionParts.push_back(intersectionParts[index]); } - Id newTableProp = - egraph.add(Intersection{ + Id newTableProp = egraph.add( + Intersection{ toId(egraph, builtinTypes, mappingIdToClass, stringCache, *it->second.readTy), toId(egraph, builtinTypes, mappingIdToClass, stringCache, *table1Ty->props.begin()->second.readTy) - }); + } + ); newIntersectionParts.push_back(egraph.add(TTable{jId, {stringCache.add(it->first)}, {newTableProp}})); diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 8e738140..bafd7303 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -126,9 +126,10 @@ struct ErrorConverter return "'" + s + "'"; }; - auto constructErrorMessage = - [&](std::string givenType, std::string wantedType, std::optional givenModule, std::optional wantedModule - ) -> std::string + auto constructErrorMessage = [&](std::string givenType, + std::string wantedType, + std::optional givenModule, + std::optional wantedModule) -> std::string { std::string given = givenModule ? quote(givenType) + " from " + quote(*givenModule) : quote(givenType); std::string wanted = wantedModule ? quote(wantedType) + " from " + quote(*wantedModule) : quote(wantedType); diff --git a/Analysis/src/FileResolver.cpp b/Analysis/src/FileResolver.cpp index fc36babb..59c00ed8 100644 --- a/Analysis/src/FileResolver.cpp +++ b/Analysis/src/FileResolver.cpp @@ -120,8 +120,10 @@ static RequireSuggestions makeSuggestionsFromNode(std::unique_ptr n return result; } -std::optional RequireSuggester::getRequireSuggestionsImpl(const ModuleName& requirer, const std::optional& path) - const +std::optional RequireSuggester::getRequireSuggestionsImpl( + const ModuleName& requirer, + const std::optional& path +) const { if (!path) return std::nullopt; diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 13a5ec3c..c25630f6 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -992,18 +992,18 @@ ScopePtr findClosestScope_DEPRECATED(const ModulePtr& module, const AstStat* nea ScopePtr findClosestScope(const ModulePtr& module, const Position& scopePos) { LUAU_ASSERT(module->hasModuleScope()); - ScopePtr closest = module->getModuleScope(); - // find the scope the nearest statement belonged to. - for (const auto& [loc, sc] : module->scopes) - { - // We bias towards the later scopes because those correspond to inner scopes. - // in the case of if statements, we create two scopes at the same location for the body of the then - // and else branches, so we need to bias later. This is why the closest update condition has a <= - // instead of a < - if (sc->location.contains(scopePos) && closest->location.begin <= sc->location.begin) - closest = sc; - } - return closest; + ScopePtr closest = module->getModuleScope(); + // find the scope the nearest statement belonged to. + for (const auto& [loc, sc] : module->scopes) + { + // We bias towards the later scopes because those correspond to inner scopes. + // in the case of if statements, we create two scopes at the same location for the body of the then + // and else branches, so we need to bias later. This is why the closest update condition has a <= + // instead of a < + if (sc->location.contains(scopePos) && closest->location.begin <= sc->location.begin) + closest = sc; + } + return closest; } std::optional parseFragment_DEPRECATED( @@ -1248,7 +1248,7 @@ FragmentTypeCheckResult typecheckFragment_( NotNull(cg.rootScope), borrowConstraints(cg.constraints), NotNull{&cg.scopeToFunction}, - incrementalModule->name, + incrementalModule, NotNull{&resolver}, {}, nullptr, @@ -1403,7 +1403,7 @@ FragmentTypeCheckResult typecheckFragment__DEPRECATED( NotNull(cg.rootScope), borrowConstraints(cg.constraints), NotNull{&cg.scopeToFunction}, - incrementalModule->name, + incrementalModule, NotNull{&resolver}, {}, nullptr, diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 9bcc53ea..1982aa92 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -47,6 +47,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) +LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) namespace Luau { @@ -235,7 +236,8 @@ LoadDefinitionFileResult Frontend::loadDefinitionFile( return LoadDefinitionFileResult{false, std::move(parseResult), std::move(sourceModule), nullptr}; Frontend::Stats dummyStats; - ModulePtr checkedModule = check(sourceModule, Mode::Definition, {}, std::nullopt, /*forAutocomplete*/ false, /*recordJsonLog*/ false, dummyStats, {}); + ModulePtr checkedModule = + check(sourceModule, Mode::Definition, {}, std::nullopt, /*forAutocomplete*/ false, /*recordJsonLog*/ false, dummyStats, {}); if (checkedModule->errors.size() > 0) return LoadDefinitionFileResult{false, std::move(parseResult), std::move(sourceModule), std::move(checkedModule)}; @@ -1045,8 +1047,9 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item) return; } - ModulePtr module = - check(sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, item.stats, std::move(typeCheckLimits)); + ModulePtr module = check( + sourceModule, mode, requireCycles, environmentScope, /*forAutocomplete*/ false, item.recordJsonLog, item.stats, std::move(typeCheckLimits) + ); double duration = getTimestamp() - timestamp; @@ -1437,17 +1440,17 @@ ModulePtr check( LUAU_TIMETRACE_ARGUMENT("module", sourceModule.name.c_str()); LUAU_TIMETRACE_ARGUMENT("name", sourceModule.humanReadableName.c_str()); - ModulePtr result = std::make_shared(); - result->checkedInNewSolver = true; - result->name = sourceModule.name; - result->humanReadableName = sourceModule.humanReadableName; - result->mode = mode; - result->internalTypes.owningModule = result.get(); - result->interfaceTypes.owningModule = result.get(); - result->internalTypes.collectSingletonStats = options.collectTypeAllocationStats; - result->allocator = sourceModule.allocator; - result->names = sourceModule.names; - result->root = sourceModule.root; + ModulePtr module = std::make_shared(); + module->checkedInNewSolver = true; + module->name = sourceModule.name; + module->humanReadableName = sourceModule.humanReadableName; + module->mode = mode; + module->internalTypes.owningModule = module.get(); + module->interfaceTypes.owningModule = module.get(); + module->internalTypes.collectSingletonStats = options.collectTypeAllocationStats; + module->allocator = sourceModule.allocator; + module->names = sourceModule.names; + module->root = sourceModule.root; iceHandler->moduleName = sourceModule.name; @@ -1455,27 +1458,27 @@ ModulePtr check( if (recordJsonLog) { logger = std::make_unique(); - std::optional source = fileResolver->readSource(result->name); + std::optional source = fileResolver->readSource(module->name); if (source) { logger->captureSource(source->source); } } - DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, NotNull{&result->defArena}, NotNull{&result->keyArena}, iceHandler); + DataFlowGraph dfg = DataFlowGraphBuilder::build(sourceModule.root, NotNull{&module->defArena}, NotNull{&module->keyArena}, iceHandler); UnifierSharedState unifierState{iceHandler}; unifierState.counters.recursionLimit = FInt::LuauTypeInferRecursionLimit; unifierState.counters.iterationLimit = limits.unifierIterationLimit.value_or(FInt::LuauTypeInferIterationLimit); - Normalizer normalizer{&result->internalTypes, builtinTypes, NotNull{&unifierState}, SolverMode::New}; - SimplifierPtr simplifier = newSimplifier(NotNull{&result->internalTypes}, builtinTypes); + Normalizer normalizer{&module->internalTypes, builtinTypes, NotNull{&unifierState}, SolverMode::New}; + SimplifierPtr simplifier = newSimplifier(NotNull{&module->internalTypes}, builtinTypes); TypeFunctionRuntime typeFunctionRuntime{iceHandler, NotNull{&limits}}; typeFunctionRuntime.allowEvaluation = true; ConstraintGenerator cg{ - result, + module, NotNull{&normalizer}, NotNull{simplifier.get()}, NotNull{&typeFunctionRuntime}, @@ -1490,7 +1493,7 @@ ModulePtr check( requireCycles }; - // FIXME: Delete this flag when clipping FFlag::LuauEagerGeneralization4. + // FIXME: Unwrap this std::option when clipping FFlag::LuauEagerGeneralization4. // // This optional<> only exists so that we can run one constructor when the flag // is set, and another when it is unset. @@ -1499,13 +1502,15 @@ ModulePtr check( if (FFlag::LuauEagerGeneralization4) { ConstraintSet constraintSet = cg.run(sourceModule.root); - result->errors = std::move(constraintSet.errors); + module->errors = std::move(constraintSet.errors); + if (FFlag::LuauNoConstraintGenRecursionLimitIce) + module->constraintGenerationDidNotComplete = cg.recursionLimitMet; cs.emplace( NotNull{&normalizer}, NotNull{simplifier.get()}, NotNull{&typeFunctionRuntime}, - result->name, + module, moduleResolver, requireCycles, logger.get(), @@ -1517,7 +1522,9 @@ ModulePtr check( else { cg.visitModuleRoot(sourceModule.root); - result->errors = std::move(cg.errors); + module->errors = std::move(cg.errors); + if (FFlag::LuauNoConstraintGenRecursionLimitIce) + module->constraintGenerationDidNotComplete = cg.recursionLimitMet; cs.emplace( NotNull{&normalizer}, @@ -1526,7 +1533,7 @@ ModulePtr check( NotNull(cg.rootScope), borrowConstraints(cg.constraints), NotNull{&cg.scopeToFunction}, - result->name, + module, moduleResolver, requireCycles, logger.get(), @@ -1546,11 +1553,11 @@ ModulePtr check( } catch (const TimeLimitError&) { - result->timeout = true; + module->timeout = true; } catch (const UserCancelError&) { - result->cancelled = true; + module->cancelled = true; } stats.dynamicConstraintsCreated += cs->solverConstraints.size(); @@ -1565,23 +1572,23 @@ ModulePtr check( } for (TypeError& e : cs->errors) - result->errors.emplace_back(std::move(e)); + module->errors.emplace_back(std::move(e)); - result->scopes = std::move(cg.scopes); - result->type = sourceModule.type; - result->upperBoundContributors = std::move(cs->upperBoundContributors); + module->scopes = std::move(cg.scopes); + module->type = sourceModule.type; + module->upperBoundContributors = std::move(cs->upperBoundContributors); - if (result->timeout || result->cancelled) + if (module->timeout || module->cancelled) { // If solver was interrupted, skip typechecking and replace all module results with error-supressing types to avoid leaking blocked/pending // types - ScopePtr moduleScope = result->getModuleScope(); + ScopePtr moduleScope = module->getModuleScope(); moduleScope->returnType = builtinTypes->errorTypePack; - for (auto& [name, ty] : result->declaredGlobals) + for (auto& [name, ty] : module->declaredGlobals) ty = builtinTypes->errorType; - for (auto& [name, tf] : result->exportedTypeBindings) + for (auto& [name, tf] : module->exportedTypeBindings) tf.type = builtinTypes->errorType; } else @@ -1600,7 +1607,7 @@ ModulePtr check( NotNull{&dfg}, NotNull{&limits}, sourceModule, - result.get() + module.get() ); break; case Mode::Definition: @@ -1614,7 +1621,7 @@ ModulePtr check( NotNull{&limits}, logger.get(), sourceModule, - result.get() + module.get() ); break; case Mode::NoCheck: @@ -1623,11 +1630,11 @@ ModulePtr check( } catch (const TimeLimitError&) { - result->timeout = true; + module->timeout = true; } catch (const UserCancelError&) { - result->cancelled = true; + module->cancelled = true; } } @@ -1636,16 +1643,16 @@ ModulePtr check( // this is probably, on the whole, a good decision to not annoy users though. if (FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) { - if (result->errors.size() == 1 && get(result->errors[0]) && + if (module->errors.size() == 1 && get(module->errors[0]) && !FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete) - result->errors.clear(); + module->errors.clear(); } ExpectedTypeVisitor etv{ - NotNull{&result->astTypes}, - NotNull{&result->astExpectedTypes}, - NotNull{&result->astResolvedTypes}, - NotNull{&result->internalTypes}, + NotNull{&module->astTypes}, + NotNull{&module->astExpectedTypes}, + NotNull{&module->astResolvedTypes}, + NotNull{&module->internalTypes}, builtinTypes, NotNull{parentScope.get()} }; @@ -1661,37 +1668,37 @@ ModulePtr check( // `result->returnType` is not filled in yet, so we // traverse the return type of the root module. - finder.traverse(result->getModuleScope()->returnType); + finder.traverse(module->getModuleScope()->returnType); - for (const auto& [_, binding] : result->exportedTypeBindings) + for (const auto& [_, binding] : module->exportedTypeBindings) finder.traverse(binding.type); - for (const auto& [_, ty] : result->astTypes) + for (const auto& [_, ty] : module->astTypes) finder.traverse(ty); - for (const auto& [_, ty] : result->astExpectedTypes) + for (const auto& [_, ty] : module->astExpectedTypes) finder.traverse(ty); - for (const auto& [_, tp] : result->astTypePacks) + for (const auto& [_, tp] : module->astTypePacks) finder.traverse(tp); - for (const auto& [_, ty] : result->astResolvedTypes) + for (const auto& [_, ty] : module->astResolvedTypes) finder.traverse(ty); - for (const auto& [_, ty] : result->astOverloadResolvedTypes) + for (const auto& [_, ty] : module->astOverloadResolvedTypes) finder.traverse(ty); - for (const auto& [_, tp] : result->astResolvedTypePacks) + for (const auto& [_, tp] : module->astResolvedTypePacks) finder.traverse(tp); } } - unfreeze(result->interfaceTypes); + unfreeze(module->interfaceTypes); if (FFlag::LuauUseWorkspacePropToChooseSolver) - result->clonePublicInterface(builtinTypes, *iceHandler, SolverMode::New); + module->clonePublicInterface(builtinTypes, *iceHandler, SolverMode::New); else - result->clonePublicInterface_DEPRECATED(builtinTypes, *iceHandler); + module->clonePublicInterface_DEPRECATED(builtinTypes, *iceHandler); if (!FFlag::LuauLimitDynamicConstraintSolving3) { @@ -1699,27 +1706,27 @@ ModulePtr check( { InternalTypeFinder finder; - finder.traverse(result->returnType); + finder.traverse(module->returnType); - for (const auto& [_, binding] : result->exportedTypeBindings) + for (const auto& [_, binding] : module->exportedTypeBindings) finder.traverse(binding.type); - for (const auto& [_, ty] : result->astTypes) + for (const auto& [_, ty] : module->astTypes) finder.traverse(ty); - for (const auto& [_, ty] : result->astExpectedTypes) + for (const auto& [_, ty] : module->astExpectedTypes) finder.traverse(ty); - for (const auto& [_, tp] : result->astTypePacks) + for (const auto& [_, tp] : module->astTypePacks) finder.traverse(tp); - for (const auto& [_, ty] : result->astResolvedTypes) + for (const auto& [_, ty] : module->astResolvedTypes) finder.traverse(ty); - for (const auto& [_, ty] : result->astOverloadResolvedTypes) + for (const auto& [_, ty] : module->astOverloadResolvedTypes) finder.traverse(ty); - for (const auto& [_, tp] : result->astResolvedTypePacks) + for (const auto& [_, tp] : module->astResolvedTypePacks) finder.traverse(tp); } } @@ -1735,10 +1742,10 @@ ModulePtr check( // Notably, we would first need to get to a place where TypeChecker2 is // never in the position of dealing with a FreeType. They should all be // bound to something by the time constraints are solved. - freeze(result->internalTypes); - freeze(result->interfaceTypes); + freeze(module->internalTypes); + freeze(module->interfaceTypes); - return result; + return module; } ModulePtr Frontend::check( diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 6028eb49..3638f997 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -276,7 +276,6 @@ struct ClonePublicInterface : Substitution else if (auto gtp = getMutable(clonedTp)) gtp->scope = nullptr; return clonedTp; - } else { @@ -400,7 +399,7 @@ void Module::clonePublicInterface_DEPRECATED(NotNull builtinTypes, Location{}, // Not amazing but the best we can do. name, InternalError{"An internal type is escaping this module; please report this bug at " - "https://github.com/luau-lang/luau/issues"} + "https://github.com/luau-lang/luau/issues"} ); } @@ -451,7 +450,7 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr Location{}, // Not amazing but the best we can do. name, InternalError{"An internal type is escaping this module; please report this bug at " - "https://github.com/luau-lang/luau/issues"} + "https://github.com/luau-lang/luau/issues"} ); } diff --git a/Analysis/src/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index d9e718bd..12d7e4d0 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -7,6 +7,7 @@ #include "Luau/Type.h" #include "Luau/TypeFunction.h" #include "Luau/TypePack.h" +#include "Luau/TypePath.h" #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" @@ -15,6 +16,7 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) namespace Luau { @@ -43,7 +45,7 @@ OverloadResolver::OverloadResolver( { } -std::pair OverloadResolver::selectOverload(TypeId ty, TypePackId argsPack, bool useFreeTypeBounds) +std::pair OverloadResolver::selectOverload(TypeId ty, TypePackId argsPack, NotNull> uniqueTypes, bool useFreeTypeBounds) { auto tryOne = [&](TypeId f) { @@ -51,6 +53,7 @@ std::pair OverloadResolver::selectOverload(T { Subtyping::Variance variance = subtyping.variance; subtyping.variance = Subtyping::Variance::Contravariant; + subtyping.uniqueTypes = uniqueTypes; SubtypingResult r; if (FFlag::LuauSubtypingGenericsDoesntUseVariance) { @@ -97,14 +100,14 @@ std::pair OverloadResolver::selectOverload(T return {Analysis::OverloadIsNonviable, ty}; } -void OverloadResolver::resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector* argExprs) +void OverloadResolver::resolve(TypeId fnTy, const TypePack* args, AstExpr* selfExpr, const std::vector* argExprs, NotNull> uniqueTypes) { fnTy = follow(fnTy); auto it = get(fnTy); if (!it) { - auto [analysis, errors] = checkOverload(fnTy, args, selfExpr, argExprs); + auto [analysis, errors] = checkOverload(fnTy, args, selfExpr, argExprs, uniqueTypes); add(analysis, fnTy, std::move(errors)); return; } @@ -114,7 +117,7 @@ void OverloadResolver::resolve(TypeId fnTy, const TypePack* args, AstExpr* selfE if (resolution.find(ty) != resolution.end()) continue; - auto [analysis, errors] = checkOverload(ty, args, selfExpr, argExprs); + auto [analysis, errors] = checkOverload(ty, args, selfExpr, argExprs, uniqueTypes); add(analysis, ty, std::move(errors)); } } @@ -184,6 +187,7 @@ std::pair OverloadResolver::checkOverload( const TypePack* args, AstExpr* fnLoc, const std::vector* argExprs, + NotNull> uniqueTypes, bool callMetamethodOk ) { @@ -193,7 +197,7 @@ std::pair OverloadResolver::checkOverload( if (get(fnTy) || get(fnTy) || get(fnTy)) return {Ok, {}}; else if (auto fn = get(fnTy)) - return checkOverload_(fnTy, fn, args, fnLoc, argExprs); // Intentionally split to reduce the stack pressure of this function. + return checkOverload_(fnTy, fn, args, fnLoc, argExprs, uniqueTypes); // Intentionally split to reduce the stack pressure of this function. else if (auto callMm = findMetatableEntry(builtinTypes, discard, fnTy, "__call", callLoc); callMm && callMetamethodOk) { // Calling a metamethod forwards the `fnTy` as self. @@ -203,7 +207,7 @@ std::pair OverloadResolver::checkOverload( std::vector withSelfExprs = *argExprs; withSelfExprs.insert(withSelfExprs.begin(), fnLoc); - return checkOverload(*callMm, &withSelf, fnLoc, &withSelfExprs, /*callMetamethodOk=*/false); + return checkOverload(*callMm, &withSelf, fnLoc, &withSelfExprs, uniqueTypes, /*callMetamethodOk=*/false); } else return {TypeIsNotAFunction, {}}; // Intentionally empty. We can just fabricate the type error later on. @@ -262,7 +266,8 @@ std::pair OverloadResolver::checkOverload_ const FunctionType* fn, const TypePack* args, AstExpr* fnExpr, - const std::vector* argExprs + const std::vector* argExprs, + NotNull> uniqueTypes ) { TypeFunctionContext context{arena, builtinTypes, scope, simplifier, normalizer, typeFunctionRuntime, ice, limits}; @@ -274,6 +279,7 @@ std::pair OverloadResolver::checkOverload_ TypePackId typ = arena->addTypePack(*args); TypeId prospectiveFunction = arena->addType(FunctionType{typ, builtinTypes->anyTypePack}); + subtyping.uniqueTypes = uniqueTypes; SubtypingResult sr = subtyping.isSubtype(fnTy, prospectiveFunction, scope); if (sr.isSubtype) @@ -331,23 +337,59 @@ std::pair OverloadResolver::checkOverload_ } } + // All unsatisfied arguments are supertypes of nil. This overload is a valid match. return {Analysis::Ok, {}}; } if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) { + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + 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 :: ... - 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) + 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) { 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(*requiredTail, sr.mappedGenericPacks).first; + const auto mappedGenHead = flatten_DEPRECATED(*requiredTail, sr.mappedGenericPacks_DEPRECATED).first; const auto prospectiveHead = flatten(typ).first; @@ -365,7 +407,6 @@ std::pair OverloadResolver::checkOverload_ } } } - else if (reason.subPath == TypePath::Path{{TypePath::PackField::Arguments, TypePath::PackField::Tail}} && reason.superPath == justArguments) { @@ -386,6 +427,7 @@ std::pair OverloadResolver::checkOverload_ ErrorVec errors; + // Translate SubtypingReasonings into TypeErrors that could be reported. for (const SubtypingReasoning& reason : sr.reasoning) { /* The return type of our prospective function is always @@ -409,13 +451,25 @@ std::pair OverloadResolver::checkOverload_ : argExprs->size() != 0 ? argExprs->back()->location : fnExpr->location; - std::optional failedSubTy = FFlag::LuauReturnMappedGenericPacksFromSubtyping2 - ? traverseForType(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) - : traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes); - std::optional failedSuperTy = - FFlag::LuauReturnMappedGenericPacksFromSubtyping2 - ? traverseForType(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) - : traverseForType_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); + // TODO: This optional can be unwrapped once we clip LuauSubtypingGenericPacksDoesntUseVariance and LuauReturnMappedGenericPacksFromSubtyping2 + std::optional failedSubTy; + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, arena); + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + 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 + std::optional failedSuperTy; + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + failedSuperTy = traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena); + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + failedSuperTy = traverseForType_DEPRECATED( + prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena + ); + else + failedSuperTy = traverseForType_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); @@ -463,20 +517,36 @@ std::pair OverloadResolver::checkOverload_ LUAU_ASSERT(false); argLocation = fnExpr->location; } - std::optional failedSubTy = traverseForType(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena); + std::optional failedSubTy = + FFlag::LuauSubtypingGenericPacksDoesntUseVariance + ? traverseForType(fnTy, reason.subPath, builtinTypes, arena) + : traverseForType_DEPRECATED(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); std::optional failedSuperTy = - traverseForType(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena); + FFlag::LuauSubtypingGenericPacksDoesntUseVariance + ? traverseForType(prospectiveFunction, reason.superPath, builtinTypes, arena) + : traverseForType_DEPRECATED( + prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena + ); maybeEmplaceError(&errors, argLocation, &reason, failedSubTy, failedSuperTy); } } - std::optional failedSubPack = FFlag::LuauReturnMappedGenericPacksFromSubtyping2 - ? traverseForPack(fnTy, reason.subPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) - : traverseForPack_DEPRECATED(fnTy, reason.subPath, builtinTypes); - std::optional failedSuperPack = - FFlag::LuauReturnMappedGenericPacksFromSubtyping2 - ? traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks}, arena) - : traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); + std::optional failedSubPack; + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + failedSubPack = traverseForPack(fnTy, reason.subPath, builtinTypes, arena); + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + 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::LuauSubtypingGenericPacksDoesntUseVariance) + failedSuperPack = traverseForPack(prospectiveFunction, reason.superPath, builtinTypes, arena); + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + failedSuperPack = + traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes, NotNull{&sr.mappedGenericPacks_DEPRECATED}, arena); + else + failedSuperPack = traverseForPack_DEPRECATED(prospectiveFunction, reason.superPath, builtinTypes); if (failedSubPack && failedSuperPack) { @@ -582,7 +652,9 @@ static std::optional selectOverload( { auto resolver = std::make_unique(builtinTypes, arena, simplifier, normalizer, typeFunctionRuntime, scope, iceReporter, limits, location); - auto [status, overload] = resolver->selectOverload(fn, argsPack, /*useFreeTypeBounds*/ false); + + DenseHashSet uniqueTypes{nullptr}; + auto [status, overload] = resolver->selectOverload(fn, argsPack, NotNull{&uniqueTypes}, /*useFreeTypeBounds*/ false); if (status == OverloadResolver::Analysis::Ok) return overload; @@ -635,12 +707,12 @@ SolveResult solveFunctionCall( { switch (unifyResult) { - case Luau::UnifyResult::Ok: - break; - case Luau::UnifyResult::OccursCheckFailed: - return {SolveResult::CodeTooComplex}; - case Luau::UnifyResult::TooComplex: - return {SolveResult::OccursCheckFailed}; + case Luau::UnifyResult::Ok: + break; + case Luau::UnifyResult::OccursCheckFailed: + return {SolveResult::CodeTooComplex}; + case Luau::UnifyResult::TooComplex: + return {SolveResult::OccursCheckFailed}; } } else diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 31becdc0..4be86352 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -24,6 +24,7 @@ LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAGVARIABLE(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauPushTypeConstraint) +LUAU_FASTFLAGVARIABLE(LuauMorePreciseExternTableRelation) namespace Luau { @@ -265,6 +266,53 @@ static bool isTypeVariable(TypeId ty) Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen); +Relation relateTableToExternType(const TableType* table, const ExternType* cls, SimplifierSeenSet& seen) +{ + + for (auto& [name, prop] : table->props) + { + if (auto propInExternType = lookupExternTypeProp(cls, name)) + { + LUAU_ASSERT(prop.readTy && propInExternType->readTy); + // For all examples, consider: + // + // declare extern type Foobar with + // prop: string | number + // end + // + switch (relate(*prop.readTy, *propInExternType->readTy, seen)) + { + case Relation::Disjoint: + // Consider `{ read prop: boolean }` and `Foobar`, these types are + // disjoint as `_.prop` would be `never. + return Relation::Disjoint; + case Relation::Coincident: + // Consider `{ read prop: string | number }` and `Foobar`, we don't really + // learn anything about these types. + break; + case Relation::Intersects: + // Consider `{ read prop: string | boolean }` and `Foobar`, these types + // intersect (imagine a `Foobar` initialized with `prop = "foo"`). + return Relation::Intersects; + case Relation::Subset: + // Consider `{ read prop: string }` and `Foobar`: we should _roughly_ + // consider this the same as intersecting. + return Relation::Intersects; + case Relation::Superset: + // This is the only mildly interesting case, consider + // `{ read prop: string | number | boolean }` and `Foobar`. + // We can _probably_ consider `Foobar` the subset here. + break; + } + } + } + + // If all the properties of the table were either coincident or + // supersets of the extern property, then we claim that the table + // is a superset. + return Relation::Superset; +} + Relation relateTables(TypeId left, TypeId right, SimplifierSeenSet& seen) { NotNull leftTable{get(left)}; @@ -594,6 +642,9 @@ 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) @@ -634,13 +685,21 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) return Relation::Disjoint; } - if (is(right)) + if (FFlag::LuauMorePreciseExternTableRelation) { - // 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)); + } + 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; + } } return Relation::Disjoint; @@ -1676,20 +1735,20 @@ TypeId TypeSimplifier::union_(TypeId left, TypeId right) switch (r) { - case Relation::Disjoint: - { - TableType result; - result.state = TableState::Sealed; - result.props[propName] = union_(*leftProp.readTy, *rightProp.readTy); - return arena->addType(result); - } - case Relation::Superset: - case Relation::Coincident: - return left; - case Relation::Subset: - return right; - default: - break; + case Relation::Disjoint: + { + TableType result; + result.state = TableState::Sealed; + result.props[propName] = union_(*leftProp.readTy, *rightProp.readTy); + return arena->addType(result); + } + case Relation::Superset: + case Relation::Coincident: + return left; + case Relation::Subset: + return right; + default: + break; } } } diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index b63be0f2..bc96ab2e 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -27,6 +27,8 @@ LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAGVARIABLE(LuauTrackUniqueness) +LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericPacksDoesntUseVariance) namespace Luau { @@ -67,6 +69,121 @@ size_t SubtypingReasoningHash::operator()(const SubtypingReasoning& r) const return TypePath::PathHash()(r.subPath) ^ (TypePath::PathHash()(r.superPath) << 1) ^ (static_cast(r.variance) << 1); } +MappedGenericEnvironment::MappedGenericFrame::MappedGenericFrame( + DenseHashMap> mappings, + const std::optional parentScopeIndex +) + : mappings(std::move(mappings)) + , parentScopeIndex(parentScopeIndex) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); +} + +MappedGenericEnvironment::LookupResult MappedGenericEnvironment::lookupGenericPack(TypePackId genericTp) const +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + genericTp = follow(genericTp); + + std::optional currentFrameIndex = currentScopeIndex; + + while (currentFrameIndex) + { + const MappedGenericFrame& currentFrame = frames[*currentFrameIndex]; + if (const auto mappedPack = currentFrame.mappings.find(genericTp)) + { + if (*mappedPack) + return mappedPack->value(); + else + return Unmapped{*currentFrameIndex}; + } + + currentFrameIndex = currentFrame.parentScopeIndex; + } + + // Do a DFS of children to see if any enclosed scope mentions this generic pack + if (currentScopeIndex) + { + const MappedGenericFrame& baseFrame = frames[*currentScopeIndex]; + std::vector toCheck = std::vector(baseFrame.children.begin(), baseFrame.children.end()); + + while (!toCheck.empty()) + { + const size_t currIndex = toCheck.back(); + toCheck.pop_back(); + + const MappedGenericFrame& currentFrame = frames[currIndex]; + if (const auto mappedPack = currentFrame.mappings.find(genericTp)) + { + if (*mappedPack) + return mappedPack->value(); + else + return Unmapped{currIndex}; + } + + toCheck.insert(toCheck.end(), currentFrame.children.begin(), currentFrame.children.end()); + } + } + + return NotBindable{}; +} + +void MappedGenericEnvironment::pushFrame(const std::vector& genericTps) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + DenseHashMap> mappings{nullptr}; + + for (TypePackId tp : genericTps) + mappings[tp] = std::nullopt; + + frames.emplace_back(std::move(mappings), currentScopeIndex); + + const size_t newFrameIndex = frames.size() - 1; + + if (currentScopeIndex) + frames[*currentScopeIndex].children.insert(newFrameIndex); + + currentScopeIndex = newFrameIndex; +} + +void MappedGenericEnvironment::popFrame() +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + LUAU_ASSERT(currentScopeIndex); + if (currentScopeIndex) + { + const std::optional newFrameIndex = frames[*currentScopeIndex].parentScopeIndex; + currentScopeIndex = newFrameIndex.value_or(0); + } +} + +bool MappedGenericEnvironment::bindGeneric(TypePackId genericTp, TypePackId bindeeTp) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + // We shouldn't bind generic type packs to themselves + if (genericTp == bindeeTp) + return true; + + if (!get(genericTp)) + { + LUAU_ASSERT(!"bindGeneric should not be called with a non-generic type pack"); + return false; + } + + const LookupResult lookupResult = lookupGenericPack(genericTp); + if (const Unmapped* unmapped = get_if(&lookupResult)) + { + frames[unmapped->scopeIndex].mappings[genericTp] = bindeeTp; + return true; + } + else + { + LUAU_ASSERT(!"bindGeneric called on a non-bindable generic type pack"); + return false; + } +} + template static void assertReasoningValid_DEPRECATED(TID subTy, TID superTy, const SubtypingResult& result, NotNull builtinTypes) { @@ -90,8 +207,16 @@ static void assertReasoningValid(TID subTy, TID superTy, const SubtypingResult& for (const SubtypingReasoning& reasoning : result.reasoning) { - LUAU_ASSERT(traverse(subTy, reasoning.subPath, builtinTypes, NotNull{&result.mappedGenericPacks}, arena)); - LUAU_ASSERT(traverse(superTy, reasoning.superPath, builtinTypes, NotNull{&result.mappedGenericPacks}, arena)); + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + 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)); + } } } @@ -430,7 +555,13 @@ struct ApplyMappedGenerics : Substitution TypePackId clean(TypePackId tp) override { - if (auto it = env.getMappedPackBounds(tp)) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + const MappedGenericEnvironment::LookupResult result = env.mappedGenericPacks.lookupGenericPack(tp); + if (const TypePackId* mappedGen = get_if(&result)) + return *mappedGen; + } + else if (auto it = env.getMappedPackBounds_DEPRECATED(tp)) return *it; // Clean is only called when isDirty found a pack bound @@ -536,7 +667,12 @@ bool SubtypingEnvironment::containsMappedType(TypeId ty) const bool SubtypingEnvironment::containsMappedPack(TypePackId tp) const { - if (mappedGenericPacks.contains(tp)) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + if (const MappedGenericEnvironment::LookupResult lookupResult = mappedGenericPacks.lookupGenericPack(tp); get_if(&lookupResult)) + return true; + } + else if (mappedGenericPacks_DEPRECATED.contains(tp)) return true; if (parent) @@ -573,13 +709,15 @@ SubtypingEnvironment::GenericBounds_DEPRECATED& SubtypingEnvironment::getMappedT return mappedGenerics_DEPRECATED[ty]; } -TypePackId* SubtypingEnvironment::getMappedPackBounds(TypePackId tp) +TypePackId* SubtypingEnvironment::getMappedPackBounds_DEPRECATED(TypePackId tp) { - if (auto it = mappedGenericPacks.find(tp)) + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + if (auto it = mappedGenericPacks_DEPRECATED.find(tp)) return it; if (parent) - return parent->getMappedPackBounds(tp); + return parent->getMappedPackBounds_DEPRECATED(tp); // This fallback is reachable in valid cases, unlike the final part of getMappedTypeBounds return nullptr; @@ -675,10 +813,8 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy, NotNull p{subTy, superTy}; - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !env.mappedGenericPacks.empty()) - result.mappedGenericPacks = env.mappedGenericPacks; + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + result.mappedGenericPacks_DEPRECATED = env.mappedGenericPacks_DEPRECATED; if (result.isCacheable) resultCache[p] = result; @@ -772,7 +905,7 @@ struct SeenTypePackSetPopper SeenTypePackSetPopper(Subtyping::SeenTypePackSet* seenTypes, std::pair pair) : seenTypes(seenTypes) - , pair(std::move(pair)) + , pair(std::move(pair)) { LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2); } @@ -810,10 +943,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub const SubtypingResult* cachedResult = resultCache.find({subTy, superTy}); if (cachedResult) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { - for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks) - env.mappedGenericPacks.try_insert(genericTp, boundTp); + for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks_DEPRECATED) + env.mappedGenericPacks_DEPRECATED.try_insert(genericTp, boundTp); } return *cachedResult; @@ -822,10 +955,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub cachedResult = env.tryFindSubtypingResult({subTy, superTy}); if (cachedResult) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { - for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks) - env.mappedGenericPacks.try_insert(genericTp, boundTp); + for (const auto& [genericTp, boundTp] : cachedResult->mappedGenericPacks_DEPRECATED) + env.mappedGenericPacks_DEPRECATED.try_insert(genericTp, boundTp); } return *cachedResult; @@ -1084,7 +1217,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) - result = isCovariantWith(env, p, scope); + { + if (FFlag::LuauTrackUniqueness) + { + const bool forceCovariantTest = uniqueTypes != nullptr && uniqueTypes->contains(subTy); + result = isCovariantWith(env, p.first, p.second, forceCovariantTest, scope); + } + else + result = isCovariantWith(env, p, scope); + } else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) @@ -1136,7 +1277,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId // Match head types pairwise for (size_t i = 0; i < headSize; ++i) - results.push_back(isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}) + results.push_back( + isCovariantWith(env, subHead[i], superHead[i], scope).withBothComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}) ); // Handle mismatched head sizes @@ -1152,9 +1294,48 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId .withSubPath(TypePath::PathBuilder().tail().variadic().build()) .withSuperComponent(TypePath::Index{i, TypePath::Index::Variant::Pack})); } - else if (auto gt = get(*subTail)) + else if (get(*subTail)) { - if (variance == Variance::Covariant) + 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: // @@ -1183,15 +1364,14 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail); } - if (TypePackId* other = env.getMappedPackBounds(*subTail)) + if (TypePackId* other = env.getMappedPackBounds_DEPRECATED(*subTail)) { if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) { const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = tp - ? get( - FFlag::LuauMissingFollowMappedGenericPacks ? follow(tp->tail) : tp->tail) - : nullptr; vtp && vtp->hidden) + if (const VariadicTypePack* vtp = + tp ? get(FFlag::LuauMissingFollowMappedGenericPacks ? follow(tp->tail) : tp->tail) : nullptr; + vtp && vtp->hidden) { TypePackId taillessTp = arena->addTypePack(tp->head); results.push_back(isCovariantWith(env, taillessTp, superTailPack, scope) @@ -1207,7 +1387,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId results.push_back(isCovariantWith(env, *other, superTailPack, scope).withSubComponent(TypePath::PackField::Tail)); } else - env.mappedGenericPacks.try_insert(*subTail, superTailPack); + 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. @@ -1246,9 +1426,47 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId .withSubComponent(TypePath::Index{i, TypePath::Index::Variant::Pack}) .withSuperPath(TypePath::PathBuilder().tail().variadic().build())); } - else if (auto gt = get(*superTail)) + else if (get(*superTail)) { - if (variance == Variance::Contravariant) + 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: // @@ -1277,15 +1495,14 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail); } - if (TypePackId* other = env.getMappedPackBounds(*superTail)) + if (TypePackId* other = env.getMappedPackBounds_DEPRECATED(*superTail)) { if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) { const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = tp - ? get( - FFlag::LuauMissingFollowMappedGenericPacks ? follow(tp->tail) : tp->tail) - : nullptr; vtp && vtp->hidden) + if (const VariadicTypePack* vtp = + tp ? get(FFlag::LuauMissingFollowMappedGenericPacks ? follow(tp->tail) : tp->tail) : nullptr; + vtp && vtp->hidden) { TypePackId taillessTp = arena->addTypePack(tp->head); results.push_back(isCovariantWith(env, subTailPack, taillessTp, scope) @@ -1301,7 +1518,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId results.push_back(isContravariantWith(env, subTailPack, *other, scope).withSuperComponent(TypePath::PackField::Tail)); } else - env.mappedGenericPacks.try_insert(*superTail, subTailPack); + 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. @@ -1336,17 +1553,99 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId // implementation; no need to add it here. results.push_back(isCovariantWith(env, p, scope).withBothComponent(TypePath::PackField::Tail)); } - else if (auto p = get2(*subTail, *superTail)) + else if (get2(*subTail, *superTail)) { - bool ok = bindGeneric(env, *subTail, *superTail); - results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail)); + 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)); + } } - else if (auto p = get2(*subTail, *superTail)) + else if (get2(*subTail, *superTail)) { - if (variance == Variance::Contravariant) + 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(env, *subTail, *superTail); + bool ok = bindGeneric_DEPRECATED(env, *subTail, *superTail); results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail)); } @@ -1366,6 +1665,32 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId // // 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 @@ -1374,7 +1699,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId else { // () -> A... <: () -> ...number - bool ok = bindGeneric(env, *subTail, *superTail); + bool ok = bindGeneric_DEPRECATED(env, *subTail, *superTail); results.push_back(SubtypingResult{ok}.withBothComponent(TypePath::PackField::Tail)); } } @@ -1395,8 +1720,34 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId } else if (get(*subTail)) { - bool ok = bindGeneric(env, *subTail, builtinTypes->emptyTypePack); - return SubtypingResult{ok}.withSubComponent(TypePath::PackField::Tail); + 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); + } } else return SubtypingResult{false} @@ -1419,9 +1770,34 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId } else if (get(*superTail)) { - if (variance == Variance::Contravariant) + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { - bool ok = bindGeneric(env, builtinTypes->emptyTypePack, *superTail); + 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 @@ -1839,7 +2215,20 @@ SubtypingResult Subtyping::isCovariantWith( return {*subSingleton == *superSingleton}; } +// Compatibility shim for the unflagged codepath of FFlag::LuauTrackUniqueness +// TODO: Delete this when clipping that flag. SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull scope) +{ + return isCovariantWith(env, subTable, superTable, /*forceCovariantTest*/ false, scope); +} + +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const TableType* subTable, + const TableType* superTable, + bool forceCovariantTest, + NotNull scope +) { SubtypingResult result{true}; @@ -1866,7 +2255,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl { std::vector results; if (auto subIter = subTable->props.find(name); subIter != subTable->props.end()) - results.push_back(isCovariantWith(env, subIter->second, superProp, name, scope)); + results.push_back(isCovariantWith(env, subIter->second, superProp, name, forceCovariantTest, scope)); else if (subTable->indexer) { if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType, scope).isSubtype) @@ -1874,8 +2263,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl if (superProp.isShared()) { results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, *superProp.readTy, scope) - .withSubComponent(TypePath::TypeField::IndexResult) - .withSuperComponent(TypePath::Property::read(name))); + .withSubComponent(TypePath::TypeField::IndexResult) + .withSuperComponent(TypePath::Property::read(name))); } else { @@ -1934,7 +2323,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta // 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, scope); + return isCovariantWith(env, subTable, superTable, /*forceCovariantTest*/ false, scope); } else { @@ -1970,7 +2359,7 @@ SubtypingResult Subtyping::isCovariantWith( { if (auto classProp = lookupExternTypeProp(subExternType, name)) { - result.andAlso(isCovariantWith(env, *classProp, prop, name, scope)); + result.andAlso(isCovariantWith(env, *classProp, prop, name, /*forceCovariantTest*/ false, scope)); } else { @@ -2009,6 +2398,21 @@ SubtypingResult Subtyping::isCovariantWith( } } + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance && !subFunction->genericPacks.empty()) + { + std::vector packs; + packs.reserve(subFunction->genericPacks.size()); + + for (TypePackId g : subFunction->genericPacks) + { + g = follow(g); + if (get(g)) + packs.emplace_back(g); + } + + env.mappedGenericPacks.pushFrame(packs); + } + { result.orElse( isContravariantWith(env, subFunction->argTypes, superFunction->argTypes, scope).withBothComponent(TypePath::PackField::Arguments) @@ -2041,6 +2445,7 @@ SubtypingResult Subtyping::isCovariantWith( TypeError{scope->location, GenericTypePackCountMismatch{superFunction->genericPacks.size(), subFunction->genericPacks.size()}} ); } + if (FFlag::LuauSubtypingGenericsDoesntUseVariance && !subFunction->generics.empty()) { for (TypeId g : subFunction->generics) @@ -2061,6 +2466,13 @@ SubtypingResult Subtyping::isCovariantWith( } } + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance && !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 + result.isCacheable = false; + } + return result; } @@ -2088,8 +2500,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim LUAU_ASSERT(*it->second.readTy); if (auto stringTable = get(*it->second.readTy)) - result.orElse(isCovariantWith(env, stringTable, superTable, scope) - .withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())); + result.orElse(isCovariantWith(env, stringTable, superTable, /*forceCovariantTest*/ false, scope) + .withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())); } } } @@ -2123,8 +2535,8 @@ SubtypingResult Subtyping::isCovariantWith( LUAU_ASSERT(*it->second.readTy); if (auto stringTable = get(*it->second.readTy)) - result.orElse(isCovariantWith(env, stringTable, superTable, scope) - .withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())); + result.orElse(isCovariantWith(env, stringTable, superTable, /*forceCovariantTest*/ false, scope) + .withSubPath(TypePath::PathBuilder().mt().readProp("__index").build())); } } } @@ -2151,19 +2563,33 @@ SubtypingResult Subtyping::isCovariantWith( const Property& subProp, const Property& superProp, const std::string& name, + bool forceCovariantTest, NotNull scope ) { SubtypingResult res{true}; if (superProp.isShared() && subProp.isShared()) - res.andAlso(isInvariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name))); + { + if (FFlag::LuauTrackUniqueness && forceCovariantTest) + res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name))); + else + res.andAlso(isInvariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name))); + } else { if (superProp.readTy.has_value() && subProp.readTy.has_value()) res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy, scope).withBothComponent(TypePath::Property::read(name))); - if (superProp.writeTy.has_value() && subProp.writeTy.has_value()) - res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy, scope).withBothComponent(TypePath::Property::write(name))); + if (FFlag::LuauTrackUniqueness) + { + if (superProp.writeTy.has_value() && subProp.writeTy.has_value() && !forceCovariantTest) + res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy, scope).withBothComponent(TypePath::Property::write(name))); + } + else + { + if (superProp.writeTy.has_value() && subProp.writeTy.has_value()) + res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy, scope).withBothComponent(TypePath::Property::write(name))); + } if (superProp.isReadWrite()) { @@ -2465,15 +2891,16 @@ SubtypingResult Subtyping::isCovariantWith( * side, it is permissible to tentatively bind that generic to the right side * type. */ -bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) +bool Subtyping::bindGeneric_DEPRECATED(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp) const { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); if (variance == Variance::Contravariant) std::swap(superTp, subTp); if (!get(subTp)) return false; - if (TypePackId* m = env.getMappedPackBounds(subTp)) + if (TypePackId* m = env.getMappedPackBounds_DEPRECATED(subTp)) return *m == superTp; if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) @@ -2483,7 +2910,7 @@ bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePac return true; } - env.mappedGenericPacks[subTp] = superTp; + env.mappedGenericPacks_DEPRECATED[subTp] = superTp; return true; } @@ -2518,11 +2945,13 @@ std::pair Subtyping::handleTypeFunctionReductionResult(const T return {builtinTypes->neverType, errors}; } -SubtypingResult Subtyping::trySemanticSubtyping(SubtypingEnvironment& env, - TypeId subTy, - TypeId superTy, - NotNull scope, - SubtypingResult& original) +SubtypingResult Subtyping::trySemanticSubtyping( + SubtypingEnvironment& env, + TypeId subTy, + TypeId superTy, + NotNull scope, + SubtypingResult& original +) { SubtypingResult semantic = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy), scope); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 907d04cb..e3c52a68 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -2,6 +2,7 @@ #include "Luau/TypeChecker2.h" #include "Luau/Ast.h" +#include "Luau/AstUtils.h" #include "Luau/AstQuery.h" #include "Luau/Common.h" #include "Luau/DcrLogger.h" @@ -36,13 +37,18 @@ LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) +LUAU_FASTFLAG(LuauTrackUniqueness) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) LUAU_FASTFLAGVARIABLE(LuauIceLess) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAGVARIABLE(LuauAllowMixedTables) LUAU_FASTFLAGVARIABLE(LuauSimplifyIntersectionForLiteralSubtypeCheck) +LUAU_FASTFLAGVARIABLE(LuauRemoveGenericErrorForParams) +LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) +LUAU_FASTFLAGVARIABLE(LuauAddErrorCaseForIncompatibleTypePacks) namespace Luau { @@ -567,6 +573,10 @@ TypeId TypeChecker2::lookupAnnotation(AstType* annotation) } TypeId* ty = module->astResolvedTypes.find(annotation); + + if (FFlag::LuauNoConstraintGenRecursionLimitIce && module->constraintGenerationDidNotComplete && !ty) + return builtinTypes->anyType; + LUAU_ASSERT(ty); return checkForTypeFunctionInhabitance(follow(*ty), annotation->location); } @@ -1249,6 +1259,10 @@ void TypeChecker2::visit(AstStatCompoundAssign* stat) visit(&fake, stat); TypeId* resultTy = module->astCompoundAssignResultTypes.find(stat); + + if (FFlag::LuauNoConstraintGenRecursionLimitIce && module->constraintGenerationDidNotComplete && !resultTy) + return; + LUAU_ASSERT(resultTy); TypeId varTy = lookupType(stat->var); @@ -1632,7 +1646,6 @@ void TypeChecker2::visitCall(AstExprCall* call) } } - OverloadResolver resolver{ builtinTypes, NotNull{&module->internalTypes}, @@ -1644,7 +1657,11 @@ void TypeChecker2::visitCall(AstExprCall* call) limits, call->location, }; - resolver.resolve(fnTy, &args, call->func, &argExprs); + DenseHashSet uniqueTypes{nullptr}; + if (FFlag::LuauTrackUniqueness) + findUniqueTypes(NotNull{&uniqueTypes}, argExprs, NotNull{&module->astTypes}); + + resolver.resolve(fnTy, &args, call->func, &argExprs, NotNull{&uniqueTypes}); auto norm = normalizer.normalize(fnTy); if (!norm) @@ -1915,7 +1932,9 @@ void TypeChecker2::visit(AstExprFunction* fn) } else if (get(normalizedFnTy->errors)) { - // Nothing + // If we have an error type, we don't want to do anything else involving the normalized type + if (FFlag::LuauNoConstraintGenRecursionLimitIce) + normalizedFnTy = nullptr; } else if (!normalizedFnTy->hasFunctions()) { @@ -2162,7 +2181,8 @@ void TypeChecker2::visit(AstExprUnary* expr) TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) { std::optional inContext; - if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or && expr->op != AstExprBinary::CompareEq && expr->op != AstExprBinary::CompareNe) + if (expr->op != AstExprBinary::And && expr->op != AstExprBinary::Or && expr->op != AstExprBinary::CompareEq && + expr->op != AstExprBinary::CompareNe) inContext.emplace(&typeContext, TypeContext::Default); visit(expr->left, ValueContext::RValue); @@ -2688,11 +2708,14 @@ void TypeChecker2::visit(AstTypeReference* ty) } ); - if (!ty->hasParameterList) + if (!FFlag::LuauRemoveGenericErrorForParams) { - if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks)) + if (!ty->hasParameterList) { - reportError(GenericError{"Type parameter list is required"}, ty->location); + if ((!alias->typeParams.empty() && !hasDefaultTypes) || (!alias->typePackParams.empty() && !hasDefaultPacks)) + { + reportError(GenericError{"Type parameter list is required"}, ty->location); + } } } @@ -2736,6 +2759,15 @@ void TypeChecker2::visit(AstTypeReference* ty) } } + if (FFlag::LuauAddErrorCaseForIncompatibleTypePacks) + { + // If we require type parameters, but no types are provided and only packs are provided, we report an error. + if (typesRequired != 0 && typesProvided == 0 && packsProvided != 0) + { + reportError(GenericError{"Type parameters must come before type pack parameters"}, ty->location); + } + } + if (extraTypes != 0 && packsProvided == 0) { // Extra types are only collected into a pack if a pack is expected @@ -2761,9 +2793,16 @@ void TypeChecker2::visit(AstTypeReference* ty) } } - if (extraTypes == 0 && packsProvided + 1 == packsRequired) + 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 { - packsProvided += 1; + if (extraTypes == 0 && packsProvided + 1 == packsRequired) + packsProvided += 1; } if (typesProvided != typesRequired || packsProvided != packsRequired) @@ -2905,14 +2944,22 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc if (reasoning.subPath.empty() && reasoning.superPath.empty()) continue; - std::optional optSubLeaf = - FFlag::LuauReturnMappedGenericPacksFromSubtyping2 - ? traverse(subTy, reasoning.subPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena) - : traverse_DEPRECATED(subTy, reasoning.subPath, builtinTypes); - std::optional optSuperLeaf = - FFlag::LuauReturnMappedGenericPacksFromSubtyping2 - ? traverse(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks}, subtyping->arena) - : traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes); + std::optional optSubLeaf; + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + optSubLeaf = traverse(subTy, reasoning.subPath, builtinTypes, subtyping->arena); + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + 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::LuauSubtypingGenericPacksDoesntUseVariance) + optSuperLeaf = traverse(superTy, reasoning.superPath, builtinTypes, subtyping->arena); + else if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + optSuperLeaf = + traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes, NotNull{&r.mappedGenericPacks_DEPRECATED}, subtyping->arena); + else + optSuperLeaf = traverse_DEPRECATED(superTy, reasoning.superPath, builtinTypes); if (!optSubLeaf || !optSuperLeaf) { @@ -2938,7 +2985,9 @@ Reasonings TypeChecker2::explainReasonings_(TID subTy, TID superTy, Location loc { if (FFlag::LuauIceLess) { - reportError(InternalError{"Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack."}, location); + reportError( + InternalError{"Subtyping test returned a reasoning where one path ends at a type and the other ends at a pack."}, location + ); return {}; // TODO test this? } else diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index 36dc9cb6..5e56abad 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -4089,12 +4089,14 @@ void TypeChecker::checkArgumentList( namePath = *path; auto [minParams, optMaxParams] = getParameterExtents(&state.log, paramPack); - state.reportError(TypeError{ - location, - CountMismatch{ - minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, std::move(namePath) + state.reportError( + TypeError{ + location, + CountMismatch{ + minParams, optMaxParams, std::distance(begin(argPack), end(argPack)), CountMismatch::Context::Arg, false, std::move(namePath) + } } - }); + ); }; while (true) @@ -4211,10 +4213,12 @@ void TypeChecker::checkArgumentList( if (std::optional path = getFunctionNameAsString(funName)) namePath = *path; - state.reportError(TypeError{ - funName.location, - CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, std::move(namePath)} - }); + state.reportError( + TypeError{ + funName.location, + CountMismatch{minParams, optMaxParams, paramIndex, CountMismatch::Context::Arg, isVariadic, std::move(namePath)} + } + ); return; } ++paramIter; @@ -4631,12 +4635,14 @@ std::unique_ptr> TypeChecker::checkCallOverload( else overloadsThatDont.push_back(fn); - errors.push_back(OverloadErrorEntry{ - std::move(state.log), - std::move(state.errors), - args->head, - ftv, - }); + errors.push_back( + OverloadErrorEntry{ + std::move(state.log), + std::move(state.errors), + args->head, + ftv, + } + ); } else { diff --git a/Analysis/src/TypePack.cpp b/Analysis/src/TypePack.cpp index b163d78c..6ebeda69 100644 --- a/Analysis/src/TypePack.cpp +++ b/Analysis/src/TypePack.cpp @@ -3,8 +3,10 @@ #include "Luau/Error.h" #include "Luau/TxnLog.h" +#include "Luau/TypeArena.h" LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) namespace Luau { @@ -453,9 +455,13 @@ std::pair, std::optional> flatten(TypePackId tp, return {flattened, tail}; } -std::pair, std::optional> flatten(TypePackId tp, const DenseHashMap& mappedGenericPacks) +std::pair, std::optional> flatten_DEPRECATED( + TypePackId tp, + const DenseHashMap& mappedGenericPacks +) { LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2); + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); tp = mappedGenericPacks.contains(tp) ? *mappedGenericPacks.find(tp) : tp; @@ -538,4 +544,29 @@ LUAU_NOINLINE Unifiable::Bound* emplaceTypePack(TypeP return &ty->ty.emplace(tyArg); } +TypePackId sliceTypePack( + const size_t sliceIndex, + const TypePackId toBeSliced, + std::vector& head, + const std::optional tail, + const NotNull builtinTypes, + const NotNull arena +) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + if (sliceIndex == 0) + return toBeSliced; + else if (sliceIndex == head.size()) + return tail.value_or(builtinTypes->emptyTypePack); + else + { + auto superHeadIter = begin(head); + for (size_t i = 0; i < sliceIndex; ++i) + ++superHeadIter; + std::vector headSlice(std::move(superHeadIter), end(head)); + return arena->addTypePack(std::move(headSlice), tail); + } +} + } // namespace Luau diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index 54ceed7b..06b45f5d 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -18,6 +18,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) // 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 @@ -65,6 +66,12 @@ bool Reduction::operator==(const Reduction& other) const return resultType == other.resultType; } +bool GenericPackMapping::operator==(const GenericPackMapping& other) const +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + return mappedType == other.mappedType; +} + Path Path::append(const Path& suffix) const { std::vector joined(components); @@ -147,6 +154,12 @@ size_t PathHash::operator()(const Reduction& reduction) const return std::hash()(reduction.resultType); } +size_t PathHash::operator()(const GenericPackMapping& mapping) const +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + return std::hash()(mapping.mappedType); +} + size_t PathHash::operator()(const Component& component) const { return visit(*this, component); @@ -299,6 +312,13 @@ PathBuilder& PathBuilder::packSlice(size_t start_index) return *this; } +PathBuilder& PathBuilder::mappedGenericPack(TypePackId mappedType) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + components.emplace_back(GenericPackMapping{mappedType}); + return *this; +} + } // namespace TypePath namespace @@ -306,13 +326,15 @@ namespace struct TraversalState { + // Clip the below two constructors with LuauSubtypingGenericPacksDoesntUseVariance TraversalState(TypeId root, NotNull builtinTypes, const DenseHashMap* mappedGenericPacks, TypeArena* arena) : current(root) , builtinTypes(builtinTypes) - , mappedGenericPacks(mappedGenericPacks) + , mappedGenericPacks_DEPRECATED(mappedGenericPacks) , arena(arena) { } + TraversalState( TypePackId root, NotNull builtinTypes, @@ -321,16 +343,31 @@ struct TraversalState ) : current(root) , builtinTypes(builtinTypes) - , mappedGenericPacks(mappedGenericPacks) + , mappedGenericPacks_DEPRECATED(mappedGenericPacks) + , arena(arena) + { + } + + TraversalState(TypeId root, const NotNull builtinTypes, TypeArena* arena) + : current(root) + , builtinTypes(builtinTypes) + , arena(arena) + { + } + + TraversalState(TypePackId root, NotNull builtinTypes, TypeArena* arena) + : current(root) + , builtinTypes(builtinTypes) , arena(arena) { } TypeOrPack current; NotNull builtinTypes; - // TODO: make these NotNull when LuauReturnMappedGenericPacksFromSubtyping2 is clipped - const DenseHashMap* mappedGenericPacks; - TypeArena* arena; + // TODO: Clip with LuauSubtypingGenericPacksDoesntUseVariance + const DenseHashMap* mappedGenericPacks_DEPRECATED = nullptr; + // TODO: make NotNull when LuauReturnMappedGenericPacksFromSubtyping2 is clipped + TypeArena* arena = nullptr; int steps = 0; void updateCurrent(TypeId ty) @@ -460,7 +497,7 @@ struct TraversalState { auto currentPack = get(current); LUAU_ASSERT(currentPack); - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance) { if (const auto tp = get(*currentPack)) { @@ -475,9 +512,9 @@ struct TraversalState updateCurrent(*it); return true; } - else if (tp->tail && mappedGenericPacks && mappedGenericPacks->contains(*tp->tail)) + else if (tp->tail && mappedGenericPacks_DEPRECATED && mappedGenericPacks_DEPRECATED->contains(*tp->tail)) { - updateCurrent(*mappedGenericPacks->find(*tp->tail)); + updateCurrent(*mappedGenericPacks_DEPRECATED->find(*tp->tail)); LUAU_ASSERT(index.index >= i); return traverse(TypePath::Index{index.index - i, TypePath::Index::Variant::Pack}); } @@ -619,8 +656,10 @@ struct TraversalState if (auto tail = it.tail()) { - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && mappedGenericPacks && mappedGenericPacks->contains(*tail)) - updateCurrent(*mappedGenericPacks->find(*tail)); + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2 && !FFlag::LuauSubtypingGenericPacksDoesntUseVariance && + mappedGenericPacks_DEPRECATED && mappedGenericPacks_DEPRECATED->contains(*tail)) + updateCurrent(*mappedGenericPacks_DEPRECATED->find(*tail)); + else updateCurrent(*tail); return true; @@ -635,21 +674,37 @@ struct TraversalState bool traverse(const TypePath::PackSlice slice) { + // TODO: clip these checks once LuauReturnMappedGenericPacksFromSubtyping2 is clipped + // arena and mappedGenericPacks_DEPRECATED should be NonNull once that happens + LUAU_ASSERT(FFlag::LuauReturnMappedGenericPacksFromSubtyping2); + if (FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + LUAU_ASSERT(arena); + + if (!arena) + return false; + } + if (checkInvariants()) return false; // TODO: clip this check once LuauReturnMappedGenericPacksFromSubtyping2 is clipped // arena and mappedGenericPacks should be NonNull once that happens - if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) - LUAU_ASSERT(arena && mappedGenericPacks); - else if (!arena || !mappedGenericPacks) - return false; + if (!FFlag::LuauSubtypingGenericPacksDoesntUseVariance) + { + if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) + LUAU_ASSERT(arena && mappedGenericPacks_DEPRECATED); + else if (!arena || !mappedGenericPacks_DEPRECATED) + return false; + } - const auto currentPack = get(current); + const TypePackId* currentPack = get(current); if (!currentPack) return false; - auto [flatHead, flatTail] = flatten(*currentPack, *mappedGenericPacks); + auto [flatHead, flatTail] = FFlag::LuauSubtypingGenericPacksDoesntUseVariance + ? flatten(*currentPack) + : flatten_DEPRECATED(*currentPack, *mappedGenericPacks_DEPRECATED); if (flatHead.size() <= slice.start_index) return false; @@ -673,6 +728,18 @@ struct TraversalState return true; } + + bool traverse(const TypePath::GenericPackMapping mapping) + { + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + if (checkInvariants()) + return false; + + updateCurrent(mapping.mappedType); + + return true; + } }; } // namespace @@ -764,6 +831,11 @@ std::string toString(const TypePath::Path& path, bool prefixDot) // https://roblox.atlassian.net/browse/CLI-104422 result << "~~>"; } + else if constexpr (std::is_same_v) + { + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + result << "~"; + } else { static_assert(always_false_v, "Unhandled Component variant"); @@ -980,6 +1052,11 @@ std::string toStringHuman(const TypePath::Path& path) result << "reduces to" << ' '; state = State::Normal; } + else if constexpr (std::is_same_v) + { + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + result << "is a generic pack mapped to "; + } else { static_assert(always_false_v, "Unhandled Component variant"); @@ -1038,6 +1115,8 @@ static bool traverse(TraversalState& state, const Path& path) std::optional traverse_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) return state.current; @@ -1047,6 +1126,8 @@ 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); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) return state.current; @@ -1054,7 +1135,18 @@ std::optional traverse_DEPRECATED(TypePackId root, const Path& path, return std::nullopt; } -std::optional traverse( +std::optional traverse(const TypePackId root, const Path& path, const NotNull builtinTypes, const NotNull arena) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + TraversalState state(follow(root), builtinTypes, arena); + if (traverse(state, path)) + return state.current; + else + return std::nullopt; +} + +std::optional traverse_DEPRECATED( TypeId root, const Path& path, NotNull builtinTypes, @@ -1062,6 +1154,8 @@ std::optional traverse( NotNull arena ) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) return state.current; @@ -1069,7 +1163,7 @@ std::optional traverse( return std::nullopt; } -std::optional traverse( +std::optional traverse_DEPRECATED( TypePackId root, const Path& path, NotNull builtinTypes, @@ -1077,6 +1171,8 @@ std::optional traverse( NotNull arena ) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) return state.current; @@ -1084,8 +1180,21 @@ std::optional traverse( return std::nullopt; } +std::optional traverse(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + TraversalState state(follow(root), builtinTypes, arena); + if (traverse(state, path)) + return state.current; + else + return std::nullopt; +} + std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, NotNull builtinTypes) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { @@ -1096,7 +1205,7 @@ std::optional traverseForType_DEPRECATED(TypeId root, const Path& path, return std::nullopt; } -std::optional traverseForType( +std::optional traverseForType_DEPRECATED( TypeId root, const Path& path, NotNull builtinTypes, @@ -1104,6 +1213,8 @@ std::optional traverseForType( NotNull arena ) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { @@ -1114,8 +1225,24 @@ std::optional traverseForType( return std::nullopt; } +std::optional traverseForType(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + TraversalState state(follow(root), builtinTypes, arena); + if (traverse(state, path)) + { + const TypeId* 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) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { @@ -1126,7 +1253,7 @@ std::optional traverseForType_DEPRECATED(TypePackId root, const Path& pa return std::nullopt; } -std::optional traverseForType( +std::optional traverseForType_DEPRECATED( TypePackId root, const Path& path, NotNull builtinTypes, @@ -1134,6 +1261,8 @@ std::optional traverseForType( NotNull arena ) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { @@ -1144,8 +1273,29 @@ std::optional traverseForType( return std::nullopt; } +std::optional traverseForType( + const TypePackId root, + const Path& path, + const NotNull builtinTypes, + const NotNull arena +) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + TraversalState state(follow(root), builtinTypes, arena); + if (traverse(state, path)) + { + const TypeId* 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) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { @@ -1156,7 +1306,7 @@ std::optional traverseForPack_DEPRECATED(TypeId root, const Path& pa return std::nullopt; } -std::optional traverseForPack( +std::optional traverseForPack_DEPRECATED( TypeId root, const Path& path, NotNull builtinTypes, @@ -1164,6 +1314,8 @@ std::optional traverseForPack( NotNull arena ) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { @@ -1174,8 +1326,29 @@ std::optional traverseForPack( return std::nullopt; } +std::optional traverseForPack( + const TypeId root, + const Path& path, + const NotNull builtinTypes, + const NotNull arena +) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + TraversalState state(follow(root), builtinTypes, arena); + if (traverse(state, path)) + { + const TypePackId* 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) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, nullptr, nullptr); if (traverse(state, path)) { @@ -1186,7 +1359,7 @@ std::optional traverseForPack_DEPRECATED(TypePackId root, const Path return std::nullopt; } -std::optional traverseForPack( +std::optional traverseForPack_DEPRECATED( TypePackId root, const Path& path, NotNull builtinTypes, @@ -1194,6 +1367,8 @@ std::optional traverseForPack( NotNull arena ) { + LUAU_ASSERT(!FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + TraversalState state(follow(root), builtinTypes, mappedGenericPacks, arena); if (traverse(state, path)) { @@ -1204,6 +1379,25 @@ std::optional traverseForPack( return std::nullopt; } +std::optional traverseForPack( + const TypePackId root, + const Path& path, + const NotNull builtinTypes, + const NotNull arena +) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + TraversalState state(follow(root), builtinTypes, arena); + if (traverse(state, path)) + { + const TypePackId* ty = get(state.current); + return ty ? std::make_optional(*ty) : std::nullopt; + } + else + return std::nullopt; +} + std::optional traverseForIndex(const Path& path) { auto componentIter = begin(path.components); @@ -1231,4 +1425,102 @@ std::optional traverseForIndex(const Path& path) return std::nullopt; } +TypePack flattenPackWithPath(TypePackId root, const Path& path) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + std::vector flattened; + + std::optional curr = root; + auto pathIter = begin(path.components); + const auto pathEnd = end(path.components); + + while (curr) + { + TypePackIterator it(*curr); + // Push back curr's head + for (; it != end(*curr); ++it) + flattened.emplace_back(*it); + + // Check if curr has a tail, and if the next bit of path is Tail + GenericPackMapping + curr = it.tail(); + if (!curr || !get(*curr) || pathIter == pathEnd) + break; + + if (const TypePath::PackField* pf = get_if(&*pathIter); !pf || *pf != TypePath::PackField::Tail) + break; + + ++pathIter; + + const TypePath::GenericPackMapping* gpm = get_if(&*pathIter); + if (!gpm) + break; + + ++pathIter; + curr = gpm->mappedType; + } + + return {flattened, curr}; +} + +TypePack traverseForFlattenedPack(const TypeId root, const Path& path, const NotNull builtinTypes, const NotNull arena) +{ + LUAU_ASSERT(FFlag::LuauSubtypingGenericPacksDoesntUseVariance); + + // 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 + size_t splitIndex = 0; + for (size_t i = path.components.size(); i > 0; --i) + { + const TypePath::Component& c = path.components[i - 1]; + + const TypePath::PackField* pf = get_if(&c); + const bool isNotTail = pf == nullptr || *pf != TypePath::PackField::Tail; + const bool isNotGPM = get_if(&c) == nullptr; + + if (isNotTail && isNotGPM) + { + splitIndex = i; + break; + } + } + + // Root is a TypeId, not a TypePackId, so splitIndex should be > 0 + LUAU_ASSERT(splitIndex > 0); + if (splitIndex == path.components.size() || splitIndex == 0) + return {{}, std::nullopt}; + + auto basePathIter = begin(path.components); + std::advance(basePathIter, splitIndex); + const std::optional basePack = traverseForPack(root, Path(std::vector(begin(path.components), basePathIter)), builtinTypes, arena); + + if (!basePack) + { + LUAU_ASSERT(!"Expected to be able to traverse to a TypePackId"); + return {{}, std::nullopt}; + } + + return flattenPackWithPath(*basePack, Path(std::vector(basePathIter, end(path.components)))); +} + +bool matchesPrefix(const Path& prefix, const Path& full) +{ + auto prefixIter = begin(prefix.components); + auto fullIter = begin(full.components); + const auto prefixEnd = end(prefix.components); + const auto fullEnd = end(full.components); + + while (prefixIter != prefixEnd) + { + if (fullIter == fullEnd) + return false; + + if (*prefixIter++ != *fullIter++) + return false; + } + + return true; +} + } // namespace Luau diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 208c13e1..a8da0ef3 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -2807,10 +2807,12 @@ void Unifier::checkChildUnifierTypeMismatch(const ErrorVec& innerErrors, const s if (auto e = hasUnificationTooComplex(innerErrors)) reportError(*e); else if (!innerErrors.empty()) - reportError(TypeError{ - location, - TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front(), mismatchContext()} - }); + reportError( + TypeError{ + location, + TypeMismatch{wantedType, givenType, format("Property '%s' is not compatible.", prop.c_str()), innerErrors.front(), mismatchContext()} + } + ); } void Unifier::ice(const std::string& message, const Location& location) diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 3e72b9ef..836708e5 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -400,8 +400,12 @@ class Parser // `astErrorLocation` is associated with the AstTypeError created // It can be useful to have different error locations so that the parse error can include the next lexeme, while the AstTypeError can precisely // define the location (possibly of zero size) where a type annotation is expected. - AstTypeError* reportMissingTypeError(const Location& parseErrorLocation, const Location& astErrorLocation, const char* format, ...) - LUAU_PRINTF_ATTR(4, 5); + AstTypeError* reportMissingTypeError( + const Location& parseErrorLocation, + const Location& astErrorLocation, + const char* format, + ... + ) LUAU_PRINTF_ATTR(4, 5); AstExpr* reportFunctionArgsError(AstExpr* func, bool self); void reportAmbiguousCallError(); diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 28adc45c..2b6290de 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -1452,9 +1452,11 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArraydata), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) - }); + props.push_back( + AstDeclaredExternTypeProperty{ + AstName(chars->data), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) + } + ); } else { @@ -1485,9 +1487,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArrayname, propName->location, propType, false, Location(propStart, lexer.previousLocation()) - }); + props.push_back( + AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())} + ); } } @@ -2118,16 +2120,18 @@ AstType* Parser::parseTableType(bool inDeclarationContext) { props.push_back(AstTableProp{AstName(chars->data), begin.location, type, access, accessLocation}); if (options.storeCstData) - cstItems.push_back(CstTypeTable::Item{ - CstTypeTable::Item::Kind::StringProperty, - begin.location.begin, - indexerClosePosition, - colonPosition, - tableSeparator(), - lexer.current().location.begin, - allocator.alloc(sourceString, style, blockDepth), - stringPosition - }); + cstItems.push_back( + CstTypeTable::Item{ + CstTypeTable::Item::Kind::StringProperty, + begin.location.begin, + indexerClosePosition, + colonPosition, + tableSeparator(), + lexer.current().location.begin, + allocator.alloc(sourceString, style, blockDepth), + stringPosition + } + ); } else report(begin.location, "String literal contains malformed escape sequence or \\0"); @@ -2148,14 +2152,16 @@ AstType* Parser::parseTableType(bool inDeclarationContext) auto tableIndexerResult = parseTableIndexer(access, accessLocation, begin); indexer = tableIndexerResult.node; if (options.storeCstData) - cstItems.push_back(CstTypeTable::Item{ - CstTypeTable::Item::Kind::Indexer, - tableIndexerResult.indexerOpenPosition, - tableIndexerResult.indexerClosePosition, - tableIndexerResult.colonPosition, - tableSeparator(), - lexer.current().location.begin, - }); + cstItems.push_back( + CstTypeTable::Item{ + CstTypeTable::Item::Kind::Indexer, + tableIndexerResult.indexerOpenPosition, + tableIndexerResult.indexerClosePosition, + tableIndexerResult.colonPosition, + tableSeparator(), + lexer.current().location.begin, + } + ); } } } @@ -2184,14 +2190,16 @@ AstType* Parser::parseTableType(bool inDeclarationContext) props.push_back(AstTableProp{name->name, name->location, type, access, accessLocation}); if (options.storeCstData) - cstItems.push_back(CstTypeTable::Item{ - CstTypeTable::Item::Kind::Property, - Position{0, 0}, - Position{0, 0}, - colonPosition, - tableSeparator(), - lexer.current().location.begin - }); + cstItems.push_back( + CstTypeTable::Item{ + CstTypeTable::Item::Kind::Property, + Position{0, 0}, + Position{0, 0}, + colonPosition, + tableSeparator(), + lexer.current().location.begin + } + ); } if (lexer.current().type == ',' || lexer.current().type == ';') @@ -3859,9 +3867,8 @@ AstExpr* Parser::parseString() style = AstExprConstantString::QuotedRaw; break; case Lexeme::QuotedString: - style = lexer.current().getQuoteStyle() == Lexeme::QuoteStyle::Single - ? AstExprConstantString::QuotedSingle - : AstExprConstantString::QuotedSimple; + style = lexer.current().getQuoteStyle() == Lexeme::QuoteStyle::Single ? AstExprConstantString::QuotedSingle + : AstExprConstantString::QuotedSimple; break; default: LUAU_ASSERT(false && "Invalid string type"); diff --git a/CMakeLists.txt b/CMakeLists.txt index 12613018..404d1923 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -230,6 +230,7 @@ if(LUAU_BUILD_CLI) target_link_libraries(Luau.Repl.CLI PRIVATE osthreads) target_link_libraries(Luau.Analyze.CLI PRIVATE osthreads) + target_link_libraries(Luau.Ast.CLI PRIVATE osthreads) target_link_libraries(Luau.Analyze.CLI PRIVATE Luau.Analysis Luau.CLI.lib Luau.RequireNavigator) diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index cbc1c41f..28dbb925 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -1058,6 +1058,7 @@ struct IrFunction std::vector bcMapping; uint32_t entryBlock = 0; uint32_t entryLocation = 0; + uint32_t endLocation = 0; // For each instruction, an operand that can be used to recompute the value std::vector valueRestoreOps; diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp index 15631542..798eaead 100644 --- a/CodeGen/src/CodeGenContext.cpp +++ b/CodeGen/src/CodeGenContext.cpp @@ -15,6 +15,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenBlockSize, 4 * 1024 * 1024) LUAU_FASTINTVARIABLE(LuauCodeGenMaxTotalSize, 256 * 1024 * 1024) +LUAU_FASTFLAGVARIABLE(LuauCodeGenUnassignedBcTargetAbort) namespace Luau { @@ -439,11 +440,30 @@ void create(lua_State* L, SharedCodeGenContext* codeGenContext) uint32_t instTarget = ir.function.entryLocation; - for (int i = 0; i < proto->sizecode; ++i) + if (FFlag::LuauCodeGenUnassignedBcTargetAbort) { - CODEGEN_ASSERT(ir.function.bcMapping[i].asmLocation >= instTarget); + uint32_t unassignedOffset = ir.function.endLocation - instTarget; - nativeExecData[i] = ir.function.bcMapping[i].asmLocation - instTarget; + for (int i = 0; i < proto->sizecode; ++i) + { + const BytecodeMapping& bcMapping = ir.function.bcMapping[i]; + + 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; + } } // Set first instruction offset to 0 so that entering this function still @@ -574,7 +594,8 @@ template } else { - compilationResult.protoFailures.push_back({protoResult, protos[i]->debugname ? getstr(protos[i]->debugname) : "", protos[i]->linedefined} + compilationResult.protoFailures.push_back( + {protoResult, protos[i]->debugname ? getstr(protos[i]->debugname) : "", protos[i]->linedefined} ); } } diff --git a/CodeGen/src/CodeGenContext.h b/CodeGen/src/CodeGenContext.h index 19ef295e..d3c773a3 100644 --- a/CodeGen/src/CodeGenContext.h +++ b/CodeGen/src/CodeGenContext.h @@ -70,8 +70,10 @@ class StandaloneCodeGenContext final : public BaseCodeGenContext public: StandaloneCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); - [[nodiscard]] virtual std::optional tryBindExistingModule(const ModuleId& moduleId, const std::vector& moduleProtos) - override; + [[nodiscard]] virtual std::optional tryBindExistingModule( + const ModuleId& moduleId, + const std::vector& moduleProtos + ) override; [[nodiscard]] virtual ModuleBindResult bindModule( const std::optional& moduleId, @@ -94,8 +96,10 @@ class SharedCodeGenContext final : public BaseCodeGenContext public: SharedCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext); - [[nodiscard]] virtual std::optional tryBindExistingModule(const ModuleId& moduleId, const std::vector& moduleProtos) - override; + [[nodiscard]] virtual std::optional tryBindExistingModule( + const ModuleId& moduleId, + const std::vector& moduleProtos + ) override; [[nodiscard]] virtual ModuleBindResult bindModule( const std::optional& moduleId, diff --git a/CodeGen/src/CodeGenLower.h b/CodeGen/src/CodeGenLower.h index 79a597eb..ee183c1b 100644 --- a/CodeGen/src/CodeGenLower.h +++ b/CodeGen/src/CodeGenLower.h @@ -112,6 +112,7 @@ inline bool lowerImpl( // Make sure entry block is first CODEGEN_ASSERT(sortedBlocks[0] == 0); + CODEGEN_ASSERT(function.entryBlock == 0); for (size_t i = 0; i < sortedBlocks.size(); ++i) { @@ -123,6 +124,7 @@ inline bool lowerImpl( CODEGEN_ASSERT(block.start != ~0u); CODEGEN_ASSERT(block.finish != ~0u); + CODEGEN_ASSERT(!seenFallback || block.kind == IrBlockKind::Fallback); // If we want to skip fallback code IR/asm, we'll record when those blocks start once we see them if (block.kind == IrBlockKind::Fallback && !seenFallback) diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 8ae40af0..764d33cc 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -12,7 +12,9 @@ #include "lstate.h" #include "lgc.h" +LUAU_FASTFLAG(LuauCodeGenUnassignedBcTargetAbort) LUAU_FASTFLAG(LuauCodeGenDirectBtest) +LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) namespace Luau { @@ -255,7 +257,7 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, , helpers(helpers) , function(function) , stats(stats) - , regs(function, stats, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}}) + , regs(build, function, stats, {{x0, x15}, {x16, x17}, {q0, q7}, {q16, q31}}) , valueTracker(function) , exitHandlerMap(~0u) { @@ -264,13 +266,16 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers, [](void* context, IrInst& inst) { IrLoweringA64* self = static_cast(context); - self->regs.restoreReg(self->build, inst); + self->regs.restoreReg(inst); } ); } void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { + if (FFlag::LuauCodeGenRegAutoSpillA64) + regs.currInstIdx = index; + valueTracker.beforeInstLowering(inst); switch (inst.cmd) @@ -848,7 +853,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { IrCondition cond = conditionOp(inst.c); - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); @@ -1025,7 +1030,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::TABLE_LEN: { RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads - regs.spill(build, index, {reg}); + regs.spill(index, {reg}); build.mov(x0, reg); build.ldr(x1, mem(rNativeContext, offsetof(NativeContext, luaH_getn))); build.blr(x1); @@ -1047,7 +1052,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 key = regOp(inst.b); RegisterA64 temp = regs.allocTemp(KindA64::w); - regs.spill(build, index, {table, key}); + regs.spill(index, {table, key}); if (w1 != key) { @@ -1069,7 +1074,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::NEW_TABLE: { - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.mov(x1, uintOp(inst.a)); build.mov(x2, uintOp(inst.b)); @@ -1081,7 +1086,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::DUP_TABLE: { RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads - regs.spill(build, index, {reg}); + regs.spill(index, {reg}); build.mov(x1, reg); build.mov(x0, rState); build.ldr(x2, mem(rNativeContext, offsetof(NativeContext, luaH_clone))); @@ -1122,7 +1127,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.tst(temp2, 1 << intOp(inst.b)); // can't use tbz/tbnz because their jump offsets are too short build.b(ConditionA64::NotEqual, labelOp(inst.c)); // Equal = Zero after tst; tmcache caches *absence* of metamethods - regs.spill(build, index, {temp1}); + regs.spill(index, {temp1}); build.mov(x0, temp1); build.mov(w1, intOp(inst.b)); build.ldr(x2, mem(rGlobalState, offsetof(global_State, tmname) + intOp(inst.b) * sizeof(TString*))); @@ -1136,7 +1141,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::NEW_USERDATA: { - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.mov(x1, intOp(inst.a)); build.mov(x2, intOp(inst.b)); @@ -1250,7 +1255,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) break; } case IrCmd::FASTCALL: - regs.spill(build, index); + regs.spill(index); error |= !emitBuiltin(build, function, regs, uintOp(inst.a), vmRegOp(inst.b), vmRegOp(inst.c), intOp(inst.d)); break; @@ -1258,7 +1263,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { // We might need a temporary and we have to preserve it over the spill RegisterA64 temp = regs.allocTemp(KindA64::q); - regs.spill(build, index, {temp}); + regs.spill(index, {temp}); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); @@ -1311,7 +1316,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.b(ConditionA64::Less, labelOp(inst.b)); break; case IrCmd::DO_ARITH: - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); @@ -1361,7 +1366,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) emitUpdateBase(build); break; case IrCmd::DO_LEN: - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); build.add(x2, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); @@ -1371,7 +1376,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) emitUpdateBase(build); break; case IrCmd::GET_TABLE: - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); @@ -1393,7 +1398,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) emitUpdateBase(build); break; case IrCmd::SET_TABLE: - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); @@ -1415,7 +1420,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) emitUpdateBase(build); break; case IrCmd::GET_IMPORT: - regs.spill(build, index); + regs.spill(index); // luaV_getimport(L, cl->env, k, ra, aux, /* propagatenil= */ false) build.mov(x0, rState); build.ldr(x1, mem(rClosure, offsetof(Closure, env))); @@ -1430,7 +1435,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) break; case IrCmd::GET_CACHED_IMPORT: { - regs.spill(build, index); + regs.spill(index); Label skip, exit; @@ -1469,7 +1474,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) break; } case IrCmd::CONCAT: - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.mov(w1, uintOp(inst.b)); build.mov(w2, vmRegOp(inst.a) + uintOp(inst.b) - 1); @@ -1520,7 +1525,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) Label skip; checkObjectBarrierConditions(build, temp1, temp2, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); - size_t spills = regs.spill(build, index, {temp1}); + size_t spills = regs.spill(index, {temp1}); build.mov(x1, temp1); build.mov(x0, rState); @@ -1528,7 +1533,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierf))); build.blr(x3); - regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state + regs.restore(spills); // need to restore before skip so that registers are in a consistent state // note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack build.setLabel(skip); @@ -1782,7 +1787,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::INTERRUPT: { - regs.spill(build, index); + regs.spill(index); Label self; @@ -1805,7 +1810,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.cmp(temp1, temp2); build.b(ConditionA64::UnsignedGreater, skip); - size_t spills = regs.spill(build, index); + size_t spills = regs.spill(index); build.mov(x0, rState); build.mov(w1, 1); @@ -1814,7 +1819,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) emitUpdateBase(build); - regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state + regs.restore(spills); // need to restore before skip so that registers are in a consistent state build.setLabel(skip); break; @@ -1827,14 +1832,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) checkObjectBarrierConditions(build, regOp(inst.a), temp, inst.b, inst.c.kind == IrOpKind::Undef ? -1 : tagOp(inst.c), skip); RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads - size_t spills = regs.spill(build, index, {reg}); + size_t spills = regs.spill(index, {reg}); build.mov(x1, reg); build.mov(x0, rState); build.ldr(x2, mem(rBase, vmRegOp(inst.b) * sizeof(TValue) + offsetof(TValue, value))); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierf))); build.blr(x3); - regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state + regs.restore(spills); // need to restore before skip so that registers are in a consistent state // note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack build.setLabel(skip); @@ -1850,14 +1855,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.tbz(temp, BLACKBIT, skip); RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads - size_t spills = regs.spill(build, index, {reg}); + size_t spills = regs.spill(index, {reg}); build.mov(x1, reg); build.mov(x0, rState); build.add(x2, x1, uint16_t(offsetof(LuaTable, gclist))); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barrierback))); build.blr(x3); - regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state + regs.restore(spills); // need to restore before skip so that registers are in a consistent state // note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack build.setLabel(skip); @@ -1872,14 +1877,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 reg = regOp(inst.a); // note: we need to call regOp before spill so that we don't do redundant reloads AddressA64 addr = tempAddr(inst.b, offsetof(TValue, value)); - size_t spills = regs.spill(build, index, {reg}); + size_t spills = regs.spill(index, {reg}); build.mov(x1, reg); build.mov(x0, rState); build.ldr(x2, addr); build.ldr(x3, mem(rNativeContext, offsetof(NativeContext, luaC_barriertable))); build.blr(x3); - regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state + regs.restore(spills); // need to restore before skip so that registers are in a consistent state // note: no emitUpdateBase necessary because luaC_ barriers do not reallocate stack build.setLabel(skip); @@ -1911,13 +1916,13 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.cmp(temp2, temp1); build.b(ConditionA64::UnsignedGreater, skip); - size_t spills = regs.spill(build, index, {temp2}); + size_t spills = regs.spill(index, {temp2}); build.mov(x1, temp2); build.mov(x0, rState); build.ldr(x2, mem(rNativeContext, offsetof(NativeContext, luaF_close))); build.blr(x2); - regs.restore(build, spills); // need to restore before skip so that registers are in a consistent state + regs.restore(spills); // need to restore before skip so that registers are in a consistent state build.setLabel(skip); break; @@ -1926,11 +1931,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) // no-op break; case IrCmd::SETLIST: - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executeSETLIST), uintOp(inst.a)); break; case IrCmd::CALL: - regs.spill(build, index); + regs.spill(index); // argtop = (nparams == LUA_MULTRET) ? L->top : ra + 1 + nparams; if (intOp(inst.b) == LUA_MULTRET) build.ldr(x2, mem(rState, offsetof(lua_State, top))); @@ -1950,7 +1955,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.cbnz(x0, helpers.continueCall); break; case IrCmd::RETURN: - regs.spill(build, index); + regs.spill(index); if (function.variadic) { @@ -2019,7 +2024,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) break; case IrCmd::FORGLOOP: // register layout: ra + 1 = table, ra + 2 = internal index, ra + 3 .. ra + aux = iteration variables - regs.spill(build, index); + regs.spill(index); // clear extra variables since we might have more than two if (intOp(inst.b) > 2) { @@ -2039,7 +2044,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) jumpOrFallthrough(blockOp(inst.d), next); break; case IrCmd::FORGLOOP_FALLBACK: - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.mov(w1, vmRegOp(inst.a)); build.mov(w2, intOp(inst.b)); @@ -2050,7 +2055,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) jumpOrFallthrough(blockOp(inst.d), next); break; case IrCmd::FORGPREP_XNEXT_FALLBACK: - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.b) * sizeof(TValue))); build.mov(w2, uintOp(inst.a) + 1); @@ -2083,14 +2088,14 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) CODEGEN_ASSERT(inst.b.kind == IrOpKind::VmReg); CODEGEN_ASSERT(inst.c.kind == IrOpKind::VmConst); - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executeGETGLOBAL), uintOp(inst.a)); break; case IrCmd::FALLBACK_SETGLOBAL: CODEGEN_ASSERT(inst.b.kind == IrOpKind::VmReg); CODEGEN_ASSERT(inst.c.kind == IrOpKind::VmConst); - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executeSETGLOBAL), uintOp(inst.a)); break; case IrCmd::FALLBACK_GETTABLEKS: @@ -2098,7 +2103,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) CODEGEN_ASSERT(inst.c.kind == IrOpKind::VmReg); CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmConst); - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executeGETTABLEKS), uintOp(inst.a)); break; case IrCmd::FALLBACK_SETTABLEKS: @@ -2106,7 +2111,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) CODEGEN_ASSERT(inst.c.kind == IrOpKind::VmReg); CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmConst); - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executeSETTABLEKS), uintOp(inst.a)); break; case IrCmd::FALLBACK_NAMECALL: @@ -2114,20 +2119,20 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) CODEGEN_ASSERT(inst.c.kind == IrOpKind::VmReg); CODEGEN_ASSERT(inst.d.kind == IrOpKind::VmConst); - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executeNAMECALL), uintOp(inst.a)); break; case IrCmd::FALLBACK_PREPVARARGS: CODEGEN_ASSERT(inst.b.kind == IrOpKind::Constant); - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executePREPVARARGS), uintOp(inst.a)); break; case IrCmd::FALLBACK_GETVARARGS: CODEGEN_ASSERT(inst.b.kind == IrOpKind::VmReg); CODEGEN_ASSERT(inst.c.kind == IrOpKind::Constant); - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); if (intOp(inst.c) == LUA_MULTRET) @@ -2155,7 +2160,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) { RegisterA64 reg = regOp(inst.b); // note: we need to call regOp before spill so that we don't do redundant reloads - regs.spill(build, index, {reg}); + regs.spill(index, {reg}); build.mov(x2, reg); build.mov(x0, rState); @@ -2175,11 +2180,11 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) CODEGEN_ASSERT(inst.b.kind == IrOpKind::VmReg); CODEGEN_ASSERT(inst.c.kind == IrOpKind::VmConst); - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executeDUPCLOSURE), uintOp(inst.a)); break; case IrCmd::FALLBACK_FORGPREP: - regs.spill(build, index); + regs.spill(index); emitFallback(build, offsetof(NativeContext, executeFORGPREP), uintOp(inst.a)); jumpOrFallthrough(blockOp(inst.c), next); break; @@ -2337,7 +2342,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) RegisterA64 temp1 = tempDouble(inst.b); RegisterA64 temp2 = isInt ? tempInt(inst.c) : tempDouble(inst.c); RegisterA64 temp3 = isInt ? noreg : regs.allocTemp(KindA64::d); // note: spill() frees all registers so we need to avoid alloc after spill - regs.spill(build, index, {temp1, temp2}); + regs.spill(index, {temp1, temp2}); if (isInt) { @@ -2359,7 +2364,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) else { RegisterA64 temp1 = tempDouble(inst.b); - regs.spill(build, index, {temp1}); + regs.spill(index, {temp1}); build.fmov(d0, temp1); } @@ -2386,7 +2391,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } case IrCmd::GET_TYPEOF: { - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); build.ldr(x2, mem(rNativeContext, offsetof(NativeContext, luaT_objtypenamestr))); @@ -2398,7 +2403,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) case IrCmd::FINDUPVAL: { - regs.spill(build, index); + regs.spill(index); build.mov(x0, rState); build.add(x1, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); build.ldr(x2, mem(rNativeContext, offsetof(NativeContext, luaF_findupval))); @@ -2525,6 +2530,9 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) valueTracker.afterInstLowering(inst, index); + if (FFlag::LuauCodeGenRegAutoSpillA64) + regs.currInstIdx = kInvalidInstIdx; + regs.freeLastUseRegs(inst, index); regs.freeTempRegs(); } @@ -2568,6 +2576,13 @@ 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(); + } + if (stats) { if (error) @@ -2780,7 +2795,7 @@ RegisterA64 IrLoweringA64::regOp(IrOp op) IrInst& inst = function.instOp(op); if (inst.spilled || inst.needsReload) - regs.restoreReg(build, inst); + regs.restoreReg(inst); CODEGEN_ASSERT(inst.regA64 != noreg); return inst.regA64; diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 934aa481..30257517 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -16,8 +16,10 @@ #include "lstate.h" #include "lgc.h" +LUAU_FASTFLAG(LuauCodeGenUnassignedBcTargetAbort) LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTFLAGVARIABLE(LuauCodeGenVBlendpdReorder) +LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) namespace Luau { @@ -2186,6 +2188,9 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) valueTracker.afterInstLowering(inst, index); + if (FFlag::LuauCodeGenRegAutoSpillA64) + regs.currInstIdx = kInvalidInstIdx; + regs.freeLastUseRegs(inst, index); } @@ -2228,6 +2233,13 @@ 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(); + } + if (stats) { if (regs.maxUsedSlot > kSpillSlots) diff --git a/CodeGen/src/IrRegAllocA64.cpp b/CodeGen/src/IrRegAllocA64.cpp index 15a306c9..ec02fdfb 100644 --- a/CodeGen/src/IrRegAllocA64.cpp +++ b/CodeGen/src/IrRegAllocA64.cpp @@ -11,6 +11,7 @@ #include LUAU_FASTFLAGVARIABLE(DebugCodegenChaosA64) +LUAU_FASTFLAGVARIABLE(LuauCodeGenRegAutoSpillA64) namespace Luau { @@ -110,8 +111,14 @@ static void restoreInst(AssemblyBuilderA64& build, uint32_t& freeSpillSlots, IrF inst.regA64 = reg; } -IrRegAllocA64::IrRegAllocA64(IrFunction& function, LoweringStats* stats, std::initializer_list> regs) - : function(function) +IrRegAllocA64::IrRegAllocA64( + AssemblyBuilderA64& build, + IrFunction& function, + LoweringStats* stats, + std::initializer_list> regs +) + : build(build) + , function(function) , stats(stats) { for (auto& p : regs) @@ -140,8 +147,26 @@ RegisterA64 IrRegAllocA64::allocReg(KindA64 kind, uint32_t index) if (set.free == 0) { - error = true; - return RegisterA64{kind, 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) + { + CODEGEN_ASSERT(currInstIdx == index); + spill(set, index, furthestUseTarget); + CODEGEN_ASSERT(set.free != 0); + } + else + { + error = true; + return RegisterA64{kind, 0}; + } + } + else + { + error = true; + return RegisterA64{kind, 0}; + } } int reg = 31 - countlz(set.free); @@ -161,8 +186,25 @@ RegisterA64 IrRegAllocA64::allocTemp(KindA64 kind) if (set.free == 0) { - error = true; - return RegisterA64{kind, 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) + { + spill(set, currInstIdx, furthestUseTarget); + CODEGEN_ASSERT(set.free != 0); + } + else + { + error = true; + return RegisterA64{kind, 0}; + } + } + else + { + error = true; + return RegisterA64{kind, 0}; + } } int reg = 31 - countlz(set.free); @@ -271,7 +313,7 @@ void IrRegAllocA64::freeTempRegs() simd.temp = 0; } -size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::initializer_list live) +size_t IrRegAllocA64::spill(uint32_t index, std::initializer_list live) { static const KindA64 sets[] = {KindA64::x, KindA64::q}; @@ -310,63 +352,79 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init while (regs) { - int reg = 31 - countlz(regs); - - uint32_t inst = set.defs[reg]; - CODEGEN_ASSERT(inst != kInvalidInstIdx); + if (FFlag::LuauCodeGenRegAutoSpillA64) + { + int reg = 31 - countlz(regs); - IrInst& def = function.instructions[inst]; - CODEGEN_ASSERT(def.regA64.index == reg); - CODEGEN_ASSERT(!def.reusedReg); - CODEGEN_ASSERT(!def.spilled); - CODEGEN_ASSERT(!def.needsReload); + uint32_t targetInstIdx = set.defs[reg]; - 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); + CODEGEN_ASSERT(targetInstIdx != kInvalidInstIdx); + CODEGEN_ASSERT(function.instructions[targetInstIdx].regA64.index == reg); - def.needsReload = true; + spill(set, index, targetInstIdx); - if (stats) - stats->spillsToRestore++; + regs &= ~(1u << reg); } else { - int slot = allocSpill(freeSpillSlots, def.regA64.kind); - if (slot < 0) + 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) { - slot = kInvalidSpill; - error = true; + // 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); - build.str(def.regA64, mem(sp, sSpillArea.data + slot * 8)); + def.needsReload = true; - Spill s = {inst, def.regA64, int8_t(slot)}; - spills.push_back(s); + if (stats) + stats->spillsToRestore++; + } + else + { + int slot = allocSpill(freeSpillSlots, def.regA64.kind); + if (slot < 0) + { + slot = kInvalidSpill; + error = true; + } - def.spilled = true; + build.str(def.regA64, mem(sp, sSpillArea.data + slot * 8)); - if (stats) - { - stats->spillsToSlot++; + 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; + if (slot != kInvalidSpill && unsigned(slot + 1) > stats->maxSpillSlotsUsed) + stats->maxSpillSlotsUsed = slot + 1; + } } - } - def.regA64 = noreg; + def.regA64 = noreg; - regs &= ~(1u << reg); - set.free |= 1u << reg; - set.defs[reg] = kInvalidInstIdx; + regs &= ~(1u << reg); + set.free |= 1u << reg; + set.defs[reg] = kInvalidInstIdx; + } } CODEGEN_ASSERT(set.free == set.base); @@ -386,7 +444,7 @@ size_t IrRegAllocA64::spill(AssemblyBuilderA64& build, uint32_t index, std::init return start; } -void IrRegAllocA64::restore(AssemblyBuilderA64& build, size_t start) +void IrRegAllocA64::restore(size_t start) { CODEGEN_ASSERT(start <= spills.size()); @@ -404,7 +462,7 @@ void IrRegAllocA64::restore(AssemblyBuilderA64& build, size_t start) } } -void IrRegAllocA64::restoreReg(AssemblyBuilderA64& build, IrInst& inst) +void IrRegAllocA64::restoreReg(IrInst& inst) { uint32_t index = function.getInstIndex(inst); @@ -426,6 +484,96 @@ void IrRegAllocA64::restoreReg(AssemblyBuilderA64& build, IrInst& inst) CODEGEN_ASSERT(!"Expected to find a spill record"); } +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; + + 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 = {targetInstIdx, 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 = {targetInstIdx, 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; + + set.free |= 1u << reg; + set.defs[reg] = kInvalidInstIdx; +} + +uint32_t IrRegAllocA64::findInstructionWithFurthestNextUse(Set& set) const +{ + CODEGEN_ASSERT(FFlag::LuauCodeGenRegAutoSpillA64); + + if (currInstIdx == kInvalidInstIdx) + return kInvalidInstIdx; + + uint32_t furthestUseTarget = kInvalidInstIdx; + uint32_t furthestUseLocation = 0; + + for (uint32_t regInstUser : set.defs) + { + // Cannot spill temporary registers or the register of the value that's defined in the current instruction + if (regInstUser == kInvalidInstIdx || regInstUser == currInstIdx) + continue; + + uint32_t nextUse = getNextInstUse(function, regInstUser, currInstIdx); + + // Cannot spill value that is about to be used in the current instruction + if (nextUse == currInstIdx) + continue; + + if (furthestUseTarget == kInvalidInstIdx || nextUse > furthestUseLocation) + { + furthestUseLocation = nextUse; + furthestUseTarget = regInstUser; + } + } + + return furthestUseTarget; +} + IrRegAllocA64::Set& IrRegAllocA64::getSet(KindA64 kind) { switch (kind) diff --git a/CodeGen/src/IrRegAllocA64.h b/CodeGen/src/IrRegAllocA64.h index d16fa19f..2c0c33d1 100644 --- a/CodeGen/src/IrRegAllocA64.h +++ b/CodeGen/src/IrRegAllocA64.h @@ -22,7 +22,12 @@ class AssemblyBuilderA64; struct IrRegAllocA64 { - IrRegAllocA64(IrFunction& function, LoweringStats* stats, std::initializer_list> regs); + IrRegAllocA64( + AssemblyBuilderA64& build, + IrFunction& function, + LoweringStats* stats, + std::initializer_list> regs + ); RegisterA64 allocReg(KindA64 kind, uint32_t index); RegisterA64 allocTemp(KindA64 kind); @@ -38,13 +43,13 @@ struct IrRegAllocA64 void freeTempRegs(); // Spills all live registers that outlive current instruction; all allocated registers are assumed to be undefined - size_t spill(AssemblyBuilderA64& build, uint32_t index, std::initializer_list live = {}); + size_t spill(uint32_t index, std::initializer_list live = {}); // Restores registers starting from the offset returned by spill(); all spills will be restored to the original registers - void restore(AssemblyBuilderA64& build, size_t start); + void restore(size_t start); // Restores register for a single instruction; may not assign the previously used register! - void restoreReg(AssemblyBuilderA64& build, IrInst& inst); + void restoreReg(IrInst& inst); struct Set { @@ -69,10 +74,19 @@ struct IrRegAllocA64 int8_t slot; }; + // Spills the selected register + void spill(Set& set, uint32_t index, uint32_t targetInstIdx); + + uint32_t findInstructionWithFurthestNextUse(Set& set) const; + Set& getSet(KindA64 kind); + AssemblyBuilderA64& build; IrFunction& function; LoweringStats* stats = nullptr; + + uint32_t currInstIdx = kInvalidInstIdx; + Set gpr, simd; std::vector spills; diff --git a/CodeGen/src/IrRegAllocX64.cpp b/CodeGen/src/IrRegAllocX64.cpp index 64625868..ce9bf19d 100644 --- a/CodeGen/src/IrRegAllocX64.cpp +++ b/CodeGen/src/IrRegAllocX64.cpp @@ -6,6 +6,8 @@ #include "EmitCommonX64.h" +LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) + namespace Luau { namespace CodeGen @@ -393,6 +395,12 @@ 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; + } + uint32_t furthestUseTarget = kInvalidInstIdx; uint32_t furthestUseLocation = 0; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index 5e438b68..ded09808 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -504,7 +504,7 @@ struct ConstPropState // Heap changes might affect table state std::vector getSlotNodeCache; // Additionally, pcpos argument might be different - std::vector checkSlotMatchCache; // Additionally, fallback block argument might be different + std::vector checkSlotMatchCache; // Additionally, fallback block argument might be different std::vector getArrAddrCache; std::vector checkArraySizeCache; // Additionally, fallback block argument might be different diff --git a/Common/include/Luau/DenseHash.h b/Common/include/Luau/DenseHash.h index 39e50f92..1548152a 100644 --- a/Common/include/Luau/DenseHash.h +++ b/Common/include/Luau/DenseHash.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/HashUtil.h" #include "Luau/Common.h" #include @@ -12,21 +13,10 @@ namespace Luau { -struct DenseHashPointer -{ - size_t operator()(const void* key) const - { - return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9); - } -}; - // Internal implementation of DenseHashSet and DenseHashMap namespace detail { -template -using DenseHashDefault = std::conditional_t, DenseHashPointer, std::hash>; - template class DenseHashTable { diff --git a/Common/include/Luau/HashUtil.h b/Common/include/Luau/HashUtil.h new file mode 100644 index 00000000..6184fa72 --- /dev/null +++ b/Common/include/Luau/HashUtil.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Luau/Common.h" + +#include +#include + +#include +#include +#include + +namespace Luau +{ + +struct DenseHashPointer +{ + size_t operator()(const void* key) const + { + return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9); + } +}; + +namespace detail +{ + +template +using DenseHashDefault = std::conditional_t, DenseHashPointer, std::hash>; + +} // namespace detail + +inline void hashCombine(size_t& seed, size_t hash) +{ + // Golden Ratio constant used for better hash scattering + // See https://softwareengineering.stackexchange.com/a/402543 + seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} + +template, typename H2 = detail::DenseHashDefault> +struct PairHash +{ + std::size_t operator()(const std::pair& p) const noexcept + { + std::size_t seed = 0; + hashCombine(seed, h1(p.first)); + hashCombine(seed, h2(p.second)); + return seed; + } + +private: + H1 h1; + H2 h2; +}; + +} // namespace Luau diff --git a/EqSat/include/Luau/LanguageHash.h b/EqSat/include/Luau/LanguageHash.h index cfc33b83..e85e98e3 100644 --- a/EqSat/include/Luau/LanguageHash.h +++ b/EqSat/include/Luau/LanguageHash.h @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once +#include "Luau/HashUtil.h" + #include #include #include @@ -24,13 +26,6 @@ size_t languageHash(const T& lang) return LanguageHash{}(lang); } -inline void hashCombine(size_t& seed, size_t hash) -{ - // Golden Ratio constant used for better hash scattering - // See https://softwareengineering.stackexchange.com/a/402543 - seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2); -} - template struct LanguageHash> { diff --git a/Sources.cmake b/Sources.cmake index 0c5a956e..d6a29aa0 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -7,6 +7,7 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.19") Common/include/Luau/BytecodeUtils.h Common/include/Luau/DenseHash.h Common/include/Luau/ExperimentalFlags.h + Common/include/Luau/HashUtil.h Common/include/Luau/Variant.h Common/include/Luau/VecDeque.h ) @@ -172,6 +173,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/ApplyTypeFunction.h Analysis/include/Luau/AstJsonEncoder.h Analysis/include/Luau/AstQuery.h + Analysis/include/Luau/AstUtils.h Analysis/include/Luau/Autocomplete.h Analysis/include/Luau/AutocompleteTypes.h Analysis/include/Luau/BuiltinDefinitions.h @@ -257,6 +259,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/ApplyTypeFunction.cpp Analysis/src/AstJsonEncoder.cpp Analysis/src/AstQuery.cpp + Analysis/src/AstUtils.cpp Analysis/src/Autocomplete.cpp Analysis/src/AutocompleteCore.cpp Analysis/src/BuiltinDefinitions.cpp diff --git a/VM/src/lstrlib.cpp b/VM/src/lstrlib.cpp index 62d71f6a..f72c018e 100644 --- a/VM/src/lstrlib.cpp +++ b/VM/src/lstrlib.cpp @@ -477,7 +477,7 @@ static const char* match(MatchState* ms, const char* s, const char* p) { p += 4; goto init; // return match(ms, s, p + 4); - } // else fail (s == NULL) + } // else fail (s == NULL) break; } case 'f': @@ -555,7 +555,7 @@ static const char* match(MatchState* ms, const char* s, const char* p) case '+': // 1 or more repetitions s++; // 1 match already done LUAU_FALLTHROUGH; // go through - case '*': // 0 or more repetitions + case '*': // 0 or more repetitions s = max_expand(ms, s, p, ep); break; case '-': // 0 or more repetitions (minimum) diff --git a/VM/src/lutf8lib.cpp b/VM/src/lutf8lib.cpp index cbb0b5b0..a45db259 100644 --- a/VM/src/lutf8lib.cpp +++ b/VM/src/lutf8lib.cpp @@ -134,9 +134,9 @@ static int luaO_utf8esc(char* buff, unsigned long x) do { // add continuation bytes buff[UTF8BUFFSZ - (n++)] = cast_to(char, 0x80 | (x & 0x3f)); - x >>= 6; // remove added bits - mfb >>= 1; // now there is one less bit available in first byte - } while (x > mfb); // still needs continuation byte? + x >>= 6; // remove added bits + mfb >>= 1; // now there is one less bit available in first byte + } while (x > mfb); // still needs continuation byte? buff[UTF8BUFFSZ - n] = cast_to(char, (~mfb << 1) | x); // add first byte } return n; diff --git a/extern/doctest.h b/extern/doctest.h index 42d93c7f..80a2a0f9 100644 --- a/extern/doctest.h +++ b/extern/doctest.h @@ -3465,7 +3465,7 @@ using ticks_t = timer_large_integer::type; MultiLaneAtomic numAssertsCurrentTest_atomic; MultiLaneAtomic numAssertsFailedCurrentTest_atomic; - std::vector> filters = decltype(filters)(9); // 9 different filters + std::vector> filters = decltype(filters)(10); // 10 different filters std::vector reporters_currently_used; @@ -6562,6 +6562,8 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "sce=", p->filters[7]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "reporters=", p->filters[8]); parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "r=", p->filters[8]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "test-path=", p->filters[9]); + parseCommaSepArgs(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX "tp=", p->filters[9]); // clang-format on int intRes = 0; @@ -6860,6 +6862,8 @@ int Context::run() { skip_me = true; if(matchesAny(tc.m_name, p->filters[5], false, p->case_sensitive)) skip_me = true; + if(!matchesAny((String(tc.m_test_suite) + "/" + String(tc.m_name)).c_str(), p->filters[9], true, p->case_sensitive)) + skip_me = true; if(!skip_me) p->numTestCasesPassingFilters++; diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index ac0ef4b7..23804709 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -105,8 +105,7 @@ TEST_CASE("encode_AstStatBlock") CHECK( toJson(&block) == - (R"({"type":"AstStatBlock","location":"0,0 - 0,0","hasEnd":true,"body":[{"type":"AstStatLocal","location":"0,0 - 0,0","vars":[{"luauType":null,"name":"a_local","type":"AstLocal","location":"0,0 - 0,0"}],"values":[]}]})" - ) + (R"({"type":"AstStatBlock","location":"0,0 - 0,0","hasEnd":true,"body":[{"type":"AstStatLocal","location":"0,0 - 0,0","vars":[{"luauType":null,"name":"a_local","type":"AstLocal","location":"0,0 - 0,0"}],"values":[]}]})") ); } diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index f5a2f0e2..9ced1c16 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -3992,8 +3992,8 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes") local x = require(@1"@2"@3) )"); - StringCompletionCallback callback = [](std::string, std::optional, std::optional contents - ) -> std::optional + StringCompletionCallback callback = + [](std::string, std::optional, std::optional contents) -> std::optional { Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}}; return results; diff --git a/tests/ClassFixture.cpp b/tests/ClassFixture.cpp index 5f84b737..69e3fa86 100644 --- a/tests/ClassFixture.cpp +++ b/tests/ClassFixture.cpp @@ -111,10 +111,12 @@ Frontend& ExternTypeFixture::getFrontend() getMutable(vector2MetaType)->props = { {"__add", {makeFunction(arena, nullopt, {vector2InstanceType, vector2InstanceType}, {vector2InstanceType})}}, {"__mul", - {arena.addType(IntersectionType{{ - makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}), - makeFunction(arena, vector2InstanceType, {getBuiltins()->numberType}, {vector2InstanceType}), - }})}} + {arena.addType( + IntersectionType{{ + makeFunction(arena, vector2InstanceType, {vector2InstanceType}, {vector2InstanceType}), + makeFunction(arena, vector2InstanceType, {getBuiltins()->numberType}, {vector2InstanceType}), + }} + )}} }; globals.globalScope->exportedTypeBindings["Vector2"] = TypeFun{{}, vector2InstanceType}; addGlobalBinding(globals, "Vector2", vector2Type, "@test"); diff --git a/tests/CodeAllocator.test.cpp b/tests/CodeAllocator.test.cpp index 953f7110..16de48e9 100644 --- a/tests/CodeAllocator.test.cpp +++ b/tests/CodeAllocator.test.cpp @@ -736,7 +736,8 @@ TEST_CASE("GeneratedCodeExecutionWithThrowOutsideTheGateX64") uint8_t* nativeData1; size_t sizeNativeData1; uint8_t* nativeEntry1; - REQUIRE(allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size(), nativeData1, sizeNativeData1, nativeEntry1) + REQUIRE( + allocator.allocate(build.data.data(), build.data.size(), build.code.data(), build.code.size(), nativeData1, sizeNativeData1, nativeEntry1) ); REQUIRE(nativeEntry1); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index f0b46e60..1f1217bd 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -2087,8 +2087,10 @@ until (function() return rr end)() < 0.5 // unless that upvalue is from an outer scope CHECK_EQ( - "\n" + compileFunction0("local stop = false stop = true function test() repeat local r = math.random() if r > 0.5 then " - "continue end r = r + 0.3 until stop or r < 0.5 end"), + "\n" + compileFunction0( + "local stop = false stop = true function test() repeat local r = math.random() if r > 0.5 then " + "continue end r = r + 0.3 until stop or r < 0.5 end" + ), R"( L0: GETIMPORT R0 2 [math.random] CALL R0 0 1 diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 58bf4b5f..f4537a75 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -44,6 +44,7 @@ LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) LUAU_FASTFLAG(LuauCodeGenVectorLerp) LUAU_DYNAMIC_FASTFLAG(LuauXpcallContNoYield) LUAU_FASTFLAG(LuauCodeGenBetterBytecodeAnalysis) +LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) static lua_CompileOptions defaultOptions() { @@ -3149,6 +3150,7 @@ TEST_CASE("SafeEnv") TEST_CASE("Native") { ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, true}; + ScopedFastFlag luauCodeGenRegAutoSpillA64{FFlag::LuauCodeGenRegAutoSpillA64, true}; // This tests requires code to run natively, otherwise all 'is_native' checks will fail if (!codegen || !luau_codegen_supported()) diff --git a/tests/ConstraintGeneratorFixture.cpp b/tests/ConstraintGeneratorFixture.cpp index 20d1ffe8..7e317129 100644 --- a/tests/ConstraintGeneratorFixture.cpp +++ b/tests/ConstraintGeneratorFixture.cpp @@ -56,7 +56,7 @@ void ConstraintGeneratorFixture::solve(const std::string& code) NotNull{rootScope}, constraints, NotNull{&cg->scopeToFunction}, - "MainModule", + mainModule, NotNull(&moduleResolver), {}, &logger, diff --git a/tests/EqSatSimplification.test.cpp b/tests/EqSatSimplification.test.cpp index 24358e7b..3ac1dd60 100644 --- a/tests/EqSatSimplification.test.cpp +++ b/tests/EqSatSimplification.test.cpp @@ -163,9 +163,10 @@ TEST_CASE_FIXTURE(ESFixture, "never & string") TEST_CASE_FIXTURE(ESFixture, "string & (unknown | never)") { CHECK( - "string" == simplifyStr(arena->addType(IntersectionType{ - {getBuiltins()->stringType, arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->neverType}})} - })) + "string" == + simplifyStr(arena->addType( + IntersectionType{{getBuiltins()->stringType, arena->addType(UnionType{{getBuiltins()->unknownType, getBuiltins()->neverType}})}} + )) ); } @@ -217,28 +218,32 @@ TEST_CASE_FIXTURE(ESFixture, "\"hello\" | string") TEST_CASE_FIXTURE(ESFixture, "\"hello\" | \"world\" | \"hello\"") { CHECK( - "\"hello\" | \"world\"" == simplifyStr(arena->addType(UnionType{{ - arena->addType(SingletonType{StringSingleton{"hello"}}), - arena->addType(SingletonType{StringSingleton{"world"}}), - arena->addType(SingletonType{StringSingleton{"hello"}}), - }})) + "\"hello\" | \"world\"" == simplifyStr(arena->addType( + UnionType{{ + arena->addType(SingletonType{StringSingleton{"hello"}}), + arena->addType(SingletonType{StringSingleton{"world"}}), + arena->addType(SingletonType{StringSingleton{"hello"}}), + }} + )) ); } TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | function | table | class | buffer") { CHECK( - "unknown" == simplifyStr(arena->addType(UnionType{{ - getBuiltins()->nilType, - getBuiltins()->booleanType, - getBuiltins()->numberType, - getBuiltins()->stringType, - getBuiltins()->threadType, - getBuiltins()->functionType, - getBuiltins()->tableType, - getBuiltins()->externType, - getBuiltins()->bufferType, - }})) + "unknown" == simplifyStr(arena->addType( + UnionType{{ + getBuiltins()->nilType, + getBuiltins()->booleanType, + getBuiltins()->numberType, + getBuiltins()->stringType, + getBuiltins()->threadType, + getBuiltins()->functionType, + getBuiltins()->tableType, + getBuiltins()->externType, + getBuiltins()->bufferType, + }} + )) ); } @@ -285,12 +290,14 @@ TEST_CASE_FIXTURE(ESFixture, "never | Parent | Unrelated") TEST_CASE_FIXTURE(ESFixture, "never | Parent | (number & string) | Unrelated") { CHECK( - "Parent | Unrelated" == simplifyStr(arena->addType(UnionType{ - {getBuiltins()->neverType, - parentClass, - arena->addType(IntersectionType{{getBuiltins()->numberType, getBuiltins()->stringType}}), - unrelatedClass} - })) + "Parent | Unrelated" == simplifyStr(arena->addType( + UnionType{ + {getBuiltins()->neverType, + parentClass, + arena->addType(IntersectionType{{getBuiltins()->numberType, getBuiltins()->stringType}}), + unrelatedClass} + } + )) ); } @@ -306,16 +313,18 @@ TEST_CASE_FIXTURE(ESFixture, "boolean & true") TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | function | table | class | buffer)") { - TypeId truthy = arena->addType(UnionType{{ - getBuiltins()->trueType, - getBuiltins()->numberType, - getBuiltins()->stringType, - getBuiltins()->threadType, - getBuiltins()->functionType, - getBuiltins()->tableType, - getBuiltins()->externType, - getBuiltins()->bufferType, - }}); + TypeId truthy = arena->addType( + UnionType{{ + getBuiltins()->trueType, + getBuiltins()->numberType, + getBuiltins()->stringType, + getBuiltins()->threadType, + getBuiltins()->functionType, + getBuiltins()->tableType, + getBuiltins()->externType, + getBuiltins()->bufferType, + }} + ); CHECK("true" == simplifyStr(arena->addType(IntersectionType{{getBuiltins()->booleanType, truthy}}))); } @@ -387,9 +396,9 @@ TEST_CASE_FIXTURE(ESFixture, "add") TEST_CASE_FIXTURE(ESFixture, "union") { CHECK( - "number" == - simplifyStr(arena->addType(TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {getBuiltins()->numberType, getBuiltins()->numberType}}) - ) + "number" == simplifyStr(arena->addType( + TypeFunctionInstanceType{builtinTypeFunctions().unionFunc, {getBuiltins()->numberType, getBuiltins()->numberType}} + )) ); } @@ -421,9 +430,11 @@ TEST_CASE_FIXTURE(ESFixture, "blocked & ~number & function") TEST_CASE_FIXTURE(ESFixture, "(number | boolean | string | nil | table) & (false | nil)") { - const TypeId t1 = arena->addType(UnionType{ - {getBuiltins()->numberType, getBuiltins()->booleanType, getBuiltins()->stringType, getBuiltins()->nilType, getBuiltins()->tableType} - }); + const TypeId t1 = arena->addType( + UnionType{ + {getBuiltins()->numberType, getBuiltins()->booleanType, getBuiltins()->stringType, getBuiltins()->nilType, getBuiltins()->tableType} + } + ); CHECK("false?" == simplifyStr(arena->addType(IntersectionType{{t1, getBuiltins()->falsyType}}))); } diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 045539f1..52b10a2f 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -166,10 +166,12 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "functions_containing_cyclic_tables_can { TypeId selfTy = arena.addType(BlockedType{}); - TypeId methodTy = arena.addType(FunctionType{ - arena.addTypePack({selfTy}), - arena.addTypePack({builtinTypes.numberType}), - }); + TypeId methodTy = arena.addType( + FunctionType{ + arena.addTypePack({selfTy}), + arena.addTypePack({builtinTypes.numberType}), + } + ); asMutable(selfTy)->ty.emplace( TableType::Props{{"count", builtinTypes.numberType}, {"method", methodTy}}, std::nullopt, TypeLevel{}, TableState::Sealed diff --git a/tests/InferPolarity.test.cpp b/tests/InferPolarity.test.cpp index 325a9538..0d70d457 100644 --- a/tests/InferPolarity.test.cpp +++ b/tests/InferPolarity.test.cpp @@ -24,13 +24,15 @@ TEST_CASE_FIXTURE(Fixture, "T where T = { m: (a) -> T }") TypeId tType = arena.addType(BlockedType{}); TypeId aType = arena.addType(GenericType{globalScope.get(), "a"}); - TypeId mType = arena.addType(FunctionType{ - TypeLevel{}, - /* generics */ {aType}, - /* genericPacks */ {}, - /* argPack */ arena.addTypePack({aType}), - /* retPack */ arena.addTypePack({tType}) - }); + TypeId mType = arena.addType( + FunctionType{ + TypeLevel{}, + /* generics */ {aType}, + /* genericPacks */ {}, + /* argPack */ arena.addTypePack({aType}), + /* retPack */ arena.addTypePack({tType}) + } + ); emplaceType( asMutable(tType), @@ -66,13 +68,15 @@ TEST_CASE_FIXTURE(Fixture, "({ read x: a, write x: b }) -> ()") ttv.state = TableState::Sealed; ttv.props["x"] = Property::create({aType}, {bType}); - TypeId mType = arena.addType(FunctionType{ - TypeLevel{}, - /* generics */ {aType, bType}, - /* genericPacks */ {}, - /* argPack */ arena.addTypePack({arena.addType(std::move(ttv))}), - /* retPack */ builtinTypes->emptyTypePack, - }); + TypeId mType = arena.addType( + FunctionType{ + TypeLevel{}, + /* generics */ {aType, bType}, + /* genericPacks */ {}, + /* argPack */ arena.addTypePack({arena.addType(std::move(ttv))}), + /* retPack */ builtinTypes->emptyTypePack, + } + ); inferGenericPolarities(NotNull{&arena}, NotNull{globalScope.get()}, mType); diff --git a/tests/Instantiation2.test.cpp b/tests/Instantiation2.test.cpp index cda002de..57544210 100644 --- a/tests/Instantiation2.test.cpp +++ b/tests/Instantiation2.test.cpp @@ -20,12 +20,12 @@ TEST_CASE_FIXTURE(Fixture, "weird_cyclic_instantiation") TypeId genericT = arena.addType(GenericType{"T"}); - TypeId idTy = arena.addType(FunctionType{ - /* generics */ {genericT}, - /* genericPacks */ {}, - /* argTypes */ arena.addTypePack({genericT}), - /* retTypes */ arena.addTypePack({genericT}) - }); + TypeId idTy = arena.addType( + FunctionType{/* generics */ {genericT}, + /* genericPacks */ {}, + /* argTypes */ arena.addTypePack({genericT}), + /* retTypes */ arena.addTypePack({genericT})} + ); DenseHashMap genericSubstitutions{nullptr}; DenseHashMap genericPackSubstitutions{nullptr}; diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 96896f23..5bf66e63 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1986,7 +1986,9 @@ Account:deposit(200.00) )"); REQUIRE(1 == result.warnings.size()); - checkDeprecatedWarning(result.warnings[0], Position(8, 0), Position(8, 15), "Member 'Account.deposit' is deprecated, use 'credit' instead. It sounds cool"); + checkDeprecatedWarning( + result.warnings[0], Position(8, 0), Position(8, 15), "Member 'Account.deposit' is deprecated, use 'credit' instead. It sounds cool" + ); } // @deprecated works for methods with a compound expression class name @@ -2007,7 +2009,9 @@ end )"); REQUIRE(1 == result.warnings.size()); - checkDeprecatedWarning(result.warnings[0], Position(12, 0), Position(12, 22), "Member 'deposit' is deprecated, use 'credit' instead. It sounds cool"); + checkDeprecatedWarning( + result.warnings[0], Position(12, 0), Position(12, 22), "Member 'deposit' is deprecated, use 'credit' instead. It sounds cool" + ); } { diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 45bfc0e4..9cfea033 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -65,9 +65,7 @@ using namespace Luau; struct NonStrictTypeCheckerFixture : Fixture { - NonStrictTypeCheckerFixture() - { - } + NonStrictTypeCheckerFixture() {} CheckResult checkNonStrict(const std::string& code) { diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index b9c32966..fbae3183 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -458,9 +458,7 @@ struct NormalizeFixture : Fixture InternalErrorReporter iceHandler; UnifierSharedState unifierState{&iceHandler}; - NormalizeFixture() - { - } + NormalizeFixture() {} std::shared_ptr toNormalizedType(const std::string& annotation, int expectedErrors = 0) { @@ -1084,10 +1082,12 @@ TEST_CASE_FIXTURE(NormalizeFixture, "truthy_table_property_and_optional_table_wi TypeId t1 = arena.addType(TableType{TableType::Props{{"x", getBuiltins()->truthyType}}, std::nullopt, TypeLevel{}, TableState::Sealed}); // { x: number? }? - TypeId t2 = arena.addType(UnionType{ - {arena.addType(TableType{TableType::Props{{"x", getBuiltins()->optionalNumberType}}, std::nullopt, TypeLevel{}, TableState::Sealed}), - getBuiltins()->nilType} - }); + TypeId t2 = arena.addType( + UnionType{ + {arena.addType(TableType{TableType::Props{{"x", getBuiltins()->optionalNumberType}}, std::nullopt, TypeLevel{}, TableState::Sealed}), + getBuiltins()->nilType} + } + ); TypeId intersection = arena.addType(IntersectionType{{t2, t1}}); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index a3a271be..2ca71e73 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -3814,9 +3814,7 @@ TEST_CASE_FIXTURE(Fixture, "do_not_hang_on_incomplete_attribute_list") function hello(x, y) return x + y end)"); - checkFirstErrorForAttributes( - result.errors, 1, Location(Position(1, 0), Position(1, 3)), "Attribute list cannot be empty" - ); + checkFirstErrorForAttributes(result.errors, 1, Location(Position(1, 0), Position(1, 3)), "Attribute list cannot be empty"); result = tryParse(R"(@[)"); @@ -3836,9 +3834,7 @@ end)"); local function foo() end )"); - checkFirstErrorForAttributes( - result.errors, 1, Location(Position(1, 8), Position(1, 13)), "Expected ']' (to close '@[' at line 1), got 'local'" - ); + checkFirstErrorForAttributes(result.errors, 1, Location(Position(1, 8), Position(1, 13)), "Expected ']' (to close '@[' at line 1), got 'local'"); } TEST_CASE_FIXTURE(Fixture, "parse_attribute_for_function_expression") diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index c4bfef3e..bd14242e 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) using namespace Luau; @@ -1241,21 +1242,25 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(...unknown) -> () <: (T...) -> ()") TEST_CASE_FIXTURE(SubtypeFixture, "bill") { - TypeId a = arena.addType(TableType{ - {{"a", getBuiltins()->stringType}}, - TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType}, - TypeLevel{}, - nullptr, - TableState::Sealed - }); + TypeId a = arena.addType( + TableType{ + {{"a", getBuiltins()->stringType}}, + TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType}, + TypeLevel{}, + nullptr, + TableState::Sealed + } + ); - TypeId b = arena.addType(TableType{ - {{"a", getBuiltins()->stringType}}, - TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType}, - TypeLevel{}, - nullptr, - TableState::Sealed - }); + TypeId b = arena.addType( + TableType{ + {{"a", getBuiltins()->stringType}}, + TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType}, + TypeLevel{}, + nullptr, + TableState::Sealed + } + ); CHECK(isSubtype(a, b).isSubtype); CHECK(isSubtype(b, a).isSubtype); @@ -1265,13 +1270,15 @@ TEST_CASE_FIXTURE(SubtypeFixture, "({[string]: number, a: string}) -> () <: ({[s { auto makeTheType = [&]() { - TypeId argType = arena.addType(TableType{ - {{"a", getBuiltins()->stringType}}, - TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType}, - TypeLevel{}, - nullptr, - TableState::Sealed - }); + TypeId argType = arena.addType( + TableType{ + {{"a", getBuiltins()->stringType}}, + TableIndexer{getBuiltins()->stringType, getBuiltins()->numberType}, + TypeLevel{}, + nullptr, + TableState::Sealed + } + ); return arena.addType(FunctionType{arena.addTypePack({argType}), getBuiltins()->emptyTypePack}); }; @@ -1398,17 +1405,20 @@ 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}; - - TypeId longTy = arena.addType(UnionType{ - {getBuiltins()->booleanType, - getBuiltins()->bufferType, - getBuiltins()->externType, - getBuiltins()->functionType, - getBuiltins()->numberType, - getBuiltins()->stringType, - getBuiltins()->tableType, - getBuiltins()->threadType} - }); + ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + TypeId longTy = arena.addType( + UnionType{ + {getBuiltins()->booleanType, + getBuiltins()->bufferType, + getBuiltins()->externType, + getBuiltins()->functionType, + getBuiltins()->numberType, + getBuiltins()->stringType, + getBuiltins()->tableType, + getBuiltins()->threadType} + } + ); TypeId tblTy = tbl({{"depth", getBuiltins()->unknownType}}); TypeId combined = meet(longTy, tblTy); TypeId subTy = arena.addType(TypeFunctionInstanceType{NotNull{&builtinTypeFunctions.unionFunc}, {combined, getBuiltins()->neverType}, {}}); @@ -1421,10 +1431,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type if (reasoning.subPath.empty() && reasoning.superPath.empty()) continue; - std::optional optSubLeaf = - traverse(subTy, reasoning.subPath, getBuiltins(), NotNull{&result.mappedGenericPacks}, NotNull{&arena}); - std::optional optSuperLeaf = - traverse(superTy, reasoning.superPath, getBuiltins(), NotNull{&result.mappedGenericPacks}, NotNull{&arena}); + std::optional optSubLeaf = traverse(subTy, reasoning.subPath, getBuiltins(), NotNull{&arena}); + std::optional optSuperLeaf = traverse(superTy, reasoning.superPath, getBuiltins(), NotNull{&arena}); if (!optSubLeaf || !optSuperLeaf) CHECK(false); @@ -1484,6 +1492,18 @@ TEST_CASE_FIXTURE(SubtypeFixture, "(x: T, y: T, f: (T, T) -> T) -> T <: (numb CHECK_IS_SUBTYPE(f1, f2); } +TEST_CASE_FIXTURE(SubtypeFixture, "(A...) -> ((A...) -> ()) <: (string -> ((number) -> ())") +{ + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, 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)); + + TypeId f2 = fn({getBuiltins()->stringType}, {numberToNothingType}); + CHECK_IS_SUBTYPE(f1, f2); +} + TEST_CASE_FIXTURE(SubtypeFixture, "no_caching_type_function_instances_with_mapped_generics") { ScopedFastFlag _{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}; @@ -1532,11 +1552,9 @@ TEST_CASE_FIXTURE(SubtypeFixture, "table_property") CHECK(!result.isSubtype); REQUIRE(result.reasoning.size() == 1); CHECK( - *result.reasoning.begin() == SubtypingReasoning{ - /* subPath */ Path(TypePath::Property::read("X")), - /* superPath */ Path(TypePath::Property::read("X")), - /* variance */ SubtypingVariance::Invariant - } + *result.reasoning.begin() == SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), + /* superPath */ Path(TypePath::Property::read("X")), + /* variance */ SubtypingVariance::Invariant} ); } @@ -1548,19 +1566,18 @@ TEST_CASE_FIXTURE(SubtypeFixture, "table_indexers") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); CHECK( - result.reasoning == - std::vector{ - SubtypingReasoning{ - /* subPath */ Path(TypePath::TypeField::IndexLookup), - /* superPath */ Path(TypePath::TypeField::IndexLookup), - /* variance */ SubtypingVariance::Invariant, - }, - SubtypingReasoning{ - /* subPath */ Path(TypePath::TypeField::IndexResult), - /* superPath */ Path(TypePath::TypeField::IndexResult), - /* variance */ SubtypingVariance::Invariant, - } - } + result.reasoning == std::vector{ + SubtypingReasoning{ + /* subPath */ Path(TypePath::TypeField::IndexLookup), + /* superPath */ Path(TypePath::TypeField::IndexLookup), + /* variance */ SubtypingVariance::Invariant, + }, + SubtypingReasoning{ + /* subPath */ Path(TypePath::TypeField::IndexResult), + /* superPath */ Path(TypePath::TypeField::IndexResult), + /* variance */ SubtypingVariance::Invariant, + } + } ); } @@ -1703,19 +1720,14 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings") SubtypingResult result = isSubtype(subTy, superTy); CHECK(!result.isSubtype); CHECK( - result.reasoning == - std::vector{ - SubtypingReasoning{ - /* subPath */ Path(TypePath::Property::read("X")), - /* superPath */ Path(TypePath::Property::read("X")), - /* variance */ SubtypingVariance::Invariant - }, - SubtypingReasoning{ - /* subPath */ Path(TypePath::Property::read("Y")), - /* superPath */ Path(TypePath::Property::read("Y")), - /* variance */ SubtypingVariance::Invariant - }, - } + result.reasoning == std::vector{ + SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), + /* superPath */ Path(TypePath::Property::read("X")), + /* variance */ SubtypingVariance::Invariant}, + SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("Y")), + /* superPath */ Path(TypePath::Property::read("Y")), + /* variance */ SubtypingVariance::Invariant}, + } ); } diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 4b464a31..c385a763 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -29,34 +29,33 @@ struct TypeFunctionFixture : Fixture TypeFunctionFixture() : Fixture(false) { - swapFunction = TypeFunction{ - /* name */ "Swap", - /* reducer */ - [](TypeId instance, const std::vector& tys, const std::vector& tps, NotNull ctx - ) -> TypeFunctionReductionResult - { - LUAU_ASSERT(tys.size() == 1); - TypeId param = follow(tys.at(0)); - - if (isString(param)) - { - return TypeFunctionReductionResult{ctx->builtins->numberType, Reduction::MaybeOk, {}, {}}; - } - else if (isNumber(param)) - { - return TypeFunctionReductionResult{ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; - } - else if (is(param) || is(param) || is(param) || - (ctx->solver && ctx->solver->hasUnresolvedConstraints(param))) - { - return TypeFunctionReductionResult{std::nullopt, Reduction::MaybeOk, {param}, {}}; - } - else - { - return TypeFunctionReductionResult{std::nullopt, Reduction::Erroneous, {}, {}}; - } - } - }; + swapFunction = + TypeFunction{/* name */ "Swap", + /* reducer */ + [](TypeId instance, const std::vector& tys, const std::vector& tps, NotNull ctx) + -> TypeFunctionReductionResult + { + LUAU_ASSERT(tys.size() == 1); + TypeId param = follow(tys.at(0)); + + if (isString(param)) + { + return TypeFunctionReductionResult{ctx->builtins->numberType, Reduction::MaybeOk, {}, {}}; + } + else if (isNumber(param)) + { + return TypeFunctionReductionResult{ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; + } + else if (is(param) || is(param) || is(param) || + (ctx->solver && ctx->solver->hasUnresolvedConstraints(param))) + { + return TypeFunctionReductionResult{std::nullopt, Reduction::MaybeOk, {param}, {}}; + } + else + { + return TypeFunctionReductionResult{std::nullopt, Reduction::Erroneous, {}, {}}; + } + }}; unfreeze(getFrontend().globals.globalTypes); TypeId t = getFrontend().globals.globalTypes.addType(GenericType{"T"}); @@ -1895,13 +1894,15 @@ TEST_CASE_FIXTURE(TFFixture, "reduce_degenerate_refinement") }; TypeId root = arena->addType(BlockedType{}); - TypeId refinement = arena->addType(TypeFunctionInstanceType{ - builtinTypeFunctions.refineFunc, - { - root, - builtinTypes_.unknownType, + TypeId refinement = arena->addType( + TypeFunctionInstanceType{ + builtinTypeFunctions.refineFunc, + { + root, + builtinTypes_.unknownType, + } } - }); + ); emplaceType(asMutable(root), refinement); reduceTypeFunctions(refinement, Location{}, tfc, true); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index a578c809..5877c700 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -11,6 +11,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauInitializeDefaultGenericParamsAtProgramPoint) +LUAU_FASTFLAG(LuauAddErrorCaseForIncompatibleTypePacks) TEST_SUITE_BEGIN("TypeAliases"); @@ -76,29 +78,27 @@ TEST_CASE_FIXTURE(Fixture, "cannot_steal_hoisted_type_alias") if (FFlag::LuauSolverV2) { CHECK( - result.errors[0] == - TypeError{ - Location{{1, 21}, {1, 26}}, - getMainSourceModule()->name, - TypeMismatch{ - getBuiltins()->numberType, - getBuiltins()->stringType, - }, - } + result.errors[0] == TypeError{ + Location{{1, 21}, {1, 26}}, + getMainSourceModule()->name, + TypeMismatch{ + getBuiltins()->numberType, + getBuiltins()->stringType, + }, + } ); } else { CHECK( - result.errors[0] == - TypeError{ - Location{{1, 8}, {1, 26}}, - getMainSourceModule()->name, - TypeMismatch{ - getBuiltins()->numberType, - getBuiltins()->stringType, - }, - } + result.errors[0] == TypeError{ + Location{{1, 8}, {1, 26}}, + getMainSourceModule()->name, + TypeMismatch{ + getBuiltins()->numberType, + getBuiltins()->stringType, + }, + } ); } } @@ -1110,14 +1110,16 @@ type Foo = Foo TEST_CASE_FIXTURE(Fixture, "recursive_type_alias_bad_pack_use_warns") { - if (!FFlag::LuauSolverV2) - return; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauAddErrorCaseForIncompatibleTypePacks, true}}; CheckResult result = check(R"( type Foo = Foo )"); - LUAU_REQUIRE_ERROR_COUNT(4, result); + LUAU_REQUIRE_ERROR_COUNT(5, result); + LUAU_CHECK_ERROR(result, GenericError); + CHECK_EQ(toString(result.errors[4]), "Generic type 'Foo' expects 1 type argument, but none are specified"); + auto occursCheckFailed = get(result.errors[1]); REQUIRE(occursCheckFailed); @@ -1283,6 +1285,74 @@ export type t0 = t0 )")); } +TEST_CASE_FIXTURE(Fixture, "evaluating_generic_default_type_shouldnt_ice") +{ + ScopedFastFlag sff{FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint, true}; + + auto result = check(R"( +local A = {} +type B = unknown +)"); + + if (FFlag::LuauSolverV2) + LUAU_REQUIRE_NO_ERRORS(result); + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, UnknownSymbol); + } +} + +TEST_CASE_FIXTURE(Fixture, "evaluating_generic_default_type_pack_shouldnt_ice") +{ + ScopedFastFlag sff{FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint, true}; + + auto result = check(R"( +local A = {} +type B = unknown +)"); + + if (FFlag::LuauSolverV2) + LUAU_REQUIRE_NO_ERRORS(result); + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, UnknownSymbol); + } +} + +TEST_CASE_FIXTURE(Fixture, "evaluating_generic_default_type_for_symbol_before_definition_is_an_error") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint, true}, + }; + + auto result = check(R"( +type B = unknown +local A = {} +)"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, UnknownSymbol); +} + +TEST_CASE_FIXTURE(Fixture, "evaluating_generic_default_type_pack_for_symbol_before_definition_is_an_error") +{ + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauInitializeDefaultGenericParamsAtProgramPoint, true}, + }; + + auto result = check(R"( +type B = unknown +local A = {} +)"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, UnknownSymbol); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.annotations.test.cpp b/tests/TypeInfer.annotations.test.cpp index 13fcda76..f3ec6d76 100644 --- a/tests/TypeInfer.annotations.test.cpp +++ b/tests/TypeInfer.annotations.test.cpp @@ -229,15 +229,14 @@ TEST_CASE_FIXTURE(Fixture, "unknown_type_reference_generates_error") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK( - result.errors[0] == - TypeError{ - Location{{1, 17}, {1, 28}}, - getMainSourceModule()->name, - UnknownSymbol{ - "IDoNotExist", - UnknownSymbol::Context::Type, - }, - } + result.errors[0] == TypeError{ + Location{{1, 17}, {1, 28}}, + getMainSourceModule()->name, + UnknownSymbol{ + "IDoNotExist", + UnknownSymbol::Context::Type, + }, + } ); } diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 4b49a583..e160a3fc 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -16,6 +16,8 @@ using std::nullopt; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauScopeMethodsAreSolverAgnostic) +LUAU_FASTFLAG(LuauMorePreciseExternTableRelation) +LUAU_FASTFLAG(LuauPushTypeConstraint) TEST_SUITE_BEGIN("TypeInferExternTypes"); @@ -784,16 +786,18 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties") TypeId scriptType = arena.addType(ExternType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test", {}}); - TypeId partType = arena.addType(ExternType{ - "Part", - {{"BrickColor", Property::rw(getBuiltins()->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}}, - instanceType, - nullopt, - {}, - {}, - "Test", - {} - }); + TypeId partType = arena.addType( + ExternType{ + "Part", + {{"BrickColor", Property::rw(getBuiltins()->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}}, + instanceType, + nullopt, + {}, + {}, + "Test", + {} + } + ); getMutable(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}}; @@ -916,4 +920,177 @@ end LUAU_REQUIRE_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "extern_type_check_missing_key") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauMorePreciseExternTableRelation, true}, + }; + + loadDefinition(R"( + declare extern type Foobar with + Enabled: boolean + function Disable(self): () + end + )"); + + CheckResult results = check(R"( + local isUsingGamepad = false + local isModalVisible = false + + local function updateGamepadCursor(foo: Foobar) + local shouldEnableCursor = isUsingGamepad and isModalVisible + + if foo.IsEnabled == shouldEnableCursor then + return + end + + if not shouldEnableCursor then + foo:Disable() + end + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, results); + auto err = get(results.errors[0]); + CHECK_EQ("IsEnabled", err->key); +} + +TEST_CASE_FIXTURE(Fixture, "extern_type_check_present_key_in_superclass") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauMorePreciseExternTableRelation, true}, + }; + + loadDefinition(R"( + declare extern type FoobarParent with + IsEnabled: boolean + end + declare extern type Foobar extends FoobarParent with + function Disable(self): () + end + )"); + + CheckResult results = check(R"( + local isUsingGamepad = false + local isModalVisible = false + + local function updateGamepadCursor(foo: Foobar) + local shouldEnableCursor = isUsingGamepad and isModalVisible + + if foo.IsEnabled == shouldEnableCursor then + return + end + + if not shouldEnableCursor then + foo:Disable() + end + end + )"); + + LUAU_REQUIRE_NO_ERRORS(results); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_becomes_never") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauMorePreciseExternTableRelation, true}, + }; + + loadDefinition(R"( + declare extern type Foobar with + IsEnabled: string + end + + declare extern type Bing with + IsEnabled: number + end + )"); + + CheckResult results = check(R"( + local function update(foo: Foobar | Bing) + assert(type(foo.IsEnabled) == "number") + return foo + end + )"); + + LUAU_REQUIRE_NO_ERRORS(results); + CHECK_EQ("(Bing | Foobar) -> Bing", toString(requireType("update"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_becomes_intersection") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauMorePreciseExternTableRelation, true}, + }; + + loadDefinition(R"( + declare extern type Foobar with + IsEnabled: string | boolean + end + )"); + + CheckResult results = check(R"( + local function update(foo: Foobar) + assert(type(foo.IsEnabled) == "string") + return foo + end + )"); + + LUAU_REQUIRE_NO_ERRORS(results); + CHECK_EQ("(Foobar) -> Foobar & { read IsEnabled: string }", toString(requireType("update"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_superset") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauPushTypeConstraint, true}, + {FFlag::LuauMorePreciseExternTableRelation, true}, + }; + + loadDefinition(R"( + declare extern type Foobar with + IsEnabled: string + end + )"); + + CheckResult results = check(R"( + local function update(foo: Foobar) + assert(type(foo.IsEnabled) == "string" or type(foo.IsEnabled) == "number") + return foo + end + )"); + + LUAU_REQUIRE_NO_ERRORS(results); + CHECK_EQ("(Foobar) -> Foobar", toString(requireType("update"))); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "extern_type_check_key_idempotent") +{ + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauMorePreciseExternTableRelation, true}, + }; + + loadDefinition(R"( + declare extern type Foobar with + IsEnabled: string + end + )"); + + CheckResult results = check(R"( + local function update(foo: Foobar) + assert(type(foo.IsEnabled) == "string") + return foo + end + )"); + + LUAU_REQUIRE_NO_ERRORS(results); + CHECK_EQ("(Foobar) -> Foobar", toString(requireType("update"))); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index a32f72c9..6c25ea82 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -31,6 +31,8 @@ LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -126,15 +128,14 @@ TEST_CASE_FIXTURE(Fixture, "cannot_hoist_interior_defns_into_signature") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK( - result.errors[0] == - TypeError{ - Location{{1, 28}, {1, 29}}, - getMainSourceModule()->name, - UnknownSymbol{ - "T", - UnknownSymbol::Context::Type, - } - } + result.errors[0] == TypeError{ + Location{{1, 28}, {1, 29}}, + getMainSourceModule()->name, + UnknownSymbol{ + "T", + UnknownSymbol::Context::Type, + } + } ); } @@ -2409,7 +2410,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "attempt_to_call_an_intersection_of_tables_wi TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic") { - ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}, + {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}, + {FFlag::LuauEagerGeneralization4, true} + }; CheckResult result = check(R"( local function apply(f: (a, b...) -> c..., x: a) @@ -2423,9 +2429,11 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic") apply(add, 5) )"); - // FIXME: this errored at some point, but doesn't anymore. - // the desired behavior here is erroring. - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_ERROR_COUNT(1, result); + const TypePackMismatch* tpm = get(result.errors[0]); + CHECK(tpm); + CHECK_EQ(toString(tpm->wantedTp), "b..."); + CHECK_EQ(toString(tpm->givenTp), "number"); } TEST_CASE_FIXTURE(BuiltinsFixture, "num_is_solved_before_num_or_str") diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 970db638..e238e7ac 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -17,6 +17,7 @@ LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) using namespace Luau; @@ -1136,10 +1137,10 @@ end -- A... = ((B...) -> number, B...)) -- B... = (number) -- A... = ((number) -> number, number) -wrapper(foo, test2, 3) -wrapper(foo, test2, 3, 3) -wrapper(foo, test2) -wrapper(foo, test2, "3") +wrapper(foo, test2, 3) -- ok +wrapper(foo, test2, 3, 3) -- not ok (too many args) +wrapper(foo, test2) -- not ok (not enough args) +wrapper(foo, test2, "3") -- not ok (type mismatch, string instead of number) )"); LUAU_REQUIRE_ERROR_COUNT(3, result); @@ -1512,19 +1513,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") if (FFlag::LuauSolverV2) { result = check(R"( - local function sum(x: a, y: a, f: (a, a) -> a) return f(x, y) end + local function sum(x: T, y: T, z: (T, T) -> T) return z(x, y) end local function sumrec(f: typeof(sum)) - return sum(2, 3, function(a: X, b: X): add return a + b end) + return sum(2, 3, function(g: X, h: X): add return g + h end) end local b = sumrec(sum) -- ok - local c = sumrec(function(x, y, f) return f(x, y) end) -- type binders are not inferred + local c = sumrec( + function(d, e, f) + return f(d, e) + end + ) -- type binders are not inferred )"); - CHECK("add | number" == toString(requireType("b"))); // FIXME CLI-161128 - CHECK("(a, a, (a, a) -> a) -> a" == toString(requireType("sum"))); - CHECK("(a, a, (a, a) -> a) -> a" == toString(requireTypeAtPosition({7, 29}))); + CHECK("add | number" == toString(requireType("b"))); // FIXME CLI-161128 + CHECK("(T, T, (T, T) -> T) -> T" == toString(requireType("sum"))); + CHECK("(T, T, (T, T) -> T) -> T" == toString(requireTypeAtPosition({7, 29}))); LUAU_REQUIRE_ERROR_COUNT(1, result); // FIXME CLI-161128 CHECK(get(result.errors[0])); } @@ -1895,6 +1900,133 @@ end )"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position") +{ + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +function f(foo: (A) -> ()): () end +function g(...: B...): () end +f(g) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_2") +{ + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +function f(foo: (number) -> (number)): () end +type T = (A...) -> A... +local t: T +f(t) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_3") +{ + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +function f(foo: (B...) -> B...): () end +type T = (A...) -> A... +local t: T +f(t) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_4") +{ + ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; + ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +function f(foo: (A...) -> A...): () end +type T = (B...) -> C... +local t: T +f(t) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_5") +{ + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +function f(foo: (number) -> number): () end +type T = (A...) -> number +local t: T +f(t) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_6") +{ + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +function f(foo: (...number) -> number): () end +type T = (A...) -> number +local t: T +f(t) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_7") +{ + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +function f(foo: () -> ()): () end +type T = () -> A... +local t: T +f(t) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "generic_packs_in_contravariant_position_8") +{ + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +function f(foo: () -> ()): () end +type T = (A...) -> A... +local t: T +f(t) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "nested_generic_packs") +{ + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + ScopedFastFlag sff1{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +type T = (A...) -> ((A...) -> ()) +type U = (string) -> ((number) -> ()) +local t: T +local u: U = t + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error") { ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches, true}}; @@ -1933,4 +2065,20 @@ TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error_1") CHECK(get(res.errors[0])); } +TEST_CASE_FIXTURE(BuiltinsFixture, "xpcall_should_work_with_generics") +{ + ScopedFastFlag _{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + + CheckResult result = check(R"( +--!strict +local v: (number) -> (number) = nil :: any + +local x = 3 + +xpcall(v, print, x) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index 46bb106f..42b7865d 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -12,6 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) TEST_SUITE_BEGIN("IntersectionTypes"); @@ -855,17 +856,29 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") { + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + CheckResult result = check(R"( function f() function g(x : ((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))) - local y : ((nil, a...) -> (nil, b...)) = x -- OK + local y : ((nil, a...) -> (nil, b...)) = x -- OK in the old solver, not OK in the new local z : ((nil, b...) -> (nil, a...)) = x -- Not OK + local w : ((number?, a...) -> (number?, b...)) = x -- OK in both solvers end end )"); - if (FFlag::LuauSolverV2) { + LUAU_REQUIRE_ERROR_COUNT(2, result); + const TypeMismatch* tm1 = get(result.errors[0]); + CHECK(tm1); + CHECK_EQ(toString(tm1->wantedType), "(nil, a...) -> (nil, b...)"); + CHECK_EQ(toString(tm1->givenType), "((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))"); + const TypeMismatch* tm2 = get(result.errors[1]); + CHECK(tm2); + CHECK_EQ(toString(tm2->wantedType), "(nil, b...) -> (nil, a...)"); + CHECK_EQ(toString(tm2->givenType), "((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))"); + const std::string expected1 = "Type\n\t" "'((number?, a...) -> (number?, b...)) & ((string?, a...) -> (string?, b...))'" @@ -883,10 +896,18 @@ TEST_CASE_FIXTURE(Fixture, "overloaded_functions_mentioning_generic_packs") "\ncould not be converted into\n\t" "'(nil, b...) -> (nil, a...)'; \n" "this is because \n\t" + " * in the 1st component of the intersection, the function returns a tail of `b...` and it returns a tail of `a...`, and `b...` is not a " + "subtype of `a...`\n\t" " * in the 1st component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the " "union as `number` and it returns the 1st entry in the type pack is `nil`, and `number` is not a subtype of `nil`\n\t" + " * in the 1st component of the intersection, the function takes a tail of `a...` and it takes a tail of `b...`, and `a...` is not a " + "supertype of `b...`\n\t" + " * in the 2nd component of the intersection, the function returns a tail of `b...` and it returns a tail of `a...`, and `b...` is not a " + "subtype of `a...`\n\t" " * in the 2nd component of the intersection, the function returns the 1st entry in the type pack which has the 1st component of the " - "union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`"; + "union as `string` and it returns the 1st entry in the type pack is `nil`, and `string` is not a subtype of `nil`\n\t" + " * in the 2nd component of the intersection, the function takes a tail of `a...` and it takes a tail of `b...`, and `a...` is not a " + "supertype of `b...`"; CHECK_EQ(expected1, toString(result.errors[0])); CHECK_EQ(expected2, toString(result.errors[1])); @@ -1102,6 +1123,8 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_1") TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") { + ScopedFastFlag _{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + CheckResult result = check(R"( function f() function g(x : ((a...) -> ()) & ((b...) -> ())) @@ -1113,7 +1136,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_2") if (FFlag::LuauSolverV2) { - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_ERROR_COUNT(1, result); + const TypeMismatch* tm = get(result.errors[0]); + CHECK(tm); + CHECK_EQ(toString(tm->wantedType), "() -> ()"); + CHECK_EQ(toString(tm->givenType), "((a...) -> ()) & ((b...) -> ())"); } else { @@ -1127,6 +1154,9 @@ 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}; + CheckResult result = check(R"( function f() function g(x : (() -> a...) & (() -> (number?,a...))) @@ -1138,7 +1168,11 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_3") if (FFlag::LuauSolverV2) { - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_ERROR_COUNT(1, result); + const TypeMismatch* tm = get(result.errors[0]); + CHECK(tm); + CHECK_EQ(toString(tm->wantedType), "() -> number"); + CHECK_EQ(toString(tm->givenType), "(() -> (a...)) & (() -> (number?, a...))"); } else { @@ -1154,6 +1188,7 @@ could not be converted into TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") { ScopedFastFlag _{FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}; + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; CheckResult result = check(R"( function f() @@ -1168,17 +1203,24 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") if (FFlag::LuauSolverV2) { - // TODO: CLI-159120 - this error message is bogus + const TypeMismatch* tm = get(result.errors[0]); + CHECK(tm); + CHECK_EQ(toString(tm->wantedType), "(number?) -> ()"); + CHECK_EQ(toString(tm->givenType), "((a...) -> ()) & ((number, a...) -> number)"); const std::string expected = "Type\n\t" "'((a...) -> ()) & ((number, a...) -> number)'" "\ncould not be converted into\n\t" - "'((a...) -> ()) & ((number, a...) -> number)'; \n" + "'(number?) -> ()'; \n" "this is because \n\t" - " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " - "the latter type, and `()` is not a subtype of `number`\n\t" - " * in the 2nd component of the intersection, the function takes a tail of `number, a...` and in the 1st component of " - "the intersection, the function takes a tail of `number, a...`, and `number, a...` is not a supertype of `number, a...`"; + " * in the 1st component of the intersection, the function takes a tail of `a...` and it takes the portion of the type pack starting at " + "index 0 to the end`number?`, and `a...` is not a supertype of `number?`\n\t" + " * in the 2nd component of the intersection, the function returns is `number` and it returns `()`, and `number` is not a subtype of " + "`()`\n\t" + " * in the 2nd component of the intersection, the function takes a tail of `a...` and it takes `number?`, and `a...` is not a supertype " + "of `number?`\n\t" + " * in the 2nd component of the intersection, the function takes the 1st entry in the type pack which is `number` and it takes the 1st " + "entry in the type pack has the 2nd component of the union as `nil`, and `number` is not a supertype of `nil`"; CHECK(expected == toString(result.errors[0])); } else diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index efa72260..dd4b66df 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -16,6 +16,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) +LUAU_FASTFLAG(LuauEagerGeneralization4) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -758,6 +760,11 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_fail_missing_instantitation_follow") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_generic_next") { + ScopedFastFlag sff[] = { + {FFlag::LuauNoScopeShallNotSubsumeAll, true}, + {FFlag::LuauEagerGeneralization4, true}, + }; + CheckResult result = check(R"( for k: number, v: number in next, {1, 2, 3} do end @@ -865,11 +872,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "loop_iter_metamethod_not_enough_returns") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK( - result.errors[0] == - TypeError{ - Location{{2, 36}, {2, 37}}, - GenericError{"__iter must return at least one value"}, - } + result.errors[0] == TypeError{ + Location{{2, 36}, {2, 37}}, + GenericError{"__iter must return at least one value"}, + } ); } diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index d6e07a6a..33697752 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -893,10 +893,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 sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauLimitDynamicConstraintSolving3, true}}; ScopedFastInt sfi{FInt::LuauSolverConstraintLimit, 5}; diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 6ea9d68b..bfae05ad 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -18,6 +18,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) +LUAU_FASTFLAG(LuauTrackUniqueness) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauSolverAgnosticStringification) @@ -1455,8 +1456,12 @@ end )"); // FIXME(CLI-165431): fixing subtyping revealed an overload selection problems - if (FFlag::LuauSolverV2 && FFlag::LuauNoScopeShallNotSubsumeAll) + if (FFlag::LuauSolverV2 && FFlag::LuauNoScopeShallNotSubsumeAll && FFlag::LuauTrackUniqueness) + LUAU_REQUIRE_NO_ERRORS(result); + else if (FFlag::LuauSolverV2 && FFlag::LuauNoScopeShallNotSubsumeAll) + { LUAU_REQUIRE_ERROR_COUNT(2, result); + } else LUAU_REQUIRE_NO_ERRORS(result); } diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index e6d470c2..9556c0e7 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -719,7 +719,7 @@ TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") } else { - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "a"); // a == b CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "string?"); // a == b } } @@ -737,7 +737,7 @@ TEST_CASE_FIXTURE(Fixture, "unknown_lvalue_is_not_synonymous_with_other_on_not_e LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b + CHECK_EQ(toString(requireTypeAtPosition({3, 33})), "any"); // a ~= b CHECK_EQ(toString(requireTypeAtPosition({3, 36})), "{ x: number }?"); // a ~= b } @@ -871,8 +871,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_table") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("{ x: number } | { y: boolean }", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table" - CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table" + CHECK_EQ("{ x: number } | { y: boolean }", toString(requireTypeAtPosition({3, 28}))); // type(x) == "table" + CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); // type(x) ~= "table" } TEST_CASE_FIXTURE(BuiltinsFixture, "typeguard_narrows_for_functions") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index f5ffe511..7aa714cd 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -37,6 +37,7 @@ LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) LUAU_FASTFLAG(LuauPushTypeConstraint) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSimplifyIntersectionForLiteralSubtypeCheck) +LUAU_FASTFLAG(LuauCacheDuplicateHasPropConstraints) TEST_SUITE_BEGIN("TableTests"); @@ -3855,13 +3856,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly if (FFlag::LuauEagerGeneralization4) { /* - * string.byte returns ...number, which cannot be passed to - * table.insert. - * - * Intuitively, Luau has no way to guarantee that string.byte() will - * always return at least 1 number and there is no table.insert overload - * that takes just 1 argument. - */ + * string.byte returns ...number, which cannot be passed to + * table.insert. + * + * Intuitively, Luau has no way to guarantee that string.byte() will + * always return at least 1 number and there is no table.insert overload + * that takes just 1 argument. + */ LUAU_REQUIRE_ERROR(result, MultipleNonviableOverloads); } else @@ -5994,13 +5995,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_162179_avoid_exponential_blowup_in_norma { ScopedFastFlag sff{FFlag::LuauNormalizationLimitTyvarUnionSize, true}; - const std::string source = - "local res = {\n" + rep("\"foo\",\n", 100) + "}\n" - + "local function check(index: number)\n" - + " if res[index] == \"foo\" then\n" - + " print(\"found a foo!\")\n" - + " end\n" - + "end"; + const std::string source = format( + R"( + local res = { %s } + + local function check(index: number) + if res[index] == "foo" then + print("found a foo!") + end + end + )", + rep(R"("foo",)", 100).c_str() + ); LUAU_REQUIRE_NO_ERRORS(check(source)); } @@ -6139,4 +6145,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_167052") )")); } +TEST_CASE_FIXTURE(Fixture, "duplicate_prop_references_share_same_result_type_and_constraint") +{ + ScopedFastFlag sff{FFlag::LuauCacheDuplicateHasPropConstraints, true}; + CheckResult result = check(R"( +local tbl = {} +function f(x : number) : () end +function tbl:updateAmmoText() + f(self.leadingZeros) + local y = self.leadingZeros - 3 +end +)"); + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index e7112ad8..d68729cd 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -6,10 +6,8 @@ #include "Luau/Scope.h" #include "Luau/TypeInfer.h" #include "Luau/Type.h" -#include "Luau/VisitType.h" #include "Fixture.h" -#include "ClassFixture.h" #include "ScopedFlags.h" #include "doctest.h" @@ -34,6 +32,7 @@ LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAG(LuauOccursCheckInCommit) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) +LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) using namespace Luau; @@ -1280,9 +1279,9 @@ TEST_CASE_FIXTURE(Fixture, "follow_on_new_types_in_substitution") TEST_CASE_FIXTURE(Fixture, "types_stored_in_astResolvedTypes") { CheckResult result = check(R"( -type alias = typeof("hello") -local function foo(param: alias) -end + type alias = typeof("hello") + local function foo(param: alias) + end )"); auto node = findNodeAtPosition(*getMainSourceModule(), {2, 16}); @@ -2593,16 +2592,12 @@ end _()(_())("",_.n0,_,_(_,true,(_))) do end )")); - } TEST_CASE_FIXTURE(Fixture, "txnlog_checks_for_occurrence_before_self_binding_a_type") { - ScopedFastFlag sff[] = { - {FFlag::LuauSolverV2, false}, - {FFlag::LuauOccursCheckInCommit, true} - }; - + ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, false}, {FFlag::LuauOccursCheckInCommit, true}}; + CheckResult result = check(R"( local any = nil :: any @@ -2642,4 +2637,44 @@ TEST_CASE_FIXTURE(Fixture, "txnlog_checks_for_occurrence_before_self_binding_a_t )"); } +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}; + + // This shouldn't ICE + CheckResult result = check(R"( + if true then + elseif true then + elseif true then + elseif true then + else + local x = 1 + end + )"); +} + +// https://github.com/luau-lang/luau/issues/1971 +TEST_CASE_FIXTURE(Fixture, "nested_functions_can_depend_on_outer_generics") +{ + CheckResult result = check(R"( + function name

(arg1: P) + return function(what: P) return what end + end + + local funcTest = name(nil) + local out = funcTest(1) -- Doesn't report type mismatch error anymore + )"); + + CHECK("(nil) -> nil" == toString(requireType("funcTest"))); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + auto tm = get(result.errors[0]); + REQUIRE_MESSAGE(tm, "Expected TypeMismatch but got " << result.errors[0]); + + CHECK("nil" == toString(tm->wantedType)); + CHECK("number" == toString(tm->givenType)); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index 3a36d94a..e3bb41ff 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -14,6 +14,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauRemoveGenericErrorForParams) +LUAU_FASTFLAG(LuauAddErrorCaseForIncompatibleTypePacks) TEST_SUITE_BEGIN("TypePackTests"); @@ -605,14 +607,6 @@ type Other = Packed LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects 2 type arguments, but only 1 is specified"); - result = check(R"( -type Packed = (T...) -> T... -local a: Packed - )"); - - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type parameter list is required"); - result = check(R"( type Packed = (T...) -> (U...) type Other = Packed<> @@ -630,6 +624,22 @@ type Other = Packed CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects 2 type pack arguments, but only 1 is specified"); } +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 + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + if (FFlag::LuauSolverV2) + CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects 1 type pack argument, but none are specified"); + else + CHECK_EQ(toString(result.errors[0]), "Type parameter list is required"); +} + TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_explicit") { CheckResult result = check(R"( @@ -792,7 +802,7 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors2") TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors3") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); + ScopedFastFlag sff{FFlag::LuauAddErrorCaseForIncompatibleTypePacks, true}; CheckResult result = check(R"( type Y = { a: (T) -> U... } @@ -800,12 +810,15 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors3") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Generic type 'Y' expects at least 1 type argument, but none are specified"); + if (FFlag::LuauSolverV2) + CHECK_EQ(toString(result.errors[0]), "Type parameters must come before type pack parameters"); + else + CHECK_EQ(toString(result.errors[0]), "Generic type 'Y' expects at least 1 type argument, but none are specified"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors4") { - DOES_NOT_PASS_NEW_SOLVER_GUARD(); + ScopedFastFlag sff{FFlag::LuauRemoveGenericErrorForParams, true}; CheckResult result = check(R"( type Packed = (T) -> T @@ -813,7 +826,10 @@ TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors4") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ(toString(result.errors[0]), "Type parameter list is required"); + if (FFlag::LuauSolverV2) + CHECK_EQ(toString(result.errors[0]), "Generic type 'Packed' expects 1 type argument, but none are specified"); + else + CHECK_EQ(toString(result.errors[0]), "Type parameter list is required"); } TEST_CASE_FIXTURE(Fixture, "type_alias_default_type_errors5") diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index d2f63b04..26ec026b 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -629,13 +629,15 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash") UnionType u; u.options.push_back(badCyclicUnionTy); - u.options.push_back(arena.addType(TableType{ - {}, - TableIndexer{getBuiltins()->numberType, getBuiltins()->numberType}, - TypeLevel{}, - getFrontend().globals.globalScope.get(), - TableState::Sealed - })); + u.options.push_back(arena.addType( + TableType{ + {}, + TableIndexer{getBuiltins()->numberType, getBuiltins()->numberType}, + TypeLevel{}, + getFrontend().globals.globalScope.get(), + TableState::Sealed + } + )); asMutable(badCyclicUnionTy)->ty.emplace(std::move(u)); diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp index 2a6908f0..93623f8b 100644 --- a/tests/TypePath.test.cpp +++ b/tests/TypePath.test.cpp @@ -19,19 +19,22 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps); LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2); +LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) struct TypePathFixture : Fixture { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; + ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; TypeArena arena; - const DenseHashMap emptyMap{nullptr}; + const DenseHashMap emptyMap_DEPRECATED{nullptr}; }; struct TypePathBuiltinsFixture : BuiltinsFixture { ScopedFastFlag sff1{FFlag::LuauSolverV2, true}; + ScopedFastFlag sff2{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; TypeArena arena; - const DenseHashMap emptyMap{nullptr}; + const DenseHashMap emptyMap_DEPRECATED{nullptr}; }; TEST_SUITE_BEGIN("TypePathManipulation"); @@ -114,7 +117,7 @@ TEST_SUITE_BEGIN("TypePathTraversal"); TEST_CASE_FIXTURE(TypePathFixture, "empty_traversal") { - CHECK(traverseForType(getBuiltins()->numberType, kEmpty, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == getBuiltins()->numberType); + CHECK(traverseForType(getBuiltins()->numberType, kEmpty, getBuiltins(), NotNull{&arena}) == getBuiltins()->numberType); } TEST_CASE_FIXTURE(TypePathFixture, "table_property") @@ -123,22 +126,16 @@ TEST_CASE_FIXTURE(TypePathFixture, "table_property") local x = { y = 123 } )"); - CHECK( - traverseForType(requireType("x"), Path(TypePath::Property{"y", true}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == - getBuiltins()->numberType - ); + CHECK(traverseForType(requireType("x"), Path(TypePath::Property{"y", true}), getBuiltins(), NotNull{&arena}) == getBuiltins()->numberType); } TEST_CASE_FIXTURE(ExternTypeFixture, "class_property") { + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; // Force this here because vector2InstanceType won't get initialized until the frontend has been forced getFrontend(); - const DenseHashMap emptyMap{nullptr}; TypeArena arena; - CHECK( - traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == - getBuiltins()->numberType - ); + CHECK(traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), getBuiltins(), NotNull{&arena}) == getBuiltins()->numberType); } TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "metatable_property") @@ -165,10 +162,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "metatable_property") )"); } - CHECK( - traverseForType(requireType("x"), Path(TypePath::Property::read("x")), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == - getBuiltins()->numberType - ); + CHECK(traverseForType(requireType("x"), Path(TypePath::Property::read("x")), getBuiltins(), NotNull{&arena}) == getBuiltins()->numberType); } TEST_CASE_FIXTURE(TypePathFixture, "index") @@ -181,17 +175,12 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") SUBCASE("in_bounds") { - CHECK( - traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == - getBuiltins()->stringType - ); + CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins(), NotNull{&arena}) == getBuiltins()->stringType); } SUBCASE("out_of_bounds") { - CHECK( - traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == std::nullopt - ); + CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins(), NotNull{&arena}) == std::nullopt); } } @@ -204,7 +193,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") SUBCASE("in_bounds") { - auto result = traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(requireTypeAlias("T"), Path(TypePath::Index{1}), getBuiltins(), NotNull{&arena}); CHECK(result); if (result) @@ -213,9 +202,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") SUBCASE("out_of_bounds") { - CHECK( - traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == std::nullopt - ); + CHECK(traverseForType(requireTypeAlias("T"), Path(TypePath::Index{97}), getBuiltins(), NotNull{&arena}) == std::nullopt); } } @@ -229,14 +216,14 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") SUBCASE("in_bounds") { Path path = Path({TypePath::PackField::Arguments, TypePath::Index{1}}); - auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins(), NotNull{&arena}); CHECK(result == getBuiltins()->stringType); } SUBCASE("out_of_bounds") { Path path = Path({TypePath::PackField::Arguments, TypePath::Index{72}}); - auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(requireTypeAlias("T"), path, getBuiltins(), NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -244,13 +231,14 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") { + ScopedFastFlag sff{FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}; + getFrontend(); - const DenseHashMap emptyMap{nullptr}; TypeArena arena; SUBCASE("string") { - auto result = traverseForType(getBuiltins()->stringType, Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(getBuiltins()->stringType, Path(TypeField::Metatable), getBuiltins(), NotNull{&arena}); CHECK(result == getMetatable(getBuiltins()->stringType, getBuiltins())); } @@ -260,7 +248,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") type T = "foo" )"); - auto result = traverseForType(requireTypeAlias("T"), Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(requireTypeAlias("T"), Path(TypeField::Metatable), getBuiltins(), NotNull{&arena}); CHECK(result == getMetatable(getBuiltins()->stringType, getBuiltins())); } @@ -275,7 +263,7 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") )"); // Tricky test setup because 'setmetatable' mutates the argument 'tbl' type - auto result = traverseForType(requireType("res"), Path(TypeField::Table), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(requireType("res"), Path(TypeField::Table), getBuiltins(), NotNull{&arena}); auto expected = lookupType("Table"); REQUIRE(expected); CHECK(result == follow(*expected)); @@ -288,13 +276,13 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") local tbl = setmetatable({}, mt) )"); - auto result = traverseForType(requireType("tbl"), Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(requireType("tbl"), Path(TypeField::Metatable), getBuiltins(), NotNull{&arena}); CHECK(result == requireType("mt")); } SUBCASE("class") { - auto result = traverseForType(vector2InstanceType, Path(TypeField::Metatable), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(vector2InstanceType, Path(TypeField::Metatable), getBuiltins(), NotNull{&arena}); // ExternTypeFixture's Vector2 metatable is just an empty table, but it's there. CHECK(result); } @@ -313,28 +301,22 @@ TEST_CASE_FIXTURE(TypePathFixture, "bounds") SUBCASE("upper") { ft->upperBound = getBuiltins()->numberType; - auto result = traverseForType(ty, Path(TypeField::UpperBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(ty, Path(TypeField::UpperBound), getBuiltins(), NotNull{&arena}); CHECK(result == getBuiltins()->numberType); } SUBCASE("lower") { ft->lowerBound = getBuiltins()->booleanType; - auto result = traverseForType(ty, Path(TypeField::LowerBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(ty, Path(TypeField::LowerBound), getBuiltins(), NotNull{&arena}); CHECK(result == getBuiltins()->booleanType); } } SUBCASE("unbounded_type") { - CHECK( - traverseForType(getBuiltins()->numberType, Path(TypeField::UpperBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == - std::nullopt - ); - CHECK( - traverseForType(getBuiltins()->numberType, Path(TypeField::LowerBound), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}) == - std::nullopt - ); + CHECK(traverseForType(getBuiltins()->numberType, Path(TypeField::UpperBound), getBuiltins(), NotNull{&arena}) == std::nullopt); + CHECK(traverseForType(getBuiltins()->numberType, Path(TypeField::LowerBound), getBuiltins(), NotNull{&arena}) == std::nullopt); } } @@ -348,10 +330,8 @@ TEST_CASE_FIXTURE(TypePathFixture, "indexers") type T = { [string]: boolean } )"); - auto lookupResult = - traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); - auto resultResult = - traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto lookupResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins(), NotNull{&arena}); + auto resultResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins(), NotNull{&arena}); CHECK(lookupResult == getBuiltins()->stringType); CHECK(resultResult == getBuiltins()->booleanType); @@ -363,10 +343,8 @@ TEST_CASE_FIXTURE(TypePathFixture, "indexers") type T = { y: number } )"); - auto lookupResult = - traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); - auto resultResult = - traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto lookupResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexLookup), getBuiltins(), NotNull{&arena}); + auto resultResult = traverseForType(requireTypeAlias("T"), Path(TypeField::IndexResult), getBuiltins(), NotNull{&arena}); CHECK(lookupResult == std::nullopt); CHECK(resultResult == std::nullopt); @@ -384,13 +362,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "negated") unfreeze(arena); TypeId ty = arena.addType(NegationType{getBuiltins()->numberType}); - auto result = traverseForType(ty, Path(TypeField::Negated), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(ty, Path(TypeField::Negated), getBuiltins(), NotNull{&arena}); CHECK(result == getBuiltins()->numberType); } SUBCASE("not_negation") { - auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Negated), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Negated), getBuiltins(), NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -403,13 +381,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "variadic") unfreeze(arena); TypePackId tp = arena.addTypePack(VariadicTypePack{getBuiltins()->numberType}); - auto result = traverseForType(tp, Path(TypeField::Variadic), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(tp, Path(TypeField::Variadic), getBuiltins(), NotNull{&arena}); CHECK(result == getBuiltins()->numberType); } SUBCASE("not_variadic") { - auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Variadic), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(getBuiltins()->numberType, Path(TypeField::Variadic), getBuiltins(), NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -423,7 +401,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "arguments") end )"); - auto result = traverseForPack(requireType("f"), Path(PackField::Arguments), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(requireType("f"), Path(PackField::Arguments), getBuiltins(), NotNull{&arena}); CHECK(result); if (result) CHECK(toString(*result) == "number, string"); @@ -431,7 +409,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "arguments") SUBCASE("not_function") { - auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Arguments), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Arguments), getBuiltins(), NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -446,7 +424,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "returns") end )"); - auto result = traverseForPack(requireType("f"), Path(PackField::Returns), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(requireType("f"), Path(PackField::Returns), getBuiltins(), NotNull{&arena}); CHECK(result); if (result) CHECK(toString(*result) == "number, string"); @@ -454,7 +432,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "returns") SUBCASE("not_function") { - auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Returns), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(getBuiltins()->booleanType, Path(PackField::Returns), getBuiltins(), NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -467,8 +445,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail") type T = (number, string, ...boolean) -> () )"); - auto result = - traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&arena}); CHECK(result); if (result) CHECK(toString(*result) == "...boolean"); @@ -480,16 +457,13 @@ TEST_CASE_FIXTURE(TypePathFixture, "tail") type T = (number, string) -> () )"); - auto result = - traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&arena}); CHECK(result == std::nullopt); } SUBCASE("type") { - auto result = traverseForPack( - getBuiltins()->stringType, Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena} - ); + auto result = traverseForPack(getBuiltins()->stringType, Path({PackField::Arguments, PackField::Tail}), getBuiltins(), NotNull{&arena}); CHECK(result == std::nullopt); } } @@ -505,8 +479,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_has_tail") type T = (number, string, ...boolean) -> () )"); - auto result = - traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&arena}); CHECK(result); if (result) CHECK(toString(*result) == "string, ...boolean"); @@ -523,8 +496,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_finite_pack") type T = (number, string) -> () )"); - auto result = - traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(requireTypeAlias("T"), Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&arena}); CHECK(result); if (result) CHECK(toString(*result) == "string"); @@ -535,8 +507,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "pack_slice_type") TypeArena& arena = getFrontend().globals.globalTypes; unfreeze(arena); - auto result = - traverseForPack(builtinTypes->stringType, Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForPack(builtinTypes->stringType, Path({PackField::Arguments, PackSlice{1}}), getBuiltins(), NotNull{&arena}); CHECK(result == std::nullopt); } @@ -553,7 +524,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5)) TypeId b = arena.addType(BoundType{a}); asMutable(a)->ty.emplace(b); - CHECK_THROWS(traverseForType(a, Path(TypeField::IndexResult), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena})); + CHECK_THROWS(traverseForType(a, Path(TypeField::IndexResult), getBuiltins(), NotNull{&arena})); } SUBCASE("table_contains_itself") @@ -564,7 +535,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5)) TypeId tbl = arena.addType(TableType{}); getMutable(tbl)->props["a"] = Luau::Property(tbl); - auto result = traverseForType(tbl, Path(TypePath::Property{"a", true}), getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(tbl, Path(TypePath::Property{"a", true}), getBuiltins(), NotNull{&arena}); CHECK(result == tbl); } } @@ -585,7 +556,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "step_limit") TypeId root = requireTypeAlias("T"); Path path = PathBuilder().readProp("x").readProp("y").readProp("z").build(); - auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(root, path, getBuiltins(), NotNull{&arena}); CHECK(!result); } @@ -603,7 +574,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains") TypeId root = requireTypeAlias("Tab"); Path path = PathBuilder().mt().readProp("__add").rets().index(0).build(); - auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(root, path, getBuiltins(), NotNull{&arena}); CHECK(result == getBuiltins()->numberType); } @@ -617,7 +588,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains") TypeId root = requireTypeAlias("Obj"); Path path = PathBuilder().readProp("method").index(0).args().index(1).build(); - auto result = traverseForType(root, path, getBuiltins(), NotNull{&emptyMap}, NotNull{&arena}); + auto result = traverseForType(root, path, getBuiltins(), NotNull{&arena}); CHECK(*result == getBuiltins()->falseType); } } diff --git a/tests/conformance/native.luau b/tests/conformance/native.luau index 94d67d04..66bfcea7 100644 --- a/tests/conformance/native.luau +++ b/tests/conformance/native.luau @@ -525,12 +525,12 @@ assert(extramath3(2) == "number") assert(extramath3("2") == "number") local function scopedrestorefailurea(format: string, b: buffer): (string, buffer) - local H1F, H2F, H3F, H4F = buffer.readu32(b, 0), buffer.readu32(b, 8), buffer.readu32(b, 16), buffer.readu32(b, 24) - local H5F, H6F, _, _ = buffer.readu32(b, 32), buffer.readu32(b, 40), buffer.readu32(b, 48), buffer.readu32(b, 56) - local H1B, H2B, H3B, H4B = buffer.readu32(b, 4), buffer.readu32(b, 12), buffer.readu32(b, 20), buffer.readu32(b, 28) - local H5B, H6B, _, _ = buffer.readu32(b, 36), buffer.readu32(b, 44), buffer.readu32(b, 52), buffer.readu32(b, 60) + local H1F, H2F, H3F, H4F = buffer.readu32(b, 0), buffer.readu32(b, 8), buffer.readu32(b, 16), buffer.readu32(b, 24) + local H5F, H6F, _, _ = buffer.readu32(b, 32), buffer.readu32(b, 40), buffer.readu32(b, 48), buffer.readu32(b, 56) + local H1B, H2B, H3B, H4B = buffer.readu32(b, 4), buffer.readu32(b, 12), buffer.readu32(b, 20), buffer.readu32(b, 28) + local H5B, H6B, _, _ = buffer.readu32(b, 36), buffer.readu32(b, 44), buffer.readu32(b, 52), buffer.readu32(b, 60) - return string.format(format, H1F, H1B, H2F, H2B, H3F, H3B, H4F, H4B, H5F, H5B, H6F, H6B), b + return string.format(format, H1F, H1B, H2F, H2B, H3F, H3B, H4F, H4B, H5F, H5B, H6F, H6B), b end local function scopedrestorefailureb() @@ -605,4 +605,32 @@ end slotcachelimit2(function(a) return -a end, vector.create(1, 2, 3)) +local function forcedregisterspill1(x, y, z) + local c1, c2, c3, c4, c5, c6, c7, c8, c9, c10 = (x + 1), (x + 2), (x + 3), (x + 4), (x + 5), (x + 6), (x + 7), (x + 8), (x + 9), (x + 10) + local d1, d2, d3, d4, d5, d6, d7, d8, d9, d10 = (y + 1), (y + 2), (y + 3), (y + 4), (y + 5), (y + 6), (y + 7), (y + 8), (y + 9), (y + 10) + local e1, e2, e3, e4, e5, e6, e7, e8, e9, e10 = (z * 1), (z * 2), (z * 3), (z * 4), (z * 5), (z * 6), (z * 7), (z * 8), (z * 9), (z * 10) + + -- force store of all those values for a call and then bring all those registers back from VM slot spills + local t = { + c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9 + c10 + (x + 1) + (x + 2) + (x + 3) + (x + 4) + (x + 5) + (x + 6) + (x + 7) + (x + 8) + (x + 9) + (x + 10), + d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8 + d9 + d10 + (y + 1) + (y + 2) + (y + 3) + (y + 4) + (y + 5) + (y + 6) + (y + 7) + (y + 8) + (y + 9) + (y + 10), + e1 + e2 + e3 + e4 + e5 + e6 + e7 + e8 + e9 + e10 + (z * 1) + (z * 2) + (z * 3) + (z * 4) + (z * 5) + (z * 6) + (z * 7) + (z * 8) + (z * 9) + (z * 10), + } + + -- bring them back once more time + local s1 = c1 + c2 + c3 + c4 + c5 + c6 + c7 + c8 + c9 + c10 + (x + 1) + (x + 2) + (x + 3) + (x + 4) + (x + 5) + (x + 6) + (x + 7) + (x + 8) + (x + 9) + (x + 10) + local s2 = d1 + d2 + d3 + d4 + d5 + d6 + d7 + d8 + d9 + d10 + (y + 1) + (y + 2) + (y + 3) + (y + 4) + (y + 5) + (y + 6) + (y + 7) + (y + 8) + (y + 9) + (y + 10) + local s3 = e1 + e2 + e3 + e4 + e5 + e6 + e7 + e8 + e9 + e10 + (z * 1) + (z * 2) + (z * 3) + (z * 4) + (z * 5) + (z * 6) + (z * 7) + (z * 8) + (z * 9) + (z * 10) + + -- have not fallen back + assert(is_native()) + return s1, s2, s3, t +end + +do + local a, b, c, t = forcedregisterspill1(2, 20, 10) + assert(a == 150 and b == 510 and c == 1100) + assert(t[1] == 150 and t[2] == 510 and t[3] == 1100) +end + return('OK') diff --git a/tests/main.cpp b/tests/main.cpp index 8ae42460..e30d0ed0 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -428,9 +428,14 @@ int main(int argc, char** argv) doctest::String filter; if (doctest::parseOption(argc, argv, "--run_test", &filter) && filter[0] == '=') { - if (doctest::parseOption(argc, argv, "--run_tests_in_file")) + if (doctest::parseOption(argc, argv, "--run_suites_in_file")) { - fprintf(stderr, "ERROR: Cannot pass both --run_test and --run_tests_in_file\n"); + fprintf(stderr, "ERROR: Cannot pass both --run_test and --run_suites_in_file\n"); + return 1; + } + if (doctest::parseOption(argc, argv, "--run_cases_in_file")) + { + fprintf(stderr, "ERROR: Cannot pass both --run_test and --run_cases_in_file\n"); return 1; } const char* f = filter.c_str() + 1; @@ -447,13 +452,26 @@ int main(int argc, char** argv) } } - doctest::String filter_path; - if (doctest::parseOption(argc, argv, "--run_tests_in_file", &filter_path) && filter_path[0] == '=') + doctest::String suite_filter_path; + if (doctest::parseOption(argc, argv, "--run_suites_in_file", &suite_filter_path) && suite_filter_path[0] == '=') + { + const char* filter_file = suite_filter_path.c_str() + 1; + std::ifstream filter_stream(filter_file); + std::stringstream buffer; + buffer << filter_stream.rdbuf(); + std::string suite_list = buffer.str(); + context.addFilter("test-suite", suite_list.c_str()); + } + + doctest::String case_filter_path; + if (doctest::parseOption(argc, argv, "--run_cases_in_file", &case_filter_path) && case_filter_path[0] == '=') { - filter_path = filter_path.substr(1, filter_path.size() - 1); - std::ifstream filter_stream(filter_path.c_str()); - std::string case_list((std::istreambuf_iterator(filter_stream)), std::istreambuf_iterator()); - context.addFilter("test-case", case_list.c_str()); + const char* filter_file = case_filter_path.c_str() + 1; + std::ifstream filter_stream(filter_file); + std::stringstream buffer; + buffer << filter_stream.rdbuf(); + std::string case_list = buffer.str(); + context.addFilter("test-path", case_list.c_str()); } // These callbacks register unit tests that need runtime support to be From 5059095fec64b658ea9f7c5fae61cc770fe0d9af Mon Sep 17 00:00:00 2001 From: Jack <85714123+jackdotink@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:37:22 -0500 Subject: [PATCH 6/8] Allow files to return strings and enforce a single return value in the REPL's require implementation (#1967) This PR changes the Luau CLI to allow string values to be returned from required files and to error whenever a file returns no values or more than one value. --- CLI/src/ReplRequirer.cpp | 14 ++-- tests/RequireByString.test.cpp | 70 +++++++++++++++++++ .../require/without_config/types/boolean.luau | 1 + .../require/without_config/types/buffer.luau | 1 + .../without_config/types/function.luau | 3 + tests/require/without_config/types/nil.luau | 1 + .../require/without_config/types/number.luau | 1 + .../require/without_config/types/string.luau | 1 + tests/require/without_config/types/table.luau | 1 + .../require/without_config/types/thread.luau | 3 + .../without_config/types/userdata.luau | 1 + .../require/without_config/types/vector.luau | 1 + 12 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 tests/require/without_config/types/boolean.luau create mode 100644 tests/require/without_config/types/buffer.luau create mode 100644 tests/require/without_config/types/function.luau create mode 100644 tests/require/without_config/types/nil.luau create mode 100644 tests/require/without_config/types/number.luau create mode 100644 tests/require/without_config/types/string.luau create mode 100644 tests/require/without_config/types/table.luau create mode 100644 tests/require/without_config/types/thread.luau create mode 100644 tests/require/without_config/types/userdata.luau create mode 100644 tests/require/without_config/types/vector.luau diff --git a/CLI/src/ReplRequirer.cpp b/CLI/src/ReplRequirer.cpp index 5a290af7..969f0faf 100644 --- a/CLI/src/ReplRequirer.cpp +++ b/CLI/src/ReplRequirer.cpp @@ -166,23 +166,25 @@ static int load(lua_State* L, void* ctx, const char* path, const char* chunkname if (status == 0) { - if (lua_gettop(ML) == 0) - lua_pushstring(ML, "module must return a value"); + if (lua_gettop(ML) != 1) + luaL_error(L, "module must return a single value"); } else if (status == LUA_YIELD) { - lua_pushstring(ML, "module can not yield"); + luaL_error(L, "module can not yield"); } else if (!lua_isstring(ML, -1)) { - lua_pushstring(ML, "unknown error while running module"); + luaL_error(L, "unknown error while running module"); + } + else + { + luaL_error(L, "error while running module: %s", lua_tostring(ML, -1)); } } // add ML result to L stack lua_xmove(ML, L, 1); - if (lua_isstring(L, -1)) - lua_error(L); // remove ML thread from L stack lua_remove(L, -2); diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index b4bff43c..e75d8e8c 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -731,4 +731,74 @@ TEST_CASE("ParseAliases") checkContents(copyAssignedConfig); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireBoolean") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/boolean"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "false"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireBuffer") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/buffer"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "buffer"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireFunction") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/function"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "function"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireNil") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/nil"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "nil"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireNumber") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/number"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "12345"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireString") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/string"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "\"foo\""}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireTable") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/table"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "{\"foo\", \"bar\"}"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireThread") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/thread"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "thread"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireUserdata") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/userdata"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "userdata"}); +} + +TEST_CASE_FIXTURE(ReplWithPathFixture, "RequireVector") +{ + std::string path = getLuauDirectory(PathType::Relative) + "/tests/require/without_config/types/vector"; + runProtectedRequire(path); + assertOutputContainsAll({"true", "1, 2, 3"}); +} + TEST_SUITE_END(); diff --git a/tests/require/without_config/types/boolean.luau b/tests/require/without_config/types/boolean.luau new file mode 100644 index 00000000..1bae4d4a --- /dev/null +++ b/tests/require/without_config/types/boolean.luau @@ -0,0 +1 @@ +return false \ No newline at end of file diff --git a/tests/require/without_config/types/buffer.luau b/tests/require/without_config/types/buffer.luau new file mode 100644 index 00000000..7a83f2c6 --- /dev/null +++ b/tests/require/without_config/types/buffer.luau @@ -0,0 +1 @@ +return buffer.create(16) \ No newline at end of file diff --git a/tests/require/without_config/types/function.luau b/tests/require/without_config/types/function.luau new file mode 100644 index 00000000..599ceec9 --- /dev/null +++ b/tests/require/without_config/types/function.luau @@ -0,0 +1,3 @@ +return function() + return 1 + 1 +end diff --git a/tests/require/without_config/types/nil.luau b/tests/require/without_config/types/nil.luau new file mode 100644 index 00000000..bd6def05 --- /dev/null +++ b/tests/require/without_config/types/nil.luau @@ -0,0 +1 @@ +return nil \ No newline at end of file diff --git a/tests/require/without_config/types/number.luau b/tests/require/without_config/types/number.luau new file mode 100644 index 00000000..931201a3 --- /dev/null +++ b/tests/require/without_config/types/number.luau @@ -0,0 +1 @@ +return 12345 \ No newline at end of file diff --git a/tests/require/without_config/types/string.luau b/tests/require/without_config/types/string.luau new file mode 100644 index 00000000..321604fa --- /dev/null +++ b/tests/require/without_config/types/string.luau @@ -0,0 +1 @@ +return "foo" \ No newline at end of file diff --git a/tests/require/without_config/types/table.luau b/tests/require/without_config/types/table.luau new file mode 100644 index 00000000..5bcfd80c --- /dev/null +++ b/tests/require/without_config/types/table.luau @@ -0,0 +1 @@ +return { "foo", "bar" } \ No newline at end of file diff --git a/tests/require/without_config/types/thread.luau b/tests/require/without_config/types/thread.luau new file mode 100644 index 00000000..b9596c6d --- /dev/null +++ b/tests/require/without_config/types/thread.luau @@ -0,0 +1,3 @@ +return coroutine.create(function() + return "foo" +end) \ No newline at end of file diff --git a/tests/require/without_config/types/userdata.luau b/tests/require/without_config/types/userdata.luau new file mode 100644 index 00000000..91abe22b --- /dev/null +++ b/tests/require/without_config/types/userdata.luau @@ -0,0 +1 @@ +return newproxy() \ No newline at end of file diff --git a/tests/require/without_config/types/vector.luau b/tests/require/without_config/types/vector.luau new file mode 100644 index 00000000..7ab44159 --- /dev/null +++ b/tests/require/without_config/types/vector.luau @@ -0,0 +1 @@ +return vector.create(1, 2, 3) \ No newline at end of file From ae59a0e33c537080c38811df030a733fd5a2cbd7 Mon Sep 17 00:00:00 2001 From: Menarul Alam <35981995+menarulalam@users.noreply.github.com> Date: Mon, 15 Sep 2025 13:16:27 -0700 Subject: [PATCH 7/8] Sync to upstream/release/691 (#1998) # New Type Solver * Fixed https://github.com/luau-lang/luau/issues/1983 by adding additional context to permit some expressions otherwise not allowed * We now skip issuing CheckedFunctionWarnings on use of unreduced type functions * Fix gcc compile error * Handle expressions `a == b` and `a ~= b` more efficiently * Subtyping now correctly handles generics over unions and intersections in cases such as ``` local function a(arr: { T }) end a({ 'one', 'two' }) ``` * Rearrange unification cases to avoid free type generalization to never * Improve generic bounds mismatch error message * Fix Ancestry bug in visitor for incomplete repeat blocks such as ``` local t = {} function t:Foo() end repeat t:| ``` * Support autocomplete for attributes # Native Codegen * Unwind info has support on Android * Introduced a separate ctypeof function which does not constant-fold for vector arguments. * We now perform string concatenation at compile time if possible (there is a known issue with the optimization that we will address next week) * Make vector.lerp and math.lerp more efficient with FMA * Record STORE_SPLIT_TVALUE as a potential restore operation to reduce number of spills to stack * When native execution is disabled, we now mark the call frame as not executing natively, making resumption safer --- Co-authored-by: Andy Friesen Co-authored-by: Annie Tang Co-authored-by: Hunter Goldstein Co-authored-by: Ilya Rezvov Co-authored-by: Sora Kanosue Co-authored-by: Varun Saini Co-authored-by: Vighnesh Vijay 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: Aviral Goel Co-authored-by: Vighnesh Co-authored-by: Vyacheslav Egorov Co-authored-by: Ariel Weiss Co-authored-by: Andy Friesen --- Analysis/include/Luau/ConstraintSolver.h | 1 + Analysis/include/Luau/Error.h | 10 + Analysis/include/Luau/IostreamHelpers.h | 1 + Analysis/include/Luau/Normalize.h | 3 + Analysis/include/Luau/VisitType.h | 83 +--- Analysis/src/AstJsonEncoder.cpp | 9 +- Analysis/src/AstQuery.cpp | 19 +- Analysis/src/AutocompleteCore.cpp | 71 ++- Analysis/src/BuiltinDefinitions.cpp | 11 +- Analysis/src/BuiltinTypeFunctions.cpp | 118 ++--- Analysis/src/Constraint.cpp | 29 +- Analysis/src/ConstraintGenerator.cpp | 390 ++++++--------- Analysis/src/ConstraintSolver.cpp | 327 ++++--------- Analysis/src/DataFlowGraph.cpp | 7 +- Analysis/src/Error.cpp | 52 +- Analysis/src/ExpectedTypeVisitor.cpp | 5 - Analysis/src/FragmentAutocomplete.cpp | 70 +-- Analysis/src/Frontend.cpp | 86 +--- Analysis/src/Generalization.cpp | 556 +++------------------ Analysis/src/InferPolarity.cpp | 4 - Analysis/src/IostreamHelpers.cpp | 5 + Analysis/src/NonStrictTypeChecker.cpp | 22 +- Analysis/src/Normalize.cpp | 17 +- Analysis/src/OverloadResolution.cpp | 4 +- Analysis/src/Scope.cpp | 25 +- Analysis/src/Subtyping.cpp | 117 +++-- Analysis/src/TableLiteralInference.cpp | 26 +- Analysis/src/Transpiler.cpp | 3 + Analysis/src/TypeChecker2.cpp | 120 ++--- Analysis/src/TypeFunction.cpp | 84 +--- Analysis/src/TypeUtils.cpp | 3 - Analysis/src/Unifier2.cpp | 123 +++-- Ast/include/Luau/Ast.h | 3 + Ast/include/Luau/Lexer.h | 1 + Ast/src/Ast.cpp | 8 + Ast/src/Lexer.cpp | 5 + Ast/src/Parser.cpp | 44 +- CMakeLists.txt | 3 + CodeGen/include/Luau/AssemblyBuilderA64.h | 2 + CodeGen/include/Luau/AssemblyBuilderX64.h | 14 +- CodeGen/include/Luau/IrData.h | 12 +- CodeGen/include/Luau/IrVisitUseDef.h | 3 - CodeGen/src/AssemblyBuilderA64.cpp | 41 ++ CodeGen/src/AssemblyBuilderX64.cpp | 98 ++-- CodeGen/src/CodeBlockUnwind.cpp | 3 - CodeGen/src/CodeGen.cpp | 27 ++ CodeGen/src/CodeGenAssembly.cpp | 5 +- CodeGen/src/CodeGenContext.cpp | 11 +- CodeGen/src/IrDump.cpp | 6 +- CodeGen/src/IrLoweringA64.cpp | 56 ++- CodeGen/src/IrLoweringX64.cpp | 101 +++- CodeGen/src/IrTranslateBuiltins.cpp | 35 +- CodeGen/src/IrTranslation.cpp | 33 +- CodeGen/src/IrUtils.cpp | 3 +- CodeGen/src/IrValueLocationTracking.cpp | 11 +- CodeGen/src/NativeState.cpp | 1 - CodeGen/src/NativeState.h | 1 - CodeGen/src/OptimizeConstProp.cpp | 6 +- CodeGen/src/OptimizeDeadStore.cpp | 1 - Compiler/include/Luau/Compiler.h | 2 +- Compiler/src/BuiltinFolding.cpp | 31 +- Compiler/src/Compiler.cpp | 19 +- Compiler/src/ConstantFolding.cpp | 29 +- Compiler/src/ConstantFolding.h | 3 +- tests/AssemblyBuilderX64.test.cpp | 12 - tests/Autocomplete.test.cpp | 132 ++++- tests/Compiler.test.cpp | 72 ++- tests/Conformance.test.cpp | 2 + tests/FragmentAutocomplete.test.cpp | 566 +++++++++++++--------- tests/Generalization.test.cpp | 17 +- tests/InferPolarity.test.cpp | 9 - tests/IrLowering.test.cpp | 47 +- tests/Linter.test.cpp | 1 - tests/NonStrictTypeChecker.test.cpp | 14 + tests/NonstrictMode.test.cpp | 3 - tests/RuntimeLimits.test.cpp | 5 - tests/Subtyping.test.cpp | 5 - tests/TypeFunction.test.cpp | 29 +- tests/TypeFunction.user.test.cpp | 9 +- tests/TypeInfer.aliases.test.cpp | 2 + tests/TypeInfer.builtins.test.cpp | 4 - tests/TypeInfer.classes.test.cpp | 2 - tests/TypeInfer.definitions.test.cpp | 9 +- tests/TypeInfer.functions.test.cpp | 108 ++--- tests/TypeInfer.generics.test.cpp | 34 +- tests/TypeInfer.loops.test.cpp | 2 - tests/TypeInfer.modules.test.cpp | 9 +- tests/TypeInfer.negations.test.cpp | 6 - tests/TypeInfer.oop.test.cpp | 9 - tests/TypeInfer.operators.test.cpp | 33 +- tests/TypeInfer.provisional.test.cpp | 5 +- tests/TypeInfer.refinements.test.cpp | 77 +-- tests/TypeInfer.singletons.test.cpp | 54 +++ tests/TypeInfer.tables.test.cpp | 70 +-- tests/TypeInfer.test.cpp | 44 +- tests/TypeInfer.typePacks.test.cpp | 4 - tests/TypeInfer.typestates.test.cpp | 2 - tests/TypeInfer.unionTypes.test.cpp | 13 +- tests/TypeInfer.unknownnever.test.cpp | 13 +- tests/Unifier2.test.cpp | 37 ++ tests/conformance/native.luau | 41 ++ 101 files changed, 2133 insertions(+), 2387 deletions(-) diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 1d1aacf4..f5000830 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -178,6 +178,7 @@ struct ConstraintSolver ConstraintSet constraintSet ); + // TODO CLI-169086: Replace all uses of this constructor with the ConstraintSet constructor, above. explicit ConstraintSolver( NotNull normalizer, NotNull simplifier, diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index f1292833..62fad5d9 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -87,6 +87,15 @@ struct CannotExtendTable bool operator==(const CannotExtendTable& rhs) const; }; +struct CannotCompareUnrelatedTypes +{ + TypeId left; + TypeId right; + AstExprBinary::Op op; + + bool operator==(const CannotCompareUnrelatedTypes& rhs) const; +}; + struct OnlyTablesCanHaveMethods { TypeId tableType; @@ -547,6 +556,7 @@ using TypeErrorData = Variant< UnknownProperty, NotATable, CannotExtendTable, + CannotCompareUnrelatedTypes, OnlyTablesCanHaveMethods, DuplicateTypeDefinition, CountMismatch, diff --git a/Analysis/include/Luau/IostreamHelpers.h b/Analysis/include/Luau/IostreamHelpers.h index a16455df..3d6f7fb1 100644 --- a/Analysis/include/Luau/IostreamHelpers.h +++ b/Analysis/include/Luau/IostreamHelpers.h @@ -22,6 +22,7 @@ std::ostream& operator<<(std::ostream& lhs, const UnknownSymbol& error); std::ostream& operator<<(std::ostream& lhs, const UnknownProperty& error); std::ostream& operator<<(std::ostream& lhs, const NotATable& error); std::ostream& operator<<(std::ostream& lhs, const CannotExtendTable& error); +std::ostream& operator<<(std::ostream& lhs, const CannotCompareUnrelatedTypes& error); std::ostream& operator<<(std::ostream& lhs, const OnlyTablesCanHaveMethods& error); std::ostream& operator<<(std::ostream& lhs, const DuplicateTypeDefinition& error); std::ostream& operator<<(std::ostream& lhs, const CountMismatch& error); diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index 4a79830a..435c86bb 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -281,6 +281,9 @@ struct NormalizedType /// Returns true if this type contains the primitve top table type, `table`. bool hasTopTable() const; + /// Returns true if this type is `nil` or `nil | *error-type*` + bool isNil() const; + // Helpers that improve readability of the above (they just say if the component is present) bool hasTops() const; bool hasBooleans() const; diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index 3d52f0e7..c3534479 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -10,8 +10,6 @@ #include "Type.h" LUAU_FASTINT(LuauVisitRecursionLimit) -LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauSolverAgnosticVisitType) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) namespace Luau @@ -261,37 +259,16 @@ struct GenericTypeVisitor } else if (auto ftv = get(ty)) { - if (FFlag::LuauSolverAgnosticVisitType) + if (visit(ty, *ftv)) { - if (visit(ty, *ftv)) - { - // Regardless of the choice of solver, all free types are guaranteed to have - // lower and upper bounds - LUAU_ASSERT(ftv->lowerBound); - LUAU_ASSERT(ftv->upperBound); + // Regardless of the choice of solver, all free types are guaranteed to have + // lower and upper bounds + LUAU_ASSERT(ftv->lowerBound); + LUAU_ASSERT(ftv->upperBound); - traverse(ftv->lowerBound); - traverse(ftv->upperBound); - } - } - else if (FFlag::LuauSolverV2) - { - if (visit(ty, *ftv)) - { - // 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 (ftv->lowerBound) - traverse(ftv->lowerBound); - - if (ftv->upperBound) - traverse(ftv->upperBound); - } + traverse(ftv->lowerBound); + traverse(ftv->upperBound); } - else - visit(ty, *ftv); } else if (auto gtv = get(ty)) visit(ty, *gtv); @@ -324,20 +301,15 @@ struct GenericTypeVisitor { for (auto& [_name, prop] : ttv->props) { - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticVisitType) - { - 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 has pretty - // significant performance consequences. - if (auto ty = prop.writeTy; ty && !prop.isShared()) - traverse(*ty); - } - else - traverse(prop.type_DEPRECATED()); + 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 has pretty + // significant performance consequences. + if (auto ty = prop.writeTy; ty && !prop.isShared()) + traverse(*ty); } if (ttv->indexer) @@ -362,20 +334,15 @@ struct GenericTypeVisitor { for (const auto& [name, prop] : etv->props) { - if (FFlag::LuauSolverV2 || FFlag::LuauSolverAgnosticVisitType) - { - 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 - // performance consequences. - if (auto ty = prop.writeTy; ty && !prop.isShared()) - traverse(*ty); - } - else - traverse(prop.type_DEPRECATED()); + 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 + // performance consequences. + if (auto ty = prop.writeTy; ty && !prop.isShared()) + traverse(*ty); } if (etv->parent) diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index 64f932d6..f38c36e1 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -8,6 +8,8 @@ #include +LUAU_FASTFLAG(LuauAutocompleteAttributes) + namespace Luau { @@ -1153,6 +1155,8 @@ struct AstJsonEncoder : public AstVisitor return writeString("native"); case AstAttr::Type::Deprecated: return writeString("deprecated"); + case AstAttr::Type::Unknown: + return writeString("unknown"); } } @@ -1163,7 +1167,10 @@ struct AstJsonEncoder : public AstVisitor "AstAttr", [&]() { - write("name", node->type); + if (FFlag::LuauAutocompleteAttributes) + write("name", node->name); + else + write("name", node->type); } ); } diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index 6ba94ec4..4513ced2 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -12,6 +12,7 @@ #include LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAGVARIABLE(LuauUnfinishedRepeatAncestryFix) namespace Luau { @@ -31,10 +32,22 @@ struct AutocompleteNodeFinder : public AstVisitor bool visit(AstExpr* expr) override { - if (expr->location.begin <= pos && pos <= expr->location.end) + if (FFlag::LuauUnfinishedRepeatAncestryFix) { - ancestry.push_back(expr); - return true; + // If the expression size is 0 (begin == end), we don't want to include it in the ancestry + if (expr->location.begin <= pos && pos <= expr->location.end && expr->location.begin != expr->location.end) + { + ancestry.push_back(expr); + return true; + } + } + else + { + if (expr->location.begin <= pos && pos <= expr->location.end) + { + ancestry.push_back(expr); + return true; + } } return false; } diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index 14d52dfc..3498be24 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -27,15 +27,17 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) -LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) LUAU_FASTFLAGVARIABLE(LuauIncludeBreakContinueStatements) LUAU_FASTFLAGVARIABLE(LuauSuggestHotComments) +LUAU_FASTFLAG(LuauAutocompleteAttributes) static constexpr std::array kStatementStartingKeywords = {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; static constexpr std::array kHotComments = {"nolint", "nocheck", "nonstrict", "strict", "optimize", "native"}; +static const std::string kKnownAttributes[] = {"checked", "deprecated", "native"}; + namespace Luau { @@ -582,39 +584,21 @@ static void autocompleteStringSingleton(TypeId ty, bool addQuotes, AstNode* node ty = follow(ty); - if (FFlag::LuauImplicitTableIndexerKeys3) + if (auto ss = get(get(ty))) { - if (auto ss = get(get(ty))) - { - // This is purposefully `try_emplace` as we don't want to override any existing entries. - result.try_emplace(formatKey(ss->value), AutocompleteEntry{AutocompleteEntryKind::String, ty, false, false, TypeCorrectKind::Correct}); - } - else if (auto uty = get(ty)) - { - for (auto el : uty) - { - if (auto ss = get(get(el))) - { - // This is purposefully `try_emplace` as we don't want to override any existing entries. - result.try_emplace( - formatKey(ss->value), AutocompleteEntry{AutocompleteEntryKind::String, ty, false, false, TypeCorrectKind::Correct} - ); - } - } - } + // This is purposefully `try_emplace` as we don't want to override any existing entries. + result.try_emplace(formatKey(ss->value), AutocompleteEntry{AutocompleteEntryKind::String, ty, false, false, TypeCorrectKind::Correct}); } - else + else if (auto uty = get(ty)) { - if (auto ss = get(get(ty))) + for (auto el : uty) { - result[formatKey(ss->value)] = AutocompleteEntry{AutocompleteEntryKind::String, ty, false, false, TypeCorrectKind::Correct}; - } - else if (auto uty = get(ty)) - { - for (auto el : uty) + if (auto ss = get(get(el))) { - if (auto ss = get(get(el))) - result[formatKey(ss->value)] = AutocompleteEntry{AutocompleteEntryKind::String, ty, false, false, TypeCorrectKind::Correct}; + // This is purposefully `try_emplace` as we don't want to override any existing entries. + result.try_emplace( + formatKey(ss->value), AutocompleteEntry{AutocompleteEntryKind::String, ty, false, false, TypeCorrectKind::Correct} + ); } } } @@ -2105,12 +2089,6 @@ AutocompleteResult autocomplete_( { AutocompleteEntryMap result; - if (!FFlag::LuauImplicitTableIndexerKeys3) - { - if (auto it = module->astExpectedTypes.find(node->asExpr())) - autocompleteStringSingleton(*it, false, node, position, result); - } - if (ancestry.size() >= 2) { if (auto idxExpr = ancestry.at(ancestry.size() - 2)->as()) @@ -2128,11 +2106,8 @@ AutocompleteResult autocomplete_( } } - if (FFlag::LuauImplicitTableIndexerKeys3) - { - if (auto it = module->astExpectedTypes.find(node->asExpr())) - autocompleteStringSingleton(*it, false, node, position, result); - } + if (auto it = module->astExpectedTypes.find(node->asExpr())) + autocompleteStringSingleton(*it, false, node, position, result); return {std::move(result), ancestry, AutocompleteContext::String}; } @@ -2143,6 +2118,22 @@ AutocompleteResult autocomplete_( AutocompleteEntryMap map; return {std::move(map), ancestry, AutocompleteContext::String}; } + else if (AstExprFunction* func = node->as()) + { + if (FFlag::LuauAutocompleteAttributes) + { + for (AstAttr* attr : func->attributes) + { + if (attr->location.begin <= position && position <= attr->location.end && attr->type == AstAttr::Type::Unknown) + { + AutocompleteEntryMap ret; + for (const auto& attr : kKnownAttributes) + ret[attr.c_str()] = {AutocompleteEntryKind::Keyword}; + return {std::move(ret), std::move(ancestry), AutocompleteContext::Keyword}; + } + } + } + } if (node->is()) return {}; diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index 9cd15da9..008a5ead 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -32,7 +32,6 @@ */ LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAG(LuauEmplaceNotPushBack) @@ -367,9 +366,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeArena& arena = globals.globalTypes; NotNull builtinTypes = globals.builtinTypes; - Scope* globalScope = nullptr; // NotNull when removing FFlag::LuauEagerGeneralization4 - if (FFlag::LuauEagerGeneralization4) - globalScope = globals.globalScope.get(); + NotNull globalScope{globals.globalScope.get()}; if (frontend.getLuauSolverMode() == SolverMode::New) builtinTypeFunctions().addToScope(NotNull{&arena}, NotNull{globals.globalScope.get()}); @@ -513,12 +510,10 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypePackId thePack = arena.addTypePack({genericTy}); TypeId idTyWithMagic = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack}); ttv->props["freeze"] = makeProperty(idTyWithMagic, "@luau/global/table.freeze"); - if (globalScope) - inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTyWithMagic); + inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTyWithMagic); TypeId idTy = arena.addType(FunctionType{{genericTy}, {}, thePack, thePack}); - if (globalScope) - inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTy); + inferGenericPolarities(NotNull{&globals.globalTypes}, NotNull{globalScope}, idTy); ttv->props["clone"] = makeProperty(idTy, "@luau/global/table.clone"); } else diff --git a/Analysis/src/BuiltinTypeFunctions.cpp b/Analysis/src/BuiltinTypeFunctions.cpp index 7ec9c5de..1295db89 100644 --- a/Analysis/src/BuiltinTypeFunctions.cpp +++ b/Analysis/src/BuiltinTypeFunctions.cpp @@ -17,9 +17,7 @@ #include "Luau/UserDefinedTypeFunction.h" #include "Luau/VisitType.h" -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAGVARIABLE(LuauDoNotBlockOnStuckTypeFunctions) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_DYNAMIC_FASTINTVARIABLE(LuauStepRefineRecursionLimit, 64) LUAU_FASTFLAGVARIABLE(LuauRefineOccursCheckDirectRecursion) @@ -30,6 +28,7 @@ LUAU_FASTFLAGVARIABLE(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauEGFixGenericsList) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) LUAU_FASTFLAG(LuauRawGetHandlesNil) +LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) namespace Luau { @@ -268,8 +267,7 @@ TypeFunctionReductionResult unmTypeFunction( if (isPending(operandTy, ctx->solver)) return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}}; - if (FFlag::LuauEagerGeneralization4) - operandTy = follow(operandTy); + operandTy = follow(operandTy); std::shared_ptr normTy = ctx->normalizer->normalize(operandTy); @@ -761,30 +759,10 @@ TypeFunctionReductionResult orTypeFunction( return {rhsTy, Reduction::MaybeOk, {}, {}}; // check to see if both operand types are resolved enough, and wait to reduce if not - if (FFlag::LuauEagerGeneralization4) - { - if (FFlag::LuauDoNotBlockOnStuckTypeFunctions) - { - if (isBlockedOrUnsolvedType(lhsTy)) - return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; - else if (isBlockedOrUnsolvedType(rhsTy)) - return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; - } - else - { - if (is(lhsTy)) - return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; - else if (is(rhsTy)) - return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; - } - } - else - { - if (isPending(lhsTy, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; - else if (isPending(rhsTy, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; - } + if (isBlockedOrUnsolvedType(lhsTy)) + return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; + else if (isBlockedOrUnsolvedType(rhsTy)) + return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; // Or evalutes to the LHS type if the LHS is truthy, and the RHS type if LHS is falsy. SimplifyResult filteredLhs = simplifyIntersection(ctx->builtins, ctx->arena, lhsTy, ctx->builtins->truthyType); @@ -818,30 +796,10 @@ static TypeFunctionReductionResult comparisonTypeFunction( if (lhsTy == instance || rhsTy == instance) return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; - if (FFlag::LuauEagerGeneralization4) - { - if (FFlag::LuauDoNotBlockOnStuckTypeFunctions) - { - if (isBlockedOrUnsolvedType(lhsTy)) - return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; - else if (isBlockedOrUnsolvedType(rhsTy)) - return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; - } - else - { - if (is(lhsTy)) - return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; - else if (is(rhsTy)) - return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; - } - } - else - { - if (isPending(lhsTy, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; - else if (isPending(rhsTy, ctx->solver)) - return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; - } + if (isBlockedOrUnsolvedType(lhsTy)) + return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; + else if (isBlockedOrUnsolvedType(rhsTy)) + return {std::nullopt, Reduction::MaybeOk, {rhsTy}, {}}; // Algebra Reduction Rules for comparison type functions // Note that comparing to never tells you nothing about the other operand @@ -1336,17 +1294,7 @@ TypeFunctionReductionResult refineTypeFunction( return {targetTy, {}}; } - bool targetIsPending = false; - - if (FFlag::LuauEagerGeneralization4) - { - targetIsPending = FFlag::LuauDoNotBlockOnStuckTypeFunctions ? isBlockedOrUnsolvedType(targetTy) - : is(targetTy); - } - else - { - targetIsPending = isPending(targetTy, ctx->solver); - } + const bool targetIsPending = isBlockedOrUnsolvedType(targetTy); // check to see if both operand types are resolved enough, and wait to reduce if not if (targetIsPending) @@ -1436,32 +1384,25 @@ TypeFunctionReductionResult refineTypeFunction( if (is(target) || isTruthyOrFalsyType(discriminant)) { SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); - if (FFlag::LuauEagerGeneralization4) + // Simplification considers free and generic types to be + // 'blocking', but that's not suitable for refine<>. + // + // If we are only blocked on those types, we consider + // the simplification a success and reduce. + if (std::all_of( + begin(result.blockedTypes), + end(result.blockedTypes), + [](TypeId v) + { + return is(follow(v)); + } + )) { - // Simplification considers free and generic types to be - // 'blocking', but that's not suitable for refine<>. - // - // If we are only blocked on those types, we consider - // the simplification a success and reduce. - if (std::all_of( - begin(result.blockedTypes), - end(result.blockedTypes), - [](auto&& v) - { - return is(follow(v)); - } - )) - { - return {result.result, {}}; - } - else - return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; + return {result.result, {}}; } else - { - if (!result.blockedTypes.empty()) - return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; - } + return {nullptr, {result.blockedTypes.begin(), result.blockedTypes.end()}}; + return {result.result, {}}; } @@ -2662,7 +2603,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions() , ltFunc{"lt", ltTypeFunction} , leFunc{"le", leTypeFunction} , eqFunc{"eq", eqTypeFunction} - , refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization4} + , refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ true} , singletonFunc{"singleton", singletonTypeFunction} , unionFunc{"union", unionTypeFunction} , intersectFunc{"intersect", intersectTypeFunction} @@ -2723,7 +2664,8 @@ void BuiltinTypeFunctions::addToScope(NotNull arena, NotNull s scope->exportedTypeBindings[ltFunc.name] = mkBinaryTypeFunctionWithDefault(<Func); scope->exportedTypeBindings[leFunc.name] = mkBinaryTypeFunctionWithDefault(&leFunc); - scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunctionWithDefault(&eqFunc); + if (!FFlag::LuauNoMoreComparisonTypeFunctions) + scope->exportedTypeBindings[eqFunc.name] = mkBinaryTypeFunctionWithDefault(&eqFunc); scope->exportedTypeBindings[keyofFunc.name] = mkUnaryTypeFunction(&keyofFunc); scope->exportedTypeBindings[rawkeyofFunc.name] = mkUnaryTypeFunction(&rawkeyofFunc); diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index 1ea149b5..c95dcea2 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -4,8 +4,6 @@ #include "Luau/TypeFunction.h" #include "Luau/VisitType.h" -LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAGVARIABLE(LuauExplicitSkipBoundTypes) namespace Luau @@ -49,11 +47,8 @@ struct ReferenceCountInitializer : TypeOnceVisitor bool visit(TypeId ty, const TableType& tt) override { - if (FFlag::LuauEagerGeneralization4) - { - if (tt.state == TableState::Unsealed || tt.state == TableState::Free) - result->insert(ty); - } + if (tt.state == TableState::Unsealed || tt.state == TableState::Free) + result->insert(ty); return true; } @@ -66,20 +61,14 @@ struct ReferenceCountInitializer : TypeOnceVisitor bool visit(TypeId, const TypeFunctionInstanceType& tfit) override { - if (FFlag::LuauForceSimplifyConstraint2) - return tfit.function->canReduceGenerics; - else - return FFlag::LuauEagerGeneralization4 && traverseIntoTypeFunctions; + return tfit.function->canReduceGenerics; } }; bool isReferenceCountedType(const TypeId typ) { - if (FFlag::LuauEagerGeneralization4) - { - if (auto tt = get(typ)) - return tt->state == TableState::Free || tt->state == TableState::Unsealed; - } + if (auto tt = get(typ)) + return tt->state == TableState::Free || tt->state == TableState::Unsealed; // n.b. this should match whatever `ReferenceCountInitializer` includes. return get(typ) || get(typ) || get(typ); @@ -128,7 +117,7 @@ TypeIds Constraint::getMaybeMutatedFreeTypes() const { rci.traverse(fchc->argsPack); } - else if (auto fcc = get(*this); fcc && FFlag::LuauEagerGeneralization4) + else if (auto fcc = get(*this)) { rci.traverseIntoTypeFunctions = false; rci.traverse(fcc->fn); @@ -142,13 +131,11 @@ TypeIds Constraint::getMaybeMutatedFreeTypes() const else if (auto hpc = get(*this)) { rci.traverse(hpc->resultType); - if (FFlag::LuauEagerGeneralization4) - rci.traverse(hpc->subjectType); + rci.traverse(hpc->subjectType); } else if (auto hic = get(*this)) { - if (FFlag::LuauEagerGeneralization4) - rci.traverse(hic->subjectType); + rci.traverse(hic->subjectType); rci.traverse(hic->resultType); // `HasIndexerConstraint` should not mutate `indexType`. } diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index dc91001f..51e09834 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -34,12 +34,9 @@ LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauEnableWriteOnlyProperties) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) -LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauInferActualIfElseExprType2) -LUAU_FASTFLAGVARIABLE(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAGVARIABLE(LuauTypeFunNoScopeMapRef) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) @@ -54,6 +51,7 @@ LUAU_FASTFLAGVARIABLE(LuauNumericUnaryOpsDontProduceNegationRefinements) LUAU_FASTFLAGVARIABLE(LuauInitializeDefaultGenericParamsAtProgramPoint) LUAU_FASTFLAGVARIABLE(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAGVARIABLE(LuauCacheDuplicateHasPropConstraints) +LUAU_FASTFLAGVARIABLE(LuauNoMoreComparisonTypeFunctions) namespace Luau { @@ -377,14 +375,9 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) { - const TypeId ft = FFlag::LuauEagerGeneralization4 ? Luau::freshType(arena, builtinTypes, scope.get(), polarity) - : Luau::freshType(arena, builtinTypes, scope.get()); - + const TypeId ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); interiorFreeTypes.back().types.push_back(ft); - - if (FFlag::LuauEagerGeneralization4) - freeTypes.insert(ft); - + freeTypes.insert(ft); return ft; } @@ -749,10 +742,7 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat if (partition.shouldAppendNilType) ty = createTypeFunctionInstance(builtinTypeFunctions().weakoptionalFunc, {ty}, {}, scope, location); - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(scope, def, ty); - else - scope->rvalueRefinements[def] = ty; + updateRValueRefinements(scope, def, ty); } } @@ -1286,10 +1276,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_) DefId def = dfg->getDef(for_->var); forScope->lvalueTypes[def] = annotationTy; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(forScope, def, annotationTy); - else - forScope->rvalueRefinements[def] = annotationTy; + updateRValueRefinements(forScope, def, annotationTy); visit(forScope, for_->body); @@ -1416,25 +1403,6 @@ static void propagateDeprecatedAttributeToConstraint(ConstraintV& c, const AstEx } } -static void propagateDeprecatedAttributeToType(TypeId signature, const AstExprFunction* func) -{ - FunctionType* fty = getMutable(signature); - LUAU_ASSERT(fty); - if (FFlag::LuauParametrizedAttributeSyntax) - { - AstAttr* deprecatedAttr = func->getAttribute(AstAttr::Type::Deprecated); - fty->isDeprecatedFunction = deprecatedAttr != nullptr; - if (deprecatedAttr) - { - fty->deprecatedInfo = std::make_shared(deprecatedAttr->deprecatedInfo()); - } - } - else - { - fty->isDeprecatedFunction = func->hasAttribute(AstAttr::Type::Deprecated); - } -} - ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFunction* function) { // Local @@ -1452,69 +1420,51 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location}; - bool sigFullyDefined = FFlag::LuauEagerGeneralization4 ? false : !hasFreeType(sig.signature); - if (sigFullyDefined) - emplaceType(asMutable(functionType), sig.signature); - DefId def = dfg->getDef(function->name); scope->lvalueTypes[def] = functionType; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(scope, def, functionType); - else - scope->rvalueRefinements[def] = functionType; + updateRValueRefinements(scope, def, functionType); sig.bodyScope->lvalueTypes[def] = sig.signature; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(sig.bodyScope, def, sig.signature); - else - sig.bodyScope->rvalueRefinements[def] = sig.signature; + updateRValueRefinements(sig.bodyScope, def, sig.signature); Checkpoint start = checkpoint(this); checkFunctionBody(sig.bodyScope, function->func); Checkpoint end = checkpoint(this); - if (!sigFullyDefined) - { - NotNull constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; - std::unique_ptr c = - std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); + NotNull constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()}; + std::unique_ptr c = + std::make_unique(constraintScope, function->name->location, GeneralizationConstraint{functionType, sig.signature}); - propagateDeprecatedAttributeToConstraint(c->c, function->func); + propagateDeprecatedAttributeToConstraint(c->c, function->func); - Constraint* previous = nullptr; - forEachConstraint( - start, - end, - this, - [&c, &previous](const ConstraintPtr& constraint) - { - if (FFlag::LuauEmplaceNotPushBack) - c->dependencies.emplace_back(constraint.get()); - else - c->dependencies.push_back(NotNull{constraint.get()}); + Constraint* previous = nullptr; + forEachConstraint( + start, + end, + this, + [&c, &previous](const ConstraintPtr& constraint) + { + if (FFlag::LuauEmplaceNotPushBack) + c->dependencies.emplace_back(constraint.get()); + else + c->dependencies.push_back(NotNull{constraint.get()}); - if (auto psc = get(*constraint); psc && psc->returns) + 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(); } - ); + } + ); - getMutable(functionType)->setOwner(addConstraint(scope, std::move(c))); - module->astTypes[function->func] = functionType; - } - else - { - module->astTypes[function->func] = sig.signature; - propagateDeprecatedAttributeToType(sig.signature, function->func); - } + getMutable(functionType)->setOwner(addConstraint(scope, std::move(c))); + module->astTypes[function->func] = functionType; return ControlFlow::None; } @@ -1526,7 +1476,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f Checkpoint start = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); - bool sigFullyDefined = FFlag::LuauEagerGeneralization4 ? false : !hasFreeType(sig.signature); DefId def = dfg->getDef(function->name); @@ -1534,26 +1483,17 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f { sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location}; sig.bodyScope->lvalueTypes[def] = sig.signature; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(sig.bodyScope, def, sig.signature); - else - sig.bodyScope->rvalueRefinements[def] = sig.signature; + updateRValueRefinements(sig.bodyScope, def, sig.signature); } else if (AstExprGlobal* globalName = function->name->as()) { sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location}; sig.bodyScope->lvalueTypes[def] = sig.signature; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(sig.bodyScope, def, sig.signature); - else - sig.bodyScope->rvalueRefinements[def] = sig.signature; + updateRValueRefinements(sig.bodyScope, def, sig.signature); } else if (AstExprIndexName* indexName = function->name->as()) { - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(sig.bodyScope, def, sig.signature); - else - sig.bodyScope->rvalueRefinements[def] = sig.signature; + updateRValueRefinements(sig.bodyScope, def, sig.signature); } if (auto indexName = function->name->as()) @@ -1601,47 +1541,39 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f Checkpoint end = checkpoint(this); TypeId generalizedType = arena->addType(BlockedType{}); - if (sigFullyDefined) - { - emplaceType(asMutable(generalizedType), sig.signature); - propagateDeprecatedAttributeToType(sig.signature, function->func); - } - else - { - const ScopePtr& constraintScope = sig.signatureScope ? sig.signatureScope : sig.bodyScope; + const ScopePtr& constraintScope = sig.signatureScope ? sig.signatureScope : sig.bodyScope; - NotNull c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature}); - getMutable(generalizedType)->setOwner(c); + NotNull c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature}); + getMutable(generalizedType)->setOwner(c); - propagateDeprecatedAttributeToConstraint(c->c, function->func); + propagateDeprecatedAttributeToConstraint(c->c, function->func); - Constraint* previous = nullptr; - forEachConstraint( - start, - end, - this, - [&c, &previous](const ConstraintPtr& constraint) - { - if (FFlag::LuauEmplaceNotPushBack) - c->dependencies.emplace_back(constraint.get()); - else - c->dependencies.push_back(NotNull{constraint.get()}); + Constraint* previous = nullptr; + forEachConstraint( + start, + end, + this, + [&c, &previous](const ConstraintPtr& constraint) + { + if (FFlag::LuauEmplaceNotPushBack) + c->dependencies.emplace_back(constraint.get()); + else + c->dependencies.push_back(NotNull{constraint.get()}); - if (auto psc = get(*constraint); psc && psc->returns) + 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 = follow(lookup(scope, function->name->location, def)); @@ -1677,10 +1609,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f if (generalizedType == nullptr) ice->ice("generalizedType == nullptr", function->location); - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(scope, def, generalizedType); - else - scope->rvalueRefinements[def] = generalizedType; + updateRValueRefinements(scope, def, generalizedType); return ControlFlow::None; } @@ -2084,10 +2013,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareGlob DefId def = dfg->getDef(global); rootScope->lvalueTypes[def] = globalTy; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(rootScope, def, globalTy); - else - rootScope->rvalueRefinements[def] = globalTy; + updateRValueRefinements(rootScope, def, globalTy); return ControlFlow::None; } @@ -2340,10 +2266,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc DefId def = dfg->getDef(global); rootScope->lvalueTypes[def] = fnType; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(rootScope, def, fnType); - else - rootScope->rvalueRefinements[def] = fnType; + updateRValueRefinements(rootScope, def, fnType); return ControlFlow::None; } @@ -2598,10 +2521,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* DefId def = dfg->getDef(targetLocal); scope->lvalueTypes[def] = resultTy; // TODO: typestates: track this as an assignment - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(scope, def, resultTy); // TODO: typestates: track this as an assignment - else - scope->rvalueRefinements[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 // inferredBindings table. We want to replace it so that we don't @@ -2623,10 +2543,7 @@ InferencePack ConstraintGenerator::checkPack(const ScopePtr& scope, AstExprCall* if (auto def = dfg->getDefOptional(targetExpr)) { scope->lvalueTypes[*def] = resultTy; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(scope, *def, resultTy); - else - scope->rvalueRefinements[*def] = resultTy; + updateRValueRefinements(scope, *def, resultTy); } } @@ -2796,22 +2713,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin if (largeTableDepth > 0) return Inference{builtinTypes->stringType}; - TypeId freeTy = nullptr; - if (FFlag::LuauEagerGeneralization4) - { - freeTy = freshType(scope, Polarity::Positive); - FreeType* ft = getMutable(freeTy); - LUAU_ASSERT(ft); - ft->lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}}); - ft->upperBound = builtinTypes->stringType; - } - else - { - FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType}; - ft.lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}}); - ft.upperBound = builtinTypes->stringType; - freeTy = arena->addType(ft); - } + TypeId freeTy = freshType(scope, Polarity::Positive); + FreeType* ft = getMutable(freeTy); + LUAU_ASSERT(ft); + ft->lowerBound = arena->addType(SingletonType{StringSingleton{std::string{string->value.data, string->value.size}}}); + ft->upperBound = builtinTypes->stringType; addConstraint(scope, string->location, PrimitiveTypeConstraint{freeTy, expectedType, builtinTypes->stringType}); return Inference{freeTy}; @@ -2847,22 +2753,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* if (largeTableDepth > 0) return Inference{builtinTypes->booleanType}; - TypeId freeTy = nullptr; - if (FFlag::LuauEagerGeneralization4) - { - freeTy = freshType(scope, Polarity::Positive); - FreeType* ft = getMutable(freeTy); - LUAU_ASSERT(ft); - ft->lowerBound = singletonType; - ft->upperBound = builtinTypes->booleanType; - } - else - { - FreeType ft = FreeType{scope.get(), builtinTypes->neverType, builtinTypes->unknownType}; - ft.lowerBound = singletonType; - ft.upperBound = builtinTypes->booleanType; - freeTy = arena->addType(ft); - } + TypeId freeTy = freshType(scope, Polarity::Positive); + FreeType* ft = getMutable(freeTy); + LUAU_ASSERT(ft); + ft->lowerBound = singletonType; + ft->upperBound = builtinTypes->booleanType; addConstraint(scope, boolExpr->location, PrimitiveTypeConstraint{freeTy, expectedType, builtinTypes->booleanType}); return Inference{freeTy}; @@ -2960,13 +2855,10 @@ Inference ConstraintGenerator::checkIndexName( if (key) { - if (auto ty = lookup(scope, indexLocation, key->def, !FFlag::LuauDoNotPrototypeTableIndex)) + if (auto ty = lookup(scope, indexLocation, key->def, false)) return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)}; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(scope, key->def, result); - else - scope->rvalueRefinements[key->def] = result; + updateRValueRefinements(scope, key->def, result); } if (key) @@ -3000,10 +2892,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprIndexExpr* in { if (auto ty = lookup(scope, indexExpr->location, key->def)) return Inference{*ty, refinementArena.proposition(key, builtinTypes->truthyType)}; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(scope, key->def, result); - else - scope->rvalueRefinements[key->def] = result; + updateRValueRefinements(scope, key->def, result); } auto c = addConstraint(scope, indexExpr->expr->location, HasIndexerConstraint{result, obj, indexType}); @@ -3224,22 +3113,27 @@ Inference ConstraintGenerator::checkAstExprBinary( case AstExprBinary::Op::CompareEq: case AstExprBinary::Op::CompareNe: { - DefId leftDef = dfg->getDef(left); - DefId rightDef = dfg->getDef(right); - bool leftSubscripted = containsSubscriptedDefinition(leftDef); - bool rightSubscripted = containsSubscriptedDefinition(rightDef); - - if (leftSubscripted && rightSubscripted) + if (FFlag::LuauNoMoreComparisonTypeFunctions) + return Inference{builtinTypes->booleanType, std::move(refinement)}; + else { - // we cannot add nil in this case because then we will blindly accept comparisons that we should not. - } - else if (leftSubscripted) - leftType = makeUnion(scope, location, leftType, builtinTypes->nilType); - else if (rightSubscripted) - rightType = makeUnion(scope, location, rightType, builtinTypes->nilType); + DefId leftDef = dfg->getDef(left); + DefId rightDef = dfg->getDef(right); + bool leftSubscripted = containsSubscriptedDefinition(leftDef); + bool rightSubscripted = containsSubscriptedDefinition(rightDef); - TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().eqFunc, {leftType, rightType}, {}, scope, location); - return Inference{resultType, std::move(refinement)}; + if (leftSubscripted && rightSubscripted) + { + // we cannot add nil in this case because then we will blindly accept comparisons that we should not. + } + else if (leftSubscripted) + leftType = makeUnion(scope, location, leftType, builtinTypes->nilType); + else if (rightSubscripted) + rightType = makeUnion(scope, location, rightType, builtinTypes->nilType); + + TypeId resultType = createTypeFunctionInstance(builtinTypeFunctions().eqFunc, {leftType, rightType}, {}, scope, location); + return Inference{resultType, std::move(refinement)}; + } } case AstExprBinary::Op::Op__Count: ice->ice("Op__Count should never be generated in an AST."); @@ -3739,10 +3633,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu DefId def = dfg->getDef(fn->self); signatureScope->lvalueTypes[def] = selfType; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(signatureScope, def, selfType); - else - signatureScope->rvalueRefinements[def] = selfType; + updateRValueRefinements(signatureScope, def, selfType); } for (size_t i = 0; i < fn->args.size; ++i) @@ -3767,10 +3658,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu DefId def = dfg->getDef(local); signatureScope->lvalueTypes[def] = argTy; - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - updateRValueRefinements(signatureScope, def, argTy); - else - signatureScope->rvalueRefinements[def] = argTy; + updateRValueRefinements(signatureScope, def, argTy); } TypePackId varargPack = nullptr; @@ -3803,48 +3691,45 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu LUAU_ASSERT(nullptr != varargPack); - if (FFlag::LuauEagerGeneralization4) + if (FFlag::LuauEGFixGenericsList) { - if (FFlag::LuauEGFixGenericsList) - { - // Some of the unannotated parameters in argTypes will eventually be - // generics, and some will not. The ones that are not generic will be - // pruned when GeneralizationConstraint dispatches. - - // The self parameter never has an annotation and so could always become generic. - if (fn->self) - genericTypes.push_back(argTypes[0]); + // Some of the unannotated parameters in argTypes will eventually be + // generics, and some will not. The ones that are not generic will be + // pruned when GeneralizationConstraint dispatches. - size_t typeIndex = fn->self ? 1 : 0; - for (auto astArg : fn->args) - { - TypeId argTy = argTypes.at(typeIndex); - if (!astArg->annotation) - genericTypes.push_back(argTy); + // The self parameter never has an annotation and so could always become generic. + if (fn->self) + genericTypes.push_back(argTypes[0]); - ++typeIndex; - } + size_t typeIndex = fn->self ? 1 : 0; + for (auto astArg : fn->args) + { + TypeId argTy = argTypes.at(typeIndex); + if (!astArg->annotation) + genericTypes.push_back(argTy); - varargPack = follow(varargPack); - returnType = follow(returnType); - if (varargPack == returnType) - genericTypePacks = {varargPack}; - else - genericTypePacks = {varargPack, returnType}; + ++typeIndex; } + + varargPack = follow(varargPack); + returnType = follow(returnType); + if (varargPack == returnType) + genericTypePacks = {varargPack}; else - { - // Some of the types in argTypes will eventually be generics, and some - // will not. The ones that are not generic will be pruned when - // GeneralizationConstraint dispatches. - genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end()); - varargPack = follow(varargPack); - returnType = follow(returnType); - if (varargPack == returnType) - genericTypePacks = {varargPack}; - else - genericTypePacks = {varargPack, returnType}; - } + genericTypePacks = {varargPack, returnType}; + } + else + { + // Some of the types in argTypes will eventually be generics, and some + // will not. The ones that are not generic will be pruned when + // GeneralizationConstraint dispatches. + genericTypes.insert(genericTypes.begin(), argTypes.begin(), argTypes.end()); + varargPack = follow(varargPack); + returnType = follow(returnType); + if (varargPack == returnType) + genericTypePacks = {varargPack}; + else + genericTypePacks = {varargPack, returnType}; } // If there is both an annotation and an expected type, the annotation wins. @@ -3888,8 +3773,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu if (expectedType && get(*expectedType)) bindFreeType(*expectedType, actualFunctionType); - if (FFlag::LuauEagerGeneralization4) - scopeToFunction[signatureScope.get()] = actualFunctionType; + scopeToFunction[signatureScope.get()] = actualFunctionType; return { /* signature */ actualFunctionType, diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 04ac5d06..e9de78d4 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -37,11 +37,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackUniqueness) -LUAU_FASTFLAG(LuauAvoidExcessiveTypeCopying) LUAU_FASTFLAG(LuauLimitUnification) -LUAU_FASTFLAGVARIABLE(LuauForceSimplifyConstraint2) LUAU_FASTFLAGVARIABLE(LuauCollapseShouldNotCrash) LUAU_FASTFLAGVARIABLE(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAGVARIABLE(LuauLimitDynamicConstraintSolving3) @@ -481,13 +478,10 @@ void ConstraintSolver::run() } // Free types that have no constraints at all can be generalized right away. - if (FFlag::LuauEagerGeneralization4) + for (TypeId ty : constraintSet.freeTypes) { - for (TypeId ty : constraintSet.freeTypes) - { - if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty()) - generalizeOneType(ty); - } + if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty()) + generalizeOneType(ty); } constraintSet.freeTypes.clear(); @@ -546,12 +540,9 @@ void ConstraintSolver::run() // expansion types, etc, so we need to follow it. ty = follow(ty); - if (FFlag::LuauEagerGeneralization4) - { - if (seen.contains(ty)) - continue; - seen.insert(ty); - } + if (seen.contains(ty)) + continue; + seen.insert(ty); size_t& refCount = unresolvedConstraints[ty]; if (refCount > 0) @@ -565,7 +556,7 @@ void ConstraintSolver::run() if (refCount <= 1) unblock(ty, Location{}); - if (FFlag::LuauEagerGeneralization4 && refCount == 0) + if (refCount == 0) generalizeOneType(ty); } } @@ -745,11 +736,8 @@ void ConstraintSolver::initFreeTypeTracking() auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); refCount += 1; - if (FFlag::LuauEagerGeneralization4) - { - auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty); - it->second.insert(c.get()); - } + auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty); + it->second.insert(c.get()); } maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); @@ -801,8 +789,7 @@ void ConstraintSolver::bind(NotNull constraint, TypeId ty, Typ constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed ); // FIXME? Is this the right polarity? - if (FFlag::LuauEagerGeneralization4) - trackInteriorFreeType(constraint->scope, ty); + trackInteriorFreeType(constraint->scope, ty); return; } @@ -970,61 +957,50 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullscope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) { - if (FFlag::LuauEagerGeneralization4) + ty = follow(ty); + if (auto freeTy = get(ty)) { - ty = follow(ty); - if (auto freeTy = get(ty)) - { - GeneralizationParams params; - params.foundOutsideFunctions = true; - params.useCount = 1; - params.polarity = freeTy->polarity; - GeneralizationResult res = generalizeType(arena, builtinTypes, constraint->scope, ty, params); - if (res.resourceLimitsExceeded) - reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this. - } - else if (get(ty)) - sealTable(constraint->scope, ty); + GeneralizationParams params; + params.foundOutsideFunctions = true; + params.useCount = 1; + params.polarity = freeTy->polarity; + GeneralizationResult res = generalizeType(arena, builtinTypes, constraint->scope, ty, params); + if (res.resourceLimitsExceeded) + reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this. } - else - generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty); + else if (get(ty)) + sealTable(constraint->scope, ty); } } - if (FFlag::LuauEagerGeneralization4) + if (constraint->scope->interiorFreeTypePacks) { - if (constraint->scope->interiorFreeTypePacks) + for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access) { - for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access) + tp = follow(tp); + if (auto freeTp = get(tp)) { - tp = follow(tp); - if (auto freeTp = get(tp)) - { - GeneralizationParams params; - params.foundOutsideFunctions = true; - params.useCount = 1; - params.polarity = freeTp->polarity; - LUAU_ASSERT(isKnown(params.polarity)); - generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params); - } + GeneralizationParams params; + params.foundOutsideFunctions = true; + params.useCount = 1; + params.polarity = freeTp->polarity; + LUAU_ASSERT(isKnown(params.polarity)); + generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params); } } } - if (FFlag::LuauEagerGeneralization4) + if (c.noGenerics) { - if (c.noGenerics) + if (auto ft = getMutable(c.sourceType)) { - if (auto ft = getMutable(c.sourceType)) - { - for (TypeId gen : ft->generics) - asMutable(gen)->ty.emplace(builtinTypes->unknownType); - ft->generics.clear(); + for (TypeId gen : ft->generics) + asMutable(gen)->ty.emplace(builtinTypes->unknownType); + ft->generics.clear(); - for (TypePackId gen : ft->genericPacks) - asMutable(gen)->ty.emplace(builtinTypes->unknownTypePack); - ft->genericPacks.clear(); - } + for (TypePackId gen : ft->genericPacks) + asMutable(gen)->ty.emplace(builtinTypes->unknownTypePack); + ft->genericPacks.clear(); } } @@ -1446,18 +1422,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull(fn)) { @@ -1594,13 +1560,10 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullscope, freeTy); - for (TypePackId freeTp : u2.newFreshTypePacks) - trackInteriorFreeTypePack(constraint->scope, freeTp); - } + for (TypeId freeTy : u2.newFreshTypes) + trackInteriorFreeType(constraint->scope, freeTy); + for (TypePackId freeTp : u2.newFreshTypePacks) + trackInteriorFreeTypePack(constraint->scope, freeTp); if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty()) { @@ -1702,48 +1665,6 @@ struct ContainsGenerics_DEPRECATED : public TypeOnceVisitor namespace { -struct ReferentialReplacer : Substitution -{ - NotNull> replacements; - NotNull> replacementPacks; - - ReferentialReplacer( - NotNull arena, - NotNull> replacements, - NotNull> replacementPacks - ) - : Substitution(TxnLog::empty(), arena) - , replacements(std::move(replacements)) - , replacementPacks(std::move(replacementPacks)) - { - } - - bool isDirty(TypeId ty) override - { - return replacements->find(ty) != nullptr; - } - - bool isDirty(TypePackId tp) override - { - return replacementPacks->find(tp) != nullptr; - } - - TypeId clean(TypeId ty) override - { - TypeId res = (*replacements)[ty]; - LUAU_ASSERT(res); - dontTraverseInto(res); - return res; - } - - TypePackId clean(TypePackId tp) override - { - TypePackId res = (*replacementPacks)[tp]; - LUAU_ASSERT(res); - dontTraverseInto(res); - return res; - } -}; struct ContainsGenerics : public TypeOnceVisitor { NotNull> generics; @@ -1854,6 +1775,8 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull expectedArgs = flatten(ftv->argTypes).first; const std::vector argPackHead = flatten(argsPack).first; + Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)}; + // If this is a self call, the types will have more elements than the AST call. // We don't attempt to perform bidirectional inference on the self type. const size_t typeOffset = c.callSite->self ? 1 : 0; @@ -1892,7 +1815,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullscope, constraint->location, this}; @@ -2077,19 +2000,12 @@ bool ConstraintSolver::tryDispatchHasIndexer( TypeId upperBound = arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed}); - if (FFlag::LuauEagerGeneralization4) - { - TypeId sr = follow(simplifyIntersection(constraint->scope, constraint->location, ft->upperBound, upperBound)); + TypeId sr = follow(simplifyIntersection(constraint->scope, constraint->location, ft->upperBound, upperBound)); - if (get(sr)) - bind(constraint, resultType, builtinTypes->errorType); - else - ft->upperBound = sr; - } + if (get(sr)) + bind(constraint, resultType, builtinTypes->errorType); else - { - unify(constraint, subjectType, upperBound); - } + ft->upperBound = sr; return true; } @@ -2108,8 +2024,7 @@ bool ConstraintSolver::tryDispatchHasIndexer( FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed}; emplace(constraint, resultType, freeResult); - if (FFlag::LuauEagerGeneralization4) - trackInteriorFreeType(constraint->scope, resultType); + trackInteriorFreeType(constraint->scope, resultType); tt->indexer = TableIndexer{indexType, resultType}; return true; @@ -2301,60 +2216,35 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNullupperBound); - if (FFlag::LuauEagerGeneralization4) + const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue); + if (!blocked.empty()) { - const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue); - if (!blocked.empty()) - { - for (TypeId t : blocked) - block(t, constraint); - return false; - } - else if (maybeTy) - { - bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{*maybeTy, builtinTypes->nilType}}) : *maybeTy); - unify(constraint, rhsType, *maybeTy); - return true; - } - else - { - TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); - - trackInteriorFreeType(constraint->scope, newUpperBound); - - TableType* upperTable = getMutable(newUpperBound); - LUAU_ASSERT(upperTable); - - upperTable->props[c.propName] = rhsType; - - // Food for thought: Could we block if simplification encounters a blocked type? - lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound); - - bind(constraint, c.propType, rhsType); - return true; - } + for (TypeId t : blocked) + block(t, constraint); + return false; + } + else if (maybeTy) + { + bind(constraint, c.propType, isIndex ? arena->addType(UnionType{{*maybeTy, builtinTypes->nilType}}) : *maybeTy); + unify(constraint, rhsType, *maybeTy); + return true; } else { - if (get(lhsFreeUpperBound) || get(lhsFreeUpperBound)) - lhsType = lhsFreeUpperBound; - else - { - TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); + TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); - trackInteriorFreeType(constraint->scope, newUpperBound); + trackInteriorFreeType(constraint->scope, newUpperBound); - TableType* upperTable = getMutable(newUpperBound); - LUAU_ASSERT(upperTable); + TableType* upperTable = getMutable(newUpperBound); + LUAU_ASSERT(upperTable); - upperTable->props[c.propName] = rhsType; + upperTable->props[c.propName] = rhsType; - // Food for thought: Could we block if simplification encounters a blocked type? - lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound); + // Food for thought: Could we block if simplification encounters a blocked type? + lhsFree->upperBound = simplifyIntersection(constraint->scope, constraint->location, lhsFreeUpperBound, newUpperBound); - bind(constraint, c.propType, rhsType); - return true; - } + bind(constraint, c.propType, rhsType); + return true; } } @@ -2825,14 +2715,7 @@ bool ConstraintSolver::tryDispatch(const SimplifyConstraint& c, NotNullscope, constraint->location, result, ty); } - if (FFlag::LuauForceSimplifyConstraint2) + // If we forced, then there _may_ be blocked types, and we should + // include those in the union as well. + for (TypeId ty : finder.blockedTys) { - // If we forced, then there _may_ be blocked types, and we should - // include those in the union as well. - for (TypeId ty : finder.blockedTys) - { - ty = follow(ty); - if (ty == target) - continue; - result = simplifyUnion(constraint->scope, constraint->location, result, ty); - } + ty = follow(ty); + if (ty == target) + continue; + result = simplifyUnion(constraint->scope, constraint->location, result, ty); } emplaceType(asMutable(target), result); return true; @@ -3742,7 +3622,7 @@ bool ConstraintSolver::isBlocked(TypeId ty) const if (auto tfit = get(ty)) { - if (FFlag::LuauEagerGeneralization4 && tfit->state != TypeFunctionInstanceState::Unsolved) + if (tfit->state != TypeFunctionInstanceState::Unsolved) return false; return uninhabitedTypeFunctions.contains(ty) == false; } @@ -3870,18 +3750,15 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target) if (!isReferenceCountedType(target)) return; - if (FFlag::LuauForceSimplifyConstraint2) - { - // This can happen in the _very_ specific case of: - // - // local Tbl = {} - // Tbl.__index = Tbl - // - // This would probably not be required if table type stating worked in - // a reasonable manner. - if (source == target) - return; - } + // This can happen in the _very_ specific case of: + // + // local Tbl = {} + // Tbl.__index = Tbl + // + // This would probably not be required if table type stating worked in + // a reasonable manner. + if (source == target) + return; auto sourceRefs = unresolvedConstraints.find(source); if (sourceRefs) @@ -3894,22 +3771,18 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target) } // Any constraint that might have mutated source may now mutate target - - if (FFlag::LuauEagerGeneralization4) + if (auto it = mutatedFreeTypeToConstraint.find(source); it != mutatedFreeTypeToConstraint.end()) { - if (auto it = mutatedFreeTypeToConstraint.find(source); it != mutatedFreeTypeToConstraint.end()) - { - const OrderedSet& constraintsAffectedBySource = it->second; - auto [it2, fresh2] = mutatedFreeTypeToConstraint.try_emplace(target); + const OrderedSet& constraintsAffectedBySource = it->second; + auto [it2, fresh2] = mutatedFreeTypeToConstraint.try_emplace(target); - OrderedSet& constraintsAffectedByTarget = it2->second; + OrderedSet& constraintsAffectedByTarget = it2->second; - for (const Constraint* constraint : constraintsAffectedBySource) - { - constraintsAffectedByTarget.insert(constraint); - auto [it3, fresh3] = maybeMutatedFreeTypes.try_emplace(NotNull{constraint}, TypeIds{}); - it3->second.insert(target); - } + for (const Constraint* constraint : constraintsAffectedBySource) + { + constraintsAffectedByTarget.insert(constraint); + auto [it3, fresh3] = maybeMutatedFreeTypes.try_emplace(NotNull{constraint}, TypeIds{}); + it3->second.insert(target); } } } diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index 90ec37b5..9dbcfd85 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -13,7 +13,6 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) namespace Luau { @@ -872,16 +871,14 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprLocal* l) { DefId def = lookup(l->local, l->local->location); const RefinementKey* key = keyArena->leaf(def); - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - graph.defToSymbol[def] = l->local; + graph.defToSymbol[def] = l->local; return {def, key}; } DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprGlobal* g) { DefId def = lookup(g->name, g->location); - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - graph.defToSymbol[def] = g->name; + graph.defToSymbol[def] = g->name; return {def, keyArena->leaf(def)}; } diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index bafd7303..762720fe 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -17,10 +17,10 @@ #include LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictReportsOneIndexedErrors) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) static std::string wrongNumberOfArgsString( size_t expectedCount, @@ -72,22 +72,6 @@ namespace Luau { // this list of binary operator type functions is used for better stringification of type functions errors -static const std::unordered_map DEPRECATED_kBinaryOps{ - {"add", "+"}, - {"sub", "-"}, - {"mul", "*"}, - {"div", "/"}, - {"idiv", "//"}, - {"pow", "^"}, - {"mod", "%"}, - {"concat", ".."}, - {"and", "and"}, - {"or", "or"}, - {"lt", "< or >="}, - {"le", "<= or >"}, - {"eq", "== or ~="} -}; - static const std::unordered_map kBinaryOps{ {"add", "+"}, {"sub", "-"}, @@ -107,7 +91,6 @@ static const std::unordered_map kUnaryOps{{"unm", "-"} // this list of type functions will receive a special error indicating that the user should file a bug on the GitHub repository // putting a type function in this list indicates that it is expected to _always_ reduce -static const std::unordered_set DEPRECATED_kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect"}; static const std::unordered_set kUnreachableTypeFunctions{"refine", "singleton", "union", "intersect", "and", "or"}; struct ErrorConverter @@ -230,6 +213,11 @@ struct ErrorConverter return ""; } + std::string operator()(const Luau::CannotCompareUnrelatedTypes& e) const + { + return "Cannot compare unrelated types '" + toString(e.left) + "' and '" + toString(e.right) + "' with '" + toString(e.op) + "'"; + } + std::string operator()(const Luau::OnlyTablesCanHaveMethods& e) const { return "Cannot add method to non-table type '" + Luau::toString(e.tableType) + "'"; @@ -673,8 +661,7 @@ struct ErrorConverter } // binary operators - const auto binaryOps = FFlag::LuauEagerGeneralization4 ? kBinaryOps : DEPRECATED_kBinaryOps; - if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end()) + if (auto binaryString = kBinaryOps.find(tfit->function->name); binaryString != kBinaryOps.end()) { std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types "; @@ -728,7 +715,7 @@ struct ErrorConverter "'"; } - if ((FFlag::LuauEagerGeneralization4 ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name)) + if (kUnreachableTypeFunctions.count(tfit->function->name)) { return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" + "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; @@ -898,23 +885,25 @@ struct ErrorConverter std::string operator()(const GenericBoundsMismatch& e) const { + LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches2); std::string lowerBounds; for (size_t i = 0; i < e.lowerBounds.size(); ++i) { if (i > 0) - lowerBounds += ", "; + lowerBounds += " | "; lowerBounds += Luau::toString(e.lowerBounds[i]); } std::string upperBounds; for (size_t i = 0; i < e.upperBounds.size(); ++i) { if (i > 0) - upperBounds += ", "; + upperBounds += " & "; upperBounds += Luau::toString(e.upperBounds[i]); } - return "The generic type parameter " + std::string{e.genericName} + "was found to have invalid bounds. Its lower bounds were [" + - lowerBounds + "], and its upper bounds were [" + upperBounds + "]."; + return "No valid instantiation could be inferred for generic type parameter " + std::string{e.genericName} + + ". It was expected to be at least:\n\t" + lowerBounds + "\nand at most:\n\t" + upperBounds + + "\nbut these types are not compatible with one another."; } std::string operator()(const UnappliedTypeFunction&) const @@ -1028,6 +1017,11 @@ bool CannotExtendTable::operator==(const CannotExtendTable& rhs) const return *tableType == *rhs.tableType && prop == rhs.prop && context == rhs.context; } +bool CannotCompareUnrelatedTypes::operator==(const CannotCompareUnrelatedTypes& rhs) const +{ + return *left == *rhs.left && right == rhs.right && op == rhs.op; +} + bool OnlyTablesCanHaveMethods::operator==(const OnlyTablesCanHaveMethods& rhs) const { return *tableType == *rhs.tableType; @@ -1329,10 +1323,12 @@ GenericBoundsMismatch::GenericBoundsMismatch(const std::string_view genericName, , lowerBounds(lowerBoundSet.take()) , upperBounds(upperBoundSet.take()) { + LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches2); } bool GenericBoundsMismatch::operator==(const GenericBoundsMismatch& rhs) const { + LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches2); return genericName == rhs.genericName && lowerBounds == rhs.lowerBounds && upperBounds == rhs.upperBounds; } @@ -1396,6 +1392,11 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) { e.tableType = clone(e.tableType); } + else if constexpr (std::is_same_v) + { + e.left = clone(e.left); + e.right = clone(e.right); + } else if constexpr (std::is_same_v) { e.tableType = clone(e.tableType); @@ -1569,6 +1570,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) } else if constexpr (std::is_same_v) { + LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches2); for (auto& lowerBound : e.lowerBounds) lowerBound = clone(lowerBound); for (auto& upperBound : e.upperBounds) diff --git a/Analysis/src/ExpectedTypeVisitor.cpp b/Analysis/src/ExpectedTypeVisitor.cpp index e1bebaaf..91a89eb3 100644 --- a/Analysis/src/ExpectedTypeVisitor.cpp +++ b/Analysis/src/ExpectedTypeVisitor.cpp @@ -8,8 +8,6 @@ #include "Luau/TypeUtils.h" #include "Luau/VisitType.h" -LUAU_FASTFLAGVARIABLE(LuauImplicitTableIndexerKeys3) - namespace Luau { @@ -149,9 +147,6 @@ struct IndexCollector : public TypeOnceVisitor bool ExpectedTypeVisitor::visit(AstExprIndexExpr* expr) { - if (!FFlag::LuauImplicitTableIndexerKeys3) - return true; - if (auto ty = astTypes->find(expr->expr)) { IndexCollector ic{arena}; diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index c25630f6..53f0696b 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -33,7 +33,6 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAGVARIABLE(DebugLogFragmentsFromAutocomplete) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) LUAU_FASTFLAGVARIABLE(LuauFragmentRequiresCanBeResolvedToAModule) -LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAGVARIABLE(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAGVARIABLE(LuauForInProvidesRecommendations) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteTakesInnermostRefinement) @@ -606,14 +605,9 @@ struct UsageFinder : public AstVisitor mentionedDefs.insert(ref->def); if (auto local = expr->as()) { - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - { - auto def = dfg->getDef(local); - localBindingsReferenced.emplace_back(def, local->local); - symbolsToRefine.emplace_back(def, Symbol(local->local)); - } - else - localBindingsReferenced.emplace_back(dfg->getDef(local), local->local); + auto def = dfg->getDef(local); + localBindingsReferenced.emplace_back(def, local->local); + symbolsToRefine.emplace_back(def, Symbol(local->local)); } return true; } @@ -621,11 +615,8 @@ struct UsageFinder : public AstVisitor bool visit(AstExprGlobal* global) override { globalDefsToPrePopulate.emplace_back(global->name, dfg->getDef(global)); - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) - { - auto def = dfg->getDef(global); - symbolsToRefine.emplace_back(def, Symbol(global->name)); - } + auto def = dfg->getDef(global); + symbolsToRefine.emplace_back(def, Symbol(global->name)); return true; } @@ -691,44 +682,27 @@ void cloneTypesFromFragment( } } - if (FFlag::LuauFragmentAutocompleteTracksRValueRefinements) + for (const auto& [d, syms] : f.symbolsToRefine) { - for (const auto& [d, syms] : f.symbolsToRefine) + for (const Scope* stale = staleScope; stale; stale = stale->parent.get()) { - for (const Scope* stale = staleScope; stale; stale = stale->parent.get()) + if (auto res = stale->refinements.find(syms); res != stale->refinements.end()) { - if (auto res = stale->refinements.find(syms); res != stale->refinements.end()) - { - destScope->rvalueRefinements[d] = Luau::cloneIncremental(res->second, *destArena, cloneState, destScope); - // If we've found a refinement, just break, otherwise we might end up doing the wrong thing for: - // - // type TaggedUnion = { tag: 'a', value: number } | { tag: 'b', value: string } - // local function foobar(obj: TaggedUnion?) - // if obj then - // if obj.tag == 'a' - // obj.| -- We want the most "narrow" refinement here. - // end - // end - // end - // - // We could find another binding for `syms` and then set _that_. - if (FFlag::LuauFragmentAutocompleteTakesInnermostRefinement) - break; - } - } - } - } - else if (!staleModule->checkedInNewSolver) - { - for (const auto& [d, loc] : f.localBindingsReferenced) - { - for (const Scope* stale = staleScope; stale; stale = stale->parent.get()) - { - if (auto res = stale->refinements.find(Symbol(loc)); res != stale->refinements.end()) - { - destScope->rvalueRefinements[d] = Luau::cloneIncremental(res->second, *destArena, cloneState, destScope); + destScope->rvalueRefinements[d] = Luau::cloneIncremental(res->second, *destArena, cloneState, destScope); + // If we've found a refinement, just break, otherwise we might end up doing the wrong thing for: + // + // type TaggedUnion = { tag: 'a', value: number } | { tag: 'b', value: string } + // local function foobar(obj: TaggedUnion?) + // if obj then + // if obj.tag == 'a' + // obj.| -- We want the most "narrow" refinement here. + // end + // end + // end + // + // We could find another binding for `syms` and then set _that_. + if (FFlag::LuauFragmentAutocompleteTakesInnermostRefinement) break; - } } } } diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 1982aa92..5d48801b 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -35,14 +35,12 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(LuauUseWorkspacePropToChooseSolver) -LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAGVARIABLE(DebugLuauAlwaysShowConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTFLAG(LuauEmplaceNotPushBack) @@ -1493,63 +1491,30 @@ ModulePtr check( requireCycles }; - // FIXME: Unwrap this std::option when clipping FFlag::LuauEagerGeneralization4. - // - // This optional<> only exists so that we can run one constructor when the flag - // is set, and another when it is unset. - std::optional cs; - - if (FFlag::LuauEagerGeneralization4) - { - ConstraintSet constraintSet = cg.run(sourceModule.root); - module->errors = std::move(constraintSet.errors); - if (FFlag::LuauNoConstraintGenRecursionLimitIce) - module->constraintGenerationDidNotComplete = cg.recursionLimitMet; - - cs.emplace( - NotNull{&normalizer}, - NotNull{simplifier.get()}, - NotNull{&typeFunctionRuntime}, - module, - moduleResolver, - requireCycles, - logger.get(), - NotNull{&dfg}, - limits, - std::move(constraintSet) - ); - } - else - { - cg.visitModuleRoot(sourceModule.root); - module->errors = std::move(cg.errors); - if (FFlag::LuauNoConstraintGenRecursionLimitIce) - module->constraintGenerationDidNotComplete = cg.recursionLimitMet; - - cs.emplace( - NotNull{&normalizer}, - NotNull{simplifier.get()}, - NotNull{&typeFunctionRuntime}, - NotNull(cg.rootScope), - borrowConstraints(cg.constraints), - NotNull{&cg.scopeToFunction}, - module, - moduleResolver, - requireCycles, - logger.get(), - NotNull{&dfg}, - limits - ); - } + ConstraintSet constraintSet = cg.run(sourceModule.root); + module->errors = std::move(constraintSet.errors); + if (FFlag::LuauNoConstraintGenRecursionLimitIce) + module->constraintGenerationDidNotComplete = cg.recursionLimitMet; - LUAU_ASSERT(bool(cs)); + ConstraintSolver cs{ + NotNull{&normalizer}, + NotNull{simplifier.get()}, + NotNull{&typeFunctionRuntime}, + module, + moduleResolver, + requireCycles, + logger.get(), + NotNull{&dfg}, + limits, + std::move(constraintSet) + }; if (options.randomizeConstraintResolutionSeed) - cs->randomize(*options.randomizeConstraintResolutionSeed); + cs.randomize(*options.randomizeConstraintResolutionSeed); try { - cs->run(); + cs.run(); } catch (const TimeLimitError&) { @@ -1560,7 +1525,7 @@ ModulePtr check( module->cancelled = true; } - stats.dynamicConstraintsCreated += cs->solverConstraints.size(); + stats.dynamicConstraintsCreated += cs.solverConstraints.size(); if (recordJsonLog) { @@ -1571,12 +1536,12 @@ ModulePtr check( printf("%s\n", output.c_str()); } - for (TypeError& e : cs->errors) + for (TypeError& e : cs.errors) module->errors.emplace_back(std::move(e)); module->scopes = std::move(cg.scopes); module->type = sourceModule.type; - module->upperBoundContributors = std::move(cs->upperBoundContributors); + module->upperBoundContributors = std::move(cs.upperBoundContributors); if (module->timeout || module->cancelled) { @@ -1641,12 +1606,9 @@ ModulePtr check( // if the only error we're producing is one about constraint solving being incomplete, we can silence it. // this means we won't give this warning if types seem totally nonsensical, but there are no other errors. // this is probably, on the whole, a good decision to not annoy users though. - if (FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) - { - if (module->errors.size() == 1 && get(module->errors[0]) && - !FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete) - module->errors.clear(); - } + if (module->errors.size() == 1 && get(module->errors[0]) && + !FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete) + module->errors.clear(); ExpectedTypeVisitor etv{ NotNull{&module->astTypes}, diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index 1f246f1f..58e91814 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -15,7 +15,6 @@ #include "Luau/TypePack.h" #include "Luau/VisitType.h" -LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauReduceSetTypeStackPressure) LUAU_FASTINTVARIABLE(LuauGenericCounterMaxDepth, 15) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) @@ -23,314 +22,6 @@ LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau { -struct MutatingGeneralizer : TypeOnceVisitor -{ - NotNull arena; - NotNull builtinTypes; - - NotNull scope; - NotNull> cachedTypes; - DenseHashMap positiveTypes; - DenseHashMap negativeTypes; - std::vector generics; - std::vector genericPacks; - - bool isWithinFunction = false; - - MutatingGeneralizer( - NotNull arena, - NotNull builtinTypes, - NotNull scope, - NotNull> cachedTypes, - DenseHashMap positiveTypes, - DenseHashMap negativeTypes - ) - : TypeOnceVisitor("MutatingGeneralizer", /* skipBoundTypes */ true) - , arena(arena) - , builtinTypes(builtinTypes) - , scope(scope) - , cachedTypes(cachedTypes) - , positiveTypes(std::move(positiveTypes)) - , negativeTypes(std::move(negativeTypes)) - { - } - - void replace(DenseHashSet& seen, TypeId haystack, TypeId needle, TypeId replacement) - { - haystack = follow(haystack); - - if (seen.find(haystack)) - return; - seen.insert(haystack); - - if (UnionType* ut = getMutable(haystack)) - { - for (auto iter = ut->options.begin(); iter != ut->options.end();) - { - // FIXME: I bet this function has reentrancy problems - TypeId option = follow(*iter); - - if (option == needle && get(replacement)) - { - iter = ut->options.erase(iter); - continue; - } - - if (option == needle) - { - *iter = replacement; - iter++; - continue; - } - - // advance the iterator, nothing after this can use it. - iter++; - - if (seen.find(option)) - continue; - seen.insert(option); - - if (get(option)) - replace(seen, option, needle, haystack); - else if (get(option)) - replace(seen, option, needle, haystack); - } - - if (ut->options.size() == 1) - { - TypeId onlyType = ut->options[0]; - LUAU_ASSERT(onlyType != haystack); - emplaceType(asMutable(haystack), onlyType); - } - else if (ut->options.empty()) - { - emplaceType(asMutable(haystack), builtinTypes->neverType); - } - - return; - } - - if (IntersectionType* it = getMutable(needle)) - { - for (auto iter = it->parts.begin(); iter != it->parts.end();) - { - // FIXME: I bet this function has reentrancy problems - TypeId part = follow(*iter); - - if (part == needle && get(replacement)) - { - iter = it->parts.erase(iter); - continue; - } - - if (part == needle) - { - *iter = replacement; - iter++; - continue; - } - - // advance the iterator, nothing after this can use it. - iter++; - - if (seen.find(part)) - continue; - seen.insert(part); - - if (get(part)) - replace(seen, part, needle, haystack); - else if (get(part)) - replace(seen, part, needle, haystack); - } - - if (it->parts.size() == 1) - { - TypeId onlyType = it->parts[0]; - LUAU_ASSERT(onlyType != needle); - emplaceType(asMutable(needle), onlyType); - } - else if (it->parts.empty()) - { - emplaceType(asMutable(needle), builtinTypes->unknownType); - } - - return; - } - } - - bool visit(TypeId ty, const FunctionType& ft) override - { - if (cachedTypes->contains(ty)) - return false; - - const bool oldValue = isWithinFunction; - - isWithinFunction = true; - - traverse(ft.argTypes); - traverse(ft.retTypes); - - isWithinFunction = oldValue; - - return false; - } - - bool visit(TypeId ty, const FreeType&) override - { - LUAU_ASSERT(!cachedTypes->contains(ty)); - - const FreeType* ft = get(ty); - LUAU_ASSERT(ft); - - traverse(ft->lowerBound); - traverse(ft->upperBound); - - // It is possible for the above traverse() calls to cause ty to be - // transmuted. We must reacquire ft if this happens. - ty = follow(ty); - ft = get(ty); - if (!ft) - return false; - - const size_t positiveCount = getCount(positiveTypes, ty); - const size_t negativeCount = getCount(negativeTypes, ty); - - if (!positiveCount && !negativeCount) - return false; - - const bool hasLowerBound = !get(follow(ft->lowerBound)); - const bool hasUpperBound = !get(follow(ft->upperBound)); - - DenseHashSet seen{nullptr}; - seen.insert(ty); - - if (!hasLowerBound && !hasUpperBound) - { - if (!isWithinFunction || (positiveCount + negativeCount == 1)) - emplaceType(asMutable(ty), builtinTypes->unknownType); - else - { - emplaceType(asMutable(ty), scope); - generics.push_back(ty); - } - } - - // It is possible that this free type has other free types in its upper - // or lower bounds. If this is the case, we must replace those - // references with never (for the lower bound) or unknown (for the upper - // bound). - // - // If we do not do this, we get tautological bounds like a <: a <: unknown. - else if (positiveCount && !hasUpperBound) - { - TypeId lb = follow(ft->lowerBound); - if (FreeType* lowerFree = getMutable(lb); lowerFree && lowerFree->upperBound == ty) - lowerFree->upperBound = builtinTypes->unknownType; - else - { - DenseHashSet replaceSeen{nullptr}; - replace(replaceSeen, lb, ty, builtinTypes->unknownType); - } - - if (lb != ty) - emplaceType(asMutable(ty), lb); - else if (!isWithinFunction || (positiveCount + negativeCount == 1)) - emplaceType(asMutable(ty), builtinTypes->unknownType); - else - { - // if the lower bound is the type in question, we don't actually have a lower bound. - emplaceType(asMutable(ty), scope); - generics.push_back(ty); - } - } - else - { - TypeId ub = follow(ft->upperBound); - if (FreeType* upperFree = getMutable(ub); upperFree && upperFree->lowerBound == ty) - upperFree->lowerBound = builtinTypes->neverType; - else - { - DenseHashSet replaceSeen{nullptr}; - replace(replaceSeen, ub, ty, builtinTypes->neverType); - } - - if (ub != ty) - emplaceType(asMutable(ty), ub); - else if (!isWithinFunction || (positiveCount + negativeCount == 1)) - emplaceType(asMutable(ty), builtinTypes->unknownType); - else - { - // if the upper bound is the type in question, we don't actually have an upper bound. - emplaceType(asMutable(ty), scope); - generics.push_back(ty); - } - } - - return false; - } - - size_t getCount(const DenseHashMap& map, const void* ty) - { - if (const size_t* count = map.find(ty)) - return *count; - else - return 0; - } - - template - static size_t getCount(const DenseHashMap& map, TID ty) - { - if (const size_t* count = map.find(ty)) - return *count; - else - return 0; - } - - bool visit(TypeId ty, const TableType&) override - { - if (cachedTypes->contains(ty)) - return false; - - const size_t positiveCount = getCount(positiveTypes, ty); - const size_t negativeCount = getCount(negativeTypes, ty); - - // FIXME: Free tables should probably just be replaced by upper bounds on free types. - // - // eg never <: 'a <: {x: number} & {z: boolean} - - if (!positiveCount && !negativeCount) - return true; - - TableType* tt = getMutable(ty); - LUAU_ASSERT(tt); - - tt->state = TableState::Sealed; - - return true; - } - - bool visit(TypePackId tp, const FreeTypePack& ftp) override - { - if (!subsumes(scope, ftp.scope)) - return true; - - tp = follow(tp); - - const size_t positiveCount = getCount(positiveTypes, tp); - const size_t negativeCount = getCount(negativeTypes, tp); - - if (1 == positiveCount + negativeCount) - emplaceTypePack(asMutable(tp), builtinTypes->unknownTypePack); - else - { - emplaceTypePack(asMutable(tp), scope); - genericPacks.push_back(tp); - } - - return true; - } -}; - struct FreeTypeSearcher : TypeVisitor { NotNull scope; @@ -409,46 +100,19 @@ struct FreeTypeSearcher : TypeVisitor bool visit(TypeId ty, const FreeType& ft) override { - if (FFlag::LuauEagerGeneralization4) - { - if (!subsumes(scope, ft.scope)) - return true; - - GeneralizationParams& params = types[ty]; - ++params.useCount; - - if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty)) - return false; + if (!subsumes(scope, ft.scope)) + return true; - if (!isWithinFunction) - params.foundOutsideFunctions = true; + GeneralizationParams& params = types[ty]; + ++params.useCount; - params.polarity |= polarity; - } - else - { - if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty)) - return false; + if (cachedTypes->contains(ty) || seenWithCurrentPolarity(ty)) + return false; - if (!subsumes(scope, ft.scope)) - return true; + if (!isWithinFunction) + params.foundOutsideFunctions = true; - switch (polarity) - { - case Polarity::Positive: - positiveTypes[ty]++; - break; - case Polarity::Negative: - negativeTypes[ty]++; - break; - case Polarity::Mixed: - positiveTypes[ty]++; - negativeTypes[ty]++; - break; - default: - LUAU_ASSERT(!"Unreachable"); - } - } + params.polarity |= polarity; return true; } @@ -459,28 +123,7 @@ struct FreeTypeSearcher : TypeVisitor return false; if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) - { - if (FFlag::LuauEagerGeneralization4) - unsealedTables.insert(ty); - else - { - switch (polarity) - { - case Polarity::Positive: - positiveTypes[ty]++; - break; - case Polarity::Negative: - negativeTypes[ty]++; - break; - case Polarity::Mixed: - positiveTypes[ty]++; - negativeTypes[ty]++; - break; - default: - LUAU_ASSERT(!"Unreachable"); - } - } - } + unsealedTables.insert(ty); for (const auto& [_name, prop] : tt.props) { @@ -516,27 +159,19 @@ struct FreeTypeSearcher : TypeVisitor if (tt.indexer) { - if (FFlag::LuauEagerGeneralization4) - { - // {[K]: V} is equivalent to three functions: get, set, and iterate - // - // (K) -> V - // (K, V) -> () - // () -> {K} - // - // K and V therefore both have mixed polarity. - - const Polarity p = polarity; - polarity = Polarity::Mixed; - traverse(tt.indexer->indexType); - traverse(tt.indexer->indexResultType); - polarity = p; - } - else - { - traverse(tt.indexer->indexType); - traverse(tt.indexer->indexResultType); - } + // {[K]: V} is equivalent to three functions: get, set, and iterate + // + // (K) -> V + // (K, V) -> () + // () -> {K} + // + // K and V therefore both have mixed polarity. + + const Polarity p = polarity; + polarity = Polarity::Mixed; + traverse(tt.indexer->indexType); + traverse(tt.indexer->indexResultType); + polarity = p; } return false; @@ -574,34 +209,13 @@ struct FreeTypeSearcher : TypeVisitor if (!subsumes(scope, ftp.scope)) return true; - if (FFlag::LuauEagerGeneralization4) - { - GeneralizationParams& params = typePacks[tp]; - ++params.useCount; + GeneralizationParams& params = typePacks[tp]; + ++params.useCount; - if (!isWithinFunction) - params.foundOutsideFunctions = true; + if (!isWithinFunction) + params.foundOutsideFunctions = true; - params.polarity |= polarity; - } - else - { - switch (polarity) - { - case Polarity::Positive: - positiveTypes[tp]++; - break; - case Polarity::Negative: - negativeTypes[tp]++; - break; - case Polarity::Mixed: - positiveTypes[tp]++; - negativeTypes[tp]++; - break; - default: - LUAU_ASSERT(!"Unreachable"); - } - } + params.polarity |= polarity; return true; } @@ -1163,7 +777,7 @@ GeneralizationResult generalizeType( if (!hasLowerBound && !hasUpperBound) { - if (!isWithinFunction || (!FFlag::LuauEagerGeneralization4 && (params.polarity != Polarity::Mixed && params.useCount == 1))) + if (!isWithinFunction) emplaceType(asMutable(freeTy), builtinTypes->unknownType); else { @@ -1187,7 +801,7 @@ GeneralizationResult generalizeType( if (follow(lb) != freeTy) emplaceType(asMutable(freeTy), lb); - else if (!isWithinFunction || (!FFlag::LuauEagerGeneralization4 && params.useCount == 1)) + else if (!isWithinFunction) emplaceType(asMutable(freeTy), builtinTypes->unknownType); else { @@ -1293,92 +907,55 @@ std::optional generalize( FreeTypeSearcher fts{scope, cachedTypes}; fts.traverse(ty); - if (FFlag::LuauEagerGeneralization4) + FunctionType* functionTy = getMutable(ty); + auto pushGeneric = [&](TypeId t) { - FunctionType* functionTy = getMutable(ty); - auto pushGeneric = [&](TypeId t) - { - if (functionTy) - functionTy->generics.push_back(t); - }; + if (functionTy) + functionTy->generics.push_back(t); + }; - auto pushGenericPack = [&](TypePackId tp) - { - if (functionTy) - functionTy->genericPacks.push_back(tp); - }; + auto pushGenericPack = [&](TypePackId tp) + { + if (functionTy) + functionTy->genericPacks.push_back(tp); + }; - for (const auto& [freeTy, params] : fts.types) + for (const auto& [freeTy, params] : fts.types) + { + if (!generalizationTarget || freeTy == *generalizationTarget) { - if (!generalizationTarget || freeTy == *generalizationTarget) - { - GeneralizationResult res = generalizeType(arena, builtinTypes, scope, freeTy, params); - if (res.resourceLimitsExceeded) - return std::nullopt; + GeneralizationResult res = generalizeType(arena, builtinTypes, scope, freeTy, params); + if (res.resourceLimitsExceeded) + return std::nullopt; - if (res && res.wasReplacedByGeneric) - pushGeneric(*res.result); - } + if (res && res.wasReplacedByGeneric) + pushGeneric(*res.result); } + } - for (TypeId unsealedTableTy : fts.unsealedTables) - { - if (!generalizationTarget || unsealedTableTy == *generalizationTarget) - sealTable(scope, unsealedTableTy); - } + for (TypeId unsealedTableTy : fts.unsealedTables) + { + if (!generalizationTarget || unsealedTableTy == *generalizationTarget) + sealTable(scope, unsealedTableTy); + } - for (const auto& [freePackId, params] : fts.typePacks) + for (const auto& [freePackId, params] : fts.typePacks) + { + TypePackId freePack = follow(freePackId); + if (!generalizationTarget) { - TypePackId freePack = follow(freePackId); - if (!generalizationTarget) - { - GeneralizationResult generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params); + GeneralizationResult generalizedTp = generalizeTypePack(arena, builtinTypes, scope, freePack, params); - if (generalizedTp.resourceLimitsExceeded) - return std::nullopt; + if (generalizedTp.resourceLimitsExceeded) + return std::nullopt; - if (generalizedTp && generalizedTp.wasReplacedByGeneric) - pushGenericPack(freePack); - } + if (generalizedTp && generalizedTp.wasReplacedByGeneric) + pushGenericPack(freePack); } - - TypeCacher cacher{cachedTypes}; - cacher.traverse(ty); } - else - { - MutatingGeneralizer gen{arena, builtinTypes, scope, cachedTypes, std::move(fts.positiveTypes), std::move(fts.negativeTypes)}; - - gen.traverse(ty); - - /* MutatingGeneralizer mutates types in place, so it is possible that ty has - * been transmuted to a BoundType. We must follow it again and verify that - * we are allowed to mutate it before we attach generics to it. - */ - ty = follow(ty); - - if (ty->owningArena != arena || ty->persistent) - return ty; - TypeCacher cacher{cachedTypes}; - cacher.traverse(ty); - - FunctionType* ftv = getMutable(ty); - if (ftv) - { - // If we're generalizing a function type, add any of the newly inferred - // generics to the list of existing generic types. - for (const auto g : std::move(gen.generics)) - { - ftv->generics.push_back(g); - } - // Ditto for generic packs. - for (const auto gp : std::move(gen.genericPacks)) - { - ftv->genericPacks.push_back(gp); - } - } - } + TypeCacher cacher{cachedTypes}; + cacher.traverse(ty); return ty; } @@ -1551,9 +1128,6 @@ void pruneUnnecessaryGenerics( TypeId ty ) { - if (!FFlag::LuauEagerGeneralization4) - return; - ty = follow(ty); if (ty->owningArena != arena || ty->persistent) diff --git a/Analysis/src/InferPolarity.cpp b/Analysis/src/InferPolarity.cpp index 40c1fa5c..7638e69c 100644 --- a/Analysis/src/InferPolarity.cpp +++ b/Analysis/src/InferPolarity.cpp @@ -5,7 +5,6 @@ #include "Luau/Scope.h" #include "Luau/VisitType.h" -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) namespace Luau @@ -136,9 +135,6 @@ struct InferPolarity : TypeVisitor template static void inferGenericPolarities_(NotNull arena, NotNull scope, TID ty) { - if (!FFlag::LuauEagerGeneralization4) - return; - InferPolarity infer{arena, scope}; infer.traverse(ty); diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index db039d8c..48b7dc8e 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -6,6 +6,8 @@ #include +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2); + namespace Luau { @@ -43,6 +45,8 @@ static void errorToString(std::ostream& stream, const T& err) stream << "NotATable { " << toString(err.ty) << " }"; else if constexpr (std::is_same_v) stream << "CannotExtendTable { " << toString(err.tableType) << ", context " << err.context << ", prop \"" << err.prop << "\" }"; + else if constexpr (std::is_same_v) + stream << "CannotCompareUnrelatedTypes { " << toString(err.left) << ", " << toString(err.right) << ", op '" << toString(err.op) << "' }"; else if constexpr (std::is_same_v) stream << "OnlyTablesCanHaveMethods { " << toString(err.tableType) << " }"; else if constexpr (std::is_same_v) @@ -271,6 +275,7 @@ static void errorToString(std::ostream& stream, const T& err) stream << "RecursiveRestraintViolation"; else if constexpr (std::is_same_v) { + LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches2); stream << "GenericBoundsMismatch { genericName = " << std::string{err.genericName} << ", lowerBounds = ["; for (size_t i = 0; i < err.lowerBounds.size(); ++i) { diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 360e0bff..66c0a5d6 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -24,6 +24,7 @@ LUAU_FASTFLAGVARIABLE(LuauNewNonStrictMoreUnknownSymbols) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictSuppressesDynamicRequireErrors) LUAU_FASTFLAG(LuauEmplaceNotPushBack) +LUAU_FASTFLAGVARIABLE(LuauUnreducedTypeFunctionsDontTriggerWarnings) namespace Luau { @@ -717,13 +718,18 @@ struct NonStrictTypeChecker AstExpr* arg = arguments[i]; if (auto runTimeFailureType = willRunTimeError(arg, fresh)) { - if (FFlag::LuauNewNonStrictNoErrorsPassingNever) + if (FFlag::LuauUnreducedTypeFunctionsDontTriggerWarnings) + reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); + else { - if (!get(follow(*runTimeFailureType))) + if (FFlag::LuauNewNonStrictNoErrorsPassingNever) + { + if (!get(follow(*runTimeFailureType))) + reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); + } + else reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); } - else - reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, functionName, i}, arg->location); } } @@ -1187,6 +1193,8 @@ struct NonStrictTypeChecker { TypeId actualType = lookupType(fragment); + if (FFlag::LuauUnreducedTypeFunctionsDontTriggerWarnings && shouldSkipRuntimeErrorTesting(actualType)) + continue; SubtypingResult r = subtyping.isSubtype(actualType, *contextTy, scope); if (r.normalizationTooComplex) reportError(NormalizationTooComplex{}, fragment->location); @@ -1228,6 +1236,12 @@ struct NonStrictTypeChecker cachedResult = arena->addType(NegationType{baseType}); return cachedResult; } + + bool shouldSkipRuntimeErrorTesting(TypeId test) + { + TypeId t = follow(test); + return is(t); + } }; void checkNonStrict( diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 109c8e32..1ad5eb96 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -24,7 +24,6 @@ LUAU_FASTINTVARIABLE(LuauNormalizeUnionLimit, 100) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauUseWorkspacePropToChooseSolver) -LUAU_FASTFLAGVARIABLE(LuauNormalizationLimitTyvarUnionSize) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) namespace Luau @@ -308,6 +307,15 @@ bool NormalizedType::isTruthy() const return !isFalsy(); } +bool NormalizedType::isNil() const +{ + if (!hasNils()) + return false; + + return !hasTops() && !hasBooleans() && !hasExternTypes() && !hasNumbers() && !hasStrings() && !hasThreads() && !hasBuffers() && !hasTables() && + !hasFunctions() && !hasTyvars(); +} + static bool isShallowInhabited(const NormalizedType& norm) { // This test is just a shallow check, for example it returns `true` for `{ p : never }` @@ -1531,11 +1539,8 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali return NormalizationResult::True; } - if (FFlag::LuauNormalizationLimitTyvarUnionSize) - { - if (here.tyvars.size() * there.tyvars.size() >= size_t(FInt::LuauNormalizeUnionLimit)) - return NormalizationResult::HitLimits; - } + 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/OverloadResolution.cpp b/Analysis/src/OverloadResolution.cpp index 12d7e4d0..4b4c513d 100644 --- a/Analysis/src/OverloadResolution.cpp +++ b/Analysis/src/OverloadResolution.cpp @@ -15,7 +15,7 @@ LUAU_FASTFLAG(LuauLimitUnification) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) -LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) namespace Luau @@ -584,7 +584,7 @@ std::pair OverloadResolver::checkOverload_ } } - if (FFlag::LuauSubtypingReportGenericBoundMismatches) + if (FFlag::LuauSubtypingReportGenericBoundMismatches2) { for (GenericBoundsMismatch& mismatch : sr.genericBoundsMismatches) errors.emplace_back(fnExpr->location, std::move(mismatch)); diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index 16c2ae3a..fad76f91 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -4,7 +4,6 @@ LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAGVARIABLE(LuauScopeMethodsAreSolverAgnostic) LUAU_FASTFLAGVARIABLE(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) @@ -222,31 +221,17 @@ std::optional> Scope::linearSearchForBindingPair(cons // Updates the `this` scope with the assignments from the `childScope` including ones that doesn't exist in `this`. void Scope::inheritAssignments(const ScopePtr& childScope) { - if (FFlag::LuauScopeMethodsAreSolverAgnostic) - { - for (const auto& [k, a] : childScope->lvalueTypes) - lvalueTypes[k] = a; - } - else - { - if (!FFlag::LuauSolverV2) - return; - - for (const auto& [k, a] : childScope->lvalueTypes) - lvalueTypes[k] = a; - } + for (const auto& [k, a] : childScope->lvalueTypes) + lvalueTypes[k] = a; } // Updates the `this` scope with the refinements from the `childScope` excluding ones that doesn't exist in `this`. void Scope::inheritRefinements(const ScopePtr& childScope) { - if (FFlag::LuauSolverV2 || FFlag::LuauScopeMethodsAreSolverAgnostic) + for (const auto& [k, a] : childScope->rvalueRefinements) { - for (const auto& [k, a] : childScope->rvalueRefinements) - { - if (lookup(NotNull{k})) - rvalueRefinements[k] = a; - } + if (lookup(NotNull{k})) + rvalueRefinements[k] = a; } for (const auto& [k, a] : childScope->refinements) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index bc96ab2e..b62b4da7 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -20,15 +20,14 @@ LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauReturnMappedGenericPacksFromSubtyping2) -LUAU_FASTFLAGVARIABLE(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAGVARIABLE(LuauSubtypingNegationsChecksNormalizationComplexity) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauEmplaceNotPushBack) -LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAGVARIABLE(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAGVARIABLE(LuauTrackUniqueness) LUAU_FASTFLAGVARIABLE(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAGVARIABLE(LuauSubtypingUnionsAndIntersectionsInGenericBounds) namespace Luau { @@ -300,7 +299,7 @@ SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other) normalizationTooComplex |= other.normalizationTooComplex; isCacheable &= other.isCacheable; errors.insert(errors.end(), other.errors.begin(), other.errors.end()); - if (FFlag::LuauSubtypingReportGenericBoundMismatches) + if (FFlag::LuauSubtypingReportGenericBoundMismatches2) genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end()); return *this; @@ -324,7 +323,7 @@ SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other) normalizationTooComplex |= other.normalizationTooComplex; isCacheable &= other.isCacheable; errors.insert(errors.end(), other.errors.begin(), other.errors.end()); - if (FFlag::LuauSubtypingReportGenericBoundMismatches) + if (FFlag::LuauSubtypingReportGenericBoundMismatches2) genericBoundsMismatches.insert(genericBoundsMismatches.end(), other.genericBoundsMismatches.begin(), other.genericBoundsMismatches.end()); return *this; @@ -848,7 +847,7 @@ SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp, NotNu { // Bounds should have exactly one entry LUAU_ASSERT(bounds->size() == 1); - if (FFlag::LuauSubtypingReportGenericBoundMismatches) + if (FFlag::LuauSubtypingReportGenericBoundMismatches2) { if (bounds->empty()) continue; @@ -1013,17 +1012,17 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub SubtypingResult result; - if (auto subUnion = get(subTy)) + if (auto subUnion = get(subTy); subUnion && !FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds) result = isCovariantWith(env, subUnion, superTy, scope); - else if (auto superUnion = get(superTy)) + else if (auto superUnion = get(superTy); superUnion && !FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds) { result = isCovariantWith(env, subTy, superUnion, scope); if (!result.isSubtype && !result.normalizationTooComplex) result = trySemanticSubtyping(env, subTy, superTy, scope, result); } - else if (auto superIntersection = get(superTy)) + else if (auto superIntersection = get(superTy); superIntersection && !FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds) result = isCovariantWith(env, subTy, superIntersection, scope); - else if (auto subIntersection = get(subTy)) + else if (auto subIntersection = get(subTy); subIntersection && !FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds) { result = isCovariantWith(env, subIntersection, superTy, scope); if (!result.isSubtype && !result.normalizationTooComplex) @@ -1043,7 +1042,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result = isCovariantWith(env, builtinTypes->unknownType, superTy, scope).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy, scope)); } - else if (get(superTy)) + else if (get(superTy) && // flag delays recursing into unions and inters, so only handle this case if subTy isn't a union or inter + (FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds ? !get(subTy) && !get(subTy) : true)) { LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. LUAU_ASSERT(!get(subTy)); // TODO: replace with ice. @@ -1099,6 +1099,30 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result.isCacheable = false; } } + else if (auto subUnion = get(subTy)) + { + LUAU_ASSERT(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds); + result = isCovariantWith(env, subUnion, superTy, scope); + } + else if (auto superUnion = get(superTy)) + { + LUAU_ASSERT(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds); + result = isCovariantWith(env, subTy, superUnion, scope); + if (!result.isSubtype && !result.normalizationTooComplex) + result = trySemanticSubtyping(env, subTy, superTy, scope, result); + } + else if (auto superIntersection = get(superTy)) + { + LUAU_ASSERT(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds); + result = isCovariantWith(env, subTy, superIntersection, scope); + } + else if (auto subIntersection = get(subTy)) + { + LUAU_ASSERT(FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds); + result = isCovariantWith(env, subIntersection, superTy, scope); + 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); @@ -1111,14 +1135,14 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result.isSubtype = ok; result.isCacheable = false; } - else if (auto pair = get2(subTy, superTy); FFlag::LuauEagerGeneralization4 && pair) + else if (auto pair = get2(subTy, superTy)) { // Any two free types are potentially subtypes of one another because // both of them could be narrowed to never. result = {true}; result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy}); } - else if (auto superFree = get(superTy); superFree && FFlag::LuauEagerGeneralization4) + else if (auto superFree = get(superTy)) { // Given SubTy <: (LB <: SuperTy <: UB) // @@ -1133,7 +1157,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub if (result.isSubtype) result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy}); } - else if (auto subFree = get(subTy); subFree && FFlag::LuauEagerGeneralization4) + else if (auto subFree = get(subTy)) { // Given (LB <: SubTy <: UB) <: SuperTy // @@ -1369,9 +1393,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) { const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = - tp ? get(FFlag::LuauMissingFollowMappedGenericPacks ? follow(tp->tail) : tp->tail) : nullptr; - vtp && vtp->hidden) + 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) @@ -1500,9 +1522,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId if (FFlag::LuauReturnMappedGenericPacksFromSubtyping2) { const TypePack* tp = get(*other); - if (const VariadicTypePack* vtp = - tp ? get(FFlag::LuauMissingFollowMappedGenericPacks ? follow(tp->tail) : tp->tail) : nullptr; - vtp && vtp->hidden) + 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) @@ -2232,23 +2252,15 @@ SubtypingResult Subtyping::isCovariantWith( { SubtypingResult result{true}; - if (FFlag::LuauEagerGeneralization4) + if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer) { - if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer) - { - // While it is certainly the case that {} props.empty() && !subTable->indexer && superTable->indexer) - return {false}; + // While it is certainly the case that {} props) @@ -2290,7 +2302,7 @@ SubtypingResult Subtyping::isCovariantWith( { if (subTable->indexer) result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope)); - else if (FFlag::LuauEagerGeneralization4 && subTable->state != TableState::Sealed) + else if (subTable->state != TableState::Sealed) { // As above, we assume that {| |} <: {T} because the unsealed table // on the left will eventually gain the necessary indexer. @@ -2456,7 +2468,7 @@ SubtypingResult Subtyping::isCovariantWith( auto bounds = env.mappedGenerics.find(g); LUAU_ASSERT(bounds && !bounds->empty()); // Check the bounds are valid - if (FFlag::LuauSubtypingReportGenericBoundMismatches) + if (FFlag::LuauSubtypingReportGenericBoundMismatches2) result.andAlso(checkGenericBounds(bounds->back(), env, scope, gen->name)); else result.andAlso(checkGenericBounds_DEPRECATED(bounds->back(), env, scope)); @@ -2977,7 +2989,7 @@ SubtypingResult Subtyping::checkGenericBounds( ) { LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); - LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches); + LUAU_ASSERT(FFlag::LuauSubtypingReportGenericBoundMismatches2); SubtypingResult result{true}; @@ -3058,8 +3070,31 @@ SubtypingResult Subtyping::checkGenericBounds( SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope); boundsResult.reasoning.clear(); - if (res == NormalizationResult::False || !boundsResult.isSubtype) + if (res == NormalizationResult::False) result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound); + else if (!boundsResult.isSubtype) + { + if (FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds) + { + // Check if the bounds are error suppressing before reporting a mismatch + switch (shouldSuppressErrors(normalizer, lowerBound).orElse(shouldSuppressErrors(normalizer, upperBound))) + { + case ErrorSuppression::Suppress: + break; + case ErrorSuppression::NormalizationFailed: + // intentionally fallthrough here since we couldn't prove this was error-suppressing + [[fallthrough]]; + case ErrorSuppression::DoNotSuppress: + result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound); + break; + default: + LUAU_ASSERT(0); + break; + } + } + else + result.genericBoundsMismatches.emplace_back(genericName, bounds.lowerBound, bounds.upperBound); + } result.andAlso(boundsResult); @@ -3073,7 +3108,7 @@ SubtypingResult Subtyping::checkGenericBounds_DEPRECATED( ) { LUAU_ASSERT(FFlag::LuauSubtypingGenericsDoesntUseVariance); - LUAU_ASSERT(!FFlag::LuauSubtypingReportGenericBoundMismatches); + LUAU_ASSERT(!FFlag::LuauSubtypingReportGenericBoundMismatches2); SubtypingResult result{true}; diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index a0172846..357f3f1e 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -12,7 +12,6 @@ #include "Luau/TypeUtils.h" #include "Luau/Unifier2.h" -LUAU_FASTFLAG(LuauEagerGeneralization4) namespace Luau { @@ -73,27 +72,16 @@ struct BidirectionalTypePusher // We'll have a cycle between trying to push `fact`'s type into its // arguments and generalizing `fact`. - if (FFlag::LuauEagerGeneralization4) + if (auto tfit = get(expectedType); tfit && tfit->state == TypeFunctionInstanceState::Unsolved) { - if (auto tfit = get(expectedType); tfit && tfit->state == TypeFunctionInstanceState::Unsolved) - { - incompleteInferences.push_back(IncompleteInference{expectedType, exprType, expr}); - return exprType; - } - - if (is(expectedType)) - { - incompleteInferences.push_back(IncompleteInference{expectedType, exprType, expr}); - return exprType; - } + incompleteInferences.push_back(IncompleteInference{expectedType, exprType, expr}); + return exprType; } - else + + if (is(expectedType)) { - if (is(expectedType)) - { - incompleteInferences.push_back(IncompleteInference{expectedType, exprType, expr}); - return exprType; - } + incompleteInferences.push_back(IncompleteInference{expectedType, exprType, expr}); + return exprType; } if (is(expectedType)) diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 548c154c..17ac51dd 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -1456,6 +1456,9 @@ struct Printer case AstAttr::Deprecated: writer.keyword("@deprecated"); break; + case AstAttr::Unknown: + writer.keyword("@" + std::string{attribute.name.value}); + break; } } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index e3c52a68..8a7b8abb 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -33,12 +33,10 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAGVARIABLE(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) -LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) +LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauTrackUniqueness) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) @@ -49,6 +47,7 @@ LUAU_FASTFLAGVARIABLE(LuauSimplifyIntersectionForLiteralSubtypeCheck) LUAU_FASTFLAGVARIABLE(LuauRemoveGenericErrorForParams) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) LUAU_FASTFLAGVARIABLE(LuauAddErrorCaseForIncompatibleTypePacks) +LUAU_FASTFLAGVARIABLE(LuauAddConditionalContextForTernary) namespace Luau { @@ -304,12 +303,6 @@ void check( typeChecker.visit(sourceModule.root); - if (!FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) - { - if (module->errors.size() == 1 && get(module->errors[0])) - module->errors.clear(); - } - unfreeze(module->interfaceTypes); copyErrors(module->errors, module->interfaceTypes, builtinTypes); freeze(module->interfaceTypes); @@ -1678,25 +1671,10 @@ void TypeChecker2::visitCall(AstExprCall* call) return; // Ok. Calling an uninhabited type is no-op. else if (!resolver.nonviableOverloads.empty()) { - if (FFlag::LuauSuppressErrorsForMultipleNonviableOverloads) - { - const bool reportedErrors = - reportNonviableOverloadErrors(resolver.nonviableOverloads, call->func->location, args.head.size(), call->location); - if (!reportedErrors) - return; // We did not report any errors, so we can just return. - } - else - { - if (resolver.nonviableOverloads.size() == 1 && !isErrorSuppressing(call->func->location, resolver.nonviableOverloads.front().first)) - reportErrors(resolver.nonviableOverloads.front().second); - else - { - std::string s = "None of the overloads for function that accept "; - s += std::to_string(args.head.size()); - s += " arguments are compatible."; - reportError(GenericError{std::move(s)}, call->location); - } - } + const bool reportedErrors = + reportNonviableOverloadErrors(resolver.nonviableOverloads, call->func->location, args.head.size(), call->location); + if (!reportedErrors) + return; // We did not report any errors, so we can just return. } else if (!resolver.arityMismatches.empty()) { @@ -2044,29 +2022,7 @@ void TypeChecker2::visit(AstExprFunction* fn) // If the function type has a function annotation, we need to see if we can suggest an annotation if (normalizedFnTy) - { - if (FFlag::LuauEagerGeneralization4) - suggestAnnotations(fn, normalizedFnTy->functions.parts.front()); - else - { - const FunctionType* inferredFtv = get(normalizedFnTy->functions.parts.front()); - LUAU_ASSERT(inferredFtv); - - TypeFunctionReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}}; - for (TypeId retTy : inferredFtv->retTypes) - { - if (get(follow(retTy))) - { - TypeFunctionReductionGuessResult result = guesser.guessTypeFunctionReductionForFunctionExpr(*fn, inferredFtv, retTy); - if (result.shouldRecommendAnnotation && !get(result.guessedReturnType)) - reportError( - ExplicitFunctionAnnotationRecommended{std::move(result.guessedFunctionAnnotations), result.guessedReturnType}, - fn->location - ); - } - } - } - } + suggestAnnotations(fn, normalizedFnTy->functions.parts.front()); functionDeclStack.pop_back(); } @@ -2178,6 +2134,37 @@ void TypeChecker2::visit(AstExprUnary* expr) } } +// Comparisons between disjoint types is usually something we warn on, but there are some special exceptions. +static bool isOkToCompare( + Normalizer& normalizer, + NormalizationResult typesHaveIntersection, + const std::shared_ptr& normLeft, + const std::shared_ptr& normRight +) +{ + // We only consider warning if we know that the types are disjoint. If + // normalization fails here, it should have also failed elsewhere and will + // already have been reported. + if (NormalizationResult::False != typesHaveIntersection) + return true; + + // We allow anything to be compared to nil. + if (normLeft->isNil() || normRight->isNil()) + return true; + + // Comparison with never is always ok. + else if (NormalizationResult::True != normalizer.isInhabited(normLeft.get()) || + NormalizationResult::True != normalizer.isInhabited(normRight.get())) + return true; + + // Comparisons between different string singleton types is allowed even + // if their intersection is technically uninhabited. + else if (!normLeft->strings.isNever() && !normRight->strings.isNever()) + return true; + + return false; +}; + TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) { std::optional inContext; @@ -2237,6 +2224,19 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) } NormalizationResult typesHaveIntersection = normalizer.isIntersectionInhabited(leftType, rightType); + if (FFlag::LuauNoMoreComparisonTypeFunctions) + { + if (isEquality || isComparison) + { + // As a special exception, we allow anything to be compared to nil. + if (!isOkToCompare(normalizer, typesHaveIntersection, normLeft, normRight)) + { + reportError(CannotCompareUnrelatedTypes{leftType, rightType, expr->op}, expr->location); + return builtinTypes->errorType; + } + } + } + if (auto it = kBinaryOpMetamethods.find(expr->op); it != kBinaryOpMetamethods.end()) { std::optional leftMt = getMetatable(leftType, builtinTypes); @@ -2566,9 +2566,21 @@ void TypeChecker2::visit(AstExprIfElse* expr) { InConditionalContext inContext(&typeContext, TypeContext::Default); - visit(expr->condition, ValueContext::RValue); - visit(expr->trueExpr, ValueContext::RValue); - visit(expr->falseExpr, ValueContext::RValue); + if (FFlag::LuauAddConditionalContextForTernary) + { + { + InConditionalContext inContext(&typeContext, TypeContext::Condition); + visit(expr->condition, ValueContext::RValue); + } + visit(expr->trueExpr, ValueContext::RValue); + visit(expr->falseExpr, ValueContext::RValue); + } + else + { + visit(expr->condition, ValueContext::RValue); + visit(expr->trueExpr, ValueContext::RValue); + visit(expr->falseExpr, ValueContext::RValue); + } } void TypeChecker2::visit(AstExprInterpString* interpString) diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 728b5c60..daa63b72 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -32,7 +32,6 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0 LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAG(LuauExplicitSkipBoundTypes) @@ -259,39 +258,8 @@ struct TypeFunctionReducer Okay, }; - SkipTestResult DEPRECATED_testForSkippability(TypeId ty) - { - ty = follow(ty); - - if (is(ty)) - { - for (auto t : cyclicTypeFunctions) - { - if (ty == t) - return SkipTestResult::CyclicTypeFunction; - } - - if (!irreducible.contains(ty)) - return SkipTestResult::Defer; - - return SkipTestResult::Irreducible; - } - else if (is(ty)) - { - if (FFlag::LuauEagerGeneralization4) - return SkipTestResult::Generic; - else - return SkipTestResult::Irreducible; - } - - return SkipTestResult::Okay; - } - SkipTestResult testForSkippability(TypeId ty) { - if (!FFlag::LuauEagerGeneralization4) - return DEPRECATED_testForSkippability(ty); - VecDeque queue; DenseHashSet seen{nullptr}; @@ -307,13 +275,11 @@ struct TypeFunctionReducer if (auto tfit = get(t)) { - if (FFlag::LuauEagerGeneralization4) - { - if (tfit->state == TypeFunctionInstanceState::Stuck) - return SkipTestResult::Stuck; - else if (tfit->state == TypeFunctionInstanceState::Solved) - return SkipTestResult::Generic; - } + if (tfit->state == TypeFunctionInstanceState::Stuck) + return SkipTestResult::Stuck; + else if (tfit->state == TypeFunctionInstanceState::Solved) + return SkipTestResult::Generic; + for (auto cyclicTy : cyclicTypeFunctions) { if (t == cyclicTy) @@ -352,10 +318,7 @@ struct TypeFunctionReducer } else if (is(ty)) { - if (FFlag::LuauEagerGeneralization4) - return SkipTestResult::Generic; - else - return SkipTestResult::Irreducible; + return SkipTestResult::Generic; } return SkipTestResult::Okay; @@ -430,22 +393,19 @@ struct TypeFunctionReducer if (FFlag::DebugLuauLogTypeFamilies) printf("%s is uninhabited\n", toString(subject, {true}).c_str()); - if (FFlag::LuauEagerGeneralization4) + if (getState(subject) == TypeFunctionInstanceState::Unsolved) { - if (getState(subject) == TypeFunctionInstanceState::Unsolved) + if (reduction.reductionStatus == Reduction::Erroneous) + setState(subject, TypeFunctionInstanceState::Stuck); + else if (reduction.reductionStatus == Reduction::Irreducible) + setState(subject, TypeFunctionInstanceState::Solved); + else if (reduction.reductionStatus == Reduction::MaybeOk) { - if (reduction.reductionStatus == Reduction::Erroneous) - setState(subject, TypeFunctionInstanceState::Stuck); - else if (reduction.reductionStatus == Reduction::Irreducible) - setState(subject, TypeFunctionInstanceState::Solved); - else if (reduction.reductionStatus == Reduction::MaybeOk) - { - // We cannot make progress because something is unsolved, but we're also forcing. - setState(subject, TypeFunctionInstanceState::Stuck); - } - else - ctx->ice->ice("Unexpected TypeFunctionInstanceState"); + // We cannot make progress because something is unsolved, but we're also forcing. + setState(subject, TypeFunctionInstanceState::Stuck); } + else + ctx->ice->ice("Unexpected TypeFunctionInstanceState"); } if constexpr (std::is_same_v) @@ -492,7 +452,6 @@ struct TypeFunctionReducer if (skip == SkipTestResult::Stuck) { // SkipTestResult::Stuck cannot happen when this flag is unset. - LUAU_ASSERT(FFlag::LuauEagerGeneralization4); if (FFlag::DebugLuauLogTypeFamilies) printf("%s is stuck!\n", toString(subject, {true}).c_str()); @@ -771,14 +730,9 @@ FunctionGraphReductionResult reduceTypeFunctions(TypePackId entrypoint, Location bool isPending(TypeId ty, ConstraintSolver* solver) { - if (FFlag::LuauEagerGeneralization4) - { - if (auto tfit = get(ty); tfit && tfit->state == TypeFunctionInstanceState::Unsolved) - return true; - return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); - } - else - return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); + if (auto tfit = get(ty); tfit && tfit->state == TypeFunctionInstanceState::Unsolved) + return true; + return is(ty) || (solver && solver->hasUnresolvedConstraints(ty)); } } // namespace Luau diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 14b87a59..7946c744 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -13,7 +13,6 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAGVARIABLE(LuauTidyTypeUtils) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauVariadicAnyPackShouldBeErrorSuppressing) @@ -632,8 +631,6 @@ void trackInteriorFreeType(Scope* scope, TypeId ty) void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) { LUAU_ASSERT(tp); - if (!FFlag::LuauEagerGeneralization4) - return; for (; scope; scope = scope->parent.get()) { diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index cd27bdb2..b4b03a7a 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -20,10 +20,11 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauEmplaceNotPushBack) LUAU_FASTFLAGVARIABLE(LuauLimitUnification) LUAU_FASTFLAGVARIABLE(LuauUnifyShortcircuitSomeIntersectionsAndUnions) +LUAU_FASTFLAGVARIABLE(LuauTryToOptimizeSetTypeUnification) +LUAU_FASTFLAGVARIABLE(LuauFixNilRightPad) namespace Luau { @@ -92,11 +93,8 @@ static bool areCompatible(TypeId left, TypeId right) // returns `true` if `ty` is irressolvable and should be added to `incompleteSubtypes`. static bool isIrresolvable(TypeId ty) { - if (FFlag::LuauEagerGeneralization4) - { - if (auto tfit = get(ty); tfit && tfit->state != TypeFunctionInstanceState::Unsolved) - return false; - } + if (auto tfit = get(ty); tfit && tfit->state != TypeFunctionInstanceState::Unsolved) + return false; return get(ty) || get(ty); } @@ -213,19 +211,67 @@ UnifyResult Unifier2::unify_(TypeId subTy, TypeId superTy) if (subFn && superFn) return unify_(subTy, superFn); - auto subUnion = get(subTy); - auto superUnion = get(superTy); - if (subUnion) - return unify_(subUnion, superTy); - else if (superUnion) - return unify_(subTy, superUnion); + 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); - auto subIntersection = get(subTy); - auto superIntersection = get(superTy); - if (subIntersection) - return unify_(subIntersection, superTy); - else 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 subNever = get(subTy); auto superNever = get(superTy); @@ -371,18 +417,11 @@ UnifyResult Unifier2::unify_(TypeId subTy, const FunctionType* superFn) for (TypePackId genericPack : subFn->genericPacks) { - if (FFlag::LuauEagerGeneralization4) - { - if (FFlag::LuauEagerGeneralization4) - genericPack = follow(genericPack); - - // TODO: Clip this follow() with LuauEagerGeneralization4 - const GenericTypePack* gen = get(follow(genericPack)); - if (gen) - genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); - } - else - genericPackSubstitutions[genericPack] = arena->freshTypePack(scope); + genericPack = follow(genericPack); + + const GenericTypePack* gen = get(genericPack); + if (gen) + genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); } } @@ -512,12 +551,10 @@ UnifyResult Unifier2::unify_(TableType* subTable, const TableType* superTable) { result &= unify_(subTable->indexer->indexType, superTable->indexer->indexType); result &= unify_(subTable->indexer->indexResultType, superTable->indexer->indexResultType); - if (FFlag::LuauEagerGeneralization4) - { - // FIXME: We can probably do something more efficient here. - result &= unify_(superTable->indexer->indexType, subTable->indexer->indexType); - result &= unify_(superTable->indexer->indexResultType, subTable->indexer->indexResultType); - } + + // FIXME: We can probably do something more efficient here. + result &= unify_(superTable->indexer->indexType, subTable->indexer->indexType); + result &= unify_(superTable->indexer->indexResultType, subTable->indexer->indexResultType); } if (!subTable->indexer && subTable->state == TableState::Unsealed && superTable->indexer) @@ -704,10 +741,18 @@ UnifyResult Unifier2::unify_(TypePackId subTp, TypePackId superTp) auto [superTypes, superTail] = extendTypePack(*arena, builtinTypes, superTp, maxLength); // right-pad the subpack with nils if `superPack` is larger since that's what a function call does - if (subTypes.size() < maxLength) + if (FFlag::LuauFixNilRightPad) { - for (size_t i = 0; i <= maxLength - subTypes.size(); i++) - subTypes.push_back(builtinTypes->nilType); + if (subTypes.size() < maxLength) + subTypes.resize(maxLength, builtinTypes->nilType); + } + else + { + if (subTypes.size() < maxLength) + { + for (size_t i = 0; i <= maxLength - subTypes.size(); i++) + subTypes.push_back(builtinTypes->nilType); + } } if (subTypes.size() < maxLength || superTypes.size() < maxLength) diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index 917217a4..3f932f5e 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -195,6 +195,7 @@ class AstAttr : public AstNode Checked, Native, Deprecated, + Unknown }; struct DeprecatedInfo @@ -205,6 +206,7 @@ class AstAttr : public AstNode }; AstAttr(const Location& location, Type type, AstArray args); + AstAttr(const Location& location, Type type, AstArray args, AstName name); AstAttr* asAttr() override { @@ -217,6 +219,7 @@ class AstAttr : public AstNode Type type; AstArray args; + AstName name; }; class AstExpr : public AstNode diff --git a/Ast/include/Luau/Lexer.h b/Ast/include/Luau/Lexer.h index 847094dd..2798e2cf 100644 --- a/Ast/include/Luau/Lexer.h +++ b/Ast/include/Luau/Lexer.h @@ -136,6 +136,7 @@ class AstNameTable std::pair getOrAddWithType(const char* name, size_t length); std::pair getWithType(const char* name, size_t length) const; + AstName getOrAdd(const char* name, size_t len); AstName getOrAdd(const char* name); AstName get(const char* name) const; diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index b29ffeaf..aecd7171 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -54,6 +54,14 @@ AstAttr::AstAttr(const Location& location, Type type, AstArray args) { } +AstAttr::AstAttr(const Location& location, Type type, AstArray args, AstName name) + : AstNode(ClassIndex(), location) + , type(type) + , args(args) + , name(name) +{ +} + void AstAttr::visit(AstVisitor* visitor) { visitor->visit(this); diff --git a/Ast/src/Lexer.cpp b/Ast/src/Lexer.cpp index 950f3661..12e35343 100644 --- a/Ast/src/Lexer.cpp +++ b/Ast/src/Lexer.cpp @@ -253,6 +253,11 @@ std::pair AstNameTable::getWithType(const char* name, siz return std::make_pair(AstName(), Lexeme::Name); } +AstName AstNameTable::getOrAdd(const char* name, size_t len) +{ + return getOrAddWithType(name, len).first; +} + AstName AstNameTable::getOrAdd(const char* name) { return getOrAddWithType(name, strlen(name)).first; diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 2b6290de..d8ee89b7 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -22,6 +22,7 @@ LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, f LUAU_FASTFLAGVARIABLE(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAGVARIABLE(LuauParametrizedAttributeSyntax) LUAU_FASTFLAGVARIABLE(DebugLuauStringSingletonBasedOnQuotes) +LUAU_FASTFLAGVARIABLE(LuauAutocompleteAttributes) // Clip with DebugLuauReportReturnTypeVariadicWithTypeSuffix bool luau_telemetry_parsed_return_type_variadic_with_type_suffix = false; @@ -934,8 +935,15 @@ void Parser::parseAttribute(TempVector& attributes) nextLexeme(); - if (type) - attributes.push_back(allocator.alloc(loc, *type, empty)); + if (FFlag::LuauAutocompleteAttributes) + { + attributes.push_back(allocator.alloc(loc, type.value_or(AstAttr::Type::Unknown), empty, AstName(name))); + } + else + { + if (type) + attributes.push_back(allocator.alloc(loc, *type, empty)); + } } else { @@ -965,14 +973,30 @@ void Parser::parseAttribute(TempVector& attributes) std::optional type = validateAttribute(nameLoc, attrName, attributes, args); - if (type) - attributes.push_back(allocator.alloc(Location(nameLoc, argsLocation), *type, args)); + if (FFlag::LuauAutocompleteAttributes) + { + attributes.push_back( + allocator.alloc(Location(nameLoc, argsLocation), type.value_or(AstAttr::Type::Unknown), args, AstName(attrName)) + ); + } + else + { + if (type) + attributes.push_back(allocator.alloc(Location(nameLoc, argsLocation), *type, args)); + } } else { std::optional type = validateAttribute(nameLoc, attrName, attributes, empty); - if (type) - attributes.push_back(allocator.alloc(nameLoc, *type, empty)); + if (FFlag::LuauAutocompleteAttributes) + { + attributes.push_back(allocator.alloc(nameLoc, type.value_or(AstAttr::Type::Unknown), empty, AstName(attrName))); + } + else + { + if (type) + attributes.push_back(allocator.alloc(nameLoc, *type, empty)); + } } if (lexer.current().type == ',') @@ -988,6 +1012,14 @@ void Parser::parseAttribute(TempVector& attributes) else { report(Location(open.location, lexer.current().location), "Attribute list cannot be empty"); + + if (FFlag::LuauAutocompleteAttributes) + { + // autocomplete expects at least one unknown attribute. + attributes.push_back( + allocator.alloc(Location(open.location, lexer.current().location), AstAttr::Type::Unknown, empty, nameError) + ); + } } expectMatchAndConsume(']', open); diff --git a/CMakeLists.txt b/CMakeLists.txt index 404d1923..ff9dc00f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -229,8 +229,11 @@ if(LUAU_BUILD_CLI) target_link_libraries(Luau.Repl.CLI PRIVATE Luau.Compiler Luau.Config Luau.CodeGen Luau.VM Luau.Require Luau.CLI.lib isocline) target_link_libraries(Luau.Repl.CLI PRIVATE osthreads) + target_link_libraries(Luau.Reduce.CLI PRIVATE osthreads) target_link_libraries(Luau.Analyze.CLI PRIVATE osthreads) target_link_libraries(Luau.Ast.CLI PRIVATE osthreads) + 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) diff --git a/CodeGen/include/Luau/AssemblyBuilderA64.h b/CodeGen/include/Luau/AssemblyBuilderA64.h index 9b7018c7..411eb09d 100644 --- a/CodeGen/include/Luau/AssemblyBuilderA64.h +++ b/CodeGen/include/Luau/AssemblyBuilderA64.h @@ -19,6 +19,7 @@ namespace A64 enum FeaturesA64 { Feature_JSCVT = 1 << 0, + Feature_AdvSIMD = 1 << 1 }; class AssemblyBuilderA64 @@ -139,6 +140,7 @@ class AssemblyBuilderA64 void fsqrt(RegisterA64 dst, RegisterA64 src); void fsub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2); void faddp(RegisterA64 dst, RegisterA64 src); + void fmla(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2); // Vector component manipulation void ins_4s(RegisterA64 dst, RegisterA64 src, uint8_t index); diff --git a/CodeGen/include/Luau/AssemblyBuilderX64.h b/CodeGen/include/Luau/AssemblyBuilderX64.h index 422cbffa..53617a1e 100644 --- a/CodeGen/include/Luau/AssemblyBuilderX64.h +++ b/CodeGen/include/Luau/AssemblyBuilderX64.h @@ -18,6 +18,12 @@ namespace CodeGen namespace X64 { +enum FeaturesX64 +{ + Feature_FMA3 = 1 << 0, + Feature_AVX = 1 << 1 +}; + enum class RoundingModeX64 { RoundToNearestEven = 0b00, @@ -42,8 +48,8 @@ enum class ABIX64 class AssemblyBuilderX64 { public: - explicit AssemblyBuilderX64(bool logText, ABIX64 abi); - explicit AssemblyBuilderX64(bool logText); + explicit AssemblyBuilderX64(bool logText, ABIX64 abi, unsigned int features = 0); + explicit AssemblyBuilderX64(bool logText, unsigned int features = 0); ~AssemblyBuilderX64(); // Base two operand instructions with 9 opcode selection @@ -172,6 +178,8 @@ class AssemblyBuilderX64 void vpinsrd(RegisterX64 dst, RegisterX64 src1, OperandX64 src2, uint8_t offset); void vdpps(OperandX64 dst, OperandX64 src1, OperandX64 src2, uint8_t mask); + void vfmadd213ps(OperandX64 dst, OperandX64 src1, OperandX64 src2); + void vfmadd213pd(OperandX64 dst, OperandX64 src1, OperandX64 src2); // Run final checks bool finalize(); @@ -216,6 +224,8 @@ class AssemblyBuilderX64 const ABIX64 abi; + const unsigned int features = 0; + private: // Instruction archetypes void placeBinary( diff --git a/CodeGen/include/Luau/IrData.h b/CodeGen/include/Luau/IrData.h index 28dbb925..c0eadc77 100644 --- a/CodeGen/include/Luau/IrData.h +++ b/CodeGen/include/Luau/IrData.h @@ -151,6 +151,9 @@ enum class IrCmd : uint8_t DIV_NUM, IDIV_NUM, MOD_NUM, + // A * B + C + // A, B, C: double + MULADD_NUM, // Get the minimum/maximum of two numbers // If one of the values is NaN, 'B' is returned as the result @@ -203,6 +206,9 @@ enum class IrCmd : uint8_t SUB_VEC, MUL_VEC, DIV_VEC, + // Lanewise A * B + C + // A, B, C: TValue + MULADD_VEC, // Negate a vector // A: TValue @@ -402,12 +408,6 @@ enum class IrCmd : uint8_t // C: Rn or unsigned int (key) SET_TABLE, - // TODO: remove with FFlagLuauCodeGenSimplifyImport2 - // Lookup a value in the environment - // A: Rn (where to store the result) - // B: unsigned int (import path) - GET_IMPORT, - // Store an import from constant or the import path // A: Rn (where to store the result) // B: Kn diff --git a/CodeGen/include/Luau/IrVisitUseDef.h b/CodeGen/include/Luau/IrVisitUseDef.h index 97707ee2..029507f4 100644 --- a/CodeGen/include/Luau/IrVisitUseDef.h +++ b/CodeGen/include/Luau/IrVisitUseDef.h @@ -63,9 +63,6 @@ static void visitVmRegDefsUses(T& visitor, IrFunction& function, const IrInst& i case IrCmd::DO_LEN: visitor.use(inst.b); - visitor.def(inst.a); - break; - case IrCmd::GET_IMPORT: visitor.def(inst.a); break; case IrCmd::GET_CACHED_IMPORT: diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp index 77c35d54..6ba989d4 100644 --- a/CodeGen/src/AssemblyBuilderA64.cpp +++ b/CodeGen/src/AssemblyBuilderA64.cpp @@ -594,6 +594,47 @@ void AssemblyBuilderA64::faddp(RegisterA64 dst, RegisterA64 src) placeR1("faddp", dst, src, 0b011'11110'0'0'11000'01101'10 | ((dst.kind == KindA64::d) << 12)); } +void AssemblyBuilderA64::fmla(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2) +{ + // There is no scalar version of FMLA instruction + // Vector instruction is used for both cases with proper sz bit. + + // Q U Sz Rm Opcode Rn Rd + uint32_t op = 0b0'0'0'011100'0'1'00000'110011'00000'00000; + const uint32_t QBit = 1 << 30; + const uint32_t SzBit = 1 << 22; + + if (dst.kind == KindA64::d) + { + CODEGEN_ASSERT(src1.kind == KindA64::d && src2.kind == KindA64::d); + + if (logText) + logAppend(" %-12sd%d,d%d,d%d\n", "fmla", dst.index, src1.index, src2.index); + + place(dst.index | (src1.index << 5) | (src2.index << 16) | op | QBit | SzBit); + } + else if (dst.kind == KindA64::s) + { + CODEGEN_ASSERT(src1.kind == KindA64::s && src2.kind == KindA64::s); + + if (logText) + logAppend(" %-12ss%d,s%d,s%d\n", "fmla", dst.index, src1.index, src2.index); + + place(dst.index | (src1.index << 5) | (src2.index << 16) | op); + } + else + { + CODEGEN_ASSERT(dst.kind == KindA64::q && src1.kind == KindA64::q && src2.kind == KindA64::q); + + if (logText) + logAppend(" %-12sv%d.4s,v%d.4s,v%d.4s\n", "fmla", dst.index, src1.index, src2.index); + + place(dst.index | (src1.index << 5) | (src2.index << 16) | op | QBit); + } + + commit(); +} + void AssemblyBuilderA64::fadd(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2) { if (dst.kind == KindA64::d) diff --git a/CodeGen/src/AssemblyBuilderX64.cpp b/CodeGen/src/AssemblyBuilderX64.cpp index f37037a6..929bd167 100644 --- a/CodeGen/src/AssemblyBuilderX64.cpp +++ b/CodeGen/src/AssemblyBuilderX64.cpp @@ -6,8 +6,6 @@ #include #include -LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodeGenFixRexw, false) - namespace Luau { namespace CodeGen @@ -39,8 +37,6 @@ static_assert(sizeof(cmovTextForCondition) / sizeof(cmovTextForCondition[0]) == #define OP_PLUS_CC(op, cc) ((op) + uint8_t(cc)) #define REX_W_BIT(value) (value ? 0x8 : 0x0) -// TODO: remove with DFFlagLuauCodeGenFixRexw -#define REX_W_DEPRECATED(reg) REX_W_BIT((reg).size == SizeX64::qword || ((reg).size == SizeX64::byte && (reg).index >= 4)) #define REX_W(reg) REX_W_BIT((reg).size == SizeX64::qword) #define REX_FORCE(reg) (((reg).size == SizeX64::byte && (reg).index >= 4) ? 0x40 : 0x00) #define REX_R(reg) (((reg).index & 0x8) >> 1) @@ -82,9 +78,10 @@ static ABIX64 getCurrentX64ABI() #endif } -AssemblyBuilderX64::AssemblyBuilderX64(bool logText, ABIX64 abi) +AssemblyBuilderX64::AssemblyBuilderX64(bool logText, ABIX64 abi, unsigned int features) : logText(logText) , abi(abi) + , features(features) , constCache32(~0u) , constCache64(~0ull) { @@ -96,8 +93,8 @@ AssemblyBuilderX64::AssemblyBuilderX64(bool logText, ABIX64 abi) codeEnd = code.data() + code.size(); } -AssemblyBuilderX64::AssemblyBuilderX64(bool logText) - : AssemblyBuilderX64(logText, getCurrentX64ABI()) +AssemblyBuilderX64::AssemblyBuilderX64(bool logText, unsigned int features) + : AssemblyBuilderX64(logText, getCurrentX64ABI(), features) { } @@ -978,6 +975,17 @@ void AssemblyBuilderX64::vdpps(OperandX64 dst, OperandX64 src1, OperandX64 src2, placeAvx("vdpps", dst, src1, src2, mask, 0x40, false, AVX_0F3A, AVX_66); } + +void AssemblyBuilderX64::vfmadd213ps(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vfmadd213ps", dst, src1, src2, 0xA8, false, AVX_0F38, AVX_66); +} + +void AssemblyBuilderX64::vfmadd213pd(OperandX64 dst, OperandX64 src1, OperandX64 src2) +{ + placeAvx("vfmadd213pd", dst, src1, src2, 0xA8, true, AVX_0F38, AVX_66); +} + bool AssemblyBuilderX64::finalize() { code.resize(codePos - code.data()); @@ -1412,52 +1420,25 @@ void AssemblyBuilderX64:: void AssemblyBuilderX64::placeRex(RegisterX64 op) { - if (DFFlag::LuauCodeGenFixRexw) - { - uint8_t code = REX_W(op) | REX_B(op) | REX_FORCE(op); - - if (code != 0) - place(code | 0x40); - } - else - { - uint8_t code = REX_W_DEPRECATED(op) | REX_B(op); + uint8_t code = REX_W(op) | REX_B(op) | REX_FORCE(op); - if (code != 0) - place(code | 0x40); - } + if (code != 0) + place(code | 0x40); } void AssemblyBuilderX64::placeRex(OperandX64 op) { - if (DFFlag::LuauCodeGenFixRexw) - { - uint8_t code = 0; - - if (op.cat == CategoryX64::reg) - code = REX_W(op.base) | REX_B(op.base) | REX_FORCE(op.base); - else if (op.cat == CategoryX64::mem) - code = REX_W_BIT(op.memSize == SizeX64::qword) | REX_X(op.index) | REX_B(op.base); - else - CODEGEN_ASSERT(!"No encoding for left operand of this category"); + uint8_t code = 0; - if (code != 0) - place(code | 0x40); - } + if (op.cat == CategoryX64::reg) + code = REX_W(op.base) | REX_B(op.base) | REX_FORCE(op.base); + else if (op.cat == CategoryX64::mem) + code = REX_W_BIT(op.memSize == SizeX64::qword) | REX_X(op.index) | REX_B(op.base); else - { - uint8_t code = 0; - - if (op.cat == CategoryX64::reg) - code = REX_W_DEPRECATED(op.base) | REX_B(op.base); - else if (op.cat == CategoryX64::mem) - code = REX_W_BIT(op.memSize == SizeX64::qword) | REX_X(op.index) | REX_B(op.base); - else - CODEGEN_ASSERT(!"No encoding for left operand of this category"); + CODEGEN_ASSERT(!"No encoding for left operand of this category"); - if (code != 0) - place(code | 0x40); - } + if (code != 0) + place(code | 0x40); } void AssemblyBuilderX64::placeRexNoW(OperandX64 op) @@ -1477,30 +1458,15 @@ void AssemblyBuilderX64::placeRexNoW(OperandX64 op) void AssemblyBuilderX64::placeRex(RegisterX64 lhs, OperandX64 rhs) { - if (DFFlag::LuauCodeGenFixRexw) - { - uint8_t code = REX_W(lhs) | REX_FORCE(lhs); - - if (rhs.cat == CategoryX64::imm) - code |= REX_B(lhs); - else - code |= REX_R(lhs) | REX_X(rhs.index) | REX_B(rhs.base) | REX_FORCE(lhs) | REX_FORCE(rhs.base); + uint8_t code = REX_W(lhs) | REX_FORCE(lhs); - if (code != 0) - place(code | 0x40); - } + if (rhs.cat == CategoryX64::imm) + code |= REX_B(lhs); else - { - uint8_t code = REX_W_DEPRECATED(lhs); + code |= REX_R(lhs) | REX_X(rhs.index) | REX_B(rhs.base) | REX_FORCE(lhs) | REX_FORCE(rhs.base); - if (rhs.cat == CategoryX64::imm) - code |= REX_B(lhs); - else - code |= REX_R(lhs) | REX_X(rhs.index) | REX_B(rhs.base); - - if (code != 0) - place(code | 0x40); - } + if (code != 0) + place(code | 0x40); } void AssemblyBuilderX64::placeVex(OperandX64 dst, OperandX64 src1, OperandX64 src2, bool setW, uint8_t mode, uint8_t prefix) diff --git a/CodeGen/src/CodeBlockUnwind.cpp b/CodeGen/src/CodeBlockUnwind.cpp index d90dfd69..b6bb426c 100644 --- a/CodeGen/src/CodeBlockUnwind.cpp +++ b/CodeGen/src/CodeBlockUnwind.cpp @@ -176,9 +176,6 @@ bool isUnwindSupported() { #if defined(_WIN32) && defined(CODEGEN_TARGET_X64) return true; -#elif defined(__ANDROID__) - // Current unwind information is not compatible with Android - return false; #elif defined(__APPLE__) && defined(CODEGEN_TARGET_A64) char ver[256]; size_t verLength = sizeof(ver); diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp index 2da43707..c8e88231 100644 --- a/CodeGen/src/CodeGen.cpp +++ b/CodeGen/src/CodeGen.cpp @@ -148,10 +148,37 @@ unsigned int getCpuFeaturesA64() size_t jscvtLen = sizeof(jscvt); if (sysctlbyname("hw.optional.arm.FEAT_JSCVT", &jscvt, &jscvtLen, nullptr, 0) == 0 && jscvt == 1) result |= A64::Feature_JSCVT; + + int advSIMD = 0; + size_t advSIMDLen = sizeof(advSIMD); + if (sysctlbyname("hw.optional.arm.AdvSIMD", &advSIMD, &advSIMDLen, nullptr, 0) == 0 && advSIMD == 1) + result |= A64::Feature_AdvSIMD; #endif return result; } +#else +unsigned int getCpuFeaturesX64() +{ + unsigned int result = 0; + + int cpuinfo[4] = {0, 0, 0, 0}; +#if defined(CODEGEN_TARGET_X64) +#ifdef _MSC_VER + __cpuid(cpuinfo, 1); +#else + __cpuid(1, cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); +#endif +#endif + + if ((cpuinfo[2] & 0x00001000) != 0) + result |= X64::Feature_FMA3; + + if ((cpuinfo[2] & 0x10000000) != 0) + result |= X64::Feature_AVX; + + return result; +} #endif bool isSupported() diff --git a/CodeGen/src/CodeGenAssembly.cpp b/CodeGen/src/CodeGenAssembly.cpp index 082b6f97..0a7c48d7 100644 --- a/CodeGen/src/CodeGenAssembly.cpp +++ b/CodeGen/src/CodeGenAssembly.cpp @@ -252,6 +252,8 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A #if defined(CODEGEN_TARGET_A64) unsigned int getCpuFeaturesA64(); +#else +unsigned int getCpuFeaturesX64(); #endif std::string getAssembly(lua_State* L, int idx, AssemblyOptions options, LoweringStats* stats) @@ -267,7 +269,8 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options, Lowering static unsigned int cpuFeatures = getCpuFeaturesA64(); A64::AssemblyBuilderA64 build(/* logText= */ options.includeAssembly, cpuFeatures); #else - X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly); + static unsigned int cpuFeatures = getCpuFeaturesX64(); + X64::AssemblyBuilderX64 build(/* logText= */ options.includeAssembly, cpuFeatures); #endif return getAssemblyImpl(build, func, options, stats); diff --git a/CodeGen/src/CodeGenContext.cpp b/CodeGen/src/CodeGenContext.cpp index 798eaead..678d4f19 100644 --- a/CodeGen/src/CodeGenContext.cpp +++ b/CodeGen/src/CodeGenContext.cpp @@ -16,6 +16,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenBlockSize, 4 * 1024 * 1024) LUAU_FASTINTVARIABLE(LuauCodeGenMaxTotalSize, 256 * 1024 * 1024) LUAU_FASTFLAGVARIABLE(LuauCodeGenUnassignedBcTargetAbort) +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodeGenDisableWithNoReentry, false) namespace Luau { @@ -29,6 +30,7 @@ static void* gPerfLogContext = nullptr; static PerfLogFn gPerfLogFn = nullptr; unsigned int getCpuFeaturesA64(); +unsigned int getCpuFeaturesX64(); void setPerfLog(void* context, PerfLogFn logFn) { @@ -376,6 +378,12 @@ static int onEnter(lua_State* L, Proto* proto) static int onEnterDisabled(lua_State* L, Proto* proto) { + if (DFFlag::LuauCodeGenDisableWithNoReentry) + { + // If the function wasn't entered natively, it cannot be resumed natively later + L->ci->flags &= ~LUA_CALLINFO_NATIVE; + } + return 1; } @@ -566,7 +574,8 @@ template static unsigned int cpuFeatures = getCpuFeaturesA64(); A64::AssemblyBuilderA64 build(/* logText= */ false, cpuFeatures); #else - X64::AssemblyBuilderX64 build(/* logText= */ false); + static unsigned int cpuFeatures = getCpuFeaturesX64(); + X64::AssemblyBuilderX64 build(/* logText= */ false, cpuFeatures); #endif ModuleHelpers helpers; diff --git a/CodeGen/src/IrDump.cpp b/CodeGen/src/IrDump.cpp index 698f145d..2937a5f4 100644 --- a/CodeGen/src/IrDump.cpp +++ b/CodeGen/src/IrDump.cpp @@ -169,6 +169,8 @@ const char* getCmdName(IrCmd cmd) return "SIGN_NUM"; case IrCmd::SELECT_NUM: return "SELECT_NUM"; + case IrCmd::MULADD_NUM: + return "MULADD_NUM"; case IrCmd::SELECT_VEC: return "SELECT_VEC"; case IrCmd::ADD_VEC: @@ -183,6 +185,8 @@ const char* getCmdName(IrCmd cmd) return "UNM_VEC"; case IrCmd::DOT_VEC: return "DOT_VEC"; + case IrCmd::MULADD_VEC: + return "MULADD_VEC"; case IrCmd::NOT_ANY: return "NOT_ANY"; case IrCmd::CMP_ANY: @@ -253,8 +257,6 @@ const char* getCmdName(IrCmd cmd) return "GET_TABLE"; case IrCmd::SET_TABLE: return "SET_TABLE"; - case IrCmd::GET_IMPORT: - return "GET_IMPORT"; case IrCmd::GET_CACHED_IMPORT: return "GET_CACHED_IMPORT"; case IrCmd::CONCAT: diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp index 764d33cc..3880349b 100644 --- a/CodeGen/src/IrLoweringA64.cpp +++ b/CodeGen/src/IrLoweringA64.cpp @@ -632,6 +632,27 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.fsub(inst.regA64, temp1, inst.regA64); break; } + case IrCmd::MULADD_NUM: + { + RegisterA64 tempA = tempDouble(inst.a); + RegisterA64 tempB = tempDouble(inst.b); + RegisterA64 tempC = tempDouble(inst.c); + + if ((build.features & Feature_AdvSIMD) != 0) + { + inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.c}); + if (inst.regA64 != tempC) + build.fmov(inst.regA64, tempC); + build.fmla(inst.regA64, tempB, tempA); + } + else + { + inst.regA64 = regs.allocReg(KindA64::d, index); + build.fmul(inst.regA64, tempB, tempA); + build.fadd(inst.regA64, inst.regA64, tempC); + } + break; + } case IrCmd::MIN_NUM: { inst.regA64 = regs.allocReuse(KindA64::d, index, {inst.a, inst.b}); @@ -741,6 +762,27 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.bit(inst.regA64, temp2, mask); break; } + case IrCmd::MULADD_VEC: + { + RegisterA64 tempA = regOp(inst.a); + RegisterA64 tempB = regOp(inst.b); + RegisterA64 tempC = regOp(inst.c); + + if ((build.features & Feature_AdvSIMD) != 0) + { + inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.c}); + if (inst.regA64 != tempC) + build.mov(inst.regA64, tempC); + build.fmla(inst.regA64, tempB, tempA); + } + else + { + inst.regA64 = regs.allocReg(KindA64::q, index); + build.fmul(inst.regA64, tempB, tempA); + build.fadd(inst.regA64, inst.regA64, tempC); + } + break; + } case IrCmd::ADD_VEC: { inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b}); @@ -1417,20 +1459,6 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.ldr(x4, mem(rNativeContext, offsetof(NativeContext, luaV_settable))); build.blr(x4); - emitUpdateBase(build); - break; - case IrCmd::GET_IMPORT: - regs.spill(index); - // luaV_getimport(L, cl->env, k, ra, aux, /* propagatenil= */ false) - build.mov(x0, rState); - build.ldr(x1, mem(rClosure, offsetof(Closure, env))); - build.mov(x2, rConstants); - build.add(x3, rBase, uint16_t(vmRegOp(inst.a) * sizeof(TValue))); - build.mov(w4, uintOp(inst.b)); - build.mov(w5, 0); - build.ldr(x6, mem(rNativeContext, offsetof(NativeContext, luaV_getimport))); - build.blr(x6); - emitUpdateBase(build); break; case IrCmd::GET_CACHED_IMPORT: diff --git a/CodeGen/src/IrLoweringX64.cpp b/CodeGen/src/IrLoweringX64.cpp index 30257517..5edb180f 100644 --- a/CodeGen/src/IrLoweringX64.cpp +++ b/CodeGen/src/IrLoweringX64.cpp @@ -478,6 +478,63 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) } build.vroundsd(inst.regX64, inst.regX64, inst.regX64, RoundingModeX64::RoundToNegativeInfinity); break; + case IrCmd::MULADD_NUM: + { + if ((build.features & Feature_FMA3) != 0) + { + if (inst.a.kind != IrOpKind::Inst) + { + inst.regX64 = regs.allocReg(SizeX64::xmmword, index); + build.vmovsd(inst.regX64, memRegDoubleOp(inst.a)); + } + else + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a}); + RegisterX64 aReg = regOp(inst.a); + if (inst.regX64 != aReg) + build.vmovupd(inst.regX64, aReg); + } + + ScopedRegX64 optBTmp{regs}; + RegisterX64 bReg{}; + + if (inst.b.kind == IrOpKind::Constant) + { + optBTmp.alloc(SizeX64::xmmword); + + build.vmovsd(optBTmp.reg, memRegDoubleOp(inst.b)); + bReg = optBTmp.reg; + } + else + { + bReg = regOp(inst.b); + } + + build.vfmadd213pd(inst.regX64, bReg, memRegDoubleOp(inst.c)); + } + else + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a}); + + if (inst.a.kind != IrOpKind::Inst && inst.b.kind != IrOpKind::Inst) + { + build.vmovsd(inst.regX64, memRegDoubleOp(inst.a)); + build.vmulsd(inst.regX64, inst.regX64, memRegDoubleOp(inst.b)); + } + else if (inst.a.kind == IrOpKind::Inst) + { + build.vmulsd(inst.regX64, regOp(inst.a), memRegDoubleOp(inst.b)); + } + else + { + CODEGEN_ASSERT(inst.b.kind == IrOpKind::Inst); + build.vmulsd(inst.regX64, regOp(inst.b), memRegDoubleOp(inst.a)); + } + + build.vaddsd(inst.regX64, inst.regX64, memRegDoubleOp(inst.c)); + } + break; + } case IrCmd::MOD_NUM: { inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a, inst.b}); @@ -723,6 +780,32 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) build.vdivps(inst.regX64, tmpa, tmpb); break; } + case IrCmd::MULADD_VEC: + { + inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a}); + ScopedRegX64 tmp1{regs}; + ScopedRegX64 tmp2{regs}; + ScopedRegX64 tmp3{regs}; + + RegisterX64 tmpa = vecOp(inst.a, tmp1); + RegisterX64 tmpb = vecOp(inst.b, tmp2); + RegisterX64 tmpc = vecOp(inst.c, tmp3); + + if ((build.features & Feature_FMA3) != 0) + { + if (inst.regX64 != tmpa) + build.vmovups(inst.regX64, tmpa); + + build.vfmadd213ps(inst.regX64, tmpb, tmpc); + } + else + { + build.vmulps(inst.regX64, tmpa, tmpb); + build.vaddps(inst.regX64, inst.regX64, tmpc); + } + + break; + } case IrCmd::UNM_VEC: { inst.regX64 = regs.allocRegOrReuse(SizeX64::xmmword, index, {inst.a}); @@ -1257,24 +1340,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next) CODEGEN_ASSERT(!"Unsupported instruction form"); } break; - case IrCmd::GET_IMPORT: - { - ScopedRegX64 tmp1{regs, SizeX64::qword}; - - build.mov(tmp1.reg, sClosure); - - IrCallWrapperX64 callWrap(regs, build, index); - callWrap.addArgument(SizeX64::qword, rState); - callWrap.addArgument(SizeX64::qword, qword[tmp1.release() + offsetof(Closure, env)]); - callWrap.addArgument(SizeX64::qword, rConstants); - callWrap.addArgument(SizeX64::qword, luauRegAddress(vmRegOp(inst.a))); - callWrap.addArgument(SizeX64::dword, uintOp(inst.b)); - callWrap.addArgument(SizeX64::dword, 0); - callWrap.call(qword[rNativeContext + offsetof(NativeContext, luaV_getimport)]); - - emitUpdateBase(build); - break; - } case IrCmd::GET_CACHED_IMPORT: { regs.assertAllFree(); diff --git a/CodeGen/src/IrTranslateBuiltins.cpp b/CodeGen/src/IrTranslateBuiltins.cpp index b372a7c9..b4b7b73e 100644 --- a/CodeGen/src/IrTranslateBuiltins.cpp +++ b/CodeGen/src/IrTranslateBuiltins.cpp @@ -10,6 +10,7 @@ LUAU_FASTFLAGVARIABLE(LuauCodeGenDirectBtest) LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorLerp) +LUAU_FASTFLAGVARIABLE(LuauCodeGenFMA) // TODO: when nresults is less than our actual result count, we can skip computing/writing unused results @@ -301,10 +302,20 @@ static BuiltinImplResult translateBuiltinVectorLerp(IrBuilder& build, int nparam IrOp tvec = build.inst(IrCmd::NUM_TO_VEC, t); IrOp one = build.inst(IrCmd::NUM_TO_VEC, build.constDouble(1.0)); IrOp diff = build.inst(IrCmd::SUB_VEC, b, a); - IrOp incr = build.inst(IrCmd::MUL_VEC, diff, tvec); - IrOp res = build.inst(IrCmd::ADD_VEC, a, incr); - IrOp ret = build.inst(IrCmd::SELECT_VEC, res, b, tvec, one); - build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), build.inst(IrCmd::TAG_VECTOR, ret)); + + if (FFlag::LuauCodeGenFMA) + { + IrOp res = build.inst(IrCmd::MULADD_VEC, diff, tvec, a); + IrOp ret = build.inst(IrCmd::SELECT_VEC, res, b, tvec, one); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), build.inst(IrCmd::TAG_VECTOR, ret)); + } + else + { + IrOp incr = build.inst(IrCmd::MUL_VEC, diff, tvec); + IrOp res = build.inst(IrCmd::ADD_VEC, a, incr); + IrOp ret = build.inst(IrCmd::SELECT_VEC, res, b, tvec, one); + build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), build.inst(IrCmd::TAG_VECTOR, ret)); + } return {BuiltinImplType::Full, 1}; } @@ -332,10 +343,20 @@ static BuiltinImplResult translateBuiltinMathLerp( IrOp b = builtinLoadDouble(build, args); IrOp t = builtinLoadDouble(build, arg3); - IrOp l = build.inst(IrCmd::ADD_NUM, a, build.inst(IrCmd::MUL_NUM, build.inst(IrCmd::SUB_NUM, b, a), t)); - IrOp r = build.inst(IrCmd::SELECT_NUM, l, b, t, build.constDouble(1.0)); // select on t==1.0 + if (FFlag::LuauCodeGenFMA) + { + IrOp l = build.inst(IrCmd::MULADD_NUM, build.inst(IrCmd::SUB_NUM, b, a), t, a); + IrOp r = build.inst(IrCmd::SELECT_NUM, l, b, t, build.constDouble(1.0)); // select on t==1.0 + + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), r); + } + else + { + IrOp l = build.inst(IrCmd::ADD_NUM, a, build.inst(IrCmd::MUL_NUM, build.inst(IrCmd::SUB_NUM, b, a), t)); + IrOp r = build.inst(IrCmd::SELECT_NUM, l, b, t, build.constDouble(1.0)); // select on t==1.0 - build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), r); + build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), r); + } if (ra != arg) build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER)); diff --git a/CodeGen/src/IrTranslation.cpp b/CodeGen/src/IrTranslation.cpp index 9a5a488d..40f584ed 100644 --- a/CodeGen/src/IrTranslation.cpp +++ b/CodeGen/src/IrTranslation.cpp @@ -12,8 +12,6 @@ #include "lstate.h" #include "ltm.h" -LUAU_FASTFLAGVARIABLE(LuauCodeGenSimplifyImport2) - namespace Luau { namespace CodeGen @@ -1217,35 +1215,8 @@ void translateInstGetImport(IrBuilder& build, const Instruction* pc, int pcpos) int k = LUAU_INSN_D(*pc); uint32_t aux = pc[1]; - if (FFlag::LuauCodeGenSimplifyImport2) - { - build.inst(IrCmd::CHECK_SAFE_ENV, build.vmExit(pcpos)); - build.inst(IrCmd::GET_CACHED_IMPORT, build.vmReg(ra), build.vmConst(k), build.constImport(aux), build.constUint(pcpos + 1)); - } - else - { - IrOp fastPath = build.block(IrBlockKind::Internal); - IrOp fallback = build.block(IrBlockKind::Fallback); - - build.inst(IrCmd::CHECK_SAFE_ENV, build.vmExit(pcpos)); - - // note: if import failed, k[] is nil; we could check this during codegen, but we instead use runtime fallback - // this allows us to handle ahead-of-time codegen smoothly when an import fails to resolve at runtime - IrOp tk = build.inst(IrCmd::LOAD_TAG, build.vmConst(k)); - build.inst(IrCmd::JUMP_EQ_TAG, tk, build.constTag(LUA_TNIL), fallback, fastPath); - - build.beginBlock(fastPath); - - IrOp tvk = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(k)); - build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), tvk); - - IrOp next = build.blockAtInst(pcpos + 2); - FallbackStreamScope scope(build, fallback, next); - - build.inst(IrCmd::SET_SAVEDPC, build.constUint(pcpos + 1)); - build.inst(IrCmd::GET_IMPORT, build.vmReg(ra), build.constUint(aux)); - build.inst(IrCmd::JUMP, next); - } + build.inst(IrCmd::CHECK_SAFE_ENV, build.vmExit(pcpos)); + build.inst(IrCmd::GET_CACHED_IMPORT, build.vmReg(ra), build.vmConst(k), build.constImport(aux), build.constUint(pcpos + 1)); } void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos) diff --git a/CodeGen/src/IrUtils.cpp b/CodeGen/src/IrUtils.cpp index e24ebe25..d8b194ab 100644 --- a/CodeGen/src/IrUtils.cpp +++ b/CodeGen/src/IrUtils.cpp @@ -184,6 +184,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::ABS_NUM: case IrCmd::SIGN_NUM: case IrCmd::SELECT_NUM: + case IrCmd::MULADD_NUM: return IrValueKind::Double; case IrCmd::ADD_VEC: case IrCmd::SUB_VEC: @@ -191,6 +192,7 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::DIV_VEC: case IrCmd::UNM_VEC: case IrCmd::SELECT_VEC: + case IrCmd::MULADD_VEC: return IrValueKind::Tvalue; case IrCmd::DOT_VEC: return IrValueKind::Double; @@ -243,7 +245,6 @@ IrValueKind getCmdValueKind(IrCmd cmd) case IrCmd::DO_LEN: case IrCmd::GET_TABLE: case IrCmd::SET_TABLE: - case IrCmd::GET_IMPORT: case IrCmd::GET_CACHED_IMPORT: case IrCmd::CONCAT: case IrCmd::GET_UPVALUE: diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp index 3f764801..4a4d148f 100644 --- a/CodeGen/src/IrValueLocationTracking.cpp +++ b/CodeGen/src/IrValueLocationTracking.cpp @@ -3,6 +3,8 @@ #include "Luau/IrUtils.h" +LUAU_FASTFLAGVARIABLE(LuauCodeGenRestoreFromSplitStore) + namespace Luau { namespace CodeGen @@ -54,7 +56,6 @@ void IrValueLocationTracking::beforeInstLowering(IrInst& inst) case IrCmd::DO_ARITH: case IrCmd::DO_LEN: case IrCmd::GET_TABLE: - case IrCmd::GET_IMPORT: case IrCmd::GET_CACHED_IMPORT: invalidateRestoreOp(inst.a, /*skipValueInvalidation*/ false); break; @@ -174,6 +175,14 @@ void IrValueLocationTracking::afterInstLowering(IrInst& inst, uint32_t instIdx) if (inst.b.kind == IrOpKind::Inst && function.instOp(inst.b).lastUse != 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); + } + break; default: break; } diff --git a/CodeGen/src/NativeState.cpp b/CodeGen/src/NativeState.cpp index 2c70fca6..b86a8b89 100644 --- a/CodeGen/src/NativeState.cpp +++ b/CodeGen/src/NativeState.cpp @@ -40,7 +40,6 @@ void initFunctions(NativeContext& context) context.luaV_dolen = luaV_dolen; context.luaV_gettable = luaV_gettable; context.luaV_settable = luaV_settable; - context.luaV_getimport = luaV_getimport; context.luaV_concat = luaV_concat; context.luaH_getn = luaH_getn; diff --git a/CodeGen/src/NativeState.h b/CodeGen/src/NativeState.h index 6de775ed..c303504d 100644 --- a/CodeGen/src/NativeState.h +++ b/CodeGen/src/NativeState.h @@ -44,7 +44,6 @@ struct NativeContext void (*luaV_dolen)(lua_State* L, StkId ra, const TValue* rb) = nullptr; void (*luaV_gettable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; void (*luaV_settable)(lua_State* L, const TValue* t, TValue* key, StkId val) = nullptr; - void (*luaV_getimport)(lua_State* L, LuaTable* env, TValue* k, StkId res, uint32_t id, bool propagatenil) = nullptr; void (*luaV_concat)(lua_State* L, int total, int last) = nullptr; int (*luaH_getn)(LuaTable* t) = nullptr; diff --git a/CodeGen/src/OptimizeConstProp.cpp b/CodeGen/src/OptimizeConstProp.cpp index ded09808..6901bb73 100644 --- a/CodeGen/src/OptimizeConstProp.cpp +++ b/CodeGen/src/OptimizeConstProp.cpp @@ -1314,6 +1314,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& state.substituteOrRecord(inst, index); break; case IrCmd::IDIV_NUM: + case IrCmd::MULADD_NUM: case IrCmd::MOD_NUM: case IrCmd::MIN_NUM: case IrCmd::MAX_NUM: @@ -1326,6 +1327,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::SIGN_NUM: case IrCmd::SELECT_NUM: case IrCmd::SELECT_VEC: + case IrCmd::MULADD_VEC: case IrCmd::NOT_ANY: state.substituteOrRecord(inst, index); break; @@ -1528,10 +1530,6 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction& case IrCmd::SET_TABLE: state.invalidateUserCall(); break; - case IrCmd::GET_IMPORT: - state.invalidate(inst.a); - state.invalidateUserCall(); - break; case IrCmd::GET_CACHED_IMPORT: state.invalidate(inst.a); diff --git a/CodeGen/src/OptimizeDeadStore.cpp b/CodeGen/src/OptimizeDeadStore.cpp index a0892919..d815ebf7 100644 --- a/CodeGen/src/OptimizeDeadStore.cpp +++ b/CodeGen/src/OptimizeDeadStore.cpp @@ -766,7 +766,6 @@ static void markDeadStoresInInst(RemoveDeadStoreState& state, IrBuilder& build, case IrCmd::DO_LEN: case IrCmd::GET_TABLE: case IrCmd::SET_TABLE: - case IrCmd::GET_IMPORT: case IrCmd::GET_CACHED_IMPORT: case IrCmd::CONCAT: case IrCmd::INTERRUPT: diff --git a/Compiler/include/Luau/Compiler.h b/Compiler/include/Luau/Compiler.h index 970eea92..2379e8dd 100644 --- a/Compiler/include/Luau/Compiler.h +++ b/Compiler/include/Luau/Compiler.h @@ -89,7 +89,7 @@ class CompileError : public std::exception }; // compiles bytecode into bytecode builder using either a pre-parsed AST or parsing it from source; throws on errors -void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, const AstNameTable& names, const CompileOptions& options = {}); +void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, AstNameTable& names, const CompileOptions& options = {}); void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options = {}, const ParseOptions& parseOptions = {}); // compiles bytecode into a bytecode blob, that either contains the valid bytecode or an encoded error that luau_load can decode diff --git a/Compiler/src/BuiltinFolding.cpp b/Compiler/src/BuiltinFolding.cpp index 28b812f5..69a30404 100644 --- a/Compiler/src/BuiltinFolding.cpp +++ b/Compiler/src/BuiltinFolding.cpp @@ -5,6 +5,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauCompileTypeofFold) + namespace Luau { namespace Compile @@ -77,6 +79,33 @@ static Constant ctype(const Constant& c) } } +static Constant ctypeof(const Constant& c) +{ + LUAU_ASSERT(c.type != Constant::Type_Unknown); + + switch (c.type) + { + case Constant::Type_Nil: + return cstring("nil"); + + case Constant::Type_Boolean: + return cstring("boolean"); + + case Constant::Type_Number: + return cstring("number"); + + case Constant::Type_Vector: + return cvar(); // vector can have a custom typeof name at runtime + + case Constant::Type_String: + return cstring("string"); + + default: + LUAU_ASSERT(!"Unsupported constant type"); + return cvar(); + } +} + static uint32_t bit32(double v) { // convert through signed 64-bit integer to match runtime behavior and gracefully truncate negative integers @@ -436,7 +465,7 @@ Constant foldBuiltin(int bfid, const Constant* args, size_t count) case LBF_TYPEOF: if (count == 1 && args[0].type != Constant::Type_Unknown) - return ctype(args[0]); + return FFlag::LuauCompileTypeofFold ? ctypeof(args[0]) : ctype(args[0]); break; case LBF_MATH_CLAMP: diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index e79af888..4a46e10b 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -91,7 +91,7 @@ struct Compiler { struct RegScope; - Compiler(BytecodeBuilder& bytecode, const CompileOptions& options) + Compiler(BytecodeBuilder& bytecode, const CompileOptions& options, AstNameTable& names) : bytecode(bytecode) , options(options) , functions(nullptr) @@ -107,6 +107,7 @@ struct Compiler , localTypes(nullptr) , exprTypes(nullptr) , builtinTypes(options.vectorType) + , names(names) { // preallocate some buffers that are very likely to grow anyway; this works around std::vector's inefficient growth policy for small arrays localStack.reserve(16); @@ -733,7 +734,7 @@ struct Compiler inlineFrames.push_back({func, oldLocals, target, targetCount}); // fold constant values updated above into expressions in the function body - foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body); + foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body, names); bool usedFallthrough = false; @@ -781,7 +782,7 @@ struct Compiler lv->init = nullptr; } - foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body); + foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, func->body, names); } void compileExprCall(AstExprCall* expr, uint8_t target, uint8_t targetCount, bool targetTop = false, bool multRet = false) @@ -3064,7 +3065,7 @@ struct Compiler locstants[var].type = Constant::Type_Number; locstants[var].valueNumber = from + iv * step; - foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, stat); + foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, stat, names); size_t iterJumps = loopJumps.size(); @@ -3092,7 +3093,7 @@ struct Compiler // clean up fold state in case we need to recompile - normally we compile the loop body once, but due to inlining we may need to do it again locstants[var].type = Constant::Type_Unknown; - foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, stat); + foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldLibraryK, options.libraryMemberConstantCb, stat, names); } void compileStatFor(AstStatFor* stat) @@ -4158,6 +4159,7 @@ struct Compiler DenseHashMap exprTypes; BuiltinAstTypes builtinTypes; + AstNameTable& names; const DenseHashMap* builtinsFold = nullptr; bool builtinsFoldLibraryK = false; @@ -4186,7 +4188,7 @@ static void setCompileOptionsForNativeCompilation(CompileOptions& options) options.typeInfoLevel = 1; } -void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, const AstNameTable& names, const CompileOptions& inputOptions) +void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, AstNameTable& names, const CompileOptions& inputOptions) { LUAU_TIMETRACE_SCOPE("compileOrThrow", "Compiler"); @@ -4219,7 +4221,7 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c if (functionVisitor.hasNativeFunction) setCompileOptionsForNativeCompilation(options); - Compiler compiler(bytecode, options); + Compiler compiler(bytecode, options, names); // since access to some global objects may result in values that change over time, we block imports from non-readonly tables assignMutable(compiler.globals, names, options.mutableGlobals); @@ -4269,7 +4271,8 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c compiler.builtinsFold, compiler.builtinsFoldLibraryK, options.libraryMemberConstantCb, - root + root, + names ); // this pass analyzes table assignments to estimate table shapes for initially empty tables diff --git a/Compiler/src/ConstantFolding.cpp b/Compiler/src/ConstantFolding.cpp index 818a5bf7..4790ffd7 100644 --- a/Compiler/src/ConstantFolding.cpp +++ b/Compiler/src/ConstantFolding.cpp @@ -2,10 +2,13 @@ #include "ConstantFolding.h" #include "BuiltinFolding.h" +#include "Luau/Lexer.h" #include #include +LUAU_FASTFLAGVARIABLE(LuauStringConstFolding) + namespace Luau { namespace Compile @@ -80,7 +83,7 @@ static void foldUnary(Constant& result, AstExprUnary::Op op, const Constant& arg } } -static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& la, const Constant& ra) +static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& la, const Constant& ra, AstNameTable& names) { switch (op) { @@ -283,6 +286,18 @@ static void foldBinary(Constant& result, AstExprBinary::Op op, const Constant& l break; case AstExprBinary::Concat: + if (FFlag::LuauStringConstFolding) + 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; + } break; case AstExprBinary::CompareNe: @@ -361,6 +376,7 @@ struct ConstantVisitor : AstVisitor const DenseHashMap* builtins; bool foldLibraryK = false; LibraryMemberConstantCallback libraryMemberConstantCb; + AstNameTable& names; bool wasEmpty = false; @@ -372,7 +388,8 @@ struct ConstantVisitor : AstVisitor DenseHashMap& locals, const DenseHashMap* builtins, bool foldLibraryK, - LibraryMemberConstantCallback libraryMemberConstantCb + LibraryMemberConstantCallback libraryMemberConstantCb, + AstNameTable& names ) : constants(constants) , variables(variables) @@ -380,6 +397,7 @@ struct ConstantVisitor : AstVisitor , builtins(builtins) , foldLibraryK(foldLibraryK) , libraryMemberConstantCb(libraryMemberConstantCb) + , names(names) { // since we do a single pass over the tree, if the initial state was empty we don't need to clear out old entries wasEmpty = constants.empty() && locals.empty(); @@ -518,7 +536,7 @@ struct ConstantVisitor : AstVisitor // note: ra doesn't need to be constant to fold and/or if (la.type != Constant::Type_Unknown) - foldBinary(result, expr->op, la, ra); + foldBinary(result, expr->op, la, ra, names); } else if (AstExprTypeAssertion* expr = node->as()) { @@ -628,10 +646,11 @@ void foldConstants( const DenseHashMap* builtins, bool foldLibraryK, LibraryMemberConstantCallback libraryMemberConstantCb, - AstNode* root + AstNode* root, + AstNameTable& names ) { - ConstantVisitor visitor{constants, variables, locals, builtins, foldLibraryK, libraryMemberConstantCb}; + ConstantVisitor visitor{constants, variables, locals, builtins, foldLibraryK, libraryMemberConstantCb, names}; root->visit(&visitor); } diff --git a/Compiler/src/ConstantFolding.h b/Compiler/src/ConstantFolding.h index 2653c064..0a56dc5c 100644 --- a/Compiler/src/ConstantFolding.h +++ b/Compiler/src/ConstantFolding.h @@ -53,7 +53,8 @@ void foldConstants( const DenseHashMap* builtins, bool foldLibraryK, LibraryMemberConstantCallback libraryMemberConstantCb, - AstNode* root + AstNode* root, + AstNameTable& names ); } // namespace Compile diff --git a/tests/AssemblyBuilderX64.test.cpp b/tests/AssemblyBuilderX64.test.cpp index d90041f6..6acb348e 100644 --- a/tests/AssemblyBuilderX64.test.cpp +++ b/tests/AssemblyBuilderX64.test.cpp @@ -7,8 +7,6 @@ #include -LUAU_DYNAMIC_FASTFLAG(LuauCodeGenFixRexw) - using namespace Luau::CodeGen; using namespace Luau::CodeGen::X64; @@ -62,8 +60,6 @@ TEST_SUITE_BEGIN("x64Assembly"); TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseBinaryInstructionForms") { - ScopedFastFlag luauCodeGenFixRexw{DFFlag::LuauCodeGenFixRexw, true}; - // reg, reg SINGLE_COMPARE(add(rax, rcx), 0x48, 0x03, 0xc1); SINGLE_COMPARE(add(rsp, r12), 0x49, 0x03, 0xe4); @@ -197,8 +193,6 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "BaseUnaryInstructionForms") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfMov") { - ScopedFastFlag luauCodeGenFixRexw{DFFlag::LuauCodeGenFixRexw, true}; - SINGLE_COMPARE(mov(rcx, 1), 0x48, 0xb9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); SINGLE_COMPARE(mov64(rcx, 0x1234567812345678ll), 0x48, 0xb9, 0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12); SINGLE_COMPARE(mov(ecx, 2), 0xb9, 0x02, 0x00, 0x00, 0x00); @@ -238,8 +232,6 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfMovExtended") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfTest") { - ScopedFastFlag luauCodeGenFixRexw{DFFlag::LuauCodeGenFixRexw, true}; - SINGLE_COMPARE(test(al, 8), 0xf6, 0xc0, 0x08); SINGLE_COMPARE(test(eax, 8), 0xf7, 0xc0, 0x08, 0x00, 0x00, 0x00); SINGLE_COMPARE(test(rax, 8), 0x48, 0xf7, 0xc0, 0x08, 0x00, 0x00, 0x00); @@ -254,8 +246,6 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfTest") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfShift") { - ScopedFastFlag luauCodeGenFixRexw{DFFlag::LuauCodeGenFixRexw, true}; - SINGLE_COMPARE(shl(al, 1), 0xd0, 0xe0); SINGLE_COMPARE(shl(al, cl), 0xd2, 0xe0); SINGLE_COMPARE(shl(sil, cl), 0x40, 0xd2, 0xe6); @@ -281,8 +271,6 @@ TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfLea") TEST_CASE_FIXTURE(AssemblyBuilderX64Fixture, "FormsOfSetcc") { - ScopedFastFlag luauCodeGenFixRexw{DFFlag::LuauCodeGenFixRexw, true}; - SINGLE_COMPARE(setcc(ConditionX64::NotEqual, bl), 0x0f, 0x95, 0xc3); SINGLE_COMPARE(setcc(ConditionX64::NotEqual, dil), 0x40, 0x0f, 0x95, 0xc7); SINGLE_COMPARE(setcc(ConditionX64::BelowEqual, byte[rcx]), 0x0f, 0x96, 0x01); diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 9ced1c16..74692ede 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -19,11 +19,12 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauEagerGeneralization4) -LUAU_FASTFLAG(LuauImplicitTableIndexerKeys3) LUAU_FASTFLAG(LuauIncludeBreakContinueStatements) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSuggestHotComments) +LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) +LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) +LUAU_FASTFLAG(LuauAutocompleteAttributes) using namespace Luau; @@ -99,6 +100,13 @@ struct ACFixtureImpl : BaseType else if (c == '@') { // skip the '@' character + if (prevChar == '\\') + { + // escaped @, prevent prevChar to be equal to '@' on next loop + c = '\0'; + // replace escaping '\' with '@' + filteredSource.back() = '@'; + } } else { @@ -4478,10 +4486,8 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union") auto ac = autocomplete('1'); - if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization4) + if (FFlag::LuauSolverV2) { - // This `if` statement is because `LuauEagerGeneralization4` - // sets some flags CHECK(ac.entryMap.count("BaseMethod") > 0); CHECK(ac.entryMap.count("Method") > 0); } @@ -4613,11 +4619,8 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_in_type_assertion") TEST_CASE_FIXTURE(ACFixture, "autocomplete_implicit_named_index_index_expr") { - ScopedFastFlag sffs[] = { - // Somewhat surprisingly, the old solver didn't cover this case. - {FFlag::LuauSolverV2, true}, - {FFlag::LuauImplicitTableIndexerKeys3, true}, - }; + // Somewhat surprisingly, the old solver didn't cover this case. + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; check(R"( type Constraint = "A" | "B" | "C" @@ -4640,10 +4643,7 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_implicit_named_index_index_expr") TEST_CASE_FIXTURE(ACFixture, "autocomplete_implicit_named_index_index_expr_without_annotation") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauImplicitTableIndexerKeys3, true}, - }; + ScopedFastFlag sffs{FFlag::LuauSolverV2, true}; check(R"( local foo = { @@ -4897,4 +4897,108 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_suggest_hot_comments") CHECK(ac.entryMap.count("optimize")); } +TEST_CASE_FIXTURE(ACFixture, "autocomplete_method_in_unfinished_repeat_body_eof") +{ + ScopedFastFlag sff{FFlag::LuauUnfinishedRepeatAncestryFix, true}; + + check(R"(local t = {} + function t:Foo() end + repeat + t:@1)"); + + auto ac = autocomplete('1'); + + CHECK(!ac.entryMap.empty()); + CHECK(ac.entryMap.count("Foo")); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_method_in_unfinished_repeat_body_not_eof") +{ + ScopedFastFlag sff{FFlag::LuauUnfinishedRepeatAncestryFix, true}; + + check(R"(local t = {} + function t:Foo() end + repeat + t:@1 + )"); + + auto ac = autocomplete('1'); + + CHECK(!ac.entryMap.empty()); + CHECK(ac.entryMap.count("Foo")); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_method_in_unfinished_while_body") +{ + check(R"(local t = {} + function t:Foo() end + while true do + t:@1)"); + + auto ac = autocomplete('1'); + + CHECK(!ac.entryMap.empty()); + CHECK(ac.entryMap.count("Foo")); +} + +TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_empty_attribute") +{ + ScopedFastFlag sff[]{{FFlag::LuauParametrizedAttributeSyntax, true}, {FFlag::LuauAutocompleteAttributes, true}}; + + check(R"( + \@@1 + function foo() return 42 end + )"); + + auto ac = autocomplete('1'); + CHECK_EQ(ac.entryMap.count("deprecated"), 1); + CHECK_EQ(ac.entryMap.count("checked"), 1); + CHECK_EQ(ac.entryMap.count("native"), 1); +} + +TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_deprecated_attribute") +{ + ScopedFastFlag sff[]{{FFlag::LuauParametrizedAttributeSyntax, true}, {FFlag::LuauAutocompleteAttributes, true}}; + + check(R"( + \@dep@1 + function foo() return 42 end + )"); + + auto ac = autocomplete('1'); + CHECK_EQ(ac.entryMap.count("deprecated"), 1); + CHECK_EQ(ac.entryMap.count("checked"), 1); + CHECK_EQ(ac.entryMap.count("native"), 1); +} + +TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_empty_braced_attribute") +{ + ScopedFastFlag sff[]{{FFlag::LuauParametrizedAttributeSyntax, true}, {FFlag::LuauAutocompleteAttributes, true}}; + + check(R"( + \@[@1] + function foo() return 42 end + )"); + + auto ac = autocomplete('1'); + CHECK_EQ(ac.entryMap.count("deprecated"), 1); + CHECK_EQ(ac.entryMap.count("checked"), 1); + CHECK_EQ(ac.entryMap.count("native"), 1); +} + +TEST_CASE_FIXTURE(ACBuiltinsFixture, "autocomplete_deprecated_braced_attribute") +{ + ScopedFastFlag sff[]{{FFlag::LuauParametrizedAttributeSyntax, true}, {FFlag::LuauAutocompleteAttributes, true}}; + + check(R"( + \@[dep@1] + function foo() return 42 end + )"); + + auto ac = autocomplete('1'); + CHECK_EQ(ac.entryMap.count("deprecated"), 1); + CHECK_EQ(ac.entryMap.count("checked"), 1); + CHECK_EQ(ac.entryMap.count("native"), 1); +} + TEST_SUITE_END(); diff --git a/tests/Compiler.test.cpp b/tests/Compiler.test.cpp index 1f1217bd..182d48e0 100644 --- a/tests/Compiler.test.cpp +++ b/tests/Compiler.test.cpp @@ -23,6 +23,8 @@ LUAU_FASTINT(LuauCompileInlineThresholdMaxBoost) LUAU_FASTINT(LuauCompileLoopUnrollThreshold) LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost) LUAU_FASTINT(LuauRecursionLimit) +LUAU_FASTFLAG(LuauStringConstFolding) +LUAU_FASTFLAG(LuauCompileTypeofFold) using namespace Luau; @@ -359,28 +361,31 @@ RETURN R0 0 TEST_CASE("ConcatChainOptimization") { - CHECK_EQ("\n" + compileFunction0("return '1' .. '2'"), R"( -LOADK R1 K0 ['1'] -LOADK R2 K1 ['2'] -CONCAT R0 R1 R2 -RETURN R0 1 + CHECK_EQ("\n" + compileFunction0("local a, b = ...; return a .. b"), R"( +GETVARARGS R0 2 +MOVE R3 R0 +MOVE R4 R1 +CONCAT R2 R3 R4 +RETURN R2 1 )"); - CHECK_EQ("\n" + compileFunction0("return '1' .. '2' .. '3'"), R"( -LOADK R1 K0 ['1'] -LOADK R2 K1 ['2'] -LOADK R3 K2 ['3'] -CONCAT R0 R1 R3 -RETURN R0 1 + CHECK_EQ("\n" + compileFunction0("local a, b, c = ...; return a .. b .. c"), R"( +GETVARARGS R0 3 +MOVE R4 R0 +MOVE R5 R1 +MOVE R6 R2 +CONCAT R3 R4 R6 +RETURN R3 1 )"); - CHECK_EQ("\n" + compileFunction0("return ('1' .. '2') .. '3'"), R"( -LOADK R3 K0 ['1'] -LOADK R4 K1 ['2'] -CONCAT R1 R3 R4 -LOADK R2 K2 ['3'] -CONCAT R0 R1 R2 -RETURN R0 1 + CHECK_EQ("\n" + compileFunction0("local a, b, c = ...; return (a .. b) .. c"), R"( +GETVARARGS R0 3 +MOVE R6 R0 +MOVE R7 R1 +CONCAT R4 R6 R7 +MOVE R5 R2 +CONCAT R3 R4 R5 +RETURN R3 1 )"); } @@ -7849,6 +7854,8 @@ RETURN R1 -1 TEST_CASE("BuiltinFolding") { + ScopedFastFlag luauCompileTypeofFold{FFlag::LuauCompileTypeofFold, true}; + CHECK_EQ( "\n" + compileFunction( R"( @@ -7904,6 +7911,7 @@ return bit32.replace(100, 1, 0), math.log(100, 10), typeof(nil), + type(vector.create(1, 0, 0)), (type("fin")) )", 0, @@ -7961,14 +7969,17 @@ LOADN R47 1 LOADN R48 101 LOADN R49 2 LOADK R50 K3 ['nil'] -LOADK R51 K4 ['string'] -RETURN R0 52 +LOADK R51 K4 ['vector'] +LOADK R52 K5 ['string'] +RETURN R0 53 )" ); } TEST_CASE("BuiltinFoldingProhibited") { + ScopedFastFlag luauCompileTypeofFold{FFlag::LuauCompileTypeofFold, true}; + CHECK_EQ( "\n" + compileFunction( R"( @@ -7982,7 +7993,8 @@ return bit32.band(1, true), bit32.bxor(1, true), bit32.btest(1, true), - math.min(1, true) + math.min(1, true), + typeof(vector.create(1, 0, 0)) )", 0, 2 @@ -8037,7 +8049,11 @@ FASTCALL2K 19 R10 K3 L9 [true] LOADK R11 K3 [true] GETIMPORT R9 26 [math.min] CALL R9 2 1 -L9: RETURN R0 10 +L9: LOADK R11 K27 [1, 0, 0] +FASTCALL1 44 R11 L10 +GETIMPORT R10 29 [typeof] +CALL R10 1 1 +L10: RETURN R0 11 )" ); } @@ -9385,4 +9401,16 @@ RETURN R1 7 ); } +TEST_CASE("ConstStringFolding") +{ + ScopedFastFlag sff{FFlag::LuauStringConstFolding, true}; + CHECK_EQ( + "\n" + compileFunction(R"(local hello = "hello"; local world = "world"; return hello .. " " .. world)", 0, 2), + R"( +LOADK R0 K0 ['hello world'] +RETURN R0 1 +)" + ); +} + TEST_SUITE_END(); diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index f4537a75..cb4a55a1 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -45,6 +45,7 @@ LUAU_FASTFLAG(LuauCodeGenVectorLerp) LUAU_DYNAMIC_FASTFLAG(LuauXpcallContNoYield) LUAU_FASTFLAG(LuauCodeGenBetterBytecodeAnalysis) LUAU_FASTFLAG(LuauCodeGenRegAutoSpillA64) +LUAU_FASTFLAG(LuauCodeGenRestoreFromSplitStore) static lua_CompileOptions defaultOptions() { @@ -3151,6 +3152,7 @@ TEST_CASE("Native") { ScopedFastFlag luauCodeGenDirectBtest{FFlag::LuauCodeGenDirectBtest, true}; 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()) diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index cea88e62..b0f8b650 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -29,13 +29,13 @@ LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauBetterReverseDependencyTracking) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauFragmentRequiresCanBeResolvedToAModule) -LUAU_FASTFLAG(LuauFragmentAutocompleteTracksRValueRefinements) LUAU_FASTFLAG(LuauPopulateSelfTypesInFragment) LUAU_FASTFLAG(LuauParseIncompleteInterpStringsWithLocation) LUAU_FASTFLAG(LuauForInProvidesRecommendations) LUAU_FASTFLAG(LuauFragmentAutocompleteTakesInnermostRefinement) LUAU_FASTFLAG(LuauSuggestHotComments) LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) +LUAU_FASTFLAG(LuauUnfinishedRepeatAncestryFix) static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { @@ -65,7 +65,6 @@ struct FragmentAutocompleteFixtureImpl : BaseType { static_assert(std::is_base_of_v, "BaseType must be a descendant of Fixture"); - ScopedFastFlag sffLuauFragmentAutocompleteTracksRValueRefinement{FFlag::LuauFragmentAutocompleteTracksRValueRefinements, true}; ScopedFastFlag sffLuauPopulateSelfTypesInFragment{FFlag::LuauPopulateSelfTypesInFragment, true}; ScopedFastFlag luauParseIncompleteInterpStringsWithLocation{FFlag::LuauParseIncompleteInterpStringsWithLocation, true}; @@ -79,6 +78,53 @@ struct FragmentAutocompleteFixtureImpl : BaseType return this->check(source, getOptions()); } + std::string cleanMarkers(const std::string& source) + { + markerPosition.clear(); + std::string filteredSource; + filteredSource.reserve(source.size()); + + Position curPos(0, 0); + char prevChar{}; + for (char c : source) + { + if (prevChar == '@') + { + LUAU_ASSERT("Illegal marker character" && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z'))); + LUAU_ASSERT("Duplicate marker found" && markerPosition.count(c) == 0); + markerPosition.insert(std::pair{c, curPos}); + } + else if (c == '@') + { + // skip the '@' character + if (prevChar == '\\') + { + // escaped @, prevent prevChar to be equal to '@' on next loop + c = '\0'; + // replace escaping '\' with '@' + filteredSource.back() = '@'; + } + } + else + { + filteredSource.push_back(c); + if (c == '\n') + { + curPos.line++; + curPos.column = 0; + } + else + { + curPos.column++; + } + } + prevChar = c; + } + LUAU_ASSERT("Digit expected after @ symbol" && prevChar != '@'); + + return filteredSource; + } + ParseResult parseHelper_(SourceModule& source, std::string document) { ParseOptions parseOptions; @@ -152,16 +198,21 @@ struct FragmentAutocompleteFixtureImpl : BaseType void autocompleteFragmentInNewSolver( const std::string& document, const std::string& updated, - Position cursorPos, + char marker, std::function assertions, std::optional fragmentEndPosition = std::nullopt ) { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + + std::string cleanDocument = cleanMarkers(document); + std::string cleanUpdated = cleanMarkers(updated); + Position cursorPos = getPosition(marker); + this->getFrontend().setLuauSolverMode(SolverMode::New); this->check(document, getOptions()); - FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); + FragmentAutocompleteStatusResult result = autocompleteFragment(cleanUpdated, cursorPos, fragmentEndPosition); CHECK(result.status != FragmentAutocompleteStatus::InternalIce); assertions(result); } @@ -169,16 +220,21 @@ struct FragmentAutocompleteFixtureImpl : BaseType void autocompleteFragmentInOldSolver( const std::string& document, const std::string& updated, - Position cursorPos, + char marker, std::function assertions, std::optional fragmentEndPosition = std::nullopt ) { ScopedFastFlag sff{FFlag::LuauSolverV2, false}; + + std::string cleanDocument = cleanMarkers(document); + std::string cleanUpdated = cleanMarkers(updated); + Position cursorPos = getPosition(marker); + this->getFrontend().setLuauSolverMode(SolverMode::Old); this->check(document, getOptions()); - FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); + FragmentAutocompleteStatusResult result = autocompleteFragment(cleanUpdated, cursorPos, fragmentEndPosition); CHECK(result.status != FragmentAutocompleteStatus::InternalIce); assertions(result); } @@ -186,24 +242,28 @@ struct FragmentAutocompleteFixtureImpl : BaseType void autocompleteFragmentInBothSolvers( const std::string& document, const std::string& updated, - Position cursorPos, + char marker, std::function assertions, std::optional fragmentEndPosition = std::nullopt ) { + std::string cleanDocument = cleanMarkers(document); + std::string cleanUpdated = cleanMarkers(updated); + Position cursorPos = getPosition(marker); + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; this->getFrontend().setLuauSolverMode(SolverMode::New); - this->check(document, getOptions()); + this->check(cleanDocument, getOptions()); - FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); + FragmentAutocompleteStatusResult result = autocompleteFragment(cleanUpdated, cursorPos, fragmentEndPosition); CHECK(result.status != FragmentAutocompleteStatus::InternalIce); assertions(result); ScopedFastFlag _{FFlag::LuauSolverV2, false}; this->getFrontend().setLuauSolverMode(SolverMode::Old); - this->check(document, getOptions()); + this->check(cleanDocument, getOptions()); - result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); + result = autocompleteFragment(cleanUpdated, cursorPos, fragmentEndPosition); CHECK(result.status != FragmentAutocompleteStatus::InternalIce); assertions(result); } @@ -240,6 +300,16 @@ struct FragmentAutocompleteFixtureImpl : BaseType return *source; } + const Position& getPosition(char marker) const + { + auto i = markerPosition.find(marker); + LUAU_ASSERT(i != markerPosition.end()); + return i->second; + } + + // Maps a marker character (0-9 inclusive) to a position in the source code. + std::map markerPosition; + private: std::unique_ptr source = std::make_unique(); }; @@ -1621,13 +1691,13 @@ local tbl = { abc = 1234} )"; const std::string updated = R"( local tbl = { abc = 1234} -tbl. +tbl. @1 )"; autocompleteFragmentInBothSolvers( source, updated, - Position{2, 5}, + '1', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1647,12 +1717,12 @@ local tbl = { abc = { def = 1234, egh = false } } )"; const std::string updated = R"( local tbl = { abc = { def = 1234, egh = false } } -tbl.abc. +tbl.abc.@1 )"; autocompleteFragmentInBothSolvers( source, updated, - Position{2, 8}, + '1', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1668,21 +1738,21 @@ tbl.abc. TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "multiple_functions_complex") { - const std::string text = R"( local function f1(a1) - local l1 = 1; - g1 = 1; + const std::string text = R"(@1 local function f1(a1)@2 + local l1 = 1;@3 + g1 = 1;@4 end - +@5 local function f2(a2) - local l2 = 1; + local l2 = 1;@6 g2 = 1; -end +end @7 )"; autocompleteFragmentInBothSolvers( text, text, - Position{0, 0}, + '1', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1701,7 +1771,7 @@ end autocompleteFragmentInBothSolvers( text, text, - Position{0, 22}, + '2', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1720,7 +1790,7 @@ end autocompleteFragmentInBothSolvers( text, text, - Position{1, 17}, + '3', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1739,7 +1809,7 @@ end autocompleteFragmentInBothSolvers( text, text, - Position{2, 11}, + '4', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1758,7 +1828,7 @@ end autocompleteFragmentInBothSolvers( text, text, - Position{4, 0}, + '5', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1777,7 +1847,7 @@ end autocompleteFragmentInBothSolvers( text, text, - Position{6, 17}, + '6', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1796,7 +1866,7 @@ end autocompleteFragmentInBothSolvers( text, text, - Position{8, 4}, + '7', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1826,14 +1896,14 @@ end type Table = { a: number, b: number } do type Table = { x: string, y: string } - local a : T + local a : T@1 end )"; autocompleteFragmentInBothSolvers( source, updated, - Position{4, 15}, + '1', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1861,13 +1931,13 @@ type Table = { a: number, b: number } do type Table = { x: string, y: string } end -local a : T +local a : T@1 )"; autocompleteFragmentInBothSolvers( source, updated, - Position{5, 11}, + '1', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1886,12 +1956,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "nested_recursive_function") { const std::string source = R"( function foo() -end +@1end )"; autocompleteFragmentInBothSolvers( source, source, - Position{2, 0}, + '1', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1905,13 +1975,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "string_literal_with_override") { const std::string source = R"( function foo(bar: string) end -foo("abc") +foo("a@1bc") )"; autocompleteFragmentInBothSolvers( source, source, - Position{2, 6}, + '1', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -1926,8 +1996,8 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "empty_program") { autocompleteFragmentInBothSolvers( "", - "", - Position{0, 0}, + "@1", + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -1941,11 +2011,11 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "empty_program") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "local_initializer") { - const std::string source = "local a ="; + const std::string source = "local a =@1"; autocompleteFragmentInBothSolvers( source, source, - Position{0, 9}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -1960,12 +2030,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "local_initializer") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "leave_numbers_alone") { - const std::string source = "local a = 3."; + const std::string source = "local a = 3.@1"; autocompleteFragmentInBothSolvers( source, source, - Position{0, 12}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -1978,12 +2048,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "leave_numbers_alone") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "user_defined_globals") { - const std::string source = "local myLocal = 4; "; + const std::string source = "local myLocal = 4;@1 "; autocompleteFragmentInBothSolvers( source, source, - Position{0, 18}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2002,16 +2072,16 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "dont_suggest_local_before_its_de const std::string source = R"( local myLocal = 4 function abc() - local myInnerLocal = 1 - +@1 local myInnerLocal = 1 +@2 end - )"; +@3 )"; // autocomplete after abc but before myInnerLocal autocompleteFragmentInBothSolvers( source, source, - Position{3, 0}, + '1', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -2024,7 +2094,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "dont_suggest_local_before_its_de autocompleteFragmentInBothSolvers( source, source, - Position{4, 0}, + '2', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -2038,7 +2108,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "dont_suggest_local_before_its_de autocompleteFragmentInBothSolvers( source, source, - Position{6, 0}, + '3', [](FragmentAutocompleteStatusResult& fragment) { REQUIRE(fragment.result); @@ -2054,14 +2124,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "nested_recursive_function") const std::string source = R"( local function outer() local function inner() - end +@1 end end )"; autocompleteFragmentInBothSolvers( source, source, - Position{3, 0}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2076,14 +2146,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "user_defined_local_functions_in_ { const std::string source = R"( local function abc() - +@1 end )"; // Autocomplete inside of abc autocompleteFragmentInBothSolvers( source, source, - Position{2, 0}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2103,11 +2173,11 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "global_functions_are_not_scoped_ end end - )"; +@1 )"; autocompleteFragmentInBothSolvers( source, source, - Position{6, 0}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2128,12 +2198,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "local_functions_fall_out_of_scop end end - )"; +@1 )"; autocompleteFragmentInBothSolvers( source, source, - Position{6, 0}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2149,13 +2219,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "function_parameters") const std::string source = R"( function abc(test) - end +@1 end )"; autocompleteFragmentInBothSolvers( source, source, - Position{3, 0}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2170,13 +2240,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "unsealed_table") const std::string source = R"( local tbl = {} tbl.prop = 5 - tbl. + tbl.@1 )"; autocompleteFragmentInBothSolvers( source, source, - Position{3, 12}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2194,13 +2264,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "unsealed_table_2") local tbl = {} local inner = { prop = 5 } tbl.inner = inner - tbl.inner. + tbl.inner.@1 )"; autocompleteFragmentInBothSolvers( source, source, - Position{4, 18}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2218,13 +2288,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "cyclic_table") local abc = {} local def = { abc = abc } abc.def = def - abc.def. + abc.def.@1 )"; autocompleteFragmentInBothSolvers( source, source, - Position{4, 16}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2248,14 +2318,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "table_union") type t1 = { a1 : string, b2 : number } type t2 = { b2 : string, c3 : string } function func(abc : t1 | t2) - abc. + abc.@1 end )"; autocompleteFragmentInBothSolvers( source, updated, - Position{4, 16}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2280,14 +2350,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "table_intersection") type t1 = { a1 : string, b2 : number } type t2 = { b2 : number, c3 : string } function func(abc : t1 & t2) - abc. + abc.@1 end )"; autocompleteFragmentInBothSolvers( source, updated, - Position{4, 16}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2303,7 +2373,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "table_intersection") TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "get_suggestions_for_the_very_start_of_the_script") { - const std::string source = R"( + const std::string source = R"(@1 function aaa() end )"; @@ -2311,7 +2381,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "get_suggestions_for_the_very_sta autocompleteFragmentInBothSolvers( source, source, - Position{0, 0}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2326,7 +2396,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "studio_ice_1") { const std::string source = R"( --Woop -@native +\@native local function test() end @@ -2334,13 +2404,13 @@ end const std::string updated = R"( --Woop -@native +\@native local function test() end -function a +function a@1 )"; - autocompleteFragmentInBothSolvers(source, updated, Position{6, 10}, [](FragmentAutocompleteStatusResult& result) {}); + autocompleteFragmentInBothSolvers(source, updated, '1', [](FragmentAutocompleteStatusResult& result) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "method_call_inside_function_body") @@ -2357,14 +2427,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "method_call_inside_function_body local game = { GetService=function(s) return 'hello' end } function a() - game: + game:@1 end )"; autocompleteFragmentInBothSolvers( source, updated, - Position{4, 17}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2383,14 +2453,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tbl_function_parameter") --!strict type Foo = {x : number, y : number} local function func(abc : Foo) - abc. + abc.@1 end )"; autocompleteFragmentInBothSolvers( source, source, - Position{4, 7}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2407,14 +2477,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tbl_local_function_parameter") --!strict type Foo = {x : number, y : number} local function func(abc : Foo) - abc. + abc.@1 end )"; autocompleteFragmentInBothSolvers( source, source, - Position{4, 7}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2430,14 +2500,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "vec3_function_parameter" const std::string source = R"( --!strict local function func(abc : FakeVec) - abc. + abc.@1 end )"; autocompleteFragmentInBothSolvers( source, source, - Position{3, 7}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2453,14 +2523,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "vec3_local_function_para const std::string source = R"( --!strict local function func(abc : FakeVec) - abc. + abc.@1 end )"; autocompleteFragmentInBothSolvers( source, source, - Position{3, 7}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2478,14 +2548,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "function_parameter_not_r local function foo(abd: FakeVec) end local function bar(abc : FakeVec) - a + a@1 end )"; autocompleteFragmentInBothSolvers( source, source, - Position{5, 5}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2502,12 +2572,12 @@ local t = 1 )"; const std::string updated = R"( t -)"; +@1)"; autocompleteFragmentInBothSolvers( source, updated, - Position{2, 1}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2525,13 +2595,13 @@ local t = 1 )"; const std::string updated = R"( local t = 1 -t +t@1 )"; autocompleteFragmentInBothSolvers( source, updated, - Position{2, 1}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2553,13 +2623,13 @@ l )"; const std::string updated = R"( local t = 1 -l +l@1 )"; autocompleteFragmentInBothSolvers( source, updated, - Position{2, 1}, + '1', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.status == FragmentAutocompleteStatus::Success); @@ -2574,14 +2644,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "do_not_recommend_results_in_mult std::string source = R"(--[[ )"; std::string dest = R"(--[[ -a +a@1 )"; autocompleteFragmentInBothSolvers( source, dest, - Position{1, 1}, + '1', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2595,14 +2665,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_simple") -- sel -- retur -- fo --- if +-- if @1 -- end -- the )"; autocompleteFragmentInBothSolvers( source, source, - Position{4, 6}, + '1', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2615,21 +2685,21 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments_blocks") const std::string source = R"( --[[ comment 1 -]] local +@1]]@2 local -- [[ comment 2]] -- -- sdfsdfsdf --[[comment 3]] ---[[ +--[[ @3 foo -bar +@4bar baz ]] )"; autocompleteFragmentInBothSolvers( source, source, - Position{3, 0}, + '1', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2639,7 +2709,7 @@ baz autocompleteFragmentInBothSolvers( source, source, - Position{3, 2}, + '2', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2650,7 +2720,7 @@ baz autocompleteFragmentInBothSolvers( source, source, - Position{8, 6}, + '3', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2660,7 +2730,7 @@ baz autocompleteFragmentInBothSolvers( source, source, - Position{10, 0}, + '4', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2671,16 +2741,16 @@ baz TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "no_recs_for_comments") { const std::string source = R"( --- sel --- retur --- fo ---[[ sel ]] -local -- hello +-- sel @1 +-- retur @2 +-- fo @3 +--[[ sel @4]] +local @5 -- hell@6o )"; autocompleteFragmentInBothSolvers( source, source, - Position{1, 7}, + '1', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2690,7 +2760,7 @@ local -- hello autocompleteFragmentInBothSolvers( source, source, - Position{2, 9}, + '2', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2700,7 +2770,7 @@ local -- hello autocompleteFragmentInBothSolvers( source, source, - Position{3, 6}, + '3', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2710,7 +2780,7 @@ local -- hello autocompleteFragmentInBothSolvers( source, source, - Position{4, 9}, + '4', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2720,7 +2790,7 @@ local -- hello autocompleteFragmentInBothSolvers( source, source, - Position{5, 6}, + '5', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2731,7 +2801,7 @@ local -- hello autocompleteFragmentInBothSolvers( source, source, - Position{5, 14}, + '6', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2747,12 +2817,12 @@ if x == 5 )"; const std::string updated = R"( local x = 5 -if x == 5 then -- a comment +if x == 5 then -- a comment @1 )"; autocompleteFragmentInBothSolvers( source, updated, - Position{2, 28}, + '1', [](FragmentAutocompleteStatusResult& frag) { CHECK(frag.result == std::nullopt); @@ -2768,13 +2838,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_handles_pa )"; const std::string updated = R"( -type A = <>random non code text here +type A = <>random non code text here @1 )"; autocompleteFragmentInBothSolvers( source, updated, - Position{1, 38}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2791,13 +2861,13 @@ return { x = 0 } fileResolver.source["MainModule"] = R"( local result = require(script.A) -local x = 1 + result. +local x = 1 + result.@1 )"; autocompleteFragmentInBothSolvers( fileResolver.source["MainModule"], fileResolver.source["MainModule"], - Position{2, 21}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2827,11 +2897,11 @@ local m = {} -- and here function m:m1() end type nt = typeof(m) -l +l @1 return m )"; - autocompleteFragmentInBothSolvers(source, updated, Position{6, 2}, [](FragmentAutocompleteStatusResult& _) {}); + autocompleteFragmentInBothSolvers(source, updated, '1', [](FragmentAutocompleteStatusResult& _) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "duped_alias") @@ -2842,7 +2912,7 @@ type a = typeof({}) )"; const std::string dest = R"( type a = typeof({}) -type a = typeof({}) +type a = typeof({})@1 )"; // Re-parsing and typechecking a type alias in the fragment that was defined in the base module will assert in ConstraintGenerator::checkAliases @@ -2850,7 +2920,7 @@ type a = typeof({}) autocompleteFragmentInBothSolvers( source, dest, - Position{2, 20}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result); @@ -2868,7 +2938,7 @@ type U = {f : number, g : U} )"; const std::string dest = R"( type U = {f : number, g : V} -type V = {h : number, i : U?} +type V = {h : number, i : U?} @1 )"; // Re-parsing and typechecking a type alias in the fragment that was defined in the base module will assert in ConstraintGenerator::checkAliases @@ -2876,7 +2946,7 @@ type V = {h : number, i : U?} autocompleteFragmentInBothSolvers( source, dest, - Position{2, 30}, + '1', [](FragmentAutocompleteStatusResult& frag) { REQUIRE(frag.result->freshScope); @@ -2910,12 +2980,12 @@ local Camera = workspace.CurrentCamera; UserInputService.InputBegan:Connect(function(Input) if (Input.KeyCode == Enum.KeyCode.One) then local Up = Input.Foo - local Vector = -(Up:Unit()) + local Vector = -(Up:Unit()) @1 end end) )"; - autocompleteFragmentInBothSolvers(source, dest, Position{8, 36}, [](FragmentAutocompleteStatusResult& _) {}); + autocompleteFragmentInBothSolvers(source, dest, '1', [](FragmentAutocompleteStatusResult& _) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "fragment_autocomplete_ensures_memory_isolation") @@ -2998,11 +3068,11 @@ return module )"; const std::string updated = R"(local module = {} -function module.f +function module.f@1 return module )"; - autocompleteFragmentInBothSolvers(source, updated, Position{1, 18}, [](FragmentAutocompleteStatusResult& result) {}); + autocompleteFragmentInBothSolvers(source, updated, '1', [](FragmentAutocompleteStatusResult& result) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "ice_caused_by_mixed_mode_use") @@ -3010,16 +3080,16 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "ice_caused_by_mixed_mode const std::string source = std::string("--[[\n\tPackage link auto-generated by Rotriever\n]]\nlocal PackageIndex = script.Parent._Index\n\nlocal Package = ") + "require(PackageIndex[\"ReactOtter\"][\"ReactOtter\"])\n\nexport type Goal = Package.Goal\nexport type SpringOptions " + - "= Package.SpringOptions\n\n\nreturn Pa"; + "= Package.SpringOptions\n\n\nreturn Pa@1"; autocompleteFragmentInBothSolvers( source, source, - Position{11, 9}, + '1', [](FragmentAutocompleteStatusResult& _) { } ); - autocompleteFragmentInBothSolvers(source, source, Position{11, 9}, [](auto& _) {}); + autocompleteFragmentInBothSolvers(source, source, '1', [](auto& _) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "free_type_in_old_solver_shouldnt_trigger_not_null_assertion") @@ -3044,10 +3114,10 @@ local e = foo().x local f = foo().y -z:a +z:a@1 )"; - autocompleteFragmentInBothSolvers(source, dest, Position{8, 3}, [](FragmentAutocompleteStatusResult& _) {}); + autocompleteFragmentInBothSolvers(source, dest, '1', [](FragmentAutocompleteStatusResult& _) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "interior_free_types_assertion_caused_by_free_type_inheriting_null_scope_from_table") @@ -3071,10 +3141,10 @@ local e = foo().x local f = foo().y -z = a.P.E +z = a.P.E@1 )"; - autocompleteFragmentInBothSolvers(source, dest, Position{8, 9}, [](FragmentAutocompleteStatusResult& _) {}); + autocompleteFragmentInBothSolvers(source, dest, '1', [](FragmentAutocompleteStatusResult& _) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "NotNull_nil_scope_assertion_caused_by_free_type_inheriting_null_scope_from_table") @@ -3098,10 +3168,10 @@ local e = foo().x local f = foo().y -z = a.P.E +z = a.P.E@1 )"; - autocompleteFragmentInBothSolvers(source, dest, Position{8, 9}, [](FragmentAutocompleteStatusResult& _) {}); + autocompleteFragmentInBothSolvers(source, dest, '1', [](FragmentAutocompleteStatusResult& _) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "user_defined_type_function_local") @@ -3158,14 +3228,14 @@ local testArr: {{a: number, b: number}} = { } for _, v in testArr do - print(v. + print(v.@1 end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{7, 12}, + '1', [](FragmentAutocompleteStatusResult& result) { CHECK(result.status != FragmentAutocompleteStatus::InternalIce); @@ -3197,14 +3267,14 @@ local testArr: {string} = { } for _, v in testArr do - print(v:) + print(v:@1) end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{7, 12}, + '1', [](FragmentAutocompleteStatusResult& result) { CHECK(result.status != FragmentAutocompleteStatus::InternalIce); @@ -3242,14 +3312,14 @@ function t.Do(fn : (Input) -> ()) end t.Do(function (f) - f. + f.@1 end) )"; autocompleteFragmentInBothSolvers( source, dest, - Position{10, 6}, + '1', [](FragmentAutocompleteStatusResult& status) { CHECK(FragmentAutocompleteStatus::Success == status.status); @@ -3264,12 +3334,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "differ_1") { const std::string source = R"()"; const std::string dest = R"(local tbl = { foo = 1, bar = 2 }; -tbl.b)"; +tbl.b@1)"; autocompleteFragmentInBothSolvers( source, dest, - Position{1, 5}, + '1', [](FragmentAutocompleteStatusResult& status) { CHECK(FragmentAutocompleteStatus::Success == status.status); @@ -3293,12 +3363,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_test_both_emp TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_test_both_empty_e2e") { - const std::string source = R"()"; + const std::string source = R"(@1)"; autocompleteFragmentInBothSolvers( source, source, - Position{0, 0}, + '1', [](FragmentAutocompleteStatusResult& result) { CHECK(FragmentAutocompleteStatus::Success == result.status); @@ -3324,13 +3394,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_ const std::string source = R"()"; const std::string dest = R"(local f1 = 4 local f2 = "a" -local f3 = f +local f3 = f@1 )"; autocompleteFragmentInBothSolvers( source, dest, - Position{2, 12}, + '1', [](FragmentAutocompleteStatusResult& result) { CHECK(FragmentAutocompleteStatus::Success == result.status); @@ -3346,14 +3416,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "block_diff_added_locals_ { const std::string source = R"()"; const std::string dest = R"(local f1 = 4 -local f2 = f +local f2 = f@1 local f3 = f )"; autocompleteFragmentInBothSolvers( source, dest, - Position{1, 12}, + '1', [](FragmentAutocompleteStatusResult& result) { CHECK(FragmentAutocompleteStatus::Success == result.status); @@ -3400,11 +3470,11 @@ local f2 = 2 + 1)"; const std::string dest = R"(local f1 = 4 local f2 = 3 local f3 = 3 -local foo = 8 + )"; +local foo = 8 + @1)"; autocompleteFragmentInBothSolvers( source, dest, - Position{3, 16}, + '1', [](FragmentAutocompleteStatusResult& result) { CHECK(FragmentAutocompleteStatus::Success == result.status); @@ -3440,12 +3510,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalReturn_a const std::string dest = R"(local function target(a: number, b: string) return a + #b end local function bar1(a: string) reutrn a .. 'x' end local function bar2(a: number) return -a end -return target(bar)"; +return target(bar@1)"; autocompleteFragmentInBothSolvers( source, dest, - Position{3, 17}, + '1', [](FragmentAutocompleteStatusResult& status) { CHECK(FragmentAutocompleteStatus::Success == status.status); @@ -3463,12 +3533,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "TypeCorrectLocalRank_ass const std::string dest = R"(local function target(a: number, b: string) return a + #b end local bar1 = 'hello' local bar2 = 4 -return target(bar)"; +return target(bar@1)"; autocompleteFragmentInBothSolvers( source, dest, - Position{3, 17}, + '1', [](FragmentAutocompleteStatusResult& status) { CHECK(FragmentAutocompleteStatus::Success == status.status); @@ -3486,12 +3556,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_finishe local foo = f)"; const std::string dest = R"(local function foobar(): string return "" end local foo = foobar() -foo:)"; +foo:@1)"; autocompleteFragmentInBothSolvers( source, dest, - Position{2, 4}, + '1', [](FragmentAutocompleteStatusResult& res) { CHECK(FragmentAutocompleteStatus::Success == res.status); @@ -3508,12 +3578,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "str_metata_table_redef") const std::string source = R"(local x = 42)"; const std::string dest = R"(local x = 42 local x = "" -x:)"; +x:@1)"; autocompleteFragmentInBothSolvers( source, dest, - Position{2, 2}, + '1', [](FragmentAutocompleteStatusResult& res) { CHECK(FragmentAutocompleteStatus::Success == res.status); @@ -3530,12 +3600,12 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "diff_multiple_blocks_on_ const std::string source = R"( do local function foo() end; local x = ""; end do local function bar() end)"; const std::string dest = R"( -do local function foo() end; local x = ""; end do local function bar() end local x = {a : number}; b end )"; +do local function foo() end; local x = ""; end do local function bar() end local x = {a : number}; b @1end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{1, 101}, + '1', [](FragmentAutocompleteStatusResult& res) { CHECK(FragmentAutocompleteStatus::Success == res.status); @@ -3563,7 +3633,7 @@ local function foo(t : {foo : string}) local x = t.foo do if t then - x: + x:@1 end end end @@ -3571,7 +3641,7 @@ end autocompleteFragmentInBothSolvers( source, dest, - Position{5, 14}, + '1', [](FragmentAutocompleteStatusResult& res) { CHECK(FragmentAutocompleteStatus::Success == res.status); @@ -3599,7 +3669,7 @@ local function foo(t : {foo : number}) if t then else local x = 4 - return x + t. + return x + t@1. end end end @@ -3607,7 +3677,7 @@ end autocompleteFragmentInBothSolvers( source, dest, - Position{6, 24}, + '1', [](FragmentAutocompleteStatusResult& res) { CHECK(FragmentAutocompleteStatus::Success == res.status); @@ -3635,11 +3705,11 @@ local Players = game:GetService("Players") Players.PlayerAdded:Connect(function(Player) for_,v in script.PlayerValue:GetChildren()do - v:L + v:L@1 end end) )"; - autocompleteFragmentInBothSolvers(source, dest, Position{5, 11}, [](auto& result) {}); + autocompleteFragmentInBothSolvers(source, dest, '1', [](auto& result) {}); } TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_cond_no_then_recs_then") @@ -3649,13 +3719,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_cond_no_then_recs_then") )"; const std::string dest = R"( -if x t +if x t@1 )"; autocompleteFragmentInBothSolvers( source, dest, - Position{1, 6}, + '1', [](auto& result) { REQUIRE(result.result); @@ -3674,14 +3744,14 @@ end const std::string dest = R"( if x then -e +e@1 end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{2, 1}, + '1', [](auto& result) { REQUIRE(result.result); @@ -3707,14 +3777,14 @@ type T = {xa : number, y : number} local t : T = {xa = 3, y = 3} if t.x then -elseif t.xa t +elseif t.xa t@1 end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{5, 13}, + '1', [](auto& result) { REQUIRE(result.result); @@ -3742,14 +3812,14 @@ type T = {xa : number, y : number} local t : T = {xa = 3, y = 3} if t.x then -elseif t. then +elseif t.@1 then end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{5, 9}, + '1', [](auto& result) { REQUIRE(result.result); @@ -3783,13 +3853,13 @@ type Result = Ok | Err local result = {} :: Result if result.type == "ok" then - result. + result.@1 end )"; autocompleteFragmentInOldSolver( source, dest, - Position{8, 11}, + '1', [](auto& result) { REQUIRE(result.result); @@ -3821,14 +3891,14 @@ type Result = Ok | Err local result = {} :: Result if result.type == "err" then - result. + result.@1 end )"; autocompleteFragmentInOldSolver( source, dest, - Position{8, 11}, + '1', [](auto& result) { REQUIRE(result.result); @@ -3861,13 +3931,13 @@ type Result = Ok | Err local result = {} :: Result if result.type == "ok" then - result. + result.@1 end )"; autocompleteFragmentInNewSolver( source, dest, - Position{8, 11}, + '1', [](auto& result) { REQUIRE(result.result); @@ -3899,14 +3969,14 @@ type Result = Ok | Err local result = {} :: Result if result.type == "err" then - result. + result.@1 end )"; autocompleteFragmentInNewSolver( source, dest, - Position{8, 11}, + '1', [](auto& result) { REQUIRE(result.result); @@ -3971,14 +4041,14 @@ type Service = { local Service: Service = {} function Service:Start() - self. + self.@1 end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{9, 9}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4040,14 +4110,14 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "self_with_fancy_metatabl end function Account:report() - print("My balance is: " .. self. ) + print("My balance is: " .. self.@1 ) end )"; autocompleteFragmentInNewSolver( source, dest, - Position{23, 44}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4080,14 +4150,14 @@ type Service = { local Service: Service = {} function Service:Start() - self: + self:@1 end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{9, 9}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4110,12 +4180,13 @@ local s = const std::string dest = R"( type Foo = {x : number, x1 : string, x2 : boolean} local e : Foo = {x = 1, x1 = "1", x2 = true} -local s = `{e. }` +local s = `{e.@1 }` )"; + autocompleteFragmentInBothSolvers( source, dest, - Position{3, 14}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4137,12 +4208,13 @@ print(`{e.x}`) const std::string dest = R"( type T = {x : number, y : number, z : number} local e = {x = 1, y = 2, z = 3} -print(`{e.x} {e.}`) +print(`{e.x} {e.@1}`) )"; + autocompleteFragmentInBothSolvers( source, dest, - Position{3, 16}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4159,13 +4231,13 @@ TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "for_in_should_rec") const std::string source = R"( type T = { x : {[number] : number}, y: number} local x : T = ({} :: T) -for _,n in pairs(x.) do +for _,n in pairs(x.@1) do end )"; autocompleteFragmentInBothSolvers( source, source, - Position{3, 19}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4187,13 +4259,13 @@ end const std::string dest = R"( type T = { x : {[number] : number}, y: number, z : number} local x : T = ({} :: T) -for i = x. +for i = x.@1 end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{3, 10}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4216,13 +4288,13 @@ end const std::string dest = R"( type T = { x : {[number] : number}, y: number, z : number} local x : T = ({} :: T) -for i = x.y, 100, x. do +for i = x.y, 100, x.@1 do end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{3, 20}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4245,13 +4317,13 @@ end const std::string dest = R"( type T = { x : {[number] : number}, y: number, z : number} local x : T = ({} :: T) -for i = x.y, x. do +for i = x.y, x.@1 do end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{3, 15}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4274,13 +4346,13 @@ end const std::string dest = R"( type T = { x : {[number] : number}, y: number, z : number} local x : T = ({} :: T) -for i = x.y, x. do +for i = x.y, x.@1 do end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{3, 15}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4317,7 +4389,7 @@ type UnionType = Type1 | Type2 local foo: UnionType? = nil if foo then if foo.Type == "Type2" then - foo. + foo.@1 end end )"; @@ -4325,7 +4397,7 @@ end autocompleteFragmentInBothSolvers( source, dest, - Position{9, 12}, + '1', [](FragmentAutocompleteStatusResult& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4338,11 +4410,11 @@ end TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "hot_comment_should_rec") { ScopedFastFlag sff{FFlag::LuauSuggestHotComments, true}; - const std::string source = R"(--!)"; + const std::string source = R"(--!@1)"; autocompleteFragmentInBothSolvers( source, source, - Position{0, 3}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4372,13 +4444,13 @@ type Pool = { numbers: { number }} local function foobar(p) local pool = p :: Pool - if #pool. + if #pool.@1 end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{5, 13}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4403,13 +4475,13 @@ type Pool = { x : number } local function foobar(p) local pool = p :: Pool - if -pool. + if -pool.@1 end )"; autocompleteFragmentInBothSolvers( source, dest, - Position{5, 13}, + '1', [](auto& result) { CHECK(!result.result->acResults.entryMap.empty()); @@ -4418,6 +4490,66 @@ end ); } +TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "method_in_unfinished_repeat_body_eof") +{ + ScopedFastFlag sff{FFlag::LuauUnfinishedRepeatAncestryFix, true}; + + std::string source = R"( +local t = {} +function t:Foo() end +repeat)"; + + std::string dest = R"( +local t = {} +function t:Foo() end +repeat +t:@1)"; + autocompleteFragmentInBothSolvers( + source, + dest, + '1', + [](auto& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("Foo")); + } + ); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "method_in_unfinished_repeat_body_not_eof") +{ + ScopedFastFlag sff{FFlag::LuauUnfinishedRepeatAncestryFix, true}; + + std::string source = R"( +local t = {} +function t:Foo() end +repeat +t + +local function whatever() end +)"; + + std::string dest = R"( +local t = {} +function t:Foo() end +repeat +t:@1 + +local function whatever() end +)"; + autocompleteFragmentInBothSolvers( + source, + dest, + '1', + [](auto& result) + { + CHECK(!result.result->acResults.entryMap.empty()); + CHECK(result.result->acResults.entryMap.count("Foo")); + } + ); +} + + // NOLINTEND(bugprone-unchecked-optional-access) TEST_SUITE_END(); diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 52b10a2f..c435f0ee 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -15,9 +15,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) -LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) @@ -231,10 +230,6 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "('a) -> 'a") TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - TableType tt; tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType}; TypeId numberArray = arena.addType(TableType{tt}); @@ -267,10 +262,6 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: number | string)) -> string?") TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - auto [aTy, aFree] = freshType(); auto [bTy, bFree] = freshType(); @@ -404,7 +395,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::LuauSubtypingReportGenericBoundMismatches, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} + {FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true} }; CheckResult result = check(R"( @@ -440,10 +431,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_argument_with_singleton_oss_1808") TEST_CASE_FIXTURE(BuiltinsFixture, "avoid_cross_module_mutation_in_bidirectional_inference") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - fileResolver.source["Module/ListFns"] = R"( local mod = {} function mod.findWhere(list, predicate): number? diff --git a/tests/InferPolarity.test.cpp b/tests/InferPolarity.test.cpp index 0d70d457..3cb4b6d6 100644 --- a/tests/InferPolarity.test.cpp +++ b/tests/InferPolarity.test.cpp @@ -8,16 +8,11 @@ using namespace Luau; -LUAU_FASTFLAG(LuauEagerGeneralization4); TEST_SUITE_BEGIN("InferPolarity"); TEST_CASE_FIXTURE(Fixture, "T where T = { m: (a) -> T }") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - TypeArena arena; ScopePtr globalScope = std::make_shared(getBuiltins()->anyTypePack); @@ -54,10 +49,6 @@ TEST_CASE_FIXTURE(Fixture, "T where T = { m: (a) -> T }") TEST_CASE_FIXTURE(Fixture, "({ read x: a, write x: b }) -> ()") { - ScopedFastFlag sffs[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - TypeArena arena; ScopePtr globalScope = std::make_shared(getBuiltins()->anyTypePack); diff --git a/tests/IrLowering.test.cpp b/tests/IrLowering.test.cpp index d8a3bfbe..2ae747fc 100644 --- a/tests/IrLowering.test.cpp +++ b/tests/IrLowering.test.cpp @@ -16,12 +16,12 @@ #include #include -LUAU_FASTFLAG(LuauCodeGenSimplifyImport2) LUAU_FASTFLAG(LuauCodeGenDirectBtest) LUAU_FASTFLAG(LuauVectorLerp) LUAU_FASTFLAG(LuauCompileVectorLerp) LUAU_FASTFLAG(LuauTypeCheckerVectorLerp) LUAU_FASTFLAG(LuauCodeGenVectorLerp) +LUAU_FASTFLAG(LuauCodeGenFMA) static void luauLibraryConstantLookup(const char* library, const char* member, Luau::CompileConstant* constant) { @@ -473,7 +473,43 @@ TEST_CASE("VectorLerp") {FFlag::LuauVectorLerp, true}, {FFlag::LuauCodeGenVectorLerp, true} }; - CHECK_EQ( + if (FFlag::LuauCodeGenFMA) + { + CHECK_EQ( + "\n" + getCodegenAssembly(R"( +local function vec3lerp(a: vector, b: vector, t: number) + return vector.lerp(a, b, t) +end +)"), + R"( +; function vec3lerp($arg0, $arg1, $arg2) line 2 +bb_0: + CHECK_TAG R0, tvector, exit(entry) + CHECK_TAG R1, tvector, exit(entry) + CHECK_TAG R2, tnumber, exit(entry) + JUMP bb_2 +bb_2: + JUMP bb_bytecode_1 +bb_bytecode_1: + CHECK_SAFE_ENV exit(2) + %15 = LOAD_TVALUE R0 + %16 = LOAD_TVALUE R1 + %17 = LOAD_DOUBLE R2 + %18 = NUM_TO_VEC %17 + %19 = NUM_TO_VEC 1 + %20 = SUB_VEC %16, %15 + MULADD_VEC %20, %18, %15 + SELECT_VEC %21, %16, %18, %19 + %23 = TAG_VECTOR %22 + STORE_TVALUE R3, %23 + INTERRUPT 8u + RETURN R3, 1i +)" + ); + } + else + { + CHECK_EQ( "\n" + getCodegenAssembly(R"( local function vec3lerp(a: vector, b: vector, t: number) return vector.lerp(a, b, t) @@ -504,7 +540,8 @@ end INTERRUPT 8u RETURN R3, 1i )" - ); + ); + } } TEST_CASE("ExtraMathMemoryOperands") @@ -547,8 +584,6 @@ end TEST_CASE("DseInitialStackState") { - ScopedFastFlag luauCodeGenSimplifyImport{FFlag::LuauCodeGenSimplifyImport2, true}; - CHECK_EQ( "\n" + getCodegenAssembly(R"( local function foo() @@ -1614,8 +1649,6 @@ end TEST_CASE("ForInManualAnnotation") { - ScopedFastFlag luauCodeGenSimplifyImport{FFlag::LuauCodeGenSimplifyImport2, true}; - CHECK_EQ( "\n" + getCodegenAssembly( R"( diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 5bf66e63..c2f44b5b 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -8,7 +8,6 @@ #include "doctest.h" LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauEagerGeneralization4); LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) using namespace Luau; diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index 9cfea033..9a735a14 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -19,6 +19,7 @@ LUAU_FASTFLAG(LuauNewNonStrictMoreUnknownSymbols) LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) LUAU_FASTFLAG(LuauNewNonStrictSuppressesDynamicRequireErrors) LUAU_FASTFLAG(LuauNewNonStrictReportsOneIndexedErrors) +LUAU_FASTFLAG(LuauUnreducedTypeFunctionsDontTriggerWarnings) using namespace Luau; @@ -884,4 +885,17 @@ getAllTheArgsWrong(3, true, "what") 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") +{ + ScopedFastFlag sff{FFlag::LuauUnreducedTypeFunctionsDontTriggerWarnings, true}; + CheckResult result = checkNonStrict(R"( +function foo(x) + local y = x + 1 + return abs(y) +end +)"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_SUITE_END(); diff --git a/tests/NonstrictMode.test.cpp b/tests/NonstrictMode.test.cpp index 9fbec8b5..b7ea3a0e 100644 --- a/tests/NonstrictMode.test.cpp +++ b/tests/NonstrictMode.test.cpp @@ -14,7 +14,6 @@ using namespace Luau; LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) TEST_SUITE_BEGIN("NonstrictModeTests"); @@ -326,7 +325,6 @@ TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden_n ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, - {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, // This debug flag is normally on, but we turn it off as we're testing // the exact behavior it enables. {FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, false}, @@ -345,7 +343,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "non_standalone_constraint_solving_incomplete ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, - {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, }; CheckResult results = check(R"( diff --git a/tests/RuntimeLimits.test.cpp b/tests/RuntimeLimits.test.cpp index ada4a52c..a4b1220c 100644 --- a/tests/RuntimeLimits.test.cpp +++ b/tests/RuntimeLimits.test.cpp @@ -22,7 +22,6 @@ LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauIceLess) LUAU_FASTFLAG(LuauSimplifyAnyAndUnion) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) @@ -296,7 +295,6 @@ TEST_CASE_FIXTURE(LimitFixture, "Signal_exerpt" * doctest::timeout(0.5)) ScopedFastFlag sff[] = { // These flags are required to surface the problem. {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, // And this flag is the one that fixes it. {FFlag::LuauSimplifyAnyAndUnion, true}, @@ -378,7 +376,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "limit_number_of_dynamically_created_constrai ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauLimitDynamicConstraintSolving3, true}, - {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauDontDynamicallyCreateRedundantSubtypeConstraints, true}, }; @@ -561,7 +558,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "test_generic_pruning_recursion_limit") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauReduceSetTypeStackPressure, true}, }; @@ -580,7 +576,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "unification_runs_a_limited_number_of_iterati ScopedFastFlag sff[] = { // These are necessary to trigger the bug {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, // This is the fix {FFlag::LuauLimitUnification, true} diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index bd14242e..943d5e0b 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -16,7 +16,6 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauVariadicAnyPackShouldBeErrorSuppressing) @@ -1756,10 +1755,6 @@ TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation") TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - TypeId argTy = arena.freshType(getBuiltins(), moduleScope.get()); FreeType* freeArg = getMutable(argTy); REQUIRE(freeArg); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index c385a763..230a4887 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -14,11 +14,9 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauDoNotBlockOnStuckTypeFunctions) -LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauRefineOccursCheckDirectRecursion) +LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauRawGetHandlesNil) @@ -726,10 +724,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_oss_crash_gh1161") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( local EnumVariants = { ["a"] = 1, ["b"] = 2, ["c"] = 3 @@ -1700,10 +1694,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fully_dispatch_type_function_that_is_paramet // This type function is stuck because it is parameterized on a stuck type // function. The call constraint must be able to dispatch. - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( --!strict @@ -1727,7 +1717,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "undefined_add_application") { ScopedFastFlag sff[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, }; CheckResult result = check(R"( @@ -1784,10 +1773,6 @@ struct TFFixture TypeCheckLimits limits; TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; - const ScopedFastFlag sff[1] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - BuiltinTypeFunctions builtinTypeFunctions; TypeFunctionContext tfc_{ @@ -1848,10 +1833,6 @@ TEST_CASE_FIXTURE(TFFixture, "a_type_function_parameterized_on_generics_is_solve TEST_CASE_FIXTURE(TFFixture, "a_tf_parameterized_on_a_solved_tf_is_solved") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - TypeId a = arena->addType(GenericType{"A"}); TypeId b = arena->addType(GenericType{"B"}); @@ -1869,10 +1850,6 @@ 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") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - TypeId innerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.bufferType, builtinTypes_.booleanType}}); TypeId outerAddTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.addFunc, {builtinTypes_.numberType, innerAddTy}}); @@ -1913,9 +1890,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_functions_should_not_get_stuck_or") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauForceSimplifyConstraint2, true}, - {FFlag::LuauDoNotBlockOnStuckTypeFunctions, true}, + {FFlag::LuauNoMoreComparisonTypeFunctions, false} }; CheckResult result = check(R"( diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 5b697bd0..f875477e 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(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTypeFunNoScopeMapRef) LUAU_FASTFLAG(LuauInstantiateResolvedTypeFunctions) @@ -1982,7 +1981,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - if (FFlag::LuauEagerGeneralization4) + if (false) // FFlag::LuauEagerGeneralization4) { // FIXME: CLI-151985 // This test breaks because we can't see that eq is already fully reduced. @@ -2005,7 +2004,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - if (FFlag::LuauEagerGeneralization4) + if (false) // FFlag::LuauEagerGeneralization4) { // FIXME: CLI-151985 // This test breaks because we can't see that eq is already fully reduced. @@ -2370,10 +2369,6 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( type Test = setmetatable diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 5877c700..e184a106 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -343,6 +343,7 @@ 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") { @@ -353,6 +354,7 @@ 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.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index 748be8ed..576b860f 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -11,9 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) -LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSolverAgnosticSetType) @@ -1724,8 +1722,6 @@ TEST_CASE_FIXTURE(Fixture, "write_only_table_assertion") TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_into_any") { - ScopedFastFlag _{FFlag::LuauSuppressErrorsForMultipleNonviableOverloads, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( table.insert(1::any, 2::any) )")); diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index e160a3fc..40f6b2bd 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(LuauScopeMethodsAreSolverAgnostic) LUAU_FASTFLAG(LuauMorePreciseExternTableRelation) LUAU_FASTFLAG(LuauPushTypeConstraint) @@ -903,7 +902,6 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "ice_while_checking_script_due_to_scopes_no // This is necessary to repro an ice that can occur in studio ScopedFastFlag luauSolverOff{FFlag::LuauSolverV2, false}; getFrontend().setLuauSolverMode(SolverMode::New); - ScopedFastFlag sff{FFlag::LuauScopeMethodsAreSolverAgnostic, true}; auto result = check(R"( local function ExitSeat(player, character, seat, weld) diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index 059898ba..22a0d2ed 100644 --- a/tests/TypeInfer.definitions.test.cpp +++ b/tests/TypeInfer.definitions.test.cpp @@ -9,6 +9,8 @@ using namespace Luau; +LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) + LUAU_FASTINT(LuauTypeInferRecursionLimit) TEST_SUITE_BEGIN("DefinitionTests"); @@ -570,7 +572,10 @@ TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully") TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func") { - ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauNoMoreComparisonTypeFunctions, true}, + }; CheckResult result = check(R"( local function middle(a: number, b: number): number @@ -590,7 +595,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cli_142285_reduce_minted_union_func") return nil end )"); - LUAU_REQUIRE_ERROR_COUNT(2, result); + 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) diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 6c25ea82..96f76a53 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -22,17 +22,16 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauCollapseShouldNotCrash) LUAU_FASTFLAG(LuauFormatUseLastPosition) LUAU_FASTFLAG(LuauSolverAgnosticStringification) -LUAU_FASTFLAG(LuauSuppressErrorsForMultipleNonviableOverloads) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) -LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauFixNilRightPad) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -247,8 +246,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "vararg_function_is_quantified") TEST_CASE_FIXTURE(Fixture, "list_only_alternative_overloads_that_match_argument_count") { - ScopedFastFlag _{FFlag::LuauSuppressErrorsForMultipleNonviableOverloads, true}; - CheckResult result = check(R"( local multiply: ((number)->number) & ((number)->string) & ((number, number)->number) multiply("") @@ -1449,7 +1446,7 @@ local a = {{x=4}, {x=7}, {x=1}} table.sort(a, function(x, y) return x.x < y.x end) )"); - if (FFlag::LuauSubtypingReportGenericBoundMismatches) + if (FFlag::LuauSubtypingReportGenericBoundMismatches2) { // FIXME CLI-161355 LUAU_REQUIRE_ERROR_COUNT(1, result); @@ -1686,9 +1683,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite") TEST_CASE_FIXTURE(BuiltinsFixture, "function_decl_non_self_sealed_overwrite_2") { - ScopedFastFlag _[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; CheckResult result = check(R"( local t: { f: ((x: number) -> number)? } = {} @@ -1774,19 +1768,11 @@ t.f = function(x) end )"); - if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2) { LUAU_CHECK_ERROR_COUNT(1, result); LUAU_CHECK_ERROR(result, WhereClauseNeeded); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK_EQ( - toString(result.errors[0]), - R"(Type function instance add depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this time)" - ); - } else { LUAU_REQUIRE_ERROR_COUNT(2, result); @@ -1969,18 +1955,12 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_ CHECK_EQ("(a) -> a", toString(requireType("f"))); - if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) + 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"))); } - else if (FFlag::LuauSolverV2) - { - // FIXME CLI-143852: Depends on interleaving generalization and type function reduction. - LUAU_CHECK_ERRORS(result); - CHECK_EQ("({ read p: unknown }) -> (*error-type* | ~(false?))?", toString(requireType("g"))); - } else { LUAU_REQUIRE_NO_ERRORS(result); @@ -2414,7 +2394,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_packs_are_not_variadic") {FFlag::LuauSolverV2, true}, {FFlag::LuauSubtypingGenericPacksDoesntUseVariance, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}, - {FFlag::LuauEagerGeneralization4, true} }; CheckResult result = check(R"( @@ -2605,7 +2584,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_return_type") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, }; // CLI-114134: This test: @@ -2631,10 +2609,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "tf_suggest_arg_type") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( function fib(n, u) return (n or u) and (n < u and n + fib(n,u)) @@ -2936,39 +2910,17 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") if (FFlag::LuauSolverV2) { // The new solver should ideally be able to do better here, but this is no worse than the old solver. + LUAU_REQUIRE_ERROR_COUNT(2, result); + auto tm1 = get(result.errors[0]); + REQUIRE(tm1); + CHECK(toString(tm1->wantedType) == "string"); + CHECK(toString(tm1->givenType) == "boolean"); - if (FFlag::LuauEagerGeneralization4) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - - auto tm1 = get(result.errors[0]); - REQUIRE(tm1); - CHECK(toString(tm1->wantedType) == "string"); - CHECK(toString(tm1->givenType) == "boolean"); - - auto tm2 = get(result.errors[1]); - REQUIRE(tm2); - CHECK(toString(tm2->wantedType) == "string"); + auto tm2 = get(result.errors[1]); + REQUIRE(tm2); + CHECK(toString(tm2->wantedType) == "string"); - CHECK(toString(tm2->givenType) == "unknown & ~(false?)"); - } - else - { - // Unfortunately, this example forces constraints a _ton_, meaning - // that we can easily end up being unable to solve constraints. This - // gets better with greedy generalization. - LUAU_REQUIRE_ERROR_COUNT(3, result); - - auto tm1 = get(result.errors[1]); - REQUIRE(tm1); - CHECK(toString(tm1->wantedType) == "string"); - CHECK(toString(tm1->givenType) == "boolean"); - - auto tm2 = get(result.errors[2]); - REQUIRE(tm2); - CHECK(toString(tm2->wantedType) == "string"); - CHECK(toString(tm2->givenType) == "~(false?)"); - } + CHECK(toString(tm2->givenType) == "unknown & ~(false?)"); } else { @@ -3285,7 +3237,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_calls_should_not_crash") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, // crash only happens right now with eager generalization off - {FFlag::LuauEagerGeneralization4, false}, {FFlag::LuauCollapseShouldNotCrash, true}, }; @@ -3342,4 +3293,35 @@ end LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "call_function_with_nothing_but_nil") +{ + ScopedFastFlag _{FFlag::LuauFixNilRightPad, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function f(n: number, x: string?, y: string?, z: string?) end + + local function g(n) + f(n) + end + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1640") +{ + ScopedFastFlag _{FFlag::LuauFixNilRightPad, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + table.create(1) -- top function call + + local function f(): string + if true then + table.create(1) -- middle function call + end + + return table.concat({}) + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index e238e7ac..91ddb335 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -11,13 +11,14 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauIntersectNotNil) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauContainsAnyGenericFollowBeforeChecking) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) -LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericPacksDoesntUseVariance) +LUAU_FASTFLAG(DebugLuauStringSingletonBasedOnQuotes) +LUAU_FASTFLAG(LuauSubtypingUnionsAndIntersectionsInGenericBounds) using namespace Luau; @@ -1485,7 +1486,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_argument_overloaded_ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_overloaded_pt_2") { ScopedFastFlag _[] = { - {FFlag::LuauSubtypingReportGenericBoundMismatches, true}, + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, }; CheckResult result = check(R"( @@ -1505,9 +1506,6 @@ TEST_CASE_FIXTURE(Fixture, "infer_generic_function_function_overloaded_pt_2") // selection and generic type substitution when TEST_CASE_FIXTURE(BuiltinsFixture, "do_not_infer_generic_functions") { - ScopedFastFlag _[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; CheckResult result; if (FFlag::LuauSolverV2) @@ -1846,7 +1844,6 @@ TEST_CASE_FIXTURE(Fixture, "generic_type_packs_shouldnt_be_bound_to_themselves") { ScopedFastFlag flags[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, }; CheckResult result = check(R"( @@ -2029,7 +2026,7 @@ local u: U = t TEST_CASE_FIXTURE(Fixture, "ensure_that_invalid_generic_instantiations_error") { - ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches, true}}; + ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}}; CheckResult res = check(R"( local func: (T, (T) -> ()) -> () = nil :: any local foobar: (number) -> () = nil :: any @@ -2045,7 +2042,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::LuauSubtypingReportGenericBoundMismatches, true}}; + ScopedFastFlag _[] = {{FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}}; CheckResult res = check(R"( --!strict @@ -2081,4 +2078,23 @@ xpcall(v, print, x) LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(Fixture, "array_of_singletons_should_subtype_against_generic_array") +{ + ScopedFastFlag _[] = { + // These flags expose the issue + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, + {FFlag::DebugLuauStringSingletonBasedOnQuotes, true}, + {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, + // And this flag fixes it + {FFlag::LuauSubtypingUnionsAndIntersectionsInGenericBounds, true} + }; + CheckResult res = check(R"( + local function a(arr: { T }) end + + a({ 'one', 'two' }) + )"); + + LUAU_REQUIRE_NO_ERRORS(res); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index dd4b66df..f6ad24c1 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -17,7 +17,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) -LUAU_FASTFLAG(LuauEagerGeneralization4) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -762,7 +761,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_generic_next") { ScopedFastFlag sff[] = { {FFlag::LuauNoScopeShallNotSubsumeAll, true}, - {FFlag::LuauEagerGeneralization4, true}, }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 33697752..235fad48 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -13,12 +13,10 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauLimitDynamicConstraintSolving3) LUAU_FASTINT(LuauSolverConstraintLimit) -LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauNameConstraintRestrictRecursiveTypes) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSolverAgnosticSetType) @@ -780,10 +778,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization4) - { - CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23}))); - } + CHECK("(unknown) -> unknown" == toString(requireTypeAtPosition({13, 23}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any") @@ -857,7 +852,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "internal_types_are_scrubbed_from_module") {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, {FFlag::LuauLimitDynamicConstraintSolving3, true}, - {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, }; fileResolver.source["game/A"] = R"( @@ -877,7 +871,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "internal_type_errors_are_only_reported_once" {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, {FFlag::LuauLimitDynamicConstraintSolving3, true}, - {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, }; fileResolver.source["game/A"] = R"( diff --git a/tests/TypeInfer.negations.test.cpp b/tests/TypeInfer.negations.test.cpp index b68930a9..8fc8ea27 100644 --- a/tests/TypeInfer.negations.test.cpp +++ b/tests/TypeInfer.negations.test.cpp @@ -9,8 +9,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauEagerGeneralization4) - namespace { @@ -52,10 +50,6 @@ TEST_CASE_FIXTURE(NegationFixture, "string_is_not_a_subtype_of_negated_string") TEST_CASE_FIXTURE(Fixture, "cofinite_strings_can_be_compared_for_equality") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( function f(e) if e == 'strictEqual' then diff --git a/tests/TypeInfer.oop.test.cpp b/tests/TypeInfer.oop.test.cpp index 2ed60f79..990f8c30 100644 --- a/tests/TypeInfer.oop.test.cpp +++ b/tests/TypeInfer.oop.test.cpp @@ -13,7 +13,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauTrackFreeInteriorTypePacks) LUAU_FASTFLAG(LuauSolverAgnosticStringification) @@ -555,10 +554,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( local Account = {} Account.__index = Account @@ -587,10 +582,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "textbook_class_pattern_2") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( local Account = {} Account.__index = Account diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index bfae05ad..ef829e15 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -2,9 +2,10 @@ #include "Luau/AstQuery.h" #include "Luau/BuiltinDefinitions.h" +#include "Luau/Error.h" #include "Luau/Scope.h" -#include "Luau/TypeInfer.h" #include "Luau/Type.h" +#include "Luau/TypeInfer.h" #include "Luau/VisitType.h" #include "Fixture.h" @@ -19,16 +20,13 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) LUAU_FASTFLAG(LuauTrackUniqueness) -LUAU_FASTFLAG(LuauEagerGeneralization4) +LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("TypeInferOperators"); TEST_CASE_FIXTURE(Fixture, "or_joins_types") { - ScopedFastFlag _[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; CheckResult result = check(R"( local s = "a" or 10 local x:string|number = s @@ -50,9 +48,6 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types") TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") { - ScopedFastFlag _[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; CheckResult result = check(R"( local s = "a" or 10 local x:number|string = s @@ -75,9 +70,6 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union") { - ScopedFastFlag _[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; CheckResult result = check(R"( local s = "a" or "b" local x:string = s @@ -646,7 +638,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error") local a = -foo )"); - if (FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2) + if (FFlag::LuauSolverV2) { LUAU_REQUIRE_ERROR_COUNT(1, result); @@ -659,15 +651,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error") CHECK("({ @metatable { __unm: (boolean) -> string }, { value: number } }) -> string" == toString(tm->wantedType, {true})); CHECK("(boolean) -> string" == toString(tm->givenType)); } - else if (FFlag::LuauSolverV2) - { - LUAU_REQUIRE_ERROR_COUNT(2, result); - - CHECK(get(result.errors[0])); - - // This second error is spurious. We should not be reporting it. - CHECK(get(result.errors[1])); - } else { LUAU_REQUIRE_ERROR_COUNT(1, result); @@ -1337,11 +1320,17 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "unrelated_extern_types_cannot_be_compared" TEST_CASE_FIXTURE(Fixture, "unrelated_primitives_cannot_be_compared") { + ScopedFastFlag sff[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauNoMoreComparisonTypeFunctions, true}, + }; + CheckResult result = check(R"( local c = 5 == true )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, CannotCompareUnrelatedTypes); } TEST_CASE_FIXTURE(BuiltinsFixture, "mm_comparisons_must_return_a_boolean") diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 72a542c8..b6f174d7 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(LuauSolverAgnosticStringification) +LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) TEST_SUITE_BEGIN("ProvisionalTests"); @@ -1304,6 +1305,8 @@ TEST_CASE_FIXTURE(Fixture, "table_containing_non_final_type_is_erroneously_cache // CLI-111113 TEST_CASE_FIXTURE(Fixture, "we_cannot_infer_functions_that_return_inconsistently") { + ScopedFastFlag sff{FFlag::LuauNoMoreComparisonTypeFunctions, true}; + CheckResult result = check(R"( function find_first(tbl: {T}, el) for i, e in tbl do @@ -1327,7 +1330,7 @@ TEST_CASE_FIXTURE(Fixture, "we_cannot_infer_functions_that_return_inconsistently if (FFlag::LuauSolverV2) { - LUAU_CHECK_ERROR_COUNT(2, result); + LUAU_CHECK_ERROR_COUNT(1, result); CHECK("({T}, unknown) -> number" == toString(requireType("find_first"))); } else diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 9556c0e7..99a42cce 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -10,20 +10,19 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauNormalizationReorderFreeTypeIntersect) LUAU_FASTFLAG(LuauRefineNoRefineAlways) -LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) -LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauNewNonStrictNoErrorsPassingNever) -LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) +LUAU_FASTFLAG(LuauNoMoreComparisonTypeFunctions) LUAU_FASTFLAG(LuauNumericUnaryOpsDontProduceNegationRefinements) +LUAU_FASTFLAG(LuauAddConditionalContextForTernary) using namespace Luau; @@ -693,7 +692,6 @@ TEST_CASE_FIXTURE(Fixture, "lvalue_is_not_nil") TEST_CASE_FIXTURE(Fixture, "free_type_is_equal_to_an_lvalue") { ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, }; @@ -799,9 +797,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "type_narrow_to_vector") TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_is_true") { - ScopedFastFlag _[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; CheckResult result = check(R"( local t = {"hello"} local v = t[2] @@ -2251,8 +2246,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauForceSimplifyConstraint2, true}, + {FFlag::LuauNoMoreComparisonTypeFunctions, true}, + {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, }; CheckResult result = check(R"( @@ -2264,7 +2259,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction" end )"); - LUAU_CHECK_ERROR_COUNT(3, result); + LUAU_CHECK_ERROR_COUNT(2, result); // For some reason we emit three error here. for (const auto& e : result.errors) @@ -2276,7 +2271,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_isindexkey_refine_conjunction_ ScopedFastFlag _[] = { {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, {FFlag::LuauNormalizationReorderFreeTypeIntersect, true}, - {FFlag::LuauEagerGeneralization4, true}, }; // FIXME CLI-141364: An underlying bug in normalization means the type of @@ -2331,7 +2325,6 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symb TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symbol_2") { - // TODO: This test is written weirdly and doesn't pass CheckResult result = check(R"( type Result = never | { tag: "ok", value: T } @@ -2346,14 +2339,7 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symb end )"); - if (FFlag::LuauSubtypingReportGenericBoundMismatches && FFlag::LuauSolverV2) - { - // FIXME CLI-161355 - LUAU_REQUIRE_ERROR_COUNT(1, result); - CHECK(get(result.errors[0])); - } - else - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_REQUIRE_NO_ERRORS(result); } TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_t_after_return_references_all_reachable_points") @@ -2593,11 +2579,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi end )")); - if (FFlag::LuauEagerGeneralization4) - // FIXME CLI-114134. We need to simplify types more consistently. - CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24}))); - else - CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); + // FIXME CLI-114134. We need to simplify types more consistently. + CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24}))); } TEST_CASE_FIXTURE(BuiltinsFixture, "nonnil_refinement_on_generic") @@ -2847,8 +2830,14 @@ TEST_CASE_FIXTURE(Fixture, "limit_complexity_of_arithmetic_type_functions" * doc TEST_CASE_FIXTURE(BuiltinsFixture, "refine_by_no_refine_should_always_reduce") { - ScopedFastFlag _{FFlag::LuauSolverV2, true}; - ScopedFastFlag refineNoRefineAlways{FFlag::LuauRefineNoRefineAlways, true}; + // NOTE: Previously this only required two flags, but clipping a flag around + // how we report constraint solving incomplete errors revealed that this + // test would always fail to solve all constraints, except under eager + // generalization. + ScopedFastFlag sffs[] = { + {FFlag::LuauSolverV2, true}, + {FFlag::LuauRefineNoRefineAlways, true}, + }; CheckResult result = check(R"( function foo(t): boolean return true end @@ -2877,10 +2866,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_by_no_refine_should_always_reduce") TEST_CASE_FIXTURE(Fixture, "table_name_index_without_prior_assignment_from_branch") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauDoNotPrototypeTableIndex, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; // The important part of this test case is: // - `CharEntry` is represented as a phi node in the data flow graph; @@ -2903,8 +2889,6 @@ TEST_CASE_FIXTURE(Fixture, "table_name_index_without_prior_assignment_from_branc TEST_CASE_FIXTURE(Fixture, "cli_120460_table_access_on_phi_node") { - ScopedFastFlag _{FFlag::LuauDoNotPrototypeTableIndex, true}; - LUAU_REQUIRE_NO_ERRORS(check(R"( --!strict local function foo(bar: string): string @@ -2951,10 +2935,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinements_from_and_should_not_refine_to_ne TEST_CASE_FIXTURE(Fixture, "force_simplify_constraint_doesnt_drop_blocked_type") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauForceSimplifyConstraint2, true}, - }; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; CheckResult results = check(R"( local function track(instance): boolean @@ -3010,4 +2991,24 @@ end CHECK(toString(ty) != "never"); } +TEST_CASE_FIXTURE(BuiltinsFixture, "inline_if_conditional_context") +{ + ScopedFastFlag _{FFlag::LuauAddConditionalContextForTernary, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + + type Value = { + kind: "value", + value: T + } + + local function peek(state: Value | T): T + return if typeof(state) == "table" and state.kind == "value" + then (state :: Value).value :: T + else state :: T + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index 6485370b..766888b1 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -2,6 +2,7 @@ #include "Fixture.h" +#include "ScopedFlags.h" #include "doctest.h" using namespace Luau; @@ -672,4 +673,57 @@ TEST_CASE_FIXTURE(Fixture, "tagged_union_in_ternary") )")); } +TEST_CASE_FIXTURE(Fixture, "table_literal_with_singleton_union_values") +{ + ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + + CheckResult result = check(R"( + local t1: {[string]: "a" | "b"} = { a = "a", b = "b" } + local t2: {[string]: "a" | true} = { a = "a", b = true } + local t3: {[string]: "a" | nil} = { a = "a" } + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + +TEST_CASE_FIXTURE(Fixture, "singleton_type_mismatch_via_variable") +{ + ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + + CheckResult result = check(R"( + local c = "c" + local x: "a" = c + local y: "a" | "b" = c + local z: "a"? = c + local w: "a" | "b" = "c" + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + + REQUIRE(get(result.errors[0])); + REQUIRE(get(result.errors[1])); + REQUIRE(get(result.errors[2])); + REQUIRE(get(result.errors[3])); +} + +TEST_CASE_FIXTURE(Fixture, "cli_163481_any_indexer_pushes_type") +{ + ScopedFastFlag _{FFlag::LuauPushTypeConstraint, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + --!strict + + type test = "A" + type test2 = "A"|"B"|"C" + + local t: { [any]: test } = { A = "A" } + + local t2: { [any]: test2 } = { + A = "A", + B = "B", + C = "C" + } + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 7aa714cd..3793fa9d 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -21,19 +21,16 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTINT(LuauPrimitiveInferenceInTableLimit) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) -LUAU_FASTFLAG(LuauDoNotPrototypeTableIndex) LUAU_FASTFLAG(LuauNoScopeShallNotSubsumeAll) -LUAU_FASTFLAG(LuauNormalizationLimitTyvarUnionSize) LUAU_FASTFLAG(LuauExtendSealedTableUpperBounds) LUAU_FASTFLAG(LuauSubtypingGenericsDoesntUseVariance) LUAU_FASTFLAG(LuauAllowMixedTables) LUAU_FASTFLAG(LuauSolverAgnosticSetType) -LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches) +LUAU_FASTFLAG(LuauSubtypingReportGenericBoundMismatches2) LUAU_FASTFLAG(LuauPushTypeConstraint) LUAU_FASTFLAG(LuauUnifyShortcircuitSomeIntersectionsAndUnions) LUAU_FASTFLAG(LuauSimplifyIntersectionForLiteralSubtypeCheck) @@ -709,10 +706,8 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization4) + if (FFlag::LuauSolverV2) CHECK("({a}) -> ()" == toString(requireType("swap"))); - else if (FFlag::LuauSolverV2) - CHECK("({unknown}) -> ()" == toString(requireType("swap"))); else { const FunctionType* ftv = get(requireType("swap")); @@ -2335,7 +2330,7 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table // compare functions inside of table properties ScopedFastFlag sff[] = { {FFlag::LuauSubtypingGenericsDoesntUseVariance, true}, - {FFlag::LuauSubtypingReportGenericBoundMismatches, true}, + {FFlag::LuauSubtypingReportGenericBoundMismatches2, true}, {FFlag::LuauInstantiateInSubtyping, FFlag::LuauSolverV2}, }; @@ -3711,10 +3706,6 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_a_subtype_of_a_compatible_polymorphic_shap TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( local function f(s) return s:absolutely_no_scalar_has_this_method() @@ -3853,25 +3844,15 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "a_free_shape_can_turn_into_a_scalar_directly if (FFlag::LuauSolverV2) { LUAU_REQUIRE_ERRORS(result); - if (FFlag::LuauEagerGeneralization4) - { - /* - * string.byte returns ...number, which cannot be passed to - * table.insert. - * - * Intuitively, Luau has no way to guarantee that string.byte() will - * always return at least 1 number and there is no table.insert overload - * that takes just 1 argument. - */ - LUAU_REQUIRE_ERROR(result, MultipleNonviableOverloads); - } - else - { - const GenericError* err = findError(result); - REQUIRE(err); - CHECK(err->message == "None of the overloads for function that accept 1 arguments are compatible."); - LUAU_REQUIRE_ERROR(result, GenericError); - } + /* + * string.byte returns ...number, which cannot be passed to + * table.insert. + * + * Intuitively, Luau has no way to guarantee that string.byte() will + * always return at least 1 number and there is no table.insert overload + * that takes just 1 argument. + */ + LUAU_REQUIRE_ERROR(result, MultipleNonviableOverloads); } else LUAU_REQUIRE_NO_ERRORS(result); @@ -4605,10 +4586,6 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( function oc(player, speaker) local head = speaker.Character:FindFirstChild('Head') @@ -4656,17 +4633,8 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array") end )"); - if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization4) - { - LUAU_CHECK_ERROR_COUNT(1, result); - LUAU_CHECK_ERROR(result, NotATable); - CHECK_EQ("(unknown, *error-type*) -> *error-type*", toString(requireType("foo"))); - } - else - { - LUAU_REQUIRE_NO_ERRORS(result); - CHECK("({a}, a) -> a" == toString(requireType("foo"))); - } + LUAU_REQUIRE_NO_ERRORS(result); + CHECK("({a}, a) -> a" == toString(requireType("foo"))); } TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_string") @@ -4704,10 +4672,7 @@ TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_ LUAU_REQUIRE_NO_ERRORS(result); // FIXME CLI-114134. We need to simplify types more consistently. - if (FFlag::LuauEagerGeneralization4) - CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f"))); - else - CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f"))); + CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f"))); } TEST_CASE_FIXTURE(Fixture, "write_to_union_property_not_all_present") @@ -5872,7 +5837,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1450") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, {FFlag::LuauUnifyShortcircuitSomeIntersectionsAndUnions, true}, {FFlag::LuauPushTypeConstraint, true}, }; @@ -5958,7 +5922,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_119126_regression") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1914_access_after_assignment_with_assertion") { - ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauDoNotPrototypeTableIndex, true}}; + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( --!strict @@ -5993,8 +5957,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1914_access_after_assignment_with_assert TEST_CASE_FIXTURE(BuiltinsFixture, "cli_162179_avoid_exponential_blowup_in_normalization" * doctest::timeout(1.0)) { - ScopedFastFlag sff{FFlag::LuauNormalizationLimitTyvarUnionSize, true}; - const std::string source = format( R"( local res = { %s } diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index d68729cd..327d53c4 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -22,17 +22,16 @@ LUAU_FASTINT(LuauNormalizeCacheLimit) LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeInferTypePackLoopLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauInferActualIfElseExprType2) -LUAU_FASTFLAG(LuauForceSimplifyConstraint2) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete) LUAU_FASTFLAG(LuauReturnMappedGenericPacksFromSubtyping2) LUAU_FASTFLAG(LuauMissingFollowMappedGenericPacks) LUAU_FASTFLAG(LuauOccursCheckInCommit) +LUAU_FASTFLAG(LuauEGFixGenericsList) LUAU_FASTFLAG(LuauParametrizedAttributeSyntax) LUAU_FASTFLAG(LuauNoConstraintGenRecursionLimitIce) +LUAU_FASTFLAG(LuauTryToOptimizeSetTypeUnification) using namespace Luau; @@ -440,7 +439,6 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit") #endif ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100}; ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100}; - ScopedFastFlag _{FFlag::LuauEagerGeneralization4, false}; CheckResult result = check(R"(("foo"))" + rep(":lower()", limit)); @@ -2024,7 +2022,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, }; auto result = check(R"( @@ -2059,7 +2056,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, }; CheckResult result = check(R"( @@ -2092,7 +2088,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, }; LUAU_REQUIRE_ERRORS(check(R"( @@ -2292,7 +2287,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "config_reader_example") // test suite starts, which will cause an assert if we try to eagerly // generalize _after_ the test is set up. Additionally, this code block // crashes under the new solver without flags. - if (!(FFlag::LuauEagerGeneralization4 && FFlag::LuauSolverV2)) + if (!FFlag::LuauSolverV2) return; fileResolver.source["game/ConfigReader"] = R"( @@ -2339,9 +2334,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "config_reader_example") TEST_CASE_FIXTURE(BuiltinsFixture, "is_safe_integer_example") { - if (!FFlag::LuauEagerGeneralization4) - return; - fileResolver.source["game/isInteger"] = R"( --!strict return function(value) @@ -2368,10 +2360,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "is_safe_integer_example") TEST_CASE_FIXTURE(BuiltinsFixture, "type_remover_heap_use_after_free") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - LUAU_REQUIRE_ERRORS(check(R"( _ = if l0.n0.n0 then {n4(...,setmetatable(setmetatable(_),_)),_ == _,} elseif _.ceil._ then _ elseif _ then not _ )")); @@ -2520,7 +2508,6 @@ TEST_CASE_FIXTURE(Fixture, "standalone_constraint_solving_incomplete_is_hidden") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, - {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, // This debug flag is normally on, but we turn it off as we're testing // the exact behavior it enables. {FFlag::DebugLuauAlwaysShowConstraintSolvingIncomplete, false}, @@ -2538,7 +2525,6 @@ TEST_CASE_FIXTURE(Fixture, "non_standalone_constraint_solving_incomplete_is_hidd ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::DebugLuauMagicTypes, true}, - {FFlag::LuauNewNonStrictSuppressSoloConstraintSolvingIncomplete, true}, }; CheckResult results = check(R"( @@ -2556,7 +2542,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_missing_type_pack_follow") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauReturnMappedGenericPacksFromSubtyping2, true}, - {FFlag::LuauMissingFollowMappedGenericPacks, true}, }; LUAU_REQUIRE_ERRORS(check(R"( @@ -2658,6 +2643,8 @@ TEST_CASE_FIXTURE(Fixture, "constraint_generation_recursion_limit") // https://github.com/luau-lang/luau/issues/1971 TEST_CASE_FIXTURE(Fixture, "nested_functions_can_depend_on_outer_generics") { + ScopedFastFlag sff{FFlag::LuauEGFixGenericsList, true}; + CheckResult result = check(R"( function name

(arg1: P) return function(what: P) return what end @@ -2677,4 +2664,25 @@ 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_SUITE_END(); diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index e3bb41ff..ffa680e9 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(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauSolverAgnosticStringification) LUAU_FASTFLAG(LuauRemoveGenericErrorForParams) LUAU_FASTFLAG(LuauAddErrorCaseForIncompatibleTypePacks) @@ -91,9 +90,6 @@ TEST_CASE_FIXTURE(Fixture, "last_element_of_return_statement_can_itself_be_a_pac TEST_CASE_FIXTURE(Fixture, "higher_order_function") { - ScopedFastFlag _[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; CheckResult result = check(R"( function apply(f, g, x) return f(g(x)) diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 3fffacf3..5046c5b3 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -4,7 +4,6 @@ #include "doctest.h" LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauRefineDistributesOverUnions) LUAU_FASTFLAG(LuauReduceSetTypeStackPressure) LUAU_FASTFLAG(LuauSolverAgnosticStringification) @@ -408,7 +407,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization4, true}, }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 26ec026b..7f160bfe 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -10,7 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization4) LUAU_FASTFLAG(LuauSolverAgnosticStringification) TEST_SUITE_BEGIN("UnionTypes"); @@ -898,15 +897,9 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization4) - CHECK_EQ( - "(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) - ); - else - CHECK_EQ( - "(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { 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 71c88c20..1bae4837 100644 --- a/tests/TypeInfer.unknownnever.test.cpp +++ b/tests/TypeInfer.unknownnever.test.cpp @@ -7,8 +7,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauEagerGeneralization4); -LUAU_FASTFLAG(LuauForceSimplifyConstraint2) TEST_SUITE_BEGIN("TypeInferUnknownNever"); @@ -329,11 +327,6 @@ TEST_CASE_FIXTURE(Fixture, "length_of_never") TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_in_any_ordering_operators") { - ScopedFastFlag sffs[] = { - {FFlag::LuauEagerGeneralization4, true}, - {FFlag::LuauForceSimplifyConstraint2, true}, - }; - CheckResult result = check(R"( local function ord(x: nil, y) return x ~= nil and x > y @@ -356,10 +349,6 @@ TEST_CASE_FIXTURE(Fixture, "dont_unify_operands_if_one_of_the_operand_is_never_i TEST_CASE_FIXTURE(Fixture, "math_operators_and_never") { - ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization4, true}, - }; - CheckResult result = check(R"( local function mul(x: nil, y) return x ~= nil and x * y -- infers boolean | never, which is normalized into boolean @@ -390,7 +379,7 @@ TEST_CASE_FIXTURE(Fixture, "compare_never") end )"); - LUAU_REQUIRE_NO_ERRORS(result); + LUAU_CHECK_NO_ERRORS(result); CHECK_EQ("(nil, number) -> boolean", toString(requireType("cmp"))); } diff --git a/tests/Unifier2.test.cpp b/tests/Unifier2.test.cpp index 41dc64c8..2e3bf96c 100644 --- a/tests/Unifier2.test.cpp +++ b/tests/Unifier2.test.cpp @@ -13,6 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauTryToOptimizeSetTypeUnification) struct Unifier2Fixture { @@ -132,4 +133,40 @@ 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") +{ + ScopedFastFlag _{FFlag::LuauTryToOptimizeSetTypeUnification, true}; + // 'a + TypeId freeTy = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType}); + // 'a & ~(false?) + TypeId subTy = arena.addType(IntersectionType{{ freeTy, builtinTypes.truthyType}}); + // number? + TypeId superTy = arena.addType(UnionType{{builtinTypes.numberType, builtinTypes.nilType}}); + u2.unify(subTy, superTy); + + CHECK("('a <: number?)" == toString(freeTy)); +} + +TEST_CASE_FIXTURE(Unifier2Fixture, "unify_unfortunate_free_type_lb_from_intersection") +{ + ScopedFastFlag _{FFlag::LuauTryToOptimizeSetTypeUnification, true}; + // 'a + TypeId freeTy = arena.addType(FreeType{&scope, builtinTypes.neverType, builtinTypes.unknownType}); + // 'a? + 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"}}) + } + )}}); + 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)); +} + TEST_SUITE_END(); diff --git a/tests/conformance/native.luau b/tests/conformance/native.luau index 66bfcea7..f0fe3ce6 100644 --- a/tests/conformance/native.luau +++ b/tests/conformance/native.luau @@ -633,4 +633,45 @@ do assert(t[1] == 150 and t[2] == 510 and t[3] == 1100) end +local function splitStoreRestore(x) + local c1; c1 = function() return 1 + x end + local c2; c2 = function() return 2 + x end + local c3; c3 = function() return 3 + x end + local c4; c4 = function() return 4 + x end + local c5; c5 = function() return 5 + x end + local c6; c6 = function() return 6 + x end + local c7; c7 = function() return 7 + x end + local c8; c8 = function() return 8 + x end + local c9; c9 = function() return 9 + x end + local ca; ca = function() return 10 + x end + local cb; cb = function() return 11 + x end + local cc; cc = function() return 12 + x end + local cd; cd = function() return 13 + x end + local ce; ce = function() return 14 + x end + local cf; cf = function() return 15 + x end + + local t = {} + + t.a1 = c1 + t.a2 = c2 + t.a3 = c3 + t.a4 = c4 + t.a5 = c5 + t.a6 = c6 + t.a7 = c7 + t.a8 = c8 + t.a9 = c9 + t.aa = ca + t.ab = cb + t.ac = cc + t.ad = cd + t.ae = ce + t.af = cf + + assert(is_native()) + return t +end + +assert(splitStoreRestore(2).ad() == 15) + return('OK') From ecc1e3c0d5105dba83d646fcca1e9cfda197483d Mon Sep 17 00:00:00 2001 From: Harold Cindy <120691094+HaroldCindy@users.noreply.github.com> Date: Sun, 2 Nov 2025 00:27:39 -0700 Subject: [PATCH 8/8] Don't trigger Luau release build on PR --- .github/workflows/release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4e00384..42ca46f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,12 +9,12 @@ on: - 'papers/**' - 'rfcs/**' - '*.md' - pull_request: - paths-ignore: - - 'docs/**' - - 'papers/**' - - 'rfcs/**' - - '*.md' +# pull_request: +# paths-ignore: +# - 'docs/**' +# - 'papers/**' +# - 'rfcs/**' +# - '*.md' jobs: unix: