diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..2d053924 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,3 @@ +--- +InheritParentConfig: true +Checks: '-roblox-*' diff --git a/Analysis/include/Luau/AutocompleteTypes.h b/Analysis/include/Luau/AutocompleteTypes.h index 37d45244..ea934a3a 100644 --- a/Analysis/include/Luau/AutocompleteTypes.h +++ b/Analysis/include/Luau/AutocompleteTypes.h @@ -57,7 +57,7 @@ struct AutocompleteEntry // Set if this suggestion matches the type expected in the context TypeCorrectKind typeCorrect = TypeCorrectKind::None; - std::optional containingClass = std::nullopt; + std::optional containingExternType = std::nullopt; std::optional prop = std::nullopt; std::optional documentationSymbol = std::nullopt; Tags tags; @@ -85,7 +85,7 @@ struct AutocompleteResult }; using StringCompletionCallback = - std::function(std::string tag, std::optional ctx, std::optional contents)>; + std::function(std::string tag, std::optional ctx, std::optional contents)>; constexpr char kGeneratedAnonymousFunctionEntryName[] = "function (anonymous autofilled)"; diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index c3a531d1..3a896e2d 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -3,6 +3,7 @@ #include "Luau/Ast.h" #include "Luau/Constraint.h" +#include "Luau/ConstraintSet.h" #include "Luau/ControlFlow.h" #include "Luau/DataFlowGraph.h" #include "Luau/EqSatSimplification.h" @@ -91,9 +92,8 @@ struct ConstraintGenerator // Constraints that go straight to the solver. std::vector constraints; - // Constraints that do not go to the solver right away. Other constraints - // will enqueue them during solving. - std::vector unqueuedConstraints; + // The set of all free types introduced during constraint generation. + DenseHashSet freeTypes{nullptr}; // Map a function's signature scope back to its signature type. DenseHashMap scopeToFunction{nullptr}; @@ -151,6 +151,9 @@ struct ConstraintGenerator std::vector requireCycles ); + ConstraintSet run(AstStatBlock* block); + ConstraintSet runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block); + /** * The entry point to the ConstraintGenerator. This will construct a set * of scopes, constraints, and free types that can be solved later. @@ -269,7 +272,7 @@ private: ControlFlow visit(const ScopePtr& scope, AstStatTypeAlias* alias); ControlFlow visit(const ScopePtr& scope, AstStatTypeFunction* function); ControlFlow visit(const ScopePtr& scope, AstStatDeclareGlobal* declareGlobal); - ControlFlow visit(const ScopePtr& scope, AstStatDeclareClass* declareClass); + ControlFlow visit(const ScopePtr& scope, AstStatDeclareExternType* declareExternType); ControlFlow visit(const ScopePtr& scope, AstStatDeclareFunction* declareFunction); ControlFlow visit(const ScopePtr& scope, AstStatError* error); @@ -481,9 +484,4 @@ private: TypeId simplifyUnion(const ScopePtr& scope, Location location, TypeId left, TypeId right); }; -/** Borrow a vector of pointers from a vector of owning pointers to constraints. - */ -std::vector> borrowConstraints(const std::vector& constraints); - - } // namespace Luau diff --git a/Analysis/include/Luau/ConstraintSet.h b/Analysis/include/Luau/ConstraintSet.h new file mode 100644 index 00000000..7ce8c44f --- /dev/null +++ b/Analysis/include/Luau/ConstraintSet.h @@ -0,0 +1,32 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#pragma once + +#include "Luau/Constraint.h" +#include "Luau/DenseHash.h" +#include "Luau/Error.h" + +#include + +namespace Luau +{ + +struct ConstraintSet +{ + NotNull rootScope; + + std::vector constraints; + + // The set of all free types created during constraint generation + DenseHashSet freeTypes{nullptr}; + + // Map a function's signature scope back to its signature type. Once we've + // dispatched all of the constraints pertaining to a particular free type, + // we use this mapping to generalize that free type. + DenseHashMap scopeToFunction{nullptr}; + + // It is pretty uncommon for constraint generation to itself produce errors, but it can happen. + std::vector errors; +}; + +} diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 55d337ff..caf3969c 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -3,6 +3,7 @@ #pragma once #include "Luau/Constraint.h" +#include "Luau/ConstraintSet.h" #include "Luau/DataFlowGraph.h" #include "Luau/DenseHash.h" #include "Luau/EqSatSimplification.h" @@ -87,6 +88,7 @@ struct ConstraintSolver NotNull simplifier; NotNull typeFunctionRuntime; // The entire set of constraints that the solver is trying to resolve. + ConstraintSet constraintSet; std::vector> constraints; NotNull> scopeToFunction; NotNull rootScope; @@ -140,6 +142,19 @@ struct ConstraintSolver DenseHashMap typeFunctionsToFinalize{nullptr}; + explicit ConstraintSolver( + NotNull normalizer, + NotNull simplifier, + NotNull typeFunctionRuntime, + ModuleName moduleName, + NotNull moduleResolver, + std::vector requireCycles, + DcrLogger* logger, + NotNull dfg, + TypeCheckLimits limits, + ConstraintSet constraintSet + ); + explicit ConstraintSolver( NotNull normalizer, NotNull simplifier, @@ -174,6 +189,9 @@ struct ConstraintSolver bool isDone() const; private: + /// A helper that does most of the setup work that is shared between the two constructors. + void initFreeTypeTracking(); + void generalizeOneType(TypeId ty); /** @@ -432,6 +450,10 @@ public: void fillInDiscriminantTypes(NotNull constraint, const std::vector>& discriminantTypes); }; +/** Borrow a vector of pointers from a vector of owning pointers to constraints. + */ +std::vector> borrowConstraints(const std::vector& constraints); + void dump(NotNull rootScope, struct ToStringOptions& opts); } // namespace Luau diff --git a/Analysis/include/Luau/DataFlowGraph.h b/Analysis/include/Luau/DataFlowGraph.h index 90ab6419..67e52240 100644 --- a/Analysis/include/Luau/DataFlowGraph.h +++ b/Analysis/include/Luau/DataFlowGraph.h @@ -173,7 +173,7 @@ private: ControlFlow visit(AstStatTypeFunction* f); ControlFlow visit(AstStatDeclareGlobal* d); ControlFlow visit(AstStatDeclareFunction* d); - ControlFlow visit(AstStatDeclareClass* d); + ControlFlow visit(AstStatDeclareExternType* d); ControlFlow visit(AstStatError* error); DataFlowResult visitExpr(AstExpr* e); diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index c1ac076e..243daef1 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -332,11 +332,11 @@ struct TypePackMismatch bool operator==(const TypePackMismatch& rhs) const; }; -struct DynamicPropertyLookupOnClassesUnsafe +struct DynamicPropertyLookupOnExternTypesUnsafe { TypeId ty; - bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const; + bool operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const; }; struct UninhabitedTypeFunction @@ -499,7 +499,7 @@ using TypeErrorData = Variant< TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, - DynamicPropertyLookupOnClassesUnsafe, + DynamicPropertyLookupOnExternTypesUnsafe, UninhabitedTypeFunction, UninhabitedTypePackFunction, WhereClauseNeeded, diff --git a/Analysis/include/Luau/FileResolver.h b/Analysis/include/Luau/FileResolver.h index e3fc292a..bcc2e0e0 100644 --- a/Analysis/include/Luau/FileResolver.h +++ b/Analysis/include/Luau/FileResolver.h @@ -117,8 +117,7 @@ struct FileResolver return std::nullopt; } - // Make non-virtual when removing FFlagLuauImproveRequireByStringAutocomplete. - virtual std::optional getRequireSuggestions(const ModuleName& requirer, const std::optional& pathString) const; + std::optional getRequireSuggestions(const ModuleName& requirer, const std::optional& pathString) const; std::shared_ptr requireSuggester; }; diff --git a/Analysis/include/Luau/Instantiation.h b/Analysis/include/Luau/Instantiation.h index 73345f98..3e61da7f 100644 --- a/Analysis/include/Luau/Instantiation.h +++ b/Analysis/include/Luau/Instantiation.h @@ -133,9 +133,9 @@ struct GenericTypeFinder : TypeOnceVisitor return false; } - bool visit(TypeId ty, const Luau::ClassType&) override + bool visit(TypeId ty, const Luau::ExternType&) override { - // During function instantiation, classes are not traversed even if they have generics + // During function instantiation, extern types are not traversed even if they have generics return false; } }; diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index f014c433..d3e16b32 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -181,7 +181,7 @@ struct NormalizedStringType bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& superStr); -struct NormalizedClassType +struct NormalizedExternType { /** Has the following structure: * @@ -192,7 +192,7 @@ struct NormalizedClassType * * Each TypeId is a class type. */ - std::unordered_map classes; + std::unordered_map externTypes; /** * In order to maintain a consistent insertion order, we use this vector to @@ -245,7 +245,7 @@ enum class NormalizationResult }; // A normalized type is either any, unknown, or one of the form P | T | F | G where -// * P is a union of primitive types (including singletons, classes and the error type) +// * P is a union of primitive types (including singletons, extern types and the error type) // * T is a union of table types // * F is a union of an intersection of function types // * G is a union of generic/free/blocked types, intersected with a normalized type @@ -260,7 +260,7 @@ struct NormalizedType // This type is either never, boolean type, or a boolean singleton. TypeId booleans; - NormalizedClassType classes; + NormalizedExternType externTypes; // The error part of the type. // This type is either never or the error type. @@ -333,7 +333,7 @@ struct NormalizedType // Helpers that improve readability of the above (they just say if the component is present) bool hasTops() const; bool hasBooleans() const; - bool hasClasses() const; + bool hasExternTypes() const; bool hasErrors() const; bool hasNils() const; bool hasNumbers() const; @@ -391,10 +391,10 @@ public: void unionTysWithTy(TypeIds& here, TypeId there); TypeId unionOfTops(TypeId here, TypeId there); TypeId unionOfBools(TypeId here, TypeId there); - void unionClassesWithClass(TypeIds& heres, TypeId there); - void unionClasses(TypeIds& heres, const TypeIds& theres); - void unionClassesWithClass(NormalizedClassType& heres, TypeId there); - void unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres); + void unionExternTypesWithExternType(TypeIds& heres, TypeId there); + void unionExternTypes(TypeIds& heres, const TypeIds& theres); + void unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there); + void unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres); void unionStrings(NormalizedStringType& here, const NormalizedStringType& there); std::optional unionOfTypePacks(TypePackId here, TypePackId there); std::optional unionOfFunctions(TypeId here, TypeId there); @@ -423,8 +423,8 @@ public: // ------- Normalizing intersections TypeId intersectionOfTops(TypeId here, TypeId there); TypeId intersectionOfBools(TypeId here, TypeId there); - void intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres); - void intersectClassesWithClass(NormalizedClassType& heres, TypeId there); + void intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres); + void intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there); void intersectStrings(NormalizedStringType& here, const NormalizedStringType& there); std::optional intersectionOfTypePacks(TypePackId here, TypePackId there); std::optional intersectionOfTables(TypeId here, TypeId there, SeenTablePropPairs& seenTablePropPairs, Set& seenSet); diff --git a/Analysis/include/Luau/Quantify.h b/Analysis/include/Luau/Quantify.h index 8478ddb2..d9b87664 100644 --- a/Analysis/include/Luau/Quantify.h +++ b/Analysis/include/Luau/Quantify.h @@ -16,7 +16,7 @@ struct Scope; void quantify(TypeId ty, TypeLevel level); -// TODO: This is eerily similar to the pattern that NormalizedClassType +// TODO: This is eerily similar to the pattern that NormalizedExternType // implements. We could, and perhaps should, merge them together. template struct OrderedMap diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index 26c4553e..aeee0829 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -22,7 +22,7 @@ struct InternalErrorReporter; class TypeIds; class Normalizer; -struct NormalizedClassType; +struct NormalizedExternType; struct NormalizedFunctionType; struct NormalizedStringType; struct NormalizedType; @@ -121,7 +121,7 @@ struct SubtypingEnvironment DenseHashMap mappedGenericPacks{nullptr}; /* - * See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for + * See the test cyclic_tables_are_assumed_to_be_compatible_with_extern_types for * details. * * An empty value is equivalent to a nonexistent key. @@ -229,9 +229,8 @@ private: SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull scope); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull scope); - SubtypingResult - isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const FunctionType* subFunction, @@ -259,11 +258,11 @@ private: ); SubtypingResult isCovariantWith( SubtypingEnvironment& env, - const NormalizedClassType& subClass, - const NormalizedClassType& superClass, + const NormalizedExternType& subExternType, + const NormalizedExternType& superExternType, NotNull scope ); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull scope); + SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull scope); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const NormalizedStringType& subString, diff --git a/Analysis/include/Luau/ToString.h b/Analysis/include/Luau/ToString.h index 4862e3b4..6496c588 100644 --- a/Analysis/include/Luau/ToString.h +++ b/Analysis/include/Luau/ToString.h @@ -44,6 +44,7 @@ struct ToStringOptions bool hideTableKind = false; // If true, all tables will be surrounded with plain '{}' bool hideNamedFunctionTypeParameters = false; // If true, type parameters of functions will be hidden at top-level. bool hideFunctionSelfArgument = false; // If true, `self: X` will be omitted from the function signature if the function has self + bool hideTableAliasExpansions = false; // If true, all table aliases will not be expanded bool useQuestionMarks = true; // If true, use a postfix ? for options, else write them out as unions that include nil. size_t maxTableLength = size_t(FInt::LuauTableTypeMaximumStringifierLength); // Only applied to TableTypes size_t maxTypeLength = size_t(FInt::LuauTypeMaximumStringifierLength); diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h index 3af8fa6c..65661b46 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -291,7 +291,7 @@ struct MagicFunctionCallContext { NotNull solver; NotNull constraint; - const class AstExprCall* callSite; + NotNull callSite; TypePackId arguments; TypePackId result; }; @@ -536,15 +536,15 @@ struct ClassUserData virtual ~ClassUserData() {} }; -/** The type of a class. +/** The type of an external userdata exposed to Luau. * - * Classes behave like tables in many ways, but there are some important differences: + * Extern types behave like tables in many ways, but there are some important differences: * * The properties of a class are always exactly known. - * Classes optionally have a parent class. - * Two different classes that share the same properties are nevertheless distinct and mutually incompatible. + * Extern types optionally have a parent type. + * Two different extern types that share the same properties are nevertheless distinct and mutually incompatible. */ -struct ClassType +struct ExternType { using Props = TableType::Props; @@ -558,7 +558,7 @@ struct ClassType std::optional definitionLocation; std::optional indexer; - ClassType( + ExternType( Name name, Props props, std::optional parent, @@ -579,7 +579,7 @@ struct ClassType { } - ClassType( + ExternType( Name name, Props props, std::optional parent, @@ -779,7 +779,7 @@ using TypeVariant = Unifiable::Variant< FunctionType, TableType, MetatableType, - ClassType, + ExternType, AnyType, UnionType, IntersectionType, @@ -990,7 +990,7 @@ public: const TypeId threadType; const TypeId bufferType; const TypeId functionType; - const TypeId classType; + const TypeId externType; const TypeId tableType; const TypeId emptyTableType; const TypeId trueType; @@ -1002,6 +1002,7 @@ public: const TypeId noRefineType; const TypeId falsyType; const TypeId truthyType; + const TypeId notNilType; const TypeId optionalNumberType; const TypeId optionalStringType; @@ -1022,10 +1023,10 @@ TypeLevel* getMutableLevel(TypeId ty); std::optional getLevel(TypePackId tp); -const Property* lookupClassProp(const ClassType* cls, const Name& name); +const Property* lookupExternTypeProp(const ExternType* cls, const Name& name); // Whether `cls` is a subclass of `parent` -bool isSubclass(const ClassType* cls, const ClassType* parent); +bool isSubclass(const ExternType* cls, const ExternType* parent); Type* asMutable(TypeId ty); diff --git a/Analysis/include/Luau/TypeAttach.h b/Analysis/include/Luau/TypeAttach.h index c945805a..ad4d83fb 100644 --- a/Analysis/include/Luau/TypeAttach.h +++ b/Analysis/include/Luau/TypeAttach.h @@ -11,7 +11,7 @@ namespace Luau struct TypeRehydrationOptions { std::unordered_set bannedNames; - bool expandClassProps = false; + bool expandExternTypeProps = false; }; void attachTypeData(SourceModule& source, Module& result); diff --git a/Analysis/include/Luau/TypeChecker2.h b/Analysis/include/Luau/TypeChecker2.h index 2f88f345..838688ee 100644 --- a/Analysis/include/Luau/TypeChecker2.h +++ b/Analysis/include/Luau/TypeChecker2.h @@ -160,7 +160,7 @@ private: void visit(AstTypeList types); void visit(AstStatDeclareFunction* stat); void visit(AstStatDeclareGlobal* stat); - void visit(AstStatDeclareClass* stat); + void visit(AstStatDeclareExternType* stat); void visit(AstStatError* stat); void visit(AstExpr* expr, ValueContext context); void visit(AstExprGroup* expr, ValueContext context); diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index a7a26958..108910c1 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -205,7 +205,7 @@ struct TypeFunctionTableType std::optional metatable; }; -struct TypeFunctionClassType +struct TypeFunctionExternType { using Name = std::string; using Props = std::map; @@ -222,7 +222,7 @@ struct TypeFunctionClassType std::optional readParent; std::optional writeParent; - TypeId classTy; + TypeId externTy; }; struct TypeFunctionGenericType @@ -244,7 +244,7 @@ using TypeFunctionTypeVariant = Luau::Variant< TypeFunctionNegationType, TypeFunctionFunctionType, TypeFunctionTableType, - TypeFunctionClassType, + TypeFunctionExternType, TypeFunctionGenericType>; struct TypeFunctionType diff --git a/Analysis/include/Luau/TypeFwd.h b/Analysis/include/Luau/TypeFwd.h index 42d582fe..895feaf0 100644 --- a/Analysis/include/Luau/TypeFwd.h +++ b/Analysis/include/Luau/TypeFwd.h @@ -29,7 +29,7 @@ struct SingletonType; struct FunctionType; struct TableType; struct MetatableType; -struct ClassType; +struct ExternType; struct AnyType; struct UnionType; struct IntersectionType; diff --git a/Analysis/include/Luau/TypeInfer.h b/Analysis/include/Luau/TypeInfer.h index 5e817f41..524908f0 100644 --- a/Analysis/include/Luau/TypeInfer.h +++ b/Analysis/include/Luau/TypeInfer.h @@ -90,11 +90,11 @@ struct TypeChecker ControlFlow check(const ScopePtr& scope, TypeId ty, const ScopePtr& funScope, const AstStatLocalFunction& function); ControlFlow check(const ScopePtr& scope, const AstStatTypeAlias& typealias); ControlFlow check(const ScopePtr& scope, const AstStatTypeFunction& typefunction); - ControlFlow check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass); + ControlFlow check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType); ControlFlow check(const ScopePtr& scope, const AstStatDeclareFunction& declaredFunction); void prototype(const ScopePtr& scope, const AstStatTypeAlias& typealias, int subLevel = 0); - void prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass); + void prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType); ControlFlow checkBlock(const ScopePtr& scope, const AstStatBlock& statement); ControlFlow checkBlockWithoutRecursionCheck(const ScopePtr& scope, const AstStatBlock& statement); @@ -487,7 +487,7 @@ private: /** * A set of incorrect class definitions which is used to avoid a second-pass analysis. */ - DenseHashSet incorrectClassDefinitions{nullptr}; + DenseHashSet incorrectExternTypeDefinitions{nullptr}; std::vector> deferredQuantification; }; diff --git a/Analysis/include/Luau/Unifier.h b/Analysis/include/Luau/Unifier.h index 8d0f2806..eb978fba 100644 --- a/Analysis/include/Luau/Unifier.h +++ b/Analysis/include/Luau/Unifier.h @@ -140,7 +140,7 @@ private: void tryUnifyTables(TypeId subTy, TypeId superTy, bool isIntersection = false, const LiteralProperties* aliasableMap = nullptr); void tryUnifyScalarShape(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed); - void tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed); + void tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed); void tryUnifyNegations(TypeId subTy, TypeId superTy); TypePackId tryApplyOverloadedFunction(TypeId function, const NormalizedFunctionType& overloads, TypePackId args); diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h index a9685462..06e20a94 100644 --- a/Analysis/include/Luau/VisitType.h +++ b/Analysis/include/Luau/VisitType.h @@ -126,7 +126,7 @@ struct GenericTypeVisitor { return visit(ty); } - virtual bool visit(TypeId ty, const ClassType& ctv) + virtual bool visit(TypeId ty, const ExternType& etv) { return visit(ty); } @@ -313,11 +313,11 @@ struct GenericTypeVisitor traverse(mtv->metatable); } } - else if (auto ctv = get(ty)) + else if (auto etv = get(ty)) { - if (visit(ty, *ctv)) + if (visit(ty, *etv)) { - for (const auto& [name, prop] : ctv->props) + for (const auto& [name, prop] : etv->props) { if (FFlag::LuauSolverV2) { @@ -335,16 +335,16 @@ struct GenericTypeVisitor traverse(prop.type()); } - if (ctv->parent) - traverse(*ctv->parent); + if (etv->parent) + traverse(*etv->parent); - if (ctv->metatable) - traverse(*ctv->metatable); + if (etv->metatable) + traverse(*etv->metatable); - if (ctv->indexer) + if (etv->indexer) { - traverse(ctv->indexer->indexType); - traverse(ctv->indexer->indexResultType); + traverse(etv->indexer->indexType); + traverse(etv->indexer->indexResultType); } } } @@ -396,7 +396,7 @@ struct GenericTypeVisitor traverse(unwrapped); // Visiting into LazyType that hasn't been unwrapped may necessarily cause infinite expansion, so we don't do that on purpose. - // Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ClassType + // Asserting also makes no sense, because the type _will_ happen here, most likely as a property of some ExternType // that doesn't need to be expanded. } else if (auto stv = get(ty)) diff --git a/Analysis/src/Anyification.cpp b/Analysis/src/Anyification.cpp index 4bacec03..ec4e05e2 100644 --- a/Analysis/src/Anyification.cpp +++ b/Analysis/src/Anyification.cpp @@ -88,7 +88,7 @@ TypePackId Anyification::clean(TypePackId tp) bool Anyification::ignoreChildren(TypeId ty) { - if (get(ty)) + if (get(ty)) return true; return ty->persistent; diff --git a/Analysis/src/ApplyTypeFunction.cpp b/Analysis/src/ApplyTypeFunction.cpp index 025e8f6d..04035dda 100644 --- a/Analysis/src/ApplyTypeFunction.cpp +++ b/Analysis/src/ApplyTypeFunction.cpp @@ -31,7 +31,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty) { if (get(ty)) return true; - else if (get(ty)) + else if (get(ty)) return true; else return false; diff --git a/Analysis/src/AstJsonEncoder.cpp b/Analysis/src/AstJsonEncoder.cpp index 9318a8fa..416a0b9c 100644 --- a/Analysis/src/AstJsonEncoder.cpp +++ b/Analysis/src/AstJsonEncoder.cpp @@ -8,6 +8,8 @@ #include +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) + namespace Luau { @@ -431,8 +433,16 @@ struct AstJsonEncoder : public AstVisitor if (node->self) PROP(self); PROP(args); - if (node->returnAnnotation) - PROP(returnAnnotation); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + if (node->returnAnnotation) + PROP(returnAnnotation); + } + else + { + if (node->returnAnnotation_DEPRECATED) + write("returnAnnotation", node->returnAnnotation_DEPRECATED); + } PROP(vararg); PROP(varargLocation); if (node->varargAnnotation) @@ -902,7 +912,10 @@ struct AstJsonEncoder : public AstVisitor PROP(paramNames); PROP(vararg); PROP(varargLocation); - PROP(retTypes); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + PROP(retTypes); + else + write("retTypes", node->retTypes_DEPRECATED); PROP(generics); PROP(genericPacks); } @@ -923,7 +936,7 @@ struct AstJsonEncoder : public AstVisitor ); } - void write(const AstDeclaredClassProp& prop) + void write(const AstDeclaredExternTypeProperty& prop) { writeRaw("{"); bool c = pushComma(); @@ -936,7 +949,7 @@ struct AstJsonEncoder : public AstVisitor writeRaw("}"); } - void write(class AstStatDeclareClass* node) + void write(class AstStatDeclareExternType* node) { writeNode( node, @@ -1048,7 +1061,10 @@ struct AstJsonEncoder : public AstVisitor PROP(genericPacks); PROP(argTypes); PROP(argNames); - PROP(returnTypes); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + PROP(returnTypes); + else + write("returnTypes", node->returnTypes_DEPRECATED); } ); } @@ -1429,7 +1445,7 @@ struct AstJsonEncoder : public AstVisitor return false; } - bool visit(class AstStatDeclareClass* node) override + bool visit(class AstStatDeclareExternType* node) override { write(node); return false; diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp index 02d6444b..342d621c 100644 --- a/Analysis/src/AstQuery.cpp +++ b/Analysis/src/AstQuery.cpp @@ -574,11 +574,11 @@ std::optional getDocumentationSymbolAtPosition(const Source return checkOverloadedDocumentationSymbol(module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol); } } - else if (const ClassType* ctv = get(parentTy)) + else if (const ExternType* etv = get(parentTy)) { - while (ctv) + while (etv) { - if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end()) + if (auto propIt = etv->props.find(indexName->index.value); propIt != etv->props.end()) { if (FFlag::LuauSolverV2) { @@ -590,7 +590,7 @@ std::optional getDocumentationSymbolAtPosition(const Source module, propIt->second.type(), parentExpr, propIt->second.documentationSymbol ); } - ctv = ctv->parent ? Luau::get(*ctv->parent) : nullptr; + etv = etv->parent ? Luau::get(*etv->parent) : nullptr; } } else if (const PrimitiveType* ptv = get(parentTy); ptv && ptv->metatable) diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index 7918e91f..e862f02e 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -24,14 +24,10 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferIterationLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAGVARIABLE(DebugLuauMagicVariableNames) - -LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete) - -LUAU_FASTFLAGVARIABLE(LuauAutocompleteRefactorsForIncrementalAutocomplete) - LUAU_FASTFLAGVARIABLE(LuauAutocompleteUsesModuleForTypeCompatibility) LUAU_FASTFLAGVARIABLE(LuauAutocompleteUnionCopyPreviousSeen) LUAU_FASTFLAGVARIABLE(LuauAutocompleteMissingFollows) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) static const std::unordered_set kStatementStartingKeywords = {"while", "if", "local", "repeat", "function", "do", "for", "return", "break", "continue", "type", "export"}; @@ -311,7 +307,7 @@ static void autocompleteProps( const std::vector& nodes, AutocompleteEntryMap& result, std::unordered_set& seen, - std::optional containingClass = std::nullopt + std::optional containingExternType = std::nullopt ) { rootTy = follow(rootTy); @@ -334,8 +330,8 @@ static void autocompleteProps( if (calledWithSelf == ftv->hasSelf) return true; - // Calls on classes require strict match between how function is declared and how it's called - if (get(rootTy)) + // Calls on extern types require strict match between how function is declared and how it's called + if (get(rootTy)) return false; // When called with ':', but declared without 'self', it is invalid if a function has incompatible first argument or no arguments at all @@ -368,7 +364,7 @@ static void autocompleteProps( return calledWithSelf; }; - auto fillProps = [&](const ClassType::Props& props) + auto fillProps = [&](const ExternType::Props& props) { for (const auto& [name, prop] : props) { @@ -401,7 +397,7 @@ static void autocompleteProps( prop.deprecated, isWrongIndexer(type), typeCorrect, - containingClass, + containingExternType, &prop, prop.documentationSymbol, {}, @@ -432,12 +428,12 @@ static void autocompleteProps( } }; - if (auto cls = get(ty)) + if (auto cls = get(ty)) { - containingClass = containingClass.value_or(cls); + containingExternType = containingExternType.value_or(cls); fillProps(cls->props); if (cls->parent) - autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingClass); + autocompleteProps(module, typeArena, builtinTypes, rootTy, *cls->parent, indexType, nodes, result, seen, containingExternType); } else if (auto tbl = get(ty)) fillProps(tbl->props); @@ -491,7 +487,7 @@ static void autocompleteProps( // If we don't do this, and we have the misfortune of receiving a // recursive union like: // - // t1 where t1 = t1 | Class + // t1 where t1 = t1 | ExternType // // Then we are on a one way journey to a stack overflow. if (FFlag::LuauAutocompleteUnionCopyPreviousSeen) @@ -591,7 +587,7 @@ AutocompleteEntryMap autocompleteProps( AutocompleteEntryMap autocompleteModuleTypes(const Module& module, const ScopePtr& scopeAtPosition, Position position, std::string_view moduleName) { AutocompleteEntryMap result; - ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); + ScopePtr startScope = scopeAtPosition; for (ScopePtr& scope = startScope; scope; scope = scope->parent) { if (auto it = scope->importedTypeBindings.find(std::string(moduleName)); it != scope->importedTypeBindings.end()) @@ -703,6 +699,30 @@ static std::optional findTypeElementAt(const AstTypeList& astTypeList, T return {}; } +static std::optional findTypeElementAt(AstTypePack* astTypePack, TypePackId tp, Position position) +{ + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); + if (const auto typePack = astTypePack->as()) + { + return findTypeElementAt(typePack->typeList, tp, position); + } + else if (const auto variadic = astTypePack->as()) + { + if (variadic->location.containsClosed(position)) + { + auto [_, tail] = flatten(tp); + + if (tail) + { + if (const VariadicTypePack* vtp = get(follow(*tail))) + return findTypeElementAt(variadic->variadicType, vtp->ty, position); + } + } + } + + return {}; +} + static std::optional findTypeElementAt(AstType* astType, TypeId ty, Position position) { ty = follow(ty); @@ -723,8 +743,16 @@ static std::optional findTypeElementAt(AstType* astType, TypeId ty, Posi if (auto element = findTypeElementAt(type->argTypes, ftv->argTypes, position)) return element; - if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position)) - return element; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + if (auto element = findTypeElementAt(type->returnTypes, ftv->retTypes, position)) + return element; + } + else + { + if (auto element = findTypeElementAt(type->returnTypes_DEPRECATED, ftv->retTypes, position)) + return element; + } } // It's possible to walk through other types like intrsection and unions if we find value in doing that @@ -733,7 +761,7 @@ static std::optional findTypeElementAt(AstType* astType, TypeId ty, Posi std::optional getLocalTypeInScopeAt(const Module& module, const ScopePtr& scopeAtPosition, Position position, AstLocal* local) { - if (ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position)) + if (ScopePtr scope = scopeAtPosition) { for (const auto& [name, binding] : scope->bindings) { @@ -875,7 +903,7 @@ AutocompleteEntryMap autocompleteTypeNames( { AutocompleteEntryMap result; - ScopePtr startScope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); + ScopePtr startScope = scopeAtPosition; for (ScopePtr scope = startScope; scope; scope = scope->parent) { @@ -1054,29 +1082,46 @@ AutocompleteEntryMap autocompleteTypeNames( } } - if (!node->returnAnnotation) - return result; - - for (size_t i = 0; i < node->returnAnnotation->types.size; i++) + if (FFlag::LuauStoreReturnTypesAsPackOnAst) { - AstType* ret = node->returnAnnotation->types.data[i]; + if (!node->returnAnnotation) + return result; - if (ret->location.containsClosed(position)) + if (const auto typePack = node->returnAnnotation->as()) { - if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node)) + for (size_t i = 0; i < typePack->typeList.types.size; i++) { - if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i)) - tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position); + AstType* ret = typePack->typeList.types.data[i]; + + if (ret->location.containsClosed(position)) + { + if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node)) + { + if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i)) + tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position); + } + + // TODO: with additional type information, we could suggest inferred return type here + break; + } } - // TODO: with additional type information, we could suggest inferred return type here - break; + if (AstTypePack* retTp = typePack->typeList.tailType) + { + if (auto variadic = retTp->as()) + { + if (variadic->location.containsClosed(position)) + { + if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node)) + { + if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u)) + tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position); + } + } + } + } } - } - - if (AstTypePack* retTp = node->returnAnnotation->tailType) - { - if (auto variadic = retTp->as()) + else if (auto variadic = node->returnAnnotation->as()) { if (variadic->location.containsClosed(position)) { @@ -1088,6 +1133,43 @@ AutocompleteEntryMap autocompleteTypeNames( } } } + else + { + if (!node->returnAnnotation_DEPRECATED) + return result; + + for (size_t i = 0; i < node->returnAnnotation_DEPRECATED->types.size; i++) + { + AstType* ret = node->returnAnnotation_DEPRECATED->types.data[i]; + + if (ret->location.containsClosed(position)) + { + if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node)) + { + if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, i)) + tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position); + } + + // TODO: with additional type information, we could suggest inferred return type here + break; + } + } + + if (AstTypePack* retTp = node->returnAnnotation_DEPRECATED->tailType) + { + if (auto variadic = retTp->as()) + { + if (variadic->location.containsClosed(position)) + { + if (const FunctionType* ftv = tryGetExpectedFunctionType(module, node)) + { + if (auto ty = tryGetTypePackTypeAt(ftv->retTypes, ~0u)) + tryAddTypeCorrectSuggestion(result, startScope, topType, *ty, position); + } + } + } + } + } } return result; @@ -1208,7 +1290,7 @@ static AutocompleteEntryMap autocompleteStatement( ) { // This is inefficient. :( - ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); + ScopePtr scope = scopeAtPosition; AutocompleteEntryMap result; @@ -1386,7 +1468,7 @@ static AutocompleteContext autocompleteExpression( else { // This is inefficient. :( - ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(module, position); + ScopePtr scope = scopeAtPosition; while (scope) { @@ -1455,7 +1537,7 @@ static AutocompleteResult autocompleteExpression( return {result, ancestry, context}; } -static std::optional getMethodContainingClass(const ModulePtr& module, AstExpr* funcExpr) +static std::optional getMethodContainingExternType(const ModulePtr& module, AstExpr* funcExpr) { AstExpr* parentExpr = nullptr; if (auto indexName = funcExpr->as()) @@ -1479,14 +1561,14 @@ static std::optional getMethodContainingClass(const ModulePtr& Luau::TypeId parentType = Luau::follow(*parentIt); - if (auto parentClass = Luau::get(parentType)) + if (auto parentExternType = Luau::get(parentType)) { - return parentClass; + return parentExternType; } if (auto parentUnion = Luau::get(parentType)) { - return returnFirstNonnullOptionOfType(parentUnion); + return returnFirstNonnullOptionOfType(parentUnion); } return std::nullopt; @@ -1544,10 +1626,7 @@ static std::optional convertRequireSuggestionsToAutocomple { AutocompleteEntry entry = {AutocompleteEntryKind::RequirePath}; entry.insertText = std::move(suggestion.fullPath); - if (FFlag::LuauExposeRequireByStringAutocomplete) - { - entry.tags = std::move(suggestion.tags); - } + entry.tags = std::move(suggestion.tags); result[std::move(suggestion.label)] = std::move(entry); } return result; @@ -1608,7 +1687,7 @@ static std::optional autocompleteStringParams( { return convertRequireSuggestionsToAutocompleteEntryMap(fileResolver->getRequireSuggestions(module->name, candidateString)); } - if (std::optional ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString)) + if (std::optional ret = callback(tag, getMethodContainingExternType(module, candidate->func), candidateString)) { return ret; } @@ -1776,7 +1855,7 @@ static std::optional makeAnonymousAutofilled( if (!type) return std::nullopt; - const ScopePtr scope = FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete ? scopeAtPosition : findScopeAtPosition(*module, position); + const ScopePtr scope = scopeAtPosition; if (!scope) return std::nullopt; diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index d3c0e093..6250cc0b 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -32,10 +32,10 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) -LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauFollowTableFreeze) LUAU_FASTFLAGVARIABLE(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked) +LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) namespace Luau { @@ -344,7 +344,7 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC if (auto it = globals.globalScope->exportedTypeBindings.find("vector"); it != globals.globalScope->exportedTypeBindings.end()) { TypeId vectorTy = it->second.type; - ClassType* vectorCls = getMutable(vectorTy); + ExternType* vectorCls = getMutable(vectorTy); vectorCls->metatable = arena.addType(TableType{{}, std::nullopt, TypeLevel{}, TableState::Sealed}); TableType* metatableTy = Luau::getMutable(vectorCls->metatable); @@ -705,6 +705,14 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context) return true; } + // CLI-150726: The block below effectively constructs a type pack and then type checks it by going parameter-by-parameter. + // This does _not_ handle cases like: + // + // local foo : () -> (...string) = (nil :: any) + // print(string.format("%s %d %s", foo())) + // + // ... which should be disallowed. + std::vector expected = parseFormatString(context.builtinTypes, fmt->value.data, fmt->value.size); const auto& [params, tail] = flatten(context.arguments); @@ -716,7 +724,9 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context) { TypeId actualTy = params[i + paramOffset]; TypeId expectedTy = expected[i]; - Location location = context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location; + Location location = FFlag::LuauFormatUseLastPosition + ? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location + : context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location; // use subtyping instead here SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); @@ -1529,8 +1539,7 @@ bool MagicClone::infer(const MagicFunctionCallContext& context) tableType->scope = context.constraint->scope.get(); } - if (FFlag::LuauTrackInteriorFreeTypesOnScope) - trackInteriorFreeType(context.constraint->scope.get(), resultType); + trackInteriorFreeType(context.constraint->scope.get(), resultType); TypePackId clonedTypePack = arena->addTypePack({resultType}); asMutable(context.result)->ty.emplace(clonedTypePack); diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp index 38635a2d..00618100 100644 --- a/Analysis/src/Clone.cpp +++ b/Analysis/src/Clone.cpp @@ -355,7 +355,7 @@ private: t->metatable = shallowClone(t->metatable); } - void cloneChildren(ClassType* t) + void cloneChildren(ExternType* t) { for (auto& [_, p] : t->props) p = shallowClone(p); diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index e62a3f18..42fd690c 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -20,7 +20,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor DenseHashSet* result; - ReferenceCountInitializer(DenseHashSet* result) + explicit ReferenceCountInitializer(DenseHashSet* result) : result(result) { } @@ -43,9 +43,9 @@ struct ReferenceCountInitializer : TypeOnceVisitor return false; } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { - // ClassTypes never contain free types. + // ExternTypes never contain free types. return false; } diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 36482075..db4dd714 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -34,15 +34,12 @@ LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) -LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAGVARIABLE(LuauPropagateExpectedTypesForCalls) LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAGVARIABLE(LuauUngeneralizedTypesForRecursiveFunctions) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) -LUAU_FASTFLAGVARIABLE(LuauDoNotLeakNilInRefinement) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) @@ -50,9 +47,11 @@ LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAGVARIABLE(LuauCacheInferencePerAstExpr) LUAU_FASTFLAGVARIABLE(LuauAlwaysResolveAstTypes) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauNoTypeFunctionsNamedTypeOf) +LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) namespace Luau { @@ -157,7 +156,7 @@ struct HasFreeType : TypeOnceVisitor return true; } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } @@ -218,6 +217,32 @@ ConstraintGenerator::ConstraintGenerator( LUAU_ASSERT(module); } +ConstraintSet ConstraintGenerator::run(AstStatBlock* block) +{ + visitModuleRoot(block); + + return ConstraintSet{ + NotNull{rootScope}, + std::move(constraints), + std::move(freeTypes), + std::move(scopeToFunction), + std::move(errors) + }; +} + +ConstraintSet ConstraintGenerator::runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block) +{ + visitFragmentRoot(resumeScope, block); + + return ConstraintSet{ + NotNull{rootScope}, + std::move(constraints), + std::move(freeTypes), + std::move(scopeToFunction), + std::move(errors) + }; +} + void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) { LUAU_TIMETRACE_SCOPE("ConstraintGenerator::visitModuleRoot", "Typechecking"); @@ -263,8 +288,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) GeneralizationConstraint{ result, moduleFnTy, - (FFlag::LuauNonReentrantGeneralization2 || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector{} - : std::move(DEPRECATED_interiorTypes.back()) + std::vector{}, } ); @@ -273,7 +297,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); } - else if (FFlag::LuauTrackInteriorFreeTypesOnScope) + else scope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); getMutable(result)->setOwner(genConstraint); @@ -359,17 +383,17 @@ TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) { auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); interiorFreeTypes.back().types.push_back(ft); - return ft; - } - else if (FFlag::LuauTrackInteriorFreeTypesOnScope) - { - auto ft = Luau::freshType(arena, builtinTypes, scope.get()); - DEPRECATED_interiorTypes.back().push_back(ft); + + if (FFlag::DebugLuauGreedyGeneralization) + freeTypes.insert(ft); + return ft; } else { - return Luau::freshType(arena, builtinTypes, scope.get()); + auto ft = Luau::freshType(arena, builtinTypes, scope.get()); + DEPRECATED_interiorTypes.back().push_back(ft); + return ft; } } @@ -578,15 +602,8 @@ void ConstraintGenerator::computeRefinement( // When the top-level expression is `t[x]`, we want to refine it into `nil`, not `never`. LUAU_ASSERT(refis->get(proposition->key->def)); - if (FFlag::LuauDoNotLeakNilInRefinement) - { - refis->get(proposition->key->def)->shouldAppendNilType = - (sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall; - } - else - { - refis->get(proposition->key->def)->shouldAppendNilType = (sense || !eq) && containsSubscriptedDefinition(proposition->key->def); - } + refis->get(proposition->key->def)->shouldAppendNilType = + (sense || !eq) && containsSubscriptedDefinition(proposition->key->def) && !proposition->implicitFromCall; } } @@ -633,7 +650,7 @@ struct FindSimplificationBlockers : TypeOnceVisitor return false; } - bool visit(TypeId, const ClassType&) override + bool visit(TypeId, const ExternType&) override { return false; } @@ -858,7 +875,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc aliasDefinitionLocations[function->name.value] = function->location; } - else if (auto classDeclaration = stat->as()) + else if (auto classDeclaration = stat->as()) { if (scope->exportedTypeBindings.count(classDeclaration->name.value)) { @@ -1052,7 +1069,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat) return visit(scope, s); else if (auto s = stat->as()) return visit(scope, s); - else if (auto s = stat->as()) + else if (auto s = stat->as()) return visit(scope, s); else if (auto s = stat->as()) return visit(scope, s); @@ -1273,11 +1290,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI for (AstLocal* var : forIn->vars) { - TypeId assignee = arena->addType(BlockedType{}); - variableTypes.push_back(assignee); - TypeId loopVar = arena->addType(BlockedType{}); - localTypes[loopVar].insert(assignee); + variableTypes.push_back(loopVar); if (var->annotation) { @@ -1296,6 +1310,23 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI loopScope, getLocation(forIn->values), IterableConstraint{iterator, variableTypes, forIn->values.data[0], &module->astForInNextTypes} ); + if (FFlag::LuauAddCallConstraintForIterableFunctions) + { + // Add an intersection ReduceConstraint for the key variable to denote that it can't be nil + AstLocal* keyVar = *forIn->vars.begin(); + const DefId keyDef = dfg->getDef(keyVar); + const TypeId loopVar = loopScope->lvalueTypes[keyDef]; + + const TypeId intersectionTy = + createTypeFunctionInstance(builtinTypeFunctions().intersectFunc, {loopVar, builtinTypes->notNilType}, {}, loopScope, keyVar->location); + + loopScope->bindings[keyVar] = Binding{intersectionTy, keyVar->location}; + loopScope->lvalueTypes[keyDef] = intersectionTy; + + auto c = addConstraint(loopScope, keyVar->location, ReduceConstraint{intersectionTy}); + c->dependencies.push_back(iterable); + } + for (TypeId var : variableTypes) { auto bt = getMutable(var); @@ -1779,7 +1810,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio GeneralizationConstraint{ generalizedTy, sig.signature, - FFlag::LuauTrackInteriorFreeTypesOnScope ? std::vector{} : std::move(DEPRECATED_interiorTypes.back()) + std::vector{}, } ); @@ -1788,7 +1819,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); } - else if (FFlag::LuauTrackInteriorFreeTypesOnScope) + else sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); getMutable(generalizedTy)->setOwner(gc); @@ -1852,71 +1883,71 @@ static bool isMetamethod(const Name& name) name == "__idiv"; } -ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClass* declaredClass) +ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExternType* declaredExternType) { // If a class with the same name was already defined, we skip over - auto bindingIt = scope->exportedTypeBindings.find(declaredClass->name.value); + auto bindingIt = scope->exportedTypeBindings.find(declaredExternType->name.value); if (bindingIt == scope->exportedTypeBindings.end()) return ControlFlow::None; - std::optional superTy = std::make_optional(builtinTypes->classType); - if (declaredClass->superName) + std::optional superTy = std::make_optional(builtinTypes->externType); + if (declaredExternType->superName) { - Name superName = Name(declaredClass->superName->value); + Name superName = Name(declaredExternType->superName->value); std::optional lookupType = scope->lookupType(superName); if (!lookupType) { - reportError(declaredClass->location, UnknownSymbol{superName, UnknownSymbol::Type}); + reportError(declaredExternType->location, UnknownSymbol{superName, UnknownSymbol::Type}); return ControlFlow::None; } - // We don't have generic classes, so this assertion _should_ never be hit. + // We don't have generic extern types, so this assertion _should_ never be hit. LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); superTy = follow(lookupType->type); - if (!get(follow(*superTy))) + if (!get(follow(*superTy))) { reportError( - declaredClass->location, - GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass->name.value)} + declaredExternType->location, + GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value)} ); return ControlFlow::None; } } - Name className(declaredClass->name.value); + Name className(declaredExternType->name.value); - TypeId classTy = arena->addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredClass->location)); - ClassType* ctv = getMutable(classTy); + TypeId externTy = arena->addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, module->name, declaredExternType->location)); + ExternType* etv = getMutable(externTy); TypeId metaTy = arena->addType(TableType{TableState::Sealed, scope->level, scope.get()}); TableType* metatable = getMutable(metaTy); - ctv->metatable = metaTy; + etv->metatable = metaTy; TypeId classBindTy = bindingIt->second.type; - emplaceType(asMutable(classBindTy), classTy); + emplaceType(asMutable(classBindTy), externTy); - if (declaredClass->indexer) + if (declaredExternType->indexer) { RecursionCounter counter{&recursionCount}; if (recursionCount >= FInt::LuauCheckRecursionLimit) { - reportCodeTooComplex(declaredClass->indexer->location); + reportCodeTooComplex(declaredExternType->indexer->location); } else { - ctv->indexer = TableIndexer{ - resolveType(scope, declaredClass->indexer->indexType, /* inTypeArguments */ false), - resolveType(scope, declaredClass->indexer->resultType, /* inTypeArguments */ false), + etv->indexer = TableIndexer{ + resolveType(scope, declaredExternType->indexer->indexType, /* inTypeArguments */ false), + resolveType(scope, declaredExternType->indexer->resultType, /* inTypeArguments */ false), }; } } - for (const AstDeclaredClassProp& prop : declaredClass->props) + for (const AstDeclaredExternTypeProperty& prop : declaredExternType->props) { Name propName(prop.name.value); TypeId propTy = resolveType(scope, prop.ty, /* inTypeArguments */ false); @@ -1930,7 +1961,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas if (FunctionType* ftv = getMutable(propTy)) { ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); - ftv->argTypes = addTypePack({classTy}, ftv->argTypes); + ftv->argTypes = addTypePack({externTy}, ftv->argTypes); ftv->hasSelf = true; @@ -1945,7 +1976,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas } } - TableType::Props& props = assignToMetatable ? metatable->props : ctv->props; + TableType::Props& props = assignToMetatable ? metatable->props : etv->props; if (props.count(propName) == 0) { @@ -1976,7 +2007,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareClas } else { - reportError(declaredClass->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); + reportError(declaredExternType->location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); } } } @@ -2008,7 +2039,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareFunc funScope = childScope(global, scope); TypePackId paramPack = resolveTypePack(funScope, global->params, /* inTypeArguments */ false); - TypePackId retPack = resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false); + TypePackId retPack = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, global->retTypes, /* inTypeArguments */ false) + : resolveTypePack(funScope, global->retTypes_DEPRECATED, /* inTypeArguments */ false); FunctionDefinition defn; @@ -2658,8 +2690,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun GeneralizationConstraint{ generalizedTy, sig.signature, - (FFlag::LuauNonReentrantGeneralization2 || FFlag::LuauTrackInteriorFreeTypesOnScope) ? std::vector{} - : std::move(DEPRECATED_interiorTypes.back()) + std::vector{}, } ); @@ -2673,9 +2704,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun } else { - if (FFlag::LuauTrackInteriorFreeTypesOnScope) - sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); - + sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); getMutable(generalizedTy)->setOwner(gc); DEPRECATED_interiorTypes.pop_back(); } @@ -2963,7 +2992,7 @@ std::tuple ConstraintGenerator::checkBinary( else if (typeguard->type == "userdata") { // For now, we don't really care about being accurate with userdata if the typeguard was using typeof. - discriminantTy = builtinTypes->classType; + discriminantTy = builtinTypes->externType; } else if (!typeguard->isTypeof && typeguard->type == "vector") discriminantTy = builtinTypes->neverType; // TODO: figure out a way to deal with this quirky type @@ -2973,8 +3002,8 @@ std::tuple ConstraintGenerator::checkBinary( { TypeId ty = follow(typeFun->type); - // We're only interested in the root class of any classes. - if (auto ctv = get(ty); ctv && (ctv->parent == builtinTypes->classType || hasTag(ty, kTypeofRootTag))) + // We're only interested in the root type of any extern type. + if (auto etv = get(ty); etv && (etv->parent == builtinTypes->externType || hasTag(ty, kTypeofRootTag))) discriminantTy = ty; } @@ -3381,14 +3410,29 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu bodyScope->varargPack = std::nullopt; } + if (FFlag::DebugLuauGreedyGeneralization) + { + genericTypes = argTypes; + } + LUAU_ASSERT(nullptr != varargPack); // If there is both an annotation and an expected type, the annotation wins. // Type checking will sort out any discrepancies later. - if (fn->returnAnnotation) + if (FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation) { TypePackId annotatedRetType = - resolveTypePack(signatureScope, *fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true); + resolveTypePack(signatureScope, fn->returnAnnotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true); + // We bind the annotated type directly here so that, when we need to + // generate constraints for return types, we have a guarantee that we + // know the annotated return type already, if one was provided. + LUAU_ASSERT(get(returnType)); + emplaceTypePack(asMutable(returnType), annotatedRetType); + } + else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && fn->returnAnnotation_DEPRECATED) + { + TypePackId annotatedRetType = + resolveTypePack(signatureScope, *fn->returnAnnotation_DEPRECATED, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true); // We bind the annotated type directly here so that, when we need to // generate constraints for return types, we have a guarantee that we // know the annotated return type already, if one was provided. @@ -3639,8 +3683,16 @@ TypeId ConstraintGenerator::resolveFunctionType( AstTypePackExplicit tempArgTypes{Location{}, fn->argTypes}; TypePackId argTypes = resolveTypePack_(signatureScope, &tempArgTypes, inTypeArguments, replaceErrorWithFresh); - AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes}; - TypePackId returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh); + TypePackId returnTypes; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + returnTypes = resolveTypePack_(signatureScope, fn->returnTypes, inTypeArguments, replaceErrorWithFresh); + } + else + { + AstTypePackExplicit tempRetTypes{Location{}, fn->returnTypes_DEPRECATED}; + returnTypes = resolveTypePack_(signatureScope, &tempRetTypes, inTypeArguments, replaceErrorWithFresh); + } // TODO: FunctionType needs a pointer to the scope so that we know // how to quantify/instantiate it. @@ -3725,12 +3777,8 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo } else { - if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) - { - if (unionAnnotation->types.size == 1) - return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); - } - + if (unionAnnotation->types.size == 1) + return resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); std::vector parts; for (AstType* part : unionAnnotation->types) { @@ -3759,12 +3807,8 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo } else { - if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) - { - if (intersectionAnnotation->types.size == 1) - return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); - } - + if (intersectionAnnotation->types.size == 1) + return resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); std::vector parts; for (AstType* part : intersectionAnnotation->types) { @@ -4294,15 +4338,4 @@ TypeId ConstraintGenerator::simplifyUnion(const ScopePtr& scope, Location locati return ::Luau::simplifyUnion(builtinTypes, arena, left, right).result; } -std::vector> borrowConstraints(const std::vector& constraints) -{ - std::vector> result; - result.reserve(constraints.size()); - - for (const auto& c : constraints) - result.emplace_back(c.get()); - - return result; -} - } // namespace Luau diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index c9e52953..2259af45 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -11,7 +11,6 @@ #include "Luau/Location.h" #include "Luau/ModuleResolver.h" #include "Luau/OverloadResolution.h" -#include "Luau/Quantify.h" #include "Luau/RecursionCounter.h" #include "Luau/Simplify.h" #include "Luau/TableLiteralInference.h" @@ -33,20 +32,20 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverIncludeDependencies) LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) -LUAU_FASTFLAGVARIABLE(LuauTrackInteriorFreeTablesOnScope) LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(DebugLuauGreedyGeneralization) -LUAU_FASTFLAG(LuauSearchForRefineableType) LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) +LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) namespace Luau { +static void dump(ConstraintSolver* cs, ToStringOptions& opts); + size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const { size_t result = 0; @@ -276,26 +275,6 @@ size_t HashInstantiationSignature::operator()(const InstantiationSignature& sign return hash; } -void dump(ConstraintSolver* cs, ToStringOptions& opts) -{ - printf("constraints:\n"); - for (NotNull c : cs->unsolvedConstraints) - { - auto it = cs->blockedConstraints.find(c); - int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second); - printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str()); - - if (FFlag::DebugLuauLogSolverIncludeDependencies) - { - for (NotNull dep : c->dependencies) - { - if (std::find(cs->unsolvedConstraints.begin(), cs->unsolvedConstraints.end(), dep) != cs->unsolvedConstraints.end()) - printf("\t\t|\t%s\n", toString(*dep, opts).c_str()); - } - } - } -} - struct InstantiationQueuer : TypeOnceVisitor { ConstraintSolver* solver; @@ -321,12 +300,44 @@ struct InstantiationQueuer : TypeOnceVisitor return true; } - bool visit(TypeId ty, const ClassType& ctv) override + bool visit(TypeId ty, const ExternType& etv) override { return false; } }; +ConstraintSolver::ConstraintSolver( + NotNull normalizer, + NotNull simplifier, + NotNull typeFunctionRuntime, + ModuleName moduleName, + NotNull moduleResolver, + std::vector requireCycles, + DcrLogger* logger, + NotNull dfg, + TypeCheckLimits limits, + ConstraintSet constraintSet_ +) + : arena(normalizer->arena) + , builtinTypes(normalizer->builtinTypes) + , normalizer(normalizer) + , simplifier(simplifier) + , typeFunctionRuntime(typeFunctionRuntime) + , constraintSet(std::move(constraintSet_)) + , constraints(borrowConstraints(constraintSet.constraints)) + , scopeToFunction(&constraintSet.scopeToFunction) + , rootScope(constraintSet.rootScope) + , currentModuleName(std::move(moduleName)) + , dfg(dfg) + , moduleResolver(moduleResolver) + , requireCycles(std::move(requireCycles)) + , logger(logger) + , limits(std::move(limits)) + , opts{/*exhaustive*/ true} +{ + initFreeTypeTracking(); +} + ConstraintSolver::ConstraintSolver( NotNull normalizer, NotNull simplifier, @@ -346,6 +357,7 @@ ConstraintSolver::ConstraintSolver( , normalizer(normalizer) , simplifier(simplifier) , typeFunctionRuntime(typeFunctionRuntime) + , constraintSet{rootScope} , constraints(std::move(constraints)) , scopeToFunction(scopeToFunction) , rootScope(rootScope) @@ -355,33 +367,9 @@ ConstraintSolver::ConstraintSolver( , requireCycles(std::move(requireCycles)) , logger(logger) , limits(std::move(limits)) + , opts{/*exhaustive*/ true} { - opts.exhaustive = true; - - for (NotNull c : this->constraints) - { - unsolvedConstraints.emplace_back(c); - - auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes(); - for (auto ty : maybeMutatedTypesPerConstraint) - { - auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); - refCount += 1; - - if (FFlag::DebugLuauGreedyGeneralization) - { - auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, DenseHashSet{nullptr}); - it->second.insert(c.get()); - } - } - maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); - - - for (NotNull dep : c->dependencies) - { - block(dep, c); - } - } + initFreeTypeTracking(); } void ConstraintSolver::randomize(unsigned seed) @@ -426,6 +414,18 @@ void ConstraintSolver::run() logger->captureInitialSolverState(rootScope, unsolvedConstraints); } + // Free types that have no constraints at all can be generalized right away. + if (FFlag::DebugLuauGreedyGeneralization) + { + for (TypeId ty : constraintSet.freeTypes) + { + if (auto it = mutatedFreeTypeToConstraint.find(ty); it == mutatedFreeTypeToConstraint.end() || it->second.empty()) + generalizeOneType(ty); + } + } + + constraintSet.freeTypes.clear(); + auto runSolverPass = [&](bool force) { bool progress = false; @@ -655,12 +655,40 @@ struct TypeSearcher : TypeVisitor // } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } }; +void ConstraintSolver::initFreeTypeTracking() +{ + for (NotNull c : this->constraints) + { + unsolvedConstraints.emplace_back(c); + + auto maybeMutatedTypesPerConstraint = c->getMaybeMutatedFreeTypes(); + for (auto ty : maybeMutatedTypesPerConstraint) + { + auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); + refCount += 1; + + if (FFlag::DebugLuauGreedyGeneralization) + { + auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr); + it->second.insert(c.get()); + } + } + maybeMutatedFreeTypes.emplace(c, maybeMutatedTypesPerConstraint); + + + for (NotNull dep : c->dependencies) + { + block(dep, c); + } + } +} + void ConstraintSolver::generalizeOneType(TypeId ty) { ty = follow(ty); @@ -858,62 +886,53 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullerrorRecoveryType()); } - if (FFlag::LuauTrackInteriorFreeTypesOnScope) + // We check if this member is initialized and then access it, but + // clang-tidy doesn't understand this is safe. + if (constraint->scope->interiorFreeTypes) { - // We check if this member is initialized and then access it, but - // clang-tidy doesn't understand this is safe. - if (constraint->scope->interiorFreeTypes) + for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) { - for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) + if (FFlag::LuauNonReentrantGeneralization2) { - if (FFlag::LuauNonReentrantGeneralization2) + ty = follow(ty); + if (auto freeTy = get(ty)) { - ty = follow(ty); - if (auto freeTy = get(ty)) - { - GeneralizationParams params; - params.foundOutsideFunctions = true; - params.useCount = 1; - params.polarity = freeTy->polarity; + GeneralizationParams params; + params.foundOutsideFunctions = true; + params.useCount = 1; + params.polarity = freeTy->polarity; - GeneralizationResult res = generalizeType(arena, builtinTypes, constraint->scope, ty, params); - if (res.resourceLimitsExceeded) - reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this. - } - else if (get(ty)) - sealTable(constraint->scope, ty); + GeneralizationResult res = generalizeType(arena, builtinTypes, constraint->scope, ty, params); + if (res.resourceLimitsExceeded) + reportError(CodeTooComplex{}, constraint->scope->location); // FIXME: We don't have a very good location for this. } - else - generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty); + else if (get(ty)) + sealTable(constraint->scope, ty); } + else + generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty); } + } - if (FFlag::LuauNonReentrantGeneralization2) + if (FFlag::LuauNonReentrantGeneralization2) + { + if (constraint->scope->interiorFreeTypePacks) { - if (constraint->scope->interiorFreeTypePacks) + for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access) { - for (TypePackId tp : *constraint->scope->interiorFreeTypePacks) // NOLINT(bugprone-unchecked-optional-access) + tp = follow(tp); + if (auto freeTp = get(tp)) { - tp = follow(tp); - if (auto freeTp = get(tp)) - { - GeneralizationParams params; - params.foundOutsideFunctions = true; - params.useCount = 1; - params.polarity = freeTp->polarity; - LUAU_ASSERT(isKnown(params.polarity)); - generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params); - } + GeneralizationParams params; + params.foundOutsideFunctions = true; + params.useCount = 1; + params.polarity = freeTp->polarity; + LUAU_ASSERT(isKnown(params.polarity)); + generalizeTypePack(arena, builtinTypes, constraint->scope, tp, params); } } } } - else - { - for (TypeId ty : c.interiorTypes) - generalize(NotNull{arena}, builtinTypes, constraint->scope, generalizedTypes, ty); - } - return true; } @@ -989,16 +1008,12 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNullscope, Polarity::Mixed); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); - if (FFlag::LuauTrackInteriorFreeTypesOnScope) - { - trackInteriorFreeType(constraint->scope, keyTy); - trackInteriorFreeType(constraint->scope, valueTy); - } + trackInteriorFreeType(constraint->scope, keyTy); + trackInteriorFreeType(constraint->scope, valueTy); TypeId tableTy = arena->addType(TableType{TableType::Props{}, TableIndexer{keyTy, valueTy}, TypeLevel{}, constraint->scope, TableState::Free}); - if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope) - trackInteriorFreeType(constraint->scope, tableTy); + trackInteriorFreeType(constraint->scope, tableTy); unify(constraint, nextTy, tableTy); @@ -1322,29 +1337,14 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull constra if (!ty) continue; - if (FFlag::LuauSearchForRefineableType) - { - if (isBlocked(*ty)) - // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. - emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); - - // We also need to unconditionally unblock these types, otherwise - // you end up with funky looking "Blocked on *no-refine*." - unblock(*ty, constraint->location); - } - else - { - - // If the discriminant type has been transmuted, we need to unblock them. - if (!isBlocked(*ty)) - { - unblock(*ty, constraint->location); - continue; - } - + if (isBlocked(*ty)) // We bind any unused discriminants to the `*no-refine*` type indicating that it can be safely ignored. emplaceType(asMutable(follow(*ty)), builtinTypes->noRefineType); - } + + // We also need to unconditionally unblock these types, otherwise + // you end up with funky looking "Blocked on *no-refine*." + unblock(*ty, constraint->location); + } } @@ -1457,9 +1457,9 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullmagic) + if (ftv->magic && c.callSite) { - usedMagic = ftv->magic->infer(MagicFunctionCallContext{NotNull{this}, constraint, c.callSite, c.argsPack, result}); + usedMagic = ftv->magic->infer(MagicFunctionCallContext{NotNull{this}, constraint, NotNull{c.callSite}, c.argsPack, result}); ftv->magic->refine(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes}); } } @@ -1534,7 +1534,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullscope, inferredTy); unblock(c.result, constraint->location); @@ -1903,7 +1903,7 @@ bool ConstraintSolver::tryDispatchHasIndexer( } else if (auto mt = get(subjectType)) return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen); - else if (auto ct = get(subjectType)) + else if (auto ct = get(subjectType)) { if (auto indexer = ct->indexer) { @@ -2065,9 +2065,9 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull(lhsType)) + if (auto lhsExternType = get(lhsType)) { - const Property* prop = lookupClassProp(lhsClass, propName); + const Property* prop = lookupExternTypeProp(lhsExternType, propName); if (!prop || !prop->writeTy.has_value()) { bind(constraint, c.propType, builtinTypes->anyType); @@ -2088,8 +2088,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNulladdType(TableType{TableState::Free, TypeLevel{}, constraint->scope}); - if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope) - trackInteriorFreeType(constraint->scope, newUpperBound); + trackInteriorFreeType(constraint->scope, newUpperBound); TableType* upperTable = getMutable(newUpperBound); LUAU_ASSERT(upperTable); @@ -2252,20 +2251,20 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull(lhsType)) + if (auto lhsExternType = get(lhsType)) { while (true) { - if (lhsClass->indexer) + if (lhsExternType->indexer) { - unify(constraint, indexType, lhsClass->indexer->indexType); - unify(constraint, rhsType, lhsClass->indexer->indexResultType); - bind(constraint, c.propType, arena->addType(UnionType{{lhsClass->indexer->indexResultType, builtinTypes->nilType}})); + unify(constraint, indexType, lhsExternType->indexer->indexType); + unify(constraint, rhsType, lhsExternType->indexer->indexResultType); + bind(constraint, c.propType, arena->addType(UnionType{{lhsExternType->indexer->indexResultType, builtinTypes->nilType}})); return true; } - if (lhsClass->parent) - lhsClass = get(lhsClass->parent); + if (lhsExternType->parent) + lhsExternType = get(lhsExternType->parent); else break; } @@ -2292,7 +2291,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull(follow(t))) + else if (auto cls = get(follow(t))) { while (true) { @@ -2304,7 +2303,7 @@ bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNullparent) - cls = get(cls->parent); + cls = get(cls->parent); else break; } @@ -2355,8 +2354,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNullscope, Polarity::Positive); // FIXME? Is this the right polarity? - if (FFlag::LuauTrackInteriorFreeTypesOnScope) - trackInteriorFreeType(constraint->scope, f); + trackInteriorFreeType(constraint->scope, f); shiftReferences(resultTy, f); emplaceType(asMutable(resultTy), f); } @@ -2499,11 +2497,8 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl { TypeId keyTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); TypeId valueTy = freshType(arena, builtinTypes, constraint->scope, Polarity::Mixed); - if (FFlag::LuauTrackInteriorFreeTypesOnScope) - { - trackInteriorFreeType(constraint->scope, keyTy); - trackInteriorFreeType(constraint->scope, valueTy); - } + trackInteriorFreeType(constraint->scope, keyTy); + trackInteriorFreeType(constraint->scope, valueTy); TypeId tableTy = arena->addType(TableType{TableState::Sealed, {}, constraint->scope}); getMutable(tableTy)->indexer = TableIndexer{keyTy, valueTy}; @@ -2653,33 +2648,53 @@ bool ConstraintSolver::tryDispatchIterableFunction(TypeId nextTy, TypeId tableTy const FunctionType* nextFn = get(nextTy); // If this does not hold, we should've never called `tryDispatchIterableFunction` in the first place. LUAU_ASSERT(nextFn); - const TypePackId nextRetPack = nextFn->retTypes; // the type of the `nextAstFragment` is the `nextTy`. (*c.astForInNextTypes)[c.nextAstFragment] = nextTy; - auto it = begin(nextRetPack); - std::vector modifiedNextRetHead; - - // The first value is never nil in the context of the loop, even if it's nil - // in the next function's return type, because the loop will not advance if - // it's nil. - if (it != end(nextRetPack)) + if (FFlag::LuauAddCallConstraintForIterableFunctions) { - TypeId firstRet = *it; - TypeId modifiedFirstRet = stripNil(builtinTypes, *arena, firstRet); - modifiedNextRetHead.push_back(modifiedFirstRet); - ++it; + // Construct a FunctionCallConstraint, to help us learn about the type of the loop variables being assigned to in this iterable + TypePackId tableTyPack = arena->addTypePack({tableTy}); + + TypePackId variablesPack = arena->addTypePack(BlockedTypePack{}); + + auto callConstraint = pushConstraint(constraint->scope, constraint->location, FunctionCallConstraint{nextTy, tableTyPack, variablesPack}); + + getMutable(variablesPack)->owner = callConstraint.get(); + + auto unpackConstraint = unpackAndAssign(c.variables, variablesPack, constraint); + + inheritBlocks(constraint, callConstraint); + + inheritBlocks(unpackConstraint, callConstraint); } + else + { + const TypePackId nextRetPack = nextFn->retTypes; + TypePackIterator it = begin(nextRetPack); + std::vector modifiedNextRetHead; - for (; it != end(nextRetPack); ++it) - modifiedNextRetHead.push_back(*it); + // The first value is never nil in the context of the loop, even if it's nil + // in the next function's return type, because the loop will not advance if + // it's nil. + if (it != end(nextRetPack)) + { + TypeId firstRet = *it; + TypeId modifiedFirstRet = stripNil(builtinTypes, *arena, firstRet); + modifiedNextRetHead.push_back(modifiedFirstRet); + ++it; + } - TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail()); + for (; it != end(nextRetPack); ++it) + modifiedNextRetHead.push_back(*it); - auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint); + TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail()); - inheritBlocks(constraint, unpackConstraint); + auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint); + + inheritBlocks(constraint, unpackConstraint); + } return true; } @@ -2760,8 +2775,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( if (ttv->state == TableState::Free) { TypeId result = freshType(arena, builtinTypes, ttv->scope, Polarity::Mixed); - if (FFlag::LuauTrackInteriorFreeTypesOnScope) - trackInteriorFreeType(ttv->scope, result); + trackInteriorFreeType(ttv->scope, result); switch (context) { case ValueContext::RValue: @@ -2835,9 +2849,9 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( else if (get(mtt)) return lookupTableProp(constraint, mtt, propName, context, inConditional, suppressSimplification, seen); } - else if (auto ct = get(subjectType)) + else if (auto ct = get(subjectType)) { - if (auto p = lookupClassProp(ct, propName)) + if (auto p = lookupExternTypeProp(ct, propName)) return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy}; if (ct->indexer) { @@ -2862,21 +2876,18 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( if (get(upperBound) || get(upperBound)) return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen); - // TODO: The upper bound could be an intersection that contains suitable tables or classes. + // 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}); - if (FFlag::LuauTrackInteriorFreeTypesOnScope && FFlag::LuauTrackInteriorFreeTablesOnScope) - trackInteriorFreeType(constraint->scope, newUpperBound); + trackInteriorFreeType(constraint->scope, newUpperBound); TableType* tt = getMutable(newUpperBound); LUAU_ASSERT(tt); TypeId propType = freshType(arena, builtinTypes, scope, Polarity::Mixed); - - if (FFlag::LuauTrackInteriorFreeTypesOnScope) - trackInteriorFreeType(scope, propType); + trackInteriorFreeType(scope, propType); switch (context) { @@ -3095,7 +3106,7 @@ struct Blocker : TypeOnceVisitor return false; } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } @@ -3471,4 +3482,35 @@ LUAU_NOINLINE void ConstraintSolver::throwUserCancelError() const template bool ConstraintSolver::unify(NotNull constraint, TypeId subTy, TypeId superTy); template bool ConstraintSolver::unify(NotNull constraint, TypePackId subTy, TypePackId superTy); +std::vector> borrowConstraints(const std::vector& constraints) +{ + std::vector> result; + result.reserve(constraints.size()); + + for (const auto& c : constraints) + result.emplace_back(c.get()); + + return result; +} + +void dump(ConstraintSolver* cs, ToStringOptions& opts) +{ + printf("constraints:\n"); + for (NotNull c : cs->unsolvedConstraints) + { + auto it = cs->blockedConstraints.find(c); + int blockCount = it == cs->blockedConstraints.end() ? 0 : int(it->second); + printf("\t%d\t%s\n", blockCount, toString(*c, opts).c_str()); + + if (FFlag::DebugLuauLogSolverIncludeDependencies) + { + for (NotNull dep : c->dependencies) + { + if (std::find(cs->unsolvedConstraints.begin(), cs->unsolvedConstraints.end(), dep) != cs->unsolvedConstraints.end()) + printf("\t\t|\t%s\n", toString(*dep, opts).c_str()); + } + } + } +} + } // namespace Luau diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp index 0d75dcc2..467b341f 100644 --- a/Analysis/src/DataFlowGraph.cpp +++ b/Analysis/src/DataFlowGraph.cpp @@ -16,6 +16,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauPreprocessTypestatedArgument) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackTrueReset) LUAU_FASTFLAGVARIABLE(LuauDfgScopeStackNotNull) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) + namespace Luau { @@ -470,7 +472,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStat* s) return visit(d); else if (auto d = s->as()) return visit(d); - else if (auto d = s->as()) + else if (auto d = s->as()) return visit(d); else if (auto error = s->as()) return visit(error); @@ -808,12 +810,15 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareFunction* d) visitGenerics(d->generics); visitGenericPacks(d->genericPacks); visitTypeList(d->params); - visitTypeList(d->retTypes); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + visitTypePack(d->retTypes); + else + visitTypeList(d->retTypes_DEPRECATED); return ControlFlow::None; } -ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d) +ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareExternType* d) { // This declaration does not "introduce" any bindings in value namespace, // so there's no symbolic value to begin with. We'll traverse the properties @@ -821,7 +826,7 @@ ControlFlow DataFlowGraphBuilder::visit(AstStatDeclareClass* d) DfgScope* unreachable = makeChildScope(); PushScope ps{scopeStack, unreachable}; - for (AstDeclaredClassProp prop : d->props) + for (AstDeclaredExternTypeProperty prop : d->props) visitType(prop.ty); return ControlFlow::None; @@ -1032,8 +1037,16 @@ DataFlowResult DataFlowGraphBuilder::visitExpr(AstExprFunction* f) if (f->varargAnnotation) visitTypePack(f->varargAnnotation); - if (f->returnAnnotation) - visitTypeList(*f->returnAnnotation); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + if (f->returnAnnotation) + visitTypePack(f->returnAnnotation); + } + else + { + if (f->returnAnnotation_DEPRECATED) + visitTypeList(*f->returnAnnotation_DEPRECATED); + } // TODO: function body can be re-entrant, as in mutations that occurs at the end of the function can also be // visible to the beginning of the function, so statically speaking, the body of the function has an exit point @@ -1275,7 +1288,10 @@ void DataFlowGraphBuilder::visitType(AstTypeFunction* f) visitGenerics(f->generics); visitGenericPacks(f->genericPacks); visitTypeList(f->argTypes); - visitTypeList(f->returnTypes); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + visitTypePack(f->returnTypes); + else + visitTypeList(f->returnTypes_DEPRECATED); } void DataFlowGraphBuilder::visitType(AstTypeTypeof* t) diff --git a/Analysis/src/Differ.cpp b/Analysis/src/Differ.cpp index e6222067..dd026f47 100644 --- a/Analysis/src/Differ.cpp +++ b/Analysis/src/Differ.cpp @@ -277,7 +277,7 @@ static DifferResult diffSingleton(DifferEnvironment& env, TypeId left, TypeId ri static DifferResult diffFunction(DifferEnvironment& env, TypeId left, TypeId right); static DifferResult diffGeneric(DifferEnvironment& env, TypeId left, TypeId right); static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId right); -static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right); +static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right); struct FindSeteqCounterexampleResult { // nullopt if no counterexample found @@ -481,14 +481,14 @@ static DifferResult diffNegation(DifferEnvironment& env, TypeId left, TypeId rig return differResult; } -static DifferResult diffClass(DifferEnvironment& env, TypeId left, TypeId right) +static DifferResult diffExternType(DifferEnvironment& env, TypeId left, TypeId right) { - const ClassType* leftClass = get(left); - const ClassType* rightClass = get(right); - LUAU_ASSERT(leftClass); - LUAU_ASSERT(rightClass); + const ExternType* leftExternType = get(left); + const ExternType* rightExternType = get(right); + LUAU_ASSERT(leftExternType); + LUAU_ASSERT(rightExternType); - if (leftClass == rightClass) + if (leftExternType == rightExternType) { return DifferResult{}; } @@ -651,9 +651,9 @@ static DifferResult diffUsingEnv(DifferEnvironment& env, TypeId left, TypeId rig { return diffNegation(env, left, right); } - else if (auto lc = get(left)) + else if (auto lc = get(left)) { - return diffClass(env, left, right); + return diffExternType(env, left, right); } throw InternalCompilerError{"Unimplemented Simple TypeId variant for diffing"}; @@ -960,7 +960,7 @@ bool isSimple(TypeId ty) { ty = follow(ty); // TODO: think about GenericType, etc. - return get(ty) || get(ty) || get(ty) || get(ty) || get(ty) || + return get(ty) || get(ty) || get(ty) || get(ty) || get(ty) || get(ty) || get(ty); } diff --git a/Analysis/src/EmbeddedBuiltinDefinitions.cpp b/Analysis/src/EmbeddedBuiltinDefinitions.cpp index 1f8ec09a..778fb567 100644 --- a/Analysis/src/EmbeddedBuiltinDefinitions.cpp +++ b/Analysis/src/EmbeddedBuiltinDefinitions.cpp @@ -1,6 +1,8 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BuiltinDefinitions.h" +LUAU_FASTFLAG(LuauDeclareExternType) + namespace Luau { @@ -259,7 +261,37 @@ declare buffer: { )BUILTIN_SRC"; -static const std::string kBuiltinDefinitionVectorSrc = R"BUILTIN_SRC( +static const std::string kBuiltinDefinitionVectorSrc = (FFlag::LuauDeclareExternType) + ? R"BUILTIN_SRC( + +-- While vector would have been better represented as a built-in primitive type, type solver extern type handling covers most of the properties +declare extern type vector with + x: number + y: number + z: number +end + +declare vector: { + create: @checked (x: number, y: number, z: number?) -> vector, + magnitude: @checked (vec: vector) -> number, + normalize: @checked (vec: vector) -> vector, + cross: @checked (vec1: vector, vec2: vector) -> vector, + dot: @checked (vec1: vector, vec2: vector) -> number, + angle: @checked (vec1: vector, vec2: vector, axis: vector?) -> number, + floor: @checked (vec: vector) -> vector, + ceil: @checked (vec: vector) -> vector, + abs: @checked (vec: vector) -> vector, + sign: @checked (vec: vector) -> vector, + clamp: @checked (vec: vector, min: vector, max: vector) -> vector, + max: @checked (vector, ...vector) -> vector, + min: @checked (vector, ...vector) -> vector, + + zero: vector, + one: vector, +} + +)BUILTIN_SRC" + : R"BUILTIN_SRC( -- While vector would have been better represented as a built-in primitive type, type solver class handling covers most of the properties declare class vector diff --git a/Analysis/src/EqSatSimplification.cpp b/Analysis/src/EqSatSimplification.cpp index edcc42fb..d26697a7 100644 --- a/Analysis/src/EqSatSimplification.cpp +++ b/Analysis/src/EqSatSimplification.cpp @@ -330,9 +330,9 @@ Id toId( return egraph.add(TOpaque{ty}); else if (get(ty)) return egraph.add(TFunction{ty}); - else if (ty == builtinTypes->classType) + else if (ty == builtinTypes->externType) return egraph.add(TTopClass{}); - else if (get(ty)) + else if (get(ty)) return egraph.add(TClass{ty}); else if (get(ty)) return egraph.add(TAny{}); @@ -752,7 +752,7 @@ TypeId fromId( else if (node.get()) return builtinTypes->tableType; else if (node.get()) - return builtinTypes->classType; + return builtinTypes->externType; else if (node.get()) return builtinTypes->bufferType; else if (auto opaque = node.get()) @@ -1007,7 +1007,7 @@ static std::string getNodeName(const StringCache& strings, const EType& node) return "\xe2\x88\xa9"; else if (auto cls = node.get()) { - const ClassType* ct = get(cls->value()); + const ExternType* ct = get(cls->value()); LUAU_ASSERT(ct); return ct->name; } @@ -1177,12 +1177,12 @@ enum SubclassRelationship static SubclassRelationship relateClasses(const TClass* leftClass, const TClass* rightClass) { - const ClassType* leftClassType = Luau::get(leftClass->value()); - const ClassType* rightClassType = Luau::get(rightClass->value()); + const ExternType* leftExternType = Luau::get(leftClass->value()); + const ExternType* rightExternType = Luau::get(rightClass->value()); - if (isSubclass(leftClassType, rightClassType)) + if (isSubclass(leftExternType, rightExternType)) return RightSuper; - else if (isSubclass(rightClassType, leftClassType)) + else if (isSubclass(rightExternType, leftExternType)) return LeftSuper; else return Unrelated; diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index a4304cf3..1d1c924a 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -203,7 +203,7 @@ struct ErrorConverter TypeId t = follow(e.table); if (get(t)) return "Key '" + e.key + "' not found in table '" + Luau::toString(t) + "'"; - else if (get(t)) + else if (get(t)) return "Key '" + e.key + "' not found in class '" + Luau::toString(t) + "'"; else return "Type '" + Luau::toString(e.table) + "' does not have key '" + e.key + "'"; @@ -371,7 +371,7 @@ struct ErrorConverter std::string s = "Key '" + e.key + "' not found in "; TypeId t = follow(e.table); - if (get(t)) + if (get(t)) s += "class"; else s += "table"; @@ -402,8 +402,8 @@ struct ErrorConverter std::optional metatable; if (const MetatableType* mtType = get(type)) metatable = mtType->metatable; - else if (const ClassType* classType = get(type)) - metatable = classType->metatable; + else if (const ExternType* externType = get(type)) + metatable = externType->metatable; if (!metatable) return std::nullopt; @@ -611,7 +611,7 @@ struct ErrorConverter return ss; } - std::string operator()(const DynamicPropertyLookupOnClassesUnsafe& e) const + std::string operator()(const DynamicPropertyLookupOnExternTypesUnsafe& e) const { return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime"; } @@ -1149,7 +1149,7 @@ bool TypePackMismatch::operator==(const TypePackMismatch& rhs) const return *wantedTp == *rhs.wantedTp && *givenTp == *rhs.givenTp; } -bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const +bool DynamicPropertyLookupOnExternTypesUnsafe::operator==(const DynamicPropertyLookupOnExternTypesUnsafe& rhs) const { return ty == rhs.ty; } @@ -1391,7 +1391,7 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState) e.wantedTp = clone(e.wantedTp); e.givenTp = clone(e.givenTp); } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) e.ty = clone(e.ty); else if constexpr (std::is_same_v) e.ty = clone(e.ty); diff --git a/Analysis/src/FileResolver.cpp b/Analysis/src/FileResolver.cpp index acad7b34..34befcba 100644 --- a/Analysis/src/FileResolver.cpp +++ b/Analysis/src/FileResolver.cpp @@ -10,10 +10,6 @@ #include #include -LUAU_FASTFLAGVARIABLE(LuauExposeRequireByStringAutocomplete) -LUAU_FASTFLAGVARIABLE(LuauEscapeCharactersInRequireSuggestions) -LUAU_FASTFLAGVARIABLE(LuauHideImpossibleRequireSuggestions) - namespace Luau { @@ -22,12 +18,9 @@ static std::optional processRequireSuggestions(std::optional if (!suggestions) return suggestions; - if (FFlag::LuauEscapeCharactersInRequireSuggestions) + for (RequireSuggestion& suggestion : *suggestions) { - for (RequireSuggestion& suggestion : *suggestions) - { - suggestion.fullPath = escape(suggestion.fullPath); - } + suggestion.fullPath = escape(suggestion.fullPath); } return suggestions; @@ -112,13 +105,11 @@ static RequireSuggestions makeSuggestionsFromNode(std::unique_ptr n continue; std::string pathComponent = child->getPathComponent(); - if (FFlag::LuauHideImpossibleRequireSuggestions) - { - // If path component contains a slash, it cannot be required by string. - // There's no point suggesting it. - if (pathComponent.find('/') != std::string::npos) - continue; - } + + // If path component contains a slash, it cannot be required by string. + // There's no point suggesting it. + if (pathComponent.find('/') != std::string::npos) + continue; RequireSuggestion suggestion; suggestion.label = isPartialPath || path.back() == '/' ? child->getLabel() : "/" + child->getLabel(); @@ -163,9 +154,6 @@ std::optional RequireSuggester::getRequireSuggestions(const std::optional FileResolver::getRequireSuggestions(const ModuleName& requirer, const std::optional& path) const { - if (!FFlag::LuauExposeRequireByStringAutocomplete) - return std::nullopt; - return requireSuggester ? requireSuggester->getRequireSuggestions(requirer, path) : std::nullopt; } diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 463d3c2d..07be1405 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -27,7 +27,6 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit); LUAU_FASTINT(LuauTypeInferIterationLimit); LUAU_FASTINT(LuauTarjanChildLimit) -LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAGVARIABLE(LuauMixedModeDefFinderTraversesTypeOf) LUAU_FASTFLAGVARIABLE(LuauCloneIncrementalModule) @@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauBetterScopeSelection) LUAU_FASTFLAGVARIABLE(LuauBlockDiffFragmentSelection) LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace { @@ -97,7 +97,11 @@ Location getFunctionDeclarationExtents(AstExprFunction* exprFn, AstExpr* exprNam { auto fnBegin = exprFn->location.begin; auto fnEnd = exprFn->location.end; - if (auto returnAnnot = exprFn->returnAnnotation) + if (auto returnAnnot = exprFn->returnAnnotation; FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot) + { + fnEnd = returnAnnot->location.end; + } + else if (auto returnAnnot = exprFn->returnAnnotation_DEPRECATED; !FFlag::LuauStoreReturnTypesAsPackOnAst && returnAnnot) { if (returnAnnot->tailType) fnEnd = returnAnnot->tailType->location.end; @@ -542,6 +546,11 @@ struct UsageFinder : public AstVisitor return true; } + bool visit(AstTypePack* node) override + { + return FFlag::LuauStoreReturnTypesAsPackOnAst; + } + bool visit(AstStatTypeAlias* alias) override { declaredAliases.insert(std::string(alias->name.value)); @@ -1706,7 +1715,6 @@ FragmentAutocompleteResult fragmentAutocomplete( IFragmentAutocompleteReporter* reporter ) { - LUAU_ASSERT(FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete); LUAU_TIMETRACE_SCOPE("Luau::fragmentAutocomplete", "FragmentAutocomplete"); LUAU_TIMETRACE_ARGUMENT("name", moduleName.c_str()); diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 7167fdf0..c31b2d55 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -40,6 +40,7 @@ LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauRethrowKnownExceptions, false) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) @@ -128,9 +129,9 @@ static void generateDocumentationSymbols(TypeId ty, const std::string& rootName) prop.documentationSymbol = rootName + "." + name; } } - else if (ClassType* ctv = getMutable(ty)) + else if (ExternType* etv = getMutable(ty)) { - for (auto& [name, prop] : ctv->props) + for (auto& [name, prop] : etv->props) { prop.documentationSymbol = rootName + "." + name; } @@ -1304,7 +1305,7 @@ ModulePtr check( struct InternalTypeFinder : TypeOnceVisitor { - bool visit(TypeId, const ClassType&) override + bool visit(TypeId, const ExternType&) override { return false; } @@ -1419,30 +1420,59 @@ ModulePtr check( requireCycles }; - cg.visitModuleRoot(sourceModule.root); - result->errors = std::move(cg.errors); + // FIXME: Delete this flag when clipping FFlag::DebugLuauGreedyGeneralization. + // + // This optional<> only exists so that we can run one constructor when the flag + // is set, and another when it is unset. + std::optional cs; - ConstraintSolver cs{ - NotNull{&normalizer}, - NotNull{simplifier.get()}, - NotNull{&typeFunctionRuntime}, - NotNull(cg.rootScope), - borrowConstraints(cg.constraints), - NotNull{&cg.scopeToFunction}, - result->name, - moduleResolver, - requireCycles, - logger.get(), - NotNull{&dfg}, - limits - }; + if (FFlag::DebugLuauGreedyGeneralization) + { + ConstraintSet constraintSet = cg.run(sourceModule.root); + result->errors = std::move(constraintSet.errors); + + cs.emplace( + NotNull{&normalizer}, + NotNull{simplifier.get()}, + NotNull{&typeFunctionRuntime}, + result->name, + moduleResolver, + requireCycles, + logger.get(), + NotNull{&dfg}, + limits, + std::move(constraintSet) + ); + } + else + { + cg.visitModuleRoot(sourceModule.root); + result->errors = std::move(cg.errors); + + cs.emplace( + NotNull{&normalizer}, + NotNull{simplifier.get()}, + NotNull{&typeFunctionRuntime}, + NotNull(cg.rootScope), + borrowConstraints(cg.constraints), + NotNull{&cg.scopeToFunction}, + result->name, + moduleResolver, + requireCycles, + logger.get(), + NotNull{&dfg}, + limits + ); + } + + LUAU_ASSERT(bool(cs)); if (options.randomizeConstraintResolutionSeed) - cs.randomize(*options.randomizeConstraintResolutionSeed); + cs->randomize(*options.randomizeConstraintResolutionSeed); try { - cs.run(); + cs->run(); } catch (const TimeLimitError&) { @@ -1462,12 +1492,12 @@ ModulePtr check( printf("%s\n", output.c_str()); } - for (TypeError& e : cs.errors) + for (TypeError& e : cs->errors) result->errors.emplace_back(std::move(e)); result->scopes = std::move(cg.scopes); result->type = sourceModule.type; - result->upperBoundContributors = std::move(cs.upperBoundContributors); + result->upperBoundContributors = std::move(cs->upperBoundContributors); if (result->timeout || result->cancelled) { diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index ca77c8dd..ad07b0fd 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -15,7 +15,6 @@ #include "Luau/VisitType.h" LUAU_FASTFLAG(DebugLuauGreedyGeneralization) -LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTFLAGVARIABLE(LuauNonReentrantGeneralization2) @@ -549,7 +548,7 @@ struct FreeTypeSearcher : TypeVisitor traverse(*prop.readTy); else { - LUAU_ASSERT(prop.isShared() || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete); + LUAU_ASSERT(prop.isShared()); Polarity p = polarity; polarity = Polarity::Mixed; @@ -605,7 +604,7 @@ struct FreeTypeSearcher : TypeVisitor return false; } - bool visit(TypeId, const ClassType&) override + bool visit(TypeId, const ExternType&) override { return false; } @@ -897,7 +896,7 @@ struct TypeCacher : TypeOnceVisitor return false; } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { cache(ty); return false; @@ -1513,26 +1512,22 @@ void pruneUnnecessaryGenerics( if (!functionTy) return; - // Types (and packs) to be removed from the generics list - DenseHashSet clipTypes{nullptr}; - DenseHashSet clipTypePacks{nullptr}; + // If a generic has no explicit name and is only referred to in one place in + // the function's signature, it can be replaced with unknown. GenericCounter counter{cachedTypes}; for (TypeId generic : functionTy->generics) { + generic = follow(generic); auto g = get(generic); - LUAU_ASSERT(g); - if (!g) - clipTypes.insert(generic); - else if (!g->explicitName) + if (g && !g->explicitName) counter.generics[generic] = 0; } for (TypePackId genericPack : functionTy->genericPacks) { + genericPack = follow(genericPack); auto g = get(genericPack); - if (!g) - clipTypePacks.insert(genericPack); - else if (!g->explicitName) + if (g && !g->explicitName) counter.genericPacks[genericPack] = 0; } @@ -1541,18 +1536,22 @@ void pruneUnnecessaryGenerics( for (const auto& [generic, count] : counter.generics) { if (count == 1) - { emplaceType(asMutable(generic), builtinTypes->unknownType); - clipTypes.insert(generic); - } } + // Remove duplicates and types that aren't actually generics. + DenseHashSet seen{nullptr}; auto it = std::remove_if( functionTy->generics.begin(), functionTy->generics.end(), [&](TypeId ty) { - return clipTypes.contains(ty); + ty = follow(ty); + if (seen.contains(ty)) + return true; + seen.insert(ty); + + return !get(ty); } ); @@ -1561,18 +1560,21 @@ void pruneUnnecessaryGenerics( for (const auto& [genericPack, count] : counter.genericPacks) { if (count == 1) - { emplaceTypePack(asMutable(genericPack), builtinTypes->unknownTypePack); - clipTypePacks.insert(genericPack); - } } + DenseHashSet seen2{nullptr}; auto it2 = std::remove_if( functionTy->genericPacks.begin(), functionTy->genericPacks.end(), [&](TypePackId tp) { - return clipTypePacks.contains(tp); + tp = follow(tp); + if (seen2.contains(tp)) + return true; + seen2.insert(tp); + + return !get(tp); } ); diff --git a/Analysis/src/InferPolarity.cpp b/Analysis/src/InferPolarity.cpp index aae80a2d..2ce8f508 100644 --- a/Analysis/src/InferPolarity.cpp +++ b/Analysis/src/InferPolarity.cpp @@ -95,16 +95,16 @@ struct InferPolarity : TypeVisitor // types. for (TypeId generic : ft.generics) { + generic = follow(generic); const auto gen = get(generic); - LUAU_ASSERT(gen); - if (subsumes(scope, gen->scope)) + if (gen && subsumes(scope, gen->scope)) types[generic] = Polarity::None; } for (TypePackId genericPack : ft.genericPacks) { + genericPack = follow(genericPack); const auto gen = get(genericPack); - LUAU_ASSERT(gen); - if (subsumes(scope, gen->scope)) + if (gen && subsumes(scope, gen->scope)) packs[genericPack] = Polarity::None; } @@ -118,7 +118,7 @@ struct InferPolarity : TypeVisitor return false; } - bool visit(TypeId, const ClassType&) override + bool visit(TypeId, const ExternType&) override { return false; } diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp index dff01daa..7ab53d9c 100644 --- a/Analysis/src/Instantiation.cpp +++ b/Analysis/src/Instantiation.cpp @@ -50,7 +50,7 @@ bool Instantiation::ignoreChildren(TypeId ty) { if (log->getMutable(ty)) return true; - else if (get(ty)) + else if (get(ty)) return true; else return false; @@ -120,7 +120,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty) // whenever we quantify, so the vectors overlap if and only if they are equal. return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks); } - else if (get(ty)) + else if (get(ty)) return true; else { diff --git a/Analysis/src/Instantiation2.cpp b/Analysis/src/Instantiation2.cpp index 106ad870..e7541173 100644 --- a/Analysis/src/Instantiation2.cpp +++ b/Analysis/src/Instantiation2.cpp @@ -6,7 +6,7 @@ namespace Luau bool Instantiation2::ignoreChildren(TypeId ty) { - if (get(ty)) + if (get(ty)) return true; if (auto ftv = get(ty)) diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp index 6e0c64d0..f78e2143 100644 --- a/Analysis/src/IostreamHelpers.cpp +++ b/Analysis/src/IostreamHelpers.cpp @@ -193,8 +193,8 @@ static void errorToString(std::ostream& stream, const T& err) stream << "NormalizationTooComplex { }"; else if constexpr (std::is_same_v) stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }"; - else if constexpr (std::is_same_v) - stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }"; + else if constexpr (std::is_same_v) + stream << "DynamicPropertyLookupOnExternTypesUnsafe { " << toString(err.ty) << " }"; else if constexpr (std::is_same_v) stream << "UninhabitedTypeFunction { " << toString(err.ty) << " }"; else if constexpr (std::is_same_v) diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index dc3bbb64..795d0fc8 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -20,6 +20,7 @@ LUAU_FASTFLAG(LuauAttribute) LUAU_FASTFLAGVARIABLE(LintRedundantNativeAttribute) LUAU_FASTFLAG(LuauDeprecatedAttribute) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace Luau { @@ -908,6 +909,11 @@ private: return true; } + bool visit(AstTypePack* node) override + { + return FFlag::LuauStoreReturnTypesAsPackOnAst; + } + bool visit(AstTypeReference* node) override { if (!node->prefix) @@ -1970,6 +1976,11 @@ private: return true; } + bool visit(AstTypePack* node) override + { + return FFlag::LuauStoreReturnTypesAsPackOnAst; + } + bool visit(AstTypeTable* node) override { if (FFlag::LuauSolverV2) @@ -2372,9 +2383,9 @@ private: void check(AstExprIndexName* node, TypeId ty) { - if (const ClassType* cty = get(ty)) + if (const ExternType* cty = get(ty)) { - const Property* prop = lookupClassProp(cty, node->index.value); + const Property* prop = lookupExternTypeProp(cty, node->index.value); if (prop && prop->deprecated) report(node->location, *prop, cty->name.c_str(), node->index.value); diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 3ee7fce4..d404f5ad 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -26,6 +26,7 @@ LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAGVARIABLE(LuauNonStrictVisitorImprovements) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictWarnOnUnknownGlobals) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace Luau { @@ -311,7 +312,7 @@ struct NonStrictTypeChecker return visit(s); else if (auto s = stat->as()) return visit(s); - else if (auto s = stat->as()) + else if (auto s = stat->as()) return visit(s); else if (auto s = stat->as()) return visit(s); @@ -544,7 +545,7 @@ struct NonStrictTypeChecker return {}; } - NonStrictContext visit(AstStatDeclareClass* declClass) + NonStrictContext visit(AstStatDeclareExternType* declClass) { if (FFlag::LuauNewNonStrictVisitTypes) { @@ -833,8 +834,16 @@ struct NonStrictTypeChecker { visitGenerics(exprFn->generics, exprFn->genericPacks); - if (exprFn->returnAnnotation) - visit(*exprFn->returnAnnotation); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + if (exprFn->returnAnnotation) + visit(exprFn->returnAnnotation); + } + else + { + if (exprFn->returnAnnotation_DEPRECATED) + visit(*exprFn->returnAnnotation_DEPRECATED); + } if (exprFn->varargAnnotation) visit(exprFn->varargAnnotation); diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 3754f5a3..cadc9244 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -249,23 +249,23 @@ bool isSubtype(const NormalizedStringType& subStr, const NormalizedStringType& s return true; } -void NormalizedClassType::pushPair(TypeId ty, TypeIds negations) +void NormalizedExternType::pushPair(TypeId ty, TypeIds negations) { - auto result = classes.insert(std::make_pair(ty, std::move(negations))); + auto result = externTypes.insert(std::make_pair(ty, std::move(negations))); if (result.second) ordering.push_back(ty); - LUAU_ASSERT(ordering.size() == classes.size()); + LUAU_ASSERT(ordering.size() == externTypes.size()); } -void NormalizedClassType::resetToNever() +void NormalizedExternType::resetToNever() { ordering.clear(); - classes.clear(); + externTypes.clear(); } -bool NormalizedClassType::isNever() const +bool NormalizedExternType::isNever() const { - return classes.empty(); + return externTypes.empty(); } void NormalizedFunctionType::resetToTop() @@ -307,14 +307,14 @@ bool NormalizedType::isUnknown() const strings.isString() && isThread(threads) && isBuffer(buffers); // Check is class - bool isTopClass = false; - for (auto [t, disj] : classes.classes) + bool isTopExternType = false; + for (const auto& [t, disj] : externTypes.externTypes) { - if (auto ct = get(t)) + if (auto ct = get(t)) { if (ct->name == "class" && disj.empty()) { - isTopClass = true; + isTopExternType = true; break; } } @@ -330,24 +330,24 @@ bool NormalizedType::isUnknown() const } } // any = unknown or error ==> we need to make sure we have all the unknown components, but not errors - return get(errors) && hasAllPrimitives && isTopClass && isTopTable && functions.isTop; + return get(errors) && hasAllPrimitives && isTopExternType && isTopTable && functions.isTop; } bool NormalizedType::isExactlyNumber() const { - return hasNumbers() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() && + return hasNumbers() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasStrings() && !hasThreads() && !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); } bool NormalizedType::isSubtypeOfString() const { - return hasStrings() && !hasTops() && !hasBooleans() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() && + return hasStrings() && !hasTops() && !hasBooleans() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasThreads() && !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); } bool NormalizedType::isSubtypeOfBooleans() const { - return hasBooleans() && !hasTops() && !hasClasses() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() && + return hasBooleans() && !hasTops() && !hasExternTypes() && !hasErrors() && !hasNils() && !hasNumbers() && !hasStrings() && !hasThreads() && !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars(); } @@ -380,9 +380,9 @@ bool NormalizedType::hasBooleans() const return !get(booleans); } -bool NormalizedType::hasClasses() const +bool NormalizedType::hasExternTypes() const { - return !classes.isNever(); + return !externTypes.isNever(); } bool NormalizedType::hasErrors() const @@ -440,7 +440,7 @@ bool NormalizedType::isFalsy() const hasAFalse = !bs->value; } - return (hasAFalse || hasNils()) && (!hasTops() && !hasClasses() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() && + return (hasAFalse || hasNils()) && (!hasTops() && !hasExternTypes() && !hasErrors() && !hasNumbers() && !hasStrings() && !hasThreads() && !hasBuffers() && !hasTables() && !hasFunctions() && !hasTyvars()); } @@ -452,7 +452,7 @@ bool NormalizedType::isTruthy() const static bool isShallowInhabited(const NormalizedType& norm) { // This test is just a shallow check, for example it returns `true` for `{ p : never }` - return !get(norm.tops) || !get(norm.booleans) || !norm.classes.isNever() || !get(norm.errors) || + return !get(norm.tops) || !get(norm.booleans) || !norm.externTypes.isNever() || !get(norm.errors) || !get(norm.nils) || !get(norm.numbers) || !norm.strings.isNever() || !get(norm.threads) || !get(norm.buffers) || !norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty(); } @@ -471,7 +471,7 @@ NormalizationResult Normalizer::isInhabited(const NormalizedType* norm, Set(norm->tops) || !get(norm->booleans) || !get(norm->errors) || !get(norm->nils) || - !get(norm->numbers) || !get(norm->threads) || !get(norm->buffers) || !norm->classes.isNever() || + !get(norm->numbers) || !get(norm->threads) || !get(norm->buffers) || !norm->externTypes.isNever() || !norm->strings.isNever() || !norm->functions.isNever()) return NormalizationResult::True; @@ -619,13 +619,13 @@ static int tyvarIndex(TypeId ty) return 0; } -static bool isTop(NotNull builtinTypes, const NormalizedClassType& classes) +static bool isTop(NotNull builtinTypes, const NormalizedExternType& externTypes) { - if (classes.classes.size() != 1) + if (externTypes.externTypes.size() != 1) return false; - auto first = classes.classes.begin(); - if (first->first != builtinTypes->classType) + auto first = externTypes.externTypes.begin(); + if (first->first != builtinTypes->externType) return false; if (!first->second.empty()) @@ -634,11 +634,11 @@ static bool isTop(NotNull builtinTypes, const NormalizedClassType& return true; } -static void resetToTop(NotNull builtinTypes, NormalizedClassType& classes) +static void resetToTop(NotNull builtinTypes, NormalizedExternType& externTypes) { - classes.ordering.clear(); - classes.classes.clear(); - classes.pushPair(builtinTypes->classType, TypeIds{}); + externTypes.ordering.clear(); + externTypes.externTypes.clear(); + externTypes.pushPair(builtinTypes->externType, TypeIds{}); } #ifdef LUAU_ASSERTENABLED @@ -762,50 +762,50 @@ static bool areNormalizedTables(const TypeIds& tys) return true; } -static bool areNormalizedClasses(const NormalizedClassType& tys) +static bool areNormalizedExternTypes(const NormalizedExternType& tys) { - for (const auto& [ty, negations] : tys.classes) + for (const auto& [ty, negations] : tys.externTypes) { - const ClassType* ctv = get(ty); - if (!ctv) + const ExternType* etv = get(ty); + if (!etv) { return false; } for (TypeId negation : negations) { - const ClassType* nctv = get(negation); + const ExternType* nctv = get(negation); if (!nctv) { return false; } - if (!isSubclass(nctv, ctv)) + if (!isSubclass(nctv, etv)) { return false; } } - for (const auto& [otherTy, otherNegations] : tys.classes) + for (const auto& [otherTy, otherNegations] : tys.externTypes) { if (otherTy == ty) continue; - const ClassType* octv = get(otherTy); + const ExternType* octv = get(otherTy); if (!octv) { return false; } - if (isSubclass(ctv, octv)) + if (isSubclass(etv, octv)) { - auto iss = [ctv](TypeId t) + auto iss = [etv](TypeId t) { - const ClassType* c = get(t); + const ExternType* c = get(t); if (!c) return false; - return isSubclass(ctv, c); + return isSubclass(etv, c); }; if (!std::any_of(otherNegations.begin(), otherNegations.end(), iss)) @@ -847,7 +847,7 @@ static void assertInvariant(const NormalizedType& norm) LUAU_ASSERT(isNormalizedTop(norm.tops)); LUAU_ASSERT(isNormalizedBoolean(norm.booleans)); - LUAU_ASSERT(areNormalizedClasses(norm.classes)); + LUAU_ASSERT(areNormalizedExternTypes(norm.externTypes)); LUAU_ASSERT(isNormalizedError(norm.errors)); LUAU_ASSERT(isNormalizedNil(norm.nils)); LUAU_ASSERT(isNormalizedNumber(norm.numbers)); @@ -988,7 +988,7 @@ void Normalizer::clearNormal(NormalizedType& norm) { norm.tops = builtinTypes->neverType; norm.booleans = builtinTypes->neverType; - norm.classes.resetToNever(); + norm.externTypes.resetToNever(); norm.errors = builtinTypes->neverType; norm.nils = builtinTypes->neverType; norm.numbers = builtinTypes->neverType; @@ -1138,17 +1138,17 @@ TypeId Normalizer::unionOfBools(TypeId here, TypeId there) return builtinTypes->booleanType; } -void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there) +void Normalizer::unionExternTypesWithExternType(TypeIds& heres, TypeId there) { if (heres.count(there)) return; - const ClassType* tctv = get(there); + const ExternType* tctv = get(there); for (auto it = heres.begin(); it != heres.end();) { TypeId here = *it; - const ClassType* hctv = get(here); + const ExternType* hctv = get(here); if (isSubclass(tctv, hctv)) return; else if (isSubclass(hctv, tctv)) @@ -1160,16 +1160,16 @@ void Normalizer::unionClassesWithClass(TypeIds& heres, TypeId there) heres.insert(there); } -void Normalizer::unionClasses(TypeIds& heres, const TypeIds& theres) +void Normalizer::unionExternTypes(TypeIds& heres, const TypeIds& theres) { for (TypeId there : theres) - unionClassesWithClass(heres, there); + unionExternTypesWithExternType(heres, there); } static bool isSubclass(TypeId test, TypeId parent) { - const ClassType* testCtv = get(test); - const ClassType* parentCtv = get(parent); + const ExternType* testCtv = get(test); + const ExternType* parentCtv = get(parent); LUAU_ASSERT(testCtv); LUAU_ASSERT(parentCtv); @@ -1177,12 +1177,12 @@ static bool isSubclass(TypeId test, TypeId parent) return isSubclass(testCtv, parentCtv); } -void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there) +void Normalizer::unionExternTypesWithExternType(NormalizedExternType& heres, TypeId there) { for (auto it = heres.ordering.begin(); it != heres.ordering.end();) { TypeId hereTy = *it; - TypeIds& hereNegations = heres.classes.at(hereTy); + TypeIds& hereNegations = heres.externTypes.at(hereTy); // If the incoming class is a subclass of another class in the map, we // must ensure that it is negated by one of the negations in the same @@ -1204,7 +1204,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there) } // If the incoming class is a superclass of one of the // negations, then the negation no longer applies and must be - // removed. This is also true if they are equal. Since classes + // removed. This is also true if they are equal. Since extern types // are, at this time, entirely persistent (we do not clone // them), a pointer identity check is sufficient. else if (isSubclass(hereNegation, there)) @@ -1231,7 +1231,7 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there) { TypeIds negations = std::move(hereNegations); it = heres.ordering.erase(it); - heres.classes.erase(hereTy); + heres.externTypes.erase(hereTy); heres.pushPair(there, std::move(negations)); return; @@ -1248,10 +1248,10 @@ void Normalizer::unionClassesWithClass(NormalizedClassType& heres, TypeId there) heres.pushPair(there, TypeIds{}); } -void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassType& theres) +void Normalizer::unionExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres) { - // This method bears much similarity with unionClassesWithClass, but is - // solving a more general problem. In unionClassesWithClass, we are dealing + // This method bears much similarity with unionExternTypesWithExternType, but is + // solving a more general problem. In unionExternTypesWithExternType, we are dealing // with a singular positive type. Since it's one type, we can use early // returns as control flow. Since it's guaranteed to be positive, we do not // have negations to worry about combining. The two aspects combine to make @@ -1260,9 +1260,9 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT for (const TypeId thereTy : theres.ordering) { - const TypeIds& thereNegations = theres.classes.at(thereTy); + const TypeIds& thereNegations = theres.externTypes.at(thereTy); - // If it happens that there are _no_ classes in the current map, or the + // If it happens that there are _no_ extern types in the current map, or the // incoming class is completely unrelated to any class in the current // map, we must insert the incoming pair as-is. bool insert = true; @@ -1270,7 +1270,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT for (auto it = heres.ordering.begin(); it != heres.ordering.end();) { TypeId hereTy = *it; - TypeIds& hereNegations = heres.classes.at(hereTy); + TypeIds& hereNegations = heres.externTypes.at(hereTy); if (isSubclass(thereTy, hereTy)) { @@ -1294,7 +1294,7 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT // If the incoming class is a superclass of one of the // negations, then the negation no longer applies and must // be removed. This is also true if they are equal. Since - // classes are, at this time, entirely persistent (we do not + // extern types are, at this time, entirely persistent (we do not // clone them), a pointer identity check is sufficient. else if (isSubclass(hereNegateTy, thereTy)) { @@ -1319,17 +1319,17 @@ void Normalizer::unionClasses(NormalizedClassType& heres, const NormalizedClassT else if (isSubclass(hereTy, thereTy)) { TypeIds negations = std::move(hereNegations); - unionClasses(negations, thereNegations); + unionExternTypes(negations, thereNegations); it = heres.ordering.erase(it); - heres.classes.erase(hereTy); + heres.externTypes.erase(hereTy); heres.pushPair(thereTy, std::move(negations)); insert = false; break; } else if (hereTy == thereTy) { - unionClasses(hereNegations, thereNegations); + unionExternTypes(hereNegations, thereNegations); insert = false; break; } @@ -1690,7 +1690,7 @@ NormalizationResult Normalizer::unionNormals(NormalizedType& here, const Normali return NormalizationResult::HitLimits; here.booleans = unionOfBools(here.booleans, there.booleans); - unionClasses(here.classes, there.classes); + unionExternTypes(here.externTypes, there.externTypes); here.errors = (get(there.errors) ? here.errors : there.errors); here.nils = (get(there.nils) ? here.nils : there.nils); @@ -1830,8 +1830,8 @@ NormalizationResult Normalizer::unionNormalWithTy( unionFunctionsWithFunction(here.functions, there); else if (get(there) || get(there)) unionTablesWithTable(here.tables, there); - else if (get(there)) - unionClassesWithClass(here.classes, there); + else if (get(there)) + unionExternTypesWithExternType(here.externTypes, there); else if (get(there)) here.errors = there; else if (const PrimitiveType* ptv = get(there)) @@ -1944,29 +1944,29 @@ std::optional Normalizer::negateNormal(const NormalizedType& her result.booleans = builtinTypes->trueType; } - if (here.classes.isNever()) + if (here.externTypes.isNever()) { - resetToTop(builtinTypes, result.classes); + resetToTop(builtinTypes, result.externTypes); } - else if (isTop(builtinTypes, result.classes)) + else if (isTop(builtinTypes, result.externTypes)) { - result.classes.resetToNever(); + result.externTypes.resetToNever(); } else { TypeIds rootNegations{}; - for (const auto& [hereParent, hereNegations] : here.classes.classes) + for (const auto& [hereParent, hereNegations] : here.externTypes.externTypes) { - if (hereParent != builtinTypes->classType) + if (hereParent != builtinTypes->externType) rootNegations.insert(hereParent); for (TypeId hereNegation : hereNegations) - unionClassesWithClass(result.classes, hereNegation); + unionExternTypesWithExternType(result.externTypes, hereNegation); } if (!rootNegations.empty()) - result.classes.pushPair(builtinTypes->classType, rootNegations); + result.externTypes.pushPair(builtinTypes->externType, rootNegations); } result.nils = get(here.nils) ? builtinTypes->nilType : builtinTypes->neverType; @@ -2144,7 +2144,7 @@ TypeId Normalizer::intersectionOfBools(TypeId here, TypeId there) return there; } -void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedClassType& theres) +void Normalizer::intersectExternTypes(NormalizedExternType& heres, const NormalizedExternType& theres) { if (theres.isNever()) { @@ -2178,12 +2178,12 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl // declare the result of the intersection operation to be never. for (const TypeId thereTy : theres.ordering) { - const TypeIds& thereNegations = theres.classes.at(thereTy); + const TypeIds& thereNegations = theres.externTypes.at(thereTy); for (auto it = heres.ordering.begin(); it != heres.ordering.end();) { TypeId hereTy = *it; - TypeIds& hereNegations = heres.classes.at(hereTy); + TypeIds& hereNegations = heres.externTypes.at(hereTy); if (isSubclass(thereTy, hereTy)) { @@ -2206,10 +2206,10 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl } } - unionClasses(negations, thereNegations); + unionExternTypes(negations, thereNegations); it = heres.ordering.erase(it); - heres.classes.erase(hereTy); + heres.externTypes.erase(hereTy); heres.pushPair(thereTy, std::move(negations)); break; } @@ -2234,15 +2234,15 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl { if (isSubclass(hereTy, *nIt)) { - // eg SomeClass & (class & ~SomeClass) - // or SomeClass & (class & ~ParentClass) - heres.classes.erase(hereTy); + // eg SomeExternType & (class & ~SomeExternType) + // or SomeExternType & (class & ~ParentExternType) + heres.externTypes.erase(hereTy); it = heres.ordering.erase(it); erasedHere = true; break; } - // eg SomeClass & (class & ~Unrelated) + // eg SomeExternType & (class & ~Unrelated) if (!isSubclass(*nIt, hereTy)) nIt = negations.erase(nIt); else @@ -2251,30 +2251,30 @@ void Normalizer::intersectClasses(NormalizedClassType& heres, const NormalizedCl if (!erasedHere) { - unionClasses(hereNegations, negations); + unionExternTypes(hereNegations, negations); ++it; } } else if (hereTy == thereTy) { - unionClasses(hereNegations, thereNegations); + unionExternTypes(hereNegations, thereNegations); break; } else { it = heres.ordering.erase(it); - heres.classes.erase(hereTy); + heres.externTypes.erase(hereTy); } } } } -void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId there) +void Normalizer::intersectExternTypesWithExternType(NormalizedExternType& heres, TypeId there) { for (auto it = heres.ordering.begin(); it != heres.ordering.end();) { TypeId hereTy = *it; - const TypeIds& hereNegations = heres.classes.at(hereTy); + const TypeIds& hereNegations = heres.externTypes.at(hereTy); // If the incoming class _is_ the current class, we skip it. Maybe // another entry will have a different story. We check for this first @@ -2319,7 +2319,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th } it = heres.ordering.erase(it); - heres.classes.erase(hereTy); + heres.externTypes.erase(hereTy); if (!emptyIntersectWithNegation) heres.pushPair(there, std::move(negations)); break; @@ -2335,7 +2335,7 @@ void Normalizer::intersectClassesWithClass(NormalizedClassType& heres, TypeId th else { it = heres.ordering.erase(it); - heres.classes.erase(hereTy); + heres.externTypes.erase(hereTy); } } } @@ -3083,7 +3083,7 @@ NormalizationResult Normalizer::intersectNormals(NormalizedType& here, const Nor here.booleans = intersectionOfBools(here.booleans, there.booleans); - intersectClasses(here.classes, there.classes); + intersectExternTypes(here.externTypes, there.externTypes); here.errors = (get(there.errors) ? there.errors : here.errors); here.nils = (get(there.nils) ? there.nils : here.nils); here.numbers = (get(there.numbers) ? there.numbers : here.numbers); @@ -3205,12 +3205,12 @@ NormalizationResult Normalizer::intersectNormalWithTy( intersectTablesWithTable(tables, there, seenTablePropPairs, seenSetTypes); here.tables = std::move(tables); } - else if (get(there)) + else if (get(there)) { - NormalizedClassType nct = std::move(here.classes); + NormalizedExternType nct = std::move(here.externTypes); clearNormal(here); - intersectClassesWithClass(nct, there); - here.classes = std::move(nct); + intersectExternTypesWithExternType(nct, there); + here.externTypes = std::move(nct); } else if (get(there)) { @@ -3274,7 +3274,7 @@ NormalizationResult Normalizer::intersectNormalWithTy( subtractPrimitive(here, ntv->ty); else if (const SingletonType* stv = get(t)) subtractSingleton(here, follow(ntv->ty)); - else if (get(t)) + else if (get(t)) { NormalizationResult res = intersectNormalWithNegationTy(t, here); if (shouldEarlyExit(res)) @@ -3334,7 +3334,7 @@ NormalizationResult Normalizer::intersectNormalWithTy( } else if (get(there)) { - here.classes.resetToNever(); + here.externTypes.resetToNever(); } else if (get(there)) { @@ -3403,18 +3403,18 @@ TypeId Normalizer::typeFromNormal(const NormalizedType& norm) if (!get(norm.booleans)) result.push_back(norm.booleans); - if (isTop(builtinTypes, norm.classes)) + if (isTop(builtinTypes, norm.externTypes)) { - result.push_back(builtinTypes->classType); + result.push_back(builtinTypes->externType); } - else if (!norm.classes.isNever()) + else if (!norm.externTypes.isNever()) { std::vector parts; - parts.reserve(norm.classes.classes.size()); + parts.reserve(norm.externTypes.externTypes.size()); - for (const TypeId normTy : norm.classes.ordering) + for (const TypeId normTy : norm.externTypes.ordering) { - const TypeIds& normNegations = norm.classes.classes.at(normTy); + const TypeIds& normNegations = norm.externTypes.externTypes.at(normTy); if (normNegations.empty()) { diff --git a/Analysis/src/RequireTracer.cpp b/Analysis/src/RequireTracer.cpp index 76970f73..6e9dd854 100644 --- a/Analysis/src/RequireTracer.cpp +++ b/Analysis/src/RequireTracer.cpp @@ -4,6 +4,8 @@ #include "Luau/Ast.h" #include "Luau/Module.h" +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) + namespace Luau { @@ -65,6 +67,12 @@ struct RequireTracer : AstVisitor return true; } + bool visit(AstTypePack* node) override + { + // allow resolving require inside `typeof` annotations + return FFlag::LuauStoreReturnTypesAsPackOnAst; + } + AstExpr* getDependent_DEPRECATED(AstExpr* node) { if (AstExprLocal* expr = node->as()) diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp index 7c5188f7..67043e4f 100644 --- a/Analysis/src/Simplify.cpp +++ b/Analysis/src/Simplify.cpp @@ -356,7 +356,7 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) // * FunctionType // * TableType // * MetatableType - // * ClassType + // * ExternType // * UnionType // * IntersectionType // * NegationType @@ -476,13 +476,13 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) return Relation::Disjoint; } - if (get(right) || get(right) || get(right) || get(right)) + if (get(right) || get(right) || get(right) || get(right)) return Relation::Disjoint; } if (auto ls = get(left)) { - if (get(right) || get(right) || get(right) || get(right)) + if (get(right) || get(right) || get(right) || get(right)) return Relation::Disjoint; if (get(right)) @@ -551,9 +551,9 @@ Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen) return Relation::Disjoint; } - if (auto ct = get(left)) + if (auto ct = get(left)) { - if (auto rct = get(right)) + if (auto rct = get(right)) { if (isSubclass(ct, rct)) return Relation::Subset; @@ -873,7 +873,7 @@ std::optional TypeSimplifier::basicIntersectWithTruthy(TypeId target) co if (is(target)) return target; - if (is(target)) + if (is(target)) return target; if (auto pt = get(target)) @@ -909,7 +909,7 @@ std::optional TypeSimplifier::basicIntersectWithFalsy(TypeId target) con if (is(target)) return builtinTypes->falsyType; - if (is(target)) + if (is(target)) return builtinTypes->neverType; if (auto pt = get(target)) diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp index 4736c5ee..791109c8 100644 --- a/Analysis/src/Substitution.cpp +++ b/Analysis/src/Substitution.cpp @@ -136,9 +136,9 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log) clone.parts = a.parts; return dest.addType(std::move(clone)); } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { - ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer}; + ExternType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.definitionLocation, a.indexer}; return dest.addType(std::move(clone)); } else if constexpr (std::is_same_v) @@ -252,21 +252,21 @@ void Tarjan::visitChildren(TypeId ty, int index) for (TypePackId a : tfit->packArguments) visitChild(a); } - else if (const ClassType* ctv = get(ty)) + else if (const ExternType* etv = get(ty)) { - for (const auto& [name, prop] : ctv->props) + for (const auto& [name, prop] : etv->props) visitChild(prop.type()); - if (ctv->parent) - visitChild(*ctv->parent); + if (etv->parent) + visitChild(*etv->parent); - if (ctv->metatable) - visitChild(*ctv->metatable); + if (etv->metatable) + visitChild(*etv->metatable); - if (ctv->indexer) + if (etv->indexer) { - visitChild(ctv->indexer->indexType); - visitChild(ctv->indexer->indexResultType); + visitChild(etv->indexer->indexType); + visitChild(etv->indexer->indexResultType); } } else if (const NegationType* ntv = get(ty)) @@ -838,21 +838,21 @@ void Substitution::replaceChildren(TypeId ty) for (TypePackId& a : tfit->packArguments) a = replace(a); } - else if (ClassType* ctv = getMutable(ty)) + else if (ExternType* etv = getMutable(ty)) { - for (auto& [name, prop] : ctv->props) + for (auto& [name, prop] : etv->props) prop.setType(replace(prop.type())); - if (ctv->parent) - ctv->parent = replace(*ctv->parent); + if (etv->parent) + etv->parent = replace(*etv->parent); - if (ctv->metatable) - ctv->metatable = replace(*ctv->metatable); + if (etv->metatable) + etv->metatable = replace(*etv->metatable); - if (ctv->indexer) + if (etv->indexer) { - ctv->indexer->indexType = replace(ctv->indexer->indexType); - ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType); + etv->indexer->indexType = replace(etv->indexer->indexType); + etv->indexer->indexResultType = replace(etv->indexer->indexResultType); } } else if (NegationType* ntv = getMutable(ty)) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index b3bac468..0c965ca6 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -315,7 +315,7 @@ struct ApplyMappedGenerics : Substitution bool ignoreChildren(TypeId ty) override { - if (get(ty)) + if (get(ty)) return true; return ty->persistent; @@ -744,9 +744,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result = isCovariantWith(env, p, scope); else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); - else if (auto p = get2(subTy, superTy)) + else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); - else if (auto p = get2(subTy, superTy)) + else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, subTy, p.first, superTy, p.second, scope); else if (auto p = get2(subTy, superTy)) result = isCovariantWith(env, p, scope); @@ -1336,7 +1336,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type } // the top class type is not actually a primitive type, so the negation of // any one of them includes the top class type. - else if (auto p = get2(subTy, negatedTy)) + else if (auto p = get2(subTy, negatedTy)) result = {true}; else if (auto p = get(negatedTy); p && is(subTy)) result = {p->type != PrimitiveType::Table}; @@ -1344,9 +1344,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Type result = {p.second->type != PrimitiveType::Function}; else if (auto p = get2(subTy, negatedTy)) result = {*p.first != *p.second}; - else if (auto p = get2(subTy, negatedTy)) + else if (auto p = get2(subTy, negatedTy)) result = SubtypingResult::negate(isCovariantWith(env, p.first, p.second, scope)); - else if (get2(subTy, negatedTy)) + else if (get2(subTy, negatedTy)) result = {true}; else if (is(negatedTy)) iceReporter->ice("attempting to negate a non-testable type"); @@ -1471,15 +1471,15 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta } } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull scope) +SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull scope) { - return {isSubclass(subClass, superClass)}; + return {isSubclass(subExternType, superExternType)}; } SubtypingResult Subtyping::isCovariantWith( SubtypingEnvironment& env, TypeId subTy, - const ClassType* subClass, + const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull scope @@ -1491,7 +1491,7 @@ SubtypingResult Subtyping::isCovariantWith( for (const auto& [name, prop] : superTable->props) { - if (auto classProp = lookupClassProp(subClass, name)) + if (auto classProp = lookupExternTypeProp(subExternType, name)) { result.andAlso(isCovariantWith(env, *classProp, prop, name, scope)); } @@ -1661,7 +1661,7 @@ SubtypingResult Subtyping::isCovariantWith( SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope); result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope)); result.andAlso( - isCovariantWith(env, subNorm->classes, superNorm->classes, scope).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables, scope)) + isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope)) ); result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope)); result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope)); @@ -1678,24 +1678,24 @@ SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith( SubtypingEnvironment& env, - const NormalizedClassType& subClass, - const NormalizedClassType& superClass, + const NormalizedExternType& subExternType, + const NormalizedExternType& superExternType, NotNull scope ) { - for (const auto& [subClassTy, _] : subClass.classes) + for (const auto& [subExternTypeTy, _] : subExternType.externTypes) { SubtypingResult result; - for (const auto& [superClassTy, superNegations] : superClass.classes) + for (const auto& [superExternTypeTy, superNegations] : superExternType.externTypes) { - result.orElse(isCovariantWith(env, subClassTy, superClassTy, scope)); + result.orElse(isCovariantWith(env, subExternTypeTy, superExternTypeTy, scope)); if (!result.isSubtype) continue; for (TypeId negation : superNegations) { - result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation, scope))); + result.andAlso(SubtypingResult::negate(isCovariantWith(env, subExternTypeTy, negation, scope))); if (result.isSubtype) break; } @@ -1710,17 +1710,17 @@ SubtypingResult Subtyping::isCovariantWith( SubtypingResult Subtyping::isCovariantWith( SubtypingEnvironment& env, - const NormalizedClassType& subClass, + const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull scope ) { - for (const auto& [subClassTy, _] : subClass.classes) + for (const auto& [subExternTypeTy, _] : subExternType.externTypes) { SubtypingResult result; for (TypeId superTableTy : superTables) - result.orElse(isCovariantWith(env, subClassTy, superTableTy, scope)); + result.orElse(isCovariantWith(env, subExternTypeTy, superTableTy, scope)); if (!result.isSubtype) return result; diff --git a/Analysis/src/ToDot.cpp b/Analysis/src/ToDot.cpp index 9b1c20fb..ecf89fbf 100644 --- a/Analysis/src/ToDot.cpp +++ b/Analysis/src/ToDot.cpp @@ -299,9 +299,9 @@ void StateDot::visitChildren(TypeId ty, int index) finishNodeLabel(ty); finishNode(); } - else if constexpr (std::is_same_v) + else if constexpr (std::is_same_v) { - formatAppend(result, "ClassType %s", t.name.c_str()); + formatAppend(result, "ExternType %s", t.name.c_str()); finishNodeLabel(ty); finishNode(); diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp index 20ed7a11..525b2144 100644 --- a/Analysis/src/ToString.cpp +++ b/Analysis/src/ToString.cpp @@ -19,6 +19,8 @@ #include #include +LUAU_FASTFLAGVARIABLE(LuauEnableDenseTableAlias) + LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauSyntheticErrors) LUAU_FASTFLAGVARIABLE(LuauStringPartLengthLimit) @@ -121,7 +123,7 @@ struct FindCyclicTypes final : TypeVisitor return true; } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } @@ -720,7 +722,13 @@ struct TypeStringifier if (ttv.boundTo) return stringify(*ttv.boundTo); - if (!state.exhaustive) + bool showName = !state.exhaustive; + if (FFlag::LuauEnableDenseTableAlias) + { + // if hide table alias expansions are enabled and there is a name found for the table, use it + showName = !state.exhaustive || state.opts.hideTableAliasExpansions; + } + if (showName) { if (ttv.name) { @@ -743,6 +751,10 @@ struct TypeStringifier stringify(ttv.instantiatedTypeParams, ttv.instantiatedTypePackParams); return; } + } + + if (!state.exhaustive) + { if (ttv.syntheticName) { state.result.invalid = true; @@ -881,9 +893,9 @@ struct TypeStringifier state.emit(" }"); } - void operator()(TypeId, const ClassType& ctv) + void operator()(TypeId, const ExternType& etv) { - state.emit(ctv.name); + state.emit(etv.name); } void operator()(TypeId, const AnyType&) diff --git a/Analysis/src/TopoSortStatements.cpp b/Analysis/src/TopoSortStatements.cpp index fbf74115..a4203dab 100644 --- a/Analysis/src/TopoSortStatements.cpp +++ b/Analysis/src/TopoSortStatements.cpp @@ -41,6 +41,8 @@ #include #include +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) + namespace Luau { @@ -297,6 +299,11 @@ struct ArcCollector : public AstVisitor add(*name); return true; } + + bool visit(AstTypePack* node) override + { + return FFlag::LuauStoreReturnTypesAsPackOnAst; + } }; struct ContainsFunctionCall : public AstVisitor diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 7ff4cf68..00e69ecd 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -15,6 +15,7 @@ LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauParseOptionalAsNode2) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace { @@ -331,7 +332,7 @@ struct Printer_DEPRECATED } } - void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg) + void visualizeTypePackAnnotation(const AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true) { advance(annotation.location.begin); if (const AstTypePackVariadic* variadicTp = annotation.as()) @@ -349,7 +350,7 @@ struct Printer_DEPRECATED else if (const AstTypePackExplicit* explicitTp = annotation.as()) { LUAU_ASSERT(!forVarArg); - visualizeTypeList(explicitTp->typeList, true); + visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize); } else { @@ -1065,12 +1066,15 @@ struct Printer_DEPRECATED writer.symbol(")"); - if (writeTypes && func.returnAnnotation) + if (writeTypes && (FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value())) { writer.symbol(":"); writer.space(); - visualizeTypeList(*func.returnAnnotation, false); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + visualizeTypePackAnnotation(*func.returnAnnotation, false, false); + else + visualizeTypeList(*func.returnAnnotation_DEPRECATED, false); } visualizeBlock(*func.body); @@ -1174,7 +1178,10 @@ struct Printer_DEPRECATED } writer.symbol("->"); - visualizeTypeList(a->returnTypes, true); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + visualizeTypePackAnnotation(*a->returnTypes, false); + else + visualizeTypeList(a->returnTypes_DEPRECATED, true); } else if (const auto& a = typeAnnotation.as()) { @@ -1368,7 +1375,7 @@ struct Printer } } - void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg) + void visualizeTypePackAnnotation(AstTypePack& annotation, bool forVarArg, bool unconditionallyParenthesize = true) { advance(annotation.location.begin); if (const AstTypePackVariadic* variadicTp = annotation.as()) @@ -1390,10 +1397,10 @@ struct Printer LUAU_ASSERT(!forVarArg); if (const auto cstNode = lookupCstNode(explicitTp)) visualizeTypeList( - explicitTp->typeList, true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions + explicitTp->typeList, FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions ); else - visualizeTypeList(explicitTp->typeList, true); + visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize); } else { @@ -2383,14 +2390,17 @@ struct Printer advanceBefore(func.argLocation->end, 1); writer.symbol(")"); - if (writeTypes && func.returnAnnotation) + if (writeTypes && FFlag::LuauStoreReturnTypesAsPackOnAst ? func.returnAnnotation != nullptr : func.returnAnnotation_DEPRECATED.has_value()) { if (cstNode) advance(cstNode->returnSpecifierPosition); writer.symbol(":"); writer.space(); - visualizeTypeList(*func.returnAnnotation, false); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + visualizeTypePackAnnotation(*func.returnAnnotation, false, false); + else + visualizeTypeList(*func.returnAnnotation_DEPRECATED, false); } visualizeBlock(*func.body); @@ -2573,7 +2583,10 @@ struct Printer if (cstNode) advance(cstNode->returnArrowPosition); writer.symbol("->"); - visualizeTypeList(a->returnTypes, true); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + visualizeTypePackAnnotation(*a->returnTypes, false); + else + visualizeTypeList(a->returnTypes_DEPRECATED, true); } else if (const auto& a = typeAnnotation.as()) { diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index 23f407d2..3add4de9 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -282,8 +282,8 @@ std::optional getMetatable(TypeId type, NotNull builtinTyp if (const MetatableType* mtType = get(type)) return mtType->metatable; - else if (const ClassType* classType = get(type)) - return classType->metatable; + else if (const ExternType* externType = get(type)) + return externType->metatable; else if (isString(type)) { auto ptv = get(builtinTypes->stringType); @@ -346,10 +346,10 @@ std::optional getDefinitionModuleName(TypeId type) if (ftv->definition) return ftv->definition->definitionModuleName; } - else if (auto ctv = get(type)) + else if (auto etv = get(type)) { - if (!ctv->definitionModuleName.empty()) - return ctv->definitionModuleName; + if (!etv->definitionModuleName.empty()) + return etv->definitionModuleName; } return std::nullopt; @@ -1014,7 +1014,7 @@ BuiltinTypes::BuiltinTypes() , threadType(arena->addType(Type{PrimitiveType{PrimitiveType::Thread}, /*persistent*/ true})) , bufferType(arena->addType(Type{PrimitiveType{PrimitiveType::Buffer}, /*persistent*/ true})) , functionType(arena->addType(Type{PrimitiveType{PrimitiveType::Function}, /*persistent*/ true})) - , classType(arena->addType(Type{ClassType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true})) + , externType(arena->addType(Type{ExternType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true})) , tableType(arena->addType(Type{PrimitiveType{PrimitiveType::Table}, /*persistent*/ true})) , emptyTableType(arena->addType(Type{TableType{TableState::Sealed, TypeLevel{}, nullptr}, /*persistent*/ true})) , trueType(arena->addType(Type{SingletonType{BooleanSingleton{true}}, /*persistent*/ true})) @@ -1026,6 +1026,7 @@ BuiltinTypes::BuiltinTypes() , noRefineType(arena->addType(Type{NoRefineType{}, /*persistent*/ true})) , falsyType(arena->addType(Type{UnionType{{falseType, nilType}}, /*persistent*/ true})) , truthyType(arena->addType(Type{NegationType{falsyType}, /*persistent*/ true})) + , notNilType(arena->addType(Type{NegationType{nilType}, /*persistent*/ true})) , optionalNumberType(arena->addType(Type{UnionType{{numberType, nilType}}, /*persistent*/ true})) , optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true})) , emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true})) @@ -1104,9 +1105,9 @@ void persist(TypeId ty) queue.push_back(ttv->indexer->indexResultType); } } - else if (auto ctv = get(t)) + else if (auto etv= get(t)) { - for (const auto& [_name, prop] : ctv->props) + for (const auto& [_name, prop] : etv->props) queue.push_back(prop.type()); } else if (auto utv = get(t)) @@ -1206,7 +1207,7 @@ std::optional getLevel(TypePackId tp) return std::nullopt; } -const Property* lookupClassProp(const ClassType* cls, const Name& name) +const Property* lookupExternTypeProp(const ExternType* cls, const Name& name) { while (cls) { @@ -1215,7 +1216,7 @@ const Property* lookupClassProp(const ClassType* cls, const Name& name) return &it->second; if (cls->parent) - cls = get(*cls->parent); + cls = get(*cls->parent); else return nullptr; @@ -1225,7 +1226,7 @@ const Property* lookupClassProp(const ClassType* cls, const Name& name) return nullptr; } -bool isSubclass(const ClassType* cls, const ClassType* parent) +bool isSubclass(const ExternType* cls, const ExternType* parent) { while (cls) { @@ -1234,7 +1235,7 @@ bool isSubclass(const ClassType* cls, const ClassType* parent) else if (!cls->parent) return false; - cls = get(*cls->parent); + cls = get(*cls->parent); LUAU_ASSERT(cls); } @@ -1303,8 +1304,8 @@ static Tags* getTags(TypeId ty) return &ftv->tags; else if (auto ttv = getMutable(ty)) return &ttv->tags; - else if (auto ctv = getMutable(ty)) - return &ctv->tags; + else if (auto etv = getMutable(ty)) + return &etv->tags; return nullptr; } @@ -1334,19 +1335,19 @@ bool hasTag(TypeId ty, const std::string& tagName) { ty = follow(ty); - // We special case classes because getTags only returns a pointer to one vector of tags. - // But classes has multiple vector of tags, represented throughout the hierarchy. - if (auto ctv = get(ty)) + // We special case extern types because getTags only returns a pointer to one vector of tags. + // But extern types has multiple vector of tags, represented throughout the hierarchy. + if (auto etv = get(ty)) { - while (ctv) + while (etv) { - if (hasTag(ctv->tags, tagName)) + if (hasTag(etv->tags, tagName)) return true; - else if (!ctv->parent) + else if (!etv->parent) return false; - ctv = get(*ctv->parent); - LUAU_ASSERT(ctv); + etv = get(*etv->parent); + LUAU_ASSERT(etv); } } else if (auto tags = getTags(ty)) diff --git a/Analysis/src/TypeAttach.cpp b/Analysis/src/TypeAttach.cpp index cd5fc050..176bdb17 100644 --- a/Analysis/src/TypeAttach.cpp +++ b/Analysis/src/TypeAttach.cpp @@ -14,6 +14,7 @@ #include LUAU_FASTFLAG(LuauStoreCSTData2) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) static char* allocateString(Luau::Allocator& allocator, std::string_view contents) { @@ -219,21 +220,21 @@ public: return Luau::visit(*this, mtv.table->ty); } - AstType* operator()(const ClassType& ctv) + AstType* operator()(const ExternType& etv) { RecursionCounter counter(&count); - char* name = allocateString(*allocator, ctv.name); + char* name = allocateString(*allocator, etv.name); - if (!options.expandClassProps || hasSeen(&ctv) || count > 1) + if (!options.expandExternTypeProps || hasSeen(&etv) || count > 1) return allocator->alloc(Location(), std::nullopt, AstName{name}, std::nullopt, Location()); AstArray props; - props.size = ctv.props.size(); + props.size = etv.props.size(); props.data = static_cast(allocator->allocate(sizeof(AstTableProp) * props.size)); int idx = 0; - for (const auto& [propName, prop] : ctv.props) + for (const auto& [propName, prop] : etv.props) { char* name = allocateString(*allocator, propName); @@ -244,13 +245,13 @@ public: } AstTableIndexer* indexer = nullptr; - if (ctv.indexer) + if (etv.indexer) { RecursionCounter counter(&count); indexer = allocator->alloc(); - indexer->indexType = Luau::visit(*this, ctv.indexer->indexType->ty); - indexer->resultType = Luau::visit(*this, ctv.indexer->indexResultType->ty); + indexer->indexType = Luau::visit(*this, etv.indexer->indexType->ty); + indexer->resultType = Luau::visit(*this, etv.indexer->indexResultType->ty); } return allocator->alloc(Location(), props, indexer); @@ -328,9 +329,19 @@ public: if (retTail) retTailAnnotation = rehydrate(*retTail); - return allocator->alloc( - Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation} - ); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + auto returnAnnotation = allocator->alloc(Location(), AstTypeList{returnTypes, retTailAnnotation}); + return allocator->alloc( + Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, returnAnnotation + ); + } + else + { + return allocator->alloc( + Location(), generics, genericPacks, AstTypeList{argTypes, argTailAnnotation}, argNames, AstTypeList{returnTypes, retTailAnnotation} + ); + } } AstType* operator()(const ErrorType&) { @@ -585,19 +596,40 @@ public: visitLocal(arg); } - if (!fn->returnAnnotation) + if (FFlag::LuauStoreReturnTypesAsPackOnAst) { - if (auto result = getScope(fn->body->location)) + if (!fn->returnAnnotation) { - TypePackId ret = result->returnType; + if (auto result = getScope(fn->body->location)) + { + TypePackId ret = result->returnType; - AstTypePack* variadicAnnotation = nullptr; - const auto& [v, tail] = flatten(ret); + AstTypePack* variadicAnnotation = nullptr; + const auto& [v, tail] = flatten(ret); - if (tail) - variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail); + if (tail) + variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail); - fn->returnAnnotation = AstTypeList{typeAstPack(ret), variadicAnnotation}; + fn->returnAnnotation = allocator->alloc(Location(), AstTypeList{typeAstPack(ret), variadicAnnotation}); + } + } + } + else + { + if (!fn->returnAnnotation_DEPRECATED) + { + if (auto result = getScope(fn->body->location)) + { + TypePackId ret = result->returnType; + + AstTypePack* variadicAnnotation = nullptr; + const auto& [v, tail] = flatten(ret); + + if (tail) + variadicAnnotation = TypeRehydrationVisitor(allocator, &syntheticNames).rehydrate(*tail); + + fn->returnAnnotation_DEPRECATED = AstTypeList{typeAstPack(ret), variadicAnnotation}; + } } } diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 8df11f5e..3550a65a 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -34,6 +34,8 @@ LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAGVARIABLE(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats) +LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace Luau { @@ -661,7 +663,7 @@ void TypeChecker2::visit(AstStat* stat) return visit(s); else if (auto s = stat->as()) return visit(s); - else if (auto s = stat->as()) + else if (auto s = stat->as()) return visit(s); else if (auto s = stat->as()) return visit(s); @@ -1221,7 +1223,10 @@ void TypeChecker2::visit(AstStatDeclareFunction* stat) { visitGenerics(stat->generics, stat->genericPacks); visit(stat->params); - visit(stat->retTypes); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + visit(stat->retTypes); + else + visit(stat->retTypes_DEPRECATED); } void TypeChecker2::visit(AstStatDeclareGlobal* stat) @@ -1229,9 +1234,9 @@ void TypeChecker2::visit(AstStatDeclareGlobal* stat) visit(stat->type); } -void TypeChecker2::visit(AstStatDeclareClass* stat) +void TypeChecker2::visit(AstStatDeclareExternType* stat) { - for (const AstDeclaredClassProp& prop : stat->props) + for (const AstDeclaredExternTypeProperty& prop : stat->props) visit(prop.ty); } @@ -1675,12 +1680,12 @@ void TypeChecker2::visit(AstExprIndexExpr* indexExpr, ValueContext context) { return indexExprMetatableHelper(indexExpr, mt, exprType, indexType); } - else if (auto cls = get(exprType)) + else if (auto cls = get(exprType)) { if (cls->indexer) testIsSubtype(indexType, cls->indexer->indexType, indexExpr->index->location); else - reportError(DynamicPropertyLookupOnClassesUnsafe{exprType}, indexExpr->location); + reportError(DynamicPropertyLookupOnExternTypesUnsafe{exprType}, indexExpr->location); } else if (get(exprType) && isOptional(exprType)) { @@ -1821,8 +1826,16 @@ void TypeChecker2::visit(AstExprFunction* fn) visit(fn->body); // we need to typecheck the return annotation itself, if it exists. - if (fn->returnAnnotation) - visit(*fn->returnAnnotation); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + if (fn->returnAnnotation) + visit(fn->returnAnnotation); + } + else + { + if (fn->returnAnnotation_DEPRECATED) + visit(*fn->returnAnnotation_DEPRECATED); + } // If the function type has a function annotation, we need to see if we can suggest an annotation @@ -2036,7 +2049,7 @@ TypeId TypeChecker2::visit(AstExprBinary* expr, AstNode* overrideKey) // If we're working with things that are not tables, the metatable comparisons above are a little excessive // It's ok for one type to have a meta table and the other to not. In that case, we should fall back on // checking if the intersection of the types is inhabited. If `typesHaveIntersection` failed due to limits, - // TODO: Maybe add more checks here (e.g. for functions, classes, etc) + // TODO: Maybe add more checks here (e.g. for functions, extern types, etc) if (!(get(leftType) || get(rightType))) if (!leftMt.has_value() || !rightMt.has_value()) matches = matches || typesHaveIntersection != NormalizationResult::False; @@ -2613,7 +2626,10 @@ void TypeChecker2::visit(AstTypeFunction* ty) { visitGenerics(ty->generics, ty->genericPacks); visit(ty->argTypes); - visit(ty->returnTypes); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + visit(ty->returnTypes); + else + visit(ty->returnTypes_DEPRECATED); } void TypeChecker2::visit(AstTypeTypeof* ty) @@ -2937,7 +2953,7 @@ PropertyTypes TypeChecker2::lookupProp( if (normValid) { - for (const auto& [ty, _negations] : norm->classes.classes) + for (const auto& [ty, _negations] : norm->externTypes.externTypes) { fetch(ty); @@ -3032,10 +3048,10 @@ void TypeChecker2::checkIndexTypeFromType( if (propTypes.foundOneProp()) reportError(MissingUnionProperty{tableTy, propTypes.missingProp, prop}, location); // For class LValues, we don't want to report an extension error, - // because classes come into being with full knowledge of their + // because extern types come into being with full knowledge of their // shape. We instead want to report the unknown property error of // the `else` branch. - else if (context == ValueContext::LValue && !get(tableTy)) + else if (context == ValueContext::LValue && !get(tableTy)) { const auto lvPropTypes = lookupProp(norm.get(), prop, ValueContext::RValue, location, astIndexExprType, dummy); if (lvPropTypes.foundOneProp() && lvPropTypes.noneMissingProp()) @@ -3045,7 +3061,7 @@ void TypeChecker2::checkIndexTypeFromType( else reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location); } - else if (context == ValueContext::RValue && !get(tableTy)) + else if (context == ValueContext::RValue && !get(tableTy)) { const auto rvPropTypes = lookupProp(norm.get(), prop, ValueContext::LValue, location, astIndexExprType, dummy); if (rvPropTypes.foundOneProp() && rvPropTypes.noneMissingProp()) @@ -3098,19 +3114,25 @@ PropertyType TypeChecker2::hasIndexTypeFromType( return {NormalizationResult::True, {tt->indexer->indexResultType}}; } - - // if we are in a conditional context, we treat the property as present and `unknown` because - // we may be _refining_ `tableTy` to include that property. we will want to revisit this a bit - // in the future once luau has support for exact tables since this only applies when inexact. - return {inConditional(typeContext) ? NormalizationResult::True : NormalizationResult::False, {builtinTypes->unknownType}}; + if (FFlag::LuauTypeCheckerStricterIndexCheck) + { + return {NormalizationResult::False, {builtinTypes->unknownType}}; + } + else + { + // if we are in a conditional context, we treat the property as present and `unknown` because + // we may be _refining_ `tableTy` to include that property. we will want to revisit this a bit + // in the future once luau has support for exact tables since this only applies when inexact. + return {inConditional(typeContext) ? NormalizationResult::True : NormalizationResult::False, {builtinTypes->unknownType}}; + } } - else if (const ClassType* cls = get(ty)) + else if (const ExternType* cls = get(ty)) { // If the property doesn't exist on the class, we consult the indexer // We need to check if the type of the index expression foo (x[foo]) // is compatible with the indexer's indexType // Construct the intersection and test inhabitedness! - if (auto property = lookupClassProp(cls, prop)) + if (auto property = lookupExternTypeProp(cls, prop)) return {NormalizationResult::True, context == ValueContext::LValue ? property->writeTy : property->readTy}; if (cls->indexer) { @@ -3183,17 +3205,17 @@ void TypeChecker2::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& if (auto ttv = getTableType(utk->table)) accumulate(ttv->props); - else if (auto ctv = get(follow(utk->table))) + else if (auto etv = get(follow(utk->table))) { - while (ctv) + while (etv) { - accumulate(ctv->props); + accumulate(etv->props); - if (!ctv->parent) + if (!etv->parent) break; - ctv = get(*ctv->parent); - LUAU_ASSERT(ctv); + etv = get(*etv->parent); + LUAU_ASSERT(etv); } } diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 45edb903..4fae1a70 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -58,8 +58,6 @@ LUAU_FASTFLAGVARIABLE(LuauIndexTypeFunctionFunctionMetamethods) LUAU_FASTFLAGVARIABLE(LuauIntersectNotNil) LUAU_FASTFLAGVARIABLE(LuauSkipNoRefineDuringRefinement) LUAU_FASTFLAGVARIABLE(LuauMetatablesHaveLength) -LUAU_FASTFLAGVARIABLE(LuauDontForgetToReduceUnionFunc) -LUAU_FASTFLAGVARIABLE(LuauSearchForRefineableType) LUAU_FASTFLAGVARIABLE(LuauIndexAnyIsAny) LUAU_FASTFLAGVARIABLE(LuauFixCyclicIndexInIndexer) LUAU_FASTFLAGVARIABLE(LuauSimplyRefineNotNil) @@ -68,6 +66,7 @@ LUAU_FASTFLAGVARIABLE(LuauNewTypeFunReductionChecks2) LUAU_FASTFLAGVARIABLE(LuauReduceUnionFollowUnionType) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) +LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget) namespace Luau { @@ -106,7 +105,7 @@ struct InstanceCollector_DEPRECATED : TypeOnceVisitor cyclicInstance.push_back(t); } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } @@ -182,7 +181,7 @@ struct InstanceCollector : TypeOnceVisitor } } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } @@ -270,7 +269,7 @@ struct UnscopedGenericFinder : TypeOnceVisitor return false; } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } @@ -844,7 +843,7 @@ static std::optional> tryDistributeTypeFunct { arguments[unionIndex] = option; - TypeFunctionReductionResult result = f(instance, arguments, packParams, ctx, args...); + TypeFunctionReductionResult result = f(instance, arguments, packParams, ctx, args...); // NOLINT blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end()); if (result.reductionStatus != Reduction::MaybeOk) reductionStatus = result.reductionStatus; @@ -869,7 +868,7 @@ static std::optional> tryDistributeTypeFunct {}, }); - if (FFlag::LuauDontForgetToReduceUnionFunc && ctx->solver) + if (ctx->solver) ctx->pushConstraint(ReduceConstraint{resultTy}); return {{resultTy, Reduction::MaybeOk, {}, {}}}; @@ -908,7 +907,7 @@ struct FindUserTypeFunctionBlockers : TypeOnceVisitor return true; } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } @@ -2130,7 +2129,7 @@ struct FindRefinementBlockers : TypeOnceVisitor return false; } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } @@ -2252,6 +2251,18 @@ TypeFunctionReductionResult refineTypeFunction( return {std::nullopt, Reduction::MaybeOk, {t}, {}}; } } + + if (FFlag::LuauRefineWaitForBlockedTypesInTarget) + { + // If we have a blocked type in the target, we *could* potentially + // refine it, but more likely we end up with some type explosion in + // normalization. + FindRefinementBlockers frb; + frb.traverse(targetTy); + if (!frb.found.empty()) + return {std::nullopt, Reduction::MaybeOk, {frb.found.begin(), frb.found.end()}, {}}; + } + // 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> @@ -2282,28 +2293,14 @@ TypeFunctionReductionResult refineTypeFunction( } else { - if (FFlag::LuauSearchForRefineableType) - { - // If the discriminant type is only: - // - The `*no-refine*` type or, - // - tables, metatables, unions, intersections, functions, or negations _containing_ `*no-refine*`. - // There's no point in refining against it. - ContainsRefinableType crt; - crt.traverse(discriminant); - if (!crt.found) - return {target, {}}; - } - else - { - if (FFlag::LuauSkipNoRefineDuringRefinement) - if (get(discriminant)) - return {target, {}}; - if (auto nt = get(discriminant)) - { - if (get(follow(nt->ty))) - return {target, {}}; - } - } + // If the discriminant type is only: + // - The `*no-refine*` type or, + // - tables, metatables, unions, intersections, functions, or negations _containing_ `*no-refine*`. + // There's no point in refining against it. + ContainsRefinableType crt; + crt.traverse(discriminant); + if (!crt.found) + return {target, {}}; if (FFlag::LuauSimplyRefineNotNil) { @@ -2684,7 +2681,7 @@ bool computeKeysOf(TypeId ty, Set& result, DenseHashSet& se return res; } - if (auto classTy = get(ty)) + if (auto classTy = get(ty)) { for (auto [key, _] : classTy->props) result.insert(key); @@ -2707,7 +2704,7 @@ bool computeKeysOf(TypeId ty, Set& result, DenseHashSet& se return res; } - // this should not be reachable since the type should be a valid tables or classes part from normalization. + // this should not be reachable since the type should be a valid tables or extern types part from normalization. LUAU_ASSERT(false); return false; } @@ -2733,9 +2730,9 @@ TypeFunctionReductionResult keyofFunctionImpl( if (!normTy) return {std::nullopt, Reduction::MaybeOk, {}, {}}; - // if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes + // if we don't have either just tables or just extern types, we've got nothing to get keys of (at least until a future version perhaps adds extern types // as well) - if (normTy->hasTables() == normTy->hasClasses()) + if (normTy->hasTables() == normTy->hasExternTypes()) return {std::nullopt, Reduction::Erroneous, {}, {}}; // this is sort of atrocious, but we're trying to reject any type that has not normalized to a table or a union of tables. @@ -2746,31 +2743,31 @@ TypeFunctionReductionResult keyofFunctionImpl( // we're going to collect the keys in here Set keys{{}}; - // computing the keys for classes - if (normTy->hasClasses()) + // computing the keys for extern types + if (normTy->hasExternTypes()) { LUAU_ASSERT(!normTy->hasTables()); - // seen set for key computation for classes + // seen set for key computation for extern types DenseHashSet seen{{}}; - auto classesIter = normTy->classes.ordering.begin(); - auto classesIterEnd = normTy->classes.ordering.end(); - LUAU_ASSERT(classesIter != classesIterEnd); // should be guaranteed by the `hasClasses` check earlier + 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(*classesIter, keys, seen, isRaw, ctx)) + 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 (++classesIter != classesIterEnd) + while (++externTypeIter != externTypeIterEnd) { 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(*classesIter, localKeys, seen, isRaw, ctx)) + if (!computeKeysOf(*externTypeIter, localKeys, seen, isRaw, ctx)) continue; for (auto& key : keys) @@ -2785,7 +2782,7 @@ TypeFunctionReductionResult keyofFunctionImpl( // computing the keys for tables if (normTy->hasTables()) { - LUAU_ASSERT(!normTy->hasClasses()); + LUAU_ASSERT(!normTy->hasExternTypes()); // seen set for key computation for tables DenseHashSet seen{{}}; @@ -2947,7 +2944,7 @@ bool searchPropsAndIndexer( return false; } -/* Handles recursion / metamethods of tables/classes +/* Handles recursion / metamethods of tables and extern types `isRaw` parameter indicates whether or not we should follow __index metamethods returns false if property of `ty` could not be found */ bool tblIndexInto_DEPRECATED(TypeId indexer, TypeId indexee, DenseHashSet& result, NotNull ctx, bool isRaw) @@ -3122,11 +3119,11 @@ TypeFunctionReductionResult indexFunctionImpl( return {ctx->builtins->anyType, Reduction::MaybeOk, {}, {}}; } - // if we don't have either just tables or just classes, we've got nothing to index into - if (indexeeNormTy->hasTables() == indexeeNormTy->hasClasses()) + // if we don't have either just tables or just extern types, we've got nothing to index into + if (indexeeNormTy->hasTables() == indexeeNormTy->hasExternTypes()) return {std::nullopt, Reduction::Erroneous, {}, {}}; - // we're trying to reject any type that has not normalized to a table/class or a union of tables/classes. + // we're trying to reject any type that has not normalized to a table or extern type or a union of tables or extern types. if (indexeeNormTy->hasTops() || indexeeNormTy->hasBooleans() || indexeeNormTy->hasErrors() || indexeeNormTy->hasNils() || indexeeNormTy->hasNumbers() || indexeeNormTy->hasStrings() || indexeeNormTy->hasThreads() || indexeeNormTy->hasBuffers() || indexeeNormTy->hasFunctions() || indexeeNormTy->hasTyvars()) @@ -3157,18 +3154,18 @@ TypeFunctionReductionResult indexFunctionImpl( DenseHashSet properties{{}}; // vector of types that will be returned - if (indexeeNormTy->hasClasses()) + if (indexeeNormTy->hasExternTypes()) { LUAU_ASSERT(!indexeeNormTy->hasTables()); - if (isRaw) // rawget should never reduce for classes (to match the behavior of the rawget global function) + if (isRaw) // rawget should never reduce for extern types (to match the behavior of the rawget global function) return {std::nullopt, Reduction::Erroneous, {}, {}}; - // at least one class is guaranteed to be in the iterator by .hasClasses() - for (auto classesIter = indexeeNormTy->classes.ordering.begin(); classesIter != indexeeNormTy->classes.ordering.end(); ++classesIter) + // at least one class is guaranteed to be in the iterator by .hasExternTypes() + for (auto externTypeIter = indexeeNormTy->externTypes.ordering.begin(); externTypeIter != indexeeNormTy->externTypes.ordering.end(); ++externTypeIter) { - auto classTy = get(*classesIter); - if (!classTy) + auto externTy = get(*externTypeIter); + if (!externTy) { LUAU_ASSERT(false); // this should not be possible according to normalization's spec return {std::nullopt, Reduction::Erroneous, {}, {}}; @@ -3177,16 +3174,16 @@ TypeFunctionReductionResult indexFunctionImpl( for (TypeId ty : *typesToFind) { // Search for all instances of indexer in class->props and class->indexer - if (searchPropsAndIndexer(ty, classTy->props, classTy->indexer, properties, ctx)) + if (searchPropsAndIndexer(ty, externTy->props, externTy->indexer, properties, ctx)) continue; // Indexer was found in this class, so we can move on to the next - auto parent = classTy->parent; + auto parent = externTy->parent; bool foundInParent = false; while (parent && !foundInParent) { - auto parentClass = get(follow(*parent)); - foundInParent = searchPropsAndIndexer(ty, parentClass->props, parentClass->indexer, properties, ctx); - parent = parentClass->parent; + auto parentExternType = get(follow(*parent)); + foundInParent = searchPropsAndIndexer(ty, parentExternType->props, parentExternType->indexer, properties, ctx); + parent = parentExternType->parent; } // we move on to the next type if any of the parents we went through had the property. @@ -3198,7 +3195,7 @@ TypeFunctionReductionResult indexFunctionImpl( // 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, *classesIter, "__index", Location{}); + std::optional mmType = findMetatableEntry(ctx->builtins, dummy, *externTypeIter, "__index", Location{}); if (!mmType) // if a metatable does not exist, there is no where else to look return {std::nullopt, Reduction::Erroneous, {}, {}}; @@ -3210,7 +3207,7 @@ TypeFunctionReductionResult indexFunctionImpl( if (indexeeNormTy->hasTables()) { - LUAU_ASSERT(!indexeeNormTy->hasClasses()); + LUAU_ASSERT(!indexeeNormTy->hasExternTypes()); // at least one table is guaranteed to be in the iterator by .hasTables() for (auto tablesIter = indexeeNormTy->tables.begin(); tablesIter != indexeeNormTy->tables.end(); ++tablesIter) @@ -3305,7 +3302,7 @@ TypeFunctionReductionResult setmetatableTypeFunction( // we're trying to reject any type that has not normalized to a table or a union/intersection of tables. if (targetNorm->hasTops() || targetNorm->hasBooleans() || targetNorm->hasErrors() || targetNorm->hasNils() || targetNorm->hasNumbers() || targetNorm->hasStrings() || targetNorm->hasThreads() || targetNorm->hasBuffers() || targetNorm->hasFunctions() || targetNorm->hasTyvars() || - targetNorm->hasClasses()) + targetNorm->hasExternTypes()) return {std::nullopt, Reduction::Erroneous, {}, {}}; // if the supposed metatable is not a table, we will fail to reduce. @@ -3379,7 +3376,7 @@ static TypeFunctionReductionResult getmetatableHelper(TypeId targetTy, c erroneous = false; } - if (auto clazz = get(targetTy)) + if (auto clazz = get(targetTy)) { metatable = clazz->metatable; erroneous = false; diff --git a/Analysis/src/TypeFunctionReductionGuesser.cpp b/Analysis/src/TypeFunctionReductionGuesser.cpp index 389a797d..59b94125 100644 --- a/Analysis/src/TypeFunctionReductionGuesser.cpp +++ b/Analysis/src/TypeFunctionReductionGuesser.cpp @@ -46,7 +46,7 @@ struct InstanceCollector2 : TypeOnceVisitor cyclicInstance.insert(t); } - bool visit(TypeId ty, const ClassType&) override + bool visit(TypeId ty, const ExternType&) override { return false; } diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index 3627b90e..15d7cfd4 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -154,7 +154,7 @@ static std::string getTag(lua_State* L, TypeFunctionTypeId ty) return "table"; else if (get(ty)) return "function"; - else if (get(ty)) + else if (get(ty)) return "class"; else if (get(ty)) return "generic"; @@ -1114,7 +1114,7 @@ static int getClassParent_DEPRECATED(lua_State* L) luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount); TypeFunctionTypeId self = getTypeUserData(L, 1); - auto tfct = get(self); + auto tfct = get(self); if (!tfct) luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); @@ -1136,7 +1136,7 @@ static int getReadParent(lua_State* L) luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount); TypeFunctionTypeId self = getTypeUserData(L, 1); - auto tfct = get(self); + auto tfct = get(self); if (!tfct) luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); @@ -1158,7 +1158,7 @@ static int getWriteParent(lua_State* L) luaL_error(L, "type.parent: expected 1 arguments, but got %d", argumentCount); TypeFunctionTypeId self = getTypeUserData(L, 1); - auto tfct = get(self); + auto tfct = get(self); if (!tfct) luaL_error(L, "type.parent: expected self to be a class, but got %s instead", getTag(L, self).c_str()); @@ -1242,7 +1242,7 @@ static int getProps(lua_State* L) return 1; } - if (auto tfct = get(self)) + if (auto tfct = get(self)) { lua_createtable(L, int(tfct->props.size()), 0); for (auto& [name, prop] : tfct->props) @@ -1305,7 +1305,7 @@ static int getIndexer(lua_State* L) return 1; } - if (auto tfct = get(self)) + if (auto tfct = get(self)) { // if the indexer does not exist, we should return nil if (!tfct->indexer.has_value()) @@ -1353,7 +1353,7 @@ static int getReadIndexer(lua_State* L) return 1; } - if (auto tfct = get(self)) + if (auto tfct = get(self)) { // if the indexer does not exist, we should return nil if (!tfct->indexer.has_value()) @@ -1399,7 +1399,7 @@ static int getWriteIndexer(lua_State* L) return 1; } - if (auto tfct = get(self)) + if (auto tfct = get(self)) { // if the indexer does not exist, we should return nil if (!tfct->indexer.has_value()) @@ -1439,7 +1439,7 @@ static int getMetatable(lua_State* L) return 1; } - if (auto tfct = get(self)) + if (auto tfct = get(self)) { // if the metatable does not exist, we should return nil if (!tfct->metatable.has_value()) @@ -1593,7 +1593,7 @@ void registerTypeUserData(lua_State* L) // Union and Intersection type methods {"components", getComponents}, - // Class type methods + // Extern type methods {FFlag::LuauTypeFunReadWriteParents ? "readparent" : "parent", FFlag::LuauTypeFunReadWriteParents ? getReadParent : getClassParent_DEPRECATED}, // Function type methods (cont.) @@ -1604,7 +1604,7 @@ void registerTypeUserData(lua_State* L) {"name", getGenericName}, {"ispack", getGenericIsPack}, - // move this under Class type methods when removing FFlagLuauTypeFunReadWriteParents + // move this under extern type methods when removing FFlagLuauTypeFunReadWriteParents {FFlag::LuauTypeFunReadWriteParents ? "writeparent" : nullptr, FFlag::LuauTypeFunReadWriteParents ? getWriteParent : nullptr}, {nullptr, nullptr} @@ -1903,12 +1903,12 @@ bool areEqual(SeenSet& seen, const TypeFunctionFunctionType& lhs, const TypeFunc return true; } -bool areEqual(SeenSet& seen, const TypeFunctionClassType& lhs, const TypeFunctionClassType& rhs) +bool areEqual(SeenSet& seen, const TypeFunctionExternType& lhs, const TypeFunctionExternType& rhs) { if (seenSetContains(seen, &lhs, &rhs)) return true; - return lhs.classTy == rhs.classTy; + return lhs.externTy == rhs.externTy; } bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType& rhs) @@ -1976,8 +1976,8 @@ bool areEqual(SeenSet& seen, const TypeFunctionType& lhs, const TypeFunctionType } { - const TypeFunctionClassType* lf = get(&lhs); - const TypeFunctionClassType* rf = get(&rhs); + const TypeFunctionExternType* lf = get(&lhs); + const TypeFunctionExternType* rf = get(&rhs); if (lf && rf) return areEqual(seen, *lf, *rf); } @@ -2266,7 +2266,7 @@ private: TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } - else if (auto c = get(ty)) + else if (auto c = get(ty)) target = ty; // Don't copy a class since they are immutable else if (auto g = get(ty)) target = typeFunctionRuntime->typeArena.allocate(TypeFunctionGenericType{g->isNamed, g->isPack, g->name}); @@ -2321,7 +2321,7 @@ private: cloneChildren(t1, t2); else if (auto [f1, f2] = std::tuple{getMutable(ty), getMutable(tfti)}; f1 && f2) cloneChildren(f1, f2); - else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) + else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) cloneChildren(c1, c2); else if (auto [g1, g2] = std::tuple{getMutable(ty), getMutable(tfti)}; g1 && g2) cloneChildren(g1, g2); @@ -2431,7 +2431,7 @@ private: f2->retTypes = shallowClone(f1->retTypes); } - void cloneChildren(TypeFunctionClassType* c1, TypeFunctionClassType* c2) + void cloneChildren(TypeFunctionExternType* c1, TypeFunctionExternType* c2) { // noop. } diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index 3aa6f70c..a0694984 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -206,12 +206,12 @@ private: TypeFunctionTypePackId emptyTypePack = typeFunctionRuntime->typePackArena.allocate(TypeFunctionTypePack{}); target = typeFunctionRuntime->typeArena.allocate(TypeFunctionFunctionType{{}, {}, emptyTypePack, emptyTypePack}); } - else if (auto c = get(ty)) + else if (auto c = get(ty)) { // Since there aren't any new class types being created in type functions, we will deserialize by using a direct reference to the original // class target = typeFunctionRuntime->typeArena.allocate( - TypeFunctionClassType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty} + TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty} ); } else if (auto g = get(ty)) @@ -291,7 +291,7 @@ private: serializeChildren(m1, m2); else if (auto [f1, f2] = std::tuple{get(ty), getMutable(tfti)}; f1 && f2) serializeChildren(f1, f2); - else if (auto [c1, c2] = std::tuple{get(ty), getMutable(tfti)}; c1 && c2) + else if (auto [c1, c2] = std::tuple{get(ty), getMutable(tfti)}; c1 && c2) serializeChildren(c1, c2); else if (auto [g1, g2] = std::tuple{get(ty), getMutable(tfti)}; g1 && g2) serializeChildren(g1, g2); @@ -411,7 +411,7 @@ private: f2->retTypes = shallowSerialize(f1->retTypes); } - void serializeChildren(const ClassType* c1, TypeFunctionClassType* c2) + void serializeChildren(const ExternType* c1, TypeFunctionExternType* c2) { for (const auto& [k, p] : c1->props) { @@ -702,9 +702,9 @@ private: TypePackId emptyTypePack = state->ctx->arena->addTypePack(TypePack{}); target = state->ctx->arena->addType(FunctionType{emptyTypePack, emptyTypePack, {}, false}); } - else if (auto c = get(ty)) + else if (auto c = get(ty)) { - target = c->classTy; + target = c->externTy; } else if (auto g = get(ty)) { @@ -811,7 +811,7 @@ private: deserializeChildren(m2, m1); else if (auto [f1, f2] = std::tuple{getMutable(ty), getMutable(tfti)}; f1 && f2) deserializeChildren(f2, f1); - else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) + else if (auto [c1, c2] = std::tuple{getMutable(ty), getMutable(tfti)}; c1 && c2) deserializeChildren(c2, c1); else if (auto [g1, g2] = std::tuple{getMutable(ty), getMutable(tfti)}; g1 && g2) deserializeChildren(g2, g1); @@ -972,7 +972,7 @@ private: f1->retTypes = shallowDeserialize(f2->retTypes); } - void deserializeChildren(TypeFunctionClassType* c2, ClassType* c1) + void deserializeChildren(TypeFunctionExternType* c2, ExternType* c1) { // noop. } diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index d8334195..922ecc66 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -32,8 +32,9 @@ LUAU_FASTINTVARIABLE(LuauVisitRecursionLimit, 500) LUAU_FASTFLAG(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) + LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauStatForInFix) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) @@ -44,7 +45,7 @@ namespace Luau static bool typeCouldHaveMetatable(TypeId ty) { - return get(follow(ty)) || get(follow(ty)) || get(follow(ty)); + return get(follow(ty)) || get(follow(ty)) || get(follow(ty)); } static void defaultLuauPrintLine(const std::string& s) @@ -318,7 +319,7 @@ ModulePtr TypeChecker::checkWithoutRecursionCheck(const SourceModule& module, Mo unifierState.skipCacheForType.clear(); duplicateTypeAliases.clear(); - incorrectClassDefinitions.clear(); + incorrectExternTypeDefinitions.clear(); return std::move(currentModule); } @@ -383,7 +384,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStat& program) } else if (auto global = program.as()) return check(scope, *global); - else if (auto global = program.as()) + else if (auto global = program.as()) return check(scope, *global); else if (auto errorStatement = program.as()) { @@ -498,9 +499,9 @@ ControlFlow TypeChecker::checkBlockWithoutRecursionCheck(const ScopePtr& scope, prototype(scope, *typealias, subLevel); ++subLevel; } - else if (const auto& declaredClass = stat->as()) + else if (const auto& declaredExternType = stat->as()) { - prototype(scope, *declaredClass); + prototype(scope, *declaredExternType); } } @@ -788,7 +789,7 @@ struct Demoter : Substitution bool ignoreChildren(TypeId ty) override { - if (get(ty)) + if (get(ty)) return true; return false; @@ -1686,82 +1687,82 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea } } -void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) +void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType) { - std::optional superTy = std::make_optional(builtinTypes->classType); - if (declaredClass.superName) + std::optional superTy = std::make_optional(builtinTypes->externType); + if (declaredExternType.superName) { - Name superName = Name(declaredClass.superName->value); + Name superName = Name(declaredExternType.superName->value); std::optional lookupType = scope->lookupType(superName); if (!lookupType) { - reportError(declaredClass.location, UnknownSymbol{superName, UnknownSymbol::Type}); - incorrectClassDefinitions.insert(&declaredClass); + reportError(declaredExternType.location, UnknownSymbol{superName, UnknownSymbol::Type}); + incorrectExternTypeDefinitions.insert(&declaredExternType); return; } - // We don't have generic classes, so this assertion _should_ never be hit. + // We don't have generic extern types, so this assertion _should_ never be hit. LUAU_ASSERT(lookupType->typeParams.size() == 0 && lookupType->typePackParams.size() == 0); superTy = lookupType->type; - if (!get(follow(*superTy))) + if (!get(follow(*superTy))) { reportError( - declaredClass.location, - GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredClass.name.value)} + declaredExternType.location, + GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType.name.value)} ); - incorrectClassDefinitions.insert(&declaredClass); + incorrectExternTypeDefinitions.insert(&declaredExternType); return; } } - Name className(declaredClass.name.value); + Name className(declaredExternType.name.value); - TypeId classTy = addType(ClassType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredClass.location)); - ClassType* ctv = getMutable(classTy); + TypeId classTy = addType(ExternType(className, {}, superTy, std::nullopt, {}, {}, currentModule->name, declaredExternType.location)); + ExternType* etv = getMutable(classTy); TypeId metaTy = addType(TableType{TableState::Sealed, scope->level}); - ctv->metatable = metaTy; + etv->metatable = metaTy; if (FFlag::LuauRetainDefinitionAliasLocations) - scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredClass.location}; + scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location}; else scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; } -ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& declaredClass) +ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType) { - Name className(declaredClass.name.value); + Name className(declaredExternType.name.value); // Don't bother checking if the class definition was incorrect - if (incorrectClassDefinitions.find(&declaredClass)) + if (incorrectExternTypeDefinitions.find(&declaredExternType)) return ControlFlow::None; std::optional binding; if (auto it = scope->exportedTypeBindings.find(className); it != scope->exportedTypeBindings.end()) binding = it->second; - // This class definition must have been `prototype()`d first. + // This extern type definition must have been `prototype()`d first. if (!binding) - ice("Class not predeclared"); + ice("Extern type not predeclared"); - TypeId classTy = binding->type; - ClassType* ctv = getMutable(classTy); + TypeId externTy = binding->type; + ExternType* etv = getMutable(externTy); - if (!ctv->metatable) - ice("No metatable for declared class"); + if (!etv->metatable) + ice("No metatable for declared extern type"); - if (const auto& indexer = declaredClass.indexer) - ctv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType)); + if (const auto& indexer = declaredExternType.indexer) + etv->indexer = TableIndexer(resolveType(scope, *indexer->indexType), resolveType(scope, *indexer->resultType)); - TableType* metatable = getMutable(*ctv->metatable); - for (const AstDeclaredClassProp& prop : declaredClass.props) + TableType* metatable = getMutable(*etv->metatable); + for (const AstDeclaredExternTypeProperty& prop : declaredExternType.props) { Name propName(prop.name.value); TypeId propTy = resolveType(scope, *prop.ty); bool assignToMetatable = isMetamethod(propName); - Luau::ClassType::Props& assignTo = assignToMetatable ? metatable->props : ctv->props; + Luau::ExternType::Props& assignTo = assignToMetatable ? metatable->props : etv->props; // Function types always take 'self', but this isn't reflected in the // parsed annotation. Add it here. @@ -1770,7 +1771,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& if (FunctionType* ftv = getMutable(propTy)) { ftv->argNames.insert(ftv->argNames.begin(), FunctionArgument{"self", {}}); - ftv->argTypes = addTypePack(TypePack{{classTy}, ftv->argTypes}); + ftv->argTypes = addTypePack(TypePack{{externTy}, ftv->argTypes}); ftv->hasSelf = true; FunctionDefinition defn; @@ -1813,7 +1814,7 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareClass& } else { - reportError(declaredClass.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); + reportError(declaredExternType.location, GenericError{format("Cannot overload non-function class member '%s'", propName.c_str())}); } } } @@ -1852,7 +1853,8 @@ ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareFuncti ); TypePackId argPack = resolveTypePack(funScope, global.params); - TypePackId retPack = resolveTypePack(funScope, global.retTypes); + TypePackId retPack = + FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funScope, *global.retTypes) : resolveTypePack(funScope, global.retTypes_DEPRECATED); FunctionDefinition defn; @@ -2137,9 +2139,9 @@ std::optional TypeChecker::getIndexTypeFromTypeImpl( if (auto found = findTablePropertyRespectingMeta(type, name, location, addErrors)) return *found; } - else if (const ClassType* cls = get(type)) + else if (const ExternType* cls = get(type)) { - const Property* prop = lookupClassProp(cls, name); + const Property* prop = lookupExternTypeProp(cls, name); if (prop) return prop->type(); @@ -3462,14 +3464,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex return errorRecoveryType(scope); } } - else if (const ClassType* lhsClass = get(lhs)) + else if (const ExternType* lhsExternType = get(lhs)) { - if (const Property* prop = lookupClassProp(lhsClass, name)) + if (const Property* prop = lookupExternTypeProp(lhsExternType, name)) { return prop->type(); } - if (auto indexer = lhsClass->indexer) + if (auto indexer = lhsExternType->indexer) { Unifier state = mkUnifier(scope, expr.location); state.tryUnify(stringType, indexer->indexType); @@ -3521,14 +3523,14 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex if (value) { - if (const ClassType* exprClass = get(exprType)) + if (const ExternType* exprExternType = get(exprType)) { - if (const Property* prop = lookupClassProp(exprClass, value->value.data)) + if (const Property* prop = lookupExternTypeProp(exprExternType, value->value.data)) { return prop->type(); } - if (auto indexer = exprClass->indexer) + if (auto indexer = exprExternType->indexer) { unify(stringType, indexer->indexType, scope, expr.index->location); return indexer->indexResultType; @@ -3554,20 +3556,20 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex } else { - if (const ClassType* exprClass = get(exprType)) + if (const ExternType* exprExternType = get(exprType)) { - if (auto indexer = exprClass->indexer) + if (auto indexer = exprExternType->indexer) { unify(indexType, indexer->indexType, scope, expr.index->location); return indexer->indexResultType; } } - if (const ClassType* exprClass = get(exprType)) + if (const ExternType* exprExternType = get(exprType)) { if (isNonstrictMode()) return unknownType; - reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}}); + reportError(TypeError{expr.location, DynamicPropertyLookupOnExternTypesUnsafe{exprType}}); return errorRecoveryType(scope); } } @@ -3848,8 +3850,10 @@ std::pair TypeChecker::checkFunctionSignature( auto [generics, genericPacks] = createGenericTypes(funScope, std::nullopt, expr, expr.generics, expr.genericPacks); TypePackId retPack; - if (expr.returnAnnotation) + if (FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation) retPack = resolveTypePack(funScope, *expr.returnAnnotation); + else if (!FFlag::LuauStoreReturnTypesAsPackOnAst && expr.returnAnnotation_DEPRECATED) + retPack = resolveTypePack(funScope, *expr.returnAnnotation_DEPRECATED); else if (isNonstrictMode()) retPack = anyTypePack; else if (expectedFunctionType && expectedFunctionType->generics.empty() && expectedFunctionType->genericPacks.empty()) @@ -4056,7 +4060,8 @@ void TypeChecker::checkFunctionBody(const ScopePtr& scope, TypeId ty, const AstE // If we're in nonstrict mode we want to only report this missing return // statement if there are type annotations on the function. In strict mode // we report it regardless. - if (!isNonstrictMode() || function.returnAnnotation) + if (!isNonstrictMode() || + (FFlag::LuauStoreReturnTypesAsPackOnAst ? function.returnAnnotation != nullptr : function.returnAnnotation_DEPRECATED.has_value())) { reportError(getEndLocation(function), FunctionExitsWithoutReturning{funTy->retTypes}); } @@ -4611,9 +4616,9 @@ std::unique_ptr> TypeChecker::checkCallOverload( { callTy = getIndexTypeFromType(scope, mttv->metatable, "__call", expr.func->location, /* addErrors= */ false); } - else if (const ClassType* ctv = get(fn); ctv && ctv->metatable) + else if (const ExternType* etv = get(fn); etv && etv->metatable) { - callTy = getIndexTypeFromType(scope, *ctv->metatable, "__call", expr.func->location, /* addErrors= */ false); + callTy = getIndexTypeFromType(scope, *etv->metatable, "__call", expr.func->location, /* addErrors= */ false); } if (callTy) @@ -5316,17 +5321,17 @@ void TypeChecker::diagnoseMissingTableKey(UnknownProperty* utk, TypeErrorData& d if (auto ttv = getTableType(utk->table)) accumulate(ttv->props); - else if (auto ctv = get(follow(utk->table))) + else if (auto etv = get(follow(utk->table))) { - while (ctv) + while (etv) { - accumulate(ctv->props); + accumulate(etv->props); - if (!ctv->parent) + if (!etv->parent) break; - ctv = get(*ctv->parent); - LUAU_ASSERT(ctv); + etv = get(*etv->parent); + LUAU_ASSERT(etv); } } @@ -5803,7 +5808,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno auto [generics, genericPacks] = createGenericTypes(funcScope, std::nullopt, annotation, func->generics, func->genericPacks); TypePackId argTypes = resolveTypePack(funcScope, func->argTypes); - TypePackId retTypes = resolveTypePack(funcScope, func->returnTypes); + TypePackId retTypes = FFlag::LuauStoreReturnTypesAsPackOnAst ? resolveTypePack(funcScope, *func->returnTypes) + : resolveTypePack(funcScope, func->returnTypes_DEPRECATED); std::vector genericTys; genericTys.reserve(generics.size()); @@ -5855,12 +5861,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno } else if (const auto& un = annotation.as()) { - if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) - { - if (un->types.size == 1) - return resolveType(scope, *un->types.data[0]); - } - + if (un->types.size == 1) + return resolveType(scope, *un->types.data[0]); std::vector types; for (AstType* ann : un->types) types.push_back(resolveType(scope, *ann)); @@ -5869,12 +5871,8 @@ TypeId TypeChecker::resolveTypeWorker(const ScopePtr& scope, const AstType& anno } else if (const auto& un = annotation.as()) { - if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) - { - if (un->types.size == 1) - return resolveType(scope, *un->types.data[0]); - } - + if (un->types.size == 1) + return resolveType(scope, *un->types.data[0]); std::vector types; for (AstType* ann : un->types) types.push_back(resolveType(scope, *ann)); @@ -6481,7 +6479,7 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r return refine( [](TypeId ty) -> bool { - return get(ty); + return get(ty); } ); } @@ -6496,13 +6494,13 @@ void TypeChecker::resolve(const TypeGuardPredicate& typeguardP, RefinementMap& r TypeId type = follow(typeFun->type); // You cannot refine to the top class type. - if (type == builtinTypes->classType) + if (type == builtinTypes->externType) { return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); } - // We're only interested in the root class of any classes. - if (auto ctv = get(type); !ctv || (ctv->parent != builtinTypes->classType && !hasTag(type, kTypeofRootTag))) + // We're only interested in the root type of any extern type. + if (auto etv = get(type); !etv || (etv->parent != builtinTypes->externType && !hasTag(type, kTypeofRootTag))) return addRefinement(refis, typeguardP.lvalue, errorRecoveryType(scope)); // This probably hints at breaking out type filtering functions from the predicate solver so that typeof is not tightly coupled with IsA. diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp index 32cc3f57..74611857 100644 --- a/Analysis/src/TypePath.cpp +++ b/Analysis/src/TypePath.cpp @@ -307,9 +307,9 @@ struct TraversalState prop = &it->second; } } - else if (auto c = get(*currentType)) + else if (auto c = get(*currentType)) { - prop = lookupClassProp(c, property.name); + prop = lookupExternTypeProp(c, property.name); } // For a metatable type, the table takes priority; check that before // falling through to the metatable entry below. @@ -461,7 +461,7 @@ struct TraversalState indexer = &(*mtMt->indexer); } // Note: we don't appear to walk the class hierarchy for indexers - else if (auto ct = get(current); ct && ct->indexer) + else if (auto ct = get(current); ct && ct->indexer) indexer = &(*ct->indexer); if (indexer) diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 04f7679a..88fda762 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -11,8 +11,6 @@ #include LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete); -LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope); LUAU_FASTFLAG(LuauFreeTypesMustHaveBounds) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) @@ -327,8 +325,7 @@ TypePack extendTypePack( { FreeType ft{ftp->scope, builtinTypes->neverType, builtinTypes->unknownType, ftp->polarity}; t = arena.addType(ft); - if (FFlag::LuauTrackInteriorFreeTypesOnScope) - trackInteriorFreeType(ftp->scope, t); + trackInteriorFreeType(ftp->scope, t); } else t = FFlag::LuauFreeTypesMustHaveBounds ? arena.freshType(builtinTypes, ftp->scope) : arena.freshType_DEPRECATED(ftp->scope); @@ -438,7 +435,6 @@ TypeId stripNil(NotNull builtinTypes, TypeArena& arena, TypeId ty) ErrorSuppression shouldSuppressErrors(NotNull normalizer, TypeId ty) { - LUAU_ASSERT(FFlag::LuauSolverV2 || FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete); std::shared_ptr normType = normalizer->normalize(ty); if (!normType) @@ -556,10 +552,8 @@ std::vector findBlockedArgTypesIn(AstExprCall* expr, NotNullparent.get()) { if (scope->interiorFreeTypes) diff --git a/Analysis/src/Unifier.cpp b/Analysis/src/Unifier.cpp index 47b7cc41..27356870 100644 --- a/Analysis/src/Unifier.cpp +++ b/Analysis/src/Unifier.cpp @@ -292,7 +292,7 @@ TypePackId Widen::clean(TypePackId) bool Widen::ignoreChildren(TypeId ty) { - if (get(ty)) + if (get(ty)) return true; return !log->is(ty); @@ -693,13 +693,13 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool else if (log.getMutable(subTy)) tryUnifyWithMetatable(superTy, subTy, /*reversed*/ true); - else if (log.getMutable(superTy)) - tryUnifyWithClass(subTy, superTy, /*reversed*/ false); + else if (log.getMutable(superTy)) + tryUnifyWithExternType(subTy, superTy, /*reversed*/ false); - // Unification of nonclasses with classes is almost, but not quite symmetrical. - // The order in which we perform this test is significant in the case that both types are classes. - else if (log.getMutable(subTy)) - tryUnifyWithClass(subTy, superTy, /*reversed*/ true); + // Unification of Luau types with extern types is almost, but not quite symmetrical. + // The order in which we perform this test is significant in the case that both types are extern types. + else if (log.getMutable(subTy)) + tryUnifyWithExternType(subTy, superTy, /*reversed*/ true); else if (log.get(superTy) || log.get(subTy)) tryUnifyNegations(subTy, superTy); @@ -1107,15 +1107,15 @@ void Unifier::tryUnifyNormalizedTypes( if (!get(superNorm.errors)) return reportError(location, TypeMismatch{superTy, subTy, reason, error, mismatchContext()}); - for (const auto& [subClass, _] : subNorm.classes.classes) + for (const auto& [subExternType, _] : subNorm.externTypes.externTypes) { bool found = false; - const ClassType* subCtv = get(subClass); + const ExternType* subCtv = get(subExternType); LUAU_ASSERT(subCtv); - for (const auto& [superClass, superNegations] : superNorm.classes.classes) + for (const auto& [superExternType, superNegations] : superNorm.externTypes.externTypes) { - const ClassType* superCtv = get(superClass); + const ExternType* superCtv = get(superExternType); LUAU_ASSERT(superCtv); if (isSubclass(subCtv, superCtv)) @@ -1124,7 +1124,7 @@ void Unifier::tryUnifyNormalizedTypes( for (TypeId negation : superNegations) { - const ClassType* negationCtv = get(negation); + const ExternType* negationCtv = get(negation); LUAU_ASSERT(negationCtv); if (isSubclass(subCtv, negationCtv)) @@ -2382,8 +2382,8 @@ void Unifier::tryUnifyWithMetatable(TypeId subTy, TypeId superTy, bool reversed) } } -// Class unification is almost, but not quite symmetrical. We use the 'reversed' boolean to indicate which scenario we are evaluating. -void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed) +// Extern type unification is almost, but not quite symmetrical. We use the 'reversed' boolean to indicate which scenario we are evaluating. +void Unifier::tryUnifyWithExternType(TypeId subTy, TypeId superTy, bool reversed) { if (reversed) std::swap(superTy, subTy); @@ -2396,20 +2396,20 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed) reportError(location, TypeMismatch{subTy, superTy, mismatchContext()}); }; - const ClassType* superClass = get(superTy); - if (!superClass) - ice("tryUnifyClass invoked with non-class Type"); + const ExternType* superExternType = get(superTy); + if (!superExternType) + ice("tryUnifyExternType invoked with non-class Type"); - if (const ClassType* subClass = get(subTy)) + if (const ExternType* subExternType = get(subTy)) { switch (variance) { case Covariant: - if (!isSubclass(subClass, superClass)) + if (!isSubclass(subExternType, superExternType)) return fail(); return; case Invariant: - if (subClass != superClass) + if (subExternType != superExternType) return fail(); return; } @@ -2434,7 +2434,7 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed) for (const auto& [propName, prop] : subTable->props) { - const Property* classProp = lookupClassProp(superClass, propName); + const Property* classProp = lookupExternTypeProp(superExternType, propName); if (!classProp) { ok = false; @@ -2462,7 +2462,7 @@ void Unifier::tryUnifyWithClass(TypeId subTy, TypeId superTy, bool reversed) if (subTable->indexer) { ok = false; - std::string msg = "Class " + superClass->name + " does not have an indexer"; + std::string msg = "Extern type " + superExternType->name + " does not have an indexer"; reportError(location, GenericError{msg}); } @@ -2635,9 +2635,9 @@ static void tryUnifyWithAny( queue.push_back(mt->table); queue.push_back(mt->metatable); } - else if (state.log.getMutable(ty)) + else if (state.log.getMutable(ty)) { - // ClassTypes never contain free types. + // ExternTypes never contain free types. } else if (auto union_ = state.log.getMutable(ty)) queue.insert(queue.end(), union_->options.begin(), union_->options.end()); @@ -2654,7 +2654,7 @@ void Unifier::tryUnifyWithAny(TypeId subTy, TypeId anyTy) LUAU_ASSERT(get(anyTy) || get(anyTy) || get(anyTy) || get(anyTy)); // These types are not visited in general loop below - if (log.get(subTy) || log.get(subTy) || log.get(subTy)) + if (log.get(subTy) || log.get(subTy) || log.get(subTy)) return; TypePackId anyTp = types->addTypePack(TypePackVar{VariadicTypePack{anyTy}}); diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index b3a0a7ad..467deb75 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -321,9 +321,9 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) { for (TypeId generic : subFn->generics) { - const GenericType* gen = get(generic); - LUAU_ASSERT(gen); - genericSubstitutions[generic] = freshType(scope, gen->polarity); + const GenericType* gen = get(follow(generic)); + if (gen) + genericSubstitutions[generic] = freshType(scope, gen->polarity); } for (TypePackId genericPack : subFn->genericPacks) @@ -331,8 +331,8 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) if (FFlag::LuauNonReentrantGeneralization2) { const GenericTypePack* gen = get(follow(genericPack)); - LUAU_ASSERT(gen); - genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); + if (gen) + genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); } else genericPackSubstitutions[genericPack] = arena->freshTypePack(scope); diff --git a/Ast/include/Luau/Ast.h b/Ast/include/Luau/Ast.h index d0e1db43..2e7ca6b7 100644 --- a/Ast/include/Luau/Ast.h +++ b/Ast/include/Luau/Ast.h @@ -446,7 +446,25 @@ public: AstStatBlock* body, size_t functionDepth, const AstName& debugname, - const std::optional& returnAnnotation = {}, + AstTypePack* returnAnnotation, + AstTypePack* varargAnnotation = nullptr, + const std::optional& argLocation = std::nullopt + ); + + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst + AstExprFunction( + const Location& location, + const AstArray& attributes, + const AstArray& generics, + const AstArray& genericPacks, + AstLocal* self, + const AstArray& args, + bool vararg, + const Location& varargLocation, + AstStatBlock* body, + size_t functionDepth, + const AstName& debugname, + const std::optional& returnAnnotation, AstTypePack* varargAnnotation = nullptr, const std::optional& argLocation = std::nullopt ); @@ -461,7 +479,9 @@ public: AstArray genericPacks; AstLocal* self; AstArray args; - std::optional returnAnnotation; + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst + std::optional returnAnnotation_DEPRECATED; + AstTypePack* returnAnnotation = nullptr; bool vararg = false; Location varargLocation; AstTypePack* varargAnnotation; @@ -929,6 +949,36 @@ class AstStatDeclareFunction : public AstStat public: LUAU_RTTI(AstStatDeclareFunction) + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst + AstStatDeclareFunction( + const Location& location, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& params, + const AstArray& paramNames, + bool vararg, + const Location& varargLocation, + AstTypePack* retTypes + ); + + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst + AstStatDeclareFunction( + const Location& location, + const AstArray& attributes, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& params, + const AstArray& paramNames, + bool vararg, + const Location& varargLocation, + AstTypePack* retTypes + ); + + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst AstStatDeclareFunction( const Location& location, const AstName& name, @@ -942,6 +992,7 @@ public: const AstTypeList& retTypes ); + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst AstStatDeclareFunction( const Location& location, const AstArray& attributes, @@ -971,10 +1022,12 @@ public: AstArray paramNames; bool vararg = false; Location varargLocation; - AstTypeList retTypes; + AstTypePack* retTypes; + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst + AstTypeList retTypes_DEPRECATED; }; -struct AstDeclaredClassProp +struct AstDeclaredExternTypeProperty { AstName name; Location nameLocation; @@ -1000,16 +1053,16 @@ struct AstTableIndexer std::optional accessLocation; }; -class AstStatDeclareClass : public AstStat +class AstStatDeclareExternType : public AstStat { public: - LUAU_RTTI(AstStatDeclareClass) + LUAU_RTTI(AstStatDeclareExternType) - AstStatDeclareClass( + AstStatDeclareExternType( const Location& location, const AstName& name, std::optional superName, - const AstArray& props, + const AstArray& props, AstTableIndexer* indexer = nullptr ); @@ -1018,7 +1071,7 @@ public: AstName name; std::optional superName; - AstArray props; + AstArray props; AstTableIndexer* indexer; }; @@ -1095,6 +1148,26 @@ class AstTypeFunction : public AstType public: LUAU_RTTI(AstTypeFunction) + AstTypeFunction( + const Location& location, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& argTypes, + const AstArray>& argNames, + AstTypePack* returnTypes + ); + + AstTypeFunction( + const Location& location, + const AstArray& attributes, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& argTypes, + const AstArray>& argNames, + AstTypePack* returnTypes + ); + + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst AstTypeFunction( const Location& location, const AstArray& generics, @@ -1104,6 +1177,7 @@ public: const AstTypeList& returnTypes ); + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst AstTypeFunction( const Location& location, const AstArray& attributes, @@ -1124,7 +1198,9 @@ public: AstArray genericPacks; AstTypeList argTypes; AstArray> argNames; - AstTypeList returnTypes; + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst + AstTypeList returnTypes_DEPRECATED; + AstTypePack* returnTypes; }; class AstTypeTypeof : public AstType @@ -1483,7 +1559,7 @@ public: { return visit(static_cast(node)); } - virtual bool visit(class AstStatDeclareClass* node) + virtual bool visit(class AstStatDeclareExternType* node) { return visit(static_cast(node)); } diff --git a/Ast/include/Luau/Cst.h b/Ast/include/Luau/Cst.h index 3c006eb0..70148c50 100644 --- a/Ast/include/Luau/Cst.h +++ b/Ast/include/Luau/Cst.h @@ -473,8 +473,10 @@ class CstTypePackExplicit : public CstNode public: LUAU_CST_RTTI(CstTypePackExplicit) - CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray commaPositions); + explicit CstTypePackExplicit(); + explicit CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray commaPositions); + bool hasParentheses; Position openParenthesesPosition; Position closeParenthesesPosition; AstArray commaPositions; diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 312f5a87..a4ba540b 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -157,8 +157,8 @@ private: // type function Name ... end AstStat* parseTypeFunction(const Location& start, bool exported, Position typeKeywordPosition); - AstDeclaredClassProp parseDeclaredClassMethod(const AstArray& attributes); - AstDeclaredClassProp parseDeclaredClassMethod_DEPRECATED(); + AstDeclaredExternTypeProperty parseDeclaredExternTypeMethod(const AstArray& attributes); + AstDeclaredExternTypeProperty parseDeclaredExternTypeMethod_DEPRECATED(); // `declare global' Name: Type | @@ -182,6 +182,14 @@ private: const Name* localName, const AstArray& attributes ); + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst + std::pair parseFunctionBody_DEPRECATED( + bool hasself, + const Lexeme& matchFunction, + const AstName& debugname, + const Name* localName, + const AstArray& attributes + ); // explist ::= {exp `,'} exp void parseExprList(TempVector& result, TempVector* commaPositions = nullptr); @@ -219,8 +227,12 @@ private: TempVector>* nameColonPositions = nullptr ); - std::optional parseOptionalReturnType(Position* returnSpecifierPosition = nullptr); - std::pair parseReturnType(); + AstTypePack* parseOptionalReturnType(Position* returnSpecifierPosition = nullptr); + AstTypePack* parseReturnType(); + + // Clip with FFlagLuauStoreReturnTypesAsPackOnAst + std::optional parseOptionalReturnType_DEPRECATED(Position* returnSpecifierPosition = nullptr); + std::pair parseReturnType_DEPRECATED(); struct TableIndexerResult { @@ -491,7 +503,7 @@ private: std::vector scratchCstTableTypeProps; std::vector scratchType; std::vector scratchTypeOrPack; - std::vector scratchDeclaredClassProps; + std::vector scratchDeclaredClassProps; std::vector scratchItem; std::vector scratchCstItem; std::vector scratchArgName; diff --git a/Ast/src/Ast.cpp b/Ast/src/Ast.cpp index 37287162..7ded45c9 100644 --- a/Ast/src/Ast.cpp +++ b/Ast/src/Ast.cpp @@ -4,6 +4,7 @@ #include "Luau/Common.h" LUAU_FASTFLAG(LuauDeprecatedAttribute); +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) namespace Luau { @@ -241,7 +242,7 @@ AstExprFunction::AstExprFunction( AstStatBlock* body, size_t functionDepth, const AstName& debugname, - const std::optional& returnAnnotation, + AstTypePack* returnAnnotation, AstTypePack* varargAnnotation, const std::optional& argLocation ) @@ -260,6 +261,41 @@ AstExprFunction::AstExprFunction( , debugname(debugname) , argLocation(argLocation) { + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); +} + +AstExprFunction::AstExprFunction( + const Location& location, + const AstArray& attributes, + const AstArray& generics, + const AstArray& genericPacks, + AstLocal* self, + const AstArray& args, + bool vararg, + const Location& varargLocation, + AstStatBlock* body, + size_t functionDepth, + const AstName& debugname, + const std::optional& returnAnnotation, + AstTypePack* varargAnnotation, + const std::optional& argLocation +) + : AstExpr(ClassIndex(), location) + , attributes(attributes) + , generics(generics) + , genericPacks(genericPacks) + , self(self) + , args(args) + , returnAnnotation_DEPRECATED(returnAnnotation) + , vararg(vararg) + , varargLocation(varargLocation) + , varargAnnotation(varargAnnotation) + , body(body) + , functionDepth(functionDepth) + , debugname(debugname) + , argLocation(argLocation) +{ + LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst); } void AstExprFunction::visit(AstVisitor* visitor) @@ -275,8 +311,16 @@ void AstExprFunction::visit(AstVisitor* visitor) if (varargAnnotation) varargAnnotation->visit(visitor); - if (returnAnnotation) - visitTypeList(visitor, *returnAnnotation); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + if (returnAnnotation) + returnAnnotation->visit(visitor); + } + else + { + if (returnAnnotation_DEPRECATED) + visitTypeList(visitor, *returnAnnotation_DEPRECATED); + } body->visit(visitor); } @@ -855,7 +899,7 @@ AstStatDeclareFunction::AstStatDeclareFunction( const AstArray& paramNames, bool vararg, const Location& varargLocation, - const AstTypeList& retTypes + AstTypePack* retTypes ) : AstStat(ClassIndex(), location) , attributes() @@ -869,8 +913,66 @@ AstStatDeclareFunction::AstStatDeclareFunction( , varargLocation(varargLocation) , retTypes(retTypes) { + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); } +AstStatDeclareFunction::AstStatDeclareFunction( + const Location& location, + const AstArray& attributes, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& params, + const AstArray& paramNames, + bool vararg, + const Location& varargLocation, + AstTypePack* retTypes +) + : AstStat(ClassIndex(), location) + , attributes(attributes) + , name(name) + , nameLocation(nameLocation) + , generics(generics) + , genericPacks(genericPacks) + , params(params) + , paramNames(paramNames) + , vararg(vararg) + , varargLocation(varargLocation) + , retTypes(retTypes) +{ + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); +} + +// Clip with FFlagLuauStoreReturnTypesAsPackOnAst +AstStatDeclareFunction::AstStatDeclareFunction( + const Location& location, + const AstName& name, + const Location& nameLocation, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& params, + const AstArray& paramNames, + bool vararg, + const Location& varargLocation, + const AstTypeList& retTypes +) + : AstStat(ClassIndex(), location) + , attributes() + , name(name) + , nameLocation(nameLocation) + , generics(generics) + , genericPacks(genericPacks) + , params(params) + , paramNames(paramNames) + , vararg(vararg) + , varargLocation(varargLocation) + , retTypes_DEPRECATED(retTypes) +{ + LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst); +} + +// Clip with FFlagLuauStoreReturnTypesAsPackOnAst AstStatDeclareFunction::AstStatDeclareFunction( const Location& location, const AstArray& attributes, @@ -894,8 +996,9 @@ AstStatDeclareFunction::AstStatDeclareFunction( , paramNames(paramNames) , vararg(vararg) , varargLocation(varargLocation) - , retTypes(retTypes) + , retTypes_DEPRECATED(retTypes) { + LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst); } void AstStatDeclareFunction::visit(AstVisitor* visitor) @@ -903,7 +1006,10 @@ void AstStatDeclareFunction::visit(AstVisitor* visitor) if (visitor->visit(this)) { visitTypeList(visitor, params); - visitTypeList(visitor, retTypes); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + retTypes->visit(visitor); + else + visitTypeList(visitor, retTypes_DEPRECATED); } } @@ -925,11 +1031,11 @@ bool AstStatDeclareFunction::hasAttribute(AstAttr::Type attributeType) const return hasAttributeInArray(attributes, attributeType); } -AstStatDeclareClass::AstStatDeclareClass( +AstStatDeclareExternType::AstStatDeclareExternType( const Location& location, const AstName& name, std::optional superName, - const AstArray& props, + const AstArray& props, AstTableIndexer* indexer ) : AstStat(ClassIndex(), location) @@ -940,11 +1046,11 @@ AstStatDeclareClass::AstStatDeclareClass( { } -void AstStatDeclareClass::visit(AstVisitor* visitor) +void AstStatDeclareExternType::visit(AstVisitor* visitor) { if (visitor->visit(this)) { - for (const AstDeclaredClassProp& prop : props) + for (const AstDeclaredExternTypeProperty& prop : props) prop.ty->visit(visitor); } } @@ -1035,7 +1141,7 @@ AstTypeFunction::AstTypeFunction( const AstArray& genericPacks, const AstTypeList& argTypes, const AstArray>& argNames, - const AstTypeList& returnTypes + AstTypePack* returnTypes ) : AstType(ClassIndex(), location) , attributes() @@ -1045,9 +1151,53 @@ AstTypeFunction::AstTypeFunction( , argNames(argNames) , returnTypes(returnTypes) { + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size); } +AstTypeFunction::AstTypeFunction( + const Location& location, + const AstArray& attributes, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& argTypes, + const AstArray>& argNames, + AstTypePack* returnTypes +) + : AstType(ClassIndex(), location) + , attributes(attributes) + , generics(generics) + , genericPacks(genericPacks) + , argTypes(argTypes) + , argNames(argNames) + , returnTypes(returnTypes) +{ + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); + LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size); +} + +// Clip with FFlagLuauStoreReturnTypesAsPackOnAst +AstTypeFunction::AstTypeFunction( + const Location& location, + const AstArray& generics, + const AstArray& genericPacks, + const AstTypeList& argTypes, + const AstArray>& argNames, + const AstTypeList& returnTypes +) + : AstType(ClassIndex(), location) + , attributes() + , generics(generics) + , genericPacks(genericPacks) + , argTypes(argTypes) + , argNames(argNames) + , returnTypes_DEPRECATED(returnTypes) +{ + LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst); + LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size); +} + +// Clip with FFlagLuauStoreReturnTypesAsPackOnAst AstTypeFunction::AstTypeFunction( const Location& location, const AstArray& attributes, @@ -1063,8 +1213,9 @@ AstTypeFunction::AstTypeFunction( , genericPacks(genericPacks) , argTypes(argTypes) , argNames(argNames) - , returnTypes(returnTypes) + , returnTypes_DEPRECATED(returnTypes) { + LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst); LUAU_ASSERT(argNames.size == 0 || argNames.size == argTypes.types.size); } @@ -1073,7 +1224,10 @@ void AstTypeFunction::visit(AstVisitor* visitor) if (visitor->visit(this)) { visitTypeList(visitor, argTypes); - visitTypeList(visitor, returnTypes); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + returnTypes->visit(visitor); + else + visitTypeList(visitor, returnTypes_DEPRECATED); } } diff --git a/Ast/src/Cst.cpp b/Ast/src/Cst.cpp index ca2bd105..e374b189 100644 --- a/Ast/src/Cst.cpp +++ b/Ast/src/Cst.cpp @@ -252,8 +252,18 @@ CstTypeSingletonString::CstTypeSingletonString(AstArray sourceString, CstE LUAU_ASSERT(quoteStyle != CstExprConstantString::QuotedInterp); } +CstTypePackExplicit::CstTypePackExplicit() + : CstNode(CstClassIndex()) + , hasParentheses(false) + , openParenthesesPosition(Position{0, 0}) + , closeParenthesesPosition(Position{0, 0}) + , commaPositions({}) +{ +} + CstTypePackExplicit::CstTypePackExplicit(Position openParenthesesPosition, Position closeParenthesesPosition, AstArray commaPositions) : CstNode(CstClassIndex()) + , hasParentheses(true) , openParenthesesPosition(openParenthesesPosition) , closeParenthesesPosition(closeParenthesesPosition) , commaPositions(commaPositions) diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index 429e5d10..de15eee8 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -19,14 +19,14 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100) // See docs/SyntaxChanges.md for an explanation. LUAU_FASTFLAGVARIABLE(LuauSolverV2) LUAU_FASTFLAGVARIABLE(LuauStoreCSTData2) -LUAU_FASTFLAGVARIABLE(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAGVARIABLE(LuauAstTypeGroup3) -LUAU_FASTFLAGVARIABLE(ParserNoErrorLimit) LUAU_FASTFLAGVARIABLE(LuauFixDoBlockEndLocation) LUAU_FASTFLAGVARIABLE(LuauParseOptionalAsNode2) +LUAU_FASTFLAGVARIABLE(LuauDeclareExternType) LUAU_FASTFLAGVARIABLE(LuauParseStringIndexer) LUAU_FASTFLAGVARIABLE(LuauTypeFunResultInAutocomplete) LUAU_FASTFLAGVARIABLE(LuauDeprecatedAttribute) +LUAU_FASTFLAGVARIABLE(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAGVARIABLE(LuauFixFunctionWithAttributesStartLocation) LUAU_DYNAMIC_FASTFLAGVARIABLE(DebugLuauReportReturnTypeVariadicWithTypeSuffix, false) @@ -788,7 +788,9 @@ AstStat* Parser::parseFunctionStat(const AstArray& attributes) matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; - AstExprFunction* body = parseFunctionBody(hasself, matchFunction, debugname, nullptr, attributes).first; + AstExprFunction* body = FFlag::LuauStoreReturnTypesAsPackOnAst + ? parseFunctionBody(hasself, matchFunction, debugname, nullptr, attributes).first + : parseFunctionBody_DEPRECATED(hasself, matchFunction, debugname, nullptr, attributes).first; matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; @@ -941,7 +943,8 @@ AstStat* Parser::parseLocal(const AstArray& attributes) matchRecoveryStopOnToken[Lexeme::ReservedEnd]++; - auto [body, var] = parseFunctionBody(false, matchFunction, name.name, &name, attributes); + auto [body, var] = FFlag::LuauStoreReturnTypesAsPackOnAst ? parseFunctionBody(false, matchFunction, name.name, &name, attributes) + : parseFunctionBody_DEPRECATED(false, matchFunction, name.name, &name, attributes); matchRecoveryStopOnToken[Lexeme::ReservedEnd]--; @@ -1111,7 +1114,10 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio size_t oldTypeFunctionDepth = typeFunctionDepth; typeFunctionDepth = functionStack.size(); - AstExprFunction* body = parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray({nullptr, 0})).first; + AstExprFunction* body = + FFlag::LuauStoreReturnTypesAsPackOnAst + ? parseFunctionBody(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray({nullptr, 0})).first + : parseFunctionBody_DEPRECATED(/* hasself */ false, matchFn, fnName->name, nullptr, AstArray({nullptr, 0})).first; typeFunctionDepth = oldTypeFunctionDepth; @@ -1133,7 +1139,7 @@ AstStat* Parser::parseTypeFunction(const Location& start, bool exported, Positio } } -AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray& attributes) +AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod(const AstArray& attributes) { LUAU_ASSERT(FFlag::LuauDeprecatedAttribute); @@ -1164,7 +1170,18 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray& expectMatchAndConsume(')', matchParen); - AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy(nullptr, 0), nullptr}); + AstTypePack* retTypes; + AstTypeList retTypes_DEPRECATED; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + retTypes = parseOptionalReturnType(); + if (!retTypes) + retTypes = allocator.alloc(lexer.current().location, AstTypeList{copy(nullptr, 0), nullptr}); + } + else + { + retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy(nullptr, 0), nullptr}); + } Location end = lexer.previousLocation(); TempVector vars(scratchType); @@ -1172,7 +1189,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray& if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) { - return AstDeclaredClassProp{ + return AstDeclaredExternTypeProperty{ fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true }; } @@ -1191,14 +1208,25 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod(const AstArray& if (vararg && !varargAnnotation) report(start, "All declaration parameters aside from 'self' must be annotated"); - AstType* fnType = allocator.alloc( - Location(start, end), attributes, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes - ); + AstType* fnType = + FFlag::LuauStoreReturnTypesAsPackOnAst + ? allocator.alloc( + Location(start, end), attributes, generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes + ) + : allocator.alloc( + Location(start, end), + attributes, + generics, + genericPacks, + AstTypeList{copy(vars), varargAnnotation}, + copy(varNames), + retTypes_DEPRECATED + ); - return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)}; + return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)}; } -AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED() +AstDeclaredExternTypeProperty Parser::parseDeclaredExternTypeMethod_DEPRECATED() { Location start = lexer.current().location; @@ -1227,7 +1255,18 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED() expectMatchAndConsume(')', matchParen); - AstTypeList retTypes = parseOptionalReturnType().value_or(AstTypeList{copy(nullptr, 0), nullptr}); + AstTypePack* retTypes; + AstTypeList retTypes_DEPRECATED; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + retTypes = parseOptionalReturnType(); + if (!retTypes) + retTypes = allocator.alloc(lexer.current().location, AstTypeList{copy(nullptr, 0), nullptr}); + } + else + { + retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy(nullptr, 0), nullptr}); + } Location end = lexer.previousLocation(); TempVector vars(scratchType); @@ -1235,7 +1274,7 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED() if (args.size() == 0 || args[0].name.name != "self" || args[0].annotation != nullptr) { - return AstDeclaredClassProp{ + return AstDeclaredExternTypeProperty{ fnName.name, fnName.location, reportTypeError(Location(start, end), {}, "'self' must be present as the unannotated first parameter"), true }; } @@ -1254,11 +1293,16 @@ AstDeclaredClassProp Parser::parseDeclaredClassMethod_DEPRECATED() if (vararg && !varargAnnotation) report(start, "All declaration parameters aside from 'self' must be annotated"); - AstType* fnType = allocator.alloc( - Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes - ); + AstType* fnType = + FFlag::LuauStoreReturnTypesAsPackOnAst + ? allocator.alloc( + Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes + ) + : allocator.alloc( + Location(start, end), generics, genericPacks, AstTypeList{copy(vars), varargAnnotation}, copy(varNames), retTypes_DEPRECATED + ); - return AstDeclaredClassProp{fnName.name, fnName.location, fnType, true, Location(start, end)}; + return AstDeclaredExternTypeProperty{fnName.name, fnName.location, fnType, true, Location(start, end)}; } AstStat* Parser::parseDeclaration(const Location& start, const AstArray& attributes) @@ -1296,7 +1340,18 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray(nullptr, 0)}); + AstTypePack* retTypes; + AstTypeList retTypes_DEPRECATED; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + retTypes = parseOptionalReturnType(); + if (!retTypes) + retTypes = allocator.alloc(lexer.current().location, AstTypeList{copy(nullptr, 0), nullptr}); + } + else + { + retTypes_DEPRECATED = parseOptionalReturnType_DEPRECATED().value_or(AstTypeList{copy(nullptr, 0)}); + } Location end = lexer.current().location; TempVector vars(scratchType); @@ -1314,34 +1369,77 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray( - Location(start, end), - attributes, - globalName.name, - globalName.location, - generics, - genericPacks, - AstTypeList{copy(vars), varargAnnotation}, - copy(varNames), - vararg, - varargLocation, - retTypes - ); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + return allocator.alloc( + Location(start, end), + attributes, + globalName.name, + globalName.location, + generics, + genericPacks, + AstTypeList{copy(vars), varargAnnotation}, + copy(varNames), + vararg, + varargLocation, + retTypes + ); + } + else + { + return allocator.alloc( + Location(start, end), + attributes, + globalName.name, + globalName.location, + generics, + genericPacks, + AstTypeList{copy(vars), varargAnnotation}, + copy(varNames), + vararg, + varargLocation, + retTypes_DEPRECATED + ); + } } - else if (AstName(lexer.current().name) == "class") + else if (AstName(lexer.current().name) == "class" || (FFlag::LuauDeclareExternType && AstName(lexer.current().name) == "extern")) { + bool foundExtern = false; + if (FFlag::LuauDeclareExternType) + { + if (AstName(lexer.current().name) == "extern") + { + foundExtern = true; + nextLexeme(); + if (AstName(lexer.current().name) != "type") + return reportStatError(lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name); + } + } + nextLexeme(); + Location classStart = lexer.current().location; - Name className = parseName("class name"); + Name className = parseName(FFlag::LuauDeclareExternType ? "type name" : "class name"); std::optional superName = std::nullopt; if (AstName(lexer.current().name) == "extends") { nextLexeme(); - superName = parseName("superclass name").name; + superName = parseName(FFlag::LuauDeclareExternType ? "supertype name" : "superclass name").name; } - TempVector props(scratchDeclaredClassProps); + if (FFlag::LuauDeclareExternType) + { + if (foundExtern) + { + if (AstName(lexer.current().name) != "with") + report(lexer.current().location, "Expected `with` keyword before listing properties of the external type, but got %s instead", lexer.current().name); + else + nextLexeme(); + } + } + + TempVector props(scratchDeclaredClassProps); AstTableIndexer* indexer = nullptr; while (lexer.current().type != Lexeme::ReservedEnd) @@ -1368,9 +1466,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArraydata), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) }); } @@ -1413,7 +1511,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArraylocation, "Cannot have more than one class indexer"); + if (FFlag::LuauDeclareExternType) + report(badIndexer->location, "Cannot have more than one indexer on an extern type"); + else + report(badIndexer->location, "Cannot have more than one class indexer"); } else { @@ -1434,7 +1535,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArrayname, propName->location, propType, false, Location(propStart, lexer.previousLocation())} + AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())} ); } } @@ -1444,9 +1545,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArraydata), Location(nameBegin, nameEnd), type, false, Location(begin.location, lexer.previousLocation()) }); } @@ -1492,7 +1593,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArraylocation, "Cannot have more than one class indexer"); + if (FFlag::LuauDeclareExternType) + report(badIndexer->location, "Cannot have more than one indexer on an extern type"); + else + report(badIndexer->location, "Cannot have more than one class indexer"); } else { @@ -1515,7 +1619,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArrayname, propName->location, propType, false, Location(propStart, lexer.previousLocation())} + AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())} ); } } @@ -1524,7 +1628,7 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray(Location(classStart, classEnd), className.name, superName, copy(props), indexer); + return allocator.alloc(Location(classStart, classEnd), className.name, superName, copy(props), indexer); } else if (std::optional globalName = parseNameOpt("global variable name")) { @@ -1533,6 +1637,10 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray(Location(start, type->location), globalName->name, globalName->location, type); } + else if (FFlag::LuauDeclareExternType) + { + return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'extern type'"); + } else { return reportStatError(start, {}, {}, "declare must be followed by an identifier, 'function', or 'class'"); @@ -1638,6 +1746,8 @@ std::pair Parser::parseFunctionBody( const AstArray& attributes ) { + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); + Location start = matchFunction.location; if (FFlag::LuauFixFunctionWithAttributesStartLocation) @@ -1689,7 +1799,145 @@ std::pair Parser::parseFunctionBody( matchRecoveryStopOnToken[')']--; - std::optional typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr); + AstTypePack* typelist = parseOptionalReturnType(cstNode ? &cstNode->returnSpecifierPosition : nullptr); + + AstLocal* funLocal = nullptr; + + if (localName) + funLocal = pushLocal(Binding(*localName, nullptr)); + + unsigned int localsBegin = saveLocals(); + + Function fun; + fun.vararg = vararg; + + functionStack.emplace_back(fun); + + auto [self, vars] = prepareFunctionArguments(start, hasself, args); + + AstStatBlock* body = parseBlock(); + + functionStack.pop_back(); + + restoreLocals(localsBegin); + + Location end = lexer.current().location; + + bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction); + body->hasEnd = hasEnd; + + if (FFlag::LuauStoreCSTData2) + { + AstExprFunction* node = allocator.alloc( + Location(start, end), + attributes, + generics, + genericPacks, + self, + vars, + vararg, + varargLocation, + body, + functionStack.size(), + debugname, + typelist, + varargAnnotation, + argLocation + ); + if (options.storeCstData) + { + cstNode->functionKeywordPosition = matchFunction.location.begin; + cstNodeMap[node] = cstNode; + } + + return {node, funLocal}; + } + else + { + return { + allocator.alloc( + Location(start, end), + attributes, + generics, + genericPacks, + self, + vars, + vararg, + varargLocation, + body, + functionStack.size(), + debugname, + typelist, + varargAnnotation, + argLocation + ), + funLocal + }; + } +} + +std::pair Parser::parseFunctionBody_DEPRECATED( + bool hasself, + const Lexeme& matchFunction, + const AstName& debugname, + const Name* localName, + const AstArray& attributes +) +{ + LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst); + + Location start = matchFunction.location; + + if (FFlag::LuauFixFunctionWithAttributesStartLocation) + { + if (attributes.size > 0) + start = attributes.data[0]->location; + } + + auto* cstNode = FFlag::LuauStoreCSTData2 && options.storeCstData ? allocator.alloc() : nullptr; + + auto [generics, genericPacks] = + FFlag::LuauStoreCSTData2 && cstNode + ? parseGenericTypeList( + /* withDefaultValues= */ false, &cstNode->openGenericsPosition, &cstNode->genericsCommaPositions, &cstNode->closeGenericsPosition + ) + : parseGenericTypeList(/* withDefaultValues= */ false); + + MatchLexeme matchParen = lexer.current(); + expectAndConsume('(', "function"); + + // NOTE: This was added in conjunction with passing `searchForMissing` to + // `expectMatchAndConsume` inside `parseTableType` so that the behavior of + // parsing code like below (note the missing `}`): + // + // function (t: { a: number ) end + // + // ... will still parse as (roughly): + // + // function (t: { a: number }) end + // + matchRecoveryStopOnToken[')']++; + + TempVector args(scratchBinding); + + bool vararg = false; + Location varargLocation; + AstTypePack* varargAnnotation = nullptr; + + if (lexer.current().type != ')') + std::tie(vararg, varargLocation, varargAnnotation) = + parseBindingList(args, /* allowDot3= */ true, cstNode ? &cstNode->argsCommaPositions : nullptr); + + std::optional argLocation; + + if (matchParen.type == Lexeme::Type('(') && lexer.current().type == Lexeme::Type(')')) + argLocation = Location(matchParen.position, lexer.current().location.end); + + expectMatchAndConsume(')', matchParen, true); + + matchRecoveryStopOnToken[')']--; + + std::optional typelist = parseOptionalReturnType_DEPRECATED(cstNode ? &cstNode->returnSpecifierPosition : nullptr); AstLocal* funLocal = nullptr; @@ -1916,8 +2164,9 @@ AstTypePack* Parser::parseTypeList( return nullptr; } -std::optional Parser::parseOptionalReturnType(Position* returnSpecifierPosition) +AstTypePack* Parser::parseOptionalReturnType(Position* returnSpecifierPosition) { + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow) { if (lexer.current().type == Lexeme::SkinnyArrow) @@ -1929,7 +2178,41 @@ std::optional Parser::parseOptionalReturnType(Position* returnSpeci unsigned int oldRecursionCount = recursionCounter; - auto [_location, result] = parseReturnType(); + auto result = parseReturnType(); + LUAU_ASSERT(result); + + // At this point, if we find a , character, it indicates that there are multiple return types + // in this type annotation, but the list wasn't wrapped in parentheses. + if (lexer.current().type == ',') + { + report(lexer.current().location, "Expected a statement, got ','; did you forget to wrap the list of return types in parentheses?"); + + nextLexeme(); + } + + recursionCounter = oldRecursionCount; + + return result; + } + + return nullptr; +} + +std::optional Parser::parseOptionalReturnType_DEPRECATED(Luau::Position* returnSpecifierPosition) +{ + LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst); + if (lexer.current().type == ':' || lexer.current().type == Lexeme::SkinnyArrow) + { + if (lexer.current().type == Lexeme::SkinnyArrow) + report(lexer.current().location, "Function return type annotations are written after ':' instead of '->'"); + + if (FFlag::LuauStoreCSTData2 && returnSpecifierPosition) + *returnSpecifierPosition = lexer.current().location.begin; + nextLexeme(); + + unsigned int oldRecursionCount = recursionCounter; + + auto [_location, result] = parseReturnType_DEPRECATED(); // At this point, if we find a , character, it indicates that there are multiple return types // in this type annotation, but the list wasn't wrapped in parentheses. @@ -1949,8 +2232,119 @@ std::optional Parser::parseOptionalReturnType(Position* returnSpeci } // ReturnType ::= Type | `(' TypeList `)' -std::pair Parser::parseReturnType() +AstTypePack* Parser::parseReturnType() { + LUAU_ASSERT(FFlag::LuauStoreReturnTypesAsPackOnAst); + incrementRecursionCounter("type annotation"); + + Lexeme begin = lexer.current(); + + if (lexer.current().type != '(') + { + if (shouldParseTypePack(lexer)) + { + return parseTypePack(); + } + else + { + AstType* type = parseType(); + AstTypePackExplicit* node = allocator.alloc(type->location, AstTypeList{copy(&type, 1), nullptr}); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(); + return node; + } + } + + nextLexeme(); + + Location innerBegin = lexer.current().location; + + matchRecoveryStopOnToken[Lexeme::SkinnyArrow]++; + + TempVector result(scratchType); + TempVector> resultNames(scratchOptArgName); + TempVector commaPositions(scratchPosition); + AstTypePack* varargAnnotation = nullptr; + + // possibly () -> ReturnType + if (lexer.current().type != ')') + { + if (options.storeCstData) + varargAnnotation = parseTypeList(result, resultNames, &commaPositions); + else + varargAnnotation = parseTypeList(result, resultNames); + } + + const Location location{begin.location, lexer.current().location}; + Position closeParenthesesPosition = lexer.current().location.begin; + expectMatchAndConsume(')', begin, true); + + matchRecoveryStopOnToken[Lexeme::SkinnyArrow]--; + + if (lexer.current().type != Lexeme::SkinnyArrow && resultNames.empty()) + { + // If it turns out that it's just '(A)', it's possible that there are unions/intersections to follow, so fold over it. + if (FFlag::LuauAstTypeGroup3) + { + if (result.size() == 1) + { + // TODO(CLI-140667): stop parsing type suffix when varargAnnotation != nullptr - this should be a parse error + AstType* inner = varargAnnotation == nullptr ? allocator.alloc(location, result[0]) : result[0]; + AstType* returnType = parseTypeSuffix(inner, begin.location); + + if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && + (returnType->is() || returnType->is())) + luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; + + // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack + // type to successfully parse. We need the span of the whole annotation. + Position endPos = result.size() == 1 ? location.end : returnType->location.end; + + AstTypePackExplicit* node = allocator.alloc(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(); + return node; + } + } + else + { + if (result.size() == 1) + { + AstType* returnType = parseTypeSuffix(result[0], innerBegin); + + if (DFFlag::DebugLuauReportReturnTypeVariadicWithTypeSuffix && varargAnnotation != nullptr && + (returnType->is() || returnType->is())) + luau_telemetry_parsed_return_type_variadic_with_type_suffix = true; + + // If parseType parses nothing, then returnType->location.end only points at the last non-type-pack + // type to successfully parse. We need the span of the whole annotation. + Position endPos = result.size() == 1 ? location.end : returnType->location.end; + + AstTypePackExplicit* node = allocator.alloc(Location{location.begin, endPos}, AstTypeList{copy(&returnType, 1), varargAnnotation}); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(); + return node; + } + } + + AstTypePackExplicit* node = allocator.alloc(location, AstTypeList{copy(result), varargAnnotation}); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(location.begin, closeParenthesesPosition, copy(commaPositions)); + return node; + } + + AstType* tail = parseFunctionTypeTail(begin, {nullptr, 0}, {}, {}, copy(result), copy(resultNames), varargAnnotation); + + AstTypePackExplicit* node = allocator.alloc(Location{location, tail->location}, AstTypeList{copy(&tail, 1), nullptr}); + if (options.storeCstData) + cstNodeMap[node] = allocator.alloc(); + return node; +} + +// ReturnType ::= Type | `(' TypeList `)' +std::pair Parser::parseReturnType_DEPRECATED() +{ + LUAU_ASSERT(!FFlag::LuauStoreReturnTypesAsPackOnAst); incrementRecursionCounter("type annotation"); Lexeme begin = lexer.current(); @@ -2574,12 +2968,25 @@ AstType* Parser::parseFunctionTypeTail( expectAndConsume(Lexeme::SkinnyArrow, "function type"); } - auto [endLocation, returnTypeList] = parseReturnType(); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + auto returnType = parseReturnType(); + LUAU_ASSERT(returnType); - AstTypeList paramTypes = AstTypeList{params, varargAnnotation}; - return allocator.alloc( - Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList - ); + AstTypeList paramTypes = AstTypeList{params, varargAnnotation}; + return allocator.alloc( + Location(begin.location, returnType->location), attributes, generics, genericPacks, paramTypes, paramNames, returnType + ); + } + else + { + auto [endLocation, returnTypeList] = parseReturnType_DEPRECATED(); + + AstTypeList paramTypes = AstTypeList{params, varargAnnotation}; + return allocator.alloc( + Location(begin.location, endLocation), attributes, generics, genericPacks, paramTypes, paramNames, returnTypeList + ); + } } static bool isTypeFollow(Lexeme::Type c) @@ -2693,17 +3100,8 @@ AstType* Parser::parseTypeSuffix(AstType* type, const Location& begin) } } - if (FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) - { - if (parts.size() == 1 && !isUnion && !isIntersection) - return parts[0]; - } - else - { - if (parts.size() == 1) - return parts[0]; - } - + if (parts.size() == 1 && !isUnion && !isIntersection) + return parts[0]; if (isUnion && isIntersection) { return reportTypeError( @@ -3536,7 +3934,10 @@ AstExpr* Parser::parseSimpleExpr() Lexeme matchFunction = lexer.current(); nextLexeme(); - return parseFunctionBody(false, matchFunction, AstName(), nullptr, attributes).first; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + return parseFunctionBody(false, matchFunction, AstName(), nullptr, attributes).first; + else + return parseFunctionBody_DEPRECATED(false, matchFunction, AstName(), nullptr, attributes).first; } else if (lexer.current().type == Lexeme::Number) { @@ -4666,7 +5067,7 @@ void Parser::report(const Location& location, const char* format, va_list args) parseErrors.emplace_back(location, message); - if (parseErrors.size() >= unsigned(FInt::LuauParseErrorLimit) && (!FFlag::ParserNoErrorLimit || !options.noErrorLimit)) + if (parseErrors.size() >= unsigned(FInt::LuauParseErrorLimit) && !options.noErrorLimit) ParseError::raise(location, "Reached error limit (%d)", int(FInt::LuauParseErrorLimit)); } diff --git a/CLI/src/ReplRequirer.cpp b/CLI/src/ReplRequirer.cpp index 22184ddb..7d405a7e 100644 --- a/CLI/src/ReplRequirer.cpp +++ b/CLI/src/ReplRequirer.cpp @@ -131,7 +131,7 @@ static luarequire_WriteResult get_config(lua_State* L, void* ctx, char* buffer, return write(getFileContents(req->absPath, "/.luaurc"), buffer, buffer_size, size_out); } -static int load(lua_State* L, void* ctx, const char* chunkname, const char* contents) +static int load(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents) { ReplRequirer* req = static_cast(ctx); diff --git a/Compiler/src/Compiler.cpp b/Compiler/src/Compiler.cpp index 565fdd3e..f7418f46 100644 --- a/Compiler/src/Compiler.cpp +++ b/Compiler/src/Compiler.cpp @@ -28,6 +28,8 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5) LUAU_FASTFLAGVARIABLE(LuauSeparateCompilerTypeInfo) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) + namespace Luau { @@ -4317,26 +4319,54 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c mainFlags |= LPF_NATIVE_FUNCTION; } - AstExprFunction main( - root->location, - /* attributes= */ AstArray({nullptr, 0}), - /* generics= */ AstArray(), - /* genericPacks= */ AstArray(), - /* self= */ nullptr, - AstArray(), - /* vararg= */ true, - /* varargLocation= */ Luau::Location(), - root, - /* functionDepth= */ 0, - /* debugname= */ AstName() - ); - uint32_t mainid = compiler.compileFunction(&main, mainFlags); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + AstExprFunction main( + root->location, + /* attributes= */ AstArray({nullptr, 0}), + /* generics= */ AstArray(), + /* genericPacks= */ AstArray(), + /* self= */ nullptr, + AstArray(), + /* vararg= */ true, + /* varargLocation= */ Luau::Location(), + root, + /* functionDepth= */ 0, + /* debugname= */ AstName(), + /* returnAnnotation= */ nullptr + ); + uint32_t mainid = compiler.compileFunction(&main, mainFlags); - const Compiler::Function* mainf = compiler.functions.find(&main); - LUAU_ASSERT(mainf && mainf->upvals.empty()); + const Compiler::Function* mainf = compiler.functions.find(&main); + LUAU_ASSERT(mainf && mainf->upvals.empty()); - bytecode.setMainFunction(mainid); - bytecode.finalize(); + bytecode.setMainFunction(mainid); + bytecode.finalize(); + } + else + { + AstExprFunction main( + root->location, + /* attributes= */ AstArray({nullptr, 0}), + /* generics= */ AstArray(), + /* genericPacks= */ AstArray(), + /* self= */ nullptr, + AstArray(), + /* vararg= */ true, + /* varargLocation= */ Luau::Location(), + root, + /* functionDepth= */ 0, + /* debugname= */ AstName(), + /* returnAnnotation= */ std::nullopt + ); + uint32_t mainid = compiler.compileFunction(&main, mainFlags); + + const Compiler::Function* mainf = compiler.functions.find(&main); + LUAU_ASSERT(mainf && mainf->upvals.empty()); + + bytecode.setMainFunction(mainid); + bytecode.finalize(); + } } void compileOrThrow(BytecodeBuilder& bytecode, const std::string& source, const CompileOptions& options, const ParseOptions& parseOptions) diff --git a/Require/Navigator/include/Luau/RequireNavigator.h b/Require/Navigator/include/Luau/RequireNavigator.h index 21b01abd..1be079b7 100644 --- a/Require/Navigator/include/Luau/RequireNavigator.h +++ b/Require/Navigator/include/Luau/RequireNavigator.h @@ -3,7 +3,6 @@ #include "Luau/Config.h" -#include #include #include #include diff --git a/Require/Runtime/include/Luau/Require.h b/Require/Runtime/include/Luau/Require.h index 1765994a..45a05e7e 100644 --- a/Require/Runtime/include/Luau/Require.h +++ b/Require/Runtime/include/Luau/Require.h @@ -107,7 +107,7 @@ struct luarequire_Configuration // Executes the module and places the result on the stack. Returns the // number of results placed on the stack. - int (*load)(lua_State* L, void* ctx, const char* chunkname, const char* contents); + int (*load)(lua_State* L, void* ctx, const char* path, const char* chunkname, const char* contents); }; // Populates function pointers in the given luarequire_Configuration. @@ -115,7 +115,18 @@ typedef void (*luarequire_Configuration_init)(luarequire_Configuration* config); // Initializes and pushes the require closure onto the stack without // registration. -LUALIB_API int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx); +LUALIB_API int luarequire_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx); // Initializes the require library and registers it globally. LUALIB_API void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx); + +// Initializes and pushes a "proxyrequire" closure onto the stack. This function +// takes two parameters: the string path to resolve and the chunkname of an +// existing module. The path is resolved as if it were being required from the +// module that the chunkname represents. +LUALIB_API int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx); + +// Registers an aliased require path to a result. After registration, the given +// result will always be immediately returned when the given path is required. +// Expects the path and table to be passed as arguments on the stack. +LUALIB_API int luarequire_registermodule(lua_State* L); diff --git a/Require/Runtime/src/Require.cpp b/Require/Runtime/src/Require.cpp index 9884d235..fa4b3feb 100644 --- a/Require/Runtime/src/Require.cpp +++ b/Require/Runtime/src/Require.cpp @@ -35,7 +35,13 @@ static void validateConfig(lua_State* L, const luarequire_Configuration& config) luaL_error(L, "require configuration is missing required function pointer: load"); } -int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx) +static int pushrequireclosureinternal( + lua_State* L, + luarequire_Configuration_init config_init, + void* ctx, + lua_CFunction requirelikefunc, + const char* debugname +) { luarequire_Configuration* config = static_cast(lua_newuserdata(L, sizeof(luarequire_Configuration))); if (!config) @@ -46,13 +52,28 @@ int lua_pushrequire(lua_State* L, luarequire_Configuration_init config_init, voi lua_pushlightuserdata(L, ctx); - // "require" captures config and ctx as upvalues - lua_pushcclosure(L, Luau::Require::lua_require, "require", 2); + // require-like closure captures config and ctx as upvalues + lua_pushcclosure(L, requirelikefunc, debugname, 2); return 1; } +int luarequire_pushrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx) +{ + return pushrequireclosureinternal(L, config_init, ctx, Luau::Require::lua_require, "require"); +} + void luaopen_require(lua_State* L, luarequire_Configuration_init config_init, void* ctx) { - lua_pushrequire(L, config_init, ctx); + luarequire_pushrequire(L, config_init, ctx); lua_setglobal(L, "require"); } + +int luarequire_pushproxyrequire(lua_State* L, luarequire_Configuration_init config_init, void* ctx) +{ + return pushrequireclosureinternal(L, config_init, ctx, Luau::Require::lua_proxyrequire, "proxyrequire"); +} + +int luarequire_registermodule(lua_State* L) +{ + return Luau::Require::registerModuleImpl(L); +} diff --git a/Require/Runtime/src/RequireImpl.cpp b/Require/Runtime/src/RequireImpl.cpp index 1858abcb..30575964 100644 --- a/Require/Runtime/src/RequireImpl.cpp +++ b/Require/Runtime/src/RequireImpl.cpp @@ -13,7 +13,11 @@ namespace Luau::Require { -static const char* cacheTableKey = "_MODULES"; +// Stores explicitly registered modules. +static const char* registeredCacheTableKey = "_REGISTEREDMODULES"; + +// Stores the results of require calls. +static const char* requiredCacheTableKey = "_MODULES"; struct ResolvedRequire { @@ -32,7 +36,7 @@ struct ResolvedRequire static bool isCached(lua_State* L, const std::string& key) { - luaL_findtable(L, LUA_REGISTRYINDEX, cacheTableKey, 1); + luaL_findtable(L, LUA_REGISTRYINDEX, requiredCacheTableKey, 1); lua_getfield(L, -1, key.c_str()); bool cached = !lua_isnil(L, -1); lua_pop(L, 2); @@ -40,15 +44,12 @@ static bool isCached(lua_State* L, const std::string& key) return cached; } -static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, std::string path) +static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* L, void* ctx, const char* requirerChunkname, std::string path) { - lua_Debug ar; - lua_getinfo(L, 1, "s", &ar); - - if (!lrc->is_require_allowed(L, ctx, ar.source)) + if (!lrc->is_require_allowed(L, ctx, requirerChunkname)) luaL_error(L, "require is not supported in this context"); - RuntimeNavigationContext navigationContext{lrc, L, ctx, ar.source}; + RuntimeNavigationContext navigationContext{lrc, L, ctx, requirerChunkname}; RuntimeErrorHandler errorHandler{L}; // Errors reported directly to lua_State. Navigator navigator(navigationContext, errorHandler); @@ -74,7 +75,7 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* if (isCached(L, *cacheKey)) { // Put cached result on top of stack before returning. - lua_getfield(L, LUA_REGISTRYINDEX, cacheTableKey); + lua_getfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey); lua_getfield(L, -1, cacheKey->c_str()); lua_remove(L, -2); @@ -103,7 +104,21 @@ static ResolvedRequire resolveRequire(luarequire_Configuration* lrc, lua_State* }; } -int lua_require(lua_State* L) +static int checkRegisteredModules(lua_State* L, const char* path) +{ + luaL_findtable(L, LUA_REGISTRYINDEX, registeredCacheTableKey, 1); + lua_getfield(L, -1, path); + if (lua_isnil(L, -1)) + { + lua_pop(L, 2); + return 0; + } + + lua_remove(L, -2); + return 1; +} + +int lua_requireinternal(lua_State* L, const char* requirerChunkname) { luarequire_Configuration* lrc = static_cast(lua_touserdata(L, lua_upvalueindex(1))); if (!lrc) @@ -113,11 +128,14 @@ int lua_require(lua_State* L) const char* path = luaL_checkstring(L, 1); - ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, path); + if (checkRegisteredModules(L, path) == 1) + return 1; + + ResolvedRequire resolvedRequire = resolveRequire(lrc, L, ctx, requirerChunkname, path); if (resolvedRequire.status == ResolvedRequire::Status::Cached) return 1; - int numResults = lrc->load(L, ctx, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str()); + int numResults = lrc->load(L, ctx, path, resolvedRequire.chunkname.c_str(), resolvedRequire.contents.c_str()); if (numResults > 1) luaL_error(L, "module must return a single value"); @@ -127,7 +145,7 @@ int lua_require(lua_State* L) // Initial stack state // (-1) result - lua_getfield(L, LUA_REGISTRYINDEX, cacheTableKey); + lua_getfield(L, LUA_REGISTRYINDEX, requiredCacheTableKey); // (-2) result, (-1) cache table lua_pushvalue(L, -2); @@ -143,4 +161,42 @@ int lua_require(lua_State* L) return numResults; } +int lua_proxyrequire(lua_State* L) +{ + const char* requirerChunkname = luaL_checkstring(L, 2); + return lua_requireinternal(L, requirerChunkname); +} + +int lua_require(lua_State* L) +{ + lua_Debug ar; + lua_getinfo(L, 1, "s", &ar); + return lua_requireinternal(L, ar.source); +} + +int registerModuleImpl(lua_State* L) +{ + if (lua_gettop(L) != 2) + luaL_error(L, "expected 2 arguments: aliased require path and desired result"); + + size_t len; + const char* path = luaL_checklstring(L, 1, &len); + std::string_view pathView(path, len); + if (pathView.empty() || pathView[0] != '@') + luaL_argerrorL(L, 1, "path must begin with '@'"); + + luaL_findtable(L, LUA_REGISTRYINDEX, registeredCacheTableKey, 1); + // (1) path, (2) result, (3) cache table + + lua_insert(L, 1); + // (1) cache table, (2) path, (3) result + + lua_settable(L, 1); + // (1) cache table + + lua_pop(L, 1); + + return 0; +} + } // namespace Luau::Require diff --git a/Require/Runtime/src/RequireImpl.h b/Require/Runtime/src/RequireImpl.h index 9889acc7..5b460779 100644 --- a/Require/Runtime/src/RequireImpl.h +++ b/Require/Runtime/src/RequireImpl.h @@ -7,5 +7,8 @@ namespace Luau::Require { int lua_require(lua_State* L); +int lua_proxyrequire(lua_State* L); + +int registerModuleImpl(lua_State* L); } // namespace Luau::Require diff --git a/Sources.cmake b/Sources.cmake index d3453184..c8bb7214 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -179,6 +179,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Clone.h Analysis/include/Luau/Constraint.h Analysis/include/Luau/ConstraintGenerator.h + Analysis/include/Luau/ConstraintSet.h Analysis/include/Luau/ConstraintSolver.h Analysis/include/Luau/ControlFlow.h Analysis/include/Luau/DataFlowGraph.h diff --git a/VM/include/lualib.h b/VM/include/lualib.h index 5860c613..0e138b11 100644 --- a/VM/include/lualib.h +++ b/VM/include/lualib.h @@ -58,6 +58,9 @@ LUALIB_API const char* luaL_findtable(lua_State* L, int idx, const char* fname, LUALIB_API const char* luaL_typename(lua_State* L, int idx); +// wrapper for making calls from yieldable C functions +LUALIB_API int luaL_callyieldable(lua_State* L, int nargs, int nresults); + /* ** =============================================================== ** some useful macros diff --git a/VM/src/laux.cpp b/VM/src/laux.cpp index 1d23b155..8e708d16 100644 --- a/VM/src/laux.cpp +++ b/VM/src/laux.cpp @@ -12,6 +12,7 @@ #include LUAU_FASTFLAGVARIABLE(LuauLibWhereErrorAutoreserve) +LUAU_FASTFLAG(LuauYieldableContinuations) // convert a stack index to positive #define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1) @@ -355,6 +356,22 @@ const char* luaL_typename(lua_State* L, int idx) return obj ? luaT_objtypename(L, obj) : "no value"; } +int luaL_callyieldable(lua_State* L, int nargs, int nresults) +{ + LUAU_ASSERT(FFlag::LuauYieldableContinuations); + + api_check(L, iscfunction(L->ci->func)); + Closure* cl = clvalue(L->ci->func); + api_check(L, cl->c.cont); + + lua_call(L, nargs, nresults); + + if (L->status == LUA_YIELD || L->status == LUA_BREAK) + return -1; // -1 is a marker for yielding from C + + return cl->c.cont(L, LUA_OK); +} + /* ** {====================================================== ** Generic Buffer manipulation diff --git a/VM/src/lbaselib.cpp b/VM/src/lbaselib.cpp index f4dac61f..18f5791c 100644 --- a/VM/src/lbaselib.cpp +++ b/VM/src/lbaselib.cpp @@ -11,6 +11,8 @@ #include #include +LUAU_FASTFLAG(LuauYieldableContinuations) + static void writestring(const char* s, size_t l) { fwrite(s, 1, l, stdout); @@ -294,10 +296,18 @@ static int luaB_pcally(lua_State* L) // any errors from this point on are handled by continuation L->ci->flags |= LUA_CALLINFO_HANDLE; - // maintain yieldable invariant (baseCcalls <= nCcalls) - L->baseCcalls++; + if (!FFlag::LuauYieldableContinuations) + { + // maintain yieldable invariant (baseCcalls <= nCcalls) + L->baseCcalls++; + } + int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), 0); - L->baseCcalls--; + + if (!FFlag::LuauYieldableContinuations) + { + L->baseCcalls--; + } // necessary to accomodate functions that return lots of values expandstacklimit(L, L->top); @@ -348,12 +358,20 @@ static int luaB_xpcally(lua_State* L) StkId errf = L->base; StkId func = L->base + 1; - // maintain yieldable invariant (baseCcalls <= nCcalls) - L->baseCcalls++; - int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf)); - L->baseCcalls--; + if (!FFlag::LuauYieldableContinuations) + { + // maintain yieldable invariant (baseCcalls <= nCcalls) + L->baseCcalls++; + } - // necessary to accomodate functions that return lots of values + int status = luaD_pcall(L, luaB_pcallrun, func, savestack(L, func), savestack(L, errf)); + + if (!FFlag::LuauYieldableContinuations) + { + L->baseCcalls--; + } + + // necessary to accommodate functions that return lots of values expandstacklimit(L, L->top); // yielding means we need to propagate yield; resume will call continuation function later diff --git a/VM/src/ldo.cpp b/VM/src/ldo.cpp index d3c8de4d..4d6f3ecf 100644 --- a/VM/src/ldo.cpp +++ b/VM/src/ldo.cpp @@ -17,6 +17,8 @@ #include +LUAU_FASTFLAGVARIABLE(LuauYieldableContinuations) + // keep max stack allocation request under 1GB #define MAX_STACK_SIZE (int(1024 / sizeof(TValue)) * 1024 * 1024) @@ -260,24 +262,79 @@ void luaD_call(lua_State* L, StkId func, int nresults) if (++L->nCcalls >= LUAI_MAXCCALLS) luaD_checkCstack(L); - ptrdiff_t old_func = savestack(L, func); + if (FFlag::LuauYieldableContinuations) + { + // when called from a yieldable C function, maintain yieldable invariant (baseCcalls <= nCcalls) + bool fromyieldableccall = false; - if (luau_precall(L, func, nresults) == PCRLUA) - { // is a Lua function? - L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame + if (L->ci != L->base_ci) + { + Closure* ccl = clvalue(L->ci->func); - bool oldactive = L->isactive; - L->isactive = true; - luaC_threadbarrier(L); + if (ccl->isC && ccl->c.cont) + { + fromyieldableccall = true; + L->baseCcalls++; + } + } - luau_execute(L); // call it + ptrdiff_t funcoffset = savestack(L, func); + ptrdiff_t cioffset = saveci(L, L->ci); - if (!oldactive) - L->isactive = false; + if (luau_precall(L, func, nresults) == PCRLUA) + { // is a Lua function? + L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame + + bool oldactive = L->isactive; + L->isactive = true; + luaC_threadbarrier(L); + + luau_execute(L); // call it + + if (!oldactive) + L->isactive = false; + } + + bool yielded = L->status == LUA_YIELD || L->status == LUA_BREAK; + + if (fromyieldableccall) + { + // restore original yieldable invariant + // in case of an error, this would either be restored by luaD_pcall or the thread would no longer be resumable + L->baseCcalls--; + + // on yield, we have to set the CallInfo top of the C function including slots for expected results, to restore later + if (yielded) + { + CallInfo* callerci = restoreci(L, cioffset); + callerci->top = restorestack(L, funcoffset) + (nresults != LUA_MULTRET ? nresults : 0); + } + } + + if (nresults != LUA_MULTRET && !yielded) + L->top = restorestack(L, funcoffset) + nresults; } + else + { + ptrdiff_t old_func = savestack(L, func); - if (nresults != LUA_MULTRET) - L->top = restorestack(L, old_func) + nresults; + if (luau_precall(L, func, nresults) == PCRLUA) + { // is a Lua function? + L->ci->flags |= LUA_CALLINFO_RETURN; // luau_execute will stop after returning from the stack frame + + bool oldactive = L->isactive; + L->isactive = true; + luaC_threadbarrier(L); + + luau_execute(L); // call it + + if (!oldactive) + L->isactive = false; + } + + if (nresults != LUA_MULTRET) + L->top = restorestack(L, old_func) + nresults; + } L->nCcalls--; luaC_checkGC(L); @@ -323,9 +380,18 @@ static void resume_continue(lua_State* L) // C continuation; we expect this to be followed by Lua continuations int n = cl->c.cont(L, 0); - // Continuation can break again - if (L->status == LUA_BREAK) - break; + if (FFlag::LuauYieldableContinuations) + { + // continuation can break or yield again + if (L->status == LUA_BREAK || L->status == LUA_YIELD) + break; + } + else + { + // Continuation can break again + if (L->status == LUA_BREAK) + break; + } luau_poscall(L, L->top - n); } @@ -370,6 +436,11 @@ static void resume(lua_State* L, void* ud) // finish interrupted execution of `OP_CALL' luau_poscall(L, firstArg); } + else if (FFlag::LuauYieldableContinuations) + { + // restore arguments we have protected for C continuation + L->base = L->ci->base; + } } else { @@ -576,6 +647,7 @@ static void restore_stack_limit(lua_State* L) int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t ef) { unsigned short oldnCcalls = L->nCcalls; + unsigned short oldbaseCcalls = FFlag::LuauYieldableContinuations ? L->baseCcalls : 0; ptrdiff_t old_ci = saveci(L, L->ci); bool oldactive = L->isactive; int status = luaD_rawrunprotected(L, func, u); @@ -612,6 +684,9 @@ int luaD_pcall(lua_State* L, Pfunc func, void* u, ptrdiff_t old_top, ptrdiff_t e // restore nCcalls before calling the debugprotectederror callback which may rely on the proper value to have been restored. L->nCcalls = oldnCcalls; + if (FFlag::LuauYieldableContinuations) + L->baseCcalls = oldbaseCcalls; + // an error occurred, check if we have a protected error callback if (yieldable && L->global->cb.debugprotectederror) { diff --git a/fuzz/proto.cpp b/fuzz/proto.cpp index adba6bdb..787bd950 100644 --- a/fuzz/proto.cpp +++ b/fuzz/proto.cpp @@ -124,8 +124,8 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for // Vector3 stub TypeId vector3MetaType = arena.addType(TableType{}); - TypeId vector3InstanceType = arena.addType(ClassType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test", {}}); - getMutable(vector3InstanceType)->props = { + TypeId vector3InstanceType = arena.addType(ExternType{"Vector3", {}, nullopt, vector3MetaType, {}, {}, "Test", {}}); + getMutable(vector3InstanceType)->props = { {"X", {builtinTypes.numberType}}, {"Y", {builtinTypes.numberType}}, {"Z", {builtinTypes.numberType}}, @@ -139,16 +139,16 @@ int registerTypes(Luau::Frontend& frontend, Luau::GlobalTypes& globals, bool for globals.globalScope->exportedTypeBindings["Vector3"] = TypeFun{{}, vector3InstanceType}; // Instance stub - TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}}); - getMutable(instanceType)->props = { + TypeId instanceType = arena.addType(ExternType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}}); + getMutable(instanceType)->props = { {"Name", {builtinTypes.stringType}}, }; globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType}; // Part stub - TypeId partType = arena.addType(ClassType{"Part", {}, instanceType, nullopt, {}, {}, "Test", {}}); - getMutable(partType)->props = { + TypeId partType = arena.addType(ExternType{"Part", {}, instanceType, nullopt, {}, {}, "Test", {}}); + getMutable(partType)->props = { {"Position", {vector3InstanceType}}, }; diff --git a/fuzz/protoprint.cpp b/fuzz/protoprint.cpp index 0adc0968..dc3160eb 100644 --- a/fuzz/protoprint.cpp +++ b/fuzz/protoprint.cpp @@ -36,7 +36,7 @@ static const std::string kTypes[] = { "vector", }; -static const std::string kClasses[] = { +static const std::string kExternTypes[] = { "Vector3", "Instance", "Part", @@ -902,8 +902,8 @@ struct ProtoToLuau void print(const luau::TypeClass& type) { - size_t index = size_t(type.kind()) % std::size(kClasses); - source += kClasses[index]; + size_t index = size_t(type.kind()) % std::size(kExternTypes); + source += kExternTypes[index]; } void print(const luau::TypeRef& type) diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index a4ae4fdc..5b894bea 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -13,6 +13,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) struct JsonEncoderFixture { @@ -420,20 +421,39 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction") { AstStat* statement = expectParseStatement("declare function foo(x: number): string"); - std::string_view expected = - R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]},"generics":[],"genericPacks":[]})"; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + std::string_view expected = + R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypePackExplicit","location":"0,33 - 0,39","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]}},"generics":[],"genericPacks":[]})"; + CHECK(toJson(statement) == expected); + } + else + { + std::string_view expected = + R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,39","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}]},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":false,"varargLocation":"0,0 - 0,0","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,33 - 0,39","name":"string","nameLocation":"0,33 - 0,39","parameters":[]}]},"generics":[],"genericPacks":[]})"; - CHECK(toJson(statement) == expected); + CHECK(toJson(statement) == expected); + } } TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareFunction2") { AstStat* statement = expectParseStatement("declare function foo(x: number, ...: string): string"); - std::string_view expected = - R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]},"generics":[],"genericPacks":[]})"; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + std::string_view expected = + R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypePackExplicit","location":"0,46 - 0,52","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]}},"generics":[],"genericPacks":[]})"; - CHECK(toJson(statement) == expected); + CHECK(toJson(statement) == expected); + } + else + { + std::string_view expected = + R"({"type":"AstStatDeclareFunction","location":"0,0 - 0,52","attributes":[],"name":"foo","nameLocation":"0,17 - 0,20","params":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,24 - 0,30","name":"number","nameLocation":"0,24 - 0,30","parameters":[]}],"tailType":{"type":"AstTypePackVariadic","location":"0,37 - 0,43","variadicType":{"type":"AstTypeReference","location":"0,37 - 0,43","name":"string","nameLocation":"0,37 - 0,43","parameters":[]}}},"paramNames":[{"type":"AstArgumentName","name":"x","location":"0,21 - 0,22"}],"vararg":true,"varargLocation":"0,32 - 0,35","retTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,46 - 0,52","name":"string","nameLocation":"0,46 - 0,52","parameters":[]}]},"generics":[],"genericPacks":[]})"; + + CHECK(toJson(statement) == expected); + } } TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstAttr") @@ -463,9 +483,18 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatDeclareClass") REQUIRE(2 == root->body.size); - std::string_view expected1 = - R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}},"location":"3,12 - 3,54"}],"indexer":null})"; - CHECK(toJson(root->body.data[0]) == expected1); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + std::string_view expected1 = + R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypePackExplicit","location":"3,48 - 3,54","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}}},"location":"3,12 - 3,54"}],"indexer":null})"; + CHECK(toJson(root->body.data[0]) == expected1); + } + else + { + std::string_view expected1 = + R"({"type":"AstStatDeclareClass","location":"1,22 - 4,11","name":"Foo","props":[{"name":"prop","nameLocation":"2,12 - 2,16","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"2,18 - 2,24","name":"number","nameLocation":"2,18 - 2,24","parameters":[]},"location":"2,12 - 2,24"},{"name":"method","nameLocation":"3,21 - 3,27","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeFunction","location":"3,12 - 3,54","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,39 - 3,45","name":"number","nameLocation":"3,39 - 3,45","parameters":[]}]},"argNames":[{"type":"AstArgumentName","name":"foo","location":"3,34 - 3,37"}],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"3,48 - 3,54","name":"string","nameLocation":"3,48 - 3,54","parameters":[]}]}},"location":"3,12 - 3,54"}],"indexer":null})"; + CHECK(toJson(root->body.data[0]) == expected1); + } std::string_view expected2 = R"({"type":"AstStatDeclareClass","location":"6,22 - 8,11","name":"Bar","superName":"Foo","props":[{"name":"prop2","nameLocation":"7,12 - 7,17","type":"AstDeclaredClassProp","luauType":{"type":"AstTypeReference","location":"7,19 - 7,25","name":"string","nameLocation":"7,19 - 7,25","parameters":[]},"location":"7,12 - 7,25"}],"indexer":null})"; @@ -476,7 +505,19 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_annotation") { AstStat* statement = expectParseStatement("type T = ((number) -> (string | nil)) & ((string) -> ())"); - if (FFlag::LuauAstTypeGroup3) + if (FFlag::LuauStoreReturnTypesAsPackOnAst && FFlag::LuauAstTypeGroup3) + { + std::string_view expected = + R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,22 - 0,36","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,53 - 0,55","typeList":{"type":"AstTypeList","types":[]}}}}]},"exported":false})"; + CHECK(toJson(statement) == expected); + } + else if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + std::string_view expected = + R"({"type":"AstStatTypeAlias","location":"0,0 - 0,55","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,55","types":[{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,22 - 0,36","typeList":{"type":"AstTypeList","types":[{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}]}}},{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypePackExplicit","location":"0,53 - 0,55","typeList":{"type":"AstTypeList","types":[]}}}]},"exported":false})"; + CHECK(toJson(statement) == expected); + } + else if (FFlag::LuauAstTypeGroup3) { std::string_view expected = R"({"type":"AstStatTypeAlias","location":"0,0 - 0,56","name":"T","generics":[],"genericPacks":[],"value":{"type":"AstTypeIntersection","location":"0,9 - 0,56","types":[{"type":"AstTypeGroup","location":"0,9 - 0,37","inner":{"type":"AstTypeFunction","location":"0,10 - 0,36","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,11 - 0,17","name":"number","nameLocation":"0,11 - 0,17","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[{"type":"AstTypeGroup","location":"0,22 - 0,36","inner":{"type":"AstTypeUnion","location":"0,23 - 0,35","types":[{"type":"AstTypeReference","location":"0,23 - 0,29","name":"string","nameLocation":"0,23 - 0,29","parameters":[]},{"type":"AstTypeReference","location":"0,32 - 0,35","name":"nil","nameLocation":"0,32 - 0,35","parameters":[]}]}}]}}},{"type":"AstTypeGroup","location":"0,40 - 0,56","inner":{"type":"AstTypeFunction","location":"0,41 - 0,55","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,42 - 0,48","name":"string","nameLocation":"0,42 - 0,48","parameters":[]}]},"argNames":[],"returnTypes":{"type":"AstTypeList","types":[]}}}]},"exported":false})"; @@ -516,10 +557,18 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeFunction") { AstStat* statement = expectParseStatement(R"(type fun = (string, bool, named: number) -> ())"); - std::string_view expected = - R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypeList","types":[]}},"exported":false})"; - - CHECK(toJson(statement) == expected); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + std::string_view expected = + R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypePackExplicit","location":"0,44 - 0,46","typeList":{"type":"AstTypeList","types":[]}}},"exported":false})"; + CHECK(toJson(statement) == expected); + } + else + { + std::string_view expected = + R"({"type":"AstStatTypeAlias","location":"0,0 - 0,46","name":"fun","generics":[],"genericPacks":[],"value":{"type":"AstTypeFunction","location":"0,11 - 0,46","attributes":[],"generics":[],"genericPacks":[],"argTypes":{"type":"AstTypeList","types":[{"type":"AstTypeReference","location":"0,12 - 0,18","name":"string","nameLocation":"0,12 - 0,18","parameters":[]},{"type":"AstTypeReference","location":"0,20 - 0,24","name":"bool","nameLocation":"0,20 - 0,24","parameters":[]},{"type":"AstTypeReference","location":"0,33 - 0,39","name":"number","nameLocation":"0,33 - 0,39","parameters":[]}]},"argNames":[null,null,{"type":"AstArgumentName","name":"named","location":"0,26 - 0,31"}],"returnTypes":{"type":"AstTypeList","types":[]}},"exported":false})"; + CHECK(toJson(statement) == expected); + } } TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstTypeError") diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index 959ea78d..3d1750e6 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -20,14 +20,13 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauExposeRequireByStringAutocomplete) LUAU_FASTFLAG(LuauAutocompleteUnionCopyPreviousSeen) LUAU_FASTFLAG(LuauUserTypeFunTypecheck) LUAU_FASTFLAG(LuauTypeFunResultInAutocomplete) using namespace Luau; -static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) +static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { return std::nullopt; } @@ -159,7 +158,7 @@ struct ACBuiltinsFixture : ACFixtureImpl { }; -struct ACClassFixture : ACFixtureImpl +struct ACExternTypeFixture : ACFixtureImpl { }; @@ -3754,7 +3753,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback") bool isCorrect = false; auto ac1 = autocomplete( '1', - [&isCorrect](std::string, std::optional, std::optional contents) -> std::optional + [&isCorrect](std::string, std::optional, std::optional contents) -> std::optional { isCorrect = contents && *contents == "testing/"; return std::nullopt; @@ -3766,8 +3765,6 @@ TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback") TEST_CASE_FIXTURE(ACBuiltinsFixture, "require_by_string") { - ScopedFastFlag sff{FFlag::LuauExposeRequireByStringAutocomplete, true}; - fileResolver.source["MainModule"] = R"( local info = "MainModule serves as the root directory" )"; @@ -3960,7 +3957,7 @@ TEST_CASE_FIXTURE(ACFixture, "string_completion_outside_quotes") local x = require(@1"@2"@3) )"); - StringCompletionCallback callback = [](std::string, std::optional, std::optional contents + StringCompletionCallback callback = [](std::string, std::optional, std::optional contents ) -> std::optional { Luau::AutocompleteEntryMap results = {{"test", Luau::AutocompleteEntry{Luau::AutocompleteEntryKind::String, std::nullopt, false, false}}}; @@ -4432,7 +4429,7 @@ local x = 1 + result. CHECK(ac.entryMap.count("x")); } -TEST_CASE_FIXTURE(ACClassFixture, "ac_dont_overflow_on_recursive_union") +TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union") { ScopedFastFlag _{FFlag::LuauAutocompleteUnionCopyPreviousSeen, true}; check(R"( diff --git a/tests/BuiltinDefinitions.test.cpp b/tests/BuiltinDefinitions.test.cpp index 08505c8d..56355f56 100644 --- a/tests/BuiltinDefinitions.test.cpp +++ b/tests/BuiltinDefinitions.test.cpp @@ -28,9 +28,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "lib_documentation_symbols") { props = &ttv->props; } - else if (const ClassType* ctv = get(binding.typeId)) + else if (const ExternType* etv = get(binding.typeId)) { - props = &ctv->props; + props = &etv->props; } if (props) diff --git a/tests/ClassFixture.cpp b/tests/ClassFixture.cpp index 6ec4ec20..a8689db8 100644 --- a/tests/ClassFixture.cpp +++ b/tests/ClassFixture.cpp @@ -9,7 +9,7 @@ using std::nullopt; namespace Luau { -ClassFixture::ClassFixture(bool prepareAutocomplete) +ExternTypeFixture::ExternTypeFixture(bool prepareAutocomplete) : BuiltinsFixture(prepareAutocomplete) { GlobalTypes& globals = frontend.globals; @@ -19,22 +19,22 @@ ClassFixture::ClassFixture(bool prepareAutocomplete) unfreeze(arena); - TypeId connectionType = arena.addType(ClassType{"Connection", {}, nullopt, nullopt, {}, {}, "Connection", {}}); + TypeId connectionType = arena.addType(ExternType{"Connection", {}, nullopt, nullopt, {}, {}, "Connection", {}}); - TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); - getMutable(baseClassInstanceType)->props = { + TypeId baseClassInstanceType = arena.addType(ExternType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); + getMutable(baseClassInstanceType)->props = { {"BaseMethod", Property::readonly(makeFunction(arena, baseClassInstanceType, {numberType}, {}))}, {"BaseField", {numberType}}, {"Touched", Property::readonly(connectionType)}, }; - getMutable(connectionType)->props = { + getMutable(connectionType)->props = { {"Connect", {makeFunction(arena, connectionType, {makeFunction(arena, nullopt, {baseClassInstanceType}, {})}, {})}} }; - TypeId baseClassType = arena.addType(ClassType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); - getMutable(baseClassType)->props = { + TypeId baseClassType = arena.addType(ExternType{"BaseClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); + getMutable(baseClassType)->props = { {"StaticMethod", {makeFunction(arena, nullopt, {}, {numberType})}}, {"Clone", {makeFunction(arena, nullopt, {baseClassInstanceType}, {baseClassInstanceType})}}, {"New", {makeFunction(arena, nullopt, {}, {baseClassInstanceType})}}, @@ -42,49 +42,49 @@ ClassFixture::ClassFixture(bool prepareAutocomplete) globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType}; addGlobalBinding(globals, "BaseClass", baseClassType, "@test"); - TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); + TypeId childClassInstanceType = arena.addType(ExternType{"ChildClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); - getMutable(childClassInstanceType)->props = { + getMutable(childClassInstanceType)->props = { {"Method", {makeFunction(arena, childClassInstanceType, {}, {stringType})}}, }; - TypeId childClassType = arena.addType(ClassType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test", {}}); - getMutable(childClassType)->props = { + TypeId childClassType = arena.addType(ExternType{"ChildClass", {}, baseClassType, nullopt, {}, {}, "Test", {}}); + getMutable(childClassType)->props = { {"New", {makeFunction(arena, nullopt, {}, {childClassInstanceType})}}, }; globals.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType}; addGlobalBinding(globals, "ChildClass", childClassType, "@test"); - TypeId grandChildInstanceType = arena.addType(ClassType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test", {}}); + TypeId grandChildInstanceType = arena.addType(ExternType{"GrandChild", {}, childClassInstanceType, nullopt, {}, {}, "Test", {}}); - getMutable(grandChildInstanceType)->props = { + getMutable(grandChildInstanceType)->props = { {"Method", {makeFunction(arena, grandChildInstanceType, {}, {stringType})}}, }; - TypeId grandChildType = arena.addType(ClassType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test", {}}); - getMutable(grandChildType)->props = { + TypeId grandChildType = arena.addType(ExternType{"GrandChild", {}, baseClassType, nullopt, {}, {}, "Test", {}}); + getMutable(grandChildType)->props = { {"New", {makeFunction(arena, nullopt, {}, {grandChildInstanceType})}}, }; globals.globalScope->exportedTypeBindings["GrandChild"] = TypeFun{{}, grandChildInstanceType}; addGlobalBinding(globals, "GrandChild", childClassType, "@test"); - TypeId anotherChildInstanceType = arena.addType(ClassType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); + TypeId anotherChildInstanceType = arena.addType(ExternType{"AnotherChild", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); - getMutable(anotherChildInstanceType)->props = { + getMutable(anotherChildInstanceType)->props = { {"Method", {makeFunction(arena, anotherChildInstanceType, {}, {stringType})}}, }; - TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test", {}}); - getMutable(anotherChildType)->props = { + TypeId anotherChildType = arena.addType(ExternType{"AnotherChild", {}, baseClassType, nullopt, {}, {}, "Test", {}}); + getMutable(anotherChildType)->props = { {"New", {makeFunction(arena, nullopt, {}, {anotherChildInstanceType})}}, }; globals.globalScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildInstanceType}; addGlobalBinding(globals, "AnotherChild", childClassType, "@test"); - TypeId unrelatedClassInstanceType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); + TypeId unrelatedClassInstanceType = arena.addType(ExternType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); - TypeId unrelatedClassType = arena.addType(ClassType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); - getMutable(unrelatedClassType)->props = { + TypeId unrelatedClassType = arena.addType(ExternType{"UnrelatedClass", {}, nullopt, nullopt, {}, {}, "Test", {}}); + getMutable(unrelatedClassType)->props = { {"New", {makeFunction(arena, nullopt, {}, {unrelatedClassInstanceType})}}, }; globals.globalScope->exportedTypeBindings["UnrelatedClass"] = TypeFun{{}, unrelatedClassInstanceType}; @@ -92,14 +92,14 @@ ClassFixture::ClassFixture(bool prepareAutocomplete) TypeId vector2MetaType = arena.addType(TableType{}); - vector2InstanceType = arena.addType(ClassType{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test", {}}); - getMutable(vector2InstanceType)->props = { + vector2InstanceType = arena.addType(ExternType{"Vector2", {}, nullopt, vector2MetaType, {}, {}, "Test", {}}); + getMutable(vector2InstanceType)->props = { {"X", {numberType}}, {"Y", {numberType}}, }; - vector2Type = arena.addType(ClassType{"Vector2", {}, nullopt, nullopt, {}, {}, "Test", {}}); - getMutable(vector2Type)->props = { + vector2Type = arena.addType(ExternType{"Vector2", {}, nullopt, nullopt, {}, {}, "Test", {}}); + getMutable(vector2Type)->props = { {"New", {makeFunction(arena, nullopt, {numberType, numberType}, {vector2InstanceType})}}, }; getMutable(vector2MetaType)->props = { @@ -114,7 +114,7 @@ ClassFixture::ClassFixture(bool prepareAutocomplete) addGlobalBinding(globals, "Vector2", vector2Type, "@test"); TypeId callableClassMetaType = arena.addType(TableType{}); - TypeId callableClassType = arena.addType(ClassType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test", {}}); + TypeId callableClassType = arena.addType(ExternType{"CallableClass", {}, nullopt, callableClassMetaType, {}, {}, "Test", {}}); getMutable(callableClassMetaType)->props = { {"__call", {makeFunction(arena, nullopt, {callableClassType, stringType}, {numberType})}}, }; @@ -124,7 +124,7 @@ ClassFixture::ClassFixture(bool prepareAutocomplete) { TypeId indexableClassMetaType = arena.addType(TableType{}); TypeId indexableClassType = - arena.addType(ClassType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", {}, TableIndexer{keyType, returnType}}); + arena.addType(ExternType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", {}, TableIndexer{keyType, returnType}}); globals.globalScope->exportedTypeBindings[className] = TypeFun{{}, indexableClassType}; }; @@ -134,9 +134,9 @@ ClassFixture::ClassFixture(bool prepareAutocomplete) addIndexableClass("IndexableNumericKeyClass", numberType, numberType); // Add a confusing derived class which shares the same name internally, but has a unique alias - TypeId duplicateBaseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); + TypeId duplicateBaseClassInstanceType = arena.addType(ExternType{"BaseClass", {}, baseClassInstanceType, nullopt, {}, {}, "Test", {}}); - getMutable(duplicateBaseClassInstanceType)->props = { + getMutable(duplicateBaseClassInstanceType)->props = { {"Method", {makeFunction(arena, duplicateBaseClassInstanceType, {}, {stringType})}}, }; diff --git a/tests/ClassFixture.h b/tests/ClassFixture.h index d7db1220..6ea6e851 100644 --- a/tests/ClassFixture.h +++ b/tests/ClassFixture.h @@ -6,9 +6,9 @@ namespace Luau { -struct ClassFixture : BuiltinsFixture +struct ExternTypeFixture : BuiltinsFixture { - explicit ClassFixture(bool prepareAutocomplete = false); + explicit ExternTypeFixture(bool prepareAutocomplete = false); TypeId vector2Type; TypeId vector2InstanceType; diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp index 5419866a..67fb7981 100644 --- a/tests/Conformance.test.cpp +++ b/tests/Conformance.test.cpp @@ -35,6 +35,7 @@ LUAU_FASTFLAG(LuauLibWhereErrorAutoreserve) LUAU_FASTFLAG(DebugLuauAbortingChecks) LUAU_FASTINT(CodegenHeuristicsInstructionLimit) LUAU_DYNAMIC_FASTFLAG(LuauStringFormatFixC) +LUAU_FASTFLAG(LuauYieldableContinuations) static lua_CompileOptions defaultOptions() { @@ -823,6 +824,236 @@ TEST_CASE("Pack") runConformance("tpack.luau"); } +int singleYield(lua_State* L) +{ + lua_pushnumber(L, 2); + + return lua_yield(L, 1); +} + +int singleYieldContinuation(lua_State* L, int status) +{ + lua_pushnumber(L, 4); + return 1; +} + +int multipleYields(lua_State* L) +{ + lua_settop(L, 1); // Only 1 argument expected + int base = luaL_checkinteger(L, 1); + + luaL_checkstack(L, 2, "cmultiyield"); + + // current state + int pos = 1; + lua_pushinteger(L, pos); + + // return value + lua_pushinteger(L, base + pos); + return lua_yield(L, 1); +} + +int multipleYieldsContinuation(lua_State* L, int status) +{ + // function arguments are still alive + int base = luaL_checkinteger(L, 1); + + // function state is still alive + int pos = luaL_checkinteger(L, 2) + 1; + luaL_checkstack(L, 1, "cmultiyieldcont"); + lua_pushinteger(L, pos); + lua_replace(L, 2); + + luaL_checkstack(L, 1, "cmultiyieldcont"); + + if (pos < 4) + { + lua_pushinteger(L, base + pos); + return lua_yield(L, 1); + } + else + { + lua_pushinteger(L, base + pos); + return 1; + } +} + +int nestedMultipleYieldHelper(lua_State* L) +{ + int context = luaL_checkinteger(L, lua_upvalueindex(1)); + + lua_pushinteger(L, 100 + context); + return lua_yield(L, 1); +} + +int nestedMultipleYieldHelperContinuation(lua_State* L, int status) +{ + int context = luaL_checkinteger(L, lua_upvalueindex(1)); + lua_pushinteger(L, 110 + context); + return 1; +} + +int nestedMultipleYieldHelperNonYielding(lua_State* L) +{ + int context = luaL_checkinteger(L, lua_upvalueindex(1)); + lua_pushinteger(L, 105 + context); + return 1; +} + +int multipleYieldsWithNestedCall(lua_State* L) +{ + lua_settop(L, 2); // Only 2 arguments expected + bool nestedShouldYield = luaL_checkboolean(L, 2); + + lua_pushinteger(L, 0); // state + + lua_pushnumber(L, 5); + if (nestedShouldYield) + lua_pushcclosurek(L, nestedMultipleYieldHelper, nullptr, 1, nestedMultipleYieldHelperContinuation); + else + lua_pushcclosurek(L, nestedMultipleYieldHelperNonYielding, nullptr, 1, nullptr); + + return luaL_callyieldable(L, 0, 1); +} + +int multipleYieldsWithNestedCallContinuation(lua_State* L, int status) +{ + int state = luaL_checkinteger(L, 3); + luaL_checkstack(L, 1, "cnestedmultiyieldcont"); + lua_pushinteger(L, state + 1); + lua_replace(L, 3); + + if (state == 0) + { + return lua_yield(L, lua_gettop(L) - 3); + } + else if (state == 1) + { + lua_pushnumber(L, luaL_checkinteger(L, 1) + 200); + return lua_yield(L, 1); + } + else + { + lua_pushnumber(L, luaL_checkinteger(L, 1) + 210); + return 1; + } +} + +int passthroughCall(lua_State* L) +{ + luaL_checkstack(L, 3, "cpass"); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_pushvalue(L, 3); + return luaL_callyieldable(L, 2, 1); +} + +int passthroughCallContinuation(lua_State* L, int status) +{ + LUAU_ASSERT(lua_gettop(L) == 4); // 3 original arguments and the return value + LUAU_ASSERT(lua_tonumber(L, -1) == 0.5); + return 1; +} + +int passthroughCallMoreResults(lua_State* L) +{ + luaL_checkstack(L, 3, "cpass"); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_pushvalue(L, 3); + return luaL_callyieldable(L, 2, 10); +} + +int passthroughCallMoreResultsContinuation(lua_State* L, int status) +{ + LUAU_ASSERT(lua_gettop(L) == 13); // 3 original arguments and 10 requested return values + + for (int i = 0; i < 9; i++) + { + LUAU_ASSERT(lua_isnil(L, -1)); + lua_pop(L, 1); + } + + LUAU_ASSERT(lua_tonumber(L, -1) == 0.5); + return 1; +} + +int passthroughCallArgReuse(lua_State* L) +{ + return luaL_callyieldable(L, 2, 1); +} + +int passthroughCallArgReuseContinuation(lua_State* L, int status) +{ + LUAU_ASSERT(lua_gettop(L) == 1); // Original arguments were consumed, only return remains + LUAU_ASSERT(lua_tonumber(L, -1) == 0.5); + return 1; +} + +int passthroughCallVaradic(lua_State* L) +{ + luaL_checkany(L, 1); + return luaL_callyieldable(L, lua_gettop(L) - 1, LUA_MULTRET); +} + +int passthroughCallVaradicContinuation(lua_State* L, int status) +{ + return lua_gettop(L); +} + +int passthroughCallWithState(lua_State* L) +{ + luaL_checkany(L, 1); + int args = lua_gettop(L) - 1; + + lua_pushnumber(L, 42); + lua_insert(L, 1); + + return luaL_callyieldable(L, args, LUA_MULTRET); +} + +int passthroughCallWithStateContinuation(lua_State* L, int status) +{ + LUAU_ASSERT(luaL_checkinteger(L, 1) == 42); + + return lua_gettop(L) - 1; +} + +TEST_CASE("CYield") +{ + ScopedFastFlag luauYieldableContinuations{FFlag::LuauYieldableContinuations, true}; + + runConformance( + "cyield.luau", + [](lua_State* L) + { + lua_pushcclosurek(L, singleYield, "singleYield", 0, singleYieldContinuation); + lua_setglobal(L, "singleYield"); + + lua_pushcclosurek(L, multipleYields, "multipleYields", 0, multipleYieldsContinuation); + lua_setglobal(L, "multipleYields"); + + lua_pushcclosurek(L, multipleYieldsWithNestedCall, "multipleYieldsWithNestedCall", 0, multipleYieldsWithNestedCallContinuation); + lua_setglobal(L, "multipleYieldsWithNestedCall"); + + lua_pushcclosurek(L, passthroughCall, "passthroughCall", 0, passthroughCallContinuation); + lua_setglobal(L, "passthroughCall"); + + lua_pushcclosurek(L, passthroughCallMoreResults, "passthroughCallMoreResults", 0, passthroughCallMoreResultsContinuation); + lua_setglobal(L, "passthroughCallMoreResults"); + + lua_pushcclosurek(L, passthroughCallArgReuse, "passthroughCallArgReuse", 0, passthroughCallArgReuseContinuation); + lua_setglobal(L, "passthroughCallArgReuse"); + + lua_pushcclosurek(L, passthroughCallVaradic, "passthroughCallVaradic", 0, passthroughCallVaradicContinuation); + lua_setglobal(L, "passthroughCallVaradic"); + + lua_pushcclosurek(L, passthroughCallWithState, "passthroughCallWithState", 0, passthroughCallWithStateContinuation); + lua_setglobal(L, "passthroughCallWithState"); + } + ); +} + TEST_CASE("Vector") { lua_CompileOptions copts = defaultOptions(); @@ -957,7 +1188,7 @@ static void populateRTTI(lua_State* L, Luau::TypeId type) lua_pushstring(L, "function"); } - else if (auto c = Luau::get(type)) + else if (auto c = Luau::get(type)) { lua_pushstring(L, c->name.c_str()); } diff --git a/tests/Differ.test.cpp b/tests/Differ.test.cpp index 8050974e..8bf8dd1f 100644 --- a/tests/Differ.test.cpp +++ b/tests/Differ.test.cpp @@ -1583,7 +1583,7 @@ TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "metatable_metamissing_right") ); } -TEST_CASE_FIXTURE(DifferFixtureGeneric, "equal_class") +TEST_CASE_FIXTURE(DifferFixtureGeneric, "equal_class") { CheckResult result = check(R"( local foo = BaseClass @@ -1594,7 +1594,7 @@ TEST_CASE_FIXTURE(DifferFixtureGeneric, "equal_class") compareTypesEq("foo", "almostFoo"); } -TEST_CASE_FIXTURE(DifferFixtureGeneric, "class_normal") +TEST_CASE_FIXTURE(DifferFixtureGeneric, "class_normal") { CheckResult result = check(R"( local foo = BaseClass diff --git a/tests/EqSatSimplification.test.cpp b/tests/EqSatSimplification.test.cpp index 6fe2660f..63998be3 100644 --- a/tests/EqSatSimplification.test.cpp +++ b/tests/EqSatSimplification.test.cpp @@ -33,7 +33,7 @@ struct ESFixture : Fixture ESFixture() : simplifier(newSimplifier(arena, builtinTypes)) { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); ScopePtr moduleScope = frontend.globals.globalScope; @@ -234,7 +234,7 @@ TEST_CASE_FIXTURE(ESFixture, "nil | boolean | number | string | thread | functio builtinTypes->threadType, builtinTypes->functionType, builtinTypes->tableType, - builtinTypes->classType, + builtinTypes->externType, builtinTypes->bufferType, }})) ); @@ -262,12 +262,12 @@ TEST_CASE_FIXTURE(ESFixture, "Child | Parent") TEST_CASE_FIXTURE(ESFixture, "class | Child") { - CHECK("class" == simplifyStr(arena->addType(UnionType{{builtinTypes->classType, childClass}}))); + CHECK("class" == simplifyStr(arena->addType(UnionType{{builtinTypes->externType, childClass}}))); } TEST_CASE_FIXTURE(ESFixture, "Parent | class | Child") { - CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, builtinTypes->classType, childClass}}))); + CHECK("class" == simplifyStr(arena->addType(UnionType{{parentClass, builtinTypes->externType, childClass}}))); } TEST_CASE_FIXTURE(ESFixture, "Parent | Unrelated") @@ -311,7 +311,7 @@ TEST_CASE_FIXTURE(ESFixture, "boolean & (true | number | string | thread | funct builtinTypes->threadType, builtinTypes->functionType, builtinTypes->tableType, - builtinTypes->classType, + builtinTypes->externType, builtinTypes->bufferType, }}); diff --git a/tests/Fixture.cpp b/tests/Fixture.cpp index 6dd69dba..b3c7c776 100644 --- a/tests/Fixture.cpp +++ b/tests/Fixture.cpp @@ -847,14 +847,14 @@ void registerHiddenTypes(Frontend* frontend) globalScope->exportedTypeBindings["Not"] = TypeFun{{genericT}, globals.globalTypes.addType(NegationType{t})}; globalScope->exportedTypeBindings["Mt"] = TypeFun{{genericT, genericU}, globals.globalTypes.addType(MetatableType{t, u})}; globalScope->exportedTypeBindings["fun"] = TypeFun{{}, frontend->builtinTypes->functionType}; - globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->classType}; + globalScope->exportedTypeBindings["cls"] = TypeFun{{}, frontend->builtinTypes->externType}; globalScope->exportedTypeBindings["err"] = TypeFun{{}, frontend->builtinTypes->errorType}; globalScope->exportedTypeBindings["tbl"] = TypeFun{{}, frontend->builtinTypes->tableType}; freeze(globals.globalTypes); } -void createSomeClasses(Frontend* frontend) +void createSomeExternTypes(Frontend* frontend) { GlobalTypes& globals = frontend->globals; @@ -863,27 +863,27 @@ void createSomeClasses(Frontend* frontend) ScopePtr moduleScope = globals.globalScope; - TypeId parentType = arena.addType(ClassType{"Parent", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test", {}}); + TypeId parentType = arena.addType(ExternType{"Parent", {}, frontend->builtinTypes->externType, std::nullopt, {}, nullptr, "Test", {}}); - ClassType* parentClass = getMutable(parentType); - parentClass->props["method"] = {makeFunction(arena, parentType, {}, {})}; + ExternType* parentExternType = getMutable(parentType); + parentExternType->props["method"] = {makeFunction(arena, parentType, {}, {})}; - parentClass->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})}; + parentExternType->props["virtual_method"] = {makeFunction(arena, parentType, {}, {})}; addGlobalBinding(globals, "Parent", {parentType}); moduleScope->exportedTypeBindings["Parent"] = TypeFun{{}, parentType}; - TypeId childType = arena.addType(ClassType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test", {}}); + TypeId childType = arena.addType(ExternType{"Child", {}, parentType, std::nullopt, {}, nullptr, "Test", {}}); addGlobalBinding(globals, "Child", {childType}); moduleScope->exportedTypeBindings["Child"] = TypeFun{{}, childType}; - TypeId anotherChildType = arena.addType(ClassType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test", {}}); + TypeId anotherChildType = arena.addType(ExternType{"AnotherChild", {}, parentType, std::nullopt, {}, nullptr, "Test", {}}); addGlobalBinding(globals, "AnotherChild", {anotherChildType}); moduleScope->exportedTypeBindings["AnotherChild"] = TypeFun{{}, anotherChildType}; - TypeId unrelatedType = arena.addType(ClassType{"Unrelated", {}, frontend->builtinTypes->classType, std::nullopt, {}, nullptr, "Test", {}}); + TypeId unrelatedType = arena.addType(ExternType{"Unrelated", {}, frontend->builtinTypes->externType, std::nullopt, {}, nullptr, "Test", {}}); addGlobalBinding(globals, "Unrelated", {unrelatedType}); moduleScope->exportedTypeBindings["Unrelated"] = TypeFun{{}, unrelatedType}; diff --git a/tests/Fixture.h b/tests/Fixture.h index 1ad89b34..2d0f9790 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -213,7 +213,7 @@ std::optional lookupName(ScopePtr scope, const std::string& name); // Wa std::optional linearSearchForBinding(Scope* scope, const char* name); void registerHiddenTypes(Frontend* frontend); -void createSomeClasses(Frontend* frontend); +void createSomeExternTypes(Frontend* frontend); template const E* findError(const CheckResult& result) diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 5bc51ac9..04d6e48c 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -23,7 +23,6 @@ using namespace Luau; -LUAU_FASTFLAG(LuauAutocompleteRefactorsForIncrementalAutocomplete) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauCloneIncrementalModule) @@ -34,7 +33,6 @@ LUAU_FASTFLAG(LuauBetterReverseDependencyTracking) LUAU_FASTFLAG(LuauAutocompleteUsesModuleForTypeCompatibility) LUAU_FASTFLAG(LuauBetterCursorInCommentDetection) LUAU_FASTFLAG(LuauAllFreeTypesHaveScopes) -LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauClonedTableAndFunctionTypesMustHaveScopes) LUAU_FASTFLAG(LuauDisableNewSolverAssertsInMixedMode) LUAU_FASTFLAG(LuauCloneTypeAliasBindings) @@ -46,7 +44,7 @@ LUAU_FASTFLAG(LuauBlockDiffFragmentSelection) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) -static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) +static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { return std::nullopt; } @@ -74,7 +72,6 @@ struct FragmentAutocompleteFixtureImpl : BaseType { static_assert(std::is_base_of_v, "BaseType must be a descendant of Fixture"); - ScopedFastFlag luauAutocompleteRefactorsForIncrementalAutocomplete{FFlag::LuauAutocompleteRefactorsForIncrementalAutocomplete, true}; ScopedFastFlag luauFreeTypesMustHaveBounds{FFlag::LuauFreeTypesMustHaveBounds, true}; ScopedFastFlag luauCloneIncrementalModule{FFlag::LuauCloneIncrementalModule, true}; ScopedFastFlag luauAllFreeTypesHaveScopes{FFlag::LuauAllFreeTypesHaveScopes, true}; @@ -386,7 +383,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "inside_do") R"( local x = 4 do - + end )", {3, 3} @@ -492,7 +489,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "writing_func_annotation") { auto region = getAutocompleteRegion( R"( -function f(arg1 : T +function f(arg1 : T )", {1, 19} ); @@ -755,7 +752,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial") { auto region = getAutocompleteRegion( R"( -if +if )", Position{1, 3} ); @@ -768,7 +765,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial_in_condition_at") { auto region = getAutocompleteRegion( R"( -if true +if true )", Position{1, 7} ); @@ -781,7 +778,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_partial_in_condition_after") { auto region = getAutocompleteRegion( R"( -if true +if true )", Position{1, 8} ); @@ -838,7 +835,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if") auto region = getAutocompleteRegion( R"( if true then -elseif +elseif end )", @@ -854,7 +851,7 @@ TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "if_else_if_no_end") auto region = getAutocompleteRegion( R"( if true then -elseif +elseif )", Position{2, 8} ); @@ -1463,7 +1460,7 @@ local tbl = { abc = 1234} auto fragment = autocompleteFragment( R"( local tbl = { abc = 1234} -tbl. +tbl. )", Position{2, 5} ); @@ -1589,7 +1586,7 @@ local tbl = { abc = 1234} )"; const std::string updated = R"( local tbl = { abc = 1234} -tbl. +tbl. )"; autocompleteFragmentInBothSolvers( @@ -1644,7 +1641,7 @@ end local function f2(a2) local l2 = 1; g2 = 1; -end +end )"; autocompleteFragmentInBothSolvers( @@ -3027,7 +3024,6 @@ z:a TEST_CASE_FIXTURE(FragmentAutocompleteBuiltinsFixture, "interior_free_types_assertion_caused_by_free_type_inheriting_null_scope_from_table") { - ScopedFastFlag sff{FFlag::LuauTrackInteriorFreeTypesOnScope, true}; const std::string source = R"(--!strict local foo local a = foo() diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index 8d232981..f9797784 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -17,7 +17,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) -LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) TEST_SUITE_BEGIN("Generalization"); @@ -116,12 +115,12 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "dont_traverse_into_class_types_when_ge { auto [propTy, _] = freshType(); - TypeId cursedClass = arena.addType(ClassType{"Cursed", {{"oh_no", Property::readonly(propTy)}}, std::nullopt, std::nullopt, {}, {}, "", {}}); + TypeId cursedExternType = arena.addType(ExternType{"Cursed", {{"oh_no", Property::readonly(propTy)}}, std::nullopt, std::nullopt, {}, {}, "", {}}); - auto genClass = generalize(cursedClass); - REQUIRE(genClass); + auto genExternType = generalize(cursedExternType); + REQUIRE(genExternType); - auto genPropTy = get(*genClass)->props.at("oh_no").readTy; + auto genPropTy = get(*genExternType)->props.at("oh_no").readTy; CHECK(is(*genPropTy)); } @@ -344,7 +343,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type") { ScopedFastFlag sffs[] = { {FFlag::DebugLuauForbidInternalTypes, true}, - {FFlag::LuauTrackInteriorFreeTypesOnScope, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true} }; diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index 816c6f93..84cec466 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1252,15 +1252,19 @@ _ = { [2] = 2, [1] = 3, } + +function _foo(): { first: number, second: string, first: boolean } +end )"); - REQUIRE(6 == result.warnings.size()); + REQUIRE(7 == result.warnings.size()); CHECK_EQ(result.warnings[0].text, "Table field 'first' is a duplicate; previously defined at line 3"); CHECK_EQ(result.warnings[1].text, "Table field 'first' is a duplicate; previously defined at line 9"); CHECK_EQ(result.warnings[2].text, "Table index 1 is a duplicate; previously defined as a list entry"); CHECK_EQ(result.warnings[3].text, "Table index 3 is a duplicate; previously defined as a list entry"); CHECK_EQ(result.warnings[4].text, "Table type field 'first' is a duplicate; previously defined at line 24"); CHECK_EQ(result.warnings[5].text, "Table index 1 is a duplicate; previously defined at line 36"); + CHECK_EQ(result.warnings[6].text, "Table type field 'first' is a duplicate; previously defined at line 41"); } TEST_CASE_FIXTURE(Fixture, "read_write_table_props") @@ -1299,6 +1303,19 @@ TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInTypeAnnotation") CHECK_EQ(result.warnings[0].text, "Variable 'x' is never used; prefix with '_' to silence"); } +TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInReturnType") +{ + LintResult result = lint(R"( + local Foo = require(script.Parent.Foo) + + function foo(): Foo.Y + end + )"); + + REQUIRE(1 == result.warnings.size()); + CHECK_EQ(result.warnings[0].text, "Function 'foo' is never used; prefix with '_' to silence"); +} + TEST_CASE_FIXTURE(Fixture, "DisableUnknownGlobalWithTypeChecking") { LintResult result = lint(R"( @@ -1504,11 +1521,11 @@ TEST_CASE_FIXTURE(Fixture, "LintHygieneUAF") TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiTyped") { unfreeze(frontend.globals.globalTypes); - TypeId instanceType = frontend.globals.globalTypes.addType(ClassType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test", {}}); + TypeId instanceType = frontend.globals.globalTypes.addType(ExternType{"Instance", {}, std::nullopt, std::nullopt, {}, {}, "Test", {}}); persist(instanceType); frontend.globals.globalScope->exportedTypeBindings["Instance"] = TypeFun{{}, instanceType}; - getMutable(instanceType)->props = { + getMutable(instanceType)->props = { {"Name", {builtinTypes->stringType}}, {"DataCost", {builtinTypes->numberType, /* deprecated= */ true}}, {"Wait", {builtinTypes->anyType, /* deprecated= */ true}}, @@ -1827,7 +1844,7 @@ Account = { balance=0 } function Account:deposit(v) self.balance = self.balance + v end - + Account:deposit(200.00) )"); @@ -1848,7 +1865,7 @@ end function Account:deposit (v) self.balance = self.balance + v end - + (getAccount()):deposit(200.00) )"); diff --git a/tests/Module.test.cpp b/tests/Module.test.cpp index 21b6bcf7..9564f081 100644 --- a/tests/Module.test.cpp +++ b/tests/Module.test.cpp @@ -237,7 +237,7 @@ TEST_CASE_FIXTURE(Fixture, "deepClone_intersection") TEST_CASE_FIXTURE(Fixture, "clone_class") { - Type exampleMetaClass{ClassType{ + Type exampleMetaClass{ExternType{ "ExampleClassMeta", { {"__add", {builtinTypes->anyType}}, @@ -249,7 +249,7 @@ TEST_CASE_FIXTURE(Fixture, "clone_class") "Test", {} }}; - Type exampleClass{ClassType{ + Type exampleClass{ExternType{ "ExampleClass", { {"PropOne", {builtinTypes->numberType}}, @@ -267,14 +267,14 @@ TEST_CASE_FIXTURE(Fixture, "clone_class") CloneState cloneState{builtinTypes}; TypeId cloned = clone(&exampleClass, dest, cloneState); - const ClassType* ctv = get(cloned); - REQUIRE(ctv != nullptr); + const ExternType* etv = get(cloned); + REQUIRE(etv != nullptr); - REQUIRE(ctv->metatable); - const ClassType* metatable = get(*ctv->metatable); + REQUIRE(etv->metatable); + const ExternType* metatable = get(*etv->metatable); REQUIRE(metatable); - CHECK_EQ("ExampleClass", ctv->name); + CHECK_EQ("ExampleClass", etv->name); CHECK_EQ("ExampleClassMeta", metatable->name); } diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 3ec880e0..32febf02 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauNormalizationCatchMetatableCycles) LUAU_FASTFLAG(LuauSubtypingEnableReasoningLimit) LUAU_FASTFLAG(LuauTypePackDetectCycles) @@ -323,9 +324,9 @@ TEST_CASE_FIXTURE(IsSubtypeFixture, "cyclic_table") } #endif -TEST_CASE_FIXTURE(IsSubtypeFixture, "classes") +TEST_CASE_FIXTURE(IsSubtypeFixture, "extern_types") { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); check(""); // Ensure that we have a main Module. @@ -760,7 +761,7 @@ TEST_CASE_FIXTURE(Fixture, "cyclic_table_normalizes_sensibly") TEST_CASE_FIXTURE(BuiltinsFixture, "skip_force_normal_on_external_types") { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CheckResult result = check(R"( export type t0 = { a: Child } @@ -779,24 +780,24 @@ export type t0 = (((any)&({_:l0.t0,n0:t0,_G:any,}))&({_:any,}))&(((any)&({_:l0.t LUAU_REQUIRE_ERRORS(result); } -TEST_CASE_FIXTURE(NormalizeFixture, "unions_of_classes") +TEST_CASE_FIXTURE(NormalizeFixture, "unions_of_extern_types") { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CHECK("Parent | Unrelated" == toString(normal("Parent | Unrelated"))); CHECK("Parent" == toString(normal("Parent | Child"))); CHECK("Parent | Unrelated" == toString(normal("Parent | Child | Unrelated"))); } -TEST_CASE_FIXTURE(NormalizeFixture, "intersections_of_classes") +TEST_CASE_FIXTURE(NormalizeFixture, "intersections_of_extern_types") { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CHECK("Child" == toString(normal("Parent & Child"))); CHECK("never" == toString(normal("Child & Unrelated"))); } -TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_classes_with_intersection") +TEST_CASE_FIXTURE(NormalizeFixture, "narrow_union_of_extern_types_with_intersection") { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CHECK("Child" == toString(normal("(Child | Unrelated) & Child"))); } @@ -868,9 +869,9 @@ TEST_CASE_FIXTURE(NormalizeFixture, "crazy_metatable") CHECK("never" == toString(normal("Mt<{}, number> & Mt<{}, string>"))); } -TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes") +TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_extern_types") { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CHECK("(Parent & ~Child) | Unrelated" == toString(normal("(Parent & Not) | Unrelated"))); CHECK("((class & ~Child) | boolean | buffer | function | number | string | table | thread)?" == toString(normal("Not"))); CHECK("never" == toString(normal("Not & Child"))); @@ -883,15 +884,15 @@ TEST_CASE_FIXTURE(NormalizeFixture, "negations_of_classes") CHECK("Child" == toString(normal("(Child | Unrelated) & Not"))); } -TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_unknown") +TEST_CASE_FIXTURE(NormalizeFixture, "extern_types_and_unknown") { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CHECK("Parent" == toString(normal("Parent & unknown"))); } -TEST_CASE_FIXTURE(NormalizeFixture, "classes_and_never") +TEST_CASE_FIXTURE(NormalizeFixture, "extern_types_and_never") { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CHECK("never" == toString(normal("Parent & never"))); } @@ -1238,6 +1239,12 @@ do end TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") { + // FIXME? This test code happens not to ICE with eager generalization + // enabled. This could either be because the problem is fixed, or because + // another bug is obscuring the problem. + if (FFlag::DebugLuauGreedyGeneralization) + return; + ScopedFastFlag sff[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTypePackDetectCycles, true}}; // Note: if this stops throwing an exception, it means we fixed cycle construction and can replace with a regular check diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index 39fe3662..f8c6d63f 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -3,6 +3,7 @@ #include "AstQueryDsl.h" #include "Fixture.h" +#include "Luau/Ast.h" #include "Luau/Common.h" #include "ScopedFlags.h" @@ -16,12 +17,13 @@ LUAU_FASTINT(LuauRecursionLimit) LUAU_FASTINT(LuauTypeLengthLimit) LUAU_FASTINT(LuauParseErrorLimit) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauAstTypeGroup3) LUAU_FASTFLAG(LuauFixDoBlockEndLocation) LUAU_FASTFLAG(LuauParseOptionalAsNode2) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauParseStringIndexer) LUAU_FASTFLAG(LuauFixFunctionWithAttributesStartLocation) +LUAU_FASTFLAG(LuauDeclareExternType) LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_DYNAMIC_FASTFLAG(DebugLuauReportReturnTypeVariadicWithTypeSuffix) @@ -154,9 +156,20 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_return_annotations") AstStatFunction* statFunction = block->body.data[0]->as(); REQUIRE(statFunction != nullptr); - REQUIRE(statFunction->func->returnAnnotation.has_value()); - CHECK_EQ(statFunction->func->returnAnnotation->types.size, 1); - CHECK(statFunction->func->returnAnnotation->tailType == nullptr); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + REQUIRE(statFunction->func->returnAnnotation); + auto typePack = statFunction->func->returnAnnotation->as(); + REQUIRE(typePack); + CHECK_EQ(typePack->typeList.types.size, 1); + CHECK(typePack->typeList.tailType == nullptr); + } + else + { + REQUIRE(statFunction->func->returnAnnotation_DEPRECATED.has_value()); + CHECK_EQ(statFunction->func->returnAnnotation_DEPRECATED->types.size, 1); + CHECK(statFunction->func->returnAnnotation_DEPRECATED->tailType == nullptr); + } } TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation") @@ -171,13 +184,28 @@ TEST_CASE_FIXTURE(Fixture, "functions_can_have_a_function_type_annotation") AstStatFunction* statFunc = block->body.data[0]->as(); REQUIRE(statFunc != nullptr); - REQUIRE(statFunc->func->returnAnnotation.has_value()); - CHECK(statFunc->func->returnAnnotation->tailType == nullptr); - AstArray& retTypes = statFunc->func->returnAnnotation->types; - REQUIRE(retTypes.size == 1); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + REQUIRE(statFunc->func->returnAnnotation); + auto typePack = statFunc->func->returnAnnotation->as(); + REQUIRE(typePack); + CHECK(typePack->typeList.tailType == nullptr); + AstArray& retTypes = typePack->typeList.types; + REQUIRE(retTypes.size == 1); - AstTypeFunction* funTy = retTypes.data[0]->as(); - REQUIRE(funTy != nullptr); + AstTypeFunction* funTy = retTypes.data[0]->as(); + REQUIRE(funTy != nullptr); + } + else + { + REQUIRE(statFunc->func->returnAnnotation_DEPRECATED.has_value()); + CHECK(statFunc->func->returnAnnotation_DEPRECATED->tailType == nullptr); + AstArray& retTypes = statFunc->func->returnAnnotation_DEPRECATED->types; + REQUIRE(retTypes.size == 1); + + AstTypeFunction* funTy = retTypes.data[0]->as(); + REQUIRE(funTy != nullptr); + } } TEST_CASE_FIXTURE(Fixture, "function_return_type_should_disambiguate_from_function_type_and_multiple_returns") @@ -192,18 +220,38 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_disambiguate_from_functi AstStatFunction* statFunc = block->body.data[0]->as(); REQUIRE(statFunc != nullptr); - REQUIRE(statFunc->func->returnAnnotation.has_value()); - CHECK(statFunc->func->returnAnnotation->tailType == nullptr); - AstArray& retTypes = statFunc->func->returnAnnotation->types; - REQUIRE(retTypes.size == 2); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + REQUIRE(statFunc->func->returnAnnotation); + auto typePack = statFunc->func->returnAnnotation->as(); + REQUIRE(typePack); + CHECK(typePack->typeList.tailType == nullptr); + AstArray& retTypes = typePack->typeList.types; + REQUIRE(retTypes.size == 2); - AstTypeReference* ty0 = retTypes.data[0]->as(); - REQUIRE(ty0 != nullptr); - REQUIRE(ty0->name == "number"); + AstTypeReference* ty0 = retTypes.data[0]->as(); + REQUIRE(ty0 != nullptr); + REQUIRE(ty0->name == "number"); - AstTypeReference* ty1 = retTypes.data[1]->as(); - REQUIRE(ty1 != nullptr); - REQUIRE(ty1->name == "string"); + AstTypeReference* ty1 = retTypes.data[1]->as(); + REQUIRE(ty1 != nullptr); + REQUIRE(ty1->name == "string"); + } + else + { + REQUIRE(statFunc->func->returnAnnotation_DEPRECATED.has_value()); + CHECK(statFunc->func->returnAnnotation_DEPRECATED->tailType == nullptr); + AstArray& retTypes = statFunc->func->returnAnnotation_DEPRECATED->types; + REQUIRE(retTypes.size == 2); + + AstTypeReference* ty0 = retTypes.data[0]->as(); + REQUIRE(ty0 != nullptr); + REQUIRE(ty0->name == "number"); + + AstTypeReference* ty1 = retTypes.data[1]->as(); + REQUIRE(ty1 != nullptr); + REQUIRE(ty1->name == "string"); + } } TEST_CASE_FIXTURE(Fixture, "function_return_type_should_parse_as_function_type_annotation_with_no_args") @@ -218,20 +266,45 @@ TEST_CASE_FIXTURE(Fixture, "function_return_type_should_parse_as_function_type_a AstStatFunction* statFunc = block->body.data[0]->as(); REQUIRE(statFunc != nullptr); - REQUIRE(statFunc->func->returnAnnotation.has_value()); - CHECK(statFunc->func->returnAnnotation->tailType == nullptr); - AstArray& retTypes = statFunc->func->returnAnnotation->types; - REQUIRE(retTypes.size == 1); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + REQUIRE(statFunc->func->returnAnnotation); + auto typePack = statFunc->func->returnAnnotation->as(); + REQUIRE(typePack); + CHECK(typePack->typeList.tailType == nullptr); + AstArray& retTypes = typePack->typeList.types; + REQUIRE(retTypes.size == 1); - AstTypeFunction* funTy = retTypes.data[0]->as(); - REQUIRE(funTy != nullptr); - REQUIRE(funTy->argTypes.types.size == 0); - CHECK(funTy->argTypes.tailType == nullptr); - CHECK(funTy->returnTypes.tailType == nullptr); + AstTypeFunction* funTy = retTypes.data[0]->as(); + REQUIRE(funTy != nullptr); + REQUIRE(funTy->argTypes.types.size == 0); + CHECK(funTy->argTypes.tailType == nullptr); - AstTypeReference* ty = funTy->returnTypes.types.data[0]->as(); - REQUIRE(ty != nullptr); - REQUIRE(ty->name == "nil"); + auto funReturnPack = funTy->returnTypes->as(); + REQUIRE(funReturnPack); + CHECK(funReturnPack->typeList.tailType == nullptr); + + AstTypeReference* ty = funReturnPack->typeList.types.data[0]->as(); + REQUIRE(ty != nullptr); + REQUIRE(ty->name == "nil"); + } + else + { + REQUIRE(statFunc->func->returnAnnotation_DEPRECATED.has_value()); + CHECK(statFunc->func->returnAnnotation_DEPRECATED->tailType == nullptr); + AstArray& retTypes = statFunc->func->returnAnnotation_DEPRECATED->types; + REQUIRE(retTypes.size == 1); + + AstTypeFunction* funTy = retTypes.data[0]->as(); + REQUIRE(funTy != nullptr); + REQUIRE(funTy->argTypes.types.size == 0); + CHECK(funTy->argTypes.tailType == nullptr); + CHECK(funTy->returnTypes_DEPRECATED.tailType == nullptr); + + AstTypeReference* ty = funTy->returnTypes_DEPRECATED.types.data[0]->as(); + REQUIRE(ty != nullptr); + REQUIRE(ty->name == "nil"); + } } TEST_CASE_FIXTURE(Fixture, "annotations_can_be_tables") @@ -373,7 +446,17 @@ TEST_CASE_FIXTURE(Fixture, "return_type_is_an_intersection_type_if_led_with_one_ AstTypeFunction* annotation = local->vars.data[0]->annotation->as(); REQUIRE(annotation != nullptr); - AstTypeIntersection* returnAnnotation = annotation->returnTypes.types.data[0]->as(); + AstTypeIntersection* returnAnnotation; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + auto returnTypePack = annotation->returnTypes->as(); + REQUIRE(returnTypePack); + returnAnnotation = returnTypePack->typeList.types.data[0]->as(); + } + else + { + returnAnnotation = annotation->returnTypes_DEPRECATED.types.data[0]->as(); + } REQUIRE(returnAnnotation != nullptr); if (FFlag::LuauAstTypeGroup3) CHECK(returnAnnotation->types.data[0]->as()); @@ -1928,7 +2011,16 @@ TEST_CASE_FIXTURE(Fixture, "parse_declarations") CHECK(func->name == "bar"); CHECK(func->nameLocation == Location({2, 25}, {2, 28})); REQUIRE_EQ(func->params.types.size, 1); - REQUIRE_EQ(func->retTypes.types.size, 1); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + auto retTypePack = func->retTypes->as(); + REQUIRE(retTypePack); + REQUIRE_EQ(retTypePack->typeList.types.size, 1); + } + else + { + REQUIRE_EQ(func->retTypes_DEPRECATED.types.size, 1); + } AstStatDeclareFunction* varFunc = stat->body.data[2]->as(); REQUIRE(varFunc); @@ -1958,34 +2050,250 @@ TEST_CASE_FIXTURE(Fixture, "parse_class_declarations") REQUIRE_EQ(stat->body.size, 2); - AstStatDeclareClass* declaredClass = stat->body.data[0]->as(); - REQUIRE(declaredClass); - CHECK(declaredClass->name == "Foo"); - CHECK(!declaredClass->superName); + AstStatDeclareExternType* declaredExternType = stat->body.data[0]->as(); + REQUIRE(declaredExternType); + CHECK(declaredExternType->name == "Foo"); + CHECK(!declaredExternType->superName); - REQUIRE_EQ(declaredClass->props.size, 2); + REQUIRE_EQ(declaredExternType->props.size, 2); - AstDeclaredClassProp& prop = declaredClass->props.data[0]; + AstDeclaredExternTypeProperty& prop = declaredExternType->props.data[0]; CHECK(prop.name == "prop"); CHECK(prop.nameLocation == Location({2, 12}, {2, 16})); CHECK(prop.ty->is()); CHECK(prop.location == Location({2, 12}, {2, 24})); - AstDeclaredClassProp& method = declaredClass->props.data[1]; + AstDeclaredExternTypeProperty& method = declaredExternType->props.data[1]; CHECK(method.name == "method"); CHECK(method.nameLocation == Location({3, 21}, {3, 27})); CHECK(method.ty->is()); CHECK(method.location == Location({3, 12}, {3, 54})); CHECK(method.isMethod); - AstStatDeclareClass* subclass = stat->body.data[1]->as(); + AstStatDeclareExternType* subclass = stat->body.data[1]->as(); REQUIRE(subclass); REQUIRE(subclass->superName); CHECK(subclass->name == "Bar"); CHECK(*subclass->superName == "Foo"); REQUIRE_EQ(subclass->props.size, 1); - AstDeclaredClassProp& prop2 = subclass->props.data[0]; + AstDeclaredExternTypeProperty& prop2 = subclass->props.data[0]; + CHECK(prop2.name == "prop2"); + CHECK(prop2.nameLocation == Location({7, 12}, {7, 17})); + CHECK(prop2.ty->is()); + CHECK(prop2.location == Location({7, 12}, {7, 25})); +} + +TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations") +{ + ScopedFastFlag sff{FFlag::LuauDeclareExternType, true}; + + AstStatBlock* stat = parseEx(R"( + declare extern type Foo with + prop: number + function method(self, foo: number): string + end + + declare extern type Bar extends Foo with + prop2: string + end + )").root; + + REQUIRE_EQ(stat->body.size, 2); + + AstStatDeclareExternType* declaredExternType = stat->body.data[0]->as(); + REQUIRE(declaredExternType); + CHECK(declaredExternType->name == "Foo"); + CHECK(!declaredExternType->superName); + + REQUIRE_EQ(declaredExternType->props.size, 2); + + AstDeclaredExternTypeProperty& prop = declaredExternType->props.data[0]; + CHECK(prop.name == "prop"); + CHECK(prop.nameLocation == Location({2, 12}, {2, 16})); + CHECK(prop.ty->is()); + CHECK(prop.location == Location({2, 12}, {2, 24})); + + AstDeclaredExternTypeProperty& method = declaredExternType->props.data[1]; + CHECK(method.name == "method"); + CHECK(method.nameLocation == Location({3, 21}, {3, 27})); + CHECK(method.ty->is()); + CHECK(method.location == Location({3, 12}, {3, 54})); + CHECK(method.isMethod); + + AstStatDeclareExternType* subclass = stat->body.data[1]->as(); + REQUIRE(subclass); + REQUIRE(subclass->superName); + CHECK(subclass->name == "Bar"); + CHECK(*subclass->superName == "Foo"); + + REQUIRE_EQ(subclass->props.size, 1); + AstDeclaredExternTypeProperty& prop2 = subclass->props.data[0]; + CHECK(prop2.name == "prop2"); + CHECK(prop2.nameLocation == Location({7, 12}, {7, 17})); + CHECK(prop2.ty->is()); + CHECK(prop2.location == Location({7, 12}, {7, 25})); +} + +TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with") +{ + ScopedFastFlag sff{FFlag::LuauDeclareExternType, true}; + + ParseResult result = tryParse(R"( + declare extern type Foo + prop: number + function method(self, foo: number): string + end + + declare extern type Bar extends Foo + prop2: string + end + )"); + + REQUIRE_EQ(result.errors.size(), 2); + CHECK("Expected `with` keyword before listing properties of the external type, but got prop instead" == result.errors[0].getMessage()); + CHECK("Expected `with` keyword before listing properties of the external type, but got prop2 instead" == result.errors[1].getMessage()); + + AstStatBlock* stat = result.root; + + REQUIRE_EQ(stat->body.size, 2); + + AstStatDeclareExternType* declaredExternType = stat->body.data[0]->as(); + REQUIRE(declaredExternType); + CHECK(declaredExternType->name == "Foo"); + CHECK(!declaredExternType->superName); + + REQUIRE_EQ(declaredExternType->props.size, 2); + + AstDeclaredExternTypeProperty& prop = declaredExternType->props.data[0]; + CHECK(prop.name == "prop"); + CHECK(prop.nameLocation == Location({2, 12}, {2, 16})); + CHECK(prop.ty->is()); + CHECK(prop.location == Location({2, 12}, {2, 24})); + + AstDeclaredExternTypeProperty& method = declaredExternType->props.data[1]; + CHECK(method.name == "method"); + CHECK(method.nameLocation == Location({3, 21}, {3, 27})); + CHECK(method.ty->is()); + CHECK(method.location == Location({3, 12}, {3, 54})); + CHECK(method.isMethod); + + AstStatDeclareExternType* subclass = stat->body.data[1]->as(); + REQUIRE(subclass); + REQUIRE(subclass->superName); + CHECK(subclass->name == "Bar"); + CHECK(*subclass->superName == "Foo"); + + REQUIRE_EQ(subclass->props.size, 1); + AstDeclaredExternTypeProperty& prop2 = subclass->props.data[0]; + CHECK(prop2.name == "prop2"); + CHECK(prop2.nameLocation == Location({7, 12}, {7, 17})); + CHECK(prop2.ty->is()); + CHECK(prop2.location == Location({7, 12}, {7, 25})); +} + +TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations") +{ + ScopedFastFlag sff{FFlag::LuauDeclareExternType, true}; + + AstStatBlock* stat = parseEx(R"( + declare extern type Foo with + prop: number + function method(self, foo: number): string + end + + declare extern type Bar extends Foo with + prop2: string + end + )").root; + + REQUIRE_EQ(stat->body.size, 2); + + AstStatDeclareExternType* declaredExternType = stat->body.data[0]->as(); + REQUIRE(declaredExternType); + CHECK(declaredExternType->name == "Foo"); + CHECK(!declaredExternType->superName); + + REQUIRE_EQ(declaredExternType->props.size, 2); + + AstDeclaredExternTypeProperty& prop = declaredExternType->props.data[0]; + CHECK(prop.name == "prop"); + CHECK(prop.nameLocation == Location({2, 12}, {2, 16})); + CHECK(prop.ty->is()); + CHECK(prop.location == Location({2, 12}, {2, 24})); + + AstDeclaredExternTypeProperty& method = declaredExternType->props.data[1]; + CHECK(method.name == "method"); + CHECK(method.nameLocation == Location({3, 21}, {3, 27})); + CHECK(method.ty->is()); + CHECK(method.location == Location({3, 12}, {3, 54})); + CHECK(method.isMethod); + + AstStatDeclareExternType* subclass = stat->body.data[1]->as(); + REQUIRE(subclass); + REQUIRE(subclass->superName); + CHECK(subclass->name == "Bar"); + CHECK(*subclass->superName == "Foo"); + + REQUIRE_EQ(subclass->props.size, 1); + AstDeclaredExternTypeProperty& prop2 = subclass->props.data[0]; + CHECK(prop2.name == "prop2"); + CHECK(prop2.nameLocation == Location({7, 12}, {7, 17})); + CHECK(prop2.ty->is()); + CHECK(prop2.location == Location({7, 12}, {7, 25})); +} + +TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations_missing_with") +{ + ScopedFastFlag sff{FFlag::LuauDeclareExternType, true}; + + ParseResult result = tryParse(R"( + declare extern type Foo + prop: number + function method(self, foo: number): string + end + + declare extern type Bar extends Foo + prop2: string + end + )"); + + REQUIRE_EQ(result.errors.size(), 2); + CHECK("Expected `with` keyword before listing properties of the external type, but got prop instead" == result.errors[0].getMessage()); + CHECK("Expected `with` keyword before listing properties of the external type, but got prop2 instead" == result.errors[1].getMessage()); + + AstStatBlock* stat = result.root; + + REQUIRE_EQ(stat->body.size, 2); + + AstStatDeclareExternType* declaredExternType = stat->body.data[0]->as(); + REQUIRE(declaredExternType); + CHECK(declaredExternType->name == "Foo"); + CHECK(!declaredExternType->superName); + + REQUIRE_EQ(declaredExternType->props.size, 2); + + AstDeclaredExternTypeProperty& prop = declaredExternType->props.data[0]; + CHECK(prop.name == "prop"); + CHECK(prop.nameLocation == Location({2, 12}, {2, 16})); + CHECK(prop.ty->is()); + CHECK(prop.location == Location({2, 12}, {2, 24})); + + AstDeclaredExternTypeProperty& method = declaredExternType->props.data[1]; + CHECK(method.name == "method"); + CHECK(method.nameLocation == Location({3, 21}, {3, 27})); + CHECK(method.ty->is()); + CHECK(method.location == Location({3, 12}, {3, 54})); + CHECK(method.isMethod); + + AstStatDeclareExternType* subclass = stat->body.data[1]->as(); + REQUIRE(subclass); + REQUIRE(subclass->superName); + CHECK(subclass->name == "Bar"); + CHECK(*subclass->superName == "Foo"); + + REQUIRE_EQ(subclass->props.size, 1); + AstDeclaredExternTypeProperty& prop2 = subclass->props.data[0]; CHECK(prop2.name == "prop2"); CHECK(prop2.nameLocation == Location({7, 12}, {7, 17})); CHECK(prop2.ty->is()); @@ -2007,7 +2315,7 @@ TEST_CASE_FIXTURE(Fixture, "class_method_properties") REQUIRE_EQ(1, p1.root->body.size); - AstStatDeclareClass* klass = p1.root->body.data[0]->as(); + AstStatDeclareExternType* klass = p1.root->body.data[0]->as(); REQUIRE(klass != nullptr); CHECK_EQ(2, klass->props.size); @@ -2024,7 +2332,7 @@ TEST_CASE_FIXTURE(Fixture, "class_method_properties") REQUIRE_EQ(1, p2.root->body.size); - AstStatDeclareClass* klass2 = p2.root->body.data[0]->as(); + AstStatDeclareExternType* klass2 = p2.root->body.data[0]->as(); REQUIRE(klass2 != nullptr); CHECK_EQ(2, klass2->props.size); @@ -2042,13 +2350,13 @@ TEST_CASE_FIXTURE(Fixture, "class_indexer") REQUIRE_EQ(stat->body.size, 1); - AstStatDeclareClass* declaredClass = stat->body.data[0]->as(); - REQUIRE(declaredClass); - REQUIRE(declaredClass->indexer); - REQUIRE(declaredClass->indexer->indexType->is()); - CHECK(declaredClass->indexer->indexType->as()->name == "string"); - REQUIRE(declaredClass->indexer->resultType->is()); - CHECK(declaredClass->indexer->resultType->as()->name == "number"); + AstStatDeclareExternType* declaredExternType = stat->body.data[0]->as(); + REQUIRE(declaredExternType); + REQUIRE(declaredExternType->indexer); + REQUIRE(declaredExternType->indexer->indexType->is()); + CHECK(declaredExternType->indexer->indexType->as()->name == "string"); + REQUIRE(declaredExternType->indexer->resultType->is()); + CHECK(declaredExternType->indexer->resultType->as()->name == "number"); const ParseResult p1 = matchParseError( R"( @@ -2058,12 +2366,14 @@ TEST_CASE_FIXTURE(Fixture, "class_indexer") [number]: number end )", - "Cannot have more than one class indexer" + (FFlag::LuauDeclareExternType) + ? "Cannot have more than one indexer on an extern type" + : "Cannot have more than one class indexer" ); REQUIRE_EQ(1, p1.root->body.size); - AstStatDeclareClass* klass = p1.root->body.data[0]->as(); + AstStatDeclareExternType* klass = p1.root->body.data[0]->as(); REQUIRE(klass != nullptr); CHECK(klass->indexer); } @@ -2093,8 +2403,15 @@ TEST_CASE_FIXTURE(Fixture, "parse_variadics") REQUIRE(fnFoo); CHECK_EQ(fnFoo->argTypes.types.size, 2); CHECK(fnFoo->argTypes.tailType); - CHECK_EQ(fnFoo->returnTypes.types.size, 0); - CHECK(fnFoo->returnTypes.tailType); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + CHECK(fnFoo->returnTypes->is()); + } + else + { + CHECK_EQ(fnFoo->returnTypes_DEPRECATED.types.size, 0); + CHECK(fnFoo->returnTypes_DEPRECATED.tailType); + } AstStatTypeAlias* bar = stat->body.data[2]->as(); REQUIRE(bar); @@ -2102,8 +2419,18 @@ TEST_CASE_FIXTURE(Fixture, "parse_variadics") REQUIRE(fnBar); CHECK_EQ(fnBar->argTypes.types.size, 0); CHECK(!fnBar->argTypes.tailType); - CHECK_EQ(fnBar->returnTypes.types.size, 1); - CHECK(fnBar->returnTypes.tailType); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + auto returnTypePack = fnBar->returnTypes->as(); + REQUIRE(returnTypePack); + CHECK_EQ(returnTypePack->typeList.types.size, 1); + CHECK(returnTypePack->typeList.tailType); + } + else + { + CHECK_EQ(fnBar->returnTypes_DEPRECATED.types.size, 1); + CHECK(fnBar->returnTypes_DEPRECATED.tailType); + } } TEST_CASE_FIXTURE(Fixture, "variadics_must_be_last") @@ -2169,7 +2496,8 @@ TEST_CASE_FIXTURE(Fixture, "generic_pack_parsing") REQUIRE(argAnnot != nullptr); CHECK(argAnnot->genericName == "a"); - AstTypePackGeneric* retAnnot = fnTy->returnTypes.tailType->as(); + AstTypePackGeneric* retAnnot = FFlag::LuauStoreReturnTypesAsPackOnAst ? fnTy->returnTypes->as() + : fnTy->returnTypes_DEPRECATED.tailType->as(); REQUIRE(retAnnot != nullptr); CHECK(retAnnot->genericName == "b"); } @@ -2254,7 +2582,9 @@ TEST_CASE_FIXTURE(Fixture, "function_type_named_arguments") REQUIRE_EQ(func->argNames.size, 3); REQUIRE(func->argNames.data[2]); CHECK_EQ(func->argNames.data[2]->first, "c"); - AstTypeFunction* funcRet = func->returnTypes.types.data[0]->as(); + AstTypeFunction* funcRet = FFlag::LuauStoreReturnTypesAsPackOnAst + ? func->returnTypes->as()->typeList.types.data[0]->as() + : func->returnTypes_DEPRECATED.types.data[0]->as(); REQUIRE(funcRet != nullptr); REQUIRE_EQ(funcRet->argTypes.types.size, 3); REQUIRE_EQ(funcRet->argNames.size, 3); @@ -2427,7 +2757,6 @@ TEST_CASE_FIXTURE(Fixture, "invalid_user_defined_type_functions") TEST_CASE_FIXTURE(Fixture, "leading_union_intersection_with_single_type_preserves_the_union_intersection_ast_node") { - ScopedFastFlag _{FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true}; AstStatBlock* block = parse(R"( type Foo = | string type Bar = & number @@ -2505,9 +2834,20 @@ TEST_CASE_FIXTURE(Fixture, "parse_return_type_ast_type_group") auto funcType = alias1->type->as(); REQUIRE(funcType); - REQUIRE_EQ(1, funcType->returnTypes.types.size); - REQUIRE(!funcType->returnTypes.tailType); - CHECK(funcType->returnTypes.types.data[0]->is()); + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + auto returnTypePack = funcType->returnTypes->as(); + REQUIRE(returnTypePack); + REQUIRE_EQ(1, returnTypePack->typeList.types.size); + REQUIRE(!returnTypePack->typeList.tailType); + CHECK(returnTypePack->typeList.types.data[0]->is()); + } + else + { + REQUIRE_EQ(1, funcType->returnTypes_DEPRECATED.types.size); + REQUIRE(!funcType->returnTypes_DEPRECATED.tailType); + CHECK(funcType->returnTypes_DEPRECATED.types.data[0]->is()); + } } TEST_CASE_FIXTURE(Fixture, "inner_and_outer_scope_of_functions_have_correct_end_position") diff --git a/tests/RequireByString.test.cpp b/tests/RequireByString.test.cpp index ba3946e4..44670df8 100644 --- a/tests/RequireByString.test.cpp +++ b/tests/RequireByString.test.cpp @@ -7,6 +7,7 @@ #include "lualib.h" #include "Luau/Repl.h" +#include "Luau/Require.h" #include "Luau/FileUtils.h" #include "doctest.h" @@ -465,6 +466,20 @@ TEST_CASE_FIXTURE(ReplWithPathFixture, "CheckCachedResult") assertOutputContainsAll({"true"}); } +TEST_CASE_FIXTURE(ReplWithPathFixture, "RegisterRuntimeModule") +{ + lua_pushcfunction(L, luarequire_registermodule, nullptr); + lua_pushstring(L, "@test/helloworld"); + lua_newtable(L); + lua_pushstring(L, "hello"); + lua_pushstring(L, "world"); + lua_settable(L, -3); + lua_call(L, 2, 0); + + runCode(L, "return require('@test/helloworld').hello == 'world'"); + assertOutputContainsAll({"true"}); +} + TEST_CASE_FIXTURE(ReplWithPathFixture, "LoadStringRelative") { runCode(L, "return pcall(function() return loadstring(\"require('a/relative/path')\")() end)"); diff --git a/tests/RequireTracer.test.cpp b/tests/RequireTracer.test.cpp index 3ad3ffec..c4d2a312 100644 --- a/tests/RequireTracer.test.cpp +++ b/tests/RequireTracer.test.cpp @@ -6,6 +6,8 @@ #include "doctest.h" +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) + using namespace Luau; namespace @@ -162,6 +164,50 @@ TEST_CASE_FIXTURE(RequireTracerFixture, "follow_typeof") CHECK_EQ("workspace/CoolThing", result.exprs[call->args.data[0]].name); } +TEST_CASE_FIXTURE(RequireTracerFixture, "follow_typeof_in_return_type") +{ + AstStatBlock* block = parse(R"( + function foo(): typeof(require(workspace.CoolThing).UsefulObject) + end + )"); + REQUIRE_EQ(1, block->body.size); + + RequireTraceResult result = traceRequires(&fileResolver, block, "ModuleName"); + + AstStatFunction* func = block->body.data[0]->as(); + REQUIRE(func != nullptr); + + AstTypeTypeof* typeofAnnotation; + if (FFlag::LuauStoreReturnTypesAsPackOnAst) + { + AstTypePack* retAnnotation = func->func->returnAnnotation; + REQUIRE(retAnnotation); + + AstTypePackExplicit* tp = retAnnotation->as(); + REQUIRE(tp); + REQUIRE_EQ(tp->typeList.types.size, 1); + typeofAnnotation = tp->typeList.types.data[0]->as(); + } + else + { + std::optional retAnnotation = func->func->returnAnnotation_DEPRECATED; + REQUIRE(retAnnotation); + REQUIRE_EQ(retAnnotation->types.size, 1); + typeofAnnotation = retAnnotation->types.data[0]->as(); + } + REQUIRE(typeofAnnotation != nullptr); + + AstExprIndexName* indexName = typeofAnnotation->expr->as(); + REQUIRE(indexName != nullptr); + REQUIRE_EQ(indexName->index, "UsefulObject"); + + AstExprCall* call = indexName->expr->as(); + REQUIRE(call != nullptr); + REQUIRE_EQ(1, call->args.size); + + CHECK_EQ("workspace/CoolThing", result.exprs[call->args.data[0]].name); +} + TEST_CASE_FIXTURE(RequireTracerFixture, "follow_string_indexexpr") { AstStatBlock* block = parse(R"( diff --git a/tests/Simplify.test.cpp b/tests/Simplify.test.cpp index 92ac68de..04f9d417 100644 --- a/tests/Simplify.test.cpp +++ b/tests/Simplify.test.cpp @@ -36,7 +36,7 @@ struct SimplifyFixture : Fixture const TypeId booleanTy = builtinTypes->booleanType; const TypeId nilTy = builtinTypes->nilType; - const TypeId classTy = builtinTypes->classType; + const TypeId classTy = builtinTypes->externType; const TypeId trueTy = builtinTypes->trueType; const TypeId falseTy = builtinTypes->falseType; @@ -66,7 +66,7 @@ struct SimplifyFixture : Fixture SimplifyFixture() { - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); parentClassTy = frontend.globals.globalScope->linearSearchForBinding("Parent")->typeId; childClassTy = frontend.globals.globalScope->linearSearchForBinding("Child")->typeId; @@ -512,7 +512,7 @@ TEST_CASE_FIXTURE(SimplifyFixture, "top_class_type") CHECK(neverTy == intersect(classTy, stringTy)); } -TEST_CASE_FIXTURE(SimplifyFixture, "classes") +TEST_CASE_FIXTURE(SimplifyFixture, "extern_types") { CHECK(childClassTy == intersect(childClassTy, parentClassTy)); CHECK(childClassTy == intersect(parentClassTy, childClassTy)); @@ -523,7 +523,7 @@ TEST_CASE_FIXTURE(SimplifyFixture, "classes") CHECK(neverTy == intersect(childClassTy, unrelatedClassTy)); } -TEST_CASE_FIXTURE(SimplifyFixture, "negations_of_classes") +TEST_CASE_FIXTURE(SimplifyFixture, "negations_of_extern_types") { TypeId notChildClassTy = mkNegation(childClassTy); TypeId notParentClassTy = mkNegation(parentClassTy); diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index f5ded76e..fb20c82f 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -151,13 +151,13 @@ struct SubtypeFixture : Fixture TypeId cls(const std::string& name, std::optional parent = std::nullopt) { - return arena.addType(ClassType{name, {}, parent.value_or(builtinTypes->classType), {}, {}, nullptr, "", {}}); + return arena.addType(ExternType{name, {}, parent.value_or(builtinTypes->externType), {}, {}, nullptr, "", {}}); } - TypeId cls(const std::string& name, ClassType::Props&& props) + TypeId cls(const std::string& name, ExternType::Props&& props) { TypeId ty = cls(name); - getMutable(ty)->props = std::move(props); + getMutable(ty)->props = std::move(props); return ty; } @@ -926,9 +926,9 @@ TEST_IS_NOT_SUBTYPE(numbersToNumberType, negate(join(builtinTypes->functionType, // Negated supertypes: intersections TEST_IS_SUBTYPE(builtinTypes->booleanType, negate(meet(builtinTypes->stringType, str("foo")))); TEST_IS_SUBTYPE(builtinTypes->trueType, negate(meet(builtinTypes->booleanType, builtinTypes->numberType))); -TEST_IS_SUBTYPE(rootClass, negate(meet(builtinTypes->classType, childClass))); -TEST_IS_SUBTYPE(childClass, negate(meet(builtinTypes->classType, builtinTypes->numberType))); -TEST_IS_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->classType, builtinTypes->numberType))); +TEST_IS_SUBTYPE(rootClass, negate(meet(builtinTypes->externType, childClass))); +TEST_IS_SUBTYPE(childClass, negate(meet(builtinTypes->externType, builtinTypes->numberType))); +TEST_IS_SUBTYPE(builtinTypes->unknownType, negate(meet(builtinTypes->externType, builtinTypes->numberType))); TEST_IS_NOT_SUBTYPE(str("foo"), negate(meet(builtinTypes->stringType, negate(str("bar"))))); // Negated supertypes: tables and metatables @@ -938,7 +938,7 @@ TEST_IS_SUBTYPE(meta({}), negate(builtinTypes->numberType)); TEST_IS_NOT_SUBTYPE(meta({}), negate(builtinTypes->tableType)); // Negated supertypes: Functions -TEST_IS_SUBTYPE(numberToNumberType, negate(builtinTypes->classType)); +TEST_IS_SUBTYPE(numberToNumberType, negate(builtinTypes->externType)); TEST_IS_NOT_SUBTYPE(numberToNumberType, negate(builtinTypes->functionType)); // Negated supertypes: Primitives and singletons @@ -954,12 +954,12 @@ TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, meet(builtinTypes->booleanType, ne TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, negate(str("foo"))); TEST_IS_NOT_SUBTYPE(builtinTypes->booleanType, negate(builtinTypes->falseType)); -// Negated supertypes: Classes +// Negated supertypes: extern types TEST_IS_SUBTYPE(rootClass, negate(builtinTypes->tableType)); -TEST_IS_NOT_SUBTYPE(rootClass, negate(builtinTypes->classType)); +TEST_IS_NOT_SUBTYPE(rootClass, negate(builtinTypes->externType)); TEST_IS_NOT_SUBTYPE(childClass, negate(rootClass)); -TEST_IS_NOT_SUBTYPE(childClass, meet(builtinTypes->classType, negate(rootClass))); -TEST_IS_SUBTYPE(anotherChildClass, meet(builtinTypes->classType, negate(childClass))); +TEST_IS_NOT_SUBTYPE(childClass, meet(builtinTypes->externType, negate(rootClass))); +TEST_IS_SUBTYPE(anotherChildClass, meet(builtinTypes->externType, negate(childClass))); // Negated primitives against unknown TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->booleanType)); @@ -970,12 +970,12 @@ TEST_IS_NOT_SUBTYPE(builtinTypes->unknownType, negate(builtinTypes->bufferType)) TEST_CASE_FIXTURE(SubtypeFixture, "Root <: class") { - CHECK_IS_SUBTYPE(rootClass, builtinTypes->classType); + CHECK_IS_SUBTYPE(rootClass, builtinTypes->externType); } TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: class") { - CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), builtinTypes->classType); + CHECK_IS_SUBTYPE(join(childClass, anotherChildClass), builtinTypes->externType); } TEST_CASE_FIXTURE(SubtypeFixture, "Child | AnotherChild <: Child | AnotherChild") @@ -990,17 +990,17 @@ TEST_CASE_FIXTURE(SubtypeFixture, "Child | Root <: Root") TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: class") { - CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), builtinTypes->classType); + CHECK_IS_SUBTYPE(meet(childClass, anotherChildClass), builtinTypes->externType); } TEST_CASE_FIXTURE(SubtypeFixture, "Child & Root <: class") { - CHECK_IS_SUBTYPE(meet(childClass, rootClass), builtinTypes->classType); + CHECK_IS_SUBTYPE(meet(childClass, rootClass), builtinTypes->externType); } TEST_CASE_FIXTURE(SubtypeFixture, "Child & ~Root <: class") { - CHECK_IS_SUBTYPE(meet(childClass, negate(rootClass)), builtinTypes->classType); + CHECK_IS_SUBTYPE(meet(childClass, negate(rootClass)), builtinTypes->externType); } TEST_CASE_FIXTURE(SubtypeFixture, "Child & AnotherChild <: number") @@ -1389,7 +1389,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "subtyping_reasonings_to_follow_a_reduced_type TypeId longTy = arena.addType(UnionType{ {builtinTypes->booleanType, builtinTypes->bufferType, - builtinTypes->classType, + builtinTypes->externType, builtinTypes->functionType, builtinTypes->numberType, builtinTypes->stringType, diff --git a/tests/ToDot.test.cpp b/tests/ToDot.test.cpp index 11027e6f..668ed66d 100644 --- a/tests/ToDot.test.cpp +++ b/tests/ToDot.test.cpp @@ -21,14 +21,14 @@ struct ToDotClassFixture : Fixture TypeId baseClassMetaType = arena.addType(TableType{}); - TypeId baseClassInstanceType = arena.addType(ClassType{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}, "Test", {}}); - getMutable(baseClassInstanceType)->props = { + TypeId baseClassInstanceType = arena.addType(ExternType{"BaseClass", {}, std::nullopt, baseClassMetaType, {}, {}, "Test", {}}); + getMutable(baseClassInstanceType)->props = { {"BaseField", {builtinTypes->numberType}}, }; frontend.globals.globalScope->exportedTypeBindings["BaseClass"] = TypeFun{{}, baseClassInstanceType}; - TypeId childClassInstanceType = arena.addType(ClassType{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}, "Test", {}}); - getMutable(childClassInstanceType)->props = { + TypeId childClassInstanceType = arena.addType(ExternType{"ChildClass", {}, baseClassInstanceType, std::nullopt, {}, {}, "Test", {}}); + getMutable(childClassInstanceType)->props = { {"ChildField", {builtinTypes->stringType}}, }; frontend.globals.globalScope->exportedTypeBindings["ChildClass"] = TypeFun{{}, childClassInstanceType}; @@ -400,11 +400,11 @@ local a: ChildClass opts.showPointers = false; CHECK_EQ( R"(digraph graphname { -n1 [label="ClassType ChildClass"]; +n1 [label="ExternType ChildClass"]; n1 -> n2 [label="ChildField"]; n2 [label="string"]; n1 -> n3 [label="[parent]"]; -n3 [label="ClassType BaseClass"]; +n3 [label="ExternType BaseClass"]; n3 -> n4 [label="BaseField"]; n4 [label="number"]; n3 -> n5 [label="[metatable]"]; diff --git a/tests/TopoSort.test.cpp b/tests/TopoSort.test.cpp index 1f14ae88..02ff6cbe 100644 --- a/tests/TopoSort.test.cpp +++ b/tests/TopoSort.test.cpp @@ -338,6 +338,32 @@ TEST_CASE_FIXTURE(Fixture, "nested_type_annotations_depends_on_later_typealiases CHECK_EQ(sorted[2], Foo); } +TEST_CASE_FIXTURE(Fixture, "function_return_type_depends_on_type_aliases") +{ + AstStatBlock* program = parse(R"( + type callbackFn = (element: V, key: K, map: Map) -> () + + export type Map = { + forEach: (callback: callbackFn) -> (), + } + + function foo(key: K, value: V): Map + end + )"); + + auto sorted = toposort(*program); + + REQUIRE_EQ(3, sorted.size()); + + auto callbackFn = program->body.data[0]; + auto Map = program->body.data[1]; + auto foo = program->body.data[2]; + + CHECK_EQ(sorted[0], callbackFn); + CHECK_EQ(sorted[1], Map); + CHECK_EQ(sorted[2], foo); +} + TEST_CASE_FIXTURE(Fixture, "return_comes_last") { AstStatBlock* program = parse(R"( diff --git a/tests/Transpiler.test.cpp b/tests/Transpiler.test.cpp index 06a0aa26..ae170c3d 100644 --- a/tests/Transpiler.test.cpp +++ b/tests/Transpiler.test.cpp @@ -14,8 +14,8 @@ using namespace Luau; LUAU_FASTFLAG(LuauStoreCSTData2) LUAU_FASTFLAG(LuauAstTypeGroup3); -LUAU_FASTFLAG(LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType) LUAU_FASTFLAG(LuauParseOptionalAsNode2) +LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) TEST_SUITE_BEGIN("TranspilerTests"); @@ -1341,11 +1341,21 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_nested_2") CHECK_EQ(code, transpile(code, {}, true).code); } +TEST_CASE_FIXTURE(Fixture, "transpile_intersection_type_with_function") +{ + ScopedFastFlag flags[] = { + {FFlag::LuauStoreCSTData2, true}, + {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, + }; + std::string code = "type FnB = () -> U... & T"; + + CHECK_EQ(code, transpile(code, {}, true).code); +} + TEST_CASE_FIXTURE(Fixture, "transpile_leading_union_pipe") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true}, {FFlag::LuauParseOptionalAsNode2, true}, }; std::string code = "local a: | string | number"; @@ -1359,7 +1369,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_union_spaces_around_tokens") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true}, {FFlag::LuauParseOptionalAsNode2, true}, }; std::string code = "local a: string | number"; @@ -1373,7 +1382,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_leading_intersection_ampersand") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true}, }; std::string code = "local a: & string & number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -1386,7 +1394,6 @@ TEST_CASE_FIXTURE(Fixture, "transpile_intersection_spaces_around_tokens") { ScopedFastFlag flags[] = { {FFlag::LuauStoreCSTData2, true}, - {FFlag::LuauPreserveUnionIntersectionNodeForLeadingTokenSingleType, true}, }; std::string code = "local a: string & number"; CHECK_EQ(code, transpile(code, {}, true).code); @@ -2060,98 +2067,157 @@ end TEST_CASE("transpile_type_function_unnamed_arguments") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + ScopedFastFlag flags[] = { + {FFlag::LuauStoreCSTData2, true}, + {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, + }; std::string code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (string) -> () )"; - CHECK_EQ(R"( type Foo = (string) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (string, number) -> () )"; - CHECK_EQ(R"( type Foo = (string, number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = ( string, number) -> () )"; - CHECK_EQ(R"( type Foo = ( string, number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (string , number) -> () )"; - CHECK_EQ(R"( type Foo = (string , number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (string, number) -> () )"; - CHECK_EQ(R"( type Foo = (string, number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (string, number ) -> () )"; - CHECK_EQ(R"( type Foo = (string, number ) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (string, number) -> () )"; - CHECK_EQ(R"( type Foo = (string, number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (string, number) -> () )"; - CHECK_EQ(R"( type Foo = (string, number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); } TEST_CASE("transpile_type_function_named_arguments") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + ScopedFastFlag flags[] = { + {FFlag::LuauStoreCSTData2, true}, + {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, + }; std::string code = R"( type Foo = (x: string) -> () )"; - CHECK_EQ(R"( type Foo = (x: string) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (x: string, y: number) -> () )"; - CHECK_EQ(R"( type Foo = (x: string, y: number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = ( x: string, y: number) -> () )"; - CHECK_EQ(R"( type Foo = ( x: string, y: number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (x : string, y: number) -> () )"; - CHECK_EQ(R"( type Foo = (x : string, y: number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (x: string, y: number) -> () )"; - CHECK_EQ(R"( type Foo = (x: string, y: number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (x: string, y: number) -> () )"; - CHECK_EQ(R"( type Foo = (x: string, y: number) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (number, info: string) -> () )"; - CHECK_EQ(R"( type Foo = (number, info: string) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = (first: string, second: string, ...string) -> () )"; - CHECK_EQ(R"( type Foo = (first: string, second: string, ...string) ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); } TEST_CASE("transpile_type_function_generics") { - ScopedFastFlag _{FFlag::LuauStoreCSTData2, true}; + ScopedFastFlag flags[] = { + {FFlag::LuauStoreCSTData2, true}, + {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, + }; std::string code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = < X, Y, Z...>() -> () )"; - CHECK_EQ(R"( type Foo = < X, Y, Z...>() ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); code = R"( type Foo = () -> () )"; - CHECK_EQ(R"( type Foo = () ->() )", transpile(code, {}, true).code); + CHECK_EQ(code, transpile(code, {}, true).code); +} + +TEST_CASE("transpile_type_function_return_types") +{ + ScopedFastFlag fflags[] = { + {FFlag::LuauStoreCSTData2, true}, + {FFlag::LuauAstTypeGroup3, true}, + {FFlag::LuauStoreReturnTypesAsPackOnAst, true}, + }; + std::string code = R"( type Foo = () -> () )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> ( ) )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> string )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> (string) )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> (string) )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> ...any )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> ...any )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> ... any )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> (...any) )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> ( string, number) )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> (string , number) )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> (string, number) )"; + CHECK_EQ(code, transpile(code, {}, true).code); + + code = R"( type Foo = () -> (string, number ) )"; + CHECK_EQ(code, transpile(code, {}, true).code); } TEST_CASE("fuzzer_nil_optional") diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 854a2021..59a3509f 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -592,7 +592,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawkeyof_type_function_never_for_empty_table CHECK(toString(requireType("foo")) == "never"); } -TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_works_on_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "keyof_type_function_works_on_extern_types") { if (!FFlag::LuauSolverV2) return; @@ -612,7 +612,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_works_on_classes") CHECK_EQ("\"BaseField\" | \"BaseMethod\" | \"Touched\"", toString(tpm->givenTp)); } -TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_errors_if_it_has_nonclass_part") +TEST_CASE_FIXTURE(ExternTypeFixture, "keyof_type_function_errors_if_it_has_nonclass_part") { if (!FFlag::LuauSolverV2) return; @@ -629,7 +629,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_errors_if_it_has_nonclass_p CHECK(toString(result.errors[1]) == "Type 'BaseClass | boolean' does not have keys, so 'keyof' is invalid"); } -TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_common_subset_if_union_of_differing_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "keyof_type_function_common_subset_if_union_of_differing_extern_types") { if (!FFlag::LuauSolverV2) return; @@ -643,7 +643,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_common_subset_if_union_of_d LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_works_with_parent_classes_too") +TEST_CASE_FIXTURE(ExternTypeFixture, "keyof_type_function_works_with_parent_extern_types_too") { if (!FFlag::LuauSolverV2) return; @@ -657,7 +657,7 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_function_works_with_parent_classes_t LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "binary_type_function_works_with_default_argument") +TEST_CASE_FIXTURE(ExternTypeFixture, "binary_type_function_works_with_default_argument") { if (!FFlag::LuauSolverV2) return; @@ -672,7 +672,7 @@ TEST_CASE_FIXTURE(ClassFixture, "binary_type_function_works_with_default_argumen CHECK("() -> number" == toString(requireType("thunk"))); } -TEST_CASE_FIXTURE(ClassFixture, "vector2_multiply_is_overloaded") +TEST_CASE_FIXTURE(ExternTypeFixture, "vector2_multiply_is_overloaded") { if (!FFlag::LuauSolverV2) return; @@ -1246,7 +1246,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_rfc_alternative_section" CHECK(toString(result.errors[0]) == "Property '\"b\"' does not exist on type 'MyObject'"); } -TEST_CASE_FIXTURE(ClassFixture, "index_type_function_works_on_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "index_type_function_works_on_extern_types") { if (!FFlag::LuauSolverV2) return; @@ -1260,7 +1260,7 @@ TEST_CASE_FIXTURE(ClassFixture, "index_type_function_works_on_classes") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "index_type_function_works_on_classes_with_parents") +TEST_CASE_FIXTURE(ExternTypeFixture, "index_type_function_works_on_extern_types_with_parents") { if (!FFlag::LuauSolverV2) return; @@ -1405,7 +1405,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "rawget_type_function_works_w_index_metatable CHECK(toString(result.errors[1]) == "Property '\"Bar\" | \"Foo\"' does not exist on type 'exampleClass3'"); } -TEST_CASE_FIXTURE(ClassFixture, "rawget_type_function_errors_w_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "rawget_type_function_errors_w_extern_types") { if (!FFlag::LuauSolverV2) return; diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index 7277e50e..5c692faf 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -475,7 +475,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_negation_methods_work") CHECK(toString(tpm->givenTp) == "~string"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_negation_inner") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_negation_inner") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -635,7 +635,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_function_methods_work") CHECK(toString(tpm->givenTp) == "(string, number) -> (...boolean)"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_serialization_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -649,7 +649,7 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works2") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_serialization_works2") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -663,7 +663,7 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_serialization_works2") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_methods_works") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -685,7 +685,7 @@ TEST_CASE_FIXTURE(ClassFixture, "udtf_class_methods_works") CHECK(toString(tpm->givenTp) == "{ BaseField: number, read BaseMethod: (BaseClass, number) -> (), read Touched: Connection }"); } -TEST_CASE_FIXTURE(ClassFixture, "write_of_readonly_is_nil") +TEST_CASE_FIXTURE(ExternTypeFixture, "write_of_readonly_is_nil") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1512,7 +1512,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "print_to_error_plus_no_result") CHECK(toString(result.errors[3]) == R"(Type function instance t0 is uninhabited)"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_1") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_serialization_1") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1529,7 +1529,7 @@ local function ok(idx: pass): test return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_2") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_serialization_2") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1546,7 +1546,7 @@ local function ok(idx: pass): test return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_serialization_3") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_serialization_3") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1567,7 +1567,7 @@ local function ok(idx: pass): test return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_cloning_1") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_cloning_1") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1584,7 +1584,7 @@ local function ok(idx: pass): test return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_cloning_2") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_cloning_2") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1601,7 +1601,7 @@ local function ok(idx: pass): test return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_equality") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_equality") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1618,7 +1618,7 @@ local function ok(idx: pass): true return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_1") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_1") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1637,7 +1637,7 @@ local function ok(idx: pass): (T) -> (T) return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_2") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_2") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1660,7 +1660,7 @@ local function ok(idx: pass): (T, T) -> (T) return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_3") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_3") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1689,7 +1689,7 @@ local function ok(idx: pass<>): (T, U...) -> (T, V...) return idx LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_4") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_4") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1715,7 +1715,7 @@ local function ok(idx: pass<>): test return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_5") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_5") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1731,7 +1731,7 @@ local function ok(idx: pass<>): (T) -> () return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_6") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_6") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1758,7 +1758,7 @@ local function ok(idx: pass): (T) -> (U) return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_7") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_7") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1780,7 +1780,7 @@ local function ok(idx: pass): (T, U...) -> (T, U...) return idx e LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_8") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_8") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1802,7 +1802,7 @@ local function ok(idx: pass): (T, T) -> (T) return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_equality_2") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_equality_2") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1822,7 +1822,7 @@ local function ok(idx: get<>): false return idx end LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_1") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_error_1") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1841,7 +1841,7 @@ local function ok(idx: get<>): false return idx end ); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_2") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_error_2") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1857,7 +1857,7 @@ local function ok(idx: get<>): false return idx end CHECK(toString(result.errors[0]) == R"(Generic type 'T' is not in a scope of the active generic function)"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_3") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_error_3") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1878,7 +1878,7 @@ local function ok(idx: get<>): false return idx end CHECK(toString(result.errors[0]) == R"(Generic type 'U' is not in a scope of the active generic function)"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_4") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_error_4") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1894,7 +1894,7 @@ local function ok(idx: get<>): false return idx end CHECK(toString(result.errors[0]) == R"(Duplicate type parameter 'T')"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_5") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_error_5") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1910,7 +1910,7 @@ local function ok(idx: get<>): false return idx end CHECK(toString(result.errors[0]) == R"(Duplicate type parameter 'T')"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_6") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_error_6") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1926,7 +1926,7 @@ local function ok(idx: get<>): false return idx end CHECK(toString(result.errors[0]) == R"(Generic type pack 'U...' cannot be placed in a type position)"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_generic_api_error_7") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_generic_api_error_7") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -1942,7 +1942,7 @@ local function ok(idx: get<>): false return idx end CHECK(toString(result.errors[0]) == R"(Generic type pack 'U...' is not in a scope of the active generic function)"); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_variadic_api") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_variadic_api") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; @@ -2050,7 +2050,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_print_tab_char_fix") CHECK_EQ("1\t2", toString(result.errors[0])); } -TEST_CASE_FIXTURE(ClassFixture, "udtf_class_parent_ops") +TEST_CASE_FIXTURE(ExternTypeFixture, "udtf_class_parent_ops") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag readWriteParents{FFlag::LuauTypeFunReadWriteParents, true}; diff --git a/tests/TypeInfer.anyerror.test.cpp b/tests/TypeInfer.anyerror.test.cpp index 445ce072..8694a479 100644 --- a/tests/TypeInfer.anyerror.test.cpp +++ b/tests/TypeInfer.anyerror.test.cpp @@ -14,6 +14,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2); +LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions); TEST_SUITE_BEGIN("TypeInferAnyError"); @@ -33,7 +34,16 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::LuauSolverV2) - CHECK("any?" == toString(requireType("a"))); + { + if (FFlag::LuauAddCallConstraintForIterableFunctions) + { + CHECK("(*error-type* | ~nil)?" == toString(requireType("a"))); + } + else + { + CHECK("any?" == toString(requireType("a"))); + } + } else CHECK(builtinTypes->anyType == requireType("a")); } @@ -54,7 +64,16 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any2") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::LuauSolverV2) - CHECK("any?" == toString(requireType("a"))); + { + if (FFlag::LuauAddCallConstraintForIterableFunctions) + { + CHECK("(*error-type* | ~nil)?" == toString(requireType("a"))); + } + else + { + CHECK("any?" == toString(requireType("a"))); + } + } else CHECK("any" == toString(requireType("a"))); } @@ -73,7 +92,16 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::LuauSolverV2) - CHECK("any?" == toString(requireType("a"))); + { + if (FFlag::LuauAddCallConstraintForIterableFunctions) + { + CHECK("(*error-type* | ~nil)?" == toString(requireType("a"))); + } + else + { + CHECK("any?" == toString(requireType("a"))); + } + } else CHECK("any" == toString(requireType("a"))); } @@ -90,7 +118,16 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any2") )"); if (FFlag::LuauSolverV2) - CHECK("any?" == toString(requireType("a"))); + { + if (FFlag::LuauAddCallConstraintForIterableFunctions) + { + CHECK("(*error-type* | ~nil)?" == toString(requireType("a"))); + } + else + { + CHECK("any?" == toString(requireType("a"))); + } + } else CHECK("any" == toString(requireType("a"))); } @@ -109,7 +146,16 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any_pack") LUAU_REQUIRE_NO_ERRORS(result); if (FFlag::LuauSolverV2) - CHECK("any?" == toString(requireType("a"))); + { + if (FFlag::LuauAddCallConstraintForIterableFunctions) + { + CHECK("(*error-type* | ~nil)?" == toString(requireType("a"))); + } + else + { + CHECK("any?" == toString(requireType("a"))); + } + } else CHECK("any" == toString(requireType("a"))); } diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 0986575b..6ed3f7ea 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -1,6 +1,7 @@ // This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/BuiltinDefinitions.h" #include "Luau/Common.h" +#include "Luau/Error.h" #include "Luau/TypeInfer.h" #include "Luau/Type.h" @@ -16,9 +17,9 @@ using std::nullopt; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) -TEST_SUITE_BEGIN("TypeInferClasses"); +TEST_SUITE_BEGIN("TypeInferExternTypes"); -TEST_CASE_FIXTURE(ClassFixture, "Luau.Analyze.CLI_crashes_on_this_test") +TEST_CASE_FIXTURE(ExternTypeFixture, "Luau.Analyze.CLI_crashes_on_this_test") { CheckResult result = check(R"( local CircularQueue = {} @@ -51,7 +52,7 @@ return CircularQueue )"); } -TEST_CASE_FIXTURE(ClassFixture, "call_method_of_a_class") +TEST_CASE_FIXTURE(ExternTypeFixture, "call_method_of_a_class") { CheckResult result = check(R"( local m = BaseClass.StaticMethod() @@ -62,7 +63,7 @@ TEST_CASE_FIXTURE(ClassFixture, "call_method_of_a_class") REQUIRE_EQ("number", toString(requireType("m"))); } -TEST_CASE_FIXTURE(ClassFixture, "call_method_of_a_child_class") +TEST_CASE_FIXTURE(ExternTypeFixture, "call_method_of_a_child_class") { CheckResult result = check(R"( local m = ChildClass.StaticMethod() @@ -73,7 +74,7 @@ TEST_CASE_FIXTURE(ClassFixture, "call_method_of_a_child_class") REQUIRE_EQ("number", toString(requireType("m"))); } -TEST_CASE_FIXTURE(ClassFixture, "call_instance_method") +TEST_CASE_FIXTURE(ExternTypeFixture, "call_instance_method") { CheckResult result = check(R"( local i = ChildClass.New() @@ -85,7 +86,7 @@ TEST_CASE_FIXTURE(ClassFixture, "call_instance_method") CHECK_EQ("string", toString(requireType("result"))); } -TEST_CASE_FIXTURE(ClassFixture, "call_base_method") +TEST_CASE_FIXTURE(ExternTypeFixture, "call_base_method") { CheckResult result = check(R"( local i = ChildClass.New() @@ -95,7 +96,7 @@ TEST_CASE_FIXTURE(ClassFixture, "call_base_method") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "cannot_call_unknown_method_of_a_class") +TEST_CASE_FIXTURE(ExternTypeFixture, "cannot_call_unknown_method_of_a_class") { CheckResult result = check(R"( local m = BaseClass.Nope() @@ -104,7 +105,7 @@ TEST_CASE_FIXTURE(ClassFixture, "cannot_call_unknown_method_of_a_class") LUAU_REQUIRE_ERROR_COUNT(1, result); } -TEST_CASE_FIXTURE(ClassFixture, "cannot_call_method_of_child_on_base_instance") +TEST_CASE_FIXTURE(ExternTypeFixture, "cannot_call_method_of_child_on_base_instance") { CheckResult result = check(R"( local i = BaseClass.New() @@ -114,7 +115,7 @@ TEST_CASE_FIXTURE(ClassFixture, "cannot_call_method_of_child_on_base_instance") LUAU_REQUIRE_ERROR_COUNT(1, result); } -TEST_CASE_FIXTURE(ClassFixture, "we_can_infer_that_a_parameter_must_be_a_particular_class") +TEST_CASE_FIXTURE(ExternTypeFixture, "we_can_infer_that_a_parameter_must_be_a_particular_class") { CheckResult result = check(R"( function makeClone(o) @@ -127,7 +128,7 @@ TEST_CASE_FIXTURE(ClassFixture, "we_can_infer_that_a_parameter_must_be_a_particu CHECK_EQ("BaseClass", toString(requireType("a"))); } -TEST_CASE_FIXTURE(ClassFixture, "we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class") +TEST_CASE_FIXTURE(ExternTypeFixture, "we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class") { DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -155,7 +156,7 @@ TEST_CASE_FIXTURE(ClassFixture, "we_can_report_when_someone_is_trying_to_use_a_t CHECK_EQ("BaseClass", toString(tm->wantedType)); } -TEST_CASE_FIXTURE(ClassFixture, "we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class_using_new_solver") +TEST_CASE_FIXTURE(ExternTypeFixture, "we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class_using_new_solver") { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; @@ -183,7 +184,7 @@ TEST_CASE_FIXTURE(ClassFixture, "we_can_report_when_someone_is_trying_to_use_a_t CHECK_EQ("BaseClass", toString(tm->wantedType)); } -TEST_CASE_FIXTURE(ClassFixture, "assign_to_prop_of_class") +TEST_CASE_FIXTURE(ExternTypeFixture, "assign_to_prop_of_class") { CheckResult result = check(R"( local v = Vector2.New(0, 5) @@ -193,7 +194,7 @@ TEST_CASE_FIXTURE(ClassFixture, "assign_to_prop_of_class") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "can_read_prop_of_base_class") +TEST_CASE_FIXTURE(ExternTypeFixture, "can_read_prop_of_base_class") { CheckResult result = check(R"( local c = ChildClass.New() @@ -203,7 +204,7 @@ TEST_CASE_FIXTURE(ClassFixture, "can_read_prop_of_base_class") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "can_assign_to_prop_of_base_class") +TEST_CASE_FIXTURE(ExternTypeFixture, "can_assign_to_prop_of_base_class") { CheckResult result = check(R"( local c = ChildClass.New() @@ -213,7 +214,7 @@ TEST_CASE_FIXTURE(ClassFixture, "can_assign_to_prop_of_base_class") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "can_read_prop_of_base_class_using_string") +TEST_CASE_FIXTURE(ExternTypeFixture, "can_read_prop_of_base_class_using_string") { CheckResult result = check(R"( local c = ChildClass.New() @@ -223,7 +224,7 @@ TEST_CASE_FIXTURE(ClassFixture, "can_read_prop_of_base_class_using_string") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "can_assign_to_prop_of_base_class_using_string") +TEST_CASE_FIXTURE(ExternTypeFixture, "can_assign_to_prop_of_base_class_using_string") { CheckResult result = check(R"( local c = ChildClass.New() @@ -233,7 +234,7 @@ TEST_CASE_FIXTURE(ClassFixture, "can_assign_to_prop_of_base_class_using_string") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "cannot_unify_class_instance_with_primitive") +TEST_CASE_FIXTURE(ExternTypeFixture, "cannot_unify_class_instance_with_primitive") { // This is allowed in the new solver DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -246,7 +247,7 @@ TEST_CASE_FIXTURE(ClassFixture, "cannot_unify_class_instance_with_primitive") LUAU_REQUIRE_ERROR_COUNT(1, result); } -TEST_CASE_FIXTURE(ClassFixture, "warn_when_prop_almost_matches") +TEST_CASE_FIXTURE(ExternTypeFixture, "warn_when_prop_almost_matches") { CheckResult result = check(R"( Vector2.new(0, 0) @@ -261,7 +262,7 @@ TEST_CASE_FIXTURE(ClassFixture, "warn_when_prop_almost_matches") CHECK_EQ("New", *err->candidates.begin()); } -TEST_CASE_FIXTURE(ClassFixture, "classes_can_have_overloaded_operators") +TEST_CASE_FIXTURE(ExternTypeFixture, "extern_types_can_have_overloaded_operators") { CheckResult result = check(R"( local a = Vector2.New(1, 2) @@ -274,7 +275,7 @@ TEST_CASE_FIXTURE(ClassFixture, "classes_can_have_overloaded_operators") CHECK_EQ("Vector2", toString(requireType("c"))); } -TEST_CASE_FIXTURE(ClassFixture, "classes_without_overloaded_operators_cannot_be_added") +TEST_CASE_FIXTURE(ExternTypeFixture, "extern_types_without_overloaded_operators_cannot_be_added") { CheckResult result = check(R"( local a = BaseClass.New() @@ -285,7 +286,7 @@ TEST_CASE_FIXTURE(ClassFixture, "classes_without_overloaded_operators_cannot_be_ LUAU_REQUIRE_ERROR_COUNT(1, result); } -TEST_CASE_FIXTURE(ClassFixture, "function_arguments_are_covariant") +TEST_CASE_FIXTURE(ExternTypeFixture, "function_arguments_are_covariant") { CheckResult result = check(R"( function f(b: BaseClass) end @@ -296,7 +297,7 @@ TEST_CASE_FIXTURE(ClassFixture, "function_arguments_are_covariant") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "higher_order_function_arguments_are_contravariant") +TEST_CASE_FIXTURE(ExternTypeFixture, "higher_order_function_arguments_are_contravariant") { CheckResult result = check(R"( function apply(f: (BaseClass) -> ()) @@ -309,7 +310,7 @@ TEST_CASE_FIXTURE(ClassFixture, "higher_order_function_arguments_are_contravaria LUAU_REQUIRE_ERROR_COUNT(1, result); } -TEST_CASE_FIXTURE(ClassFixture, "higher_order_function_return_values_are_covariant") +TEST_CASE_FIXTURE(ExternTypeFixture, "higher_order_function_return_values_are_covariant") { CheckResult result = check(R"( function apply(f: () -> BaseClass) @@ -324,7 +325,7 @@ TEST_CASE_FIXTURE(ClassFixture, "higher_order_function_return_values_are_covaria LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "higher_order_function_return_type_is_not_contravariant") +TEST_CASE_FIXTURE(ExternTypeFixture, "higher_order_function_return_type_is_not_contravariant") { CheckResult result = check(R"( function apply(f: () -> BaseClass) @@ -339,7 +340,7 @@ TEST_CASE_FIXTURE(ClassFixture, "higher_order_function_return_type_is_not_contra LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "table_properties_are_invariant") +TEST_CASE_FIXTURE(ExternTypeFixture, "table_properties_are_invariant") { CheckResult result = check(R"( function f(a: {foo: BaseClass}) @@ -362,7 +363,7 @@ TEST_CASE_FIXTURE(ClassFixture, "table_properties_are_invariant") CHECK_EQ(13, result.errors[1].location.begin.line); } -TEST_CASE_FIXTURE(ClassFixture, "table_indexers_are_invariant") +TEST_CASE_FIXTURE(ExternTypeFixture, "table_indexers_are_invariant") { CheckResult result = check(R"( function f(a: {[number]: BaseClass}) @@ -385,7 +386,7 @@ TEST_CASE_FIXTURE(ClassFixture, "table_indexers_are_invariant") CHECK_EQ(13, result.errors[1].location.begin.line); } -TEST_CASE_FIXTURE(ClassFixture, "table_class_unification_reports_sane_errors_for_missing_properties") +TEST_CASE_FIXTURE(ExternTypeFixture, "table_class_unification_reports_sane_errors_for_missing_properties") { CheckResult result = check(R"( function foo(bar) @@ -411,7 +412,7 @@ TEST_CASE_FIXTURE(ClassFixture, "table_class_unification_reports_sane_errors_for } } -TEST_CASE_FIXTURE(ClassFixture, "class_unification_type_mismatch_is_correct_order") +TEST_CASE_FIXTURE(ExternTypeFixture, "class_unification_type_mismatch_is_correct_order") { CheckResult result = check(R"( local p: BaseClass @@ -425,7 +426,7 @@ TEST_CASE_FIXTURE(ClassFixture, "class_unification_type_mismatch_is_correct_orde REQUIRE_EQ("Type 'number' could not be converted into 'BaseClass'", toString(result.errors[1])); } -TEST_CASE_FIXTURE(ClassFixture, "optional_class_field_access_error") +TEST_CASE_FIXTURE(ExternTypeFixture, "optional_class_field_access_error") { CheckResult result = check(R"( local b: Vector2? = nil @@ -441,7 +442,7 @@ b.X = 2 -- real Vector2.X is also read-only CHECK_EQ("Value of type 'Vector2?' could be nil", toString(result.errors[3])); } -TEST_CASE_FIXTURE(ClassFixture, "detailed_class_unification_error") +TEST_CASE_FIXTURE(ExternTypeFixture, "detailed_class_unification_error") { CheckResult result = check(R"( local function foo(v) @@ -470,7 +471,7 @@ Type 'number' could not be converted into 'string')"; } } -TEST_CASE_FIXTURE(ClassFixture, "class_type_mismatch_with_name_conflict") +TEST_CASE_FIXTURE(ExternTypeFixture, "class_type_mismatch_with_name_conflict") { // CLI-116433 DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -485,7 +486,7 @@ local a: ChildClass = i CHECK_EQ("Type 'ChildClass' from 'Test' could not be converted into 'ChildClass' from 'MainModule'", toString(result.errors.at(0))); } -TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "intersections_of_unions_of_extern_types") { CheckResult result = check(R"( local x : (BaseClass | Vector2) & (ChildClass | AnotherChild) @@ -497,7 +498,7 @@ TEST_CASE_FIXTURE(ClassFixture, "intersections_of_unions_of_classes") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "unions_of_intersections_of_extern_types") { CheckResult result = check(R"( local x : (BaseClass & ChildClass) | (BaseClass & AnotherChild) | (BaseClass & Vector2) @@ -509,7 +510,7 @@ TEST_CASE_FIXTURE(ClassFixture, "unions_of_intersections_of_classes") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "index_instance_property") +TEST_CASE_FIXTURE(ExternTypeFixture, "index_instance_property") { CheckResult result = check(R"( local function execute(object: BaseClass, name: string) @@ -521,7 +522,7 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property") CHECK_EQ("Attempting a dynamic property access on type 'BaseClass' is unsafe and may cause exceptions at runtime", toString(result.errors.at(0))); } -TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict") +TEST_CASE_FIXTURE(ExternTypeFixture, "index_instance_property_nonstrict") { CheckResult result = check(R"( --!nonstrict @@ -534,7 +535,7 @@ TEST_CASE_FIXTURE(ClassFixture, "index_instance_property_nonstrict") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "type_mismatch_invariance_required_for_error") +TEST_CASE_FIXTURE(ExternTypeFixture, "type_mismatch_invariance_required_for_error") { CheckResult result = check(R"( type A = { x: ChildClass } @@ -564,7 +565,7 @@ Type 'ChildClass' could not be converted into 'BaseClass' in an invariant contex } } -TEST_CASE_FIXTURE(ClassFixture, "optional_class_casts_work_in_new_solver") +TEST_CASE_FIXTURE(ExternTypeFixture, "optional_class_casts_work_in_new_solver") { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; @@ -589,7 +590,7 @@ TEST_CASE_FIXTURE(ClassFixture, "optional_class_casts_work_in_new_solver") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "callable_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "callable_extern_types") { CheckResult result = check(R"( local x : CallableClass @@ -600,7 +601,7 @@ TEST_CASE_FIXTURE(ClassFixture, "callable_classes") CHECK_EQ("number", toString(requireType("y"))); } -TEST_CASE_FIXTURE(ClassFixture, "indexable_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "indexable_extern_types") { // Test reading from an index { @@ -777,17 +778,17 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties") unfreeze(arena); - TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}}); - getMutable(instanceType)->props = {{"Parent", Property::rw(instanceType)}}; + TypeId instanceType = arena.addType(ExternType{"Instance", {}, nullopt, nullopt, {}, {}, "Test", {}}); + getMutable(instanceType)->props = {{"Parent", Property::rw(instanceType)}}; // - TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test", {}}); + TypeId workspaceType = arena.addType(ExternType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test", {}}); TypeId scriptType = - arena.addType(ClassType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test", {}}); + arena.addType(ExternType{"Script", {{"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, nullopt, {}, {}, "Test", {}}); - TypeId partType = arena.addType(ClassType{ + TypeId partType = arena.addType(ExternType{ "Part", {{"BrickColor", Property::rw(builtinTypes->stringType)}, {"Parent", Property::rw(workspaceType, instanceType)}}, instanceType, @@ -798,7 +799,7 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties") {} }); - getMutable(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}}; + getMutable(workspaceType)->props = {{"Script", Property::readonly(scriptType)}, {"Part", Property::readonly(partType)}}; frontend.globals.globalScope->bindings[frontend.globals.globalNames.names->getOrAdd("script")] = Binding{scriptType}; @@ -818,7 +819,7 @@ TEST_CASE_FIXTURE(Fixture, "read_write_class_properties") CHECK(builtinTypes->numberType == tm->givenType); } -TEST_CASE_FIXTURE(ClassFixture, "cannot_index_a_class_with_no_indexer") +TEST_CASE_FIXTURE(ExternTypeFixture, "cannot_index_a_class_with_no_indexer") { CheckResult result = check(R"( local a = BaseClass.New() @@ -829,13 +830,13 @@ TEST_CASE_FIXTURE(ClassFixture, "cannot_index_a_class_with_no_indexer") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_MESSAGE( - get(result.errors[0]), "Expected DynamicPropertyLookupOnClassesUnsafe but got " << result.errors[0] + get(result.errors[0]), "Expected DynamicPropertyLookupOnExternTypesUnsafe but got " << result.errors[0] ); CHECK(builtinTypes->errorType == requireType("c")); } -TEST_CASE_FIXTURE(ClassFixture, "cyclic_tables_are_assumed_to_be_compatible_with_classes") +TEST_CASE_FIXTURE(ExternTypeFixture, "cyclic_tables_are_assumed_to_be_compatible_with_extern_types") { /* * This is technically documenting a case where we are intentionally diff --git a/tests/TypeInfer.definitions.test.cpp b/tests/TypeInfer.definitions.test.cpp index b5b494a9..962c360a 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(LuauDontForgetToReduceUnionFunc) TEST_SUITE_BEGIN("DefinitionTests"); @@ -112,7 +111,7 @@ TEST_CASE_FIXTURE(Fixture, "load_definition_file_errors_do_not_pollute_global_sc CHECK(!barTy.has_value()); } -TEST_CASE_FIXTURE(Fixture, "definition_file_classes") +TEST_CASE_FIXTURE(Fixture, "definition_file_extern_types") { loadDefinition(R"( declare class Foo @@ -202,7 +201,7 @@ TEST_CASE_FIXTURE(Fixture, "class_definitions_cannot_extend_non_class") CHECK_EQ("Cannot use non-class type 'NotAClass' as a superclass of class 'Foo'", ge->message); } -TEST_CASE_FIXTURE(Fixture, "no_cyclic_defined_classes") +TEST_CASE_FIXTURE(Fixture, "no_cyclic_defined_extern_types") { unfreeze(frontend.globals.globalTypes); LoadDefinitionFileResult result = frontend.loadDefinitionFile( @@ -330,7 +329,7 @@ TEST_CASE_FIXTURE(Fixture, "definitions_documentation_symbols") REQUIRE(bool(barTy)); CHECK_EQ(barTy->type->documentationSymbol, "@test/globaltype/Bar"); - ClassType* barClass = getMutable(barTy->type); + ExternType* barClass = getMutable(barTy->type); REQUIRE(bool(barClass)); REQUIRE_EQ(barClass->props.count("prop"), 1); CHECK_EQ(barClass->props["prop"].documentationSymbol, "@test/globaltype/Bar.prop"); @@ -359,7 +358,7 @@ TEST_CASE_FIXTURE(Fixture, "definitions_symbols_are_generated_for_recursively_re REQUIRE(bool(myClassTy)); CHECK_EQ(myClassTy->type->documentationSymbol, "@test/globaltype/MyClass"); - ClassType* cls = getMutable(myClassTy->type); + ExternType* cls = getMutable(myClassTy->type); REQUIRE(bool(cls)); REQUIRE_EQ(cls->props.count("myMethod"), 1); @@ -482,18 +481,18 @@ TEST_CASE_FIXTURE(Fixture, "class_definition_indexer") LUAU_REQUIRE_NO_ERRORS(result); - const ClassType* ctv = get(requireType("x")); - REQUIRE(ctv != nullptr); + const ExternType* etv = get(requireType("x")); + REQUIRE(etv != nullptr); - REQUIRE(bool(ctv->indexer)); + REQUIRE(bool(etv->indexer)); - CHECK_EQ(*ctv->indexer->indexType, *builtinTypes->numberType); - CHECK_EQ(*ctv->indexer->indexResultType, *builtinTypes->stringType); + CHECK_EQ(*etv->indexer->indexType, *builtinTypes->numberType); + CHECK_EQ(*etv->indexer->indexResultType, *builtinTypes->stringType); CHECK_EQ(toString(requireType("y")), "string"); } -TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_classes") +TEST_CASE_FIXTURE(Fixture, "class_definitions_reference_other_extern_types") { loadDefinition(R"( declare class Channel @@ -535,10 +534,10 @@ TEST_CASE_FIXTURE(Fixture, "definition_file_has_source_module_name_set") std::optional fooTy = frontend.globals.globalScope->lookupType("Foo"); REQUIRE(fooTy); - const ClassType* ctv = get(fooTy->type); + const ExternType* etv = get(fooTy->type); - REQUIRE(ctv); - CHECK_EQ(ctv->definitionModuleName, "@test"); + REQUIRE(etv); + CHECK_EQ(etv->definitionModuleName, "@test"); } TEST_CASE_FIXTURE(Fixture, "recursive_redefinition_reduces_rightfully") @@ -556,7 +555,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::LuauDontForgetToReduceUnionFunc, true}}; + ScopedFastFlag _{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 003e574e..f672e43f 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -28,6 +28,7 @@ LUAU_FASTFLAG(LuauReduceUnionFollowUnionType) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) +LUAU_FASTFLAG(LuauFormatUseLastPosition) TEST_SUITE_BEGIN("TypeInferFunctions"); @@ -2806,7 +2807,7 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_of_callback_property") } } -TEST_CASE_FIXTURE(ClassFixture, "bidirectional_inference_of_class_methods") +TEST_CASE_FIXTURE(ExternTypeFixture, "bidirectional_inference_of_class_methods") { CheckResult result = check(R"( local c = ChildClass.New() @@ -3233,4 +3234,24 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_unwind_mutually_recursive_union_type_func") CHECK(err1); } +TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack") +{ + ScopedFastFlag _{FFlag::LuauFormatUseLastPosition, true}; + LUAU_REQUIRE_NO_ERRORS(check(R"( + local function foo(): (string, string, string) + return "", "", "" + end + print(string.format("%s %s %s", foo())) + )")); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "string_format_pack_variadic") +{ + ScopedFastFlag _{FFlag::LuauFormatUseLastPosition, true}; + LUAU_REQUIRE_NO_ERRORS(check(R"( + local foo : () -> (...string) = (nil :: any) + print(string.format("%s %s %s", foo())) + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp index 9ddf1b6b..69057ca9 100644 --- a/tests/TypeInfer.generics.test.cpp +++ b/tests/TypeInfer.generics.test.cpp @@ -13,6 +13,8 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) +LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) +LUAU_FASTFLAG(LuauIntersectNotNil) using namespace Luau; @@ -833,7 +835,15 @@ function clone(dict: {[X]:Y}): {[X]:Y} end )"); - LUAU_REQUIRE_NO_ERRORS(result); + if (FFlag::LuauSolverV2 && FFlag::LuauAddCallConstraintForIterableFunctions && !FFlag::LuauIntersectNotNil) + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + CHECK(get(result.errors.at(0))); + } + else + { + LUAU_REQUIRE_NO_ERRORS(result); + } } TEST_CASE_FIXTURE(Fixture, "generic_functions_should_be_memory_safe") diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 85b65b7c..6a7e150f 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -16,6 +16,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauStatForInFix) +LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) TEST_SUITE_BEGIN("TypeInferLoops"); @@ -133,6 +134,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop") for i, v in pairs({ "foo" }) do n = i s = v + print(i, v) end )"); @@ -142,6 +144,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop") { CHECK("number?" == toString(requireType("n"))); CHECK("string?" == toString(requireType("s"))); + CHECK_EQ("number", toString(requireTypeAtPosition({6, 18}))); + CHECK_EQ("string", toString(requireTypeAtPosition({6, 21}))); } else { @@ -152,22 +156,60 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop") TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next") { - // CLI-116494 The generics K and V are leaking out of the next() function somehow. - DOES_NOT_PASS_NEW_SOLVER_GUARD(); + ScopedFastFlag _{FFlag::LuauAddCallConstraintForIterableFunctions, true}; + CheckResult result = check(R"( + local n + local s + for i, v in next, { "foo" } do + n = i + s = v + print(i, v) + end + )"); + LUAU_REQUIRE_NO_ERRORS(result); + + if (FFlag::LuauSolverV2) + { + CHECK("number?" == toString(requireType("n"))); + CHECK("string?" == toString(requireType("s"))); + CHECK_EQ("number", toString(requireTypeAtPosition({6, 18}))); + CHECK_EQ("string", toString(requireTypeAtPosition({6, 21}))); + } + else + { + CHECK_EQ(*builtinTypes->numberType, *requireType("n")); + CHECK_EQ(*builtinTypes->stringType, *requireType("s")); + } +} +TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_next_and_multiple_elements") +{ + ScopedFastFlag _{FFlag::LuauAddCallConstraintForIterableFunctions, true}; CheckResult result = check(R"( local n local s for i, v in next, { "foo", "bar" } do n = i s = v + print(i, v) end )"); LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ(*builtinTypes->numberType, *requireType("n")); - CHECK_EQ(*builtinTypes->stringType, *requireType("s")); + if (FFlag::LuauSolverV2) + { + CHECK("number?" == toString(requireType("n"))); + // TODO: CLI-150066 fix these redundant unions + CHECK("(string | string)?" == toString(requireType("s"))); + CHECK_EQ("number", toString(requireTypeAtPosition({6, 18}))); + CHECK_EQ("string | string", toString(requireTypeAtPosition({6, 21}))); + } + else + { + CHECK_EQ(*builtinTypes->numberType, *requireType("n")); + CHECK_EQ(*builtinTypes->stringType, *requireType("s")); + } } TEST_CASE_FIXTURE(Fixture, "for_in_with_an_iterator_of_type_any") @@ -241,7 +283,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_with_a_custom_iterator_should_type_ch end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); + if (FFlag::LuauSolverV2 && FFlag::LuauAddCallConstraintForIterableFunctions) + { + LUAU_REQUIRE_NO_ERRORS(result); + } + else + { + LUAU_REQUIRE_ERROR_COUNT(1, result); + } } TEST_CASE_FIXTURE(Fixture, "for_in_loop_on_error") @@ -1232,6 +1281,9 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "forin_metatable_iter_mm") TEST_CASE_FIXTURE(BuiltinsFixture, "iteration_preserves_error_suppression") { + ScopedFastFlag _{FFlag::LuauAddCallConstraintForIterableFunctions, true}; + ScopedFastFlag v1{FFlag::LuauSolverV2, true}; + CheckResult result = check(R"( function first(x: any) for k, v in pairs(x) do @@ -1242,7 +1294,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "iteration_preserves_error_suppression") LUAU_REQUIRE_NO_ERRORS(result); - CHECK("any" == toString(requireTypeAtPosition({3, 22}))); + CHECK("*error-type* | ~nil" == toString(requireTypeAtPosition({3, 22}))); CHECK("any" == toString(requireTypeAtPosition({3, 25}))); } @@ -1271,4 +1323,14 @@ for p in broken() do print(p) end LUAU_REQUIRE_ERRORS(result); } +TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_require") +{ + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + + LUAU_REQUIRE_NO_ERRORS(check(R"( + for _ in require do + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index c39920c4..212a3dc5 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -13,6 +13,9 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) +LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) +LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) using namespace Luau; @@ -753,9 +756,53 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "spooky_blocked_type_laundered_by_bound_type" return Cache )"; - LUAU_REQUIRE_NO_ERRORS(check(R"( + auto result = check(R"( local _ = require(game.A); - )")); + )"); + + if (FFlag::LuauAddCallConstraintForIterableFunctions && !FFlag::LuauOptimizeFalsyAndTruthyIntersect) + { + LUAU_REQUIRE_ERROR_COUNT(3, result); + } + else + { + LUAU_REQUIRE_NO_ERRORS(result); + } +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + + auto result = check(R"( + local Cache = {} + + Cache.settings = {} + + function Cache.should_cache(url) + for key, _ in pairs(Cache.settings) do + return key + end + + return "" + end + + function Cache.is_cached(url) + local setting_key = Cache.should_cache(url) + local settings = Cache.settings[setting_key] + + return settings + end + + return Cache + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + if (FFlag::DebugLuauGreedyGeneralization) + { + CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23}))); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "cycles_dont_make_everything_any") diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 28281ad1..b709157c 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -149,9 +149,9 @@ TEST_CASE_FIXTURE(Fixture, "floor_division_binary_op") { CheckResult result = check(R"( local a = 4 // 8 - local b = -4 // 9 + local b = -4 // 9 local c = 9 - c //= -6.5 + c //= -6.5 )"); LUAU_REQUIRE_NO_ERRORS(result); @@ -1270,7 +1270,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "expected_types_through_binary_or") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(ClassFixture, "unrelated_classes_cannot_be_compared") +TEST_CASE_FIXTURE(ExternTypeFixture, "unrelated_extern_types_cannot_be_compared") { CheckResult result = check(R"( local a = BaseClass.New() diff --git a/tests/TypeInfer.provisional.test.cpp b/tests/TypeInfer.provisional.test.cpp index 037e87e4..c1ecfbf2 100644 --- a/tests/TypeInfer.provisional.test.cpp +++ b/tests/TypeInfer.provisional.test.cpp @@ -1039,7 +1039,7 @@ TEST_CASE_FIXTURE(Fixture, "optional_class_instances_are_invariant_old_solver") { DOES_NOT_PASS_NEW_SOLVER_GUARD(); - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CheckResult result = check(R"( function foo(ref: {current: Parent?}) @@ -1057,7 +1057,7 @@ TEST_CASE_FIXTURE(Fixture, "optional_class_instances_are_invariant_new_solver") { ScopedFastFlag sff{FFlag::LuauSolverV2, true}; - createSomeClasses(&frontend); + createSomeExternTypes(&frontend); CheckResult result = check(R"( function foo(ref: {read current: Parent?}) diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index 7adfad3b..72f36b65 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -12,9 +12,9 @@ LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(LuauIntersectNotNil) LUAU_FASTFLAG(LuauSkipNoRefineDuringRefinement) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) -LUAU_FASTFLAG(LuauDoNotLeakNilInRefinement) LUAU_FASTFLAG(LuauSimplyRefineNotNil) LUAU_FASTFLAG(LuauWeakNilRefinementType) +LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) using namespace Luau; @@ -78,38 +78,38 @@ struct MagicInstanceIsA final : MagicFunction -struct RefinementClassFixture : BuiltinsFixture +struct RefinementExternTypeFixture : BuiltinsFixture { - RefinementClassFixture() + RefinementExternTypeFixture() { TypeArena& arena = frontend.globals.globalTypes; NotNull scope{frontend.globals.globalScope.get()}; - std::optional rootSuper = std::make_optional(builtinTypes->classType); + std::optional rootSuper = std::make_optional(builtinTypes->externType); unfreeze(arena); - TypeId vec3 = arena.addType(ClassType{"Vector3", {}, rootSuper, std::nullopt, {}, nullptr, "Test", {}}); - getMutable(vec3)->props = { + TypeId vec3 = arena.addType(ExternType{"Vector3", {}, rootSuper, std::nullopt, {}, nullptr, "Test", {}}); + getMutable(vec3)->props = { {"X", Property{builtinTypes->numberType}}, {"Y", Property{builtinTypes->numberType}}, {"Z", Property{builtinTypes->numberType}}, }; - TypeId inst = arena.addType(ClassType{"Instance", {}, rootSuper, std::nullopt, {}, nullptr, "Test", {}}); + TypeId inst = arena.addType(ExternType{"Instance", {}, rootSuper, std::nullopt, {}, nullptr, "Test", {}}); TypePackId isAParams = arena.addTypePack({inst, builtinTypes->stringType}); TypePackId isARets = arena.addTypePack({builtinTypes->booleanType}); TypeId isA = arena.addType(FunctionType{isAParams, isARets}); getMutable(isA)->magic = std::make_shared(); - getMutable(inst)->props = { + getMutable(inst)->props = { {"Name", Property{builtinTypes->stringType}}, {"IsA", Property{isA}}, }; - TypeId folder = frontend.globals.globalTypes.addType(ClassType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); - TypeId part = frontend.globals.globalTypes.addType(ClassType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); - getMutable(part)->props = { + TypeId folder = frontend.globals.globalTypes.addType(ExternType{"Folder", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); + TypeId part = frontend.globals.globalTypes.addType(ExternType{"Part", {}, inst, std::nullopt, {}, nullptr, "Test", {}}); + getMutable(part)->props = { {"Position", Property{vec3}}, }; @@ -1354,7 +1354,7 @@ TEST_CASE_FIXTURE(Fixture, "refine_a_property_not_to_be_nil_through_an_intersect LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "discriminate_from_isa_of_x") { CheckResult result = check(R"( type T = {tag: "Part", x: Part} | {tag: "Folder", x: Folder} @@ -1383,7 +1383,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "discriminate_from_isa_of_x") } } -TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeguard_cast_free_table_to_vector") { // CLI-115286 - Refining via type(x) == 'vector' does not work in the new solver DOES_NOT_PASS_NEW_SOLVER_GUARD(); @@ -1411,7 +1411,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_free_table_to_vector") CHECK_EQ("{+ X: a, Y: b, Z: c +}", toString(requireTypeAtPosition({9, 28}))); // type(vec) ~= "vector" and typeof(vec) ~= "Instance" } -TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to_vector") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeguard_cast_instance_or_vector3_to_vector") { CheckResult result = check(R"( local function f(x: Instance | Vector3) @@ -1429,7 +1429,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeguard_cast_instance_or_vector3_to CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_for_all_the_userdata") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "type_narrow_for_all_the_userdata") { CheckResult result = check(R"( local function f(x: string | number | Instance | Vector3) @@ -1447,7 +1447,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_for_all_the_userdata") CHECK_EQ("number | string", toString(requireTypeAtPosition({5, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_but_the_discriminant_type_isnt_a_class") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "type_narrow_but_the_discriminant_type_isnt_a_class") { CheckResult result = check(R"( local function f(x: string | number | Instance | Vector3) @@ -1473,7 +1473,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "type_narrow_but_the_discriminant_type } } -TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "eliminate_subclasses_of_instance") { CheckResult result = check(R"( local function f(x: Part | Folder | string) @@ -1491,7 +1491,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "eliminate_subclasses_of_instance") CHECK_EQ("string", toString(requireTypeAtPosition({5, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or_string_or_vector3") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "narrow_from_subclasses_of_instance_or_string_or_vector3") { CheckResult result = check(R"( local function f(x: Part | Folder | string | Vector3) @@ -1509,7 +1509,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "narrow_from_subclasses_of_instance_or CHECK_EQ("Vector3 | string", toString(requireTypeAtPosition({5, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is_table") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_as_any_if_x_is_instance_elseif_x_is_table") { // CLI-117136 - this code doesn't finish constraint solving and has blocked types in the output if (FFlag::LuauSolverV2) @@ -1540,7 +1540,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "x_as_any_if_x_is_instance_elseif_x_is } } -TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_instance_without_using_typeof") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_param_of_type_instance_without_using_typeof") { CheckResult result = check(R"( local function f(x: Instance) @@ -1558,7 +1558,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_instance_without CHECK_EQ("never", toString(requireTypeAtPosition({5, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_without_using_typeof") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_param_of_type_folder_or_part_without_using_typeof") { CheckResult result = check(R"( local function f(x: Part | Folder) @@ -1576,7 +1576,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_param_of_type_folder_or_part_w CHECK_EQ("Part", toString(requireTypeAtPosition({5, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahead_of_time") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "isa_type_refinement_must_be_known_ahead_of_time") { // CLI-115087 - The new solver does not consistently combine tables with // class types when they appear in the upper bounds of a free type. @@ -1600,7 +1600,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "isa_type_refinement_must_be_known_ahe CHECK_EQ("Instance", toString(requireTypeAtPosition({5, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "x_is_not_instance_or_else_not_part") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "x_is_not_instance_or_else_not_part") { // CLI-117135 - RefinementTests.x_is_not_instance_or_else_not_part not correctly applying refinements to a function parameter if (FFlag::LuauSolverV2) @@ -1822,7 +1822,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table_then_clone_it") } } -TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_during_constraint_solving_stage") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_a_param_that_got_resolved_during_constraint_solving_stage") { // CLI-117134 - Applying a refinement causes an optional value access error. if (FFlag::LuauSolverV2) @@ -1844,7 +1844,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_duri CHECK_EQ("Folder | string", toString(requireTypeAtPosition({7, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "refine_a_param_that_got_resolved_during_constraint_solving_stage_2") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "refine_a_param_that_got_resolved_during_constraint_solving_stage_2") { CheckResult result = check(R"( local function hof(f: (Instance) -> ()) end @@ -2095,7 +2095,14 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table") LUAU_REQUIRE_NO_ERRORS(result); - CHECK_EQ("(unknown) -> (unknown, unknown)", toString(requireType("f"))); + if (FFlag::LuauAddCallConstraintForIterableFunctions) + { + CHECK_EQ("(unknown) -> (~nil, unknown)", toString(requireType("f"))); + } + else + { + CHECK_EQ("(unknown) -> (unknown, unknown)", toString(requireType("f"))); + } } TEST_CASE_FIXTURE(BuiltinsFixture, "conditional_refinement_should_stay_error_suppressing") @@ -2171,7 +2178,7 @@ end CHECK("string" == toString(t)); } -TEST_CASE_FIXTURE(RefinementClassFixture, "mutate_prop_of_some_refined_symbol") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symbol") { CheckResult result = check(R"( local function instances(): {Instance} error("") end @@ -2187,7 +2194,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "mutate_prop_of_some_refined_symbol") LUAU_REQUIRE_NO_ERRORS(result); } -TEST_CASE_FIXTURE(RefinementClassFixture, "mutate_prop_of_some_refined_symbol_2") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "mutate_prop_of_some_refined_symbol_2") { CheckResult result = check(R"( type Result = never @@ -2371,7 +2378,7 @@ end )"); } -TEST_CASE_FIXTURE(RefinementClassFixture, "typeof_instance_refinement") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeof_instance_refinement") { CheckResult result = check(R"( local function f(x: Instance | Vector3) @@ -2389,7 +2396,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeof_instance_refinement") CHECK_EQ("Vector3", toString(requireTypeAtPosition({5, 28}))); } -TEST_CASE_FIXTURE(RefinementClassFixture, "typeof_instance_error") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeof_instance_error") { CheckResult result = check(R"( local function f(x: Part) @@ -2402,7 +2409,7 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "typeof_instance_error") LUAU_REQUIRE_ERROR_COUNT(1, result); } -TEST_CASE_FIXTURE(RefinementClassFixture, "typeof_instance_isa_refinement") +TEST_CASE_FIXTURE(RefinementExternTypeFixture, "typeof_instance_isa_refinement") { CheckResult result = check(R"( local function f(x: Part | Folder | string) @@ -2510,8 +2517,6 @@ TEST_CASE_FIXTURE(Fixture, "truthy_call_of_function_with_table_value_as_argument TEST_CASE_FIXTURE(BuiltinsFixture, "function_calls_are_not_nillable") { - ScopedFastFlag _{FFlag::LuauDoNotLeakNilInRefinement, true}; - LUAU_CHECK_NO_ERRORS(check(R"( local BEFORE_SLASH_PATTERN = "^(.*)[\\/]" function operateOnPath(path: string): string? @@ -2526,8 +2531,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_calls_are_not_nillable") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1528_method_calls_are_not_nillable") { - ScopedFastFlag _{FFlag::LuauDoNotLeakNilInRefinement, true}; - LUAU_CHECK_NO_ERRORS(check(R"( type RunService = { IsRunning: (RunService) -> boolean diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 7e9b607e..6c6eaef9 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -21,17 +21,16 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) -LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) -LUAU_FASTFLAG(LuauTrackInteriorFreeTablesOnScope) +LUAU_FASTFLAG(DebugLuauGreedyGeneralization) LUAU_FASTFLAG(LuauFollowTableFreeze) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) -LUAU_FASTFLAG(LuauSearchForRefineableType) LUAU_FASTFLAG(LuauImproveTypePathsInErrors) LUAU_FASTFLAG(LuauBidirectionalInferenceCollectIndexerTypes) LUAU_FASTFLAG(LuauBidirectionalFailsafe) LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) +LUAU_FASTFLAG(LuauTypeCheckerStricterIndexCheck) TEST_SUITE_BEGIN("TableTests"); @@ -3943,11 +3942,6 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_can_turn_into_a_scalar_if_it_is_compati TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible") { - ScopedFastFlag sffs[] = { - {FFlag::LuauTrackInteriorFreeTypesOnScope, true}, - {FFlag::LuauTrackInteriorFreeTablesOnScope, true}, - }; - CheckResult result = check(R"( local function f(s): string local foo = s:absolutely_no_scalar_has_this_method() @@ -4777,7 +4771,10 @@ TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_ LUAU_REQUIRE_NO_ERRORS(result); // FIXME CLI-114134. We need to simplify types more consistently. - CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f"))); + if (FFlag::DebugLuauGreedyGeneralization) + CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f"))); + else + CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f"))); } TEST_CASE_FIXTURE(Fixture, "write_to_union_property_not_all_present") @@ -5553,10 +5550,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_index_syntax_bidirectional_infer_with_tables TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauSearchForRefineableType, true}, - }; + ScopedFastFlag _{FFlag::LuauSolverV2, true}; // NOTE: This probably should be revisited after CLI-143852: we end up // cyclic types with *tons* of overlap. @@ -5665,6 +5659,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "function_call_in_indexer_with_compound_assig )"); } + +TEST_CASE_FIXTURE(Fixture, "stop_refining_new_table_indices_for_non_primitive_tables") +{ + ScopedFastFlag _{FFlag::LuauSolverV2, true}; + ScopedFastFlag stricterIndexCheck{FFlag::LuauTypeCheckerStricterIndexCheck, true}; + + CheckResult result = check(R"( + local foo:{val:number} = {val = 1} + if foo.vall then + local bar = foo.vall + end + )"); + + LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_CHECK_ERROR(result, UnknownProperty); +} + TEST_CASE_FIXTURE(Fixture, "fuzz_match_literal_type_crash_again") { ScopedFastFlag sffs[] = { diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index e0c5308e..87219cf4 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -32,12 +32,12 @@ LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauCacheInferencePerAstExpr) LUAU_FASTFLAG(LuauLimitIterationWhenCheckingArgumentCounts) LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked) -LUAU_FASTFLAG(LuauTrackInteriorFreeTypesOnScope) LUAU_FASTFLAG(LuauNonReentrantGeneralization2) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauStringPartLengthLimit) LUAU_FASTFLAG(LuauSimplificationRecheckAssumption) +LUAU_FASTFLAG(LuauAlwaysResolveAstTypes) using namespace Luau; @@ -1982,6 +1982,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_dont_double_solve_compound_assignment" * doctes TEST_CASE_FIXTURE(Fixture, "assert_allows_singleton_union_or_intersection") { + ScopedFastFlag sff{FFlag::LuauAlwaysResolveAstTypes, true}; LUAU_REQUIRE_NO_ERRORS(check(R"( local x = 42 :: | number local y = 42 :: & number @@ -2041,7 +2042,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauTrackInteriorFreeTypesOnScope, true}, {FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauNonReentrantGeneralization2, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true} @@ -2080,7 +2080,6 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauTrackInteriorFreeTypesOnScope, true}, {FFlag::LuauNonReentrantGeneralization2, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, }; @@ -2113,7 +2112,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauTrackInteriorFreeTypesOnScope, true}, {FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauNonReentrantGeneralization2, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 9729628f..2a1ba756 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -4,6 +4,7 @@ #include "doctest.h" LUAU_FASTFLAG(LuauSolverV2) +LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) using namespace Luau; @@ -523,4 +524,35 @@ TEST_CASE_FIXTURE(Fixture, "typestate_unknown_global") CHECK(get(result.errors[0])); } +TEST_CASE_FIXTURE(BuiltinsFixture, "fuzzer_normalized_type_variables_are_bad" * doctest::timeout(0.5)) +{ + ScopedFastFlag _{FFlag::LuauRefineWaitForBlockedTypesInTarget, true}; + // We do not care about the errors here, only that this finishes typing + // in a sensible amount of time. + LUAU_REQUIRE_ERRORS(check(R"( + local _ + while _[""] do + _, _ = nil + while _.n0 do + _, _ = nil + end + _, _ = nil + end + while _[""] do + while if _ then if _ then _ else "" else "" do + _, _ = nil + do + end + _, _, _ = nil + end + _, _ = nil + _, _, _ = nil + while _.readi16 do + _, _ = nil + end + _, _ = nil + end + )")); +} + TEST_SUITE_END(); diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp index facd62ad..72eb107c 100644 --- a/tests/TypePath.test.cpp +++ b/tests/TypePath.test.cpp @@ -121,7 +121,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "table_property") CHECK(traverseForType(requireType("x"), Path(TypePath::Property{"y", true}), builtinTypes) == builtinTypes->numberType); } -TEST_CASE_FIXTURE(ClassFixture, "class_property") +TEST_CASE_FIXTURE(ExternTypeFixture, "class_property") { CHECK(traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), builtinTypes) == builtinTypes->numberType); } @@ -217,7 +217,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "index") } } -TEST_CASE_FIXTURE(ClassFixture, "metatables") +TEST_CASE_FIXTURE(ExternTypeFixture, "metatables") { SUBCASE("string") { @@ -266,7 +266,7 @@ TEST_CASE_FIXTURE(ClassFixture, "metatables") SUBCASE("class") { auto result = traverseForType(vector2InstanceType, Path(TypeField::Metatable), builtinTypes); - // ClassFixture's Vector2 metatable is just an empty table, but it's there. + // ExternTypeFixture's Vector2 metatable is just an empty table, but it's there. CHECK(result); } } @@ -334,7 +334,7 @@ TEST_CASE_FIXTURE(TypePathFixture, "indexers") } } - // TODO: Class types + // TODO: Extern types } TEST_CASE_FIXTURE(TypePathFixture, "negated") @@ -508,7 +508,7 @@ TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains") type Meta = { __add: (Tab, Tab) -> number, } - + type Tab = typeof(setmetatable({}, {} :: Meta)) )"); diff --git a/tests/TypeVar.test.cpp b/tests/TypeVar.test.cpp index 1e5fdaf1..50064706 100644 --- a/tests/TypeVar.test.cpp +++ b/tests/TypeVar.test.cpp @@ -318,18 +318,18 @@ TEST_CASE("tagging_tables") CHECK(Luau::hasTag(&ttv, "foo")); } -TEST_CASE("tagging_classes") +TEST_CASE("tagging_extern_types") { - Type base{ClassType{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test", {}}}; + Type base{ExternType{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test", {}}}; CHECK(!Luau::hasTag(&base, "foo")); Luau::attachTag(&base, "foo"); CHECK(Luau::hasTag(&base, "foo")); } -TEST_CASE("tagging_subclasses") +TEST_CASE("tagging_subextern_types") { - Type base{ClassType{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test", {}}}; - Type derived{ClassType{"Derived", {}, &base, std::nullopt, {}, nullptr, "Test", {}}}; + Type base{ExternType{"Base", {}, std::nullopt, std::nullopt, {}, nullptr, "Test", {}}}; + Type derived{ExternType{"Derived", {}, &base, std::nullopt, {}, nullptr, "Test", {}}}; CHECK(!Luau::hasTag(&base, "foo")); CHECK(!Luau::hasTag(&derived, "foo")); diff --git a/tests/conformance/cyield.luau b/tests/conformance/cyield.luau new file mode 100644 index 00000000..157d9176 --- /dev/null +++ b/tests/conformance/cyield.luau @@ -0,0 +1,135 @@ +-- This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +print('testing yields from C functions') + +-- Regular yield from a C function +do + local co = coroutine.wrap(singleYield) + assert(co() == 2) + assert(co() == 4) +end + +-- Multiple yields from a C function +do + local co = coroutine.wrap(multipleYields) + assert(co(10) == 11) + assert(co() == 12) + assert(co() == 13) + assert(co() == 14) +end + +-- Multiple yields from a C function (unused extra arguments) +do + local co = coroutine.wrap(multipleYields) + assert(co(10, -1) == 11) + assert(co(-1, -2) == 12) + assert(co(-1, -2) == 13) + assert(co(-1, -2) == 14) +end + +-- Multiple yields from nested yieldable C functions +do + local co = coroutine.wrap(multipleYieldsWithNestedCall) + assert(co(10, true) == 105) + assert(co() == 115) + assert(co() == 210) + assert(co() == 220) +end + +-- Multiple yields from nested yieldable C functions (unused extra arguments) +do + local co = coroutine.wrap(multipleYieldsWithNestedCall) + assert(co(10, true, -1) == 105) + assert(co(-1, -2) == 115) + assert(co(-1, -2) == 210) + assert(co(-1, -2) == 220) +end + +do + local co = coroutine.wrap(multipleYieldsWithNestedCall) + assert(co(10, false) == 110) + assert(co() == 210) + assert(co() == 220) +end + +local function nonyieldable(x, y) + return x / y +end + +local function yieldable(x, y) + local a, b = coroutine.yield(x + y) + + for i = 1,10 do + a, b = coroutine.yield(a * b) + end + + coroutine.yield(a - b) + return x / y +end + +-- yieldable Luau function that acts as a pass-through +do + local co = coroutine.create(function(x, y) + return yieldable(x, y) + end) + + local s, x = coroutine.resume(co, 1, 2) + assert(s) + assert(x == 3) + + for i = 1, 10 do + local s, x = coroutine.resume(co, 4, 8) + assert(s) + assert(x == 32) + end + + local s, x = coroutine.resume(co, 10, 5) + assert(s) + assert(x == 5) + local s, x = coroutine.resume(co) + assert(s) + assert(x == 0.5) +end + +-- yieldable C function that acts as a pass-through (regular) +local function passthroughcheck(f) + local x = f(nonyieldable, 1, 2) + assert(x == 0.5) +end + +passthroughcheck(passthroughCall) +passthroughcheck(passthroughCallMoreResults) +passthroughcheck(passthroughCallArgReuse) +passthroughcheck(passthroughCallVaradic) +passthroughcheck(passthroughCallWithState) + +-- yieldable C function that acts as a pass-through (yield) +local function passthroughcheckyield(f) + local co = coroutine.create(function(x, y) + return f(yieldable, x, y) + end) + + local s, x = coroutine.resume(co, 1, 2) + assert(s) + assert(x == 3) + + for i = 1, 10 do + local s, x = coroutine.resume(co, 4, 8) + assert(s) + assert(x == 32) + end + + local s, x = coroutine.resume(co, 10, 5) + assert(s) + assert(x == 5) + local s, x = coroutine.resume(co) + assert(s) + assert(x == 0.5) +end + +passthroughcheckyield(passthroughCall) +passthroughcheckyield(passthroughCallMoreResults) +passthroughcheckyield(passthroughCallArgReuse) +passthroughcheckyield(passthroughCallVaradic) +passthroughcheckyield(passthroughCallWithState) + +return "OK"