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/Module.h"
#include "Luau/ModuleResolver.h"
#include "Luau/Normalize.h"
#include "Luau/NotNull.h"
#include "Luau/Polarity.h"
#include "Luau/Refinement.h"
#include "Luau/Symbol.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypeIds.h"
#include "Luau/TypeUtils.h"
#include <memory>
@ -93,7 +93,7 @@ struct ConstraintGenerator
std::vector<ConstraintPtr> constraints;
// 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.
DenseHashMap<Scope*, TypeId> scopeToFunction{nullptr};

View file

@ -3,7 +3,7 @@
#pragma once
#include "Luau/Constraint.h"
#include "Luau/DenseHash.h"
#include "Luau/TypeIds.h"
#include "Luau/Error.h"
#include <vector>
@ -18,7 +18,7 @@ struct ConstraintSet
std::vector<ConstraintPtr> constraints;
// 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
// dispatched all of the constraints pertaining to a particular free type,
@ -29,4 +29,4 @@ struct ConstraintSet
std::vector<TypeError> errors;
};
}
} // namespace Luau

View file

@ -456,6 +456,14 @@ public:
*/
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);
} // namespace Luau

View file

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

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.
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;
ErrorVec errors;
LintResult lintResult;

View file

@ -5,9 +5,9 @@
#include "Luau/NotNull.h"
#include "Luau/Set.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypeIds.h"
#include "Luau/UnifierSharedState.h"
#include <initializer_list>
#include <map>
#include <memory>
#include <unordered_map>
@ -39,57 +39,6 @@ bool isSubtype(
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
template<>
@ -302,7 +251,7 @@ struct NormalizedType
// we'd be reusing bad, stale data.
bool isCacheable = true;
NormalizedType(NotNull<BuiltinTypes> builtinTypes);
explicit NormalizedType(NotNull<BuiltinTypes> builtinTypes);
NormalizedType() = delete;
~NormalizedType() = default;

View file

@ -101,6 +101,8 @@ struct Scope
std::optional<std::vector<TypeId>> interiorFreeTypes;
std::optional<std::vector<TypePackId>> interiorFreeTypePacks;
NotNull<Scope> findNarrowestScopeContaining(Location);
};
// 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 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 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(
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(
SubtypingEnvironment& env,
const FunctionType* subFunction,
@ -267,7 +273,12 @@ private:
const NormalizedExternType& superExternType,
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(
SubtypingEnvironment& env,
const NormalizedStringType& subString,

View file

@ -34,6 +34,7 @@ using ScopePtr = std::shared_ptr<Scope>;
struct Module;
struct TypeFunction;
struct TypeFun;
struct Constraint;
struct Subtyping;
struct TypeChecker2;
@ -608,7 +609,8 @@ struct UserDefinedFunctionData
// References to AST elements are owned by the Module allocator which also stores this type
AstStatTypeFunction* definition = nullptr;
DenseHashMap<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 TxnLog;
struct ConstraintSolver;
struct TypeFunctionRuntimeBuilderState;
struct TypeFunctionContext;
class Normalizer;
using StateRef = std::unique_ptr<lua_State, void (*)(lua_State*)>;
@ -54,6 +56,9 @@ struct TypeFunctionRuntime
// Output created by 'print' function
std::vector<std::string> messages;
// Type builder, valid for the duration of a single evaluation
TypeFunctionRuntimeBuilderState* runtimeBuilder = nullptr;
private:
void prepareState();
};

View file

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

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);
/**
* @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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,7 +18,7 @@
#include <unordered_set>
LUAU_FASTINTVARIABLE(LuauIndentTypeMismatchMaxTypeLength, 10)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAGVARIABLE(LuauBetterCannotCallFunctionPrimitive)
@ -663,7 +663,7 @@ struct ErrorConverter
}
// binary operators
const auto binaryOps = FFlag::LuauEagerGeneralization ? kBinaryOps : DEPRECATED_kBinaryOps;
const auto binaryOps = FFlag::LuauEagerGeneralization2 ? kBinaryOps : DEPRECATED_kBinaryOps;
if (auto binaryString = binaryOps.find(tfit->function->name); binaryString != binaryOps.end())
{
std::string result = "Operator '" + std::string(binaryString->second) + "' could not be applied to operands of types ";
@ -718,10 +718,10 @@ struct ErrorConverter
"'";
}
if ((FFlag::LuauEagerGeneralization ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name))
if ((FFlag::LuauEagerGeneralization2 ? kUnreachableTypeFunctions : DEPRECATED_kUnreachableTypeFunctions).count(tfit->function->name))
{
return "Type function instance " + Luau::toString(e.ty) + " is uninhabited\n" +
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
"This is likely to be a bug, please report it at https://github.com/luau-lang/luau/issues";
}
// Everything should be specialized above to report a more descriptive error that hopefully does not mention "type functions" explicitly.

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -255,6 +255,29 @@ bool Scope::shouldWarnGlobal(std::string name) const
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)
{
while (right)

View file

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

View file

@ -19,16 +19,6 @@ LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
namespace Luau
{
static bool isRecord(const AstExprTable::Item& item)
{
if (item.kind == AstExprTable::Item::Record)
return true;
else if (item.kind == AstExprTable::Item::General && item.key->is<AstExprConstantString>())
return true;
else
return false;
}
TypeId matchLiteralType(
NotNull<DenseHashMap<const AstExpr*, TypeId>> astTypes,
NotNull<DenseHashMap<const AstExpr*, TypeId>> astExpectedTypes,

View file

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

View file

@ -1079,7 +1079,7 @@ void persist(TypeId ty)
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)
queue.push_back(prop.type());

View file

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

View file

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

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)));
}

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

View file

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

View file

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

View file

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

View file

@ -456,7 +456,7 @@ private:
AstType* annotation;
Position colonPosition;
explicit Binding(const Name& name, AstType* annotation = nullptr, Position colonPosition = {0,0})
explicit Binding(const Name& name, AstType* annotation = nullptr, Position colonPosition = {0, 0})
: name(name)
, annotation(annotation)
, colonPosition(colonPosition)

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())
, varsAnnotationColonPositions(varsAnnotationColonPositions)
, 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())
, annotationColonPosition(annotationColonPosition)
, 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())
, varsAnnotationColonPositions(varsAnnotationColonPositions)
, varsCommaPositions(varsCommaPositions)

View file

@ -709,7 +709,8 @@ AstStat* Parser::parseFor()
if (options.storeCstData)
{
if (FFlag::LuauStoreLocalAnnotationColonPositions)
cstNodeMap[node] = allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions));
cstNodeMap[node] =
allocator.alloc<CstStatForIn>(extractAnnotationColonPositions(names), varsCommaPosition, copy(valuesCommaPositions));
else
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 (FFlag::LuauStoreLocalAnnotationColonPositions)
cstNodeMap[node] = allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions));
cstNodeMap[node] =
allocator.alloc<CstStatLocal>(extractAnnotationColonPositions(names), varsCommaPositions, copy(valuesCommaPositions));
else
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;
nextLexeme();
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 (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
nextLexeme();
}
@ -1452,9 +1460,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
);
props.push_back(AstDeclaredExternTypeProperty{
propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())
});
}
}
else
@ -1533,9 +1541,9 @@ AstStat* Parser::parseDeclaration(const Location& start, const AstArray<AstAttr*
expectAndConsume(':', "property type annotation");
AstType* propType = parseType();
props.push_back(
AstDeclaredExternTypeProperty{propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())}
);
props.push_back(AstDeclaredExternTypeProperty{
propName->name, propName->location, propType, false, Location(propStart, lexer.previousLocation())
});
}
}
}
@ -2618,7 +2626,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
tableSeparator(),
lexer.current().location.begin,
allocator.alloc<CstExprConstantString>(sourceString, style, blockDepth),
stringPosition
stringPosition
});
}
else

View file

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

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

View file

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

View file

@ -15,6 +15,8 @@
#include <string.h>
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauUnrefExisting, false)
/*
* This file contains most implementations of core Lua APIs from lua.h.
*
@ -1445,9 +1447,26 @@ void lua_unref(lua_State* L, int ref)
global_State* g = L->global;
LuaTable* reg = hvalue(registry(L));
TValue* slot = luaH_setnum(L, reg, ref);
setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable
g->registryfree = ref;
if (DFFlag::LuauUnrefExisting)
{
const TValue* slot = luaH_getnum(reg, ref);
api_check(L, slot != luaO_nilobject);
// similar to how 'luaH_setnum' makes non-nil slot value mutable
TValue* mutableSlot = (TValue*)slot;
// NB: no barrier needed because value isn't collectable
setnvalue(mutableSlot, g->registryfree);
g->registryfree = ref;
}
else
{
TValue* slot = luaH_setnum(L, reg, ref);
setnvalue(slot, g->registryfree); // NB: no barrier needed because value isn't collectable
g->registryfree = ref;
}
}
void lua_setuserdatatag(lua_State* L, int idx, int tag)

View file

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

View file

@ -19,7 +19,8 @@
LUAU_FASTFLAG(LuauTraceTypesInNonstrictMode2)
LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauExpectedTypeVisitor)
using namespace Luau;
@ -41,6 +42,9 @@ struct ACFixtureImpl : BaseType
FrontendOptions opts;
opts.forAutocomplete = true;
opts.retainFullTypeGraphs = true;
// NOTE: Autocomplete does *not* require strict checking, meaning we should
// try to check all of these examples in `--!nocheck` mode.
this->configResolver.defaultConfig.mode = Mode::NoCheck;
this->frontend.check("MainModule", opts);
return Luau::autocomplete(this->frontend, "MainModule", Position{row, column}, nullCallback);
@ -51,6 +55,9 @@ struct ACFixtureImpl : BaseType
FrontendOptions opts;
opts.forAutocomplete = true;
opts.retainFullTypeGraphs = true;
// NOTE: Autocomplete does *not* require strict checking, meaning we should
// try to check all of these examples in `--!nocheck` mode.
this->configResolver.defaultConfig.mode = Mode::NoCheck;
this->frontend.check("MainModule", opts);
return Luau::autocomplete(this->frontend, "MainModule", getPosition(marker), callback);
@ -61,6 +68,9 @@ struct ACFixtureImpl : BaseType
FrontendOptions opts;
opts.forAutocomplete = true;
opts.retainFullTypeGraphs = true;
// NOTE: Autocomplete does *not* require strict checking, meaning we should
// try to check all of these examples in `--!nocheck` mode.
this->configResolver.defaultConfig.mode = Mode::NoCheck;
this->frontend.check(name, opts);
return Luau::autocomplete(this->frontend, name, pos, callback);
@ -103,7 +113,9 @@ struct ACFixtureImpl : BaseType
}
LUAU_ASSERT("Digit expected after @ symbol" && prevChar != '@');
return BaseType::check(filteredSource);
// NOTE: Autocomplete does *not* require strict checking, meaning we should
// try to check all of these examples in `--!nocheck` mode.
return BaseType::check(Mode::NoCheck, filteredSource, std::nullopt);
}
LoadDefinitionFileResult loadDefinition(const std::string& source)
@ -2191,7 +2203,10 @@ local fp: @1= f
if (FFlag::LuauSolverV2)
REQUIRE_EQ("({ x: number, y: number }) -> number", toString(requireType("f")));
else
REQUIRE_EQ("({| x: number, y: number |}) -> number", toString(requireType("f")));
{
// NOTE: All autocomplete tests occur under no-check mode.
REQUIRE_EQ("({| x: number, y: number |}) -> (...any)", toString(requireType("f")));
}
CHECK(ac.entryMap.count("({ x: number, y: number }) -> number"));
}
@ -3120,7 +3135,6 @@ TEST_CASE_FIXTURE(ACFixture, "autocomplete_string_singletons")
TEST_CASE_FIXTURE(ACFixture, "string_singleton_as_table_key_iso")
{
check(R"(
type Direction = "up" | "down"
local b: {[Direction]: boolean} = {["@2"] = true}
@ -4454,9 +4468,9 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
auto ac = autocomplete('1');
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization)
if (FFlag::LuauSolverV2 && FFlag::LuauEagerGeneralization2)
{
// This `if` statement is because `LuauEagerGeneralization`
// This `if` statement is because `LuauEagerGeneralization2`
// sets some flags
CHECK(ac.entryMap.count("BaseMethod") > 0);
CHECK(ac.entryMap.count("Method") > 0);
@ -4466,7 +4480,6 @@ TEST_CASE_FIXTURE(ACExternTypeFixture, "ac_dont_overflow_on_recursive_union")
// Otherwise, we don't infer anything for `value`, which is _fine_.
CHECK(ac.entryMap.empty());
}
}
TEST_CASE_FIXTURE(ACBuiltinsFixture, "type_function_has_types_definitions")
@ -4536,4 +4549,62 @@ end
CHECK_EQ(ac.entryMap.count("number"), 1);
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_for_assignment")
{
ScopedFastFlag _{FFlag::LuauExpectedTypeVisitor, true};
check(R"(
local function foobar(tbl: { tag: "left" | "right" })
tbl.tag = "@1"
end
)");
auto ac = autocomplete('1');
CHECK_EQ(ac.entryMap.count("left"), 1);
CHECK_EQ(ac.entryMap.count("right"), 1);
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_in_local_table")
{
ScopedFastFlag _{FFlag::LuauExpectedTypeVisitor, true};
check(R"(
type Entry = { field: number, prop: string }
local x : {Entry} = {}
x[1] = {
f@1,
p@2,
}
local t : { key1: boolean, thing2: CFrame, aaa3: vector } = {
k@3,
th@4,
}
)");
auto ac1 = autocomplete('1');
CHECK_EQ(ac1.entryMap.count("field"), 1);
auto ac2 = autocomplete('2');
CHECK_EQ(ac2.entryMap.count("prop"), 1);
auto ac3 = autocomplete('3');
CHECK_EQ(ac3.entryMap.count("key1"), 1);
auto ac4 = autocomplete('4');
CHECK_EQ(ac4.entryMap.count("thing2"), 1);
}
TEST_CASE_FIXTURE(ACFixture, "autocomplete_in_type_assertion")
{
ScopedFastFlag _{FFlag::LuauExpectedTypeVisitor, true};
check(R"(
type Entry = { field: number, prop: string }
return ( { f@1, p@2 } :: Entry )
)");
auto ac1 = autocomplete('1');
CHECK_EQ(ac1.entryMap.count("field"), 1);
auto ac2 = autocomplete('2');
CHECK_EQ(ac2.entryMap.count("prop"), 1);
}
TEST_SUITE_END();

View file

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

View file

@ -29,6 +29,7 @@ LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauForceAllNewSolverTests)
LUAU_FASTFLAG(LuauTypeFunOptional)
LUAU_FASTFLAG(LuauUpdateSetMetatableTypeSignature)
#define DOES_NOT_PASS_NEW_SOLVER_GUARD_IMPL(line) ScopedFastFlag sff_##line{FFlag::LuauSolverV2, FFlag::DebugLuauForceAllNewSolverTests};
@ -148,6 +149,7 @@ struct Fixture
// Most often those are changes related to builtin type definitions.
// In that case, flag can be forced to 'true' using the example below:
// ScopedFastFlag sff_LuauExampleFlagDefinition{FFlag::LuauExampleFlagDefinition, true};
ScopedFastFlag sff_LuauUpdateSetMetatableTypeSignature{FFlag::LuauUpdateSetMetatableTypeSignature, true};
// Arena freezing marks the `TypeArena`'s underlying memory as read-only, raising an access violation whenever you mutate it.
// This is useful for tracking down violations of Luau's memory model.

View file

@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauBlockDiffFragmentSelection)
LUAU_FASTFLAG(LuauFragmentAcMemoryLeak)
LUAU_FASTFLAG(LuauGlobalVariableModuleIsolation)
LUAU_FASTFLAG(LuauFragmentAutocompleteIfRecommendations)
LUAU_FASTFLAG(LuauPopulateRefinedTypesInFragmentFromOldSolver)
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 luauGlobalVariableModuleIsolation{FFlag::LuauGlobalVariableModuleIsolation, true};
ScopedFastFlag luauFragmentAutocompleteIfRecommendations{FFlag::LuauFragmentAutocompleteIfRecommendations, true};
ScopedFastFlag luauPopulateRefinedTypesInFragmentFromOldSolver{FFlag::LuauPopulateRefinedTypesInFragmentFromOldSolver, true};
FragmentAutocompleteFixtureImpl()
: BaseType(true)
@ -146,6 +148,37 @@ struct FragmentAutocompleteFixtureImpl : BaseType
return Luau::tryFragmentAutocomplete(this->frontend, "MainModule", cursorPos, context, nullCallback);
}
void autocompleteFragmentInNewSolver(
const std::string& document,
const std::string& updated,
Position cursorPos,
std::function<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(
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)
TEST_SUITE_END();

View file

@ -897,7 +897,8 @@ TEST_CASE_FIXTURE(FrontendFixture, "it_should_be_safe_to_stringify_errors_when_f
{
CHECK_EQ(
"Table type '{ count: string }' not compatible with type '{ Count: number }' because the former is missing field 'Count'",
toString(result.errors[0]));
toString(result.errors[0])
);
}
else
REQUIRE_EQ(

View file

@ -15,7 +15,7 @@
using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(DebugLuauForbidInternalTypes)
LUAU_FASTFLAG(LuauTrackInferredFunctionTypeFromCall)
@ -115,7 +115,8 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "dont_traverse_into_class_types_when_ge
{
auto [propTy, _] = freshType();
TypeId cursedExternType = arena.addType(ExternType{"Cursed", {{"oh_no", Property::readonly(propTy)}}, std::nullopt, std::nullopt, {}, {}, "", {}});
TypeId cursedExternType =
arena.addType(ExternType{"Cursed", {{"oh_no", Property::readonly(propTy)}}, std::nullopt, std::nullopt, {}, {}, "", {}});
auto genExternType = generalize(cursedExternType);
REQUIRE(genExternType);
@ -226,7 +227,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "('a) -> 'a")
TEST_CASE_FIXTURE(GeneralizationFixture, "(t1, (t1 <: 'b)) -> () where t1 = ('a <: (t1 <: 'b) & {number} & {number})")
{
ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true};
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true};
TableType tt;
tt.indexer = TableIndexer{builtinTypes.numberType, builtinTypes.numberType};
@ -260,7 +261,7 @@ TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: number | string)) -> string?")
TEST_CASE_FIXTURE(GeneralizationFixture, "(('a <: {'b})) -> ()")
{
ScopedFastFlag sff{FFlag::LuauEagerGeneralization, true};
ScopedFastFlag sff{FFlag::LuauEagerGeneralization2, true};
auto [aTy, aFree] = freshType();
auto [bTy, bFree] = freshType();
@ -341,10 +342,7 @@ end
TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_should_not_leak_free_type")
{
ScopedFastFlag sffs[] = {
{FFlag::DebugLuauForbidInternalTypes, true},
{FFlag::LuauTrackInferredFunctionTypeFromCall, true}
};
ScopedFastFlag sffs[] = {{FFlag::DebugLuauForbidInternalTypes, true}, {FFlag::LuauTrackInferredFunctionTypeFromCall, true}};
// This test case should just not assert
CheckResult result = check(R"(

View file

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

View file

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

View file

@ -16,6 +16,7 @@
#include <iostream>
LUAU_FASTFLAG(LuauNewNonStrictVisitTypes2)
LUAU_FASTFLAG(LuauNewNonStrictFixGenericTypePacks)
using namespace Luau;
@ -560,6 +561,18 @@ optionalArgsAtTheEnd1("a", nil, 3)
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "generic_type_packs_in_non_strict")
{
ScopedFastFlag sff{FFlag::LuauNewNonStrictFixGenericTypePacks, true};
CheckResult result = checkNonStrict(R"(
--!nonstrict
local test: <T...>(T...) -> () -- TypeError: Unknown type 'T'
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "optionals_in_checked_function_in_middle_cannot_be_omitted")
{
CheckResult result = checkNonStrict(R"(

View file

@ -14,7 +14,7 @@ LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTypeInferRecursionLimit)
LUAU_FASTINT(LuauNormalizeIntersectionLimit)
LUAU_FASTINT(LuauNormalizeUnionLimit)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauRefineWaitForBlockedTypesInTarget)
LUAU_FASTFLAG(LuauSimplifyOutOfLine)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -1206,7 +1206,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "fuzz_union_type_pack_cycle")
{FFlag::LuauNoMoreInjectiveTypeFunctions, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true},
{FFlag::LuauClipVariadicAnysFromArgsToGenericFuncs2, true},
{FFlag::LuauEagerGeneralization, true}
{FFlag::LuauEagerGeneralization2, true}
};
ScopedFastInt sfi{FInt::LuauTypeInferRecursionLimit, 0};

View file

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

View file

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

View file

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

View file

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

View file

@ -9,8 +9,9 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
LUAU_FASTFLAG(LuauUserTypeFunctionAliases)
TEST_SUITE_BEGIN("UserDefinedTypeFunctionTests");
@ -1987,7 +1988,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_bool")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
if (FFlag::LuauEagerGeneralization)
if (FFlag::LuauEagerGeneralization2)
{
// FIXME: CLI-151985
// This test breaks because we can't see that eq<type?, b> is already fully reduced.
@ -2010,7 +2011,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "udtf_singleton_equality_string")
{
ScopedFastFlag newSolver{FFlag::LuauSolverV2, true};
if (FFlag::LuauEagerGeneralization)
if (FFlag::LuauEagerGeneralization2)
{
// FIXME: CLI-151985
// This test breaks because we can't see that eq<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]));
}
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();

View file

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

View file

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

View file

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

View file

@ -822,7 +822,8 @@ TEST_CASE_FIXTURE(ExternTypeFixture, "cannot_index_a_class_with_no_indexer")
LUAU_REQUIRE_ERROR_COUNT(1, result);
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"));

View file

@ -22,7 +22,7 @@ LUAU_FASTFLAG(LuauInstantiateInSubtyping)
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTINT(LuauTarjanChildLimit)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauArityMismatchOnUndersaturatedUnknownArguments)
LUAU_FASTFLAG(LuauHasPropProperBlock)
LUAU_FASTFLAG(LuauOptimizeFalsyAndTruthyIntersect)
@ -1519,13 +1519,12 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number) -> string'"
"\ncaused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
const std::string expected = "Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number) -> string'"
"\ncaused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1543,14 +1542,13 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number, string) -> string'"
"\ncaused by:\n"
" Argument #2 type is not compatible.\n"
"Type 'string' could not be converted into 'number'";
const std::string expected = "Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number, string) -> string'"
"\ncaused by:\n"
" Argument #2 type is not compatible.\n"
"Type 'string' could not be converted into 'number'";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1568,13 +1566,12 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'(number, number) -> number'"
"\ncould not be converted into\n\t"
"'(number, number) -> (number, boolean)'"
"\ncaused by:\n"
" Function only returns 1 value, but 2 are required here";
const std::string expected = "Type\n\t"
"'(number, number) -> number'"
"\ncould not be converted into\n\t"
"'(number, number) -> (number, boolean)'"
"\ncaused by:\n"
" Function only returns 1 value, but 2 are required here";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1592,14 +1589,13 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number, number) -> number'"
"\ncaused by:\n"
" Return type is not compatible.\n"
"Type 'string' could not be converted into 'number'";
const std::string expected = "Type\n\t"
"'(number, number) -> string'"
"\ncould not be converted into\n\t"
"'(number, number) -> number'"
"\ncaused by:\n"
" Return type is not compatible.\n"
"Type 'string' could not be converted into 'number'";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1617,14 +1613,13 @@ local b: B = a
)");
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'(number, number) -> (number, string)'"
"\ncould not be converted into\n\t"
"'(number, number) -> (number, boolean)'"
"\ncaused by:\n"
" Return #2 type is not compatible.\n"
"Type 'string' could not be converted into 'boolean'";
const std::string expected = "Type\n\t"
"'(number, number) -> (number, string)'"
"\ncould not be converted into\n\t"
"'(number, number) -> (number, boolean)'"
"\ncaused by:\n"
" Return #2 type is not compatible.\n"
"Type 'string' could not be converted into 'boolean'";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1688,7 +1683,7 @@ t.f = function(x)
end
)");
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2)
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
{
// FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(3, result);
@ -1773,7 +1768,7 @@ t.f = function(x)
end
)");
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2)
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
{
// FIXME CLI-151985
LUAU_CHECK_ERROR_COUNT(2, result);
@ -1950,10 +1945,7 @@ TEST_CASE_FIXTURE(Fixture, "free_is_not_bound_to_unknown")
TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_call_site")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauHasPropProperBlock, true},
{FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}
};
ScopedFastFlag sffs[] = {{FFlag::LuauHasPropProperBlock, true}, {FFlag::LuauOptimizeFalsyAndTruthyIntersect, true}};
CheckResult result = check(R"(
local t = {}
@ -1975,7 +1967,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_infer_parameter_types_for_functions_from_their_
CHECK_EQ("<a>(a) -> a", toString(requireType("f")));
if (FFlag::LuauEagerGeneralization && FFlag::LuauSolverV2)
if (FFlag::LuauEagerGeneralization2 && FFlag::LuauSolverV2)
{
LUAU_CHECK_NO_ERRORS(result);
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.
if (FFlag::LuauEagerGeneralization)
if (FFlag::LuauEagerGeneralization2)
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
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);
const std::string expected =
"Type\n\t"
"'(string, number) -> string'"
"\ncould not be converted into\n\t"
"'(string) -> string'\n"
"caused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
const std::string expected = "Type\n\t"
"'(string, number) -> string'"
"\ncould not be converted into\n\t"
"'(string) -> string'\n"
"caused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'X & Y'");
@ -389,13 +388,12 @@ TEST_CASE_FIXTURE(Fixture, "table_write_sealed_indirect")
)");
LUAU_REQUIRE_ERROR_COUNT(4, result);
const std::string expected =
"Type\n\t"
"'(string, number) -> string'"
"\ncould not be converted into\n\t"
"'(string) -> string'\n"
"caused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
const std::string expected = "Type\n\t"
"'(string, number) -> string'"
"\ncould not be converted into\n\t"
"'(string) -> string'\n"
"caused by:\n"
" Argument count mismatch. Function expects 2 arguments, but only 1 is specified";
CHECK_EQ(expected, toString(result.errors[0]));
CHECK_EQ(toString(result.errors[1]), "Cannot add property 'z' to table 'XY'");
@ -428,15 +426,14 @@ local a: XYZ = 3
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type "
"'number'"
" could not be converted into "
"'X & Y & Z'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t"
" * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t"
" * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`";
const std::string expected = "Type "
"'number'"
" could not be converted into "
"'X & Y & Z'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `X`, and `number` is not a subtype of `X`\n\t"
" * the 2nd component of the intersection is `Y`, and `number` is not a subtype of `Y`\n\t"
" * the 3rd component of the intersection is `Z`, and `number` is not a subtype of `Z`";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -527,14 +524,13 @@ TEST_CASE_FIXTURE(Fixture, "intersect_bool_and_false")
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type "
"'boolean & false'"
" could not be converted into "
"'true'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
" * the 2nd component of the intersection is `false`, which is not a subtype of `true`";
const std::string expected = "Type "
"'boolean & false'"
" could not be converted into "
"'true'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
" * the 2nd component of the intersection is `false`, which is not a subtype of `true`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
@ -557,15 +553,14 @@ TEST_CASE_FIXTURE(Fixture, "intersect_false_and_bool_and_false")
// TODO: odd stringification of `false & (boolean & false)`.)
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type "
"'boolean & false & false'"
" could not be converted into "
"'true'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t"
" * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
" * the 3rd component of the intersection is `false`, which is not a subtype of `true`";
const std::string expected = "Type "
"'boolean & false & false'"
" could not be converted into "
"'true'; \n"
"this is because \n\t"
" * the 1st component of the intersection is `false`, which is not a subtype of `true`\n\t"
" * the 2nd component of the intersection is `boolean`, which is not a subtype of `true`\n\t"
" * the 3rd component of the intersection is `false`, which is not a subtype of `true`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
@ -640,13 +635,11 @@ TEST_CASE_FIXTURE(Fixture, "union_saturate_overloaded_functions")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'((number) -> number) & ((string) -> string)'"
"\ncould not be converted into\n\t"
"'(boolean | number) -> boolean | number'; none of the intersection parts are compatible";
const std::string expected = "Type\n\t"
"'((number) -> number) & ((string) -> string)'"
"\ncould not be converted into\n\t"
"'(boolean | number) -> boolean | number'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
@ -662,16 +655,15 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type "
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
" could not be converted into "
"'{ p: nil }'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t"
" * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `nil`, and `number` is not exactly `nil`";
const std::string expected = "Type "
"'{ p: number?, q: number?, r: number? } & { p: number?, q: string? }'"
" could not be converted into "
"'{ p: nil }'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `nil`, and `number` is not exactly `nil`\n\t"
" * in the 2nd component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `nil`, and `number` is not exactly `nil`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
@ -696,24 +688,23 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type\n\t"
"'{ p: number?, q: any } & { p: unknown, q: string? }'"
"\ncould not be converted into\n\t"
"'{ p: string?, q: number? }'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t"
" * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st "
"component of the union as `string`, and `number?` is not exactly `string`\n\t"
" * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in "
"`number?`, and `any` is not exactly `number?`\n\t"
" * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in "
"`string?`, and `unknown` is not exactly `string?`\n\t"
" * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and "
"accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t"
" * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st "
"component of the union as `number`, and `string?` is not exactly `number`";
const std::string expected = "Type\n\t"
"'{ p: number?, q: any } & { p: unknown, q: string? }'"
"\ncould not be converted into\n\t"
"'{ p: string?, q: number? }'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, accessing `p` has the 1st component of the union as `number` and "
"accessing `p` results in `string?`, and `number` is not exactly `string?`\n\t"
" * in the 1st component of the intersection, accessing `p` results in `number?` and accessing `p` has the 1st "
"component of the union as `string`, and `number?` is not exactly `string`\n\t"
" * in the 1st component of the intersection, accessing `q` results in `any` and accessing `q` results in "
"`number?`, and `any` is not exactly `number?`\n\t"
" * in the 2nd component of the intersection, accessing `p` results in `unknown` and accessing `p` results in "
"`string?`, and `unknown` is not exactly `string?`\n\t"
" * in the 2nd component of the intersection, accessing `q` has the 1st component of the union as `string` and "
"accessing `q` results in `number?`, and `string` is not exactly `number?`\n\t"
" * in the 2nd component of the intersection, accessing `q` results in `string?` and accessing `q` has the 1st "
"component of the union as `number`, and `string?` is not exactly `number`";
CHECK_EQ(expected, toString(result.errors[0]));
}
else
@ -924,9 +915,9 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_result")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = "Type\n\t"
"'((nil) -> unknown) & ((number) -> number)'"
"\ncould not be converted into\n\t"
"'(number?) -> number?'; none of the intersection parts are compatible";
"'((nil) -> unknown) & ((number) -> number)'"
"\ncould not be converted into\n\t"
"'(number?) -> number?'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -946,11 +937,10 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_unknown_arguments")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'((number) -> number?) & ((unknown) -> string?)'"
"\ncould not be converted into\n\t"
"'(number?) -> nil'; none of the intersection parts are compatible";
const std::string expected = "Type\n\t"
"'((number) -> number?) & ((unknown) -> string?)'"
"\ncould not be converted into\n\t"
"'(number?) -> nil'; none of the intersection parts are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -1075,9 +1065,9 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_overlapping_results_and_
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected = "Type\n\t"
"'((number?) -> (...number)) & ((string?) -> number | string)'"
"\ncould not be converted into\n\t"
"'(number | string) -> (number, number?)'; none of the intersection parts are compatible";
"'((number?) -> (...number)) & ((string?) -> number | string)'"
"\ncould not be converted into\n\t"
"'(number | string) -> (number, number?)'; none of the intersection parts are compatible";
CHECK(expected == toString(result.errors[0]));
}
@ -1172,16 +1162,15 @@ TEST_CASE_FIXTURE(Fixture, "overloadeded_functions_with_weird_typepacks_4")
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type\n\t"
"'((a...) -> ()) & ((number, a...) -> number)'"
"\ncould not be converted into\n\t"
"'((a...) -> ()) & ((number, a...) -> number)'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
"the latter type, and `()` is not a subtype of `number`\n\t"
" * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of "
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`";
const std::string expected = "Type\n\t"
"'((a...) -> ()) & ((number, a...) -> number)'"
"\ncould not be converted into\n\t"
"'((a...) -> ()) & ((number, a...) -> number)'; \n"
"this is because \n\t"
" * in the 1st component of the intersection, the function returns is `()` in the former type and `number` in "
"the latter type, and `()` is not a subtype of `number`\n\t"
" * in the 2nd component of the intersection, the function takes a tail of `a...` and in the 1st component of "
"the intersection, the function takes a tail of `a...`, and `a...` is not a supertype of `a...`";
CHECK(expected == toString(result.errors[0]));
}
else

View file

@ -1519,7 +1519,6 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "repeat_is_linearish")
)"));
CHECK_EQ("nil", toString(requireType("y")));
}
TEST_CASE_FIXTURE(Fixture, "ensure_local_in_loop_does_not_escape")
@ -1539,7 +1538,6 @@ TEST_CASE_FIXTURE(Fixture, "ensure_local_in_loop_does_not_escape")
)"));
CHECK_EQ("number", toString(requireType("y")));
}
TEST_SUITE_END();

View file

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

View file

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

View file

@ -10,7 +10,7 @@
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(DebugLuauEqSatSimplification)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauFunctionCallsAreNotNilable)
LUAU_FASTFLAG(LuauWeakNilRefinementType)
LUAU_FASTFLAG(LuauAddCallConstraintForIterableFunctions)
@ -770,12 +770,12 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "nonoptional_type_can_narrow_to_nil_if_sense_
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization)
if (FFlag::LuauEagerGeneralization2)
{
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({4, 24}))); // type(v) == "nil"
CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({6, 24}))); // type(v) ~= "nil"
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
CHECK("nil & string & unknown & unknown" == toString(requireTypeAtPosition({10, 24}))); // equivalent to type(v) == "nil"
CHECK("string & unknown & unknown & ~nil" == toString(requireTypeAtPosition({12, 24}))); // equivalent to type(v) ~= "nil"
}
else
@ -1273,7 +1273,10 @@ TEST_CASE_FIXTURE(Fixture, "discriminate_from_truthiness_of_x")
// time, sometimes this is due to hitting the simplifier rather than
// normalization.
CHECK("{ tag: \"exists\", x: string } & { x: ~(false?) }" == toString(requireTypeAtPosition({5, 28})));
CHECK(R"(({ tag: "exists", x: string } & { x: false? }) | ({ tag: "missing", x: nil } & { x: false? }))" == toString(requireTypeAtPosition({7, 28})));
CHECK(
R"(({ tag: "exists", x: string } & { x: false? }) | ({ tag: "missing", x: nil } & { x: false? }))" ==
toString(requireTypeAtPosition({7, 28}))
);
}
else
{
@ -2514,7 +2517,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "remove_recursive_upper_bound_when_generalizi
end
)"));
if (FFlag::LuauEagerGeneralization)
if (FFlag::LuauEagerGeneralization2)
CHECK_EQ("nil & string & unknown", toString(requireTypeAtPosition({4, 24})));
else
CHECK_EQ("nil", toString(requireTypeAtPosition({4, 24})));
@ -2636,10 +2639,7 @@ TEST_CASE_FIXTURE(Fixture, "oss_1687_equality_shouldnt_leak_nil")
TEST_CASE_FIXTURE(BuiltinsFixture, "oss_1451")
{
ScopedFastFlag sffs[] = {
{FFlag::LuauSolverV2, true},
{FFlag::LuauWeakNilRefinementType, true}
};
ScopedFastFlag sffs[] = {{FFlag::LuauSolverV2, true}, {FFlag::LuauWeakNilRefinementType, true}};
LUAU_REQUIRE_NO_ERRORS(check(R"(
type Part = {
@ -2682,10 +2682,17 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function")
{
LUAU_REQUIRE_ERROR_COUNT(2, result);
CHECK_EQ(toString(result.errors[0]), "Key 'Disconnect' is missing from 't2 where t1 = ExternScriptConnection | t2 | { Disconnect: (t1) -> (...any) } ; t2 = { disconnect: (t1) -> (...any) }' in the type 't1 where t1 = ExternScriptConnection | { Disconnect: (t1) -> (...any) } | { disconnect: (t1) -> (...any) }'");
CHECK_EQ(
toString(result.errors[0]),
"Key 'Disconnect' is missing from 't2 where t1 = ExternScriptConnection | t2 | { Disconnect: (t1) -> (...any) } ; t2 = { disconnect: "
"(t1) -> (...any) }' in the type 't1 where t1 = ExternScriptConnection | { Disconnect: (t1) -> (...any) } | { disconnect: (t1) -> "
"(...any) }'"
);
if (FFlag::LuauBetterCannotCallFunctionPrimitive)
CHECK_EQ(toString(result.errors[1]), "The type function is not precise enough for us to determine the appropriate result type of this call.");
CHECK_EQ(
toString(result.errors[1]), "The type function is not precise enough for us to determine the appropriate result type of this call."
);
else
CHECK_EQ(toString(result.errors[1]), "Cannot call a value of type function");
}
@ -2694,7 +2701,9 @@ TEST_CASE_FIXTURE(RefinementExternTypeFixture, "cannot_call_a_function")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauBetterCannotCallFunctionPrimitive)
CHECK_EQ(toString(result.errors[0]), "The type function is not precise enough for us to determine the appropriate result type of this call.");
CHECK_EQ(
toString(result.errors[0]), "The type function is not precise enough for us to determine the appropriate result type of this call."
);
else
CHECK_EQ(toString(result.errors[0]), "Cannot call a value of type function");
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@ using namespace Luau;
LUAU_FASTFLAG(LuauSolverV2)
LUAU_FASTFLAG(LuauEagerGeneralization)
LUAU_FASTFLAG(LuauEagerGeneralization2)
LUAU_FASTFLAG(LuauTableLiteralSubtypeSpecificCheck)
TEST_SUITE_BEGIN("UnionTypes");
@ -675,11 +675,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
LUAU_REQUIRE_ERROR_COUNT(1, result);
// NOTE: union normalization will improve this message
const std::string expected =
"Type\n\t"
"'(string) -> number'"
"\ncould not be converted into\n\t"
"'((number) -> string) | ((number) -> string)'; none of the union options are compatible";
const std::string expected = "Type\n\t"
"'(string) -> number'"
"\ncould not be converted into\n\t"
"'((number) -> string) | ((number) -> string)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -767,11 +766,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_mentioning_generic_typepacks")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'(number, a...) -> (number?, a...)'"
"\ncould not be converted into\n\t"
"'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible";
const std::string expected = "Type\n\t"
"'(number, a...) -> (number?, a...)'"
"\ncould not be converted into\n\t"
"'((number) -> number) | ((number?, a...) -> (number?, a...))'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -788,11 +786,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_arities")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'(number) -> number?'"
"\ncould not be converted into\n\t"
"'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible";
const std::string expected = "Type\n\t"
"'(number) -> number?'"
"\ncould not be converted into\n\t"
"'((number) -> nil) | ((number, string?) -> number)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -809,11 +806,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_arities")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'() -> number | string'"
"\ncould not be converted into\n\t"
"'(() -> (string, string)) | (() -> number)'; none of the union options are compatible";
const std::string expected = "Type\n\t"
"'() -> number | string'"
"\ncould not be converted into\n\t"
"'(() -> (string, string)) | (() -> number)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -830,11 +826,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_variadics")
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'(...nil) -> (...number?)'"
"\ncould not be converted into\n\t"
"'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible";
const std::string expected = "Type\n\t"
"'(...nil) -> (...number?)'"
"\ncould not be converted into\n\t"
"'((...string?) -> (...number)) | ((...string?) -> nil)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -850,11 +845,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_arg_variadics")
LUAU_REQUIRE_ERROR_COUNT(1, result);
if (FFlag::LuauSolverV2)
{
const std::string expected =
"Type\n\t"
"'(number) -> ()'"
"\ncould not be converted into\n\t"
"'((...number?) -> ()) | ((number?) -> ())'";
const std::string expected = "Type\n\t"
"'(number) -> ()'"
"\ncould not be converted into\n\t"
"'((...number?) -> ()) | ((number?) -> ())'";
CHECK(expected == toString(result.errors[0]));
}
else
@ -880,11 +874,10 @@ TEST_CASE_FIXTURE(Fixture, "union_of_functions_with_mismatching_result_variadics
LUAU_REQUIRE_ERROR_COUNT(1, result);
const std::string expected =
"Type\n\t"
"'() -> (number?, ...number)'"
"\ncould not be converted into\n\t"
"'(() -> (...number)) | (() -> number)'; none of the union options are compatible";
const std::string expected = "Type\n\t"
"'() -> (number?, ...number)'"
"\ncould not be converted into\n\t"
"'(() -> (...number)) | (() -> number)'; none of the union options are compatible";
CHECK_EQ(expected, toString(result.errors[0]));
}
@ -902,13 +895,14 @@ TEST_CASE_FIXTURE(Fixture, "less_greedy_unification_with_union_types")
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::LuauEagerGeneralization)
if (FFlag::LuauEagerGeneralization2)
CHECK_EQ(
"<a>(({ read x: a } & { x: number }) | ({ read x: a } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))
);
else
CHECK_EQ(
"(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }", toString(requireType("f"))
"(({ read x: unknown } & { x: number }) | ({ read x: unknown } & { x: string })) -> { x: number } | { x: string }",
toString(requireType("f"))
);
}