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>(T, MT) ->
setmetatable<T, MT>`.
- Make local type aliases available in type functions. For example:
```
type Foo = number
type Array<T> = {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 <afriesen@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Sora Kanosue <skanosue@roblox.com>
Co-authored-by: Talha Pathan <tpathan@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>

---------

Co-authored-by: Hunter Goldstein <hgoldstein@roblox.com>
Co-authored-by: Varun Saini <61795485+vrn-sn@users.noreply.github.com>
Co-authored-by: Menarul Alam <malam@roblox.com>
Co-authored-by: Aviral Goel <agoel@roblox.com>
Co-authored-by: Vighnesh <vvijay@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Ariel Weiss <aaronweiss@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
This commit is contained in:
ayoungbloodrbx 2025-05-30 11:17:49 -07:00 committed by GitHub
parent 5965818283
commit 92b0338400
Signed by: DevComp
GPG key ID: B5690EEEBB952194
82 changed files with 1979 additions and 881 deletions

View file

@ -10,12 +10,12 @@
#include "Luau/InsertionOrderedMap.h" #include "Luau/InsertionOrderedMap.h"
#include "Luau/Module.h" #include "Luau/Module.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Normalize.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Polarity.h" #include "Luau/Polarity.h"
#include "Luau/Refinement.h" #include "Luau/Refinement.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypeIds.h"
#include "Luau/TypeUtils.h" #include "Luau/TypeUtils.h"
#include <memory> #include <memory>
@ -93,7 +93,7 @@ struct ConstraintGenerator
std::vector<ConstraintPtr> constraints; std::vector<ConstraintPtr> constraints;
// The set of all free types introduced during constraint generation. // The set of all free types introduced during constraint generation.
DenseHashSet<TypeId> freeTypes{nullptr}; TypeIds freeTypes;
// Map a function's signature scope back to its signature type. // Map a function's signature scope back to its signature type.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr}; DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};
@ -176,7 +176,7 @@ private:
std::vector<TypeId> unionsToSimplify; std::vector<TypeId> unionsToSimplify;
// Used to keep track of when we are inside a large table and should // 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; size_t largeTableDepth = 0;
/** /**

View file

@ -3,7 +3,7 @@
#pragma once #pragma once
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/DenseHash.h" #include "Luau/TypeIds.h"
#include "Luau/Error.h" #include "Luau/Error.h"
#include <vector> #include <vector>
@ -18,7 +18,7 @@ struct ConstraintSet
std::vector<ConstraintPtr> constraints; std::vector<ConstraintPtr> constraints;
// The set of all free types created during constraint generation // The set of all free types created during constraint generation
DenseHashSet<TypeId> freeTypes{nullptr}; TypeIds freeTypes;
// Map a function's signature scope back to its signature type. Once we've // 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, // dispatched all of the constraints pertaining to a particular free type,
@ -29,4 +29,4 @@ struct ConstraintSet
std::vector<TypeError> errors; std::vector<TypeError> errors;
}; };
} } // namespace Luau

View file

@ -456,6 +456,14 @@ public:
*/ */
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints); std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints);
std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments(
TypeArena* arena,
NotNull<BuiltinTypes> builtinTypes,
const TypeFun& fn,
const std::vector<TypeId>& rawTypeArguments,
const std::vector<TypePackId>& rawPackArguments
);
void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts); void dump(NotNull<Scope> rootScope, struct ToStringOptions& opts);
} // namespace Luau } // namespace Luau

View file

@ -464,7 +464,10 @@ struct ReservedIdentifier
struct UnexpectedArrayLikeTableItem struct UnexpectedArrayLikeTableItem
{ {
bool operator==(const UnexpectedArrayLikeTableItem&) const { return true; } bool operator==(const UnexpectedArrayLikeTableItem&) const
{
return true;
}
}; };
using TypeErrorData = Variant< using TypeErrorData = Variant<

View file

@ -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<DenseHashMap<const AstExpr*, TypeId>> astTypes,
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,
NotNull<DenseHashMap<const AstType*, TypeId>> astResolvedTypes,
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> 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<DenseHashMap<const AstExpr*, TypeId>> astTypes;
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes;
NotNull<DenseHashMap<const AstType*, TypeId>> astResolvedTypes;
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtinTypes;
NotNull<Scope> rootScope;
void applyExpectedType(const TypeId expectedType, const AstExpr* expr);
};
} // namespace Luau

View file

@ -126,6 +126,9 @@ struct Module
// we need a sentinel value for the map. // we need a sentinel value for the map.
DenseHashMap<const AstNode*, Scope*> astScopes{nullptr}; DenseHashMap<const AstNode*, Scope*> astScopes{nullptr};
// Stable references for type aliases registered in the environment
std::vector<std::unique_ptr<TypeFun>> typeFunctionAliases;
std::unordered_map<Name, TypeId> declaredGlobals; std::unordered_map<Name, TypeId> declaredGlobals;
ErrorVec errors; ErrorVec errors;
LintResult lintResult; LintResult lintResult;

View file

@ -5,9 +5,9 @@
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Set.h" #include "Luau/Set.h"
#include "Luau/TypeFwd.h" #include "Luau/TypeFwd.h"
#include "Luau/TypeIds.h"
#include "Luau/UnifierSharedState.h" #include "Luau/UnifierSharedState.h"
#include <initializer_list>
#include <map> #include <map>
#include <memory> #include <memory>
#include <unordered_map> #include <unordered_map>
@ -39,57 +39,6 @@ bool isSubtype(
InternalErrorReporter& ice InternalErrorReporter& ice
); );
class TypeIds
{
private:
DenseHashMap<TypeId, bool> types{nullptr};
std::vector<TypeId> order;
std::size_t hash = 0;
public:
using iterator = std::vector<TypeId>::iterator;
using const_iterator = std::vector<TypeId>::const_iterator;
TypeIds() = default;
~TypeIds() = default;
TypeIds(std::initializer_list<TypeId> 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<class Iterator>
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 } // namespace Luau
template<> template<>
@ -302,7 +251,7 @@ struct NormalizedType
// we'd be reusing bad, stale data. // we'd be reusing bad, stale data.
bool isCacheable = true; bool isCacheable = true;
NormalizedType(NotNull<BuiltinTypes> builtinTypes); explicit NormalizedType(NotNull<BuiltinTypes> builtinTypes);
NormalizedType() = delete; NormalizedType() = delete;
~NormalizedType() = default; ~NormalizedType() = default;

View file

@ -101,6 +101,8 @@ struct Scope
std::optional<std::vector<TypeId>> interiorFreeTypes; std::optional<std::vector<TypeId>> interiorFreeTypes;
std::optional<std::vector<TypePackId>> interiorFreeTypePacks; std::optional<std::vector<TypePackId>> interiorFreeTypePacks;
NotNull<Scope> findNarrowestScopeContaining(Location);
}; };
// Returns true iff the left scope encloses the right scope. A Scope* equal to // Returns true iff the left scope encloses the right scope. A Scope* equal to

View file

@ -234,8 +234,14 @@ private:
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull<Scope> scope);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ExternType* subExternType, const ExternType* superExternType, NotNull<Scope> scope); SubtypingResult isCovariantWith(
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull<Scope>); SubtypingEnvironment& env,
const ExternType* subExternType,
const ExternType* superExternType,
NotNull<Scope> scope
);
SubtypingResult
isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ExternType* subExternType, TypeId superTy, const TableType* superTable, NotNull<Scope>);
SubtypingResult isCovariantWith( SubtypingResult isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const FunctionType* subFunction, const FunctionType* subFunction,
@ -267,7 +273,12 @@ private:
const NormalizedExternType& superExternType, const NormalizedExternType& superExternType,
NotNull<Scope> scope NotNull<Scope> scope
); );
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedExternType& subExternType, const TypeIds& superTables, NotNull<Scope> scope); SubtypingResult isCovariantWith(
SubtypingEnvironment& env,
const NormalizedExternType& subExternType,
const TypeIds& superTables,
NotNull<Scope> scope
);
SubtypingResult isCovariantWith( SubtypingResult isCovariantWith(
SubtypingEnvironment& env, SubtypingEnvironment& env,
const NormalizedStringType& subString, const NormalizedStringType& subString,

View file

@ -34,6 +34,7 @@ using ScopePtr = std::shared_ptr<Scope>;
struct Module; struct Module;
struct TypeFunction; struct TypeFunction;
struct TypeFun;
struct Constraint; struct Constraint;
struct Subtyping; struct Subtyping;
struct TypeChecker2; struct TypeChecker2;
@ -608,7 +609,8 @@ struct UserDefinedFunctionData
// References to AST elements are owned by the Module allocator which also stores this type // References to AST elements are owned by the Module allocator which also stores this type
AstStatTypeFunction* definition = nullptr; AstStatTypeFunction* definition = nullptr;
DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environment{""}; DenseHashMap<Name, std::pair<AstStatTypeFunction*, size_t>> environmentFunction{""};
DenseHashMap<Name, std::pair<TypeFun*, size_t>> environmentAlias{""};
}; };
/** /**

View file

@ -21,6 +21,8 @@ namespace Luau
struct TypeArena; struct TypeArena;
struct TxnLog; struct TxnLog;
struct ConstraintSolver; struct ConstraintSolver;
struct TypeFunctionRuntimeBuilderState;
struct TypeFunctionContext;
class Normalizer; class Normalizer;
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>; using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
@ -54,6 +56,9 @@ struct TypeFunctionRuntime
// Output created by 'print' function // Output created by 'print' function
std::vector<std::string> messages; std::vector<std::string> messages;
// Type builder, valid for the duration of a single evaluation
TypeFunctionRuntimeBuilderState* runtimeBuilder = nullptr;
private: private:
void prepareState(); void prepareState();
}; };

View file

@ -15,6 +15,8 @@ using lua_State = struct lua_State;
namespace Luau namespace Luau
{ {
struct TypeFunctionRuntime;
void* typeFunctionAlloc(void* ud, void* ptr, size_t osize, size_t nsize); void* typeFunctionAlloc(void* ud, void* ptr, size_t osize, size_t nsize);
// Replica of types from Type.h // Replica of types from Type.h
@ -274,6 +276,8 @@ T* getMutable(TypeFunctionTypeId tv)
std::optional<std::string> checkResultForError(lua_State* L, const char* typeFunctionName, int luaResult); std::optional<std::string> checkResultForError(lua_State* L, const char* typeFunctionName, int luaResult);
TypeFunctionRuntime* getTypeFunctionRuntime(lua_State* L);
TypeFunctionType* allocateTypeFunctionType(lua_State* L, TypeFunctionTypeVariant type); TypeFunctionType* allocateTypeFunctionType(lua_State* L, TypeFunctionTypeVariant type);
TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunctionTypePackVariant type); TypeFunctionTypePackVar* allocateTypeFunctionTypePack(lua_State* L, TypeFunctionTypePackVariant type);

View file

@ -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 <vector>
#include <initializer_list>
namespace Luau
{
/*
* An ordered, hashable set of TypeIds.
*/
class TypeIds
{
private:
DenseHashMap<TypeId, bool> types{nullptr};
std::vector<TypeId> order;
std::size_t hash = 0;
public:
using iterator = std::vector<TypeId>::iterator;
using const_iterator = std::vector<TypeId>::const_iterator;
TypeIds() = default;
~TypeIds() = default;
TypeIds(std::initializer_list<TypeId> 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<class Iterator>
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

View file

@ -301,4 +301,21 @@ bool fastIsSubtype(TypeId subTy, TypeId superTy);
*/ */
std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, TypeId exprType, NotNull<BuiltinTypes> builtinTypes); std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, TypeId exprType, NotNull<BuiltinTypes> 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 } // namespace Luau

View file

@ -173,12 +173,7 @@ static bool checkTypeMatch(
unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit; unifierState.counters.iterationLimit = FInt::LuauTypeInferIterationLimit;
Subtyping subtyping{ Subtyping subtyping{
builtinTypes, builtinTypes, NotNull{typeArena}, NotNull{simplifier.get()}, NotNull{&normalizer}, NotNull{&typeFunctionRuntime}, NotNull{&iceReporter}
NotNull{typeArena},
NotNull{simplifier.get()},
NotNull{&normalizer},
NotNull{&typeFunctionRuntime},
NotNull{&iceReporter}
}; };
return subtyping.isSubtype(subTy, superTy, scope).isSubtype; return subtyping.isSubtype(subTy, superTy, scope).isSubtype;

View file

@ -13,7 +13,6 @@
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/Subtyping.h" #include "Luau/Subtyping.h"
#include "Luau/Symbol.h" #include "Luau/Symbol.h"
#include "Luau/ToString.h"
#include "Luau/Type.h" #include "Luau/Type.h"
#include "Luau/TypeChecker2.h" #include "Luau/TypeChecker2.h"
#include "Luau/TypeFunction.h" #include "Luau/TypeFunction.h"
@ -30,10 +29,11 @@
*/ */
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3) LUAU_FASTFLAGVARIABLE(LuauTableCloneClonesType3)
LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAGVARIABLE(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition) LUAU_FASTFLAGVARIABLE(LuauFormatUseLastPosition)
LUAU_FASTFLAGVARIABLE(LuauUpdateSetMetatableTypeSignature)
namespace Luau namespace Luau
{ {
@ -310,8 +310,8 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeArena& arena = globals.globalTypes; TypeArena& arena = globals.globalTypes;
NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes; NotNull<BuiltinTypes> builtinTypes = globals.builtinTypes;
Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauEagerGeneralization Scope* globalScope = nullptr; // NotNull<Scope> when removing FFlag::LuauEagerGeneralization2
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
globalScope = globals.globalScope.get(); globalScope = globals.globalScope.get();
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -386,19 +386,30 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
TypeId genericT = arena.addType(GenericType{globalScope, "T"}); TypeId genericT = arena.addType(GenericType{globalScope, "T"});
TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT}); TypeId tMetaMT = arena.addType(MetatableType{genericT, genericMT});
// clang-format off if (FFlag::LuauUpdateSetMetatableTypeSignature)
// setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T } {
addGlobalBinding(globals, "setmetatable", // setmetatable<T: {}, MT>(T, MT) -> setmetatable<T, MT>
arena.addType( TypeId setmtReturn = arena.addType(TypeFunctionInstanceType{builtinTypeFunctions().setmetatableFunc, {genericT, genericMT}});
FunctionType{ addGlobalBinding(
{genericT, genericMT}, globals, "setmetatable", makeFunction(arena, std::nullopt, {genericT, genericMT}, {}, {genericT, genericMT}, {setmtReturn}), "@luau"
{}, );
arena.addTypePack(TypePack{{genericT, genericMT}}), }
arena.addTypePack(TypePack{{tMetaMT}}) else
} {
), "@luau" // clang-format off
); // setmetatable<T: {}, MT>(T, MT) -> { @metatable MT, T }
// clang-format on addGlobalBinding(globals, "setmetatable",
arena.addType(
FunctionType{
{genericT, genericMT},
{},
arena.addTypePack(TypePack{{genericT, genericMT}}),
arena.addTypePack(TypePack{{tMetaMT}})
}
), "@luau"
);
// clang-format on
}
} }
else else
{ {
@ -701,9 +712,10 @@ bool MagicFormat::typeCheck(const MagicFunctionTypeCheckContext& context)
{ {
TypeId actualTy = params[i + paramOffset]; TypeId actualTy = params[i + paramOffset];
TypeId expectedTy = expected[i]; TypeId expectedTy = expected[i];
Location location = FFlag::LuauFormatUseLastPosition Location location =
? context.callSite->args.data[std::min(context.callSite->args.size - 1, i + (calledWithSelf ? 0 : paramOffset))]->location FFlag::LuauFormatUseLastPosition
: context.callSite->args.data[i + (calledWithSelf ? 0 : paramOffset)]->location; ? 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 // use subtyping instead here
SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope); SubtypingResult result = context.typechecker->subtyping->isSubtype(actualTy, expectedTy, context.checkScope);

View file

@ -3,7 +3,7 @@
#include "Luau/Constraint.h" #include "Luau/Constraint.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
namespace Luau namespace Luau
{ {
@ -51,7 +51,7 @@ struct ReferenceCountInitializer : TypeOnceVisitor
bool visit(TypeId, const TypeFunctionInstanceType&) override bool visit(TypeId, const TypeFunctionInstanceType&) override
{ {
return FFlag::LuauEagerGeneralization && traverseIntoTypeFunctions; return FFlag::LuauEagerGeneralization2 && traverseIntoTypeFunctions;
} }
}; };
@ -104,7 +104,7 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
{ {
rci.traverse(fchc->argsPack); rci.traverse(fchc->argsPack);
} }
else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::LuauEagerGeneralization) else if (auto fcc = get<FunctionCallConstraint>(*this); fcc && FFlag::LuauEagerGeneralization2)
{ {
rci.traverseIntoTypeFunctions = false; rci.traverseIntoTypeFunctions = false;
rci.traverse(fcc->fn); rci.traverse(fcc->fn);
@ -118,12 +118,12 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
else if (auto hpc = get<HasPropConstraint>(*this)) else if (auto hpc = get<HasPropConstraint>(*this))
{ {
rci.traverse(hpc->resultType); rci.traverse(hpc->resultType);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
rci.traverse(hpc->subjectType); rci.traverse(hpc->subjectType);
} }
else if (auto hic = get<HasIndexerConstraint>(*this)) else if (auto hic = get<HasIndexerConstraint>(*this))
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
rci.traverse(hic->subjectType); rci.traverse(hic->subjectType);
rci.traverse(hic->resultType); rci.traverse(hic->resultType);
// `HasIndexerConstraint` should not mutate `indexType`. // `HasIndexerConstraint` should not mutate `indexType`.

View file

@ -11,6 +11,7 @@
#include "Luau/DenseHash.h" #include "Luau/DenseHash.h"
#include "Luau/InferPolarity.h" #include "Luau/InferPolarity.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Normalize.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
#include "Luau/RecursionCounter.h" #include "Luau/RecursionCounter.h"
#include "Luau/Refinement.h" #include "Luau/Refinement.h"
@ -33,11 +34,8 @@
LUAU_FASTINT(LuauCheckRecursionLimit) LUAU_FASTINT(LuauCheckRecursionLimit)
LUAU_FASTFLAG(DebugLuauLogSolverToJson) LUAU_FASTFLAG(DebugLuauLogSolverToJson)
LUAU_FASTFLAG(DebugLuauMagicTypes) LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType) LUAU_FASTFLAGVARIABLE(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
@ -51,6 +49,7 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables) LUAU_FASTFLAGVARIABLE(LuauDisablePrimitiveInferenceInLargeTables)
LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500) LUAU_FASTINTVARIABLE(LuauPrimitiveInferenceInTableLimit, 500)
LUAU_FASTFLAGVARIABLE(LuauUserTypeFunctionAliases)
namespace Luau namespace Luau
{ {
@ -180,6 +179,22 @@ bool hasFreeType(TypeId ty)
return hft.result; return hft.result;
} }
struct GlobalNameCollector : public AstVisitor
{
DenseHashSet<AstName> names;
GlobalNameCollector()
: names(AstName())
{
}
bool visit(AstExprGlobal* node) override
{
names.insert(node->name);
return true;
}
};
} // namespace } // namespace
ConstraintGenerator::ConstraintGenerator( ConstraintGenerator::ConstraintGenerator(
@ -220,26 +235,14 @@ ConstraintSet ConstraintGenerator::run(AstStatBlock* block)
{ {
visitModuleRoot(block); visitModuleRoot(block);
return ConstraintSet{ return ConstraintSet{NotNull{rootScope}, std::move(constraints), std::move(freeTypes), std::move(scopeToFunction), std::move(errors)};
NotNull{rootScope},
std::move(constraints),
std::move(freeTypes),
std::move(scopeToFunction),
std::move(errors)
};
} }
ConstraintSet ConstraintGenerator::runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block) ConstraintSet ConstraintGenerator::runOnFragment(const ScopePtr& resumeScope, AstStatBlock* block)
{ {
visitFragmentRoot(resumeScope, block); visitFragmentRoot(resumeScope, block);
return ConstraintSet{ return ConstraintSet{NotNull{rootScope}, std::move(constraints), std::move(freeTypes), std::move(scopeToFunction), std::move(errors)};
NotNull{rootScope},
std::move(constraints),
std::move(freeTypes),
std::move(scopeToFunction),
std::move(errors)
};
} }
void ConstraintGenerator::visitModuleRoot(AstStatBlock* block) void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
@ -254,7 +257,7 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
rootScope->location = block->location; rootScope->location = block->location;
module->astScopes[block] = NotNull{scope.get()}; module->astScopes[block] = NotNull{scope.get()};
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.emplace_back(); 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->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
scope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); 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(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); 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 // We prepopulate global data in the resumeScope to avoid writing data into the old modules scopes
prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block); prepopulateGlobalScopeForFragmentTypecheck(globalScope, resumeScope, block);
// Pre // Pre
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.emplace_back(); DEPRECATED_interiorTypes.emplace_back();
visitBlockWithoutChildScope(resumeScope, block); visitBlockWithoutChildScope(resumeScope, block);
// Post // Post
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -383,12 +386,12 @@ void ConstraintGenerator::visitFragmentRoot(const ScopePtr& resumeScope, AstStat
TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity) TypeId ConstraintGenerator::freshType(const ScopePtr& scope, Polarity polarity)
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity); auto ft = Luau::freshType(arena, builtinTypes, scope.get(), polarity);
interiorFreeTypes.back().types.push_back(ft); interiorFreeTypes.back().types.push_back(ft);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
freeTypes.insert(ft); freeTypes.insert(ft);
return ft; return ft;
@ -405,7 +408,7 @@ TypePackId ConstraintGenerator::freshTypePack(const ScopePtr& scope, Polarity po
{ {
FreeTypePack f{scope.get(), polarity}; FreeTypePack f{scope.get(), polarity};
TypePackId result = arena->addTypePack(TypePackVar{std::move(f)}); TypePackId result = arena->addTypePack(TypePackVar{std::move(f)});
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
interiorFreeTypes.back().typePacks.push_back(result); interiorFreeTypes.back().typePacks.push_back(result);
return result; return result;
} }
@ -818,8 +821,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
initialFun.typePackParams.push_back(genPack); initialFun.typePackParams.push_back(genPack);
} }
if (FFlag::LuauRetainDefinitionAliasLocations) initialFun.definitionLocation = alias->location;
initialFun.definitionLocation = alias->location;
if (alias->exported) if (alias->exported)
scope->exportedTypeBindings[alias->name.value] = std::move(initialFun); 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}; 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 // Set type bindings and definition locations for this user-defined type function
if (function->exported) if (function->exported)
@ -903,8 +904,7 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
TypeId initialType = arena->addType(BlockedType{}); TypeId initialType = arena->addType(BlockedType{});
TypeFun initialFun{initialType}; TypeFun initialFun{initialType};
if (FFlag::LuauRetainDefinitionAliasLocations) initialFun.definitionLocation = classDeclaration->location;
initialFun.definitionLocation = classDeclaration->location;
scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun); scope->exportedTypeBindings[classDeclaration->name.value] = std::move(initialFun);
classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location; classDefinitionLocations[classDeclaration->name.value] = classDeclaration->location;
@ -936,20 +936,24 @@ void ConstraintGenerator::checkAliases(const ScopePtr& scope, AstStatBlock* bloc
mainTypeFun = getMutable<TypeFunctionInstanceType>(it->second.type); mainTypeFun = getMutable<TypeFunctionInstanceType>(it->second.type);
} }
// Fill it with all visible type functions // Fill it with all visible type functions and referenced type aliases
if (mainTypeFun) if (mainTypeFun)
{ {
GlobalNameCollector globalNameCollector;
stat->visit(&globalNameCollector);
UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData; UserDefinedFunctionData& userFuncData = mainTypeFun->userFuncData;
size_t level = 0; 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)) if (auto ty = get<TypeFunctionInstanceType>(tf.type); ty && ty->userFuncData.definition)
return;
if (auto ty = get<TypeFunctionInstanceType>(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)) 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}; scope->bindings[ty->userFuncData.definition->name] = Binding{existing->typeId, ty->userFuncData.definition->location};
} }
} }
else if (FFlag::LuauUserTypeFunctionAliases && !get<TypeFunctionInstanceType>(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<TypeFun>(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) 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) for (auto& [name, tf] : curr->exportedTypeBindings)
addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf.type, level); addToEnvironment(userFuncData, typeFunctionEnvScope, name, tf, level);
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); FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
sig.bodyScope->bindings[function->name] = Binding{sig.signature, 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) if (sigFullyDefined)
emplaceType<BoundType>(asMutable(functionType), sig.signature); emplaceType<BoundType>(asMutable(functionType), sig.signature);
@ -1456,7 +1480,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
Checkpoint start = checkpoint(this); Checkpoint start = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location); 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); 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 // Place this function as a child of the non-type function scope
scope->children.push_back(NotNull{sig.signatureScope.get()}); scope->children.push_back(NotNull{sig.signatureScope.get()});
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{}); DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -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->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); 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()); sig.signatureScope->interiorFreeTypes = std::move(DEPRECATED_interiorTypes.back());
getMutable<BlockedType>(generalizedTy)->setOwner(gc); getMutable<BlockedType>(generalizedTy)->setOwner(gc);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
interiorFreeTypes.pop_back(); interiorFreeTypes.pop_back();
else else
DEPRECATED_interiorTypes.pop_back(); DEPRECATED_interiorTypes.pop_back();
@ -1884,7 +1908,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatDeclareExte
{ {
reportError( reportError(
declaredExternType->location, 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; return ControlFlow::None;
@ -2466,7 +2491,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantStrin
return Inference{builtinTypes->stringType}; return Inference{builtinTypes->stringType};
TypeId freeTy = nullptr; TypeId freeTy = nullptr;
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
freeTy = freshType(scope, Polarity::Positive); freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
@ -2507,7 +2532,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprConstantBool*
return Inference{builtinTypes->booleanType}; return Inference{builtinTypes->booleanType};
TypeId freeTy = nullptr; TypeId freeTy = nullptr;
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
freeTy = freshType(scope, Polarity::Positive); freeTy = freshType(scope, Polarity::Positive);
FreeType* ft = getMutable<FreeType>(freeTy); FreeType* ft = getMutable<FreeType>(freeTy);
@ -2605,9 +2630,7 @@ Inference ConstraintGenerator::checkIndexName(
{ {
result = arena->addType(BlockedType{}); result = arena->addType(BlockedType{});
auto c = addConstraint( auto c = addConstraint(scope, indexee->location, HasPropConstraint{result, obj, index, ValueContext::RValue, inConditional(typeContext)});
scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue, inConditional(typeContext)}
);
getMutable<BlockedType>(result)->setOwner(c); getMutable<BlockedType>(result)->setOwner(c);
} }
@ -2668,7 +2691,7 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
Checkpoint startCheckpoint = checkpoint(this); Checkpoint startCheckpoint = checkpoint(this);
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType); FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
interiorFreeTypes.emplace_back(); interiorFreeTypes.emplace_back();
else else
DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{}); DEPRECATED_interiorTypes.push_back(std::vector<TypeId>{});
@ -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->interiorFreeTypes = std::move(interiorFreeTypes.back().types);
sig.signatureScope->interiorFreeTypePacks = std::move(interiorFreeTypes.back().typePacks); 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->definitionLocation = expr->location;
ttv->scope = scope.get(); 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++; largeTableDepth++;
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
interiorFreeTypes.back().types.push_back(ty); interiorFreeTypes.back().types.push_back(ty);
else else
DEPRECATED_interiorTypes.back().push_back(ty); 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--; largeTableDepth--;
return Inference{ty}; return Inference{ty};
@ -3453,7 +3478,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
LUAU_ASSERT(nullptr != varargPack); LUAU_ASSERT(nullptr != varargPack);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// Some of the types in argTypes will eventually be generics, and some // Some of the types in argTypes will eventually be generics, and some
// will not. The ones that are not generic will be pruned when // will not. The ones that are not generic will be pruned when
@ -3518,7 +3543,7 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
if (expectedType && get<FreeType>(*expectedType)) if (expectedType && get<FreeType>(*expectedType))
bindFreeType(*expectedType, actualFunctionType); bindFreeType(*expectedType, actualFunctionType);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
scopeToFunction[signatureScope.get()] = actualFunctionType; scopeToFunction[signatureScope.get()] = actualFunctionType;
return { return {
@ -3809,33 +3834,33 @@ TypeId ConstraintGenerator::resolveType_(const ScopePtr& scope, AstType* ty, boo
} }
else if (auto unionAnnotation = ty->as<AstTypeUnion>()) else if (auto unionAnnotation = ty->as<AstTypeUnion>())
{ {
if (unionAnnotation->types.size == 1) if (unionAnnotation->types.size == 1)
result = resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments); result = resolveType_(scope, unionAnnotation->types.data[0], inTypeArguments);
else else
{
std::vector<TypeId> parts;
for (AstType* part : unionAnnotation->types)
{ {
std::vector<TypeId> parts; parts.push_back(resolveType_(scope, part, inTypeArguments));
for (AstType* part : unionAnnotation->types)
{
parts.push_back(resolveType_(scope, part, inTypeArguments));
}
result = arena->addType(UnionType{parts});
} }
result = arena->addType(UnionType{parts});
}
} }
else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>()) else if (auto intersectionAnnotation = ty->as<AstTypeIntersection>())
{ {
if (intersectionAnnotation->types.size == 1) if (intersectionAnnotation->types.size == 1)
result = resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments); result = resolveType_(scope, intersectionAnnotation->types.data[0], inTypeArguments);
else else
{
std::vector<TypeId> parts;
for (AstType* part : intersectionAnnotation->types)
{ {
std::vector<TypeId> parts; parts.push_back(resolveType_(scope, part, inTypeArguments));
for (AstType* part : intersectionAnnotation->types)
{
parts.push_back(resolveType_(scope, part, inTypeArguments));
}
result = arena->addType(IntersectionType{parts});
} }
result = arena->addType(IntersectionType{parts});
}
} }
else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>()) else if (auto typeGroupAnnotation = ty->as<AstTypeGroup>())
{ {

View file

@ -33,9 +33,8 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogBindings)
LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500) LUAU_FASTINTVARIABLE(LuauSolverRecursionLimit, 500)
LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification) LUAU_FASTFLAGVARIABLE(DebugLuauEqSatSimplification)
LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock) LUAU_FASTFLAGVARIABLE(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauDeprecatedAttribute) LUAU_FASTFLAG(LuauDeprecatedAttribute)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAGVARIABLE(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAGVARIABLE(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2) LUAU_FASTFLAGVARIABLE(LuauGuardAgainstMalformedTypeAliasExpansion2)
@ -43,6 +42,7 @@ LUAU_FASTFLAGVARIABLE(LuauInsertErrorTypesIntoIndexerResult)
LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAGVARIABLE(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
namespace Luau namespace Luau
{ {
@ -103,7 +103,7 @@ size_t HashBlockedConstraintId::operator()(const BlockedConstraintId& bci) const
return true; return true;
} }
static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments( std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments(
TypeArena* arena, TypeArena* arena,
NotNull<BuiltinTypes> builtinTypes, NotNull<BuiltinTypes> builtinTypes,
const TypeFun& fn, const TypeFun& fn,
@ -418,7 +418,7 @@ void ConstraintSolver::run()
} }
// Free types that have no constraints at all can be generalized right away. // Free types that have no constraints at all can be generalized right away.
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
for (TypeId ty : constraintSet.freeTypes) for (TypeId ty : constraintSet.freeTypes)
{ {
@ -479,7 +479,7 @@ void ConstraintSolver::run()
// expansion types, etc, so we need to follow it. // expansion types, etc, so we need to follow it.
ty = follow(ty); ty = follow(ty);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (seen.contains(ty)) if (seen.contains(ty))
continue; continue;
@ -498,7 +498,7 @@ void ConstraintSolver::run()
if (refCount <= 1) if (refCount <= 1)
unblock(ty, Location{}); unblock(ty, Location{});
if (FFlag::LuauEagerGeneralization && refCount == 0) if (FFlag::LuauEagerGeneralization2 && refCount == 0)
generalizeOneType(ty); generalizeOneType(ty);
} }
} }
@ -676,7 +676,7 @@ void ConstraintSolver::initFreeTypeTracking()
auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0); auto [refCount, _] = unresolvedConstraints.try_insert(ty, 0);
refCount += 1; refCount += 1;
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr); auto [it, fresh] = mutatedFreeTypeToConstraint.try_emplace(ty, nullptr);
it->second.insert(c.get()); it->second.insert(c.get());
@ -691,7 +691,7 @@ void ConstraintSolver::initFreeTypeTracking()
} }
// Also check flag integrity while we're here // Also check flag integrity while we're here
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
LUAU_ASSERT(FFlag::LuauSubtypeGenericsAndNegations); LUAU_ASSERT(FFlag::LuauSubtypeGenericsAndNegations);
LUAU_ASSERT(FFlag::LuauNoMoreInjectiveTypeFunctions); LUAU_ASSERT(FFlag::LuauNoMoreInjectiveTypeFunctions);
@ -739,7 +739,7 @@ void ConstraintSolver::bind(NotNull<const Constraint> constraint, TypeId ty, Typ
constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed constraint, ty, constraint->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed
); // FIXME? Is this the right polarity? ); // FIXME? Is this the right polarity?
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
trackInteriorFreeType(constraint->scope, ty); trackInteriorFreeType(constraint->scope, ty);
return; return;
@ -900,7 +900,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
{ {
for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access) for (TypeId ty : *constraint->scope->interiorFreeTypes) // NOLINT(bugprone-unchecked-optional-access)
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
ty = follow(ty); ty = follow(ty);
if (auto freeTy = get<FreeType>(ty)) if (auto freeTy = get<FreeType>(ty))
@ -922,7 +922,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
} }
} }
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (constraint->scope->interiorFreeTypePacks) if (constraint->scope->interiorFreeTypePacks)
{ {
@ -942,7 +942,7 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
} }
} }
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (c.noGenerics) if (c.noGenerics)
{ {
@ -1378,7 +1378,6 @@ void ConstraintSolver::fillInDiscriminantTypes(NotNull<const Constraint> constra
// We also need to unconditionally unblock these types, otherwise // We also need to unconditionally unblock these types, otherwise
// you end up with funky looking "Blocked on *no-refine*." // you end up with funky looking "Blocked on *no-refine*."
unblock(*ty, constraint->location); unblock(*ty, constraint->location);
} }
} }
@ -1388,7 +1387,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
TypePackId argsPack = follow(c.argsPack); TypePackId argsPack = follow(c.argsPack);
TypePackId result = follow(c.result); TypePackId result = follow(c.result);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (isBlocked(fn)) if (isBlocked(fn))
return block(c.fn, constraint); return block(c.fn, constraint);
@ -1528,7 +1527,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy); const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
for (TypeId freeTy : u2.newFreshTypes) for (TypeId freeTy : u2.newFreshTypes)
trackInteriorFreeType(constraint->scope, freeTy); trackInteriorFreeType(constraint->scope, freeTy);
@ -1942,7 +1941,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(
TypeId upperBound = TypeId upperBound =
arena->addType(TableType{/* props */ {}, TableIndexer{indexType, resultType}, TypeLevel{}, ft->scope, TableState::Unsealed}); 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)); 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}; FreeType freeResult{tt->scope, builtinTypes->neverType, builtinTypes->unknownType, Polarity::Mixed};
emplace<FreeType>(constraint, resultType, freeResult); emplace<FreeType>(constraint, resultType, freeResult);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
trackInteriorFreeType(constraint->scope, resultType); trackInteriorFreeType(constraint->scope, resultType);
tt->indexer = TableIndexer{indexType, resultType}; tt->indexer = TableIndexer{indexType, resultType};
@ -2056,7 +2055,6 @@ bool ConstraintSolver::tryDispatchHasIndexer(
r = follow(r); r = follow(r);
if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get<ErrorType>(r)) if (FFlag::LuauInsertErrorTypesIntoIndexerResult || !get<ErrorType>(r))
results.insert(r); results.insert(r);
} }
if (0 == results.size()) if (0 == results.size())
@ -2163,7 +2161,7 @@ bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const
{ {
auto lhsFreeUpperBound = follow(lhsFree->upperBound); auto lhsFreeUpperBound = follow(lhsFree->upperBound);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue); const auto [blocked, maybeTy, isIndex] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
if (!blocked.empty()) if (!blocked.empty())
@ -3064,7 +3062,7 @@ TablePropLookupResult ConstraintSolver::lookupTableProp(
{ {
const TypeId upperBound = follow(ft->upperBound); const TypeId upperBound = follow(ft->upperBound);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound)) if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
{ {
@ -3532,7 +3530,7 @@ void ConstraintSolver::shiftReferences(TypeId source, TypeId target)
// Any constraint that might have mutated source may now mutate target // Any constraint that might have mutated source may now mutate target
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
auto it = mutatedFreeTypeToConstraint.find(source); auto it = mutatedFreeTypeToConstraint.find(source);
if (it != mutatedFreeTypeToConstraint.end()) if (it != mutatedFreeTypeToConstraint.end())

View file

@ -18,7 +18,7 @@
#include <unordered_set> #include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10) LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive) LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive)
@ -663,7 +663,7 @@ struct ErrorConverter
} }
// binary operators // 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()) 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 "; 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" + 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. // Everything should be specialized above to report a more descriptive error that hopefully does not mention "type functions" explicitly.

View file

@ -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<DenseHashMap<const AstExpr*, TypeId>> astTypes,
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,
NotNull<DenseHashMap<const AstType*, TypeId>> astResolvedTypes,
NotNull<TypeArena> arena,
NotNull<BuiltinTypes> builtinTypes,
NotNull<Scope> 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<FunctionType>(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<AstExprTable>())
{
const auto expectedTableType = get<TableType>(expectedType);
if (!expectedTableType)
{
if (auto utv = get<UnionType>(expectedType))
{
if (auto exprType = astTypes->find(expr))
{
std::vector<TypeId> 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<TypeId> 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<char>& s = item.key->as<AstExprConstantString>()->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<AstExprGroup>())
{
applyExpectedType(expectedType, group->expr);
}
else if (auto ternary = expr->as<AstExprIfElse>())
{
applyExpectedType(expectedType, ternary->trueExpr);
applyExpectedType(expectedType, ternary->falseExpr);
}
}
} // namespace Luau

View file

@ -6,6 +6,7 @@
#include "Luau/Autocomplete.h" #include "Luau/Autocomplete.h"
#include "Luau/Common.h" #include "Luau/Common.h"
#include "Luau/EqSatSimplification.h" #include "Luau/EqSatSimplification.h"
#include "Luau/ExpectedTypeVisitor.h"
#include "Luau/ModuleResolver.h" #include "Luau/ModuleResolver.h"
#include "Luau/Parser.h" #include "Luau/Parser.h"
#include "Luau/ParseOptions.h" #include "Luau/ParseOptions.h"
@ -35,6 +36,8 @@ LUAU_FASTFLAGVARIABLE(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAGVARIABLE(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations) LUAU_FASTFLAGVARIABLE(LuauFragmentAutocompleteIfRecommendations)
LUAU_FASTFLAG(LuauExpectedTypeVisitor)
LUAU_FASTFLAGVARIABLE(LuauPopulateRefinedTypesInFragmentFromOldSolver)
namespace Luau namespace Luau
{ {
@ -669,6 +672,7 @@ void cloneTypesFromFragment(
destScope->lvalueTypes[d] = Luau::cloneIncremental(*lValue, *destArena, cloneState, destScope); destScope->lvalueTypes[d] = Luau::cloneIncremental(*lValue, *destArena, cloneState, destScope);
} }
} }
for (const auto& [d, loc] : f.localBindingsReferenced) for (const auto& [d, loc] : f.localBindingsReferenced)
{ {
if (std::optional<std::pair<Symbol, Binding>> pair = staleScope->linearSearchForBindingPair(loc->name.value, true)) if (std::optional<std::pair<Symbol, Binding>> 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. // 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 // If the actual type alias appears in the fragment on the lhs as a definition (in declaredAliases), it will be processed during typechecking
// anyway // anyway
@ -1191,6 +1210,19 @@ FragmentTypeCheckResult typecheckFragment_(
reportWaypoint(reporter, FragmentAutocompleteWaypoint::ConstraintSolverEnd); 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 // In frontend we would forbid internal types
// because this is just for autocomplete, we don't actually care // 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 // We also don't even need to typecheck - just synthesize types as best as we can
@ -1243,7 +1275,8 @@ std::pair<FragmentTypeCheckStatus, FragmentTypeCheckResult> typecheckFragment(
FrontendOptions frontendOptions = opts.value_or(frontend.options); FrontendOptions frontendOptions = opts.value_or(frontend.options);
const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos) const ScopePtr& closestScope = FFlag::LuauBetterScopeSelection ? findClosestScope(module, parseResult.scopePos)
: findClosestScope_DEPRECATED(module, parseResult.nearestStatement); : 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); result.ancestry = std::move(parseResult.ancestry);
reportFragmentString(reporter, tryParse->fragmentToParse); reportFragmentString(reporter, tryParse->fragmentToParse);
return {FragmentTypeCheckStatus::Success, result}; return {FragmentTypeCheckStatus::Success, result};

View file

@ -10,6 +10,7 @@
#include "Luau/DataFlowGraph.h" #include "Luau/DataFlowGraph.h"
#include "Luau/DcrLogger.h" #include "Luau/DcrLogger.h"
#include "Luau/EqSatSimplification.h" #include "Luau/EqSatSimplification.h"
#include "Luau/ExpectedTypeVisitor.h"
#include "Luau/FileResolver.h" #include "Luau/FileResolver.h"
#include "Luau/NonStrictTypeChecker.h" #include "Luau/NonStrictTypeChecker.h"
#include "Luau/NotNull.h" #include "Luau/NotNull.h"
@ -39,13 +40,14 @@ LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(LuauInferInNoCheckMode) LUAU_FASTFLAG(LuauInferInNoCheckMode)
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3) LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile) LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes) LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes)
LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceStrictMode)
LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode) LUAU_FASTFLAGVARIABLE(DebugLuauForceNonStrictMode)
LUAU_FASTFLAGVARIABLE(LuauNewSolverTypecheckCatchTimeouts) LUAU_FASTFLAGVARIABLE(LuauNewSolverTypecheckCatchTimeouts)
LUAU_FASTFLAGVARIABLE(LuauExpectedTypeVisitor)
namespace Luau namespace Luau
{ {
@ -1436,13 +1438,13 @@ ModulePtr check(
requireCycles 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 // This optional<> only exists so that we can run one constructor when the flag
// is set, and another when it is unset. // is set, and another when it is unset.
std::optional<ConstraintSolver> cs; std::optional<ConstraintSolver> cs;
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
ConstraintSet constraintSet = cg.run(sourceModule.root); ConstraintSet constraintSet = cg.run(sourceModule.root);
result->errors = std::move(constraintSet.errors); 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); unfreeze(result->interfaceTypes);
result->clonePublicInterface(builtinTypes, *iceHandler); result->clonePublicInterface(builtinTypes, *iceHandler);

View file

@ -16,7 +16,7 @@
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization) LUAU_FASTFLAGVARIABLE(LuauEagerGeneralization2)
namespace Luau namespace Luau
{ {
@ -469,7 +469,7 @@ struct FreeTypeSearcher : TypeVisitor
bool visit(TypeId ty, const FreeType& ft) override bool visit(TypeId ty, const FreeType& ft) override
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (!subsumes(scope, ft.scope)) if (!subsumes(scope, ft.scope))
return true; return true;
@ -520,7 +520,7 @@ struct FreeTypeSearcher : TypeVisitor
if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope)) if ((tt.state == TableState::Free || tt.state == TableState::Unsealed) && subsumes(scope, tt.scope))
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
unsealedTables.insert(ty); unsealedTables.insert(ty);
else else
{ {
@ -574,7 +574,6 @@ struct FreeTypeSearcher : TypeVisitor
traverse(*prop.writeTy); traverse(*prop.writeTy);
polarity = p; polarity = p;
} }
} }
else else
{ {
@ -594,7 +593,7 @@ struct FreeTypeSearcher : TypeVisitor
if (tt.indexer) if (tt.indexer)
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// {[K]: V} is equivalent to three functions: get, set, and iterate // {[K]: V} is equivalent to three functions: get, set, and iterate
// //
@ -652,7 +651,7 @@ struct FreeTypeSearcher : TypeVisitor
if (!subsumes(scope, ftp.scope)) if (!subsumes(scope, ftp.scope))
return true; return true;
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
GeneralizationParams<TypePackId>& params = typePacks[tp]; GeneralizationParams<TypePackId>& params = typePacks[tp];
++params.useCount; ++params.useCount;
@ -1247,8 +1246,7 @@ struct RemoveType : Substitution // NOLINT
* @param needle The type to be removed. * @param needle The type to be removed.
*/ */
[[nodiscard]] [[nodiscard]]
static std::optional< static std::optional<TypeId> removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
TypeId> removeType(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, TypeId haystack, TypeId needle)
{ {
RemoveType rt{builtinTypes, arena, needle}; RemoveType rt{builtinTypes, arena, needle};
return rt.substitute(haystack); return rt.substitute(haystack);
@ -1276,7 +1274,7 @@ GeneralizationResult<TypeId> generalizeType(
if (!hasLowerBound && !hasUpperBound) 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<BoundType>(asMutable(freeTy), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else else
{ {
@ -1308,7 +1306,7 @@ GeneralizationResult<TypeId> generalizeType(
if (follow(lb) != freeTy) if (follow(lb) != freeTy)
emplaceType<BoundType>(asMutable(freeTy), lb); emplaceType<BoundType>(asMutable(freeTy), lb);
else if (!isWithinFunction || (!FFlag::LuauEagerGeneralization && params.useCount == 1)) else if (!isWithinFunction || (!FFlag::LuauEagerGeneralization2 && params.useCount == 1))
emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType); emplaceType<BoundType>(asMutable(freeTy), builtinTypes->unknownType);
else else
{ {
@ -1425,7 +1423,7 @@ std::optional<TypeId> generalize(
FreeTypeSearcher fts{scope, cachedTypes}; FreeTypeSearcher fts{scope, cachedTypes};
fts.traverse(ty); fts.traverse(ty);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
FunctionType* functionTy = getMutable<FunctionType>(ty); FunctionType* functionTy = getMutable<FunctionType>(ty);
auto pushGeneric = [&](TypeId t) auto pushGeneric = [&](TypeId t)
@ -1586,7 +1584,6 @@ struct GenericCounter : TypeVisitor
traverse(*prop.writeTy); traverse(*prop.writeTy);
polarity = p; polarity = p;
} }
} }
else else
{ {
@ -1653,7 +1650,7 @@ void pruneUnnecessaryGenerics(
TypeId ty TypeId ty
) )
{ {
if (!FFlag::LuauEagerGeneralization) if (!FFlag::LuauEagerGeneralization2)
return; return;
ty = follow(ty); ty = follow(ty);

View file

@ -5,7 +5,7 @@
#include "Luau/Scope.h" #include "Luau/Scope.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
namespace Luau namespace Luau
{ {
@ -133,7 +133,7 @@ struct InferPolarity : TypeVisitor
template<typename TID> template<typename TID>
static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty) static void inferGenericPolarities_(NotNull<TypeArena> arena, NotNull<Scope> scope, TID ty)
{ {
if (!FFlag::LuauEagerGeneralization) if (!FFlag::LuauEagerGeneralization2)
return; return;
InferPolarity infer{arena, scope}; InferPolarity infer{arena, scope};

View file

@ -2294,36 +2294,36 @@ private:
bool visit(AstExprLocal* node) override bool visit(AstExprLocal* node) override
{ {
const FunctionType* fty = getFunctionType(node); const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport) if (shouldReport)
report(node->location, node->local->name.value); report(node->location, node->local->name.value);
return true; return true;
} }
bool visit(AstExprGlobal* node) override bool visit(AstExprGlobal* node) override
{ {
const FunctionType* fty = getFunctionType(node); const FunctionType* fty = getFunctionType(node);
bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty); bool shouldReport = fty && fty->isDeprecatedFunction && !inScope(fty);
if (shouldReport) if (shouldReport)
report(node->location, node->name.value); report(node->location, node->name.value);
return true; return true;
} }
bool visit(AstStatLocalFunction* node) override bool visit(AstStatLocalFunction* node) override
{ {
check(node->func); check(node->func);
return false; return false;
} }
bool visit(AstStatFunction* node) override bool visit(AstStatFunction* node) override
{ {
check(node->func); check(node->func);
return false; return false;
} }
bool visit(AstExprIndexName* node) override bool visit(AstExprIndexName* node) override

View file

@ -15,7 +15,7 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations) LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
namespace Luau namespace Luau
{ {
@ -265,10 +265,7 @@ struct ClonePublicInterface : Substitution
TypeId type = cloneType(tf.type); TypeId type = cloneType(tf.type);
if (FFlag::LuauRetainDefinitionAliasLocations) return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
return TypeFun{typeParams, typePackParams, type, tf.definitionLocation};
else
return TypeFun{typeParams, typePackParams, type};
} }
}; };
@ -309,6 +306,14 @@ void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalEr
ty = clonePublicInterface.cloneType(ty); ty = clonePublicInterface.cloneType(ty);
} }
if (FFlag::LuauUserTypeFunctionAliases)
{
for (auto& tf : typeFunctionAliases)
{
*tf = clonePublicInterface.cloneTypeFun(*tf);
}
}
// Copy external stuff over to Module itself // Copy external stuff over to Module itself
this->returnType = moduleScope->returnType; this->returnType = moduleScope->returnType;
this->exportedTypeBindings = moduleScope->exportedTypeBindings; this->exportedTypeBindings = moduleScope->exportedTypeBindings;

View file

@ -24,6 +24,7 @@ LUAU_FASTFLAG(DebugLuauMagicTypes)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAGVARIABLE(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAGVARIABLE(LuauNewNonStrictFixGenericTypePacks)
namespace Luau namespace Luau
{ {
@ -1091,23 +1092,42 @@ struct NonStrictTypeChecker
Scope* scope = findInnermostScope(tp->location); Scope* scope = findInnermostScope(tp->location);
LUAU_ASSERT(scope); LUAU_ASSERT(scope);
std::optional<TypePackId> alias = scope->lookupPack(tp->genericName.value); if (FFlag::LuauNewNonStrictFixGenericTypePacks)
if (!alias.has_value())
{ {
if (std::optional<TypePackId> alias = scope->lookupPack(tp->genericName.value))
return;
if (scope->lookupType(tp->genericName.value)) if (scope->lookupType(tp->genericName.value))
{ return reportError(
reportError(
SwappedGenericTypeParameter{ SwappedGenericTypeParameter{
tp->genericName.value, tp->genericName.value,
SwappedGenericTypeParameter::Kind::Pack, SwappedGenericTypeParameter::Kind::Pack,
}, },
tp->location tp->location
); );
}
reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location);
} }
else else
{ {
reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location); std::optional<TypePackId> 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);
}
} }
} }

View file

@ -33,150 +33,6 @@ static bool shouldEarlyExit(NormalizationResult res)
return false; return false;
} }
TypeIds::TypeIds(std::initializer_list<TypeId> 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<TypeId>{}(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<TypeId>{}(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<NeverType>(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() {}
NormalizedStringType::NormalizedStringType(bool isCofinite, std::map<std::string, TypeId> singletons) NormalizedStringType::NormalizedStringType(bool isCofinite, std::map<std::string, TypeId> singletons)

View file

@ -255,6 +255,29 @@ bool Scope::shouldWarnGlobal(std::string name) const
return false; return false;
} }
NotNull<Scope> 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) bool subsumesStrict(Scope* left, Scope* right)
{ {
while (right) while (right)

View file

@ -21,7 +21,7 @@ LUAU_FASTFLAGVARIABLE(DebugLuauSubtypingCheckPathValidity)
LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100) LUAU_FASTINTVARIABLE(LuauSubtypingReasoningLimit, 100)
LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAGVARIABLE(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
namespace Luau namespace Luau
{ {
@ -675,20 +675,21 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result.isSubtype = ok; result.isSubtype = ok;
result.isCacheable = false; result.isCacheable = false;
} }
else if (auto superGeneric = get<GenericType>(superTy); FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant) else if (auto superGeneric = get<GenericType>(superTy);
FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant)
{ {
bool ok = bindGeneric(env, subTy, superTy); bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok; result.isSubtype = ok;
result.isCacheable = false; result.isCacheable = false;
} }
else if (auto pair = get2<FreeType, FreeType>(subTy, superTy); FFlag::LuauEagerGeneralization && pair) else if (auto pair = get2<FreeType, FreeType>(subTy, superTy); FFlag::LuauEagerGeneralization2 && pair)
{ {
// Any two free types are potentially subtypes of one another because // Any two free types are potentially subtypes of one another because
// both of them could be narrowed to never. // both of them could be narrowed to never.
result = {true}; result = {true};
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy}); result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
} }
else if (auto superFree = get<FreeType>(superTy); superFree && FFlag::LuauEagerGeneralization) else if (auto superFree = get<FreeType>(superTy); superFree && FFlag::LuauEagerGeneralization2)
{ {
// Given SubTy <: (LB <: SuperTy <: UB) // Given SubTy <: (LB <: SuperTy <: UB)
// //
@ -703,7 +704,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
if (result.isSubtype) if (result.isSubtype)
result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy}); result.assumedConstraints.emplace_back(SubtypeConstraint{subTy, superTy});
} }
else if (auto subFree = get<FreeType>(subTy); subFree && FFlag::LuauEagerGeneralization) else if (auto subFree = get<FreeType>(subTy); subFree && FFlag::LuauEagerGeneralization2)
{ {
// Given (LB <: SubTy <: UB) <: SuperTy // Given (LB <: SubTy <: UB) <: SuperTy
// //
@ -767,7 +768,8 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
result.isSubtype = ok; result.isSubtype = ok;
result.isCacheable = false; result.isCacheable = false;
} }
else if (auto superGeneric = get<GenericType>(superTy); !FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant) else if (auto superGeneric = get<GenericType>(superTy);
!FFlag::LuauSubtypeGenericsAndNegations && superGeneric && variance == Variance::Contravariant)
{ {
bool ok = bindGeneric(env, subTy, superTy); bool ok = bindGeneric(env, subTy, superTy);
result.isSubtype = ok; result.isSubtype = ok;
@ -1450,7 +1452,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
{ {
SubtypingResult result{true}; SubtypingResult result{true};
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (subTable->props.empty() && !subTable->indexer && subTable->state == TableState::Sealed && superTable->indexer) 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) if (subTable->indexer)
result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer, scope)); 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 // As above, we assume that {| |} <: {T} because the unsealed table
// on the left will eventually gain the necessary indexer. // 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> scope) SubtypingResult Subtyping::isCovariantWith(
SubtypingEnvironment& env,
const ExternType* subExternType,
const ExternType* superExternType,
NotNull<Scope> scope
)
{ {
return {isSubclass(subExternType, superExternType)}; return {isSubclass(subExternType, superExternType)};
} }
@ -1737,9 +1744,8 @@ SubtypingResult Subtyping::isCovariantWith(
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope); SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops, scope);
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope)); result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans, scope));
result.andAlso( result.andAlso(isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope)
isCovariantWith(env, subNorm->externTypes, superNorm->externTypes, scope).orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope)) .orElse(isCovariantWith(env, subNorm->externTypes, superNorm->tables, scope)));
);
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope)); result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors, scope));
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope)); result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils, scope));
result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers, scope)); result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers, scope));

View file

@ -19,16 +19,6 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
namespace Luau 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<AstExprConstantString>())
return true;
else
return false;
}
TypeId matchLiteralType( TypeId matchLiteralType(
NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes, NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes, NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,

View file

@ -1381,7 +1381,11 @@ struct Printer
LUAU_ASSERT(!forVarArg); LUAU_ASSERT(!forVarArg);
if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp)) if (const auto cstNode = lookupCstNode<CstTypePackExplicit>(explicitTp))
visualizeTypeList( 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 else
visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize); visualizeTypeList(explicitTp->typeList, unconditionallyParenthesize);
@ -1986,7 +1990,7 @@ struct Printer
if (FFlag::LuauStoreLocalAnnotationColonPositions) if (FFlag::LuauStoreLocalAnnotationColonPositions)
visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0}); visualize(*a->var, cstNode ? cstNode->annotationColonPosition : Position{0, 0});
else else
visualize(*a->var, Position{0,0}); visualize(*a->var, Position{0, 0});
if (cstNode) if (cstNode)
advance(cstNode->equalsPosition); advance(cstNode->equalsPosition);

View file

@ -1079,7 +1079,7 @@ void persist(TypeId ty)
queue.push_back(ttv->indexer->indexResultType); queue.push_back(ttv->indexer->indexResultType);
} }
} }
else if (auto etv= get<ExternType>(t)) else if (auto etv = get<ExternType>(t))
{ {
for (const auto& [_name, prop] : etv->props) for (const auto& [_name, prop] : etv->props)
queue.push_back(prop.type()); queue.push_back(prop.type());

View file

@ -34,6 +34,7 @@ LUAU_FASTFLAGVARIABLE(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck) LUAU_FASTFLAGVARIABLE(LuauTypeCheckerStricterIndexCheck)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors) LUAU_FASTFLAGVARIABLE(LuauReportSubtypingErrors)
LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases) LUAU_FASTFLAGVARIABLE(LuauSkipMalformedTypeAliases)
LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAGVARIABLE(LuauTableLiteralSubtypeSpecificCheck)
@ -782,7 +783,6 @@ void TypeChecker2::visit(AstStatReturn* ret)
for (AstExpr* expr : ret->list) for (AstExpr* expr : ret->list)
visit(expr, ValueContext::RValue); visit(expr, ValueContext::RValue);
} }
void TypeChecker2::visit(AstStatExpr* expr) void TypeChecker2::visit(AstStatExpr* expr)
@ -2796,22 +2796,41 @@ void TypeChecker2::visit(AstTypePackGeneric* tp)
Scope* scope = findInnermostScope(tp->location); Scope* scope = findInnermostScope(tp->location);
LUAU_ASSERT(scope); LUAU_ASSERT(scope);
std::optional<TypePackId> alias = scope->lookupPack(tp->genericName.value); if (FFlag::LuauNewNonStrictFixGenericTypePacks)
if (!alias.has_value())
{ {
if (std::optional<TypePackId> alias = scope->lookupPack(tp->genericName.value))
return;
if (scope->lookupType(tp->genericName.value)) if (scope->lookupType(tp->genericName.value))
{ return reportError(
reportError(
SwappedGenericTypeParameter{ SwappedGenericTypeParameter{
tp->genericName.value, tp->genericName.value,
SwappedGenericTypeParameter::Kind::Pack, SwappedGenericTypeParameter::Kind::Pack,
}, },
tp->location tp->location
); );
}
else reportError(UnknownSymbol{tp->genericName.value, UnknownSymbol::Context::Type}, tp->location);
}
else
{
std::optional<TypePackId> 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); 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<AstExprConstantString>());
}
}
bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedType) bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedType)
{ {
auto exprType = follow(lookupType(expr)); auto exprType = follow(lookupType(expr));
@ -2982,7 +2993,7 @@ bool TypeChecker2::testPotentialLiteralIsSubtype(AstExpr* expr, TypeId expectedT
return testIsSubtype(exprType, expectedType, expr->location); return testIsSubtype(exprType, expectedType, expr->location);
} }
Set<std::optional<std::string> > missingKeys{{}}; Set<std::optional<std::string>> missingKeys{{}};
for (const auto& [name, prop] : expectedTableType->props) for (const auto& [name, prop] : expectedTableType->props)
{ {
if (FFlag::LuauEnableWriteOnlyProperties) if (FFlag::LuauEnableWriteOnlyProperties)

View file

@ -27,6 +27,7 @@
#include "Luau/Unifier2.h" #include "Luau/Unifier2.h"
#include "Luau/VecDeque.h" #include "Luau/VecDeque.h"
#include "Luau/VisitType.h" #include "Luau/VisitType.h"
#include "Luau/ApplyTypeFunction.h"
#include "lua.h" #include "lua.h"
#include "lualib.h" #include "lualib.h"
@ -47,8 +48,8 @@ LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyApplicationCartesianProductLimit, 5'0
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1); LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies) LUAU_FASTFLAGVARIABLE(DebugLuauLogTypeFamilies)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -56,6 +57,7 @@ LUAU_FASTFLAGVARIABLE(LuauNarrowIntersectionNevers)
LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAGVARIABLE(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAGVARIABLE(LuauNoMoreInjectiveTypeFunctions) LUAU_FASTFLAGVARIABLE(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults) LUAU_FASTFLAGVARIABLE(LuauNotAllBinaryTypeFunsHaveDefaults)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
namespace Luau namespace Luau
{ {
@ -283,7 +285,7 @@ struct TypeFunctionReducer
} }
else if (is<GenericType>(ty)) else if (is<GenericType>(ty))
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
return SkipTestResult::Generic; return SkipTestResult::Generic;
else else
return SkipTestResult::Irreducible; return SkipTestResult::Irreducible;
@ -305,7 +307,7 @@ struct TypeFunctionReducer
} }
else if (is<GenericTypePack>(ty)) else if (is<GenericTypePack>(ty))
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
return SkipTestResult::Generic; return SkipTestResult::Generic;
else else
return SkipTestResult::Irreducible; return SkipTestResult::Irreducible;
@ -569,6 +571,27 @@ struct LuauTempThreadPopper
lua_State* L = nullptr; lua_State* L = nullptr;
}; };
template<typename T>
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( static FunctionGraphReductionResult reduceFunctionsInternal(
VecDeque<TypeId> queuedTys, VecDeque<TypeId> queuedTys,
VecDeque<TypePackId> queuedTps, VecDeque<TypePackId> 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<TypeId> 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<TypeId> 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<TypeId> userDefinedTypeFunction( TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
TypeId instance, TypeId instance,
const std::vector<TypeId>& typeParams, const std::vector<TypeId>& typeParams,
@ -819,11 +933,21 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
for (auto typeParam : typeParams) for (auto typeParam : typeParams)
check.traverse(follow(typeParam)); 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()) if (!check.blockingTypes.empty())
return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}}; return {std::nullopt, Reduction::MaybeOk, check.blockingTypes, {}};
// Ensure that whole type function environment is registered // 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 // Cannot evaluate if a potential dependency couldn't be parsed
if (definition.first->hasErrors) if (definition.first->hasErrors)
@ -849,8 +973,13 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
lua_State* L = lua_newthread(global); lua_State* L = lua_newthread(global);
LuauTempThreadPopper popper(global); LuauTempThreadPopper popper(global);
std::unique_ptr<TypeFunctionRuntimeBuilderState> runtimeBuilder = std::make_unique<TypeFunctionRuntimeBuilderState>(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 // 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 // Environment table has to be filled only once in the current execution context
if (ctx->typeFunctionRuntime->initialized.find(curr.first)) if (ctx->typeFunctionRuntime->initialized.find(curr.first))
@ -870,7 +999,7 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
lua_getfenv(L, -1); lua_getfenv(L, -1);
lua_setreadonly(L, -1, false); 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 // Filter visibility based on original scope depth
if (definition.second >= curr.second) if (definition.second >= curr.second)
@ -885,6 +1014,39 @@ TypeFunctionReductionResult<TypeId> 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_setreadonly(L, -1, true);
lua_pop(L, 2); lua_pop(L, 2);
} }
@ -901,8 +1063,6 @@ TypeFunctionReductionResult<TypeId> userDefinedTypeFunction(
resetTypeFunctionState(L); resetTypeFunctionState(L);
std::unique_ptr<TypeFunctionRuntimeBuilderState> runtimeBuilder = std::make_unique<TypeFunctionRuntimeBuilderState>(ctx);
// Push serialized arguments onto the stack // Push serialized arguments onto the stack
for (auto typeParam : typeParams) for (auto typeParam : typeParams)
{ {
@ -1101,7 +1261,7 @@ TypeFunctionReductionResult<TypeId> unmTypeFunction(
if (isPending(operandTy, ctx->solver)) if (isPending(operandTy, ctx->solver))
return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {operandTy}, {}};
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
operandTy = follow(operandTy); operandTy = follow(operandTy);
std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy); std::shared_ptr<const NormalizedType> normTy = ctx->normalizer->normalize(operandTy);
@ -1698,7 +1858,7 @@ TypeFunctionReductionResult<TypeId> orTypeFunction(
return {rhsTy, Reduction::MaybeOk, {}, {}}; return {rhsTy, Reduction::MaybeOk, {}, {}};
// check to see if both operand types are resolved enough, and wait to reduce if not // check to see if both operand types are resolved enough, and wait to reduce if not
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy)) if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
@ -1745,7 +1905,7 @@ static TypeFunctionReductionResult<TypeId> comparisonTypeFunction(
if (lhsTy == instance || rhsTy == instance) if (lhsTy == instance || rhsTy == instance)
return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}}; return {ctx->builtins->neverType, Reduction::MaybeOk, {}, {}};
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy)) if (is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(lhsTy))
return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}}; return {std::nullopt, Reduction::MaybeOk, {lhsTy}, {}};
@ -2099,7 +2259,7 @@ bool isSimpleDiscriminant(TypeId ty)
return isApproximateTruthy(ty) || isApproximateFalsy(ty); return isApproximateTruthy(ty) || isApproximateFalsy(ty);
} }
} } // namespace
TypeFunctionReductionResult<TypeId> refineTypeFunction( TypeFunctionReductionResult<TypeId> refineTypeFunction(
TypeId instance, TypeId instance,
@ -2119,9 +2279,8 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
for (size_t i = 1; i < typeParams.size(); i++) for (size_t i = 1; i < typeParams.size(); i++)
discriminantTypes.push_back(follow(typeParams.at(i))); discriminantTypes.push_back(follow(typeParams.at(i)));
const bool targetIsPending = FFlag::LuauEagerGeneralization const bool targetIsPending = FFlag::LuauEagerGeneralization2 ? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy)
? is<BlockedType, PendingExpansionType, TypeFunctionInstanceType>(targetTy) : isPending(targetTy, ctx->solver);
: isPending(targetTy, ctx->solver);
// check to see if both operand types are resolved enough, and wait to reduce if not // check to see if both operand types are resolved enough, and wait to reduce if not
if (targetIsPending) if (targetIsPending)
@ -2206,7 +2365,7 @@ TypeFunctionReductionResult<TypeId> refineTypeFunction(
if (is<TableType>(target) || isSimpleDiscriminant(discriminant)) if (is<TableType>(target) || isSimpleDiscriminant(discriminant))
{ {
SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant); SimplifyResult result = simplifyIntersection(ctx->builtins, ctx->arena, target, discriminant);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// Simplification considers free and generic types to be // Simplification considers free and generic types to be
// 'blocking', but that's not suitable for refine<>. // 'blocking', but that's not suitable for refine<>.
@ -2615,8 +2774,8 @@ TypeFunctionReductionResult<TypeId> keyofFunctionImpl(
if (!normTy) if (!normTy)
return {std::nullopt, Reduction::MaybeOk, {}, {}}; 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 // 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
// as well) // types as well)
if (normTy->hasTables() == normTy->hasExternTypes()) if (normTy->hasTables() == normTy->hasExternTypes())
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
@ -2985,7 +3144,8 @@ TypeFunctionReductionResult<TypeId> indexFunctionImpl(
return {std::nullopt, Reduction::Erroneous, {}, {}}; return {std::nullopt, Reduction::Erroneous, {}, {}};
// at least one class is guaranteed to be in the iterator by .hasExternTypes() // 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<ExternType>(*externTypeIter); auto externTy = get<ExternType>(*externTypeIter);
if (!externTy) if (!externTy)
@ -3335,7 +3495,7 @@ BuiltinTypeFunctions::BuiltinTypeFunctions()
, ltFunc{"lt", ltTypeFunction} , ltFunc{"lt", ltTypeFunction}
, leFunc{"le", leTypeFunction} , leFunc{"le", leTypeFunction}
, eqFunc{"eq", eqTypeFunction} , eqFunc{"eq", eqTypeFunction}
, refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization} , refineFunc{"refine", refineTypeFunction, /*canReduceGenerics*/ FFlag::LuauEagerGeneralization2}
, singletonFunc{"singleton", singletonTypeFunction} , singletonFunc{"singleton", singletonTypeFunction}
, unionFunc{"union", unionTypeFunction} , unionFunc{"union", unionTypeFunction}
, intersectFunc{"intersect", intersectTypeFunction} , intersectFunc{"intersect", intersectTypeFunction}

View file

@ -63,7 +63,7 @@ std::optional<std::string> checkResultForError(lua_State* L, const char* typeFun
} }
} }
static TypeFunctionRuntime* getTypeFunctionRuntime(lua_State* L) TypeFunctionRuntime* getTypeFunctionRuntime(lua_State* L)
{ {
return static_cast<TypeFunctionRuntime*>(lua_getthreaddata(lua_mainthread(L))); return static_cast<TypeFunctionRuntime*>(lua_getthreaddata(lua_mainthread(L)));
} }

View file

@ -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 // 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 // class
target = typeFunctionRuntime->typeArena.allocate( target = typeFunctionRuntime->typeArena.allocate(TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty});
TypeFunctionExternType{{}, std::nullopt, std::nullopt, std::nullopt, std::nullopt, ty}
);
} }
else if (auto g = get<GenericType>(ty)) else if (auto g = get<GenericType>(ty))
{ {

153
Analysis/src/TypeIds.cpp Normal file
View file

@ -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<TypeId> 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<TypeId>{}(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<TypeId>{}(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<NeverType>(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;
}
}

View file

@ -34,7 +34,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauFreezeDuringUnification)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst) LUAU_FASTFLAG(LuauStoreReturnTypesAsPackOnAst)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure) LUAU_FASTFLAGVARIABLE(LuauReduceCheckBinaryExprStackPressure)
namespace Luau namespace Luau
@ -1664,10 +1663,7 @@ void TypeChecker::prototype(const ScopePtr& scope, const AstStatTypeAlias& typea
FreeType* ftv = getMutable<FreeType>(ty); FreeType* ftv = getMutable<FreeType>(ty);
LUAU_ASSERT(ftv); LUAU_ASSERT(ftv);
ftv->forwardedTypeAlias = true; ftv->forwardedTypeAlias = true;
if (FFlag::LuauRetainDefinitionAliasLocations) bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location};
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty, typealias.location};
else
bindingsMap[name] = {std::move(generics), std::move(genericPacks), ty};
scope->typeAliasLocations[name] = typealias.location; scope->typeAliasLocations[name] = typealias.location;
scope->typeAliasNameLocations[name] = typealias.nameLocation; 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}); TypeId metaTy = addType(TableType{TableState::Sealed, scope->level});
etv->metatable = metaTy; etv->metatable = metaTy;
if (FFlag::LuauRetainDefinitionAliasLocations) scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location};
scope->exportedTypeBindings[className] = TypeFun{{}, classTy, declaredExternType.location};
else
scope->exportedTypeBindings[className] = TypeFun{{}, classTy};
} }
ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType) ControlFlow TypeChecker::check(const ScopePtr& scope, const AstStatDeclareExternType& declaredExternType)
@ -4330,7 +4323,7 @@ void TypeChecker::checkArgumentList(
if (exceedsLoopCount()) if (exceedsLoopCount())
return; return;
} }
TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}}); TypePackId varPack = addTypePack(TypePackVar{TypePack{rest, argIter.tail()}});
state.tryUnify(varPack, tail); state.tryUnify(varPack, tail);

View file

@ -12,7 +12,7 @@
#include <algorithm> #include <algorithm>
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs) LUAU_FASTFLAGVARIABLE(LuauErrorSuppressionTypeFunctionArgs)
namespace Luau namespace Luau
@ -306,7 +306,7 @@ TypePack extendTypePack(
TypePack newPack; TypePack newPack;
newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity); newPack.tail = arena.freshTypePack(ftp->scope, ftp->polarity);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
trackInteriorFreeTypePack(ftp->scope, *newPack.tail); trackInteriorFreeTypePack(ftp->scope, *newPack.tail);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
@ -588,7 +588,7 @@ void trackInteriorFreeType(Scope* scope, TypeId ty)
void trackInteriorFreeTypePack(Scope* scope, TypePackId tp) void trackInteriorFreeTypePack(Scope* scope, TypePackId tp)
{ {
LUAU_ASSERT(tp); LUAU_ASSERT(tp);
if (!FFlag::LuauEagerGeneralization) if (!FFlag::LuauEagerGeneralization2)
return; return;
for (; scope; scope = scope->parent.get()) for (; scope; scope = scope->parent.get())
@ -685,4 +685,15 @@ std::optional<TypeId> extractMatchingTableType(std::vector<TypeId>& tables, Type
return std::nullopt; 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<AstExprConstantString>())
return true;
else
return false;
}
} // namespace Luau } // namespace Luau

View file

@ -19,7 +19,7 @@
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEnableWriteOnlyProperties) LUAU_FASTFLAG(LuauEnableWriteOnlyProperties)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
namespace Luau namespace Luau
{ {
@ -329,12 +329,12 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
for (TypePackId genericPack : subFn->genericPacks) for (TypePackId genericPack : subFn->genericPacks)
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
genericPack = follow(genericPack); genericPack = follow(genericPack);
// TODO: Clip this follow() with LuauEagerGeneralization // TODO: Clip this follow() with LuauEagerGeneralization2
const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack)); const GenericTypePack* gen = get<GenericTypePack>(follow(genericPack));
if (gen) if (gen)
genericPackSubstitutions[genericPack] = freshTypePack(scope, gen->polarity); 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->indexType, superTable->indexer->indexType);
result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType); result &= unify(subTable->indexer->indexResultType, superTable->indexer->indexResultType);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// FIXME: We can probably do something more efficient here. // FIXME: We can probably do something more efficient here.
result &= unify(superTable->indexer->indexType, subTable->indexer->indexType); result &= unify(superTable->indexer->indexType, subTable->indexer->indexType);

View file

@ -393,7 +393,7 @@ public:
std::optional<Position> separatorPosition; std::optional<Position> separatorPosition;
CstExprConstantString* stringInfo = nullptr; // only if Kind == StringProperty 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<Item> items, bool isArray); CstTypeTable(AstArray<Item> items, bool isArray);

View file

@ -456,7 +456,7 @@ private:
AstType* annotation; AstType* annotation;
Position colonPosition; 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) : name(name)
, annotation(annotation) , annotation(annotation)
, colonPosition(colonPosition) , colonPosition(colonPosition)

View file

@ -94,7 +94,11 @@ CstStatReturn::CstStatReturn(AstArray<Position> commaPositions)
{ {
} }
CstStatLocal::CstStatLocal(AstArray<Position> varsAnnotationColonPositions, AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions) CstStatLocal::CstStatLocal(
AstArray<Position> varsAnnotationColonPositions,
AstArray<Position> varsCommaPositions,
AstArray<Position> valuesCommaPositions
)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, varsAnnotationColonPositions(varsAnnotationColonPositions) , varsAnnotationColonPositions(varsAnnotationColonPositions)
, varsCommaPositions(varsCommaPositions) , varsCommaPositions(varsCommaPositions)
@ -102,7 +106,12 @@ CstStatLocal::CstStatLocal(AstArray<Position> varsAnnotationColonPositions, AstA
{ {
} }
CstStatFor::CstStatFor(Position annotationColonPosition, Position equalsPosition, Position endCommaPosition, std::optional<Position> stepCommaPosition) CstStatFor::CstStatFor(
Position annotationColonPosition,
Position equalsPosition,
Position endCommaPosition,
std::optional<Position> stepCommaPosition
)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, annotationColonPosition(annotationColonPosition) , annotationColonPosition(annotationColonPosition)
, equalsPosition(equalsPosition) , equalsPosition(equalsPosition)
@ -111,7 +120,11 @@ CstStatFor::CstStatFor(Position annotationColonPosition, Position equalsPosition
{ {
} }
CstStatForIn::CstStatForIn(AstArray<Position> varsAnnotationColonPositions, AstArray<Position> varsCommaPositions, AstArray<Position> valuesCommaPositions) CstStatForIn::CstStatForIn(
AstArray<Position> varsAnnotationColonPositions,
AstArray<Position> varsCommaPositions,
AstArray<Position> valuesCommaPositions
)
: CstNode(CstClassIndex()) : CstNode(CstClassIndex())
, varsAnnotationColonPositions(varsAnnotationColonPositions) , varsAnnotationColonPositions(varsAnnotationColonPositions)
, varsCommaPositions(varsCommaPositions) , varsCommaPositions(varsCommaPositions)

View file

@ -709,7 +709,8 @@ AstStat* Parser::parseFor()
if (options.storeCstData) if (options.storeCstData)
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions) if (FFlag::LuauStoreLocalAnnotationColonPositions)
cstNodeMap[node] = allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions)); cstNodeMap[node] =
allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions));
else else
cstNodeMap[node] = allocator.alloc<CstStatForIn>(AstArray<Position>{}, varsCommaPosition, copy(valuesCommaPositions)); cstNodeMap[node] = allocator.alloc<CstStatForIn>(AstArray<Position>{}, varsCommaPosition, copy(valuesCommaPositions));
} }
@ -1009,7 +1010,8 @@ AstStat* Parser::parseLocal(const AstArray<AstAttr*>& attributes)
if (options.storeCstData) if (options.storeCstData)
{ {
if (FFlag::LuauStoreLocalAnnotationColonPositions) if (FFlag::LuauStoreLocalAnnotationColonPositions)
cstNodeMap[node] = allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions)); cstNodeMap[node] =
allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions));
else else
cstNodeMap[node] = allocator.alloc<CstStatLocal>(AstArray<Position>{}, varsCommaPositions, copy(valuesCommaPositions)); cstNodeMap[node] = allocator.alloc<CstStatLocal>(AstArray<Position>{}, varsCommaPositions, copy(valuesCommaPositions));
} }
@ -1333,7 +1335,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
foundExtern = true; foundExtern = true;
nextLexeme(); nextLexeme();
if (AstName(lexer.current().name) != "type") if (AstName(lexer.current().name) != "type")
return reportStatError(lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name); return reportStatError(
lexer.current().location, {}, {}, "Expected `type` keyword after `extern`, but got %s instead", lexer.current().name
);
} }
} }
@ -1354,7 +1358,11 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
if (foundExtern) if (foundExtern)
{ {
if (AstName(lexer.current().name) != "with") if (AstName(lexer.current().name) != "with")
report(lexer.current().location, "Expected `with` keyword before listing properties of the external type, but got %s instead", lexer.current().name); report(
lexer.current().location,
"Expected `with` keyword before listing properties of the external type, but got %s instead",
lexer.current().name
);
else else
nextLexeme(); nextLexeme();
} }
@ -1452,9 +1460,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation"); expectAndConsume(':', "property type annotation");
AstType* propType = parseType(); AstType* propType = parseType();
props.push_back( props.push_back(AstDeclaredExternTypeProperty{
AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())} propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())
); });
} }
} }
else else
@ -1533,9 +1541,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation"); expectAndConsume(':', "property type annotation");
AstType* propType = parseType(); AstType* propType = parseType();
props.push_back( props.push_back(AstDeclaredExternTypeProperty{
AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())} propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())
); });
} }
} }
} }
@ -2618,7 +2626,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
tableSeparator(), tableSeparator(),
lexer.current().location.begin, lexer.current().location.begin,
allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth), allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth),
stringPosition stringPosition
}); });
} }
else else

View file

@ -14,16 +14,11 @@ void requireConfigInit(luarequire_Configuration* config);
struct ReplRequirer struct ReplRequirer
{ {
using CompileOptions = Luau::CompileOptions(*)(); using CompileOptions = Luau::CompileOptions (*)();
using BoolCheck = bool(*)(); using BoolCheck = bool (*)();
using Coverage = void(*)(lua_State*, int); using Coverage = void (*)(lua_State*, int);
ReplRequirer( ReplRequirer(CompileOptions copts, BoolCheck coverageActive, BoolCheck codegenEnabled, Coverage coverageTrack);
CompileOptions copts,
BoolCheck coverageActive,
BoolCheck codegenEnabled,
Coverage coverageTrack
);
CompileOptions copts; CompileOptions copts;
BoolCheck coverageActive; BoolCheck coverageActive;

View file

@ -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 // 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 // See CodeGenA64.cpp for layout
inline constexpr unsigned kStashSlots = 9; // stashed non-volatile registers inline constexpr unsigned kStashSlots = 9; // stashed non-volatile registers
inline constexpr unsigned kTempSlots = 1; // 8 bytes of temporary space, such luxury! 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 kSpillSlots = 22; // slots for spilling temporary registers
inline constexpr unsigned kStackSize = (kStashSlots + kTempSlots + kSpillSlots) * 8; inline constexpr unsigned kStackSize = (kStashSlots + kTempSlots + kSpillSlots) * 8;

View file

@ -189,6 +189,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/Documentation.h Analysis/include/Luau/Documentation.h
Analysis/include/Luau/Error.h Analysis/include/Luau/Error.h
Analysis/include/Luau/EqSatSimplification.h Analysis/include/Luau/EqSatSimplification.h
Analysis/include/Luau/ExpectedTypeVisitor.h
Analysis/include/Luau/FileResolver.h Analysis/include/Luau/FileResolver.h
Analysis/include/Luau/FragmentAutocomplete.h Analysis/include/Luau/FragmentAutocomplete.h
Analysis/include/Luau/Frontend.h Analysis/include/Luau/Frontend.h
@ -237,6 +238,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/include/Luau/TypeFunctionRuntime.h Analysis/include/Luau/TypeFunctionRuntime.h
Analysis/include/Luau/TypeFunctionRuntimeBuilder.h Analysis/include/Luau/TypeFunctionRuntimeBuilder.h
Analysis/include/Luau/TypeFwd.h Analysis/include/Luau/TypeFwd.h
Analysis/include/Luau/TypeIds.h
Analysis/include/Luau/TypeInfer.h Analysis/include/Luau/TypeInfer.h
Analysis/include/Luau/TypeOrPack.h Analysis/include/Luau/TypeOrPack.h
Analysis/include/Luau/TypePack.h Analysis/include/Luau/TypePack.h
@ -267,6 +269,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/EmbeddedBuiltinDefinitions.cpp Analysis/src/EmbeddedBuiltinDefinitions.cpp
Analysis/src/Error.cpp Analysis/src/Error.cpp
Analysis/src/EqSatSimplification.cpp Analysis/src/EqSatSimplification.cpp
Analysis/src/ExpectedTypeVisitor.cpp
Analysis/src/FileResolver.cpp Analysis/src/FileResolver.cpp
Analysis/src/FragmentAutocomplete.cpp Analysis/src/FragmentAutocomplete.cpp
Analysis/src/Frontend.cpp Analysis/src/Frontend.cpp
@ -306,6 +309,7 @@ target_sources(Luau.Analysis PRIVATE
Analysis/src/TypeFunctionReductionGuesser.cpp Analysis/src/TypeFunctionReductionGuesser.cpp
Analysis/src/TypeFunctionRuntime.cpp Analysis/src/TypeFunctionRuntime.cpp
Analysis/src/TypeFunctionRuntimeBuilder.cpp Analysis/src/TypeFunctionRuntimeBuilder.cpp
Analysis/src/TypeIds.cpp
Analysis/src/TypeInfer.cpp Analysis/src/TypeInfer.cpp
Analysis/src/TypeOrPack.cpp Analysis/src/TypeOrPack.cpp
Analysis/src/TypePack.cpp Analysis/src/TypePack.cpp

View file

@ -15,6 +15,8 @@
#include <string.h> #include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauUnrefExisting, false)
/* /*
* This file contains most implementations of core Lua APIs from lua.h. * 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; global_State* g = L->global;
LuaTable* reg = hvalue(registry(L)); 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 if (DFFlag::LuauUnrefExisting)
g->registryfree = ref; {
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) void lua_setuserdatatag(lua_State* L, int idx, int tag)

View file

@ -642,4 +642,3 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstGenericTypePackWithDefault")
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -19,7 +19,8 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2) LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel) LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauExpectedTypeVisitor)
using namespace Luau; using namespace Luau;
@ -41,6 +42,9 @@ struct ACFixtureImpl : BaseType
FrontendOptions opts; FrontendOptions opts;
opts.forAutocomplete = true; opts.forAutocomplete = true;
opts.retainFullTypeGraphs = 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); this->frontend.check("MainModule", opts);
return Luau::autocomplete(this->frontend, "MainModule", Position{row, column}, nullCallback); return Luau::autocomplete(this->frontend, "MainModule", Position{row, column}, nullCallback);
@ -51,6 +55,9 @@ struct ACFixtureImpl : BaseType
FrontendOptions opts; FrontendOptions opts;
opts.forAutocomplete = true; opts.forAutocomplete = true;
opts.retainFullTypeGraphs = 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); this->frontend.check("MainModule", opts);
return Luau::autocomplete(this->frontend, "MainModule", getPosition(marker), callback); return Luau::autocomplete(this->frontend, "MainModule", getPosition(marker), callback);
@ -61,6 +68,9 @@ struct ACFixtureImpl : BaseType
FrontendOptions opts; FrontendOptions opts;
opts.forAutocomplete = true; opts.forAutocomplete = true;
opts.retainFullTypeGraphs = 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); this->frontend.check(name, opts);
return Luau::autocomplete(this->frontend, name, pos, callback); return Luau::autocomplete(this->frontend, name, pos, callback);
@ -103,7 +113,9 @@ struct ACFixtureImpl : BaseType
} }
LUAU_ASSERT("Digit expected after @ symbol" && prevChar != '@'); 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) LoadDefinitionFileResult loadDefinition(const std::string& source)
@ -2191,7 +2203,10 @@ local fp: @1= f
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
REQUIRE_EQ("({ x: number, y: number }) -> number", toString(requireType("f"))); REQUIRE_EQ("({ x: number, y: number }) -> number", toString(requireType("f")));
else 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")); 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") TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key_iso")
{ {
check(R"( check(R"(
type Direction = "up" | "down" type Direction = "up" | "down"
local b: {[Direction]: boolean} = {["@2"] = true} local b: {[Direction]: boolean} = {["@2"] = true}
@ -4454,9 +4468,9 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
auto ac = autocomplete('1'); 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 // sets some flags
CHECK(ac.entryMap.count("BaseMethod") > 0); CHECK(ac.entryMap.count("BaseMethod") > 0);
CHECK(ac.entryMap.count("Method") > 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_. // Otherwise, we don't infer anything for `value`, which is _fine_.
CHECK(ac.entryMap.empty()); CHECK(ac.entryMap.empty());
} }
} }
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions") TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions")
@ -4536,4 +4549,62 @@ end
CHECK_EQ(ac.entryMap.count("number"), 1); 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(); TEST_SUITE_END();

View file

@ -56,7 +56,6 @@ struct DataFlowGraphFixture
CHECK(phi->operands.size() == operandSet.size()); CHECK(phi->operands.size() == operandSet.size());
for (auto o : phi->operands) for (auto o : phi->operands)
CHECK(operandSet.contains(o.get())); CHECK(operandSet.contains(o.get()));
} }
}; };

View file

@ -29,6 +29,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests) LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(LuauTypeFunOptional) LUAU_FASTFLAG(LuauTypeFunOptional)
LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature)
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests}; #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. // Most often those are changes related to builtin type definitions.
// In that case, flag can be forced to 'true' using the example below: // In that case, flag can be forced to 'true' using the example below:
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true}; // 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. // 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. // This is useful for tracking down violations of Luau's memory model.

View file

@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAG(LuauFragmentAcMemoryLeak) LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation) LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations) LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations)
LUAU_FASTFLAG(LuauPopulateRefinedTypesInFragmentFromOldSolver)
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents) static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ExternType*> ptr, std::optional<std::string> contents)
{ {
@ -65,6 +66,7 @@ struct FragmentAutocompleteFixtureImpl : BaseType
ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true}; ScopedFastFlag luauFragmentAcMemoryLeak{FFlag::LuauFragmentAcMemoryLeak, true};
ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true}; ScopedFastFlag luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true};
ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true}; ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true};
ScopedFastFlag luauPopulateRefinedTypesInFragmentFromOldSolver{FFlag::LuauPopulateRefinedTypesInFragmentFromOldSolver, true};
FragmentAutocompleteFixtureImpl() FragmentAutocompleteFixtureImpl()
: BaseType(true) : BaseType(true)
@ -146,6 +148,37 @@ struct FragmentAutocompleteFixtureImpl : BaseType
return Luau::tryFragmentAutocomplete(this->frontend, "MainModule", cursorPos, context, nullCallback); return Luau::tryFragmentAutocomplete(this->frontend, "MainModule", cursorPos, context, nullCallback);
} }
void autocompleteFragmentInNewSolver(
const std::string& document,
const std::string& updated,
Position cursorPos,
std::function<void(FragmentAutocompleteStatusResult& result)> assertions,
std::optional<Position> 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<void(FragmentAutocompleteStatusResult& result)> assertions,
std::optional<Position> 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( void autocompleteFragmentInBothSolvers(
const std::string& document, 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<T> = { type: "ok", value: T}
type Err<E> = { type : "err", error : E}
type Result<T,E> = Ok<T> | Err<E>
local result = {} :: Result<number, string>
if result.type == "ok" then
end
)";
const std::string dest = R"(
type Ok<T> = { type: "ok", value: T}
type Err<E> = { type : "err", error : E}
type Result<T,E> = Ok<T> | Err<E>
local result = {} :: Result<number, string>
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<T> = { type: "ok", value: T}
type Err<E> = { type : "err", error : E}
type Result<T,E> = Ok<T> | Err<E>
local result = {} :: Result<number, string>
if result.type == "err" then
end
)";
const std::string dest = R"(
type Ok<T> = { type: "ok", value: T}
type Err<E> = { type : "err", error : E}
type Result<T,E> = Ok<T> | Err<E>
local result = {} :: Result<number, string>
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<T> = { type: "ok", value: T}
type Err<E> = { type : "err", error : E}
type Result<T,E> = Ok<T> | Err<E>
local result = {} :: Result<number, string>
if result.type == "ok" then
end
)";
const std::string dest = R"(
type Ok<T> = { type: "ok", value: T}
type Err<E> = { type : "err", error : E}
type Result<T,E> = Ok<T> | Err<E>
local result = {} :: Result<number, string>
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<T> = { type: "ok", value: T}
type Err<E> = { type : "err", error : E}
type Result<T,E> = Ok<T> | Err<E>
local result = {} :: Result<number, string>
if result.type == "err" then
end
)";
const std::string dest = R"(
type Ok<T> = { type: "ok", value: T}
type Err<E> = { type : "err", error : E}
type Result<T,E> = Ok<T> | Err<E>
local result = {} :: Result<number, string>
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) // NOLINTEND(bugprone-unchecked-optional-access)
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -897,7 +897,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
{ {
CHECK_EQ( CHECK_EQ(
"Table type '{ count: string }' not compatible with type '{ Count: number }' because the former is missing field 'Count'", "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 else
REQUIRE_EQ( REQUIRE_EQ(

View file

@ -15,7 +15,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes) LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
@ -115,7 +115,8 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "dont_traverse_into_class_types_when_ge
{ {
auto [propTy, _] = freshType(); 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); auto genExternType = generalize(cursedExternType);
REQUIRE(genExternType); 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})") 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; TableType tt;
tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType}; 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})) -> ()") TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()")
{ {
ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true}; ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true};
auto [aTy, aFree] = freshType(); auto [aTy, aFree] = freshType();
auto [bTy, bFree] = freshType(); auto [bTy, bFree] = freshType();
@ -341,10 +342,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type") TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::DebugLuauForbidInternalTypes, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}};
{FFlag::DebugLuauForbidInternalTypes, true},
{FFlag::LuauTrackInferredFunctionTypeFromCall, true}
};
// This test case should just not assert // This test case should just not assert
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -8,13 +8,13 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauEagerGeneralization); LUAU_FASTFLAG(LuauEagerGeneralization2);
TEST_SUITE_BEGIN("InferPolarity"); TEST_SUITE_BEGIN("InferPolarity");
TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }") TEST_CASE_FIXTURE(Fixture, "T where T = { m: <a>(a) -> T }")
{ {
ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true}; ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true};
TypeArena arena; TypeArena arena;
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack); ScopePtr globalScope = std::make_shared<Scope>(builtinTypes->anyTypePack);

View file

@ -10,7 +10,7 @@
LUAU_FASTFLAG(LuauSolverV2); LUAU_FASTFLAG(LuauSolverV2);
LUAU_FASTFLAG(LintRedundantNativeAttribute); LUAU_FASTFLAG(LintRedundantNativeAttribute);
LUAU_FASTFLAG(LuauDeprecatedAttribute); LUAU_FASTFLAG(LuauDeprecatedAttribute);
LUAU_FASTFLAG(LuauEagerGeneralization); LUAU_FASTFLAG(LuauEagerGeneralization2);
using namespace Luau; using namespace Luau;
@ -1942,7 +1942,7 @@ print(foo:bar(2.0))
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations") TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
{ {
// FIXME: For now this flag causes a stack overflow on Windows. // FIXME: For now this flag causes a stack overflow on Windows.
ScopedFastFlag _{FFlag::LuauEagerGeneralization, false}; ScopedFastFlag _{FFlag::LuauEagerGeneralization2, false};
LintResult result = lint(R"( LintResult result = lint(R"(
local t = {} local t = {}

View file

@ -16,6 +16,7 @@
#include <iostream> #include <iostream>
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
using namespace Luau; using namespace Luau;
@ -560,6 +561,18 @@ optionalArgsAtTheEnd1("a", nil, 3)
LUAU_REQUIRE_NO_ERRORS(result); 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...>(T...) -> () -- TypeError: Unknown type 'T'
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "optionals_in_checked_function_in_middle_cannot_be_omitted") TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "optionals_in_checked_function_in_middle_cannot_be_omitted")
{ {
CheckResult result = checkNonStrict(R"( CheckResult result = checkNonStrict(R"(

View file

@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauNormalizeIntersectionLimit) LUAU_FASTINT(LuauNormalizeIntersectionLimit)
LUAU_FASTINT(LuauNormalizeUnionLimit) LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget) LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -1206,7 +1206,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true}, {FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::LuauEagerGeneralization, true} {FFlag::LuauEagerGeneralization2, true}
}; };
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0}; ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};
@ -1221,6 +1221,6 @@ _[_] ^= _(_(_))
InternalCompilerError InternalCompilerError
); );
} }
#endif #endif
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -2086,7 +2086,8 @@ TEST_CASE_FIXTURE(Fixture, "parse_extern_type_declarations")
declare extern type Bar extends Foo with declare extern type Bar extends Foo with
prop2: string prop2: string
end end
)").root; )")
.root;
REQUIRE_EQ(stat->body.size, 2); 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 declare extern type Bar extends Foo with
prop2: string prop2: string
end end
)").root; )")
.root;
REQUIRE_EQ(stat->body.size, 2); REQUIRE_EQ(stat->body.size, 2);
@ -2355,9 +2357,7 @@ TEST_CASE_FIXTURE(Fixture, "class_indexer")
[number]: number [number]: number
end end
)", )",
(FFlag::LuauDeclareExternType) (FFlag::LuauDeclareExternType) ? "Cannot have more than one indexer on an extern type" : "Cannot have more than one class indexer"
? "Cannot have more than one indexer on an extern type"
: "Cannot have more than one class indexer"
); );
REQUIRE_EQ(1, p1.root->body.size); 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 parseOptions;
parseOptions.storeCstData = true; parseOptions.storeCstData = true;
ParseResult result = parseEx(R"( ParseResult result = parseEx(
R"(
for value in tbl do for value in tbl do
end end
)", parseOptions); )",
parseOptions
);
REQUIRE(result.root); REQUIRE(result.root);
REQUIRE_EQ(1, result.root->body.size); REQUIRE_EQ(1, result.root->body.size);
@ -4188,7 +4191,7 @@ TEST_CASE_FIXTURE(Fixture, "grouped_function_type")
CHECK_EQ(unionTy->types.size, 2); CHECK_EQ(unionTy->types.size, 2);
auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ()) auto groupTy = unionTy->types.data[0]->as<AstTypeGroup>(); // (() -> ())
REQUIRE(groupTy); REQUIRE(groupTy);
CHECK(groupTy->type->is<AstTypeFunction>()); // () -> () CHECK(groupTy->type->is<AstTypeFunction>()); // () -> ()
CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ? CHECK(unionTy->types.data[1]->is<AstTypeOptional>()); // ?
} }

View file

@ -17,7 +17,7 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations) LUAU_FASTFLAG(LuauSubtypeGenericsAndNegations)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
using namespace Luau; using namespace Luau;
@ -1629,19 +1629,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "substitute_a_generic_for_a_negation")
TypeId bTy = arena.addType(GenericType{"B"}); TypeId bTy = arena.addType(GenericType{"B"});
getMutable<GenericType>(bTy)->scope = moduleScope.get(); getMutable<GenericType>(bTy)->scope = moduleScope.get();
TypeId genericFunctionTy = arena.addType(FunctionType{ TypeId genericFunctionTy =
{aTy, bTy}, arena.addType(FunctionType{{aTy, bTy}, {}, arena.addTypePack({aTy, bTy}), arena.addTypePack({join(meet(aTy, builtinTypes->truthyType), bTy)})}
{}, );
arena.addTypePack({aTy, bTy}),
arena.addTypePack({join(meet(aTy, builtinTypes->truthyType), bTy)})
});
const TypeId truthyTy = builtinTypes->truthyType; const TypeId truthyTy = builtinTypes->truthyType;
TypeId actualFunctionTy = fn( TypeId actualFunctionTy = fn({truthyTy, truthyTy}, {join(meet(truthyTy, builtinTypes->truthyType), truthyTy)});
{truthyTy, truthyTy},
{join(meet(truthyTy, builtinTypes->truthyType), truthyTy)}
);
SubtypingResult result = isSubtype(genericFunctionTy, actualFunctionTy); 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") 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()); TypeId argTy = arena.freshType(builtinTypes, moduleScope.get());
FreeType* freeArg = getMutable<FreeType>(argTy); FreeType* freeArg = getMutable<FreeType>(argTy);

View file

@ -878,7 +878,7 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array")
TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch") TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
{ {
ScopedFastFlag _ {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}; ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
CheckResult result = check(R"( CheckResult result = check(R"(
--!strict --!strict
@ -889,26 +889,26 @@ TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
std::string expected; std::string expected;
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
expected = "Type\n\t" expected = "Type\n\t"
"'{ a: number, b: string, c: { d: string } }'\n" "'{ a: number, b: string, c: { d: string } }'\n"
"could not be converted into\n\t" "could not be converted into\n\t"
"'{ a: number, b: string, c: { d: number } }'; \n" "'{ 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 " "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`"; "type, and `string` is not exactly `number`";
else else
expected = "Type\n\t" expected = "Type\n\t"
"'{| a: number, b: string, c: {| d: string |} |}'\n" "'{| a: number, b: string, c: {| d: string |} |}'\n"
"could not be converted into\n\t" "could not be converted into\n\t"
"'{| a: number, b: string, c: {| d: number |} |}'\n" "'{| a: number, b: string, c: {| d: number |} |}'\n"
"caused by:\n " "caused by:\n "
"Property 'c' is not compatible.\n" "Property 'c' is not compatible.\n"
"Type\n\t" "Type\n\t"
"'{| d: string |}'\n" "'{| d: string |}'\n"
"could not be converted into\n\t" "could not be converted into\n\t"
"'{| d: number |}'\n" "'{| d: number |}'\n"
"caused by:\n " "caused by:\n "
"Property 'd' is not compatible.\n" "Property 'd' is not compatible.\n"
"Type 'string' could not be converted into 'number' in an invariant context"; "Type 'string' could not be converted into 'number' in an invariant context";
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
std::string actual = toString(result.errors[0]); std::string actual = toString(result.errors[0]);

View file

@ -14,7 +14,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit) LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauSimplifyOutOfLine) LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
@ -1725,7 +1725,7 @@ struct TFFixture
TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}}; TypeFunctionRuntime runtime{NotNull{&ice}, NotNull{&limits}};
const ScopedFastFlag sff[1] = { const ScopedFastFlag sff[1] = {
{FFlag::LuauEagerGeneralization, true}, {FFlag::LuauEagerGeneralization2, true},
}; };
BuiltinTypeFunctions builtinTypeFunctions; BuiltinTypeFunctions builtinTypeFunctions;
@ -1746,9 +1746,7 @@ TEST_CASE_FIXTURE(TFFixture, "refine<G, ~(false?)>")
{ {
TypeId g = arena->addType(GenericType{globalScope.get(), Polarity::Negative}); TypeId g = arena->addType(GenericType{globalScope.get(), Polarity::Negative});
TypeId refineTy = arena->addType(TypeFunctionInstanceType{ TypeId refineTy = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.refineFunc, {g, builtinTypes->truthyType}});
builtinTypeFunctions.refineFunc, {g, builtinTypes->truthyType}
});
FunctionGraphReductionResult res = reduceTypeFunctions(refineTy, Location{}, tfc); 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 aType = arena->freshType(builtinTypes, globalScope.get());
TypeId bType = arena->freshType(builtinTypes, globalScope.get()); TypeId bType = arena->freshType(builtinTypes, globalScope.get());
TypeId orType = arena->addType(TypeFunctionInstanceType{ TypeId orType = arena->addType(TypeFunctionInstanceType{builtinTypeFunctions.orFunc, {aType, bType}});
builtinTypeFunctions.orFunc, {aType, bType}
});
FunctionGraphReductionResult res = reduceTypeFunctions(orType, Location{}, tfc); FunctionGraphReductionResult res = reduceTypeFunctions(orType, Location{}, tfc);

View file

@ -9,8 +9,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests"); TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -1987,7 +1988,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// FIXME: CLI-151985 // FIXME: CLI-151985
// This test breaks because we can't see that eq<type?, b> is already fully reduced. // This test breaks because we can't see that eq<type?, b> is already fully reduced.
@ -2010,7 +2011,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string")
{ {
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true}; ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// FIXME: CLI-151985 // FIXME: CLI-151985
// This test breaks because we can't see that eq<type?, b> is already fully reduced. // This test breaks because we can't see that eq<type?, b> 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])); 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> = 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<nil> = { a = 2 }
local y: foo<string> = "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<T> = rawget<T, "a">
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<T> = rawget<T, "a">
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, B> = (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<T, U...> = (U...) -> T
type function foo(t)
return Test(types.number, types.string, t)
end
local x: foo<boolean>
)");
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<T, U> = setmetatable<T, U>
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<number, string> 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<string>
local x: foo<boolean>
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK(toString(requireType("x"), ToStringOptions{true}) == "boolean | number");
}
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -10,7 +10,6 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauRetainDefinitionAliasLocations)
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2) LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2) LUAU_FASTFLAG(LuauGuardAgainstMalformedTypeAliasExpansion2)
LUAU_FASTFLAG(LuauSkipMalformedTypeAliases) 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") TEST_CASE_FIXTURE(Fixture, "exported_alias_location_is_accessible_on_module")
{ {
ScopedFastFlag sff{FFlag::LuauRetainDefinitionAliasLocations, true};
CheckResult result = check(R"( CheckResult result = check(R"(
export type Value = string export type Value = string
)"); )");
@ -1235,7 +1232,6 @@ TEST_CASE_FIXTURE(Fixture, "exported_type_function_location_is_accessible_on_mod
{ {
ScopedFastFlag flags[] = { ScopedFastFlag flags[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauRetainDefinitionAliasLocations, true},
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -356,7 +356,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "replace_every_free_type_when_unifying_a_comp
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("any", toString(requireType("b"))); CHECK_EQ("any", toString(requireType("b")));
} }
TEST_CASE_FIXTURE(Fixture, "call_to_any_yields_any") TEST_CASE_FIXTURE(Fixture, "call_to_any_yields_any")

View file

@ -11,7 +11,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauTableCloneClonesType3) LUAU_FASTFLAG(LuauTableCloneClonesType3)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
TEST_SUITE_BEGIN("BuiltinTests"); TEST_SUITE_BEGIN("BuiltinTests");
@ -146,20 +146,19 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number, number) -> boolean'"
"'(number, number) -> boolean'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((string, string) -> boolean)?'"
"'((string, string) -> boolean)?'" "\ncaused by:\n"
"\ncaused by:\n" " None of the union options are compatible. For example:\n"
" None of the union options are compatible. For example:\n" "Type\n\t"
"Type\n\t" "'(number, number) -> boolean'"
"'(number, number) -> boolean'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(string, string) -> boolean'"
"'(string, string) -> boolean'" "\ncaused by:\n"
"\ncaused by:\n" " Argument #1 type is not compatible.\n"
" Argument #1 type is not compatible.\n" "Type 'string' could not be converted into 'number'";
"Type 'string' could not be converted into 'number'";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -460,7 +459,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_pack_reduce")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); 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"))); CHECK("{ [number]: string | string | string, n: number }" == toString(requireType("t")));
else if (FFlag::LuauSolverV2) else if (FFlag::LuauSolverV2)
CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t"))); CHECK_EQ("{ [number]: string, n: number }", toString(requireType("t")));

View file

@ -822,7 +822,8 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "cannot_index_a_class_with_no_indexer")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
CHECK_MESSAGE( CHECK_MESSAGE(
get<DynamicPropertyLookupOnExternTypesUnsafe>(result.errors[0]), "Expected DynamicPropertyLookupOnExternTypesUnsafe but got " << result.errors[0] get<DynamicPropertyLookupOnExternTypesUnsafe>(result.errors[0]),
"Expected DynamicPropertyLookupOnExternTypesUnsafe but got " << result.errors[0]
); );
CHECK(builtinTypes->errorType == requireType("c")); CHECK(builtinTypes->errorType == requireType("c"));

View file

@ -22,7 +22,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit) LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments) LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -1519,13 +1519,12 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number, number) -> string'"
"'(number, number) -> string'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(number) -> string'"
"'(number) -> string'" "\ncaused by:\n"
"\ncaused by:\n" " Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1543,14 +1542,13 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number, number) -> string'"
"'(number, number) -> string'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(number, string) -> string'"
"'(number, string) -> string'" "\ncaused by:\n"
"\ncaused by:\n" " Argument #2 type is not compatible.\n"
" Argument #2 type is not compatible.\n" "Type 'string' could not be converted into 'number'";
"Type 'string' could not be converted into 'number'";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1568,13 +1566,12 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number, number) -> number'"
"'(number, number) -> number'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> (number, boolean)'"
"'(number, number) -> (number, boolean)'" "\ncaused by:\n"
"\ncaused by:\n" " Function only returns 1 value, but 2 are required here";
" Function only returns 1 value, but 2 are required here";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1592,14 +1589,13 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number, number) -> string'"
"'(number, number) -> string'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> number'"
"'(number, number) -> number'" "\ncaused by:\n"
"\ncaused by:\n" " Return type is not compatible.\n"
" Return type is not compatible.\n" "Type 'string' could not be converted into 'number'";
"Type 'string' could not be converted into 'number'";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1617,14 +1613,13 @@ local b: B = a
)"); )");
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number, number) -> (number, string)'"
"'(number, number) -> (number, string)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(number, number) -> (number, boolean)'"
"'(number, number) -> (number, boolean)'" "\ncaused by:\n"
"\ncaused by:\n" " Return #2 type is not compatible.\n"
" Return #2 type is not compatible.\n" "Type 'string' could not be converted into 'boolean'";
"Type 'string' could not be converted into 'boolean'";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -1688,7 +1683,7 @@ t.f = function(x)
end end
)"); )");
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 // FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(3, result); LUAU_CHECK_ERROR_COUNT(3, result);
@ -1773,7 +1768,7 @@ t.f = function(x)
end end
)"); )");
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 // FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(2, result); 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") TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_call_site")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}};
{FFlag::LuauHasPropProperBlock, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}
};
CheckResult result = check(R"( CheckResult result = check(R"(
local t = {} local t = {}
@ -1975,7 +1967,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_
CHECK_EQ("<a>(a) -> a", toString(requireType("f"))); CHECK_EQ("<a>(a) -> a", toString(requireType("f")));
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
{ {
LUAU_CHECK_NO_ERRORS(result); LUAU_CHECK_NO_ERRORS(result);
CHECK("<a>({ read p: { read q: a } }) -> (a & ~(false?))?" == toString(requireType("g"))); CHECK("<a>({ 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. // 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); LUAU_REQUIRE_ERROR_COUNT(2, result);
auto tm1 = get<TypeMismatch>(result.errors[0]); auto tm1 = get<TypeMismatch>(result.errors[0]);

View file

@ -357,13 +357,12 @@ TEST_CASE_FIXTURE(Fixture, "table_intersection_write_sealed_indirect")
{ {
LUAU_REQUIRE_ERROR_COUNT(4, result); LUAU_REQUIRE_ERROR_COUNT(4, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(string, number) -> string'"
"'(string, number) -> string'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(string) -> string'\n"
"'(string) -> string'\n" "caused by:\n"
"caused by:\n" " Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'"); 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); LUAU_REQUIRE_ERROR_COUNT(4, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(string, number) -> string'"
"'(string, number) -> string'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(string) -> string'\n"
"'(string) -> string'\n" "caused by:\n"
"caused by:\n" " Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'"); CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'");
@ -428,15 +426,14 @@ local a: XYZ = 3
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type "
"Type " "'number'"
"'number'" " could not be converted into "
" could not be converted into " "'X & Y & Z'; \n"
"'X & Y & Z'; \n" "this is because \n\t"
"this is because \n\t" " * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\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 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`";
" * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
@ -527,14 +524,13 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type "
"Type " "'boolean & false'"
"'boolean & false'" " could not be converted into "
" could not be converted into " "'true'; \n"
"'true'; \n" "this is because \n\t"
"this is because \n\t" " * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\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`";
" * the 2nd component of the intersection is `false`, which is not a subtype of `true`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else else
@ -557,15 +553,14 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
// TODO: odd stringification of `false & (boolean & false)`.) // TODO: odd stringification of `false & (boolean & false)`.)
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type "
"Type " "'boolean & false & false'"
"'boolean & false & false'" " could not be converted into "
" could not be converted into " "'true'; \n"
"'true'; \n" "this is because \n\t"
"this is because \n\t" " * the 1st component of the intersection is `false`, which is not a subtype of `true`\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 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`";
" * the 3rd component of the intersection is `false`, which is not a subtype of `true`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else else
@ -640,13 +635,11 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'((number) -> number) & ((string) -> string)'"
"'((number) -> number) & ((string) -> string)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(boolean | number) -> boolean | number'; none of the intersection parts are compatible";
"'(boolean | number) -> boolean | number'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables") TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
@ -662,16 +655,15 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type "
"Type " "'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'" " could not be converted into "
" could not be converted into " "'{ p: nil }'; \n"
"'{ p: nil }'; \n" "this is because \n\t"
"this is because \n\t" " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
" * 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"
"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 "
" * 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`";
"accessing `p` results in `nil`, and `number` is not exactly `nil`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else else
@ -696,24 +688,23 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'{ p: number?, q: any } & { p: unknown, q: string? }'"
"'{ p: number?, q: any } & { p: unknown, q: string? }'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'{ p: string?, q: number? }'; \n"
"'{ p: string?, q: number? }'; \n" "this is because \n\t"
"this is because \n\t" " * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
" * 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"
"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 "
" * 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"
"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 "
" * 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"
"`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 "
" * 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"
"`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 "
" * 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"
"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 "
" * 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`";
"component of the union as `number`, and `string?` is not exactly `number`";
CHECK_EQ(expected, toString(result.errors[0])); CHECK_EQ(expected, toString(result.errors[0]));
} }
else else
@ -924,9 +915,9 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = "Type\n\t" const std::string expected = "Type\n\t"
"'((nil) -> unknown) & ((number) -> number)'" "'((nil) -> unknown) & ((number) -> number)'"
"\ncould not be converted into\n\t" "\ncould not be converted into\n\t"
"'(number?) -> number?'; none of the intersection parts are compatible"; "'(number?) -> number?'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0])); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'((number) -> number?) & ((unknown) -> string?)'"
"'((number) -> number?) & ((unknown) -> string?)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(number?) -> nil'; none of the intersection parts are compatible";
"'(number?) -> nil'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0])); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = "Type\n\t" const std::string expected = "Type\n\t"
"'((number?) -> (...number)) & ((string?) -> number | string)'" "'((number?) -> (...number)) & ((string?) -> number | string)'"
"\ncould not be converted into\n\t" "\ncould not be converted into\n\t"
"'(number | string) -> (number, number?)'; none of the intersection parts are compatible"; "'(number | string) -> (number, number?)'; none of the intersection parts are compatible";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
@ -1172,16 +1162,15 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'((a...) -> ()) & ((number, a...) -> number)'"
"'((a...) -> ()) & ((number, a...) -> number)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((a...) -> ()) & ((number, a...) -> number)'; \n"
"'((a...) -> ()) & ((number, a...) -> number)'; \n" "this is because \n\t"
"this is because \n\t" " * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
" * 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"
"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 "
" * 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...`";
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else else

View file

@ -1519,7 +1519,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish")
)")); )"));
CHECK_EQ("nil", toString(requireType("y"))); CHECK_EQ("nil", toString(requireType("y")));
} }
TEST_CASE_FIXTURE(Fixture, "ensure_local_in_loop_does_not_escape") 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"))); CHECK_EQ("number", toString(requireType("y")));
} }
TEST_SUITE_END(); TEST_SUITE_END();

View file

@ -13,7 +13,7 @@
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2) LUAU_FASTFLAG(LuauClipVariadicAnysFromArgsToGenericFuncs2)
@ -467,10 +467,9 @@ local b: B.T = a
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'; \n"
"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 "
"this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " "`number` is not exactly `string`";
"`number` is not exactly `string`";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else else
@ -514,10 +513,9 @@ local b: B.T = a
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'; \n"
"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 "
"this is because accessing `x` results in `number` in the former type and `string` in the latter type, and " "`number` is not exactly `string`";
"`number` is not exactly `string`";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else else
@ -787,7 +785,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "leaky_generics")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23}))); CHECK_EQ("(unknown) -> unknown", toString(requireTypeAtPosition({13, 23})));
} }

View file

@ -17,7 +17,7 @@
using namespace Luau; using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
TEST_SUITE_BEGIN("TypeInferOperators"); TEST_SUITE_BEGIN("TypeInferOperators");
@ -29,7 +29,7 @@ TEST_CASE_FIXTURE(Fixture, "or_joins_types")
)"); )");
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// FIXME: Regression // FIXME: Regression
CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); 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); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// FIXME: Regression. // FIXME: Regression.
CHECK("(string & ~(false?)) | number" == toString(*requireType("s"))); 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); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
{ {
// FIXME: Regression // FIXME: Regression
CHECK("(string & ~(false?)) | string" == toString(requireType("s"))); CHECK("(string & ~(false?)) | string" == toString(requireType("s")));
@ -634,7 +634,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typecheck_unary_minus_error")
local a = -foo local a = -foo
)"); )");
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
{ {
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);

View file

@ -10,7 +10,7 @@
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification) LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable) LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauWeakNilRefinementType) LUAU_FASTFLAG(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions) LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
@ -770,12 +770,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
LUAU_REQUIRE_NO_ERRORS(result); 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("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" CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
} }
else 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 // time, sometimes this is due to hitting the simplifier rather than
// normalization. // normalization.
CHECK("{ tag: \"exists\", x: string } & { x: ~(false?) }" == toString(requireTypeAtPosition({5, 28}))); 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 else
{ {
@ -2514,7 +2517,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
end end
)")); )"));
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24}))); CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24})));
else else
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24}))); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauWeakNilRefinementType, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauWeakNilRefinementType, true}
};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
type Part = { type Part = {
@ -2682,10 +2682,17 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function")
{ {
LUAU_REQUIRE_ERROR_COUNT(2, result); 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) 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 else
CHECK_EQ(toString(result.errors[1]), "Cannot call a value of type function"); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauBetterCannotCallFunctionPrimitive) 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 else
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type function"); CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type function");
} }

View file

@ -389,7 +389,8 @@ TEST_CASE_FIXTURE(Fixture, "table_properties_type_error_escapes")
LUAU_REQUIRE_ERROR_COUNT(1, result); 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])); CHECK(expected == toString(result.errors[0]));
} }
@ -460,11 +461,10 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expectedError = "Type\n\t" const std::string expectedError = "Type\n\t"
"'{ result: string, success: boolean }'" "'{ result: string, success: boolean }'"
"\ncould not be converted into\n\t" "\ncould not be converted into\n\t"
"'Err<number> | Ok<string>'"; "'Err<number> | Ok<string>'";
CHECK(toString(result.errors[0]) == expectedError); CHECK(toString(result.errors[0]) == expectedError);
} }
TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options") TEST_CASE_FIXTURE(Fixture, "if_then_else_expression_singleton_options")

View file

@ -21,8 +21,8 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering) LUAU_FASTFLAG(LuauFixIndexerSubtypingOrdering)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint) LUAU_FASTFLAG(DebugLuauAssertOnForcedConstraint)
LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert) LUAU_FASTFLAG(LuauBidirectionalInferenceElideAssert)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -702,7 +702,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization) if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2)
CHECK("<a>({a}) -> ()" == toString(requireType("swap"))); CHECK("<a>({a}) -> ()" == toString(requireType("swap")));
else if (FFlag::LuauSolverV2) else if (FFlag::LuauSolverV2)
CHECK("({unknown}) -> ()" == toString(requireType("swap"))); CHECK("({unknown}) -> ()" == toString(requireType("swap")));
@ -925,9 +925,9 @@ TEST_CASE_FIXTURE(Fixture, "sealed_table_indexers_must_unify")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
std::string expected = "Type '{number}' could not be converted into '{string}'; \n" 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, " "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`"; "and `number` is not exactly `string`";
auto actual = toString(result.errors[0]); auto actual = toString(result.errors[0]);
CHECK_EQ(expected, actual); CHECK_EQ(expected, actual);
} }
@ -2379,7 +2379,7 @@ TEST_CASE_FIXTURE(Fixture, "invariant_table_properties_means_instantiating_table
local c : string = t.m("hi") local c : string = t.m("hi")
)"); )");
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2) if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
{ {
// FIXME CLI-151985 // FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(2, result); 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[] = { ScopedFastFlag sff[] = {
{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauReportSubtypingErrors, true},
{FFlag::LuauEagerGeneralization, true}, {FFlag::LuauEagerGeneralization2, true},
{FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true} {FFlag::LuauNoMoreInjectiveTypeFunctions, true}
}; };
@ -4298,7 +4298,7 @@ TEST_CASE_FIXTURE(Fixture, "cli_84607_missing_prop_in_array_or_dict")
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
for (const auto& err: result.errors) for (const auto& err : result.errors)
{ {
const auto* error = get<MissingProperties>(err); const auto* error = get<MissingProperties>(err);
REQUIRE(error); REQUIRE(error);
@ -4644,9 +4644,7 @@ TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
return; return;
ScopedFastFlag sff[] = { ScopedFastFlag sff[] = {
{FFlag::LuauEagerGeneralization, true}, {FFlag::LuauEagerGeneralization2, true}, {FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true}
{FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}
}; };
CheckResult result = check(R"( CheckResult result = check(R"(
@ -4698,7 +4696,7 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array")
end end
)"); )");
if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization) if (FFlag::LuauSolverV2 && !FFlag::LuauEagerGeneralization2)
{ {
LUAU_CHECK_ERROR_COUNT(1, result); LUAU_CHECK_ERROR_COUNT(1, result);
LUAU_CHECK_ERROR(result, NotATable); 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); LUAU_REQUIRE_NO_ERRORS(result);
// FIXME CLI-114134. We need to simplify types more consistently. // FIXME CLI-114134. We need to simplify types more consistently.
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f"))); CHECK("({number} & {number}, unknown) -> ()" == toString(requireType("f")));
else else
CHECK_EQ("(unknown & {number} & {number}, unknown) -> ()", toString(requireType("f"))); 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) if (!FFlag::LuauSolverV2)
return; return;
ScopedFastFlag _{FFlag::LuauTableLiteralSubtypeSpecificCheck, true};
CheckResult result = check(R"( CheckResult result = check(R"(
type self = {} & {} type self = {} & {}
type Class = typeof(setmetatable()) type Class = typeof(setmetatable())
@ -5098,18 +5094,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "subtyping_with_a_metatable_table_path")
end 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<nil, nil> is uninhabited", toString(result.errors[0]));
CHECK_EQ(result.errors[1].location, Location{{3, 8}, {5, 11}});
CHECK_EQ("Type function instance setmetatable<nil, nil> is uninhabited", toString(result.errors[1]));
CHECK_EQ( CHECK_EQ(
"Type pack '{ @metatable { }, { } & { } }' could not be converted into 'Class'; \n" "Type pack '{ @metatable { }, { } & { } }' could not be converted into 'setmetatable<nil, nil>'; \n"
"this is because \n\t" "this is because the 1st entry in the type pack is `{ @metatable { }, { } & { } }` and in the 1st entry in the type packreduces to "
" * in the 1st entry in the type pack, the metatable portion is `{ }` in the former type and `nil` in the latter type, and `{ }` " "`never`, and `{ @metatable { }, { } & { } }` is not a subtype of `never`",
"is not a subtype of `nil`\n\t" toString(result.errors[2])
" * 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])
); );
} }
@ -5399,10 +5397,7 @@ TEST_CASE_FIXTURE(Fixture, "returning_mismatched_optional_in_table")
TEST_CASE_FIXTURE(Fixture, "optional_function_in_table") TEST_CASE_FIXTURE(Fixture, "optional_function_in_table")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}
};
LUAU_CHECK_NO_ERRORS(check(R"( LUAU_CHECK_NO_ERRORS(check(R"(
local t: { (() -> ())? } = { 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") TEST_CASE_FIXTURE(Fixture, "bigger_nested_table_causes_big_type_error")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauTableLiteralSubtypeSpecificCheck, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauTableLiteralSubtypeSpecificCheck, true}
};
auto result = check(R"( auto result = check(R"(
type File = { type File = {
@ -5794,7 +5786,7 @@ TEST_CASE_FIXTURE(Fixture, "large_table_inference_does_not_bleed")
local otherWords: { Word } = {"foo"} local otherWords: { Word } = {"foo"}
)"); )");
LUAU_REQUIRE_ERROR_COUNT(3, result); 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 that all of the errors are localized to `words`, not `otherWords`
CHECK(err.location.begin.line == 2); CHECK(err.location.begin.line == 2);
} }
@ -5809,10 +5801,7 @@ TEST_CASE_FIXTURE(Fixture, "extremely_large_table" * doctest::timeout(2.0))
{FFlag::LuauDisablePrimitiveInferenceInLargeTables, true}, {FFlag::LuauDisablePrimitiveInferenceInLargeTables, true},
}; };
const std::string source = const std::string source = "local res = {\n" + rep("\"foo\",\n", 100'000) + "}";
"local res = {\n" +
rep("\"foo\",\n", 100'000) +
"}";
LUAU_REQUIRE_NO_ERRORS(check(source)); LUAU_REQUIRE_NO_ERRORS(check(source));
CHECK_EQ("{string}", toString(requireType("res"), {true})); CHECK_EQ("{string}", toString(requireType("res"), {true}));
} }

View file

@ -28,7 +28,7 @@ LUAU_FASTFLAG(LuauTypeCheckerAcceptNumberConcats)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2) LUAU_FASTFLAG(LuauMagicFreezeCheckBlocked2)
LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions) LUAU_FASTFLAG(LuauNoMoreInjectiveTypeFunctions)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect) LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
LUAU_FASTFLAG(LuauHasPropProperBlock) LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauStringPartLengthLimit) LUAU_FASTFLAG(LuauStringPartLengthLimit)
@ -447,7 +447,7 @@ TEST_CASE_FIXTURE(Fixture, "check_expr_recursion_limit")
#endif #endif
ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100}; ScopedFastInt luauRecursionLimit{FInt::LuauRecursionLimit, limit + 100};
ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100}; ScopedFastInt luauCheckRecursionLimit{FInt::LuauCheckRecursionLimit, limit - 100};
ScopedFastFlag _{FFlag::LuauEagerGeneralization, false}; ScopedFastFlag _{FFlag::LuauEagerGeneralization2, false};
CheckResult result = check(R"(("foo"))" + rep(":lower()", limit)); 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") TEST_CASE_FIXTURE(BuiltinsFixture, "assert_table_freeze_constraint_solving")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauMagicFreezeCheckBlocked2, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauMagicFreezeCheckBlocked2, true}
};
LUAU_REQUIRE_NO_ERRORS(check(R"( LUAU_REQUIRE_NO_ERRORS(check(R"(
local f = table.freeze local f = table.freeze
f(table) 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") TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_assert_table_freeze_constraint_solving")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauMagicFreezeCheckBlocked2, true}};
{FFlag::LuauSolverV2, true},
{FFlag::LuauMagicFreezeCheckBlocked2, true}
};
// This is the original fuzzer version of the above issue. // This is the original fuzzer version of the above issue.
CheckResult results = check(R"( CheckResult results = check(R"(
local function l0() local function l0()
@ -2041,7 +2035,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert")
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauHasPropProperBlock, true},
{FFlag::LuauEagerGeneralization, true}, {FFlag::LuauEagerGeneralization2, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true},
@ -2079,7 +2073,7 @@ TEST_CASE_FIXTURE(Fixture, "fuzz_generalize_one_remove_type_assert_2")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauEagerGeneralization, true}, {FFlag::LuauEagerGeneralization2, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true},
@ -2116,7 +2110,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_simplify_combinatorial_explosion")
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauHasPropProperBlock, true},
{FFlag::LuauEagerGeneralization, true}, {FFlag::LuauEagerGeneralization2, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauStringPartLengthLimit, true}, {FFlag::LuauStringPartLengthLimit, true},
{FFlag::LuauSimplificationRecheckAssumption, true}, {FFlag::LuauSimplificationRecheckAssumption, true},

View file

@ -12,7 +12,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauInstantiateInSubtyping) LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall) LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
@ -100,7 +100,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function")
LUAU_REQUIRE_NO_ERRORS(result); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
CHECK_EQ("<a, b..., c...>((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply"))); CHECK_EQ("<a, b..., c...>((c...) -> (b...), (a) -> (c...), a) -> (b...)", toString(requireType("apply")));
else else
CHECK_EQ("<a, b..., c...>((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply"))); CHECK_EQ("<a, b..., c...>((b...) -> (c...), (a) -> (b...), a) -> (c...)", toString(requireType("apply")));
@ -966,13 +966,12 @@ a = b
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'() -> (number, ...boolean)'"
"'() -> (number, ...boolean)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'() -> (number, ...string)'; \n"
"'() -> (number, ...string)'; \n" "this is because it returns a tail of the variadic `boolean` in the former type and `string` in the latter "
"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`";
"type, and `boolean` is not a subtype of `string`";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }

View file

@ -8,7 +8,7 @@ LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType) LUAU_FASTFLAG(LuauDoNotAddUpvalueTypesToLocalType)
LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow) LUAU_FASTFLAG(LuauDfgIfBlocksShouldRespectControlFlow)
LUAU_FASTFLAG(LuauReportSubtypingErrors) LUAU_FASTFLAG(LuauReportSubtypingErrors)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauPreprocessTypestatedArgument) LUAU_FASTFLAG(LuauPreprocessTypestatedArgument)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops) LUAU_FASTFLAG(LuauDfgAllowUpdatesInLoops)
@ -418,7 +418,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true},
{FFlag::LuauReportSubtypingErrors, true}, {FFlag::LuauReportSubtypingErrors, true},
{FFlag::LuauEagerGeneralization, true}, {FFlag::LuauEagerGeneralization2, true},
{FFlag::LuauSubtypeGenericsAndNegations, true}, {FFlag::LuauSubtypeGenericsAndNegations, true},
{FFlag::LuauNoMoreInjectiveTypeFunctions, true}, {FFlag::LuauNoMoreInjectiveTypeFunctions, true},
}; };
@ -763,9 +763,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring")
TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop") TEST_CASE_FIXTURE(BuiltinsFixture, "refinement_through_erroring_in_loop")
{ {
ScopedFastFlag sffs[] = { ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true}, {FFlag::LuauSolverV2, true}, {FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true}, {FFlag::LuauDfgAllowUpdatesInLoops, true}
{FFlag::LuauDfgIfBlocksShouldRespectControlFlow, true},
{FFlag::LuauDfgAllowUpdatesInLoops,true}
}; };
CheckResult result = check(R"( CheckResult result = check(R"(

View file

@ -10,7 +10,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2) LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization) LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck) LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
TEST_SUITE_BEGIN("UnionTypes"); TEST_SUITE_BEGIN("UnionTypes");
@ -675,11 +675,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
// NOTE: union normalization will improve this message // NOTE: union normalization will improve this message
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(string) -> number'"
"'(string) -> number'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((number) -> string) | ((number) -> string)'; none of the union options are compatible";
"'((number) -> string) | ((number) -> string)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0])); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number, a...) -> (number?, a...)'"
"'(number, a...) -> (number?, a...)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible";
"'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0])); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number) -> number?'"
"'(number) -> number?'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible";
"'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0])); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'() -> number | string'"
"'() -> number | string'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(() -> (string, string)) | (() -> number)'; none of the union options are compatible";
"'(() -> (string, string)) | (() -> number)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0])); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(...nil) -> (...number?)'"
"'(...nil) -> (...number?)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible";
"'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0])); 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); LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2) if (FFlag::LuauSolverV2)
{ {
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'(number) -> ()'"
"'(number) -> ()'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'((...number?) -> ()) | ((number?) -> ())'";
"'((...number?) -> ()) | ((number?) -> ())'";
CHECK(expected == toString(result.errors[0])); CHECK(expected == toString(result.errors[0]));
} }
else else
@ -880,11 +874,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
LUAU_REQUIRE_ERROR_COUNT(1, result); LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = const std::string expected = "Type\n\t"
"Type\n\t" "'() -> (number?, ...number)'"
"'() -> (number?, ...number)'" "\ncould not be converted into\n\t"
"\ncould not be converted into\n\t" "'(() -> (...number)) | (() -> number)'; none of the union options are compatible";
"'(() -> (...number)) | (() -> number)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0])); 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); LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization) if (FFlag::LuauEagerGeneralization2)
CHECK_EQ( CHECK_EQ(
"<a>(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f")) "<a>(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))
); );
else else
CHECK_EQ( 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"))
); );
} }