From 92b03384000710fd9e12ad74a211792717af4296 Mon Sep 17 00:00:00 2001 From: ayoungbloodrbx Date: Fri, 30 May 2025 11:17:49 -0700 Subject: [PATCH] Sync to upstream/release/676 (#1856) We're back on track after the long weekend! ## General - `clang-format`ed new code. Keep your code tidy! - Disable some Luau tests that are broken currently. - Enable fragment autocomplete to do tagged union completion for modules typechecked in the old solver. ## New Type Solver - Fix false positives on generic type packs in non-strict mode. - Update type signature of `setmetatable` to be `(T, MT) -> setmetatable`. - Make local type aliases available in type functions. For example: ``` type Foo = number type Array = {T} type function Bar(t) return types.unionof(Foo, Array(t)) end ``` ## VM/Runtime - Make sure `lua_unref` doesn't accept refs which did not exist in the table. --- 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: Vighnesh Vijay Co-authored-by: Vyacheslav Egorov --------- Co-authored-by: Hunter Goldstein Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com> Co-authored-by: Menarul Alam Co-authored-by: Aviral Goel Co-authored-by: Vighnesh Co-authored-by: Vyacheslav Egorov Co-authored-by: Ariel Weiss Co-authored-by: Andy Friesen --- Analysis/include/Luau/ConstraintGenerator.h | 6 +- Analysis/include/Luau/ConstraintSet.h | 6 +- Analysis/include/Luau/ConstraintSolver.h | 8 + Analysis/include/Luau/Error.h | 5 +- Analysis/include/Luau/ExpectedTypeVisitor.h | 74 +++++++ Analysis/include/Luau/Module.h | 3 + Analysis/include/Luau/Normalize.h | 55 +---- Analysis/include/Luau/Scope.h | 2 + Analysis/include/Luau/Subtyping.h | 17 +- Analysis/include/Luau/Type.h | 4 +- Analysis/include/Luau/TypeFunction.h | 5 + Analysis/include/Luau/TypeFunctionRuntime.h | 4 + Analysis/include/Luau/TypeIds.h | 67 ++++++ Analysis/include/Luau/TypeUtils.h | 17 ++ Analysis/src/AutocompleteCore.cpp | 7 +- Analysis/src/BuiltinDefinitions.cpp | 52 +++-- Analysis/src/Constraint.cpp | 10 +- Analysis/src/ConstraintGenerator.cpp | 187 +++++++++-------- Analysis/src/ConstraintSolver.cpp | 40 ++-- Analysis/src/Error.cpp | 8 +- Analysis/src/ExpectedTypeVisitor.cpp | 213 ++++++++++++++++++++ Analysis/src/FragmentAutocomplete.cpp | 35 +++- Analysis/src/Frontend.cpp | 21 +- Analysis/src/Generalization.cpp | 23 +-- Analysis/src/InferPolarity.cpp | 4 +- Analysis/src/Linter.cpp | 24 +-- Analysis/src/Module.cpp | 15 +- Analysis/src/NonStrictTypeChecker.cpp | 32 ++- Analysis/src/Normalize.cpp | 144 ------------- Analysis/src/Scope.cpp | 23 +++ Analysis/src/Subtyping.cpp | 30 +-- Analysis/src/TableLiteralInference.cpp | 10 - Analysis/src/Transpiler.cpp | 8 +- Analysis/src/Type.cpp | 2 +- Analysis/src/TypeChecker2.cpp | 45 +++-- Analysis/src/TypeFunction.cpp | 202 +++++++++++++++++-- Analysis/src/TypeFunctionRuntime.cpp | 2 +- Analysis/src/TypeFunctionRuntimeBuilder.cpp | 4 +- Analysis/src/TypeIds.cpp | 153 ++++++++++++++ Analysis/src/TypeInfer.cpp | 13 +- Analysis/src/TypeUtils.cpp | 17 +- Analysis/src/Unifier2.cpp | 10 +- Ast/include/Luau/Cst.h | 2 +- Ast/include/Luau/Parser.h | 2 +- Ast/src/Cst.cpp | 19 +- Ast/src/Parser.cpp | 30 ++- CLI/include/Luau/ReplRequirer.h | 13 +- CodeGen/src/EmitCommonA64.h | 4 +- Sources.cmake | 4 + VM/src/lapi.cpp | 25 ++- tests/AstJsonEncoder.test.cpp | 1 - tests/Autocomplete.test.cpp | 85 +++++++- tests/DataFlowGraph.test.cpp | 1 - tests/Fixture.h | 2 + tests/FragmentAutocomplete.test.cpp | 165 +++++++++++++++ tests/Frontend.test.cpp | 3 +- tests/Generalization.test.cpp | 14 +- tests/InferPolarity.test.cpp | 4 +- tests/Linter.test.cpp | 4 +- tests/NonStrictTypeChecker.test.cpp | 13 ++ tests/Normalize.test.cpp | 6 +- tests/Parser.test.cpp | 19 +- tests/Subtyping.test.cpp | 18 +- tests/ToString.test.cpp | 40 ++-- tests/TypeFunction.test.cpp | 12 +- tests/TypeFunction.user.test.cpp | 206 ++++++++++++++++++- tests/TypeInfer.aliases.test.cpp | 4 - tests/TypeInfer.anyerror.test.cpp | 1 - tests/TypeInfer.builtins.test.cpp | 31 ++- tests/TypeInfer.classes.test.cpp | 3 +- tests/TypeInfer.functions.test.cpp | 86 ++++---- tests/TypeInfer.intersectionTypes.test.cpp | 179 ++++++++-------- tests/TypeInfer.loops.test.cpp | 2 - tests/TypeInfer.modules.test.cpp | 18 +- tests/TypeInfer.operators.test.cpp | 10 +- tests/TypeInfer.refinements.test.cpp | 35 ++-- tests/TypeInfer.singletons.test.cpp | 10 +- tests/TypeInfer.tables.test.cpp | 67 +++--- tests/TypeInfer.test.cpp | 20 +- tests/TypeInfer.typePacks.test.cpp | 17 +- tests/TypeInfer.typestates.test.cpp | 8 +- tests/TypeInfer.unionTypes.test.cpp | 70 +++---- 82 files changed, 1979 insertions(+), 881 deletions(-) create mode 100644 Analysis/include/Luau/ExpectedTypeVisitor.h create mode 100644 Analysis/include/Luau/TypeIds.h create mode 100644 Analysis/src/ExpectedTypeVisitor.cpp create mode 100644 Analysis/src/TypeIds.cpp diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h index e98adf7c..3e1a9559 100644 --- a/Analysis/include/Luau/ConstraintGenerator.h +++ b/Analysis/include/Luau/ConstraintGenerator.h @@ -10,12 +10,12 @@ #include "Luau/InsertionOrderedMap.h" #include "Luau/Module.h" #include "Luau/ModuleResolver.h" -#include "Luau/Normalize.h" #include "Luau/NotNull.h" #include "Luau/Polarity.h" #include "Luau/Refinement.h" #include "Luau/Symbol.h" #include "Luau/TypeFwd.h" +#include "Luau/TypeIds.h" #include "Luau/TypeUtils.h" #include @@ -93,7 +93,7 @@ struct ConstraintGenerator std::vector constraints; // The set of all free types introduced during constraint generation. - DenseHashSet freeTypes{nullptr}; + TypeIds freeTypes; // Map a function's signature scope back to its signature type. DenseHashMap scopeToFunction{nullptr}; @@ -176,7 +176,7 @@ private: std::vector unionsToSimplify; // Used to keep track of when we are inside a large table and should - // opt *not* to do type inference for singletons. + // opt *not* to do type inference for singletons. size_t largeTableDepth = 0; /** diff --git a/Analysis/include/Luau/ConstraintSet.h b/Analysis/include/Luau/ConstraintSet.h index 7ce8c44f..2c3dd056 100644 --- a/Analysis/include/Luau/ConstraintSet.h +++ b/Analysis/include/Luau/ConstraintSet.h @@ -3,7 +3,7 @@ #pragma once #include "Luau/Constraint.h" -#include "Luau/DenseHash.h" +#include "Luau/TypeIds.h" #include "Luau/Error.h" #include @@ -18,7 +18,7 @@ struct ConstraintSet std::vector constraints; // The set of all free types created during constraint generation - DenseHashSet freeTypes{nullptr}; + TypeIds freeTypes; // 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, @@ -29,4 +29,4 @@ struct ConstraintSet std::vector errors; }; -} +} // namespace Luau diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index 91550579..f3675638 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -456,6 +456,14 @@ public: */ std::vector> borrowConstraints(const std::vector& constraints); +std::pair, std::vector> saturateArguments( + TypeArena* arena, + NotNull builtinTypes, + const TypeFun& fn, + const std::vector& rawTypeArguments, + const std::vector& rawPackArguments +); + void dump(NotNull rootScope, struct ToStringOptions& opts); } // namespace Luau diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h index 11216668..994f1140 100644 --- a/Analysis/include/Luau/Error.h +++ b/Analysis/include/Luau/Error.h @@ -464,7 +464,10 @@ struct ReservedIdentifier struct UnexpectedArrayLikeTableItem { - bool operator==(const UnexpectedArrayLikeTableItem&) const { return true; } + bool operator==(const UnexpectedArrayLikeTableItem&) const + { + return true; + } }; using TypeErrorData = Variant< diff --git a/Analysis/include/Luau/ExpectedTypeVisitor.h b/Analysis/include/Luau/ExpectedTypeVisitor.h new file mode 100644 index 00000000..294befaf --- /dev/null +++ b/Analysis/include/Luau/ExpectedTypeVisitor.h @@ -0,0 +1,74 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/Ast.h" +#include "Luau/DenseHash.h" +#include "Luau/NotNull.h" +#include "Luau/Type.h" + +namespace Luau +{ + +struct ExpectedTypeVisitor : public AstVisitor +{ + + explicit ExpectedTypeVisitor( + NotNull> astTypes, + NotNull> astExpectedTypes, + NotNull> astResolvedTypes, + NotNull arena, + NotNull builtinTypes, + NotNull rootScope + ); + + // When we have an assignment, we grab the type of the left-hand-side + // and we use it to inform what the type of the right-hand-side ought + // to be. This is important for something like: + // + // + // local function foobar(tbl: { prop: boolean }) + // tbl.prop = [autocomplete here] + // end + // + // ... where the right hand side _must_ be a subtype of `boolean` + bool visit(AstStatAssign* stat) override; + + // Similar to an assignment, we can apply expected types to the + // right-hand-side of a local based on the annotated type of the + // left-hand-side. + bool visit(AstStatLocal* stat) override; + + // Compound assignments have the curious property that they do not change + // type state, so we can use the left-hand-side to inform the + // right-hand-side. + bool visit(AstStatCompoundAssign* stat) override; + + // When we are returning something, and we've inferred a return type (or have + // a written return type), then we need to apply the expected types to the + // return type expression. + bool visit(AstStatReturn* stat) override; + + // When we have a function call, we can apply expected types to all the + // parameters. + bool visit(AstExprCall* expr) override; + + // If we have an expression of type: + // + // return X :: Y + // + // Then surely the expected type of `X` is `Y` + bool visit(AstExprTypeAssertion* expr) override; + +private: + NotNull> astTypes; + NotNull> astExpectedTypes; + NotNull> astResolvedTypes; + NotNull arena; + NotNull builtinTypes; + NotNull rootScope; + + void applyExpectedType(const TypeId expectedType, const AstExpr* expr); +}; + + +} // namespace Luau \ No newline at end of file diff --git a/Analysis/include/Luau/Module.h b/Analysis/include/Luau/Module.h index d8899414..d94956b7 100644 --- a/Analysis/include/Luau/Module.h +++ b/Analysis/include/Luau/Module.h @@ -126,6 +126,9 @@ struct Module // we need a sentinel value for the map. DenseHashMap astScopes{nullptr}; + // Stable references for type aliases registered in the environment + std::vector> typeFunctionAliases; + std::unordered_map declaredGlobals; ErrorVec errors; LintResult lintResult; diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h index d3e16b32..71062efa 100644 --- a/Analysis/include/Luau/Normalize.h +++ b/Analysis/include/Luau/Normalize.h @@ -5,9 +5,9 @@ #include "Luau/NotNull.h" #include "Luau/Set.h" #include "Luau/TypeFwd.h" +#include "Luau/TypeIds.h" #include "Luau/UnifierSharedState.h" -#include #include #include #include @@ -39,57 +39,6 @@ bool isSubtype( InternalErrorReporter& ice ); -class TypeIds -{ -private: - DenseHashMap types{nullptr}; - std::vector order; - std::size_t hash = 0; - -public: - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; - - TypeIds() = default; - ~TypeIds() = default; - - TypeIds(std::initializer_list tys); - - TypeIds(const TypeIds&) = default; - TypeIds& operator=(const TypeIds&) = default; - - TypeIds(TypeIds&&) = default; - TypeIds& operator=(TypeIds&&) = default; - - void insert(TypeId ty); - /// Erase every element that does not also occur in tys - void retain(const TypeIds& tys); - void clear(); - - TypeId front() const; - iterator begin(); - iterator end(); - const_iterator begin() const; - const_iterator end() const; - iterator erase(const_iterator it); - void erase(TypeId ty); - - size_t size() const; - bool empty() const; - size_t count(TypeId ty) const; - - template - void insert(Iterator begin, Iterator end) - { - for (Iterator it = begin; it != end; ++it) - insert(*it); - } - - bool operator==(const TypeIds& there) const; - size_t getHash() const; - bool isNever() const; -}; - } // namespace Luau template<> @@ -302,7 +251,7 @@ struct NormalizedType // we'd be reusing bad, stale data. bool isCacheable = true; - NormalizedType(NotNull builtinTypes); + explicit NormalizedType(NotNull builtinTypes); NormalizedType() = delete; ~NormalizedType() = default; diff --git a/Analysis/include/Luau/Scope.h b/Analysis/include/Luau/Scope.h index 7d253d24..7dc8bd85 100644 --- a/Analysis/include/Luau/Scope.h +++ b/Analysis/include/Luau/Scope.h @@ -101,6 +101,8 @@ struct Scope std::optional> interiorFreeTypes; std::optional> interiorFreeTypePacks; + + NotNull findNarrowestScopeContaining(Location); }; // Returns true iff the left scope encloses the right scope. A Scope* equal to diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h index f65a516e..86a6d039 100644 --- a/Analysis/include/Luau/Subtyping.h +++ b/Analysis/include/Luau/Subtyping.h @@ -234,8 +234,14 @@ 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 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 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, @@ -267,7 +273,12 @@ private: const NormalizedExternType& superExternType, NotNull scope ); - SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, 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/Type.h b/Analysis/include/Luau/Type.h index b41bf28b..1d532d80 100644 --- a/Analysis/include/Luau/Type.h +++ b/Analysis/include/Luau/Type.h @@ -34,6 +34,7 @@ using ScopePtr = std::shared_ptr; struct Module; struct TypeFunction; +struct TypeFun; struct Constraint; struct Subtyping; struct TypeChecker2; @@ -608,7 +609,8 @@ struct UserDefinedFunctionData // References to AST elements are owned by the Module allocator which also stores this type AstStatTypeFunction* definition = nullptr; - DenseHashMap> environment{""}; + DenseHashMap> environmentFunction{""}; + DenseHashMap> environmentAlias{""}; }; /** diff --git a/Analysis/include/Luau/TypeFunction.h b/Analysis/include/Luau/TypeFunction.h index 444ae3e3..10cf30e2 100644 --- a/Analysis/include/Luau/TypeFunction.h +++ b/Analysis/include/Luau/TypeFunction.h @@ -21,6 +21,8 @@ namespace Luau struct TypeArena; struct TxnLog; struct ConstraintSolver; +struct TypeFunctionRuntimeBuilderState; +struct TypeFunctionContext; class Normalizer; using StateRef = std::unique_ptr; @@ -54,6 +56,9 @@ struct TypeFunctionRuntime // Output created by 'print' function std::vector messages; + // Type builder, valid for the duration of a single evaluation + TypeFunctionRuntimeBuilderState* runtimeBuilder = nullptr; + private: void prepareState(); }; diff --git a/Analysis/include/Luau/TypeFunctionRuntime.h b/Analysis/include/Luau/TypeFunctionRuntime.h index 047ff176..26d216c4 100644 --- a/Analysis/include/Luau/TypeFunctionRuntime.h +++ b/Analysis/include/Luau/TypeFunctionRuntime.h @@ -15,6 +15,8 @@ using lua_State = struct lua_State; namespace Luau { +struct TypeFunctionRuntime; + void* typeFunctionAlloc(void* ud, void* ptr, size_t osize, size_t nsize); // Replica of types from Type.h @@ -274,6 +276,8 @@ T* getMutable(TypeFunctionTypeId tv) std::optional checkResultForError(lua_State* L, const char* typeFunctionName, int luaResult); +TypeFunctionRuntime* getTypeFunctionRuntime(lua_State* L); + TypeFunctionType* allocateTypeFunctionType(lua_State* L, TypeFunctionTypeVariant type); TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunctionTypePackVariant type); diff --git a/Analysis/include/Luau/TypeIds.h b/Analysis/include/Luau/TypeIds.h new file mode 100644 index 00000000..e74ae4d6 --- /dev/null +++ b/Analysis/include/Luau/TypeIds.h @@ -0,0 +1,67 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#pragma once + +#include "Luau/DenseHash.h" +#include "Luau/TypeFwd.h" + +#include +#include + +namespace Luau +{ + +/* + * An ordered, hashable set of TypeIds. + */ +class TypeIds +{ +private: + DenseHashMap types{nullptr}; + std::vector order; + std::size_t hash = 0; + +public: + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + + TypeIds() = default; + ~TypeIds() = default; + + TypeIds(std::initializer_list tys); + + TypeIds(const TypeIds&) = default; + TypeIds& operator=(const TypeIds&) = default; + + TypeIds(TypeIds&&) = default; + TypeIds& operator=(TypeIds&&) = default; + + void insert(TypeId ty); + /// Erase every element that does not also occur in tys + void retain(const TypeIds& tys); + void clear(); + + TypeId front() const; + iterator begin(); + iterator end(); + const_iterator begin() const; + const_iterator end() const; + iterator erase(const_iterator it); + void erase(TypeId ty); + + size_t size() const; + bool empty() const; + size_t count(TypeId ty) const; + + template + void insert(Iterator begin, Iterator end) + { + for (Iterator it = begin; it != end; ++it) + insert(*it); + } + + bool operator==(const TypeIds& there) const; + size_t getHash() const; + bool isNever() const; +}; + +} // namespace Luau diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h index 6d57caed..8479ae90 100644 --- a/Analysis/include/Luau/TypeUtils.h +++ b/Analysis/include/Luau/TypeUtils.h @@ -301,4 +301,21 @@ bool fastIsSubtype(TypeId subTy, TypeId superTy); */ std::optional extractMatchingTableType(std::vector& tables, TypeId exprType, NotNull builtinTypes); +/** + * @param item A member of a table in an AST + * @return Whether the item is a key-value pair with a statically defined string key. + * + * ``` + * { + * ["foo"] = ..., -- is a record + * bar = ..., -- is a record + * ..., -- not a record: non-string key (number) + * [true] = ..., -- not a record: non-string key (boolean) + * [ foobar() ] = ..., -- not a record: unknown key value. + * ["foo" .. "bar"] = ..., -- not a record (don't make us handle it). + * } + * ``` + */ +bool isRecord(const AstExprTable::Item& item); + } // namespace Luau diff --git a/Analysis/src/AutocompleteCore.cpp b/Analysis/src/AutocompleteCore.cpp index c6ff4b09..0fe8b676 100644 --- a/Analysis/src/AutocompleteCore.cpp +++ b/Analysis/src/AutocompleteCore.cpp @@ -173,12 +173,7 @@ static bool checkTypeMatch( unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit; Subtyping subtyping{ - builtinTypes, - NotNull{typeArena}, - NotNull{simplifier.get()}, - NotNull{&normalizer}, - NotNull{&typeFunctionRuntime}, - NotNull{&iceReporter} + builtinTypes, NotNull{typeArena}, NotNull{simplifier.get()}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&iceReporter} }; return subtyping.isSubtype(subTy, superTy, scope).isSubtype; diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp index f3b2b329..1a9f4f51 100644 --- a/Analysis/src/BuiltinDefinitions.cpp +++ b/Analysis/src/BuiltinDefinitions.cpp @@ -13,7 +13,6 @@ #include "Luau/NotNull.h" #include "Luau/Subtyping.h" #include "Luau/Symbol.h" -#include "Luau/ToString.h" #include "Luau/Type.h" #include "Luau/TypeChecker2.h" #include "Luau/TypeFunction.h" @@ -30,10 +29,11 @@ */ LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) +LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature) namespace Luau { @@ -310,8 +310,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeArena& arena = globals.globalTypes; NotNull builtinTypes = globals.builtinTypes; - Scope* globalScope = nullptr; // NotNull when removing FFlag::LuauEagerGeneralization - if (FFlag::LuauEagerGeneralization) + Scope* globalScope = nullptr; // NotNull when removing FFlag::LuauEagerGeneralization2 + if (FFlag::LuauEagerGeneralization2) globalScope = globals.globalScope.get(); if (FFlag::LuauSolverV2) @@ -386,19 +386,30 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); - // clang-format off - // setmetatable(T, MT) -> { @metatable MT, T } - addGlobalBinding(globals, "setmetatable", - arena.addType( - FunctionType{ - {genericT, genericMT}, - {}, - arena.addTypePack(TypePack{{genericT, genericMT}}), - arena.addTypePack(TypePack{{tMetaMT}}) - } - ), "@luau" - ); - // clang-format on + if (FFlag::LuauUpdateSetMetatableTypeSignature) + { + // setmetatable(T, MT) -> setmetatable + TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}}); + addGlobalBinding( + globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau" + ); + } + else + { + // clang-format off + // setmetatable(T, MT) -> { @metatable MT, T } + addGlobalBinding(globals, "setmetatable", + arena.addType( + FunctionType{ + {genericT, genericMT}, + {}, + arena.addTypePack(TypePack{{genericT, genericMT}}), + arena.addTypePack(TypePack{{tMetaMT}}) + } + ), "@luau" + ); + // clang-format on + } } else { @@ -701,9 +712,10 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context) { TypeId actualTy = params[i + paramOffset]; TypeId expectedTy = expected[i]; - 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; + 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); diff --git a/Analysis/src/Constraint.cpp b/Analysis/src/Constraint.cpp index ff19fef9..253a5002 100644 --- a/Analysis/src/Constraint.cpp +++ b/Analysis/src/Constraint.cpp @@ -3,7 +3,7 @@ #include "Luau/Constraint.h" #include "Luau/VisitType.h" -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) namespace Luau { @@ -51,7 +51,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor bool visit(TypeId, const TypeFunctionInstanceType&) override { - return FFlag::LuauEagerGeneralization && traverseIntoTypeFunctions; + return FFlag::LuauEagerGeneralization2 && traverseIntoTypeFunctions; } }; @@ -104,7 +104,7 @@ DenseHashSet Constraint::getMaybeMutatedFreeTypes() const { rci.traverse(fchc->argsPack); } - else if (auto fcc = get(*this); fcc && FFlag::LuauEagerGeneralization) + else if (auto fcc = get(*this); fcc && FFlag::LuauEagerGeneralization2) { rci.traverseIntoTypeFunctions = false; rci.traverse(fcc->fn); @@ -118,12 +118,12 @@ DenseHashSet Constraint::getMaybeMutatedFreeTypes() const else if (auto hpc = get(*this)) { rci.traverse(hpc->resultType); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) rci.traverse(hpc->subjectType); } else if (auto hic = get(*this)) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) rci.traverse(hic->subjectType); rci.traverse(hic->resultType); // `HasIndexerConstraint` should not mutate `indexType`. diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp index 228bc6c0..ad287883 100644 --- a/Analysis/src/ConstraintGenerator.cpp +++ b/Analysis/src/ConstraintGenerator.cpp @@ -11,6 +11,7 @@ #include "Luau/DenseHash.h" #include "Luau/InferPolarity.h" #include "Luau/ModuleResolver.h" +#include "Luau/Normalize.h" #include "Luau/NotNull.h" #include "Luau/RecursionCounter.h" #include "Luau/Refinement.h" @@ -33,11 +34,8 @@ LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauMagicTypes) -LUAU_FASTFLAG(LuauEagerGeneralization) -LUAU_FASTFLAG(LuauEagerGeneralization) - -LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations) - +LUAU_FASTFLAG(LuauEagerGeneralization2) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) @@ -51,6 +49,7 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) +LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases) namespace Luau { @@ -180,6 +179,22 @@ bool hasFreeType(TypeId ty) return hft.result; } +struct GlobalNameCollector : public AstVisitor +{ + DenseHashSet names; + + GlobalNameCollector() + : names(AstName()) + { + } + + bool visit(AstExprGlobal* node) override + { + names.insert(node->name); + return true; + } +}; + } // namespace ConstraintGenerator::ConstraintGenerator( @@ -220,26 +235,14 @@ ConstraintSet ConstraintGenerator::run(AstStatBlock* block) { visitModuleRoot(block); - return ConstraintSet{ - NotNull{rootScope}, - std::move(constraints), - std::move(freeTypes), - std::move(scopeToFunction), - std::move(errors) - }; + 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) - }; + return ConstraintSet{NotNull{rootScope}, std::move(constraints), std::move(freeTypes), std::move(scopeToFunction), std::move(errors)}; } void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) @@ -254,7 +257,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) rootScope->location = block->location; module->astScopes[block] = NotNull{scope.get()}; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.emplace_back(); @@ -290,7 +293,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) } ); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { scope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -309,7 +312,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) } ); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -347,13 +350,13 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat // We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block); // Pre - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.emplace_back(); visitBlockWithoutChildScope(resumeScope, block); // Post - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -383,12 +386,12 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); interiorFreeTypes.back().types.push_back(ft); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) freeTypes.insert(ft); return ft; @@ -405,7 +408,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po { FreeTypePack f{scope.get(), polarity}; TypePackId result = arena->addTypePack(TypePackVar{std::move(f)}); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.back().typePacks.push_back(result); return result; } @@ -818,8 +821,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc initialFun.typePackParams.push_back(genPack); } - if (FFlag::LuauRetainDefinitionAliasLocations) - initialFun.definitionLocation = alias->location; + initialFun.definitionLocation = alias->location; if (alias->exported) scope->exportedTypeBindings[alias->name.value] = std::move(initialFun); @@ -873,8 +875,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc TypeFun typeFunction{std::move(quantifiedTypeParams), typeFunctionTy}; - if (FFlag::LuauRetainDefinitionAliasLocations) - typeFunction.definitionLocation = function->location; + typeFunction.definitionLocation = function->location; // Set type bindings and definition locations for this user-defined type function if (function->exported) @@ -903,8 +904,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc TypeId initialType = arena->addType(BlockedType{}); TypeFun initialFun{initialType}; - if (FFlag::LuauRetainDefinitionAliasLocations) - initialFun.definitionLocation = classDeclaration->location; + initialFun.definitionLocation = classDeclaration->location; scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun); classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location; @@ -936,20 +936,24 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc mainTypeFun = getMutable(it->second.type); } - // Fill it with all visible type functions + // Fill it with all visible type functions and referenced type aliases if (mainTypeFun) { + GlobalNameCollector globalNameCollector; + stat->visit(&globalNameCollector); + UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData; size_t level = 0; - auto addToEnvironment = [this](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeId type, size_t level) + auto addToEnvironment = + [this, &globalNameCollector](UserDefinedFunctionData& userFuncData, ScopePtr scope, const Name& name, TypeFun tf, size_t level) { - if (userFuncData.environment.find(name)) - return; - - if (auto ty = get(type); ty && ty->userFuncData.definition) + if (auto ty = get(tf.type); ty && ty->userFuncData.definition) { - userFuncData.environment[name] = std::make_pair(ty->userFuncData.definition, level); + if (userFuncData.environmentFunction.find(name)) + return; + + userFuncData.environmentFunction[name] = std::make_pair(ty->userFuncData.definition, level); if (auto it = astTypeFunctionEnvironmentScopes.find(ty->userFuncData.definition)) { @@ -957,15 +961,35 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc scope->bindings[ty->userFuncData.definition->name] = Binding{existing->typeId, ty->userFuncData.definition->location}; } } + else if (FFlag::LuauUserTypeFunctionAliases && !get(tf.type)) + { + if (userFuncData.environmentAlias.find(name)) + return; + + AstName astName = module->names->get(name.c_str()); + + // Only register globals that we have detected to be used + if (!globalNameCollector.names.find(astName)) + return; + + // Function evaluation environment needs a stable reference to the alias + module->typeFunctionAliases.push_back(std::make_unique(tf)); + + userFuncData.environmentAlias[name] = std::make_pair(module->typeFunctionAliases.back().get(), level); + + // TODO: create a specific type alias type + scope->bindings[astName] = Binding{builtinTypes->anyType, tf.definitionLocation.value_or(Location())}; + } }; - for (Scope* curr = scope.get(); curr; curr = curr->parent.get()) + // Go up the scopes to register type functions and alises, but without reaching into the global scope + for (Scope* curr = scope.get(); curr && (!FFlag::LuauUserTypeFunctionAliases || curr != globalScope.get()); curr = curr->parent.get()) { for (auto& [name, tf] : curr->privateTypeBindings) - addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level); + addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf, level); for (auto& [name, tf] : curr->exportedTypeBindings) - addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level); + addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf, level); level++; } @@ -1396,7 +1420,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocalFuncti FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); sig.bodyScope->bindings[function->name] = Binding{sig.signature, function->name->location}; - bool sigFullyDefined = FFlag::LuauEagerGeneralization ? false : !hasFreeType(sig.signature); + bool sigFullyDefined = FFlag::LuauEagerGeneralization2 ? false : !hasFreeType(sig.signature); if (sigFullyDefined) emplaceType(asMutable(functionType), sig.signature); @@ -1456,7 +1480,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f Checkpoint start = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); - bool sigFullyDefined = FFlag::LuauEagerGeneralization ? false : !hasFreeType(sig.signature); + bool sigFullyDefined = FFlag::LuauEagerGeneralization2 ? false : !hasFreeType(sig.signature); DefId def = dfg->getDef(function->name); @@ -1770,7 +1794,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio // Place this function as a child of the non-type function scope scope->children.push_back(NotNull{sig.signatureScope.get()}); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.push_back(std::vector{}); @@ -1788,7 +1812,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio } ); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -1797,7 +1821,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeFunctio sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back()); getMutable(generalizedTy)->setOwner(gc); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.pop_back(); else DEPRECATED_interiorTypes.pop_back(); @@ -1884,7 +1908,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte { reportError( declaredExternType->location, - GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value)} + GenericError{format("Cannot use non-class type '%s' as a superclass of class '%s'", superName.c_str(), declaredExternType->name.value) + } ); return ControlFlow::None; @@ -2466,7 +2491,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin return Inference{builtinTypes->stringType}; TypeId freeTy = nullptr; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { freeTy = freshType(scope, Polarity::Positive); FreeType* ft = getMutable(freeTy); @@ -2507,7 +2532,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool* return Inference{builtinTypes->booleanType}; TypeId freeTy = nullptr; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { freeTy = freshType(scope, Polarity::Positive); FreeType* ft = getMutable(freeTy); @@ -2605,9 +2630,7 @@ Inference ConstraintGenerator::checkIndexName( { result = arena->addType(BlockedType{}); - auto c = addConstraint( - scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)} - ); + auto c = addConstraint(scope, indexee->location, HasPropConstraint{result, obj, index, ValueContext::RValue, inConditional(typeContext)}); getMutable(result)->setOwner(c); } @@ -2668,7 +2691,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun Checkpoint startCheckpoint = checkpoint(this); FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.emplace_back(); else DEPRECATED_interiorTypes.push_back(std::vector{}); @@ -2686,7 +2709,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun } ); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { sig.signatureScope->interiorFreeTypes = std::move(interiorFreeTypes.back().types); sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); @@ -3190,10 +3213,11 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, ttv->definitionLocation = expr->location; ttv->scope = scope.get(); - if (FFlag::LuauDisablePrimitiveInferenceInLargeTables && FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) + if (FFlag::LuauDisablePrimitiveInferenceInLargeTables && FInt::LuauPrimitiveInferenceInTableLimit > 0 && + expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) largeTableDepth++; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) interiorFreeTypes.back().types.push_back(ty); else DEPRECATED_interiorTypes.back().push_back(ty); @@ -3301,7 +3325,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, ); } - if (FFlag::LuauDisablePrimitiveInferenceInLargeTables && FInt::LuauPrimitiveInferenceInTableLimit > 0 && expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) + if (FFlag::LuauDisablePrimitiveInferenceInLargeTables && FInt::LuauPrimitiveInferenceInTableLimit > 0 && + expr->items.size > size_t(FInt::LuauPrimitiveInferenceInTableLimit)) largeTableDepth--; return Inference{ty}; @@ -3453,7 +3478,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu LUAU_ASSERT(nullptr != varargPack); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // Some of the types in argTypes will eventually be generics, and some // will not. The ones that are not generic will be pruned when @@ -3518,7 +3543,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu if (expectedType && get(*expectedType)) bindFreeType(*expectedType, actualFunctionType); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) scopeToFunction[signatureScope.get()] = actualFunctionType; return { @@ -3809,33 +3834,33 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo } else if (auto unionAnnotation = ty->as()) { - if (unionAnnotation->types.size == 1) - result = resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); - else + if (unionAnnotation->types.size == 1) + result = resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); + else + { + std::vector parts; + for (AstType* part : unionAnnotation->types) { - std::vector parts; - for (AstType* part : unionAnnotation->types) - { - parts.push_back(resolveType_(scope, part, inTypeArguments)); - } - - result = arena->addType(UnionType{parts}); + parts.push_back(resolveType_(scope, part, inTypeArguments)); } + + result = arena->addType(UnionType{parts}); + } } else if (auto intersectionAnnotation = ty->as()) { - if (intersectionAnnotation->types.size == 1) - result = resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); - else + if (intersectionAnnotation->types.size == 1) + result = resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); + else + { + std::vector parts; + for (AstType* part : intersectionAnnotation->types) { - std::vector parts; - for (AstType* part : intersectionAnnotation->types) - { - parts.push_back(resolveType_(scope, part, inTypeArguments)); - } - - result = arena->addType(IntersectionType{parts}); + parts.push_back(resolveType_(scope, part, inTypeArguments)); } + + result = arena->addType(IntersectionType{parts}); + } } else if (auto typeGroupAnnotation = ty->as()) { diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index 3fc68dfe..d62b688f 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -33,9 +33,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauDeprecatedAttribute) -LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2) @@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult) LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) +LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) namespace Luau { @@ -103,7 +103,7 @@ size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const return true; } -static std::pair, std::vector> saturateArguments( +std::pair, std::vector> saturateArguments( TypeArena* arena, NotNull builtinTypes, const TypeFun& fn, @@ -418,7 +418,7 @@ void ConstraintSolver::run() } // Free types that have no constraints at all can be generalized right away. - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { for (TypeId ty : constraintSet.freeTypes) { @@ -479,7 +479,7 @@ void ConstraintSolver::run() // expansion types, etc, so we need to follow it. ty = follow(ty); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { if (seen.contains(ty)) continue; @@ -498,7 +498,7 @@ void ConstraintSolver::run() if (refCount <= 1) unblock(ty, Location{}); - if (FFlag::LuauEagerGeneralization && refCount == 0) + if (FFlag::LuauEagerGeneralization2 && refCount == 0) generalizeOneType(ty); } } @@ -676,7 +676,7 @@ void ConstraintSolver::initFreeTypeTracking() auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); refCount += 1; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr); it->second.insert(c.get()); @@ -691,7 +691,7 @@ void ConstraintSolver::initFreeTypeTracking() } // Also check flag integrity while we're here - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { LUAU_ASSERT(FFlag::LuauSubtypeGenericsAndNegations); LUAU_ASSERT(FFlag::LuauNoMoreInjectiveTypeFunctions); @@ -739,7 +739,7 @@ void ConstraintSolver::bind(NotNull constraint, TypeId ty, Typ constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed ); // FIXME? Is this the right polarity? - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) trackInteriorFreeType(constraint->scope, ty); return; @@ -900,7 +900,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullscope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { ty = follow(ty); if (auto freeTy = get(ty)) @@ -922,7 +922,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNullscope->interiorFreeTypePacks) { @@ -942,7 +942,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull constra // We also need to unconditionally unblock these types, otherwise // you end up with funky looking "Blocked on *no-refine*." unblock(*ty, constraint->location); - } } @@ -1388,7 +1387,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNullscope, freeTy); @@ -1942,7 +1941,7 @@ bool ConstraintSolver::tryDispatchHasIndexer( TypeId upperBound = arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed}); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { TypeId sr = follow(simplifyIntersection(constraint->scope, constraint->location, ft->upperBound, upperBound)); @@ -1973,7 +1972,7 @@ bool ConstraintSolver::tryDispatchHasIndexer( FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed}; emplace(constraint, resultType, freeResult); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) trackInteriorFreeType(constraint->scope, resultType); tt->indexer = TableIndexer{indexType, resultType}; @@ -2056,7 +2055,6 @@ bool ConstraintSolver::tryDispatchHasIndexer( r = follow(r); if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get(r)) results.insert(r); - } if (0 == results.size()) @@ -2163,7 +2161,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNullupperBound); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue); if (!blocked.empty()) @@ -3064,7 +3062,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp( { const TypeId upperBound = follow(ft->upperBound); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { if (get(upperBound) || get(upperBound)) { @@ -3532,7 +3530,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target) // Any constraint that might have mutated source may now mutate target - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { auto it = mutatedFreeTypeToConstraint.find(source); if (it != mutatedFreeTypeToConstraint.end()) diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp index 2cf968a8..5d512c4a 100644 --- a/Analysis/src/Error.cpp +++ b/Analysis/src/Error.cpp @@ -18,7 +18,7 @@ #include LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive) @@ -663,7 +663,7 @@ struct ErrorConverter } // binary operators - const auto binaryOps = FFlag::LuauEagerGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps; + const auto binaryOps = FFlag::LuauEagerGeneralization2 ? kBinaryOps : DEPRECATED_kBinaryOps; if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end()) { std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types "; @@ -718,10 +718,10 @@ struct ErrorConverter "'"; } - if ((FFlag::LuauEagerGeneralization ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name)) + if ((FFlag::LuauEagerGeneralization2 ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name)) { return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" + - "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; + "This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues"; } // Everything should be specialized above to report a more descriptive error that hopefully does not mention "type functions" explicitly. diff --git a/Analysis/src/ExpectedTypeVisitor.cpp b/Analysis/src/ExpectedTypeVisitor.cpp new file mode 100644 index 00000000..b2a9fa9d --- /dev/null +++ b/Analysis/src/ExpectedTypeVisitor.cpp @@ -0,0 +1,213 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details +#include "Luau/ExpectedTypeVisitor.h" + +#include "Luau/Scope.h" +#include "Luau/TypeArena.h" +#include "Luau/TypePack.h" +#include "Luau/TypeUtils.h" + +namespace Luau +{ + +ExpectedTypeVisitor::ExpectedTypeVisitor( + NotNull> astTypes, + NotNull> astExpectedTypes, + NotNull> astResolvedTypes, + NotNull arena, + NotNull builtinTypes, + NotNull rootScope +) + : astTypes(astTypes) + , astExpectedTypes(astExpectedTypes) + , astResolvedTypes(astResolvedTypes) + , arena(arena) + , builtinTypes(builtinTypes) + , rootScope(rootScope) +{ +} + +bool ExpectedTypeVisitor::visit(AstStatAssign* stat) +{ + for (size_t idx = 0; idx < std::min(stat->vars.size, stat->values.size); idx++) + { + if (auto lhsType = astTypes->find(stat->vars.data[idx])) + applyExpectedType(*lhsType, stat->values.data[idx]); + } + return true; +} + +bool ExpectedTypeVisitor::visit(AstStatLocal* stat) +{ + for (size_t idx = 0; idx < std::min(stat->vars.size, stat->values.size); idx++) + { + if (auto annot = astResolvedTypes->find(stat->vars.data[idx]->annotation)) + applyExpectedType(*annot, stat->values.data[idx]); + } + return true; +} + +bool ExpectedTypeVisitor::visit(AstStatCompoundAssign* stat) +{ + if (auto lhsType = astTypes->find(stat->var)) + applyExpectedType(*lhsType, stat->value); + return true; +} + +bool ExpectedTypeVisitor::visit(AstStatReturn* stat) +{ + auto scope = rootScope->findNarrowestScopeContaining(stat->location); + + auto it = begin(scope->returnType); + size_t idx = 0; + + while (idx < stat->list.size && it != end(scope->returnType)) + { + applyExpectedType(*it, stat->list.data[idx]); + it++; + idx++; + } + + return true; +} + +bool ExpectedTypeVisitor::visit(AstExprCall* expr) +{ + auto ty = astTypes->find(expr->func); + if (!ty) + return true; + + const FunctionType* ftv = get(follow(*ty)); + + // FIXME: Bidirectional type checking of overloaded functions is not yet + // supported, which means we *also* do not provide autocomplete for + // the arguments of overloaded functions. + if (!ftv) + return true; + + auto it = begin(ftv->argTypes); + size_t idx = 0; + + if (expr->self && it != end(ftv->argTypes)) + { + // If we have a `foo:bar(...)` call, then the first type in the arg + // pack will be the type of `self`, so we just skip that. + it++; + } + + while (idx < expr->args.size && it != end(ftv->argTypes)) + { + applyExpectedType(*it, expr->args.data[idx]); + it++; + idx++; + } + + return true; +} + +bool ExpectedTypeVisitor::visit(AstExprTypeAssertion* expr) +{ + if (auto annot = astResolvedTypes->find(expr->annotation)) + applyExpectedType(*annot, expr->expr); + + return true; +} + +void ExpectedTypeVisitor::applyExpectedType(TypeId expectedType, const AstExpr* expr) +{ + expectedType = follow(expectedType); + + // No matter what, we set the expected type of the current expression to + // whatever was just passed in. We may traverse the type and do more. + (*astExpectedTypes)[expr] = expectedType; + + if (const auto exprTable = expr->as()) + { + const auto expectedTableType = get(expectedType); + if (!expectedTableType) + { + if (auto utv = get(expectedType)) + { + if (auto exprType = astTypes->find(expr)) + { + std::vector parts{begin(utv), end(utv)}; + if (auto tt = extractMatchingTableType(parts, *exprType, builtinTypes)) + { + applyExpectedType(*tt, expr); + return; + } + } + } + return; + } + + // If we have a table, then the expected type for any given key is a + // union between all the possible keys and an indexer type (if it exists). + std::vector possibleKeyTypes; + possibleKeyTypes.reserve(expectedTableType->props.size() + (expectedTableType->indexer ? 1 : 0)); + for (const auto& [name, _] : expectedTableType->props) + { + possibleKeyTypes.push_back(arena->addType(SingletonType{StringSingleton{name}})); + } + + if (expectedTableType->indexer) + possibleKeyTypes.push_back(expectedTableType->indexer->indexType); + + TypeId expectedKeyType = nullptr; + if (possibleKeyTypes.size() == 0) + expectedKeyType = builtinTypes->neverType; + else if (possibleKeyTypes.size() == 1) + expectedKeyType = possibleKeyTypes[0]; + else + expectedKeyType = arena->addType(UnionType{std::move(possibleKeyTypes)}); + + for (const AstExprTable::Item& item : exprTable->items) + { + if (isRecord(item)) + { + const AstArray& s = item.key->as()->value; + std::string keyStr{s.data, s.data + s.size}; + + // No mater what, we can claim that the expected key type is the + // union of all possible props plus the indexer. + applyExpectedType(expectedKeyType, item.key); + + // - If the property is defined and has a read type, apply it + // as an expected type. e.g.: + // + // -- _ will have expected type `number` + // local t: { [string]: number, write foo: boolean } = { foo = _ } + // + // - Otherwise if the property has an indexer, apply the result type. + // - Otherwise do nothing. + if (auto it = expectedTableType->props.find(keyStr); it != expectedTableType->props.end() && it->second.readTy) + { + applyExpectedType(*it->second.readTy, item.value); + } + else if (expectedTableType->indexer) + { + applyExpectedType(expectedTableType->indexer->indexResultType, item.value); + } + } + else if (item.kind == AstExprTable::Item::List && expectedTableType->indexer) + { + applyExpectedType(expectedTableType->indexer->indexResultType, item.value); + } + else if (item.kind == AstExprTable::Item::General && expectedTableType->indexer) + { + applyExpectedType(expectedTableType->indexer->indexResultType, item.value); + applyExpectedType(expectedKeyType, item.key); + } + } + } + else if (auto group = expr->as()) + { + applyExpectedType(expectedType, group->expr); + } + else if (auto ternary = expr->as()) + { + applyExpectedType(expectedType, ternary->trueExpr); + applyExpectedType(expectedType, ternary->falseExpr); + } +} + +} // namespace Luau \ No newline at end of file diff --git a/Analysis/src/FragmentAutocomplete.cpp b/Analysis/src/FragmentAutocomplete.cpp index 5e8a9598..b20830f5 100644 --- a/Analysis/src/FragmentAutocomplete.cpp +++ b/Analysis/src/FragmentAutocomplete.cpp @@ -6,6 +6,7 @@ #include "Luau/Autocomplete.h" #include "Luau/Common.h" #include "Luau/EqSatSimplification.h" +#include "Luau/ExpectedTypeVisitor.h" #include "Luau/ModuleResolver.h" #include "Luau/Parser.h" #include "Luau/ParseOptions.h" @@ -35,6 +36,8 @@ LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak) LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations) +LUAU_FASTFLAG(LuauExpectedTypeVisitor) +LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver) namespace Luau { @@ -669,6 +672,7 @@ void cloneTypesFromFragment( destScope->lvalueTypes[d] = Luau::cloneIncremental(*lValue, *destArena, cloneState, destScope); } } + for (const auto& [d, loc] : f.localBindingsReferenced) { if (std::optional> pair = staleScope->linearSearchForBindingPair(loc->name.value, true)) @@ -685,6 +689,21 @@ void cloneTypesFromFragment( } } + if (FFlag::LuauPopulateRefinedTypesInFragmentFromOldSolver && !staleModule->checkedInNewSolver) + { + for (const auto& [d, loc] : f.localBindingsReferenced) + { + for (const Scope* stale = staleScope; stale; stale = stale->parent.get()) + { + if (auto res = stale->refinements.find(Symbol(loc)); res != stale->refinements.end()) + { + destScope->rvalueRefinements[d] = Luau::cloneIncremental(res->second, *destArena, cloneState, destScope); + break; + } + } + } + } + // Second - any referenced type alias bindings need to be placed in scope so type annotation can be resolved. // If the actual type alias appears in the fragment on the lhs as a definition (in declaredAliases), it will be processed during typechecking // anyway @@ -1191,6 +1210,19 @@ FragmentTypeCheckResult typecheckFragment_( reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverEnd); + if (FFlag::LuauExpectedTypeVisitor) + { + ExpectedTypeVisitor etv{ + NotNull{&incrementalModule->astTypes}, + NotNull{&incrementalModule->astExpectedTypes}, + NotNull{&incrementalModule->astResolvedTypes}, + NotNull{&incrementalModule->internalTypes}, + frontend.builtinTypes, + NotNull{freshChildOfNearestScope.get()} + }; + root->visit(&etv); + } + // In frontend we would forbid internal types // because this is just for autocomplete, we don't actually care // We also don't even need to typecheck - just synthesize types as best as we can @@ -1243,7 +1275,8 @@ std::pair typecheckFragment( FrontendOptions frontendOptions = opts.value_or(frontend.options); const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos) : findClosestScope_DEPRECATED(module, parseResult.nearestStatement); - FragmentTypeCheckResult result = typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter); + FragmentTypeCheckResult result = + typecheckFragment_(frontend, parseResult.root, module, closestScope, cursorPos, std::move(parseResult.alloc), frontendOptions, reporter); result.ancestry = std::move(parseResult.ancestry); reportFragmentString(reporter, tryParse->fragmentToParse); return {FragmentTypeCheckStatus::Success, result}; diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp index 2b06f536..2ef14b11 100644 --- a/Analysis/src/Frontend.cpp +++ b/Analysis/src/Frontend.cpp @@ -10,6 +10,7 @@ #include "Luau/DataFlowGraph.h" #include "Luau/DcrLogger.h" #include "Luau/EqSatSimplification.h" +#include "Luau/ExpectedTypeVisitor.h" #include "Luau/FileResolver.h" #include "Luau/NonStrictTypeChecker.h" #include "Luau/NotNull.h" @@ -39,13 +40,14 @@ LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(LuauNewSolverTypecheckCatchTimeouts) +LUAU_FASTFLAGVARIABLE(LuauExpectedTypeVisitor) namespace Luau { @@ -1436,13 +1438,13 @@ ModulePtr check( requireCycles }; - // FIXME: Delete this flag when clipping FFlag::LuauEagerGeneralization. + // FIXME: Delete this flag when clipping FFlag::LuauEagerGeneralization2. // // This optional<> only exists so that we can run one constructor when the flag // is set, and another when it is unset. std::optional cs; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { ConstraintSet constraintSet = cg.run(sourceModule.root); result->errors = std::move(constraintSet.errors); @@ -1610,6 +1612,19 @@ ModulePtr check( }; } + if (FFlag::LuauExpectedTypeVisitor) + { + ExpectedTypeVisitor etv{ + NotNull{&result->astTypes}, + NotNull{&result->astExpectedTypes}, + NotNull{&result->astResolvedTypes}, + NotNull{&result->internalTypes}, + builtinTypes, + NotNull{parentScope.get()} + }; + sourceModule.root->visit(&etv); + } + unfreeze(result->interfaceTypes); result->clonePublicInterface(builtinTypes, *iceHandler); diff --git a/Analysis/src/Generalization.cpp b/Analysis/src/Generalization.cpp index 9405a333..bd26d2a2 100644 --- a/Analysis/src/Generalization.cpp +++ b/Analysis/src/Generalization.cpp @@ -16,7 +16,7 @@ LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) -LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization) +LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization2) namespace Luau { @@ -469,7 +469,7 @@ struct FreeTypeSearcher : TypeVisitor bool visit(TypeId ty, const FreeType& ft) override { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { if (!subsumes(scope, ft.scope)) return true; @@ -520,7 +520,7 @@ struct FreeTypeSearcher : TypeVisitor if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) unsealedTables.insert(ty); else { @@ -574,7 +574,6 @@ struct FreeTypeSearcher : TypeVisitor traverse(*prop.writeTy); polarity = p; } - } else { @@ -594,7 +593,7 @@ struct FreeTypeSearcher : TypeVisitor if (tt.indexer) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // {[K]: V} is equivalent to three functions: get, set, and iterate // @@ -652,7 +651,7 @@ struct FreeTypeSearcher : TypeVisitor if (!subsumes(scope, ftp.scope)) return true; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { GeneralizationParams& params = typePacks[tp]; ++params.useCount; @@ -1247,8 +1246,7 @@ struct RemoveType : Substitution // NOLINT * @param needle The type to be removed. */ [[nodiscard]] -static std::optional< - TypeId> removeType(NotNull arena, NotNull builtinTypes, TypeId haystack, TypeId needle) +static std::optional removeType(NotNull arena, NotNull builtinTypes, TypeId haystack, TypeId needle) { RemoveType rt{builtinTypes, arena, needle}; return rt.substitute(haystack); @@ -1276,7 +1274,7 @@ GeneralizationResult generalizeType( if (!hasLowerBound && !hasUpperBound) { - if (!isWithinFunction || (!FFlag::LuauEagerGeneralization && (params.polarity != Polarity::Mixed && params.useCount == 1))) + if (!isWithinFunction || (!FFlag::LuauEagerGeneralization2 && (params.polarity != Polarity::Mixed && params.useCount == 1))) emplaceType(asMutable(freeTy), builtinTypes->unknownType); else { @@ -1308,7 +1306,7 @@ GeneralizationResult generalizeType( if (follow(lb) != freeTy) emplaceType(asMutable(freeTy), lb); - else if (!isWithinFunction || (!FFlag::LuauEagerGeneralization && params.useCount == 1)) + else if (!isWithinFunction || (!FFlag::LuauEagerGeneralization2 && params.useCount == 1)) emplaceType(asMutable(freeTy), builtinTypes->unknownType); else { @@ -1425,7 +1423,7 @@ std::optional generalize( FreeTypeSearcher fts{scope, cachedTypes}; fts.traverse(ty); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { FunctionType* functionTy = getMutable(ty); auto pushGeneric = [&](TypeId t) @@ -1586,7 +1584,6 @@ struct GenericCounter : TypeVisitor traverse(*prop.writeTy); polarity = p; } - } else { @@ -1653,7 +1650,7 @@ void pruneUnnecessaryGenerics( TypeId ty ) { - if (!FFlag::LuauEagerGeneralization) + if (!FFlag::LuauEagerGeneralization2) return; ty = follow(ty); diff --git a/Analysis/src/InferPolarity.cpp b/Analysis/src/InferPolarity.cpp index 806afb27..82dbd3d4 100644 --- a/Analysis/src/InferPolarity.cpp +++ b/Analysis/src/InferPolarity.cpp @@ -5,7 +5,7 @@ #include "Luau/Scope.h" #include "Luau/VisitType.h" -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) namespace Luau { @@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor template static void inferGenericPolarities_(NotNull arena, NotNull scope, TID ty) { - if (!FFlag::LuauEagerGeneralization) + if (!FFlag::LuauEagerGeneralization2) return; InferPolarity infer{arena, scope}; diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp index 3bfa2f9e..aaeb2283 100644 --- a/Analysis/src/Linter.cpp +++ b/Analysis/src/Linter.cpp @@ -2294,36 +2294,36 @@ private: bool visit(AstExprLocal* node) override { - const FunctionType* fty = getFunctionType(node); - bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); + const FunctionType* fty = getFunctionType(node); + bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); - if (shouldReport) - report(node->location, node->local->name.value); + if (shouldReport) + report(node->location, node->local->name.value); return true; } bool visit(AstExprGlobal* node) override { - const FunctionType* fty = getFunctionType(node); - bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); + const FunctionType* fty = getFunctionType(node); + bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); - if (shouldReport) - report(node->location, node->name.value); + if (shouldReport) + report(node->location, node->name.value); return true; } bool visit(AstStatLocalFunction* node) override { - check(node->func); - return false; + check(node->func); + return false; } bool visit(AstStatFunction* node) override { - check(node->func); - return false; + check(node->func); + return false; } bool visit(AstExprIndexName* node) override diff --git a/Analysis/src/Module.cpp b/Analysis/src/Module.cpp index 58f728e0..053e19cf 100644 --- a/Analysis/src/Module.cpp +++ b/Analysis/src/Module.cpp @@ -15,7 +15,7 @@ #include LUAU_FASTFLAG(LuauSolverV2); -LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) +LUAU_FASTFLAG(LuauUserTypeFunctionAliases) namespace Luau { @@ -265,10 +265,7 @@ struct ClonePublicInterface : Substitution TypeId type = cloneType(tf.type); - if (FFlag::LuauRetainDefinitionAliasLocations) - return TypeFun{typeParams, typePackParams, type, tf.definitionLocation}; - else - return TypeFun{typeParams, typePackParams, type}; + return TypeFun{typeParams, typePackParams, type, tf.definitionLocation}; } }; @@ -309,6 +306,14 @@ void Module::clonePublicInterface(NotNull builtinTypes, InternalEr ty = clonePublicInterface.cloneType(ty); } + if (FFlag::LuauUserTypeFunctionAliases) + { + for (auto& tf : typeFunctionAliases) + { + *tf = clonePublicInterface.cloneTypeFun(*tf); + } + } + // Copy external stuff over to Module itself this->returnType = moduleScope->returnType; this->exportedTypeBindings = moduleScope->exportedTypeBindings; diff --git a/Analysis/src/NonStrictTypeChecker.cpp b/Analysis/src/NonStrictTypeChecker.cpp index 336d0290..a650877d 100644 --- a/Analysis/src/NonStrictTypeChecker.cpp +++ b/Analysis/src/NonStrictTypeChecker.cpp @@ -24,6 +24,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) +LUAU_FASTFLAGVARIABLE(LuauNewNonStrictFixGenericTypePacks) namespace Luau { @@ -1091,23 +1092,42 @@ struct NonStrictTypeChecker Scope* scope = findInnermostScope(tp->location); LUAU_ASSERT(scope); - std::optional alias = scope->lookupPack(tp->genericName.value); - if (!alias.has_value()) + if (FFlag::LuauNewNonStrictFixGenericTypePacks) { + if (std::optional alias = scope->lookupPack(tp->genericName.value)) + return; + if (scope->lookupType(tp->genericName.value)) - { - reportError( + return reportError( SwappedGenericTypeParameter{ tp->genericName.value, SwappedGenericTypeParameter::Kind::Pack, }, tp->location ); - } + + reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); } else { - reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); + std::optional alias = scope->lookupPack(tp->genericName.value); + if (!alias.has_value()) + { + if (scope->lookupType(tp->genericName.value)) + { + reportError( + SwappedGenericTypeParameter{ + tp->genericName.value, + SwappedGenericTypeParameter::Kind::Pack, + }, + tp->location + ); + } + } + else + { + reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); + } } } diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp index 6597f62e..b80ee2b8 100644 --- a/Analysis/src/Normalize.cpp +++ b/Analysis/src/Normalize.cpp @@ -33,150 +33,6 @@ static bool shouldEarlyExit(NormalizationResult res) return false; } -TypeIds::TypeIds(std::initializer_list tys) -{ - for (TypeId ty : tys) - insert(ty); -} - -void TypeIds::insert(TypeId ty) -{ - ty = follow(ty); - - // get a reference to the slot for `ty` in `types` - bool& entry = types[ty]; - - // if `ty` is fresh, we can set it to `true`, add it to the order and hash and be done. - if (!entry) - { - entry = true; - order.push_back(ty); - hash ^= std::hash{}(ty); - } -} - -void TypeIds::clear() -{ - order.clear(); - types.clear(); - hash = 0; -} - -TypeId TypeIds::front() const -{ - return order.at(0); -} - -TypeIds::iterator TypeIds::begin() -{ - return order.begin(); -} - -TypeIds::iterator TypeIds::end() -{ - return order.end(); -} - -TypeIds::const_iterator TypeIds::begin() const -{ - return order.begin(); -} - -TypeIds::const_iterator TypeIds::end() const -{ - return order.end(); -} - -TypeIds::iterator TypeIds::erase(TypeIds::const_iterator it) -{ - TypeId ty = *it; - types[ty] = false; - hash ^= std::hash{}(ty); - return order.erase(it); -} - -void TypeIds::erase(TypeId ty) -{ - const_iterator it = std::find(order.begin(), order.end(), ty); - if (it == order.end()) - return; - - erase(it); -} - -size_t TypeIds::size() const -{ - return order.size(); -} - -bool TypeIds::empty() const -{ - return order.empty(); -} - -size_t TypeIds::count(TypeId ty) const -{ - ty = follow(ty); - const bool* val = types.find(ty); - return (val && *val) ? 1 : 0; -} - -void TypeIds::retain(const TypeIds& there) -{ - for (auto it = begin(); it != end();) - { - if (there.count(*it)) - it++; - else - it = erase(it); - } -} - -size_t TypeIds::getHash() const -{ - return hash; -} - -bool TypeIds::isNever() const -{ - return std::all_of( - begin(), - end(), - [&](TypeId i) - { - // If each typeid is never, then I guess typeid's is also never? - return get(i) != nullptr; - } - ); -} - -bool TypeIds::operator==(const TypeIds& there) const -{ - // we can early return if the hashes don't match. - if (hash != there.hash) - return false; - - // we have to check equality of the sets themselves if not. - - // if the sets are unequal sizes, then they cannot possibly be equal. - // it is important to use `order` here and not `types` since the mappings - // may have different sizes since removal is not possible, and so erase - // simply writes `false` into the map. - if (order.size() != there.order.size()) - return false; - - // otherwise, we'll need to check that every element we have here is in `there`. - for (auto ty : order) - { - // if it's not, we'll return `false` - if (there.count(ty) == 0) - return false; - } - - // otherwise, we've proven the two equal! - return true; -} - NormalizedStringType::NormalizedStringType() {} NormalizedStringType::NormalizedStringType(bool isCofinite, std::map singletons) diff --git a/Analysis/src/Scope.cpp b/Analysis/src/Scope.cpp index 35259fb6..deddc506 100644 --- a/Analysis/src/Scope.cpp +++ b/Analysis/src/Scope.cpp @@ -255,6 +255,29 @@ bool Scope::shouldWarnGlobal(std::string name) const return false; } +NotNull Scope::findNarrowestScopeContaining(Location location) +{ + Scope* bestScope = this; + + bool didNarrow; + do + { + didNarrow = false; + for (auto scope : bestScope->children) + { + if (scope->location.encloses(location)) + { + bestScope = scope.get(); + didNarrow = true; + break; + } + } + } while (didNarrow && bestScope->children.size() > 0); + + return NotNull{bestScope}; +} + + bool subsumesStrict(Scope* left, Scope* right) { while (right) diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp index 5cf7fbc2..1f7fd49b 100644 --- a/Analysis/src/Subtyping.cpp +++ b/Analysis/src/Subtyping.cpp @@ -21,7 +21,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) namespace Luau { @@ -675,20 +675,21 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result.isSubtype = ok; result.isCacheable = false; } - else if (auto superGeneric = get(superTy); FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant) + else if (auto superGeneric = get(superTy); + FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant) { bool ok = bindGeneric(env, subTy, superTy); result.isSubtype = ok; result.isCacheable = false; } - else if (auto pair = get2(subTy, superTy); FFlag::LuauEagerGeneralization && pair) + else if (auto pair = get2(subTy, superTy); FFlag::LuauEagerGeneralization2 && pair) { // Any two free types are potentially subtypes of one another because // both of them could be narrowed to never. result = {true}; result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy}); } - else if (auto superFree = get(superTy); superFree && FFlag::LuauEagerGeneralization) + else if (auto superFree = get(superTy); superFree && FFlag::LuauEagerGeneralization2) { // Given SubTy <: (LB <: SuperTy <: UB) // @@ -703,7 +704,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub if (result.isSubtype) result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy}); } - else if (auto subFree = get(subTy); subFree && FFlag::LuauEagerGeneralization) + else if (auto subFree = get(subTy); subFree && FFlag::LuauEagerGeneralization2) { // Given (LB <: SubTy <: UB) <: SuperTy // @@ -767,7 +768,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub result.isSubtype = ok; result.isCacheable = false; } - else if (auto superGeneric = get(superTy); !FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant) + else if (auto superGeneric = get(superTy); + !FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant) { bool ok = bindGeneric(env, subTy, superTy); result.isSubtype = ok; @@ -1450,7 +1452,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl { SubtypingResult result{true}; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer) { @@ -1506,7 +1508,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl { if (subTable->indexer) result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope)); - else if (FFlag::LuauEagerGeneralization && subTable->state != TableState::Sealed) + else if (FFlag::LuauEagerGeneralization2 && subTable->state != TableState::Sealed) { // As above, we assume that {| |} <: {T} because the unsealed table // on the left will eventually gain the necessary indexer. @@ -1548,7 +1550,12 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Meta } } -SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull scope) +SubtypingResult Subtyping::isCovariantWith( + SubtypingEnvironment& env, + const ExternType* subExternType, + const ExternType* superExternType, + NotNull scope +) { return {isSubclass(subExternType, superExternType)}; } @@ -1737,9 +1744,8 @@ 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->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope)) - ); + result.andAlso(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)); result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers, scope)); diff --git a/Analysis/src/TableLiteralInference.cpp b/Analysis/src/TableLiteralInference.cpp index bf7d3096..1285f7a3 100644 --- a/Analysis/src/TableLiteralInference.cpp +++ b/Analysis/src/TableLiteralInference.cpp @@ -19,16 +19,6 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) namespace Luau { -static bool isRecord(const AstExprTable::Item& item) -{ - if (item.kind == AstExprTable::Item::Record) - return true; - else if (item.kind == AstExprTable::Item::General && item.key->is()) - return true; - else - return false; -} - TypeId matchLiteralType( NotNull> astTypes, NotNull> astExpectedTypes, diff --git a/Analysis/src/Transpiler.cpp b/Analysis/src/Transpiler.cpp index 27f15b12..64b2fb6a 100644 --- a/Analysis/src/Transpiler.cpp +++ b/Analysis/src/Transpiler.cpp @@ -1381,7 +1381,11 @@ struct Printer LUAU_ASSERT(!forVarArg); if (const auto cstNode = lookupCstNode(explicitTp)) visualizeTypeList( - explicitTp->typeList, FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, cstNode->openParenthesesPosition, cstNode->closeParenthesesPosition, cstNode->commaPositions + explicitTp->typeList, + FFlag::LuauStoreReturnTypesAsPackOnAst ? cstNode->hasParentheses : true, + cstNode->openParenthesesPosition, + cstNode->closeParenthesesPosition, + cstNode->commaPositions ); else visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize); @@ -1986,7 +1990,7 @@ struct Printer if (FFlag::LuauStoreLocalAnnotationColonPositions) visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0}); else - visualize(*a->var, Position{0,0}); + visualize(*a->var, Position{0, 0}); if (cstNode) advance(cstNode->equalsPosition); diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp index dbe0f89b..2b42bbbf 100644 --- a/Analysis/src/Type.cpp +++ b/Analysis/src/Type.cpp @@ -1079,7 +1079,7 @@ void persist(TypeId ty) queue.push_back(ttv->indexer->indexResultType); } } - else if (auto etv= get(t)) + else if (auto etv = get(t)) { for (const auto& [_name, prop] : etv->props) queue.push_back(prop.type()); diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp index 3a2092f1..9ea64669 100644 --- a/Analysis/src/TypeChecker2.cpp +++ b/Analysis/src/TypeChecker2.cpp @@ -34,6 +34,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) +LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks) LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck) @@ -782,7 +783,6 @@ void TypeChecker2::visit(AstStatReturn* ret) for (AstExpr* expr : ret->list) visit(expr, ValueContext::RValue); - } void TypeChecker2::visit(AstStatExpr* expr) @@ -2796,22 +2796,41 @@ void TypeChecker2::visit(AstTypePackGeneric* tp) Scope* scope = findInnermostScope(tp->location); LUAU_ASSERT(scope); - std::optional alias = scope->lookupPack(tp->genericName.value); - if (!alias.has_value()) + if (FFlag::LuauNewNonStrictFixGenericTypePacks) { + if (std::optional alias = scope->lookupPack(tp->genericName.value)) + return; + if (scope->lookupType(tp->genericName.value)) - { - reportError( + return reportError( SwappedGenericTypeParameter{ tp->genericName.value, SwappedGenericTypeParameter::Kind::Pack, }, tp->location ); - } - else + + reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); + } + else + { + std::optional alias = scope->lookupPack(tp->genericName.value); + if (!alias.has_value()) { - reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); + if (scope->lookupType(tp->genericName.value)) + { + reportError( + SwappedGenericTypeParameter{ + tp->genericName.value, + SwappedGenericTypeParameter::Kind::Pack, + }, + tp->location + ); + } + else + { + reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); + } } } } @@ -2944,14 +2963,6 @@ void TypeChecker2::explainError(TypePackId subTy, TypePackId superTy, Location l reportError(TypePackMismatch{superTy, subTy, reasonings.toString()}, location); } -namespace -{ -bool isRecord(const AstExprTable::Item& item) -{ - return item.kind == AstExprTable::Item::Record || (item.kind == AstExprTable::Item::General && item.key->is()); -} -} - bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedType) { auto exprType = follow(lookupType(expr)); @@ -2982,7 +2993,7 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT return testIsSubtype(exprType, expectedType, expr->location); } - Set > missingKeys{{}}; + Set> missingKeys{{}}; for (const auto& [name, prop] : expectedTableType->props) { if (FFlag::LuauEnableWriteOnlyProperties) diff --git a/Analysis/src/TypeFunction.cpp b/Analysis/src/TypeFunction.cpp index 8ca4e7d5..6a6a92a4 100644 --- a/Analysis/src/TypeFunction.cpp +++ b/Analysis/src/TypeFunction.cpp @@ -27,6 +27,7 @@ #include "Luau/Unifier2.h" #include "Luau/VecDeque.h" #include "Luau/VisitType.h" +#include "Luau/ApplyTypeFunction.h" #include "lua.h" #include "lualib.h" @@ -47,8 +48,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0 LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauEagerGeneralization) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) @@ -56,6 +57,7 @@ LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers) LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAGVARIABLE(LuauNoMoreInjectiveTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults) +LUAU_FASTFLAG(LuauUserTypeFunctionAliases) namespace Luau { @@ -283,7 +285,7 @@ struct TypeFunctionReducer } else if (is(ty)) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) return SkipTestResult::Generic; else return SkipTestResult::Irreducible; @@ -305,7 +307,7 @@ struct TypeFunctionReducer } else if (is(ty)) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) return SkipTestResult::Generic; else return SkipTestResult::Irreducible; @@ -569,6 +571,27 @@ struct LuauTempThreadPopper lua_State* L = nullptr; }; +template +class ScopedAssign +{ +public: + ScopedAssign(T& target, const T& value) + : target(&target) + , oldValue(target) + { + target = value; + } + + ~ScopedAssign() + { + *target = oldValue; + } + +private: + T* target = nullptr; + T oldValue; +}; + static FunctionGraphReductionResult reduceFunctionsInternal( VecDeque queuedTys, VecDeque queuedTps, @@ -789,6 +812,97 @@ struct FindUserTypeFunctionBlockers : TypeOnceVisitor } }; +static int evaluateTypeAliasCall(lua_State* L) +{ + TypeFun* tf = (TypeFun*)lua_tolightuserdata(L, lua_upvalueindex(1)); + + TypeFunctionRuntime* runtime = getTypeFunctionRuntime(L); + TypeFunctionRuntimeBuilderState* runtimeBuilder = runtime->runtimeBuilder; + + ApplyTypeFunction applyTypeFunction{runtimeBuilder->ctx->arena}; + + int argumentCount = lua_gettop(L); + std::vector rawTypeArguments; + + for (int i = 0; i < argumentCount; i++) + { + TypeFunctionTypeId tfty = getTypeUserData(L, i + 1); + TypeId ty = deserialize(tfty, runtimeBuilder); + + if (!runtimeBuilder->errors.empty()) + luaL_error(L, "failed to deserialize type at argument %d", i + 1); + + rawTypeArguments.push_back(ty); + } + + // Check if we have enough arguments, by typical typechecking rules + size_t typesRequired = tf->typeParams.size(); + size_t packsRequired = tf->typePackParams.size(); + + size_t typesProvided = rawTypeArguments.size() > typesRequired ? typesRequired : rawTypeArguments.size(); + size_t extraTypes = rawTypeArguments.size() > typesRequired ? rawTypeArguments.size() - typesRequired : 0; + size_t packsProvided = 0; + + if (extraTypes != 0 && packsProvided == 0) + { + // Extra types are only collected into a pack if a pack is expected + if (packsRequired != 0) + packsProvided += 1; + else + typesProvided += extraTypes; + } + + for (size_t i = typesProvided; i < typesRequired; ++i) + { + if (tf->typeParams[i].defaultValue) + typesProvided += 1; + } + + for (size_t i = packsProvided; i < packsRequired; ++i) + { + if (tf->typePackParams[i].defaultValue) + packsProvided += 1; + } + + if (extraTypes == 0 && packsProvided + 1 == packsRequired) + packsProvided += 1; + + if (typesProvided != typesRequired || packsProvided != packsRequired) + luaL_error(L, "not enough arguments to call"); + + // Prepare final types and packs + auto [types, packs] = saturateArguments(runtimeBuilder->ctx->arena, runtimeBuilder->ctx->builtins, *tf, rawTypeArguments, {}); + + for (size_t i = 0; i < types.size(); ++i) + applyTypeFunction.typeArguments[tf->typeParams[i].ty] = types[i]; + + for (size_t i = 0; i < packs.size(); ++i) + applyTypeFunction.typePackArguments[tf->typePackParams[i].tp] = packs[i]; + + std::optional maybeInstantiated = applyTypeFunction.substitute(tf->type); + + if (!maybeInstantiated.has_value()) + { + luaL_error(L, "failed to instantiate type alias"); + return true; + } + + TypeId target = follow(*maybeInstantiated); + + FunctionGraphReductionResult result = reduceTypeFunctions(target, Location{}, *runtimeBuilder->ctx); + + if (!result.errors.empty()) + luaL_error(L, "failed to reduce type function with: %s", toString(result.errors.front()).c_str()); + + TypeFunctionTypeId serializedTy = serialize(follow(target), runtimeBuilder); + + if (!runtimeBuilder->errors.empty()) + luaL_error(L, "%s", runtimeBuilder->errors.front().c_str()); + + allocTypeUserData(L, serializedTy->type); + return 1; +} + TypeFunctionReductionResult userDefinedTypeFunction( TypeId instance, const std::vector& typeParams, @@ -819,11 +933,21 @@ TypeFunctionReductionResult userDefinedTypeFunction( for (auto typeParam : typeParams) check.traverse(follow(typeParam)); + if (FFlag::LuauUserTypeFunctionAliases) + { + // Check that our environment doesn't depend on any type aliases that are blocked + for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias) + { + if (definition.first->typeParams.empty() && definition.first->typePackParams.empty()) + check.traverse(follow(definition.first->type)); + } + } + if (!check.blockingTypes.empty()) return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}}; // Ensure that whole type function environment is registered - for (auto& [name, definition] : typeFunction->userFuncData.environment) + for (auto& [name, definition] : typeFunction->userFuncData.environmentFunction) { // Cannot evaluate if a potential dependency couldn't be parsed if (definition.first->hasErrors) @@ -849,8 +973,13 @@ TypeFunctionReductionResult userDefinedTypeFunction( lua_State* L = lua_newthread(global); LuauTempThreadPopper popper(global); + std::unique_ptr runtimeBuilder = std::make_unique(ctx); + + ScopedAssign setRuntimeBuilder(ctx->typeFunctionRuntime->runtimeBuilder, runtimeBuilder.get()); + ScopedAssign enableReduction(ctx->normalizer->sharedState->reentrantTypeReduction, false); + // Build up the environment table of each function we have visible - for (auto& [_, curr] : typeFunction->userFuncData.environment) + for (auto& [_, curr] : typeFunction->userFuncData.environmentFunction) { // Environment table has to be filled only once in the current execution context if (ctx->typeFunctionRuntime->initialized.find(curr.first)) @@ -870,7 +999,7 @@ TypeFunctionReductionResult userDefinedTypeFunction( lua_getfenv(L, -1); lua_setreadonly(L, -1, false); - for (auto& [name, definition] : typeFunction->userFuncData.environment) + for (auto& [name, definition] : typeFunction->userFuncData.environmentFunction) { // Filter visibility based on original scope depth if (definition.second >= curr.second) @@ -885,6 +1014,39 @@ TypeFunctionReductionResult userDefinedTypeFunction( } } + if (FFlag::LuauUserTypeFunctionAliases) + { + for (auto& [name, definition] : typeFunction->userFuncData.environmentAlias) + { + // Filter visibility based on original scope depth + if (definition.second >= curr.second) + { + if (definition.first->typeParams.empty() && definition.first->typePackParams.empty()) + { + TypeId ty = follow(definition.first->type); + + // This is checked at the top of the function, and should still be true. + LUAU_ASSERT(!isPending(ty, ctx->solver)); + + TypeFunctionTypeId serializedTy = serialize(ty, runtimeBuilder.get()); + + // Only register aliases that are representable in type environment + if (runtimeBuilder->errors.empty()) + { + allocTypeUserData(L, serializedTy->type); + lua_setfield(L, -2, name.c_str()); + } + } + else + { + lua_pushlightuserdata(L, definition.first); + lua_pushcclosure(L, evaluateTypeAliasCall, name.c_str(), 1); + lua_setfield(L, -2, name.c_str()); + } + } + } + } + lua_setreadonly(L, -1, true); lua_pop(L, 2); } @@ -901,8 +1063,6 @@ TypeFunctionReductionResult userDefinedTypeFunction( resetTypeFunctionState(L); - std::unique_ptr runtimeBuilder = std::make_unique(ctx); - // Push serialized arguments onto the stack for (auto typeParam : typeParams) { @@ -1101,7 +1261,7 @@ TypeFunctionReductionResult unmTypeFunction( if (isPending(operandTy, ctx->solver)) return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}}; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) operandTy = follow(operandTy); std::shared_ptr normTy = ctx->normalizer->normalize(operandTy); @@ -1698,7 +1858,7 @@ TypeFunctionReductionResult orTypeFunction( return {rhsTy, Reduction::MaybeOk, {}, {}}; // check to see if both operand types are resolved enough, and wait to reduce if not - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { if (is(lhsTy)) return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; @@ -1745,7 +1905,7 @@ static TypeFunctionReductionResult comparisonTypeFunction( if (lhsTy == instance || rhsTy == instance) return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { if (is(lhsTy)) return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; @@ -2099,7 +2259,7 @@ bool isSimpleDiscriminant(TypeId ty) return isApproximateTruthy(ty) || isApproximateFalsy(ty); } -} +} // namespace TypeFunctionReductionResult refineTypeFunction( TypeId instance, @@ -2119,9 +2279,8 @@ TypeFunctionReductionResult refineTypeFunction( for (size_t i = 1; i < typeParams.size(); i++) discriminantTypes.push_back(follow(typeParams.at(i))); - const bool targetIsPending = FFlag::LuauEagerGeneralization - ? is(targetTy) - : isPending(targetTy, ctx->solver); + const bool targetIsPending = FFlag::LuauEagerGeneralization2 ? is(targetTy) + : isPending(targetTy, ctx->solver); // check to see if both operand types are resolved enough, and wait to reduce if not if (targetIsPending) @@ -2206,7 +2365,7 @@ TypeFunctionReductionResult refineTypeFunction( if (is(target) || isSimpleDiscriminant(discriminant)) { SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // Simplification considers free and generic types to be // 'blocking', but that's not suitable for refine<>. @@ -2615,8 +2774,8 @@ TypeFunctionReductionResult keyofFunctionImpl( if (!normTy) return {std::nullopt, Reduction::MaybeOk, {}, {}}; - // 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 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->hasExternTypes()) return {std::nullopt, Reduction::Erroneous, {}, {}}; @@ -2985,7 +3144,8 @@ TypeFunctionReductionResult indexFunctionImpl( return {std::nullopt, Reduction::Erroneous, {}, {}}; // 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) + for (auto externTypeIter = indexeeNormTy->externTypes.ordering.begin(); externTypeIter != indexeeNormTy->externTypes.ordering.end(); + ++externTypeIter) { auto externTy = get(*externTypeIter); if (!externTy) @@ -3335,7 +3495,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions() , ltFunc{"lt", ltTypeFunction} , leFunc{"le", leTypeFunction} , eqFunc{"eq", eqTypeFunction} - , refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization} + , refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization2} , singletonFunc{"singleton", singletonTypeFunction} , unionFunc{"union", unionTypeFunction} , intersectFunc{"intersect", intersectTypeFunction} diff --git a/Analysis/src/TypeFunctionRuntime.cpp b/Analysis/src/TypeFunctionRuntime.cpp index f60870c3..d5849aaa 100644 --- a/Analysis/src/TypeFunctionRuntime.cpp +++ b/Analysis/src/TypeFunctionRuntime.cpp @@ -63,7 +63,7 @@ std::optional checkResultForError(lua_State* L, const char* typeFun } } -static TypeFunctionRuntime* getTypeFunctionRuntime(lua_State* L) +TypeFunctionRuntime* getTypeFunctionRuntime(lua_State* L) { return static_cast(lua_getthreaddata(lua_mainthread(L))); } diff --git a/Analysis/src/TypeFunctionRuntimeBuilder.cpp b/Analysis/src/TypeFunctionRuntimeBuilder.cpp index e917e598..a1edc3d9 100644 --- a/Analysis/src/TypeFunctionRuntimeBuilder.cpp +++ b/Analysis/src/TypeFunctionRuntimeBuilder.cpp @@ -209,9 +209,7 @@ private: { // 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( - TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty} - ); + target = typeFunctionRuntime->typeArena.allocate(TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}); } else if (auto g = get(ty)) { diff --git a/Analysis/src/TypeIds.cpp b/Analysis/src/TypeIds.cpp new file mode 100644 index 00000000..f4189a68 --- /dev/null +++ b/Analysis/src/TypeIds.cpp @@ -0,0 +1,153 @@ +// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details + +#include "Luau/Type.h" +#include "Luau/TypeIds.h" + +namespace Luau +{ + +TypeIds::TypeIds(std::initializer_list tys) +{ + for (TypeId ty : tys) + insert(ty); +} + +void TypeIds::insert(TypeId ty) +{ + ty = follow(ty); + + // get a reference to the slot for `ty` in `types` + bool& entry = types[ty]; + + // if `ty` is fresh, we can set it to `true`, add it to the order and hash and be done. + if (!entry) + { + entry = true; + order.push_back(ty); + hash ^= std::hash{}(ty); + } +} + +void TypeIds::clear() +{ + order.clear(); + types.clear(); + hash = 0; +} + +TypeId TypeIds::front() const +{ + return order.at(0); +} + +TypeIds::iterator TypeIds::begin() +{ + return order.begin(); +} + +TypeIds::iterator TypeIds::end() +{ + return order.end(); +} + +TypeIds::const_iterator TypeIds::begin() const +{ + return order.begin(); +} + +TypeIds::const_iterator TypeIds::end() const +{ + return order.end(); +} + +TypeIds::iterator TypeIds::erase(TypeIds::const_iterator it) +{ + TypeId ty = *it; + types[ty] = false; + hash ^= std::hash{}(ty); + return order.erase(it); +} + +void TypeIds::erase(TypeId ty) +{ + const_iterator it = std::find(order.begin(), order.end(), ty); + if (it == order.end()) + return; + + erase(it); +} + +size_t TypeIds::size() const +{ + return order.size(); +} + +bool TypeIds::empty() const +{ + return order.empty(); +} + +size_t TypeIds::count(TypeId ty) const +{ + ty = follow(ty); + const bool* val = types.find(ty); + return (val && *val) ? 1 : 0; +} + +void TypeIds::retain(const TypeIds& tys) +{ + for (auto it = begin(); it != end();) + { + if (tys.count(*it)) + it++; + else + it = erase(it); + } +} + +size_t TypeIds::getHash() const +{ + return hash; +} + +bool TypeIds::isNever() const +{ + return std::all_of( + begin(), + end(), + [&](TypeId i) + { + // If each typeid is never, then I guess typeid's is also never? + return get(i) != nullptr; + } + ); +} + +bool TypeIds::operator==(const TypeIds& there) const +{ + // we can early return if the hashes don't match. + if (hash != there.hash) + return false; + + // we have to check equality of the sets themselves if not. + + // if the sets are unequal sizes, then they cannot possibly be equal. + // it is important to use `order` here and not `types` since the mappings + // may have different sizes since removal is not possible, and so erase + // simply writes `false` into the map. + if (order.size() != there.order.size()) + return false; + + // otherwise, we'll need to check that every element we have here is in `there`. + for (auto ty : order) + { + // if it's not, we'll return `false` + if (there.count(ty) == 0) + return false; + } + + // otherwise, we've proven the two equal! + return true; +} + +} diff --git a/Analysis/src/TypeInfer.cpp b/Analysis/src/TypeInfer.cpp index e17ea4b6..76990b6c 100644 --- a/Analysis/src/TypeInfer.cpp +++ b/Analysis/src/TypeInfer.cpp @@ -34,7 +34,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) -LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) namespace Luau @@ -1664,10 +1663,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea FreeType* ftv = getMutable(ty); LUAU_ASSERT(ftv); ftv->forwardedTypeAlias = true; - if (FFlag::LuauRetainDefinitionAliasLocations) - bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location}; - else - bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty}; + bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location}; scope->typeAliasLocations[name] = typealias.location; scope->typeAliasNameLocations[name] = typealias.nameLocation; @@ -1712,10 +1708,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatDeclareExternTyp TypeId metaTy = addType(TableType{TableState::Sealed, scope->level}); etv->metatable = metaTy; - if (FFlag::LuauRetainDefinitionAliasLocations) - scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location}; - else - scope->exportedTypeBindings[className] = TypeFun{{}, classTy}; + scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location}; } ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType) @@ -4330,7 +4323,7 @@ void TypeChecker::checkArgumentList( if (exceedsLoopCount()) return; - } + } TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}}); state.tryUnify(varPack, tail); diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp index 4f38c358..e2d2b028 100644 --- a/Analysis/src/TypeUtils.cpp +++ b/Analysis/src/TypeUtils.cpp @@ -12,7 +12,7 @@ #include LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs) namespace Luau @@ -306,7 +306,7 @@ TypePack extendTypePack( TypePack newPack; newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) trackInteriorFreeTypePack(ftp->scope, *newPack.tail); if (FFlag::LuauSolverV2) @@ -588,7 +588,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty) void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) { LUAU_ASSERT(tp); - if (!FFlag::LuauEagerGeneralization) + if (!FFlag::LuauEagerGeneralization2) return; for (; scope; scope = scope->parent.get()) @@ -685,4 +685,15 @@ std::optional extractMatchingTableType(std::vector& tables, Type return std::nullopt; } +bool isRecord(const AstExprTable::Item& item) +{ + if (item.kind == AstExprTable::Item::Record) + return true; + else if (item.kind == AstExprTable::Item::General && item.key->is()) + return true; + else + return false; +} + + } // namespace Luau diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp index f9bf17b8..d108d7c0 100644 --- a/Analysis/src/Unifier2.cpp +++ b/Analysis/src/Unifier2.cpp @@ -19,7 +19,7 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) namespace Luau { @@ -329,12 +329,12 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn) for (TypePackId genericPack : subFn->genericPacks) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) genericPack = follow(genericPack); - // TODO: Clip this follow() with LuauEagerGeneralization + // TODO: Clip this follow() with LuauEagerGeneralization2 const GenericTypePack* gen = get(follow(genericPack)); if (gen) genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); @@ -465,7 +465,7 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable) { result &= unify(subTable->indexer->indexType, superTable->indexer->indexType); result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // FIXME: We can probably do something more efficient here. result &= unify(superTable->indexer->indexType, subTable->indexer->indexType); diff --git a/Ast/include/Luau/Cst.h b/Ast/include/Luau/Cst.h index f5cd5a5e..b9dcb7fd 100644 --- a/Ast/include/Luau/Cst.h +++ b/Ast/include/Luau/Cst.h @@ -393,7 +393,7 @@ public: std::optional separatorPosition; CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty - Position stringPosition{0, 0}; // only if Kind == StringProperty + Position stringPosition{0, 0}; // only if Kind == StringProperty }; CstTypeTable(AstArray items, bool isArray); diff --git a/Ast/include/Luau/Parser.h b/Ast/include/Luau/Parser.h index 52dd76ea..0d5af69b 100644 --- a/Ast/include/Luau/Parser.h +++ b/Ast/include/Luau/Parser.h @@ -456,7 +456,7 @@ private: AstType* annotation; Position colonPosition; - explicit Binding(const Name& name, AstType* annotation = nullptr, Position colonPosition = {0,0}) + explicit Binding(const Name& name, AstType* annotation = nullptr, Position colonPosition = {0, 0}) : name(name) , annotation(annotation) , colonPosition(colonPosition) diff --git a/Ast/src/Cst.cpp b/Ast/src/Cst.cpp index 7616311c..a4f359a8 100644 --- a/Ast/src/Cst.cpp +++ b/Ast/src/Cst.cpp @@ -94,7 +94,11 @@ CstStatReturn::CstStatReturn(AstArray commaPositions) { } -CstStatLocal::CstStatLocal(AstArray varsAnnotationColonPositions, AstArray varsCommaPositions, AstArray valuesCommaPositions) +CstStatLocal::CstStatLocal( + AstArray varsAnnotationColonPositions, + AstArray varsCommaPositions, + AstArray valuesCommaPositions +) : CstNode(CstClassIndex()) , varsAnnotationColonPositions(varsAnnotationColonPositions) , varsCommaPositions(varsCommaPositions) @@ -102,7 +106,12 @@ CstStatLocal::CstStatLocal(AstArray varsAnnotationColonPositions, AstA { } -CstStatFor::CstStatFor(Position annotationColonPosition, Position equalsPosition, Position endCommaPosition, std::optional stepCommaPosition) +CstStatFor::CstStatFor( + Position annotationColonPosition, + Position equalsPosition, + Position endCommaPosition, + std::optional stepCommaPosition +) : CstNode(CstClassIndex()) , annotationColonPosition(annotationColonPosition) , equalsPosition(equalsPosition) @@ -111,7 +120,11 @@ CstStatFor::CstStatFor(Position annotationColonPosition, Position equalsPosition { } -CstStatForIn::CstStatForIn(AstArray varsAnnotationColonPositions, AstArray varsCommaPositions, AstArray valuesCommaPositions) +CstStatForIn::CstStatForIn( + AstArray varsAnnotationColonPositions, + AstArray varsCommaPositions, + AstArray valuesCommaPositions +) : CstNode(CstClassIndex()) , varsAnnotationColonPositions(varsAnnotationColonPositions) , varsCommaPositions(varsCommaPositions) diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp index ccfebf19..80a5369f 100644 --- a/Ast/src/Parser.cpp +++ b/Ast/src/Parser.cpp @@ -709,7 +709,8 @@ AstStat* Parser::parseFor() if (options.storeCstData) { if (FFlag::LuauStoreLocalAnnotationColonPositions) - cstNodeMap[node] = allocator.alloc(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions)); + cstNodeMap[node] = + allocator.alloc(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions)); else cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPosition, copy(valuesCommaPositions)); } @@ -1009,7 +1010,8 @@ AstStat* Parser::parseLocal(const AstArray& attributes) if (options.storeCstData) { if (FFlag::LuauStoreLocalAnnotationColonPositions) - cstNodeMap[node] = allocator.alloc(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions)); + cstNodeMap[node] = + allocator.alloc(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions)); else cstNodeMap[node] = allocator.alloc(AstArray{}, varsCommaPositions, copy(valuesCommaPositions)); } @@ -1333,7 +1335,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArrayname, propName->location, propType, false, Location(propStart, lexer.previousLocation())} - ); + props.push_back(AstDeclaredExternTypeProperty{ + propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation()) + }); } } else @@ -1533,9 +1541,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArrayname, propName->location, propType, false, Location(propStart, lexer.previousLocation())} - ); + props.push_back(AstDeclaredExternTypeProperty{ + propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation()) + }); } } } @@ -2618,7 +2626,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext) tableSeparator(), lexer.current().location.begin, allocator.alloc(sourceString, style, blockDepth), - stringPosition + stringPosition }); } else diff --git a/CLI/include/Luau/ReplRequirer.h b/CLI/include/Luau/ReplRequirer.h index cc631f78..0d2d02f5 100644 --- a/CLI/include/Luau/ReplRequirer.h +++ b/CLI/include/Luau/ReplRequirer.h @@ -14,16 +14,11 @@ void requireConfigInit(luarequire_Configuration* config); struct ReplRequirer { - using CompileOptions = Luau::CompileOptions(*)(); - using BoolCheck = bool(*)(); - using Coverage = void(*)(lua_State*, int); + using CompileOptions = Luau::CompileOptions (*)(); + using BoolCheck = bool (*)(); + using Coverage = void (*)(lua_State*, int); - ReplRequirer( - CompileOptions copts, - BoolCheck coverageActive, - BoolCheck codegenEnabled, - Coverage coverageTrack - ); + ReplRequirer(CompileOptions copts, BoolCheck coverageActive, BoolCheck codegenEnabled, Coverage coverageTrack); CompileOptions copts; BoolCheck coverageActive; diff --git a/CodeGen/src/EmitCommonA64.h b/CodeGen/src/EmitCommonA64.h index 5da992d8..610f2544 100644 --- a/CodeGen/src/EmitCommonA64.h +++ b/CodeGen/src/EmitCommonA64.h @@ -39,8 +39,8 @@ inline constexpr RegisterA64 rBase = x25; // StkId base // Native code is as stackless as the interpreter, so we can place some data on the stack once and have it accessible at any point // See CodeGenA64.cpp for layout -inline constexpr unsigned kStashSlots = 9; // stashed non-volatile registers -inline constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury! +inline constexpr unsigned kStashSlots = 9; // stashed non-volatile registers +inline constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury! inline constexpr unsigned kSpillSlots = 22; // slots for spilling temporary registers inline constexpr unsigned kStackSize = (kStashSlots + kTempSlots + kSpillSlots) * 8; diff --git a/Sources.cmake b/Sources.cmake index 33cac80d..fe8376f3 100644 --- a/Sources.cmake +++ b/Sources.cmake @@ -189,6 +189,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/Documentation.h Analysis/include/Luau/Error.h Analysis/include/Luau/EqSatSimplification.h + Analysis/include/Luau/ExpectedTypeVisitor.h Analysis/include/Luau/FileResolver.h Analysis/include/Luau/FragmentAutocomplete.h Analysis/include/Luau/Frontend.h @@ -237,6 +238,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/include/Luau/TypeFunctionRuntime.h Analysis/include/Luau/TypeFunctionRuntimeBuilder.h Analysis/include/Luau/TypeFwd.h + Analysis/include/Luau/TypeIds.h Analysis/include/Luau/TypeInfer.h Analysis/include/Luau/TypeOrPack.h Analysis/include/Luau/TypePack.h @@ -267,6 +269,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/EmbeddedBuiltinDefinitions.cpp Analysis/src/Error.cpp Analysis/src/EqSatSimplification.cpp + Analysis/src/ExpectedTypeVisitor.cpp Analysis/src/FileResolver.cpp Analysis/src/FragmentAutocomplete.cpp Analysis/src/Frontend.cpp @@ -306,6 +309,7 @@ target_sources(Luau.Analysis PRIVATE Analysis/src/TypeFunctionReductionGuesser.cpp Analysis/src/TypeFunctionRuntime.cpp Analysis/src/TypeFunctionRuntimeBuilder.cpp + Analysis/src/TypeIds.cpp Analysis/src/TypeInfer.cpp Analysis/src/TypeOrPack.cpp Analysis/src/TypePack.cpp diff --git a/VM/src/lapi.cpp b/VM/src/lapi.cpp index 4f4bb84e..98c34f93 100644 --- a/VM/src/lapi.cpp +++ b/VM/src/lapi.cpp @@ -15,6 +15,8 @@ #include +LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauUnrefExisting, false) + /* * This file contains most implementations of core Lua APIs from lua.h. * @@ -1445,9 +1447,26 @@ void lua_unref(lua_State* L, int ref) global_State* g = L->global; LuaTable* reg = hvalue(registry(L)); - TValue* slot = luaH_setnum(L, reg, ref); - setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable - g->registryfree = ref; + + if (DFFlag::LuauUnrefExisting) + { + const TValue* slot = luaH_getnum(reg, ref); + api_check(L, slot != luaO_nilobject); + + // similar to how 'luaH_setnum' makes non-nil slot value mutable + TValue* mutableSlot = (TValue*)slot; + + // NB: no barrier needed because value isn't collectable + setnvalue(mutableSlot, g->registryfree); + + g->registryfree = ref; + } + else + { + TValue* slot = luaH_setnum(L, reg, ref); + setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable + g->registryfree = ref; + } } void lua_setuserdatatag(lua_State* L, int idx, int tag) diff --git a/tests/AstJsonEncoder.test.cpp b/tests/AstJsonEncoder.test.cpp index 41d35d11..687b94c7 100644 --- a/tests/AstJsonEncoder.test.cpp +++ b/tests/AstJsonEncoder.test.cpp @@ -642,4 +642,3 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePackWithDefault") } TEST_SUITE_END(); - diff --git a/tests/Autocomplete.test.cpp b/tests/Autocomplete.test.cpp index ccc6cb35..b906dce0 100644 --- a/tests/Autocomplete.test.cpp +++ b/tests/Autocomplete.test.cpp @@ -19,7 +19,8 @@ LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTINT(LuauTypeInferRecursionLimit) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) +LUAU_FASTFLAG(LuauExpectedTypeVisitor) using namespace Luau; @@ -41,6 +42,9 @@ struct ACFixtureImpl : BaseType FrontendOptions opts; opts.forAutocomplete = true; opts.retainFullTypeGraphs = true; + // NOTE: Autocomplete does *not* require strict checking, meaning we should + // try to check all of these examples in `--!nocheck` mode. + this->configResolver.defaultConfig.mode = Mode::NoCheck; this->frontend.check("MainModule", opts); return Luau::autocomplete(this->frontend, "MainModule", Position{row, column}, nullCallback); @@ -51,6 +55,9 @@ struct ACFixtureImpl : BaseType FrontendOptions opts; opts.forAutocomplete = true; opts.retainFullTypeGraphs = true; + // NOTE: Autocomplete does *not* require strict checking, meaning we should + // try to check all of these examples in `--!nocheck` mode. + this->configResolver.defaultConfig.mode = Mode::NoCheck; this->frontend.check("MainModule", opts); return Luau::autocomplete(this->frontend, "MainModule", getPosition(marker), callback); @@ -61,6 +68,9 @@ struct ACFixtureImpl : BaseType FrontendOptions opts; opts.forAutocomplete = true; opts.retainFullTypeGraphs = true; + // NOTE: Autocomplete does *not* require strict checking, meaning we should + // try to check all of these examples in `--!nocheck` mode. + this->configResolver.defaultConfig.mode = Mode::NoCheck; this->frontend.check(name, opts); return Luau::autocomplete(this->frontend, name, pos, callback); @@ -103,7 +113,9 @@ struct ACFixtureImpl : BaseType } LUAU_ASSERT("Digit expected after @ symbol" && prevChar != '@'); - return BaseType::check(filteredSource); + // NOTE: Autocomplete does *not* require strict checking, meaning we should + // try to check all of these examples in `--!nocheck` mode. + return BaseType::check(Mode::NoCheck, filteredSource, std::nullopt); } LoadDefinitionFileResult loadDefinition(const std::string& source) @@ -2191,7 +2203,10 @@ local fp: @1= f if (FFlag::LuauSolverV2) REQUIRE_EQ("({ x: number, y: number }) -> number", toString(requireType("f"))); else - REQUIRE_EQ("({| x: number, y: number |}) -> number", toString(requireType("f"))); + { + // NOTE: All autocomplete tests occur under no-check mode. + REQUIRE_EQ("({| x: number, y: number |}) -> (...any)", toString(requireType("f"))); + } CHECK(ac.entryMap.count("({ x: number, y: number }) -> number")); } @@ -3120,7 +3135,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons") TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key_iso") { - check(R"( type Direction = "up" | "down" local b: {[Direction]: boolean} = {["@2"] = true} @@ -4454,9 +4468,9 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union") auto ac = autocomplete('1'); - if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization) + if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2) { - // This `if` statement is because `LuauEagerGeneralization` + // This `if` statement is because `LuauEagerGeneralization2` // sets some flags CHECK(ac.entryMap.count("BaseMethod") > 0); CHECK(ac.entryMap.count("Method") > 0); @@ -4466,7 +4480,6 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union") // Otherwise, we don't infer anything for `value`, which is _fine_. CHECK(ac.entryMap.empty()); } - } TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions") @@ -4536,4 +4549,62 @@ end CHECK_EQ(ac.entryMap.count("number"), 1); } +TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_assignment") +{ + ScopedFastFlag _{FFlag::LuauExpectedTypeVisitor, true}; + + check(R"( + local function foobar(tbl: { tag: "left" | "right" }) + tbl.tag = "@1" + end + )"); + + auto ac = autocomplete('1'); + CHECK_EQ(ac.entryMap.count("left"), 1); + CHECK_EQ(ac.entryMap.count("right"), 1); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_in_local_table") +{ + ScopedFastFlag _{FFlag::LuauExpectedTypeVisitor, true}; + + check(R"( + type Entry = { field: number, prop: string } + local x : {Entry} = {} + x[1] = { + f@1, + p@2, + } + + local t : { key1: boolean, thing2: CFrame, aaa3: vector } = { + k@3, + th@4, + } + )"); + + auto ac1 = autocomplete('1'); + CHECK_EQ(ac1.entryMap.count("field"), 1); + auto ac2 = autocomplete('2'); + CHECK_EQ(ac2.entryMap.count("prop"), 1); + auto ac3 = autocomplete('3'); + CHECK_EQ(ac3.entryMap.count("key1"), 1); + auto ac4 = autocomplete('4'); + CHECK_EQ(ac4.entryMap.count("thing2"), 1); +} + +TEST_CASE_FIXTURE(ACFixture, "autocomplete_in_type_assertion") +{ + ScopedFastFlag _{FFlag::LuauExpectedTypeVisitor, true}; + + check(R"( + type Entry = { field: number, prop: string } + return ( { f@1, p@2 } :: Entry ) + )"); + + auto ac1 = autocomplete('1'); + CHECK_EQ(ac1.entryMap.count("field"), 1); + auto ac2 = autocomplete('2'); + CHECK_EQ(ac2.entryMap.count("prop"), 1); +} + TEST_SUITE_END(); diff --git a/tests/DataFlowGraph.test.cpp b/tests/DataFlowGraph.test.cpp index 30f36cec..b8c53cc2 100644 --- a/tests/DataFlowGraph.test.cpp +++ b/tests/DataFlowGraph.test.cpp @@ -56,7 +56,6 @@ struct DataFlowGraphFixture CHECK(phi->operands.size() == operandSet.size()); for (auto o : phi->operands) CHECK(operandSet.contains(o.get())); - } }; diff --git a/tests/Fixture.h b/tests/Fixture.h index 531d76f0..cca3aec4 100644 --- a/tests/Fixture.h +++ b/tests/Fixture.h @@ -29,6 +29,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) LUAU_FASTFLAG(LuauTypeFunOptional) +LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature) #define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests}; @@ -148,6 +149,7 @@ struct Fixture // Most often those are changes related to builtin type definitions. // In that case, flag can be forced to 'true' using the example below: // ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true}; + ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true}; // Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it. // This is useful for tracking down violations of Luau's memory model. diff --git a/tests/FragmentAutocomplete.test.cpp b/tests/FragmentAutocomplete.test.cpp index 11d4244c..20b337f7 100644 --- a/tests/FragmentAutocomplete.test.cpp +++ b/tests/FragmentAutocomplete.test.cpp @@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauBlockDiffFragmentSelection) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations) +LUAU_FASTFLAG(LuauPopulateRefinedTypesInFragmentFromOldSolver) static std::optional nullCallback(std::string tag, std::optional ptr, std::optional contents) { @@ -65,6 +66,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true}; ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true}; ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true}; + ScopedFastFlag luauPopulateRefinedTypesInFragmentFromOldSolver{FFlag::LuauPopulateRefinedTypesInFragmentFromOldSolver, true}; FragmentAutocompleteFixtureImpl() : BaseType(true) @@ -146,6 +148,37 @@ struct FragmentAutocompleteFixtureImpl : BaseType return Luau::tryFragmentAutocomplete(this->frontend, "MainModule", cursorPos, context, nullCallback); } + void autocompleteFragmentInNewSolver( + const std::string& document, + const std::string& updated, + Position cursorPos, + std::function assertions, + std::optional fragmentEndPosition = std::nullopt + ) + { + ScopedFastFlag sff{FFlag::LuauSolverV2, true}; + this->check(document, getOptions()); + + FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); + CHECK(result.status != FragmentAutocompleteStatus::InternalIce); + assertions(result); + } + + void autocompleteFragmentInOldSolver( + const std::string& document, + const std::string& updated, + Position cursorPos, + std::function assertions, + std::optional fragmentEndPosition = std::nullopt + ) + { + ScopedFastFlag sff{FFlag::LuauSolverV2, false}; + this->check(document, getOptions()); + + FragmentAutocompleteStatusResult result = autocompleteFragment(updated, cursorPos, fragmentEndPosition); + CHECK(result.status != FragmentAutocompleteStatus::InternalIce); + assertions(result); + } void autocompleteFragmentInBothSolvers( const std::string& document, @@ -3702,6 +3735,138 @@ end ); } +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_first_branch_of_union_old_solver") +{ + const std::string source = R"( +type Ok = { type: "ok", value: T} +type Err = { type : "err", error : E} +type Result = Ok | Err + +local result = {} :: Result + +if result.type == "ok" then + +end +)"; + + const std::string dest = R"( +type Ok = { type: "ok", value: T} +type Err = { type : "err", error : E} +type Result = Ok | Err + +local result = {} :: Result + +if result.type == "ok" then + result. +end +)"; + autocompleteFragmentInOldSolver(source, dest, Position{8, 11}, [](auto& result){ + REQUIRE(result.result); + CHECK_EQ(result.result->acResults.entryMap.count("type"), 1); + CHECK_EQ(result.result->acResults.entryMap.count("value"), 1); + }); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_old_solver") +{ + const std::string source = R"( +type Ok = { type: "ok", value: T} +type Err = { type : "err", error : E} +type Result = Ok | Err + +local result = {} :: Result + +if result.type == "err" then + +end +)"; + + const std::string dest = R"( +type Ok = { type: "ok", value: T} +type Err = { type : "err", error : E} +type Result = Ok | Err + +local result = {} :: Result + +if result.type == "err" then + result. +end +)"; + + autocompleteFragmentInOldSolver(source, dest, Position{8, 11}, [](auto& result){ + REQUIRE(result.result); + CHECK_EQ(result.result->acResults.entryMap.count("type"), 1); + CHECK_EQ(result.result->acResults.entryMap.count("error"), 1); + }); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_first_branch_of_union_new_solver" * doctest::skip(true)) +{ + // TODO: CLI-155619 - Fragment autocomplete needs to use stale refinement information for modules typechecked in the new solver as well + const std::string source = R"( +type Ok = { type: "ok", value: T} +type Err = { type : "err", error : E} +type Result = Ok | Err + +local result = {} :: Result + +if result.type == "ok" then + +end +)"; + + const std::string dest = R"( +type Ok = { type: "ok", value: T} +type Err = { type : "err", error : E} +type Result = Ok | Err + +local result = {} :: Result + +if result.type == "ok" then + result. +end +)"; + autocompleteFragmentInNewSolver(source, dest, Position{8, 11}, [](auto& result){ + REQUIRE(result.result); + CHECK_EQ(result.result->acResults.entryMap.count("type"), 1); + CHECK_EQ(result.result->acResults.entryMap.count("value"), 1); + }); +} + +TEST_CASE_FIXTURE(FragmentAutocompleteFixture, "tagged_union_completion_second_branch_of_union_new_solver" * doctest::skip(true)) +{ + // TODO: CLI-155619 - Fragment autocomplete needs to use stale refinement information for modules typechecked in the new solver as well + const std::string source = R"( +type Ok = { type: "ok", value: T} +type Err = { type : "err", error : E} +type Result = Ok | Err + +local result = {} :: Result + +if result.type == "err" then + +end +)"; + + const std::string dest = R"( +type Ok = { type: "ok", value: T} +type Err = { type : "err", error : E} +type Result = Ok | Err + +local result = {} :: Result + +if result.type == "err" then + result. +end +)"; + + autocompleteFragmentInNewSolver(source, dest, Position{8, 11}, [](auto& result){ + REQUIRE(result.result); + CHECK_EQ(result.result->acResults.entryMap.count("type"), 1); + CHECK_EQ(result.result->acResults.entryMap.count("error"), 1); + }); +} + // NOLINTEND(bugprone-unchecked-optional-access) TEST_SUITE_END(); diff --git a/tests/Frontend.test.cpp b/tests/Frontend.test.cpp index 788481b7..2b0a299d 100644 --- a/tests/Frontend.test.cpp +++ b/tests/Frontend.test.cpp @@ -897,7 +897,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f { CHECK_EQ( "Table type '{ count: string }' not compatible with type '{ Count: number }' because the former is missing field 'Count'", - toString(result.errors[0])); + toString(result.errors[0]) + ); } else REQUIRE_EQ( diff --git a/tests/Generalization.test.cpp b/tests/Generalization.test.cpp index efd3218e..019e5ce0 100644 --- a/tests/Generalization.test.cpp +++ b/tests/Generalization.test.cpp @@ -15,7 +15,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) @@ -115,7 +115,8 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "dont_traverse_into_class_types_when_ge { auto [propTy, _] = freshType(); - TypeId cursedExternType = arena.addType(ExternType{"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 genExternType = generalize(cursedExternType); REQUIRE(genExternType); @@ -226,7 +227,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "('a) -> 'a") TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})") { - ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true}; + ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true}; TableType tt; tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType}; @@ -260,7 +261,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: number | string)) -> string?") TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()") { - ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true}; + ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true}; auto [aTy, aFree] = freshType(); auto [bTy, bFree] = freshType(); @@ -341,10 +342,7 @@ end TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type") { - ScopedFastFlag sffs[] = { - {FFlag::DebugLuauForbidInternalTypes, true}, - {FFlag::LuauTrackInferredFunctionTypeFromCall, true} - }; + ScopedFastFlag sffs[] = {{FFlag::DebugLuauForbidInternalTypes, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}}; // This test case should just not assert CheckResult result = check(R"( diff --git a/tests/InferPolarity.test.cpp b/tests/InferPolarity.test.cpp index 84e0c2d3..d2b9cadc 100644 --- a/tests/InferPolarity.test.cpp +++ b/tests/InferPolarity.test.cpp @@ -8,13 +8,13 @@ using namespace Luau; -LUAU_FASTFLAG(LuauEagerGeneralization); +LUAU_FASTFLAG(LuauEagerGeneralization2); TEST_SUITE_BEGIN("InferPolarity"); TEST_CASE_FIXTURE(Fixture, "T where T = { m: (a) -> T }") { - ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true}; + ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true}; TypeArena arena; ScopePtr globalScope = std::make_shared(builtinTypes->anyTypePack); diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp index b24b6e70..b6156d03 100644 --- a/tests/Linter.test.cpp +++ b/tests/Linter.test.cpp @@ -10,7 +10,7 @@ LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LintRedundantNativeAttribute); LUAU_FASTFLAG(LuauDeprecatedAttribute); -LUAU_FASTFLAG(LuauEagerGeneralization); +LUAU_FASTFLAG(LuauEagerGeneralization2); using namespace Luau; @@ -1942,7 +1942,7 @@ print(foo:bar(2.0)) TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations") { // FIXME: For now this flag causes a stack overflow on Windows. - ScopedFastFlag _{FFlag::LuauEagerGeneralization, false}; + ScopedFastFlag _{FFlag::LuauEagerGeneralization2, false}; LintResult result = lint(R"( local t = {} diff --git a/tests/NonStrictTypeChecker.test.cpp b/tests/NonStrictTypeChecker.test.cpp index cfa4050f..3b911cc9 100644 --- a/tests/NonStrictTypeChecker.test.cpp +++ b/tests/NonStrictTypeChecker.test.cpp @@ -16,6 +16,7 @@ #include LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) +LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks) using namespace Luau; @@ -560,6 +561,18 @@ optionalArgsAtTheEnd1("a", nil, 3) LUAU_REQUIRE_NO_ERRORS(result); } +TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "generic_type_packs_in_non_strict") +{ + ScopedFastFlag sff{FFlag::LuauNewNonStrictFixGenericTypePacks, true}; + + CheckResult result = checkNonStrict(R"( + --!nonstrict + local test: (T...) -> () -- TypeError: Unknown type 'T' + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "optionals_in_checked_function_in_middle_cannot_be_omitted") { CheckResult result = checkNonStrict(R"( diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp index 408bf54b..40f01048 100644 --- a/tests/Normalize.test.cpp +++ b/tests/Normalize.test.cpp @@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) @@ -1206,7 +1206,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle") {FFlag::LuauNoMoreInjectiveTypeFunctions, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}, - {FFlag::LuauEagerGeneralization, true} + {FFlag::LuauEagerGeneralization2, true} }; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; @@ -1221,6 +1221,6 @@ _[_] ^= _(_(_)) InternalCompilerError ); } -#endif +#endif TEST_SUITE_END(); diff --git a/tests/Parser.test.cpp b/tests/Parser.test.cpp index f9cf3f13..411bebdc 100644 --- a/tests/Parser.test.cpp +++ b/tests/Parser.test.cpp @@ -2086,7 +2086,8 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations") declare extern type Bar extends Foo with prop2: string end - )").root; + )") + .root; REQUIRE_EQ(stat->body.size, 2); @@ -2194,7 +2195,8 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations") declare extern type Bar extends Foo with prop2: string end - )").root; + )") + .root; REQUIRE_EQ(stat->body.size, 2); @@ -2355,9 +2357,7 @@ TEST_CASE_FIXTURE(Fixture, "class_indexer") [number]: number end )", - (FFlag::LuauDeclareExternType) - ? "Cannot have more than one indexer on an extern type" - : "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); @@ -2904,10 +2904,13 @@ TEST_CASE_FIXTURE(Fixture, "for_loop_with_single_var_has_comma_positions_of_size ParseOptions parseOptions; parseOptions.storeCstData = true; - ParseResult result = parseEx(R"( + ParseResult result = parseEx( + R"( for value in tbl do end - )", parseOptions); + )", + parseOptions + ); REQUIRE(result.root); REQUIRE_EQ(1, result.root->body.size); @@ -4188,7 +4191,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type") CHECK_EQ(unionTy->types.size, 2); auto groupTy = unionTy->types.data[0]->as(); // (() -> ()) REQUIRE(groupTy); - CHECK(groupTy->type->is()); // () -> () + CHECK(groupTy->type->is()); // () -> () CHECK(unionTy->types.data[1]->is()); // ? } diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp index 4d0d625a..db0cd61e 100644 --- a/tests/Subtyping.test.cpp +++ b/tests/Subtyping.test.cpp @@ -17,7 +17,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) using namespace Luau; @@ -1629,19 +1629,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation") TypeId bTy = arena.addType(GenericType{"B"}); getMutable(bTy)->scope = moduleScope.get(); - TypeId genericFunctionTy = arena.addType(FunctionType{ - {aTy, bTy}, - {}, - arena.addTypePack({aTy, bTy}), - arena.addTypePack({join(meet(aTy, builtinTypes->truthyType), bTy)}) - }); + TypeId genericFunctionTy = + arena.addType(FunctionType{{aTy, bTy}, {}, arena.addTypePack({aTy, bTy}), arena.addTypePack({join(meet(aTy, builtinTypes->truthyType), bTy)})} + ); const TypeId truthyTy = builtinTypes->truthyType; - TypeId actualFunctionTy = fn( - {truthyTy, truthyTy}, - {join(meet(truthyTy, builtinTypes->truthyType), truthyTy)} - ); + TypeId actualFunctionTy = fn({truthyTy, truthyTy}, {join(meet(truthyTy, builtinTypes->truthyType), truthyTy)}); SubtypingResult result = isSubtype(genericFunctionTy, actualFunctionTy); @@ -1650,7 +1644,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation") TEST_CASE_FIXTURE(SubtypeFixture, "free_types_might_be_subtypes") { - ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true}; + ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true}; TypeId argTy = arena.freshType(builtinTypes, moduleScope.get()); FreeType* freeArg = getMutable(argTy); diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp index dfc93060..3553593e 100644 --- a/tests/ToString.test.cpp +++ b/tests/ToString.test.cpp @@ -878,7 +878,7 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array") TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") { - ScopedFastFlag _ {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; + ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; CheckResult result = check(R"( --!strict @@ -889,26 +889,26 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") std::string expected; if (FFlag::LuauSolverV2) - expected = "Type\n\t" - "'{ a: number, b: string, c: { d: string } }'\n" - "could not be converted into\n\t" - "'{ a: number, b: string, c: { d: number } }'; \n" - "this is because accessing `c.d` results in `string` in the former type and `number` in the latter " - "type, and `string` is not exactly `number`"; + expected = "Type\n\t" + "'{ a: number, b: string, c: { d: string } }'\n" + "could not be converted into\n\t" + "'{ a: number, b: string, c: { d: number } }'; \n" + "this is because accessing `c.d` results in `string` in the former type and `number` in the latter " + "type, and `string` is not exactly `number`"; else - expected = "Type\n\t" - "'{| a: number, b: string, c: {| d: string |} |}'\n" - "could not be converted into\n\t" - "'{| a: number, b: string, c: {| d: number |} |}'\n" - "caused by:\n " - "Property 'c' is not compatible.\n" - "Type\n\t" - "'{| d: string |}'\n" - "could not be converted into\n\t" - "'{| d: number |}'\n" - "caused by:\n " - "Property 'd' is not compatible.\n" - "Type 'string' could not be converted into 'number' in an invariant context"; + expected = "Type\n\t" + "'{| a: number, b: string, c: {| d: string |} |}'\n" + "could not be converted into\n\t" + "'{| a: number, b: string, c: {| d: number |} |}'\n" + "caused by:\n " + "Property 'c' is not compatible.\n" + "Type\n\t" + "'{| d: string |}'\n" + "could not be converted into\n\t" + "'{| d: number |}'\n" + "caused by:\n " + "Property 'd' is not compatible.\n" + "Type 'string' could not be converted into 'number' in an invariant context"; LUAU_REQUIRE_ERROR_COUNT(1, result); std::string actual = toString(result.errors[0]); diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 1eac0247..e383570b 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -14,7 +14,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) @@ -1725,7 +1725,7 @@ struct TFFixture TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; const ScopedFastFlag sff[1] = { - {FFlag::LuauEagerGeneralization, true}, + {FFlag::LuauEagerGeneralization2, true}, }; BuiltinTypeFunctions builtinTypeFunctions; @@ -1746,9 +1746,7 @@ TEST_CASE_FIXTURE(TFFixture, "refine") { TypeId g = arena->addType(GenericType{globalScope.get(), Polarity::Negative}); - TypeId refineTy = arena->addType(TypeFunctionInstanceType{ - builtinTypeFunctions.refineFunc, {g, builtinTypes->truthyType} - }); + TypeId refineTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.refineFunc, {g, builtinTypes->truthyType}}); FunctionGraphReductionResult res = reduceTypeFunctions(refineTy, Location{}, tfc); @@ -1764,9 +1762,7 @@ TEST_CASE_FIXTURE(TFFixture, "or<'a, 'b>") TypeId aType = arena->freshType(builtinTypes, globalScope.get()); TypeId bType = arena->freshType(builtinTypes, globalScope.get()); - TypeId orType = arena->addType(TypeFunctionInstanceType{ - builtinTypeFunctions.orFunc, {aType, bType} - }); + TypeId orType = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.orFunc, {aType, bType}}); FunctionGraphReductionResult res = reduceTypeFunctions(orType, Location{}, tfc); diff --git a/tests/TypeFunction.user.test.cpp b/tests/TypeFunction.user.test.cpp index e2df2c9b..366063f4 100644 --- a/tests/TypeFunction.user.test.cpp +++ b/tests/TypeFunction.user.test.cpp @@ -9,8 +9,9 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) +LUAU_FASTFLAG(LuauUserTypeFunctionAliases) TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); @@ -1987,7 +1988,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // FIXME: CLI-151985 // This test breaks because we can't see that eq is already fully reduced. @@ -2010,7 +2011,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string") { ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // FIXME: CLI-151985 // This test breaks because we can't see that eq is already fully reduced. @@ -2225,4 +2226,203 @@ TEST_CASE_FIXTURE(Fixture, "typeof_is_not_a_valid_type_function_name") CHECK("typeof cannot be used as an identifier for a type function or alias" == toString(result.errors[0])); } +TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_call") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + CheckResult result = check(R"( +type Test = T? + +type function foo(t) + return Test(t) +end + +local x: foo<{a: number}> = { a = 2 } +local y: foo<{b: number}> = { b = 2 } + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: number }?"); + CHECK(toString(requireType("y"), ToStringOptions{true}) == "{ b: number }?"); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_values") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + CheckResult result = check(R"( +type Test = { a: number } + +type function foo(t) + return types.unionof(Test, t) +end + +local x: foo = { a = 2 } +local y: foo = "a" + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK(toString(requireType("x"), ToStringOptions{true}) == "{ a: number }?"); + CHECK(toString(requireType("y"), ToStringOptions{true}) == "string | { a: number }"); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_call_with_reduction") +{ + if (!FFlag::LuauSolverV2) + return; + + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + CheckResult result = check(R"( +type Test = rawget + +type function foo(t) + return Test(t) +end + +local x: foo<{ a: number }> = 2 +local y: foo<{ a: string }> = "x" + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK(toString(requireType("x"), ToStringOptions{true}) == "number"); + CHECK(toString(requireType("y"), ToStringOptions{true}) == "string"); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_implicit_export") +{ + if (!FFlag::LuauSolverV2) + return; + + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + fileResolver.source["game/A"] = R"( +type Test = rawget + +export type function foo(t) + return Test(t) +end +local x: foo<{ a: number }> = 2 +return {} + )"; + + CheckResult aResult = frontend.check("game/A"); + LUAU_REQUIRE_NO_ERRORS(aResult); + + CHECK(toString(requireType("game/A", "x")) == R"(number)"); + + CheckResult bResult = check(R"( +local Test = require(game.A); +local y: Test.foo<{ a: string }> = "x" + )"); + LUAU_REQUIRE_NO_ERRORS(bResult); + + CHECK(toString(requireType("y")) == R"(string)"); +} + +TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_not_too_many_globals") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + CheckResult result = check(R"( +type function get() + return number +end +local function ok(idx: get<>): number return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(5, result); + CHECK(toString(result.errors[0]) == R"(Unknown global 'number')"); +} + +TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_not_enough_arguments") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + CheckResult result = check(R"( +type Test = (a: A, b: B) -> A + +type function get() + return Test(types.number) +end + +local function ok(idx: get<>): number return idx end + )"); + + LUAU_REQUIRE_ERROR_COUNT(4, result); + CHECK(toString(result.errors[0]) == R"('get' type function errored at runtime: [string "get"]:5: not enough arguments to call)"); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_can_call_packs") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + CheckResult result = check(R"( +type Test = (U...) -> T + +type function foo(t) + return Test(types.number, types.string, t) +end + +local x: foo + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK(toString(requireType("x"), ToStringOptions{true}) == "(string, boolean) -> number"); +} + +TEST_CASE_FIXTURE(ExternTypeFixture, "type_alias_reduction_errors") +{ + if (!FFlag::LuauSolverV2) + return; + + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + CheckResult result = check(R"( +type Test = setmetatable + +type function get() + return Test(types.number, types.string) +end + +local function ok(idx: get<>): number return idx end + )"); + + // TODO: type solving fails to complete in this test because of the blocked NameConstraint on the 'Test' alias + LUAU_REQUIRE_ERROR_COUNT(5, result); + CHECK( + toString(result.errors[1]) == + R"('get' type function errored at runtime: [string "get"]:5: failed to reduce type function with: Type function instance setmetatable is uninhabited)" + ); +} + +TEST_CASE_FIXTURE(BuiltinsFixture, "type_alias_unreferenced_do_not_block") +{ + ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; + ScopedFastFlag luauUserTypeFunctionAliases{FFlag::LuauUserTypeFunctionAliases, true}; + + CheckResult result = check(R"( +type function foo(t) + return types.unionof(types.number, t) +end + +type Test = foo + +local x: foo + )"); + + LUAU_REQUIRE_NO_ERRORS(result); + + CHECK(toString(requireType("x"), ToStringOptions{true}) == "boolean | number"); +} + TEST_SUITE_END(); diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp index 758d2b48..fe5178eb 100644 --- a/tests/TypeInfer.aliases.test.cpp +++ b/tests/TypeInfer.aliases.test.cpp @@ -10,7 +10,6 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2) LUAU_FASTFLAG(LuauSkipMalformedTypeAliases) @@ -1217,8 +1216,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "gh1632_no_infinite_recursion_in_normalizatio TEST_CASE_FIXTURE(Fixture, "exported_alias_location_is_accessible_on_module") { - ScopedFastFlag sff{FFlag::LuauRetainDefinitionAliasLocations, true}; - CheckResult result = check(R"( export type Value = string )"); @@ -1235,7 +1232,6 @@ TEST_CASE_FIXTURE(Fixture, "exported_type_function_location_is_accessible_on_mod { ScopedFastFlag flags[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauRetainDefinitionAliasLocations, true}, }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.anyerror.test.cpp b/tests/TypeInfer.anyerror.test.cpp index cff2e1f4..ceb5cdea 100644 --- a/tests/TypeInfer.anyerror.test.cpp +++ b/tests/TypeInfer.anyerror.test.cpp @@ -356,7 +356,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_comp LUAU_REQUIRE_NO_ERRORS(result); CHECK_EQ("any", toString(requireType("b"))); - } TEST_CASE_FIXTURE(Fixture, "call_to_any_yields_any") diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp index bc3988da..4688d122 100644 --- a/tests/TypeInfer.builtins.test.cpp +++ b/tests/TypeInfer.builtins.test.cpp @@ -11,7 +11,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauTableCloneClonesType3) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) TEST_SUITE_BEGIN("BuiltinTests"); @@ -146,20 +146,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate") )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(number, number) -> boolean'" - "\ncould not be converted into\n\t" - "'((string, string) -> boolean)?'" - "\ncaused by:\n" - " None of the union options are compatible. For example:\n" - "Type\n\t" - "'(number, number) -> boolean'" - "\ncould not be converted into\n\t" - "'(string, string) -> boolean'" - "\ncaused by:\n" - " Argument #1 type is not compatible.\n" - "Type 'string' could not be converted into 'number'"; + const std::string expected = "Type\n\t" + "'(number, number) -> boolean'" + "\ncould not be converted into\n\t" + "'((string, string) -> boolean)?'" + "\ncaused by:\n" + " None of the union options are compatible. For example:\n" + "Type\n\t" + "'(number, number) -> boolean'" + "\ncould not be converted into\n\t" + "'(string, string) -> boolean'" + "\ncaused by:\n" + " Argument #1 type is not compatible.\n" + "Type 'string' could not be converted into 'number'"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -460,7 +459,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce") )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization) + if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2) CHECK("{ [number]: string | string | string, n: number }" == toString(requireType("t"))); else if (FFlag::LuauSolverV2) CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t"))); diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp index 4c7dd2a1..5e790834 100644 --- a/tests/TypeInfer.classes.test.cpp +++ b/tests/TypeInfer.classes.test.cpp @@ -822,7 +822,8 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "cannot_index_a_class_with_no_indexer") LUAU_REQUIRE_ERROR_COUNT(1, result); CHECK_MESSAGE( - get(result.errors[0]), "Expected DynamicPropertyLookupOnExternTypesUnsafe but got " << result.errors[0] + get(result.errors[0]), + "Expected DynamicPropertyLookupOnExternTypesUnsafe but got " << result.errors[0] ); CHECK(builtinTypes->errorType == requireType("c")); diff --git a/tests/TypeInfer.functions.test.cpp b/tests/TypeInfer.functions.test.cpp index 06aa914d..a419e395 100644 --- a/tests/TypeInfer.functions.test.cpp +++ b/tests/TypeInfer.functions.test.cpp @@ -22,7 +22,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) @@ -1519,13 +1519,12 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(number, number) -> string'" - "\ncould not be converted into\n\t" - "'(number) -> string'" - "\ncaused by:\n" - " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; + const std::string expected = "Type\n\t" + "'(number, number) -> string'" + "\ncould not be converted into\n\t" + "'(number) -> string'" + "\ncaused by:\n" + " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1543,14 +1542,13 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(number, number) -> string'" - "\ncould not be converted into\n\t" - "'(number, string) -> string'" - "\ncaused by:\n" - " Argument #2 type is not compatible.\n" - "Type 'string' could not be converted into 'number'"; + const std::string expected = "Type\n\t" + "'(number, number) -> string'" + "\ncould not be converted into\n\t" + "'(number, string) -> string'" + "\ncaused by:\n" + " Argument #2 type is not compatible.\n" + "Type 'string' could not be converted into 'number'"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1568,13 +1566,12 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(number, number) -> number'" - "\ncould not be converted into\n\t" - "'(number, number) -> (number, boolean)'" - "\ncaused by:\n" - " Function only returns 1 value, but 2 are required here"; + const std::string expected = "Type\n\t" + "'(number, number) -> number'" + "\ncould not be converted into\n\t" + "'(number, number) -> (number, boolean)'" + "\ncaused by:\n" + " Function only returns 1 value, but 2 are required here"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1592,14 +1589,13 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(number, number) -> string'" - "\ncould not be converted into\n\t" - "'(number, number) -> number'" - "\ncaused by:\n" - " Return type is not compatible.\n" - "Type 'string' could not be converted into 'number'"; + const std::string expected = "Type\n\t" + "'(number, number) -> string'" + "\ncould not be converted into\n\t" + "'(number, number) -> number'" + "\ncaused by:\n" + " Return type is not compatible.\n" + "Type 'string' could not be converted into 'number'"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1617,14 +1613,13 @@ local b: B = a )"); LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(number, number) -> (number, string)'" - "\ncould not be converted into\n\t" - "'(number, number) -> (number, boolean)'" - "\ncaused by:\n" - " Return #2 type is not compatible.\n" - "Type 'string' could not be converted into 'boolean'"; + const std::string expected = "Type\n\t" + "'(number, number) -> (number, string)'" + "\ncould not be converted into\n\t" + "'(number, number) -> (number, boolean)'" + "\ncaused by:\n" + " Return #2 type is not compatible.\n" + "Type 'string' could not be converted into 'boolean'"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1688,7 +1683,7 @@ t.f = function(x) end )"); - if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) + if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) { // FIXME CLI-151985 LUAU_CHECK_ERROR_COUNT(3, result); @@ -1773,7 +1768,7 @@ t.f = function(x) end )"); - if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) + if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) { // FIXME CLI-151985 LUAU_CHECK_ERROR_COUNT(2, result); @@ -1950,10 +1945,7 @@ TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_unknown") TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_call_site") { - ScopedFastFlag sffs[] = { - {FFlag::LuauHasPropProperBlock, true}, - {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}}; CheckResult result = check(R"( local t = {} @@ -1975,7 +1967,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_ CHECK_EQ("(a) -> a", toString(requireType("f"))); - if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) + if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) { LUAU_CHECK_NO_ERRORS(result); CHECK("({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g"))); @@ -2940,7 +2932,7 @@ TEST_CASE_FIXTURE(Fixture, "unifier_should_not_bind_free_types") { // The new solver should ideally be able to do better here, but this is no worse than the old solver. - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { LUAU_REQUIRE_ERROR_COUNT(2, result); auto tm1 = get(result.errors[0]); diff --git a/tests/TypeInfer.intersectionTypes.test.cpp b/tests/TypeInfer.intersectionTypes.test.cpp index aee9b14a..e443d111 100644 --- a/tests/TypeInfer.intersectionTypes.test.cpp +++ b/tests/TypeInfer.intersectionTypes.test.cpp @@ -357,13 +357,12 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect") { LUAU_REQUIRE_ERROR_COUNT(4, result); - const std::string expected = - "Type\n\t" - "'(string, number) -> string'" - "\ncould not be converted into\n\t" - "'(string) -> string'\n" - "caused by:\n" - " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; + const std::string expected = "Type\n\t" + "'(string, number) -> string'" + "\ncould not be converted into\n\t" + "'(string) -> string'\n" + "caused by:\n" + " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'"); @@ -389,13 +388,12 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect") )"); LUAU_REQUIRE_ERROR_COUNT(4, result); - const std::string expected = - "Type\n\t" - "'(string, number) -> string'" - "\ncould not be converted into\n\t" - "'(string) -> string'\n" - "caused by:\n" - " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; + const std::string expected = "Type\n\t" + "'(string, number) -> string'" + "\ncould not be converted into\n\t" + "'(string) -> string'\n" + "caused by:\n" + " Argument count mismatch. Function expects 2 arguments, but only 1 is specified"; CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'"); @@ -428,15 +426,14 @@ local a: XYZ = 3 if (FFlag::LuauSolverV2) { - const std::string expected = - "Type " - "'number'" - " could not be converted into " - "'X & Y & Z'; \n" - "this is because \n\t" - " * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t" - " * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t" - " * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`"; + const std::string expected = "Type " + "'number'" + " could not be converted into " + "'X & Y & Z'; \n" + "this is because \n\t" + " * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t" + " * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t" + " * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -527,14 +524,13 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false") if (FFlag::LuauSolverV2) { - const std::string expected = - "Type " - "'boolean & false'" - " could not be converted into " - "'true'; \n" - "this is because \n\t" - " * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t" - " * the 2nd component of the intersection is `false`, which is not a subtype of `true`"; + const std::string expected = "Type " + "'boolean & false'" + " could not be converted into " + "'true'; \n" + "this is because \n\t" + " * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t" + " * the 2nd component of the intersection is `false`, which is not a subtype of `true`"; CHECK_EQ(expected, toString(result.errors[0])); } else @@ -557,15 +553,14 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false") // TODO: odd stringification of `false & (boolean & false)`.) if (FFlag::LuauSolverV2) { - const std::string expected = - "Type " - "'boolean & false & false'" - " could not be converted into " - "'true'; \n" - "this is because \n\t" - " * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t" - " * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t" - " * the 3rd component of the intersection is `false`, which is not a subtype of `true`"; + const std::string expected = "Type " + "'boolean & false & false'" + " could not be converted into " + "'true'; \n" + "this is because \n\t" + " * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t" + " * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t" + " * the 3rd component of the intersection is `false`, which is not a subtype of `true`"; CHECK_EQ(expected, toString(result.errors[0])); } else @@ -640,13 +635,11 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'((number) -> number) & ((string) -> string)'" - "\ncould not be converted into\n\t" - "'(boolean | number) -> boolean | number'; none of the intersection parts are compatible"; + const std::string expected = "Type\n\t" + "'((number) -> number) & ((string) -> string)'" + "\ncould not be converted into\n\t" + "'(boolean | number) -> boolean | number'; none of the intersection parts are compatible"; CHECK_EQ(expected, toString(result.errors[0])); - } TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") @@ -662,16 +655,15 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") if (FFlag::LuauSolverV2) { - const std::string expected = - "Type " - "'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'" - " could not be converted into " - "'{ p: nil }'; \n" - "this is because \n\t" - " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " - "accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t" - " * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and " - "accessing `p` results in `nil`, and `number` is not exactly `nil`"; + const std::string expected = "Type " + "'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'" + " could not be converted into " + "'{ p: nil }'; \n" + "this is because \n\t" + " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " + "accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t" + " * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and " + "accessing `p` results in `nil`, and `number` is not exactly `nil`"; CHECK_EQ(expected, toString(result.errors[0])); } else @@ -696,24 +688,23 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties") if (FFlag::LuauSolverV2) { - const std::string expected = - "Type\n\t" - "'{ p: number?, q: any } & { p: unknown, q: string? }'" - "\ncould not be converted into\n\t" - "'{ p: string?, q: number? }'; \n" - "this is because \n\t" - " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " - "accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t" - " * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st " - "component of the union as `string`, and `number?` is not exactly `string`\n\t" - " * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in " - "`number?`, and `any` is not exactly `number?`\n\t" - " * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in " - "`string?`, and `unknown` is not exactly `string?`\n\t" - " * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and " - "accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t" - " * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st " - "component of the union as `number`, and `string?` is not exactly `number`"; + const std::string expected = "Type\n\t" + "'{ p: number?, q: any } & { p: unknown, q: string? }'" + "\ncould not be converted into\n\t" + "'{ p: string?, q: number? }'; \n" + "this is because \n\t" + " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and " + "accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t" + " * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st " + "component of the union as `string`, and `number?` is not exactly `string`\n\t" + " * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in " + "`number?`, and `any` is not exactly `number?`\n\t" + " * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in " + "`string?`, and `unknown` is not exactly `string?`\n\t" + " * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and " + "accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t" + " * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st " + "component of the union as `number`, and `string?` is not exactly `number`"; CHECK_EQ(expected, toString(result.errors[0])); } else @@ -924,9 +915,9 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result") LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = "Type\n\t" - "'((nil) -> unknown) & ((number) -> number)'" - "\ncould not be converted into\n\t" - "'(number?) -> number?'; none of the intersection parts are compatible"; + "'((nil) -> unknown) & ((number) -> number)'" + "\ncould not be converted into\n\t" + "'(number?) -> number?'; none of the intersection parts are compatible"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -946,11 +937,10 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'((number) -> number?) & ((unknown) -> string?)'" - "\ncould not be converted into\n\t" - "'(number?) -> nil'; none of the intersection parts are compatible"; + const std::string expected = "Type\n\t" + "'((number) -> number?) & ((unknown) -> string?)'" + "\ncould not be converted into\n\t" + "'(number?) -> nil'; none of the intersection parts are compatible"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -1075,9 +1065,9 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_ LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expected = "Type\n\t" - "'((number?) -> (...number)) & ((string?) -> number | string)'" - "\ncould not be converted into\n\t" - "'(number | string) -> (number, number?)'; none of the intersection parts are compatible"; + "'((number?) -> (...number)) & ((string?) -> number | string)'" + "\ncould not be converted into\n\t" + "'(number | string) -> (number, number?)'; none of the intersection parts are compatible"; CHECK(expected == toString(result.errors[0])); } @@ -1172,16 +1162,15 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4") if (FFlag::LuauSolverV2) { - const std::string expected = - "Type\n\t" - "'((a...) -> ()) & ((number, a...) -> number)'" - "\ncould not be converted into\n\t" - "'((a...) -> ()) & ((number, a...) -> number)'; \n" - "this is because \n\t" - " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " - "the latter type, and `()` is not a subtype of `number`\n\t" - " * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of " - "the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`"; + const std::string expected = "Type\n\t" + "'((a...) -> ()) & ((number, a...) -> number)'" + "\ncould not be converted into\n\t" + "'((a...) -> ()) & ((number, a...) -> number)'; \n" + "this is because \n\t" + " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in " + "the latter type, and `()` is not a subtype of `number`\n\t" + " * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of " + "the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`"; CHECK(expected == toString(result.errors[0])); } else diff --git a/tests/TypeInfer.loops.test.cpp b/tests/TypeInfer.loops.test.cpp index 258d0dd3..604ec0d3 100644 --- a/tests/TypeInfer.loops.test.cpp +++ b/tests/TypeInfer.loops.test.cpp @@ -1519,7 +1519,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish") )")); CHECK_EQ("nil", toString(requireType("y"))); - } TEST_CASE_FIXTURE(Fixture, "ensure_local_in_loop_does_not_escape") @@ -1539,7 +1538,6 @@ TEST_CASE_FIXTURE(Fixture, "ensure_local_in_loop_does_not_escape") )")); CHECK_EQ("number", toString(requireType("y"))); - } TEST_SUITE_END(); diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp index 19a70513..cfad64d5 100644 --- a/tests/TypeInfer.modules.test.cpp +++ b/tests/TypeInfer.modules.test.cpp @@ -13,7 +13,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) @@ -467,10 +467,9 @@ local b: B.T = a if (FFlag::LuauSolverV2) { - const std::string expected = - "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n" - "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " - "`number` is not exactly `string`"; + const std::string expected = "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n" + "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " + "`number` is not exactly `string`"; CHECK(expected == toString(result.errors[0])); } else @@ -514,10 +513,9 @@ local b: B.T = a if (FFlag::LuauSolverV2) { - const std::string expected = - "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n" - "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " - "`number` is not exactly `string`"; + const std::string expected = "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n" + "this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " + "`number` is not exactly `string`"; CHECK(expected == toString(result.errors[0])); } else @@ -787,7 +785,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23}))); } diff --git a/tests/TypeInfer.operators.test.cpp b/tests/TypeInfer.operators.test.cpp index 0868edd9..b8d2eb85 100644 --- a/tests/TypeInfer.operators.test.cpp +++ b/tests/TypeInfer.operators.test.cpp @@ -17,7 +17,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) TEST_SUITE_BEGIN("TypeInferOperators"); @@ -29,7 +29,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types") )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // FIXME: Regression CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); @@ -51,7 +51,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_extras") )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // FIXME: Regression. CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); @@ -72,7 +72,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types_with_no_superfluous_union") )"); LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { // FIXME: Regression CHECK("(string & ~(false?)) | string" == toString(requireType("s"))); @@ -634,7 +634,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error") local a = -foo )"); - if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) + if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) { LUAU_REQUIRE_ERROR_COUNT(1, result); diff --git a/tests/TypeInfer.refinements.test.cpp b/tests/TypeInfer.refinements.test.cpp index bade0ce6..1234591f 100644 --- a/tests/TypeInfer.refinements.test.cpp +++ b/tests/TypeInfer.refinements.test.cpp @@ -10,7 +10,7 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(DebugLuauEqSatSimplification) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauWeakNilRefinementType) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) @@ -770,12 +770,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_ LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) { - CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" + CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil" CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil" - CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" + CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil" CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil" } else @@ -1273,7 +1273,10 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x") // time, sometimes this is due to hitting the simplifier rather than // normalization. CHECK("{ tag: \"exists\", x: string } & { x: ~(false?) }" == toString(requireTypeAtPosition({5, 28}))); - CHECK(R"(({ tag: "exists", x: string } & { x: false? }) | ({ tag: "missing", x: nil } & { x: false? }))" == toString(requireTypeAtPosition({7, 28}))); + CHECK( + R"(({ tag: "exists", x: string } & { x: false? }) | ({ tag: "missing", x: nil } & { x: false? }))" == + toString(requireTypeAtPosition({7, 28})) + ); } else { @@ -2514,7 +2517,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi end )")); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24}))); else CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); @@ -2636,10 +2639,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1687_equality_shouldnt_leak_nil") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauWeakNilRefinementType, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauWeakNilRefinementType, true}}; LUAU_REQUIRE_NO_ERRORS(check(R"( type Part = { @@ -2682,10 +2682,17 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function") { LUAU_REQUIRE_ERROR_COUNT(2, result); - CHECK_EQ(toString(result.errors[0]), "Key 'Disconnect' is missing from 't2 where t1 = ExternScriptConnection | t2 | { Disconnect: (t1) -> (...any) } ; t2 = { disconnect: (t1) -> (...any) }' in the type 't1 where t1 = ExternScriptConnection | { Disconnect: (t1) -> (...any) } | { disconnect: (t1) -> (...any) }'"); + CHECK_EQ( + toString(result.errors[0]), + "Key 'Disconnect' is missing from 't2 where t1 = ExternScriptConnection | t2 | { Disconnect: (t1) -> (...any) } ; t2 = { disconnect: " + "(t1) -> (...any) }' in the type 't1 where t1 = ExternScriptConnection | { Disconnect: (t1) -> (...any) } | { disconnect: (t1) -> " + "(...any) }'" + ); if (FFlag::LuauBetterCannotCallFunctionPrimitive) - CHECK_EQ(toString(result.errors[1]), "The type function is not precise enough for us to determine the appropriate result type of this call."); + CHECK_EQ( + toString(result.errors[1]), "The type function is not precise enough for us to determine the appropriate result type of this call." + ); else CHECK_EQ(toString(result.errors[1]), "Cannot call a value of type function"); } @@ -2694,7 +2701,9 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function") LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::LuauBetterCannotCallFunctionPrimitive) - CHECK_EQ(toString(result.errors[0]), "The type function is not precise enough for us to determine the appropriate result type of this call."); + CHECK_EQ( + toString(result.errors[0]), "The type function is not precise enough for us to determine the appropriate result type of this call." + ); else CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type function"); } diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp index f6d97217..1dfc8715 100644 --- a/tests/TypeInfer.singletons.test.cpp +++ b/tests/TypeInfer.singletons.test.cpp @@ -389,7 +389,8 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = R"(Table type '{ ["\n"]: number }' not compatible with type '{ ["<>"]: number }' because the former is missing field '<>')"; + const std::string expected = + R"(Table type '{ ["\n"]: number }' not compatible with type '{ ["<>"]: number }' because the former is missing field '<>')"; CHECK(expected == toString(result.errors[0])); } @@ -460,11 +461,10 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias") LUAU_REQUIRE_ERROR_COUNT(1, result); const std::string expectedError = "Type\n\t" - "'{ result: string, success: boolean }'" - "\ncould not be converted into\n\t" - "'Err | Ok'"; + "'{ result: string, success: boolean }'" + "\ncould not be converted into\n\t" + "'Err | Ok'"; CHECK(toString(result.errors[0]) == expectedError); - } TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options") diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp index 00e1e43a..1762a04e 100644 --- a/tests/TypeInfer.tables.test.cpp +++ b/tests/TypeInfer.tables.test.cpp @@ -21,8 +21,8 @@ LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) -LUAU_FASTFLAG(LuauEagerGeneralization) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) @@ -702,7 +702,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization) + if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2) CHECK("({a}) -> ()" == toString(requireType("swap"))); else if (FFlag::LuauSolverV2) CHECK("({unknown}) -> ()" == toString(requireType("swap"))); @@ -925,9 +925,9 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify") if (FFlag::LuauSolverV2) { - std::string expected = "Type '{number}' could not be converted into '{string}'; \n" - "this is because the result of indexing is `number` in the former type and `string` in the latter type, " - "and `number` is not exactly `string`"; + std::string expected = "Type '{number}' could not be converted into '{string}'; \n" + "this is because the result of indexing is `number` in the former type and `string` in the latter type, " + "and `number` is not exactly `string`"; auto actual = toString(result.errors[0]); CHECK_EQ(expected, actual); } @@ -2379,7 +2379,7 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table local c : string = t.m("hi") )"); - if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) + if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2) { // FIXME CLI-151985 LUAU_CHECK_ERROR_COUNT(2, result); @@ -3749,7 +3749,7 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_ { ScopedFastFlag sff[] = { {FFlag::LuauReportSubtypingErrors, true}, - {FFlag::LuauEagerGeneralization, true}, + {FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true} }; @@ -4298,7 +4298,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict") if (FFlag::LuauSolverV2) { - for (const auto& err: result.errors) + for (const auto& err : result.errors) { const auto* error = get(err); REQUIRE(error); @@ -4644,9 +4644,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties") return; ScopedFastFlag sff[] = { - {FFlag::LuauEagerGeneralization, true}, - {FFlag::LuauSubtypeGenericsAndNegations, true}, - {FFlag::LuauNoMoreInjectiveTypeFunctions, true} + {FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true} }; CheckResult result = check(R"( @@ -4698,7 +4696,7 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array") end )"); - if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization) + if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization2) { LUAU_CHECK_ERROR_COUNT(1, result); LUAU_CHECK_ERROR(result, NotATable); @@ -4746,7 +4744,7 @@ TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_ LUAU_REQUIRE_NO_ERRORS(result); // FIXME CLI-114134. We need to simplify types more consistently. - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f"))); else CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f"))); @@ -5088,8 +5086,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path") if (!FFlag::LuauSolverV2) return; - ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; - CheckResult result = check(R"( type self = {} & {} type Class = typeof(setmetatable()) @@ -5098,18 +5094,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path") end )"); - LUAU_REQUIRE_ERROR_COUNT(1, result); + LUAU_REQUIRE_ERROR_COUNT(3, result); + + // We shouldn't allow `setmetatable()` to type check + CHECK_EQ(result.errors[0].location, Location{{2, 21}, {2, 43}}); + CHECK_EQ("Type function instance setmetatable is uninhabited", toString(result.errors[0])); + + CHECK_EQ(result.errors[1].location, Location{{3, 8}, {5, 11}}); + CHECK_EQ("Type function instance setmetatable is uninhabited", toString(result.errors[1])); CHECK_EQ( - "Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; \n" - "this is because \n\t" - " * in the 1st entry in the type pack, the metatable portion is `{ }` in the former type and `nil` in the latter type, and `{ }` " - "is not a subtype of `nil`\n\t" - " * in the 1st entry in the type pack, the table portion has the 1st component of the intersection as `{ }` and in the 1st entry " - "in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`\n\t" - " * in the 1st entry in the type pack, the table portion has the 2nd component of the intersection as `{ }` and in the 1st entry " - "in the type pack, the table portion is `nil`, and `{ }` is not a subtype of `nil`", - toString(result.errors[0]) + "Type pack '{ @metatable { }, { } & { } }' could not be converted into 'setmetatable'; \n" + "this is because the 1st entry in the type pack is `{ @metatable { }, { } & { } }` and in the 1st entry in the type packreduces to " + "`never`, and `{ @metatable { }, { } & { } }` is not a subtype of `never`", + toString(result.errors[2]) ); } @@ -5399,10 +5397,7 @@ TEST_CASE_FIXTURE(Fixture, "returning_mismatched_optional_in_table") TEST_CASE_FIXTURE(Fixture, "optional_function_in_table") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeSpecificCheck, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}}; LUAU_CHECK_NO_ERRORS(check(R"( local t: { (() -> ())? } = { @@ -5551,10 +5546,7 @@ TEST_CASE_FIXTURE(Fixture, "deeply_nested_classish_inference") TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauTableLiteralSubtypeSpecificCheck, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}}; auto result = check(R"( type File = { @@ -5794,7 +5786,7 @@ TEST_CASE_FIXTURE(Fixture, "large_table_inference_does_not_bleed") local otherWords: { Word } = {"foo"} )"); LUAU_REQUIRE_ERROR_COUNT(3, result); - for (const auto& err: result.errors) + for (const auto& err : result.errors) // Check that all of the errors are localized to `words`, not `otherWords` CHECK(err.location.begin.line == 2); } @@ -5809,10 +5801,7 @@ TEST_CASE_FIXTURE(Fixture, "extremely_large_table" * doctest::timeout(2.0)) {FFlag::LuauDisablePrimitiveInferenceInLargeTables, true}, }; - const std::string source = - "local res = {\n" + - rep("\"foo\",\n", 100'000) + - "}"; + const std::string source = "local res = {\n" + rep("\"foo\",\n", 100'000) + "}"; LUAU_REQUIRE_NO_ERRORS(check(source)); CHECK_EQ("{string}", toString(requireType("res"), {true})); } diff --git a/tests/TypeInfer.test.cpp b/tests/TypeInfer.test.cpp index cb669b1a..be9b60e0 100644 --- a/tests/TypeInfer.test.cpp +++ b/tests/TypeInfer.test.cpp @@ -28,7 +28,7 @@ LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats) LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauStringPartLengthLimit) @@ -447,7 +447,7 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit") #endif ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100}; ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100}; - ScopedFastFlag _{FFlag::LuauEagerGeneralization, false}; + ScopedFastFlag _{FFlag::LuauEagerGeneralization2, false}; CheckResult result = check(R"(("foo"))" + rep(":lower()", limit)); @@ -1990,10 +1990,7 @@ TEST_CASE_FIXTURE(Fixture, "assert_allows_singleton_union_or_intersection") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_table_freeze_constraint_solving") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauMagicFreezeCheckBlocked2, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauMagicFreezeCheckBlocked2, true}}; LUAU_REQUIRE_NO_ERRORS(check(R"( local f = table.freeze f(table) @@ -2002,10 +1999,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_table_freeze_constraint_solving") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving") { - ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauMagicFreezeCheckBlocked2, true} - }; + ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauMagicFreezeCheckBlocked2, true}}; // This is the original fuzzer version of the above issue. CheckResult results = check(R"( local function l0() @@ -2041,7 +2035,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauHasPropProperBlock, true}, - {FFlag::LuauEagerGeneralization, true}, + {FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true}, @@ -2079,7 +2073,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2") { ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, - {FFlag::LuauEagerGeneralization, true}, + {FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true}, @@ -2116,7 +2110,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion") ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauHasPropProperBlock, true}, - {FFlag::LuauEagerGeneralization, true}, + {FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauStringPartLengthLimit, true}, {FFlag::LuauSimplificationRecheckAssumption, true}, diff --git a/tests/TypeInfer.typePacks.test.cpp b/tests/TypeInfer.typePacks.test.cpp index b52c9308..482e2b8d 100644 --- a/tests/TypeInfer.typePacks.test.cpp +++ b/tests/TypeInfer.typePacks.test.cpp @@ -12,7 +12,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauInstantiateInSubtyping) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) @@ -100,7 +100,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) CHECK_EQ("((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply"))); else CHECK_EQ("((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply"))); @@ -966,13 +966,12 @@ a = b if (FFlag::LuauSolverV2) { - const std::string expected = - "Type\n\t" - "'() -> (number, ...boolean)'" - "\ncould not be converted into\n\t" - "'() -> (number, ...string)'; \n" - "this is because it returns a tail of the variadic `boolean` in the former type and `string` in the latter " - "type, and `boolean` is not a subtype of `string`"; + const std::string expected = "Type\n\t" + "'() -> (number, ...boolean)'" + "\ncould not be converted into\n\t" + "'() -> (number, ...string)'; \n" + "this is because it returns a tail of the variadic `boolean` in the former type and `string` in the latter " + "type, and `boolean` is not a subtype of `string`"; CHECK(expected == toString(result.errors[0])); } diff --git a/tests/TypeInfer.typestates.test.cpp b/tests/TypeInfer.typestates.test.cpp index 90c6ca73..5f64ac00 100644 --- a/tests/TypeInfer.typestates.test.cpp +++ b/tests/TypeInfer.typestates.test.cpp @@ -8,7 +8,7 @@ LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) LUAU_FASTFLAG(LuauReportSubtypingErrors) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) @@ -418,7 +418,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur ScopedFastFlag sffs[] = { {FFlag::LuauSolverV2, true}, {FFlag::LuauReportSubtypingErrors, true}, - {FFlag::LuauEagerGeneralization, true}, + {FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true}, }; @@ -763,9 +763,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring") TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop") { ScopedFastFlag sffs[] = { - {FFlag::LuauSolverV2, true}, - {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, - {FFlag::LuauDfgAllowUpdatesInLoops,true} + {FFlag::LuauSolverV2, true}, {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, {FFlag::LuauDfgAllowUpdatesInLoops, true} }; CheckResult result = check(R"( diff --git a/tests/TypeInfer.unionTypes.test.cpp b/tests/TypeInfer.unionTypes.test.cpp index 2a2e5a3b..d007513e 100644 --- a/tests/TypeInfer.unionTypes.test.cpp +++ b/tests/TypeInfer.unionTypes.test.cpp @@ -10,7 +10,7 @@ using namespace Luau; LUAU_FASTFLAG(LuauSolverV2) -LUAU_FASTFLAG(LuauEagerGeneralization) +LUAU_FASTFLAG(LuauEagerGeneralization2) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) TEST_SUITE_BEGIN("UnionTypes"); @@ -675,11 +675,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect") LUAU_REQUIRE_ERROR_COUNT(1, result); // NOTE: union normalization will improve this message - const std::string expected = - "Type\n\t" - "'(string) -> number'" - "\ncould not be converted into\n\t" - "'((number) -> string) | ((number) -> string)'; none of the union options are compatible"; + const std::string expected = "Type\n\t" + "'(string) -> number'" + "\ncould not be converted into\n\t" + "'((number) -> string) | ((number) -> string)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -767,11 +766,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(number, a...) -> (number?, a...)'" - "\ncould not be converted into\n\t" - "'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible"; + const std::string expected = "Type\n\t" + "'(number, a...) -> (number?, a...)'" + "\ncould not be converted into\n\t" + "'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -788,11 +786,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(number) -> number?'" - "\ncould not be converted into\n\t" - "'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible"; + const std::string expected = "Type\n\t" + "'(number) -> number?'" + "\ncould not be converted into\n\t" + "'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -809,11 +806,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'() -> number | string'" - "\ncould not be converted into\n\t" - "'(() -> (string, string)) | (() -> number)'; none of the union options are compatible"; + const std::string expected = "Type\n\t" + "'() -> number | string'" + "\ncould not be converted into\n\t" + "'(() -> (string, string)) | (() -> number)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -830,11 +826,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics") LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'(...nil) -> (...number?)'" - "\ncould not be converted into\n\t" - "'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible"; + const std::string expected = "Type\n\t" + "'(...nil) -> (...number?)'" + "\ncould not be converted into\n\t" + "'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -850,11 +845,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics") LUAU_REQUIRE_ERROR_COUNT(1, result); if (FFlag::LuauSolverV2) { - const std::string expected = - "Type\n\t" - "'(number) -> ()'" - "\ncould not be converted into\n\t" - "'((...number?) -> ()) | ((number?) -> ())'"; + const std::string expected = "Type\n\t" + "'(number) -> ()'" + "\ncould not be converted into\n\t" + "'((...number?) -> ()) | ((number?) -> ())'"; CHECK(expected == toString(result.errors[0])); } else @@ -880,11 +874,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics LUAU_REQUIRE_ERROR_COUNT(1, result); - const std::string expected = - "Type\n\t" - "'() -> (number?, ...number)'" - "\ncould not be converted into\n\t" - "'(() -> (...number)) | (() -> number)'; none of the union options are compatible"; + const std::string expected = "Type\n\t" + "'() -> (number?, ...number)'" + "\ncould not be converted into\n\t" + "'(() -> (...number)) | (() -> number)'; none of the union options are compatible"; CHECK_EQ(expected, toString(result.errors[0])); } @@ -902,13 +895,14 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types") LUAU_REQUIRE_NO_ERRORS(result); - if (FFlag::LuauEagerGeneralization) + if (FFlag::LuauEagerGeneralization2) CHECK_EQ( "(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) ); else CHECK_EQ( - "(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) + "(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", + toString(requireType("f")) ); }