From c51743268bcbe5c5af1bd78bcacaefe7f6fe3391 Mon Sep 17 00:00:00 2001 From: Andy Friesen Date: Fri, 25 Apr 2025 14:19:27 -0700 Subject: [PATCH 1/2] Sync to upstream/release/671 (#1787) # General * Internally rename `ClassType` to `ExternType`. In definition files, the syntax to define these types has changed to `declare extern type Foo with prop: type end` * Add `luarequire_registermodule` to Luau.Require * Support yieldable Luau C functions calling other functions * Store return types as `AstTypePack*` on Ast nodes ## New Solver * Improve the logic that determines constraint dispatch ordering * Fix a crash in the type solver that arose when using multi-return functions with `string.format` * Fix https://github.com/luau-lang/luau/issues/1736 * Initial steps toward rethinking function generalization: * Instead of generalizing every type in a function all at once, we will instead generalize individual type variables once their bounds have been fully resolved. This will make it possible to properly interleave type function reduction and generalization. * Magic functions are no longer considered magical in cases where they are not explicitly called by the code. * The most prominent example of this is in `for..in` loops where the function call is part of the desugaring process. * Almost all magic functions work by directly inspecting the AST, so they can't work without an AST fragment anyway. * Further, none of the magic functions we have are usefully used in this way. Co-authored-by: Andy Friesen Co-authored-by: Ariel Weiss Co-authored-by: Hunter Goldstein Co-authored-by: Sora Kanosue Co-authored-by: Talha Pathan Co-authored-by: Varun Saini Co-authored-by: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --- Analysis/include/Luau/AutocompleteTypes.h | 4 +- Analysis/include/Luau/ConstraintGenerator.h | 16 +- Analysis/include/Luau/ConstraintSet.h | 32 ++ Analysis/include/Luau/ConstraintSolver.h | 22 + Analysis/include/Luau/DataFlowGraph.h | 2 +- Analysis/include/Luau/Error.h | 6 +- Analysis/include/Luau/FileResolver.h | 3 +- Analysis/include/Luau/Instantiation.h | 4 +- Analysis/include/Luau/Normalize.h | 22 +- Analysis/include/Luau/Quantify.h | 2 +- Analysis/include/Luau/Subtyping.h | 15 +- Analysis/include/Luau/ToString.h | 1 + Analysis/include/Luau/Type.h | 25 +- Analysis/include/Luau/TypeAttach.h | 2 +- Analysis/include/Luau/TypeChecker2.h | 2 +- Analysis/include/Luau/TypeFunctionRuntime.h | 6 +- Analysis/include/Luau/TypeFwd.h | 2 +- Analysis/include/Luau/TypeInfer.h | 6 +- Analysis/include/Luau/Unifier.h | 2 +- Analysis/include/Luau/VisitType.h | 24 +- Analysis/src/Anyification.cpp | 2 +- Analysis/src/ApplyTypeFunction.cpp | 2 +- Analysis/src/AstJsonEncoder.cpp | 30 +- Analysis/src/AstQuery.cpp | 8 +- Analysis/src/AutocompleteCore.cpp | 173 ++++-- Analysis/src/BuiltinDefinitions.cpp | 19 +- Analysis/src/Clone.cpp | 2 +- Analysis/src/Constraint.cpp | 6 +- Analysis/src/ConstraintGenerator.cpp | 217 ++++--- Analysis/src/ConstraintSolver.cpp | 386 +++++++------ Analysis/src/DataFlowGraph.cpp | 30 +- Analysis/src/Differ.cpp | 20 +- Analysis/src/EmbeddedBuiltinDefinitions.cpp | 34 +- Analysis/src/EqSatSimplification.cpp | 16 +- Analysis/src/Error.cpp | 14 +- Analysis/src/FileResolver.cpp | 26 +- Analysis/src/FragmentAutocomplete.cpp | 14 +- Analysis/src/Frontend.cpp | 76 ++- Analysis/src/Generalization.cpp | 46 +- Analysis/src/InferPolarity.cpp | 10 +- Analysis/src/Instantiation.cpp | 4 +- Analysis/src/Instantiation2.cpp | 2 +- Analysis/src/IostreamHelpers.cpp | 4 +- Analysis/src/Linter.cpp | 15 +- Analysis/src/NonStrictTypeChecker.cpp | 17 +- Analysis/src/Normalize.cpp | 206 +++---- Analysis/src/RequireTracer.cpp | 8 + Analysis/src/Simplify.cpp | 14 +- Analysis/src/Substitution.cpp | 40 +- Analysis/src/Subtyping.cpp | 40 +- Analysis/src/ToDot.cpp | 4 +- Analysis/src/ToString.cpp | 20 +- Analysis/src/TopoSortStatements.cpp | 7 + Analysis/src/Transpiler.cpp | 35 +- Analysis/src/Type.cpp | 45 +- Analysis/src/TypeAttach.cpp | 70 ++- Analysis/src/TypeChecker2.cpp | 76 ++- Analysis/src/TypeFunction.cpp | 125 ++-- Analysis/src/TypeFunctionReductionGuesser.cpp | 2 +- Analysis/src/TypeFunctionRuntime.cpp | 36 +- Analysis/src/TypeFunctionRuntimeBuilder.cpp | 16 +- Analysis/src/TypeInfer.cpp | 156 +++-- Analysis/src/TypePath.cpp | 6 +- Analysis/src/TypeUtils.cpp | 12 +- Analysis/src/Unifier.cpp | 50 +- Analysis/src/Unifier2.cpp | 10 +- Ast/include/Luau/Ast.h | 98 +++- Ast/include/Luau/Cst.h | 4 +- Ast/include/Luau/Parser.h | 22 +- Ast/src/Ast.cpp | 180 +++++- Ast/src/Cst.cpp | 10 + Ast/src/Parser.cpp | 541 +++++++++++++++--- CLI/src/ReplRequirer.cpp | 2 +- Compiler/src/Compiler.cpp | 66 ++- .../Navigator/include/Luau/RequireNavigator.h | 1 - Require/Runtime/include/Luau/Require.h | 15 +- Require/Runtime/src/Require.cpp | 29 +- Require/Runtime/src/RequireImpl.cpp | 82 ++- Require/Runtime/src/RequireImpl.h | 3 + Sources.cmake | 1 + VM/include/lualib.h | 3 + VM/src/laux.cpp | 17 + VM/src/lbaselib.cpp | 34 +- VM/src/ldo.cpp | 105 +++- fuzz/proto.cpp | 12 +- fuzz/protoprint.cpp | 6 +- tests/AstJsonEncoder.test.cpp | 77 ++- tests/Autocomplete.test.cpp | 13 +- tests/BuiltinDefinitions.test.cpp | 4 +- tests/ClassFixture.cpp | 60 +- tests/ClassFixture.h | 4 +- tests/Conformance.test.cpp | 233 +++++++- tests/Differ.test.cpp | 4 +- tests/EqSatSimplification.test.cpp | 10 +- tests/Fixture.cpp | 18 +- tests/Fixture.h | 2 +- tests/FragmentAutocomplete.test.cpp | 26 +- tests/Generalization.test.cpp | 10 +- tests/Linter.test.cpp | 27 +- tests/Module.test.cpp | 14 +- tests/Normalize.test.cpp | 37 +- tests/Parser.test.cpp | 468 ++++++++++++--- tests/RequireByString.test.cpp | 15 + tests/RequireTracer.test.cpp | 46 ++ tests/Simplify.test.cpp | 8 +- tests/Subtyping.test.cpp | 34 +- tests/ToDot.test.cpp | 12 +- tests/TopoSort.test.cpp | 26 + tests/Transpiler.test.cpp | 138 +++-- tests/TypeFunction.test.cpp | 18 +- tests/TypeFunction.user.test.cpp | 58 +- tests/TypeInfer.anyerror.test.cpp | 56 +- tests/TypeInfer.classes.test.cpp | 97 ++-- tests/TypeInfer.definitions.test.cpp | 29 +- tests/TypeInfer.functions.test.cpp | 23 +- tests/TypeInfer.generics.test.cpp | 12 +- tests/TypeInfer.loops.test.cpp | 74 ++- tests/TypeInfer.modules.test.cpp | 51 +- tests/TypeInfer.operators.test.cpp | 6 +- tests/TypeInfer.provisional.test.cpp | 4 +- tests/TypeInfer.refinements.test.cpp | 73 +-- tests/TypeInfer.tables.test.cpp | 37 +- tests/TypeInfer.test.cpp | 6 +- tests/TypeInfer.typestates.test.cpp | 32 ++ tests/TypePath.test.cpp | 10 +- tests/TypeVar.test.cpp | 10 +- tests/conformance/cyield.luau | 135 +++++ 127 files changed, 4025 insertions(+), 1572 deletions(-) create mode 100644 Analysis/include/Luau/ConstraintSet.h create mode 100644 tests/conformance/cyield.luau 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 212b9aa6..7a3ff88f 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 9d59f41c..1e89fdeb 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -1253,15 +1253,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") @@ -1300,6 +1304,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"( @@ -1505,11 +1522,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}}, @@ -1828,7 +1845,7 @@ Account = { balance=0 } function Account:deposit(v) self.balance = self.balance + v end - + Account:deposit(200.00) )"); @@ -1849,7 +1866,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 5ab7ef9e..a86f2554 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) @@ -324,9 +325,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. @@ -761,7 +762,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 } @@ -780,24 +781,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"))); } @@ -869,9 +870,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"))); @@ -884,15 +885,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"))); } @@ -1242,6 +1243,12 @@ do end #if 0 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 0edb68fb..d0725d9f 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; @@ -1983,6 +1983,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 @@ -2042,7 +2043,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} @@ -2081,7 +2081,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}, }; @@ -2114,7 +2113,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" From d9aa88e772a7a460272e3efc79f029b64ac18022 Mon Sep 17 00:00:00 2001 From: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Date: Mon, 28 Apr 2025 11:15:43 -0700 Subject: [PATCH 2/2] Fix crash when `require` is called from root VM stack (#1788) Copied from #1785: > If require is called from the root interpreter stack (e.g. using C API) then lua_getinfo call will not succeed, leaving garbage in lua_Debug ar struct. > Accessing later ar.source as null-terminated string is unsafe and can cause a crash. > > This PR adds a check to ensure that lua_getinfo call is successful. Co-authored-by: Alex Orlenko --- Require/Runtime/src/RequireImpl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Require/Runtime/src/RequireImpl.cpp b/Require/Runtime/src/RequireImpl.cpp index 30575964..5b0dd987 100644 --- a/Require/Runtime/src/RequireImpl.cpp +++ b/Require/Runtime/src/RequireImpl.cpp @@ -170,7 +170,8 @@ int lua_proxyrequire(lua_State* L) int lua_require(lua_State* L) { lua_Debug ar; - lua_getinfo(L, 1, "s", &ar); + if (!lua_getinfo(L, 1, "s", &ar)) + luaL_error(L, "require is not supported in this context"); return lua_requireinternal(L, ar.source); }