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 @@ private: 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 @@ public: 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); + 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); - 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); + 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); - bool unify(const MetatableType* subMetatable, const AnyType*); - bool unify(const AnyType*, const MetatableType* superMetatable); + UnifyResult unify_(const MetatableType* subMetatable, const AnyType*); + UnifyResult 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_(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()) { - // 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}; + LUAU_ASSERT(!normTy->hasTables()); - // computing the keys for extern types - if (normTy->hasExternTypes()) + // 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(*externTypeIter, keys, seen, isRaw, ctx)) + return {ctx->builtins->stringType, Reduction::MaybeOk, {}, {}}; // if it failed, we have a top type! + + // we need to look at each class to remove any keys that are not common amongst them all + while (++externTypeIter != externTypeIterEnd) { - LUAU_ASSERT(!normTy->hasTables()); + 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! - - // we need to look at each class to remove any keys that are not common amongst them all - while (++externTypeIter != externTypeIterEnd) + for (auto& key : keys) { - seen.clear(); // we'll reuse the same seen set - - Set> localKeys{std::nullopt}; - - // we can skip to the next class if this one is a top type - if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx)) - continue; - - for (auto& key : keys) - { - // remove any keys that are not present in each class - if (!localKeys.contains(key)) - keys.erase(key); - } + // 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()) - { - LUAU_ASSERT(!normTy->hasExternTypes()); - - // seen set for key computation for tables - DenseHashSet seen{{}}; - - auto tablesIter = normTy->tables.begin(); - LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier - - // 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 need to look at each tables to remove any keys that are not common amongst them all - while (++tablesIter != normTy->tables.end()) - { - 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); - } - } - } - - // 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 + + // computing the keys for tables + if (normTy->hasTables()) { + LUAU_ASSERT(!normTy->hasExternTypes()); - // we're going to collect the keys in here - Set keys{{}}; + // seen set for key computation for tables + DenseHashSet seen{{}}; - // computing the keys for extern types - if (normTy->hasExternTypes()) + auto tablesIter = normTy->tables.begin(); + LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier + + // 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 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->hasTables()); + 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 table if this one is the top table type + if (!computeKeysOf(*tablesIter, localKeys, seen, isRaw, ctx)) + continue; - // 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! - - // we need to look at each class to remove any keys that are not common amongst them all - while (++externTypeIter != externTypeIterEnd) + for (auto& key : keys) { - seen.clear(); // we'll reuse the same seen set - - Set localKeys{{}}; - - // we can skip to the next class if this one is a top type - if (!computeKeysOf_DEPRECATED(*externTypeIter, localKeys, seen, isRaw, ctx)) - continue; - - for (auto& key : keys) - { - // remove any keys that are not present in each class - if (!localKeys.contains(key)) - keys.erase(key); - } + // remove any keys that are not present in each table + if (!localKeys.contains(key)) + keys.erase(key); } } - - // computing the keys for tables - if (normTy->hasTables()) - { - LUAU_ASSERT(!normTy->hasExternTypes()); - - // seen set for key computation for tables - DenseHashSet seen{{}}; - - auto tablesIter = normTy->tables.begin(); - LUAU_ASSERT(tablesIter != normTy->tables.end()); // should be guaranteed by the `hasTables` check earlier - - // 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 need to look at each tables to remove any keys that are not common amongst them all - while (++tablesIter != normTy->tables.end()) - { - 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); - } - } - } - - // 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 std::string& key : keys) - 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, {}, {}}; } + + // 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, {}, {}}; } 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) { - 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); - } - - if (indexValueLowerBound.size() == 1) - { - indexValue = *indexValueLowerBound.begin(); - } - else - { - indexValue = arena->addType(UnionType{std::vector(indexValueLowerBound.begin(), indexValueLowerBound.end())}); - unionsToSimplify.push_back(indexValue); - } - - ttv->indexer = TableIndexer{indexKey, indexValue}; + indexKey = *indexKeyLowerBound.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}; + indexKey = arena->addType(UnionType{std::vector(indexKeyLowerBound.begin(), indexKeyLowerBound.end())}); + unionsToSimplify.push_back(indexKey); } + + if (indexValueLowerBound.size() == 1) + { + indexValue = *indexValueLowerBound.begin(); + } + else + { + 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ private: 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(left)) + if (auto res = basicIntersectWithTruthy(right)) + return res; - if (isApproximatelyTruthyType(right)) - if (auto res = basicIntersectWithTruthy(left)) - return res; + if (isApproximatelyTruthyType(right)) + if (auto res = basicIntersectWithTruthy(left)) + return res; - if (isApproximatelyFalsyType(left)) - if (auto res = basicIntersectWithFalsy(right)) - return res; + if (isApproximatelyFalsyType(left)) + if (auto res = basicIntersectWithFalsy(right)) + 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 (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 (bounds.upperBound.empty()) - return builtinTypes->unknownType; + 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; - if (bounds.upperBound.size() == 1) - return *begin(bounds.upperBound); + for (TypeId ub : upperBound) + { + // quick and dirty check to avoid adding generic types + if (!get(ub)) + boundsToUse.insert(ub); + } - return arena->addType(IntersectionType{std::vector(begin(bounds.upperBound), end(bounds.upperBound))}); + 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.size() == 1) + return *begin(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) { - if (auto it = mappedGenerics.find(ty)) + 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) +{ + 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(); + + result.andAlso(boundsResult); } - - - SubtypingEnvironment boundsEnv; - boundsEnv.parent = &env; - SubtypingResult boundsResult = isCovariantWith(boundsEnv, lowerBound, upperBound, scope); - boundsResult.reasoning.clear(); - - 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 (*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()) + { + 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 @@ private: 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 @@ private: 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 @@ private: 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 @@ public: 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 @@ public: void visit(AstVisitor* visitor) override; + DeprecatedInfo deprecatedInfo() const; + Type type; + AstArray args; }; class AstExpr : public AstNode @@ -455,6 +465,7 @@ public: bool hasNativeAttribute() const; bool hasAttribute(AstAttr::Type attributeType) const; + AstAttr* getAttribute(AstAttr::Type attributeType) const; AstArray attributes; AstArray generics; @@ -499,6 +510,8 @@ public: void visit(AstVisitor* visitor) override; + std::optional getRecord(const char* key) const; + AstArray items; }; @@ -960,6 +973,7 @@ public: bool isCheckedFunction() const; bool hasAttribute(AstAttr::Type attributeType) const; + AstAttr* getAttribute(AstAttr::Type attributeType) const; AstArray attributes; AstName name; @@ -1117,6 +1131,7 @@ public: bool isCheckedFunction() const; bool hasAttribute(AstAttr::Type attributeType) const; + AstAttr* getAttribute(AstAttr::Type attributeType) const; AstArray attributes; AstArray generics; @@ -1561,6 +1576,8 @@ public: }; 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 @@ private: // 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 @@ private: // 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)); + 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 (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) + constPropInBlockChains(ir); + + if (!FFlag::DebugCodegenOptSize) { - bool useValueNumbering = !FFlag::DebugCodegenSkipNumbering; + double startTime = 0.0; + unsigned constPropInstructionCount = 0; - constPropInBlockChains(ir, useValueNumbering); - - if (!FFlag::DebugCodegenOptSize) + if (stats) { - double startTime = 0.0; - unsigned constPropInstructionCount = 0; - - if (stats) - { - constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE); - startTime = lua_clock(); - } - - createLinearBlocks(ir, useValueNumbering); - - if (stats) - { - stats->blockLinearizationStats.timeSeconds += lua_clock() - startTime; - constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE) - constPropInstructionCount; - stats->blockLinearizationStats.constPropInstructionCount += constPropInstructionCount; - } + constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE); + startTime = lua_clock(); } - markDeadStoresInBlockChains(ir); + createLinearBlocks(ir); + + if (stats) + { + stats->blockLinearizationStats.timeSeconds += lua_clock() - startTime; + constPropInstructionCount = getInstructionCount(ir.function.instructions, IrCmd::SUBSTITUTE) - constPropInstructionCount; + stats->blockLinearizationStats.constPropInstructionCount += constPropInstructionCount; + } } + 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)