Sync to upstream/release/619 (#1218)

# What's Changed

## New Type Solver
- Many fixes to crashes, assertions, and hangs
- Binary type family aliases now have a default parameter
- Added a debug check for unsolved types escaping the constraint solver
- Overloaded functions are no longer inferred
- Unification creates additional subtyping constraints for blocked types
- Attempt to guess the result type for type families that are too large
to resolve timely

## Native Code Generation
- Fixed `IrCmd::CHECK_TRUTHY` lowering in a specific case
- Detailed compilation errors are now supported
- More work on the new allocator

---

# Internal Contributors

Co-authored-by: Aaron Weiss <aaronweiss@roblox.com>
Co-authored-by: Alexander McCord <amccord@roblox.com>
Co-authored-by: Andy Friesen <afriesen@roblox.com>
Co-authored-by: James McNellis <jmcnellis@roblox.com>
Co-authored-by: Lily Brown <lbrown@roblox.com>
Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
This commit is contained in:
Lily Brown 2024-03-30 16:14:44 -07:00 committed by GitHub
parent bac85116f6
commit 47ad768c69
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 1853 additions and 626 deletions

View file

@ -224,7 +224,6 @@ struct HasIndexerConstraint
// If the table is a free or unsealed table, we augment it with a new indexer.
struct SetIndexerConstraint
{
TypeId resultType;
TypeId subjectType;
TypeId indexType;
TypeId propType;
@ -256,6 +255,20 @@ struct UnpackConstraint
bool resultIsLValue = false;
};
// resultType ~ unpack sourceType
//
// The same as UnpackConstraint, but specialized for a pair of types as opposed to packs.
struct Unpack1Constraint
{
TypeId resultType;
TypeId sourceType;
// UnpackConstraint is sometimes used to resolve the types of assignments.
// When this is the case, any LocalTypes in resultPack can have their
// domains extended by the corresponding type from sourcePack.
bool resultIsLValue = false;
};
// resultType ~ T0 op T1 op ... op TN
//
// op is either union or intersection. If any of the input types are blocked,
@ -290,8 +303,8 @@ struct ReducePackConstraint
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint,
SetPropConstraint, HasIndexerConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint,
EqualityConstraint>;
SetPropConstraint, HasIndexerConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, Unpack1Constraint,
SetOpConstraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
struct Constraint
{

View file

@ -95,6 +95,10 @@ struct ConstraintGenerator
// will enqueue them during solving.
std::vector<ConstraintPtr> unqueuedConstraints;
// Type family instances created by the generator. This is used to ensure
// that these instances are reduced fully by the solver.
std::vector<TypeId> familyInstances;
// The private scope of type aliases for which the type parameters belong to.
DenseHashMap<const AstStatTypeAlias*, ScopePtr> astTypeAliasDefiningScopes{nullptr};
@ -254,16 +258,18 @@ private:
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
/**
* Generate constraints to assign assignedTy to the expression expr
* @returns the type of the expression. This may or may not be assignedTy itself.
*/
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy);
std::optional<TypeId> checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy);
TypeId updateProperty(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy);
struct LValueBounds
{
std::optional<TypeId> annotationTy;
std::optional<TypeId> assignedTy;
};
LValueBounds checkLValue(const ScopePtr& scope, AstExpr* expr, bool transform);
LValueBounds checkLValue(const ScopePtr& scope, AstExprLocal* local, bool transform);
LValueBounds checkLValue(const ScopePtr& scope, AstExprGlobal* global);
LValueBounds checkLValue(const ScopePtr& scope, AstExprIndexName* indexName);
LValueBounds checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
LValueBounds updateProperty(const ScopePtr& scope, AstExpr* expr);
struct FunctionSignature
{
@ -370,6 +376,8 @@ private:
* yields a vector of size 1, with value: [number | string]
*/
std::vector<std::optional<TypeId>> getExpectedCallTypesForFunctionOverloads(const TypeId fnType);
TypeId createFamilyInstance(TypeFamilyInstanceType instance, const ScopePtr& scope, Location location);
};
/** Borrow a vector of pointers from a vector of owning pointers to constraints.

View file

@ -25,6 +25,8 @@ enum class ValueContext;
struct DcrLogger;
class AstExpr;
// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
// never dereference this pointer.
using BlockedConstraintId = Variant<TypeId, TypePackId, const Constraint*>;
@ -73,6 +75,9 @@ struct ConstraintSolver
// A constraint can be both blocked and unsolved, for instance.
std::vector<NotNull<const Constraint>> unsolvedConstraints;
// This is a set of type families that need to be reduced after all constraints have been dispatched.
DenseHashSet<TypeId> familyInstances{nullptr};
// A mapping of constraint pointer to how many things the constraint is
// blocked on. Can be empty or 0 for constraints that are not blocked on
// anything.
@ -130,14 +135,23 @@ struct ConstraintSolver
bool tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const PrimitiveTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const HasPropConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatchHasIndexer(int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType);
bool tryDispatch(const HasIndexerConstraint& c, NotNull<const Constraint> constraint);
/// (dispatched, found) where
/// - dispatched: this constraint can be considered having dispatched.
/// - found: true if adding an indexer for a particular type was allowed.
std::pair<bool, bool> tryDispatchSetIndexer(NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds);
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue);
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const Unpack1Constraint& c, NotNull<const Constraint> constraint);
bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
@ -151,10 +165,10 @@ struct ConstraintSolver
bool tryDispatchIterableFunction(
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional = false, bool suppressSimplification = false);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet<TypeId>& seen);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(NotNull<const Constraint> constraint, TypeId subjectType,
const std::string& propName, ValueContext context, bool inConditional = false, bool suppressSimplification = false);
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(NotNull<const Constraint> constraint, TypeId subjectType,
const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet<TypeId>& seen);
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
/**
@ -242,17 +256,6 @@ struct ConstraintSolver
*/
bool hasUnresolvedConstraints(TypeId ty);
/**
* Creates a new Unifier and performs a single unification operation.
*
* @param subType the sub-type to unify.
* @param superType the super-type to unify.
* @returns true if the unification succeeded. False if the unification was
* too complex.
*/
template <typename TID>
bool unify(NotNull<Scope> scope, Location location, TID subType, TID superType);
/** Attempts to unify subTy with superTy. If doing so would require unifying
* BlockedTypes, fail and block the constraint on those BlockedTypes.
*

View file

@ -299,6 +299,7 @@ using MagicFunction = std::function<std::optional<WithPredicate<TypePackId>>(
struct MagicFunctionCallContext
{
NotNull<struct ConstraintSolver> solver;
NotNull<const Constraint> constraint;
const class AstExprCall* callSite;
TypePackId arguments;
TypePackId result;

View file

@ -13,6 +13,7 @@
#include "Luau/TypeFwd.h"
#include "Luau/VisitType.h"
#include "Luau/NotNull.h"
#include "TypeArena.h"
namespace Luau
{
@ -42,11 +43,14 @@ struct TypeFamilyReductionGuesser
DenseHashSet<TypeId> cyclicInstances{nullptr};
// Utilities
NotNull<TypeArena> arena;
NotNull<BuiltinTypes> builtins;
NotNull<Normalizer> normalizer;
TypeFamilyReductionGuesser(NotNull<BuiltinTypes> builtins, NotNull<Normalizer> normalizer);
TypeFamilyReductionGuesser(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, NotNull<Normalizer> normalizer);
std::optional<TypeId> guess(TypeId typ);
std::optional<TypePackId> guess(TypePackId typ);
TypeFamilyReductionGuessResult guessTypeFamilyReductionForFunction(const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy);
private:

View file

@ -2,11 +2,12 @@
#pragma once
#include "Luau/Constraint.h"
#include "Luau/DenseHash.h"
#include "Luau/NotNull.h"
#include "Luau/TypePairHash.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypePairHash.h"
#include <optional>
#include <vector>
@ -46,6 +47,8 @@ struct Unifier2
int recursionCount = 0;
int recursionLimit = 0;
std::vector<ConstraintV> incompleteSubtypes;
Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice);
/** Attempt to commit the subtype relation subTy <: superTy to the type
@ -61,6 +64,7 @@ struct Unifier2
* free TypePack to another and encounter an occurs check violation.
*/
bool unify(TypeId subTy, TypeId superTy);
bool unifyFreeWithType(TypeId subTy, TypeId superTy);
bool unify(const LocalType* subTy, TypeId superFn);
bool unify(TypeId subTy, const FunctionType* superFn);
bool unify(const UnionType* subUnion, TypeId superTy);

View file

@ -64,6 +64,9 @@ inline void unsee(DenseHashSet<void*>& seen, const void* tv)
} // namespace visit_detail
// recursion counter is equivalent here, but we'd like a better name to express the intent.
using TypeFamilyDepthCounter = RecursionCounter;
template<typename S>
struct GenericTypeVisitor
{
@ -72,6 +75,7 @@ struct GenericTypeVisitor
Set seen;
bool skipBoundTypes = false;
int recursionCounter = 0;
int typeFamilyDepth = 0;
GenericTypeVisitor() = default;
@ -400,6 +404,8 @@ struct GenericTypeVisitor
}
else if (auto tfit = get<TypeFamilyInstanceType>(ty))
{
TypeFamilyDepthCounter tfdc{&typeFamilyDepth};
if (visit(ty, *tfit))
{
for (TypeId p : tfit->typeArguments)
@ -460,6 +466,8 @@ struct GenericTypeVisitor
visit(tp, *btp);
else if (auto tfitp = get<TypeFamilyInstanceTypePack>(tp))
{
TypeFamilyDepthCounter tfdc{&typeFamilyDepth};
if (visit(tp, *tfitp))
{
for (TypeId t : tfitp->typeArguments)

View file

@ -431,7 +431,7 @@ static bool dcrMagicFunctionFormat(MagicFunctionCallContext context)
// unify the prefix one argument at a time
for (size_t i = 0; i < expected.size() && i + paramOffset < params.size(); ++i)
{
context.solver->unify(context.solver->rootScope, context.callSite->location, params[i + paramOffset], expected[i]);
context.solver->unify(context.constraint, params[i + paramOffset], expected[i]);
}
// if we know the argument count or if we have too many arguments for sure, we can issue an error
@ -561,7 +561,7 @@ static bool dcrMagicFunctionGmatch(MagicFunctionCallContext context)
if (returnTypes.empty())
return false;
context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType);
context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType);
const TypePackId emptyPack = arena->addTypePack({});
const TypePackId returnList = arena->addTypePack(returnTypes);
@ -630,13 +630,13 @@ static bool dcrMagicFunctionMatch(MagicFunctionCallContext context)
if (returnTypes.empty())
return false;
context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], context.solver->builtinTypes->stringType);
context.solver->unify(context.constraint, params[0], context.solver->builtinTypes->stringType);
const TypeId optionalNumber = arena->addType(UnionType{{context.solver->builtinTypes->nilType, context.solver->builtinTypes->numberType}});
size_t initIndex = context.callSite->self ? 1 : 2;
if (params.size() == 3 && context.callSite->args.size > initIndex)
context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber);
context.solver->unify(context.constraint, params[2], optionalNumber);
const TypePackId returnList = arena->addTypePack(returnTypes);
asMutable(context.result)->ty.emplace<BoundTypePack>(returnList);
@ -733,17 +733,17 @@ static bool dcrMagicFunctionFind(MagicFunctionCallContext context)
return false;
}
context.solver->unify(context.solver->rootScope, context.callSite->location, params[0], builtinTypes->stringType);
context.solver->unify(context.constraint, params[0], builtinTypes->stringType);
const TypeId optionalNumber = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->numberType}});
const TypeId optionalBoolean = arena->addType(UnionType{{builtinTypes->nilType, builtinTypes->booleanType}});
size_t initIndex = context.callSite->self ? 1 : 2;
if (params.size() >= 3 && context.callSite->args.size > initIndex)
context.solver->unify(context.solver->rootScope, context.callSite->location, params[2], optionalNumber);
context.solver->unify(context.constraint, params[2], optionalNumber);
if (params.size() == 4 && context.callSite->args.size > plainIndex)
context.solver->unify(context.solver->rootScope, context.callSite->location, params[3], optionalBoolean);
context.solver->unify(context.constraint, params[3], optionalBoolean);
returnTypes.insert(returnTypes.begin(), {optionalNumber, optionalNumber});

View file

@ -525,12 +525,11 @@ void ConstraintGenerator::applyRefinements(const ScopePtr& scope, Location locat
{
if (mustDeferIntersection(ty) || mustDeferIntersection(dt))
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.refineFamily},
{ty, dt},
{},
});
addConstraint(scope, location, ReduceConstraint{resultType});
}, scope, location);
ty = resultType;
}
@ -1005,8 +1004,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
{
Checkpoint check1 = checkpoint(this);
std::optional<TypeId> lvalueType = checkLValue(scope, indexName, generalizedType);
LUAU_ASSERT(lvalueType);
auto [_, lvalueType] = checkLValue(scope, indexName);
Checkpoint check2 = checkpoint(this);
forEachConstraint(check1, check2, this, [&excludeList](const ConstraintPtr& c) {
@ -1017,7 +1015,8 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
if (lvalueType && *lvalueType != generalizedType)
{
addConstraint(scope, indexName->location, SubtypeConstraint{*lvalueType, generalizedType});
LUAU_ASSERT(get<BlockedType>(lvalueType));
emplaceType<BoundType>(asMutable(*lvalueType), generalizedType);
}
}
else if (AstExprError* err = function->name->as<AstExprError>())
@ -1110,14 +1109,17 @@ static void bindFreeType(TypeId a, TypeId b)
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* assign)
{
std::vector<TypeId> assignees;
assignees.reserve(assign->vars.size);
std::vector<TypeId> upperBounds;
upperBounds.reserve(assign->vars.size);
std::vector<TypeId> typeStates;
typeStates.reserve(assign->vars.size);
Checkpoint lvalueBeginCheckpoint = checkpoint(this);
size_t i = 0;
for (AstExpr* lvalue : assign->vars)
{
TypeId assignee = arena->addType(BlockedType{});
// This is a really weird thing to do, but it's critically important for some kinds of
// assignments with the current type state behavior. Consider this code:
// local function f(l, r)
@ -1158,18 +1160,28 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
}
}
checkLValue(scope, lvalue, assignee, transform);
assignees.push_back(assignee);
auto [upperBound, typeState] = checkLValue(scope, lvalue, transform);
upperBounds.push_back(upperBound.value_or(builtinTypes->unknownType));
typeStates.push_back(typeState.value_or(builtinTypes->unknownType));
++i;
}
Checkpoint lvalueEndCheckpoint = checkpoint(this);
TypePackId resultPack = checkPack(scope, assign->values).tp;
auto c = addConstraint(scope, assign->location, UnpackConstraint{arena->addTypePack(assignees), resultPack, /*resultIsLValue*/ true});
for (TypeId assignee : assignees)
auto uc = addConstraint(scope, assign->location, UnpackConstraint{arena->addTypePack(typeStates), resultPack, /*resultIsLValue*/ true});
forEachConstraint(lvalueBeginCheckpoint, lvalueEndCheckpoint, this, [uc](const ConstraintPtr& constraint) {
uc->dependencies.push_back(NotNull{constraint.get()});
});
auto psc = addConstraint(scope, assign->location, PackSubtypeConstraint{resultPack, arena->addTypePack(std::move(upperBounds))});
psc->dependencies.push_back(uc);
for (TypeId assignee : typeStates)
{
auto blocked = getMutable<BlockedType>(assignee);
LUAU_ASSERT(blocked);
blocked->setOwner(c);
if (blocked && !blocked->getOwner())
blocked->setOwner(uc);
}
return ControlFlow::None;
@ -1180,7 +1192,21 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
AstExprBinary binop = AstExprBinary{assign->location, assign->op, assign->var, assign->value};
TypeId resultTy = check(scope, &binop).ty;
checkLValue(scope, assign->var, resultTy, true);
auto [upperBound, typeState] = checkLValue(scope, assign->var, true);
Constraint* sc = nullptr;
if (upperBound)
sc = addConstraint(scope, assign->location, SubtypeConstraint{resultTy, *upperBound});
if (typeState)
{
NotNull<Constraint> uc = addConstraint(scope, assign->location, Unpack1Constraint{*typeState, resultTy, /*resultIsLValue=*/ true});
if (auto blocked = getMutable<BlockedType>(*typeState); blocked && !blocked->getOwner())
blocked->setOwner(uc);
if (sc)
uc->dependencies.push_back(NotNull{sc});
}
DefId def = dfg->getDef(assign->var);
scope->lvalueTypes[def] = resultTy;
@ -2024,32 +2050,29 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprUnary* unary)
{
case AstExprUnary::Op::Not:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.notFamily},
{operandType},
{},
});
addConstraint(scope, unary->location, ReduceConstraint{resultType});
}, scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)};
}
case AstExprUnary::Op::Len:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.lenFamily},
{operandType},
{},
});
addConstraint(scope, unary->location, ReduceConstraint{resultType});
}, scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)};
}
case AstExprUnary::Op::Minus:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unmFamily},
{operandType},
{},
});
addConstraint(scope, unary->location, ReduceConstraint{resultType});
}, scope, unary->location);
return Inference{resultType, refinementArena.negation(refinement)};
}
default: // msvc can't prove that this is exhaustive.
@ -2065,153 +2088,138 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprBinary* binar
{
case AstExprBinary::Op::Add:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.addFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Sub:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.subFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Mul:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.mulFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Div:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.divFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::FloorDiv:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.idivFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Pow:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.powFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Mod:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.modFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Concat:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.concatFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::And:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.andFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Or:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.orFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareLt:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.ltFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareGe:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.ltFamily},
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareLe:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.leFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareGt:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.leFamily},
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::CompareEq:
case AstExprBinary::Op::CompareNe:
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.eqFamily},
{leftType, rightType},
{},
});
addConstraint(scope, binary->location, ReduceConstraint{resultType});
}, scope, binary->location);
return Inference{resultType, std::move(refinement)};
}
case AstExprBinary::Op::Op__Count:
@ -2365,31 +2373,29 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
}
}
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy, bool transform)
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, bool transform)
{
if (auto local = expr->as<AstExprLocal>())
return checkLValue(scope, local, assignedTy, transform);
return checkLValue(scope, local, transform);
else if (auto global = expr->as<AstExprGlobal>())
return checkLValue(scope, global, assignedTy);
return checkLValue(scope, global);
else if (auto indexName = expr->as<AstExprIndexName>())
return checkLValue(scope, indexName, assignedTy);
return checkLValue(scope, indexName);
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
return checkLValue(scope, indexExpr, assignedTy);
return checkLValue(scope, indexExpr);
else if (auto error = expr->as<AstExprError>())
{
check(scope, error);
return builtinTypes->errorRecoveryType();
return {builtinTypes->errorRecoveryType(), builtinTypes->errorRecoveryType()};
}
else
ice->ice("checkLValue is inexhaustive");
}
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, TypeId assignedTy, bool transform)
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, bool transform)
{
std::optional<TypeId> annotatedTy = scope->lookup(local->local);
LUAU_ASSERT(annotatedTy);
if (annotatedTy)
addConstraint(scope, local->location, SubtypeConstraint{assignedTy, *annotatedTy});
const DefId defId = dfg->getDef(local);
std::optional<TypeId> ty = scope->lookupUnrefinedType(defId);
@ -2430,41 +2436,48 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As
scope->lvalueTypes[defId] = *ty;
}
// TODO: Need to clip this, but this requires more code to be reworked first before we can clip this.
std::optional<TypeId> assignedTy;
if (transform)
{
Constraint* owner = nullptr;
if (auto blocked = get<BlockedType>(*ty))
owner = blocked->getOwner();
assignedTy = arena->addType(BlockedType{});
auto unpackC = addConstraint(scope, local->location,
UnpackConstraint{arena->addTypePack({*ty}), arena->addTypePack({assignedTy}),
UnpackConstraint{arena->addTypePack({*ty}), arena->addTypePack({*assignedTy}),
/*resultIsLValue*/ true});
if (owner)
unpackC->dependencies.push_back(NotNull{owner});
if (auto blocked = get<BlockedType>(*ty))
{
if (blocked->getOwner())
unpackC->dependencies.push_back(NotNull{blocked->getOwner()});
else if (auto blocked = getMutable<BlockedType>(*ty))
blocked->setOwner(unpackC);
}
recordInferredBinding(local->local, *ty);
}
return ty;
return {annotatedTy, assignedTy};
}
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId assignedTy)
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprGlobal* global)
{
return scope->lookup(Symbol{global->name});
std::optional<TypeId> annotatedTy = scope->lookup(Symbol{global->name});
if (annotatedTy)
return {annotatedTy, arena->addType(BlockedType{})};
else
return {annotatedTy, std::nullopt};
}
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId assignedTy)
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName)
{
return updateProperty(scope, indexName, assignedTy);
return updateProperty(scope, indexName);
}
std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId assignedTy)
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
{
return updateProperty(scope, indexExpr, assignedTy);
return updateProperty(scope, indexExpr);
}
/**
@ -2472,15 +2485,14 @@ std::optional<TypeId> ConstraintGenerator::checkLValue(const ScopePtr& scope, As
*
* If expr has the form name.a.b.c
*/
TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr, TypeId assignedTy)
ConstraintGenerator::LValueBounds ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr)
{
// There are a bunch of cases where we realize that this is not the kind of
// assignment that potentially changes the shape of a table. When we
// encounter them, we call this to fall back and do the "usual thing."
auto fallback = [&]() {
auto fallback = [&]() -> LValueBounds {
TypeId resTy = check(scope, expr).ty;
addConstraint(scope, expr->location, SubtypeConstraint{assignedTy, resTy});
return resTy;
return {resTy, std::nullopt};
};
LUAU_ASSERT(expr->is<AstExprIndexName>() || expr->is<AstExprIndexExpr>());
@ -2499,15 +2511,14 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr,
// a.b.c[1] = 44 -- lvalue
// a.b[4].c = 2 -- rvalue
TypeId resultType = arena->addType(BlockedType{});
TypeId subjectType = check(scope, indexExpr->expr).ty;
TypeId indexType = check(scope, indexExpr->index).ty;
auto c = addConstraint(scope, expr->location, SetIndexerConstraint{resultType, subjectType, indexType, assignedTy});
getMutable<BlockedType>(resultType)->setOwner(c);
TypeId assignedTy = arena->addType(BlockedType{});
addConstraint(scope, expr->location, SetIndexerConstraint{subjectType, indexType, assignedTy});
module->astTypes[expr] = assignedTy;
return assignedTy;
return {assignedTy, assignedTy};
}
Symbol sym;
@ -2573,6 +2584,7 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr,
std::vector<std::string> segmentStrings(begin(segments), end(segments));
TypeId updatedType = arena->addType(BlockedType{});
TypeId assignedTy = arena->addType(BlockedType{});
auto setC = addConstraint(scope, expr->location, SetPropConstraint{updatedType, subjectType, std::move(segmentStrings), assignedTy});
getMutable<BlockedType>(updatedType)->setOwner(setC);
@ -2604,7 +2616,7 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr,
}
}
return assignedTy;
return {assignedTy, assignedTy};
}
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
@ -3287,24 +3299,22 @@ void ConstraintGenerator::reportCodeTooComplex(Location location)
TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.unionFamily},
{lhs, rhs},
{},
});
addConstraint(scope, location, ReduceConstraint{resultType});
}, scope, location);
return resultType;
}
TypeId ConstraintGenerator::makeIntersect(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
{
TypeId resultType = arena->addType(TypeFamilyInstanceType{
TypeId resultType = createFamilyInstance(TypeFamilyInstanceType{
NotNull{&kBuiltinTypeFamilies.intersectFamily},
{lhs, rhs},
{},
});
addConstraint(scope, location, ReduceConstraint{resultType});
}, scope, location);
return resultType;
}
@ -3454,6 +3464,14 @@ std::vector<std::optional<TypeId>> ConstraintGenerator::getExpectedCallTypesForF
return expectedTypes;
}
TypeId ConstraintGenerator::createFamilyInstance(TypeFamilyInstanceType instance, const ScopePtr& scope, Location location)
{
TypeId result = arena->addType(std::move(instance));
addConstraint(scope, location, ReduceConstraint{result});
familyInstances.push_back(result);
return result;
}
std::vector<NotNull<Constraint>> borrowConstraints(const std::vector<ConstraintPtr>& constraints)
{
std::vector<NotNull<Constraint>> result;

View file

@ -273,9 +273,10 @@ struct InstantiationQueuer : TypeOnceVisitor
return false;
}
bool visit(TypeId ty, const TypeFamilyInstanceType& tfit) override
bool visit(TypeId ty, const TypeFamilyInstanceType&) override
{
solver->pushConstraint(scope, location, ReduceConstraint{ty});
solver->familyInstances.insert(ty);
return true;
}
@ -454,6 +455,16 @@ void ConstraintSolver::run()
progress |= runSolverPass(true);
} while (progress);
for (TypeId instance : familyInstances)
{
if (FFlag::DebugLuauLogSolver)
printf("Post-solve family reduction of %s\n", toString(instance).c_str());
TypeCheckLimits limits{};
FamilyGraphReductionResult result =
reduceFamilies(instance, Location{}, TypeFamilyContext{arena, builtinTypes, rootScope, normalizer, NotNull{&iceReporter}, NotNull{&limits}}, false);
}
if (FFlag::DebugLuauLogSolver)
{
dumpBindings(rootScope, opts);
@ -479,29 +490,6 @@ struct TypeAndLocation
Location location;
};
struct FreeTypeSearcher : TypeOnceVisitor
{
VecDeque<TypeAndLocation>* result;
Location location;
FreeTypeSearcher(VecDeque<TypeAndLocation>* result, Location location)
: result(result)
, location(location)
{
}
bool visit(TypeId ty, const FreeType&) override
{
result->push_back({ty, location});
return false;
}
bool visit(TypeId, const ClassType&) override
{
return false;
}
};
} // namespace
bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
@ -534,7 +522,7 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
else if (auto hpc = get<HasPropConstraint>(*constraint))
success = tryDispatch(*hpc, constraint);
else if (auto spc = get<SetPropConstraint>(*constraint))
success = tryDispatch(*spc, constraint, force);
success = tryDispatch(*spc, constraint);
else if (auto spc = get<HasIndexerConstraint>(*constraint))
success = tryDispatch(*spc, constraint);
else if (auto spc = get<SetIndexerConstraint>(*constraint))
@ -543,6 +531,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
success = tryDispatch(*sottc, constraint);
else if (auto uc = get<UnpackConstraint>(*constraint))
success = tryDispatch(*uc, constraint);
else if (auto uc = get<Unpack1Constraint>(*constraint))
success = tryDispatch(*uc, constraint);
else if (auto soc = get<SetOpConstraint>(*constraint))
success = tryDispatch(*soc, constraint, force);
else if (auto rc = get<ReduceConstraint>(*constraint))
@ -626,7 +616,10 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
unblock(c.sourceType, constraint->location);
for (TypeId ty : c.interiorTypes)
{
u2.generalize(ty);
unblock(ty, constraint->location);
}
return true;
}
@ -1075,7 +1068,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
if (ftv)
{
if (ftv->dcrMagicFunction)
usedMagic = ftv->dcrMagicFunction(MagicFunctionCallContext{NotNull(this), c.callSite, c.argsPack, result});
usedMagic = ftv->dcrMagicFunction(MagicFunctionCallContext{NotNull{this}, constraint, c.callSite, c.argsPack, result});
if (ftv->dcrMagicRefinement)
ftv->dcrMagicRefinement(MagicRefinementContext{constraint->scope, c.callSite, c.discriminantTypes});
@ -1292,7 +1285,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFamilyInstanceType>(subjectType))
return block(subjectType, constraint);
auto [blocked, result] = lookupTableProp(subjectType, c.prop, c.context, c.inConditional, c.suppressSimplification);
auto [blocked, result] = lookupTableProp(constraint, subjectType, c.prop, c.context, c.inConditional, c.suppressSimplification);
if (!blocked.empty())
{
for (TypeId blocked : blocked)
@ -1381,7 +1374,7 @@ static void updateTheTableType(
tt->props[lastSegment].setType(replaceTy);
}
bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force)
bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint)
{
TypeId subjectType = follow(c.subjectType);
const TypeId propType = follow(c.propType);
@ -1403,7 +1396,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
ValueContext ctx = i == c.path.size() - 1 ? ValueContext::LValue : ValueContext::RValue;
auto [blocked, result] = lookupTableProp(*existingPropType, segment, ctx);
auto [blocked, result] = lookupTableProp(constraint, *existingPropType, segment, ctx);
if (!blocked.empty())
{
for (TypeId blocked : blocked)
@ -1420,8 +1413,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
if (existingPropType)
{
unify(constraint->scope, constraint->location, propType, *existingPropType);
unify(constraint->scope, constraint->location, *existingPropType, propType);
unify(constraint, propType, *existingPropType);
unify(constraint, *existingPropType, propType);
bind(c.resultType, c.subjectType);
unblock(c.resultType, constraint->location);
return true;
@ -1640,63 +1633,79 @@ bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull<const
return tryDispatchHasIndexer(recursionDepth, constraint, subjectType, indexType, c.resultType);
}
std::pair<bool, bool> ConstraintSolver::tryDispatchSetIndexer(NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds)
{
if (isBlocked(subjectType))
return {block(subjectType, constraint), false};
if (auto tt = getMutable<TableType>(subjectType))
{
if (tt->indexer)
{
unify(constraint, indexType, tt->indexer->indexType);
// We have a `BoundType` check here because we must mutate only our owning `BlockedType`, not some other constraint's `BlockedType`.
// TODO: We should rather have a `bool mutateProp` parameter that is set to false if we're traversing a union or intersection type.
// The union or intersection type themselves should be the one to mutate the `propType`, not each or first `TableType` in a union/intersection type.
//
// Fixing this requires fixing other ones first.
if (!get<BoundType>(propType) && get<BlockedType>(propType))
emplaceType<BoundType>(asMutable(propType), tt->indexer->indexResultType);
return {true, true};
}
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
{
tt->indexer = TableIndexer{indexType, propType};
return {true, true};
}
else
return {true, false};
}
else if (auto ft = getMutable<FreeType>(subjectType); ft && expandFreeTypeBounds)
{
// Setting an indexer on some fresh type means we use that fresh type in a negative position.
// Therefore, we only care about the upper bound.
//
// We'll extend the upper bound if we could dispatch, but could not find a table type to update the indexer.
auto [dispatched, found] = tryDispatchSetIndexer(constraint, ft->upperBound, indexType, propType, /*expandFreeTypeBounds=*/ false);
if (dispatched && !found)
{
TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()});
TableType* tt2 = getMutable<TableType>(tableTy);
tt2->indexer = TableIndexer{indexType, propType};
ft->upperBound = simplifyIntersection(builtinTypes, arena, ft->upperBound, tableTy).result; // TODO: intersect type family or a constraint.
}
return {dispatched, found};
}
else if (auto it = get<IntersectionType>(subjectType))
{
std::pair<bool, bool> result{true, true};
for (TypeId part : it)
{
auto [dispatched, found] = tryDispatchSetIndexer(constraint, part, indexType, propType, expandFreeTypeBounds);
result.first &= dispatched;
result.second &= found;
}
return result;
}
return {true, false};
}
bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
{
TypeId subjectType = follow(c.subjectType);
if (isBlocked(subjectType))
return block(subjectType, constraint);
if (auto ft = get<FreeType>(subjectType))
{
Scope* scope = ft->scope;
TableType* tt = &asMutable(subjectType)->ty.emplace<TableType>(TableState::Free, TypeLevel{}, scope);
tt->indexer = TableIndexer{c.indexType, c.propType};
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
TypeId propType = freshType(arena, builtinTypes, scope);
asMutable(c.propType)->ty.emplace<BoundType>(propType);
auto [dispatched, found] = tryDispatchSetIndexer(constraint, subjectType, c.indexType, c.propType, /*expandFreeTypeBounds=*/ true);
if (dispatched && found)
unblock(c.propType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
}
else if (auto tt = get<TableType>(subjectType))
{
if (tt->indexer)
{
if (isBlocked(tt->indexer->indexResultType))
return block(tt->indexer->indexResultType, constraint);
// TODO This probably has to be invariant.
unify(constraint, c.indexType, tt->indexer->indexType);
asMutable(c.propType)->ty.emplace<BoundType>(tt->indexer->indexResultType);
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
unblock(c.propType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
}
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
{
TypeId promotedIndexTy = freshType(arena, builtinTypes, tt->scope);
unify(constraint, c.indexType, promotedIndexTy);
auto mtt = getMutable<TableType>(subjectType);
mtt->indexer = TableIndexer{promotedIndexTy, c.propType};
TypeId propType = freshType(arena, builtinTypes, tt->scope);
asMutable(c.propType)->ty.emplace<BoundType>(propType);
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
unblock(c.propType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
}
// Do not augment sealed or generic tables that lack indexers
}
asMutable(c.propType)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
asMutable(c.resultType)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
unblock(c.propType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
return dispatched;
}
bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint)
@ -1719,33 +1728,12 @@ bool ConstraintSolver::tryDispatch(const SingletonOrTopTypeConstraint& c, NotNul
return true;
}
bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint)
bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultTy, TypeId srcTy, bool resultIsLValue)
{
TypePackId sourcePack = follow(c.sourcePack);
TypePackId resultPack = follow(c.resultPack);
if (isBlocked(sourcePack))
return block(sourcePack, constraint);
if (isBlocked(resultPack))
{
LUAU_ASSERT(canMutate(resultPack, constraint));
LUAU_ASSERT(resultPack != sourcePack);
asMutable(resultPack)->ty.emplace<BoundTypePack>(sourcePack);
unblock(resultPack, constraint->location);
return true;
}
TypePack srcPack = extendTypePack(*arena, builtinTypes, sourcePack, size(resultPack));
auto resultIter = begin(resultPack);
auto resultEnd = end(resultPack);
auto apply = [&](TypeId resultTy, TypeId srcTy) {
resultTy = follow(resultTy);
LUAU_ASSERT(canMutate(resultTy, constraint));
if (auto lt = getMutable<LocalType>(resultTy); c.resultIsLValue && lt)
if (auto lt = getMutable<LocalType>(resultTy); resultIsLValue && lt)
{
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result;
LUAU_ASSERT(lt->blockCount > 0);
@ -1772,12 +1760,35 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
}
else
{
LUAU_ASSERT(c.resultIsLValue);
LUAU_ASSERT(resultIsLValue);
unify(constraint, resultTy, srcTy);
}
unblock(resultTy, constraint->location);
};
return true;
}
bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint)
{
TypePackId sourcePack = follow(c.sourcePack);
TypePackId resultPack = follow(c.resultPack);
if (isBlocked(sourcePack))
return block(sourcePack, constraint);
if (isBlocked(resultPack))
{
LUAU_ASSERT(canMutate(resultPack, constraint));
LUAU_ASSERT(resultPack != sourcePack);
asMutable(resultPack)->ty.emplace<BoundTypePack>(sourcePack);
unblock(resultPack, constraint->location);
return true;
}
TypePack srcPack = extendTypePack(*arena, builtinTypes, sourcePack, size(resultPack));
auto resultIter = begin(resultPack);
auto resultEnd = end(resultPack);
size_t i = 0;
while (resultIter != resultEnd)
@ -1795,10 +1806,10 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
if (auto ut = getMutable<UnionType>(resultTy))
{
for (auto opt : ut->options)
apply(opt, srcTy);
tryDispatchUnpack1(constraint, opt, srcTy, c.resultIsLValue);
}
else
apply(resultTy, srcTy);
tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue);
}
else
unify(constraint, resultTy, srcTy);
@ -1836,6 +1847,11 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
return true;
}
bool ConstraintSolver::tryDispatch(const Unpack1Constraint& c, NotNull<const Constraint> constraint)
{
return tryDispatchUnpack1(constraint, c.resultType, c.sourceType, c.resultIsLValue);
}
bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force)
{
bool blocked = false;
@ -1914,8 +1930,8 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force)
{
unify(constraint->scope, constraint->location, c.resultType, c.assignmentType);
unify(constraint->scope, constraint->location, c.assignmentType, c.resultType);
unify(constraint, c.resultType, c.assignmentType);
unify(constraint, c.assignmentType, c.resultType);
return true;
}
@ -2034,7 +2050,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
LUAU_ASSERT(nextFn);
const TypePackId nextRetPack = nextFn->retTypes;
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack});
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/ true});
return true;
}
else
@ -2115,15 +2131,15 @@ bool ConstraintSolver::tryDispatchIterableFunction(
return true;
}
std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(
TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification)
std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(NotNull<const Constraint> constraint, TypeId subjectType,
const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification)
{
DenseHashSet<TypeId> seen{nullptr};
return lookupTableProp(subjectType, propName, context, inConditional, suppressSimplification, seen);
return lookupTableProp(constraint, subjectType, propName, context, inConditional, suppressSimplification, seen);
}
std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(
TypeId subjectType, const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet<TypeId>& seen)
std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(NotNull<const Constraint> constraint, TypeId subjectType,
const std::string& propName, ValueContext context, bool inConditional, bool suppressSimplification, DenseHashSet<TypeId>& seen)
{
if (seen.contains(subjectType))
return {};
@ -2197,7 +2213,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
}
else if (auto mt = get<MetatableType>(subjectType); mt && context == ValueContext::RValue)
{
auto [blocked, result] = lookupTableProp(mt->table, propName, context, inConditional, suppressSimplification, seen);
auto [blocked, result] = lookupTableProp(constraint, mt->table, propName, context, inConditional, suppressSimplification, seen);
if (!blocked.empty() || result)
return {blocked, result};
@ -2228,10 +2244,10 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
}
}
else
return lookupTableProp(indexType, propName, context, inConditional, suppressSimplification, seen);
return lookupTableProp(constraint, indexType, propName, context, inConditional, suppressSimplification, seen);
}
else if (get<MetatableType>(mtt))
return lookupTableProp(mtt, propName, context, inConditional, suppressSimplification, seen);
return lookupTableProp(constraint, mtt, propName, context, inConditional, suppressSimplification, seen);
}
else if (auto ct = get<ClassType>(subjectType))
{
@ -2251,14 +2267,14 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
if (indexProp == metatable->props.end())
return {{}, std::nullopt};
return lookupTableProp(indexProp->second.type(), propName, context, inConditional, suppressSimplification, seen);
return lookupTableProp(constraint, indexProp->second.type(), propName, context, inConditional, suppressSimplification, seen);
}
else if (auto ft = get<FreeType>(subjectType))
{
const TypeId upperBound = follow(ft->upperBound);
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
return lookupTableProp(upperBound, propName, context, inConditional, suppressSimplification, seen);
return lookupTableProp(constraint, upperBound, propName, context, inConditional, suppressSimplification, seen);
// TODO: The upper bound could be an intersection that contains suitable tables or classes.
@ -2279,7 +2295,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
break;
}
unify(scope, Location{}, subjectType, newUpperBound);
unify(constraint, subjectType, newUpperBound);
return {{}, propType};
}
@ -2290,7 +2306,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
for (TypeId ty : utv)
{
auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, context, inConditional, suppressSimplification, seen);
auto [innerBlocked, innerResult] = lookupTableProp(constraint, ty, propName, context, inConditional, suppressSimplification, seen);
blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
if (innerResult)
options.insert(*innerResult);
@ -2319,7 +2335,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
for (TypeId ty : itv)
{
auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, context, inConditional, suppressSimplification, seen);
auto [innerBlocked, innerResult] = lookupTableProp(constraint, ty, propName, context, inConditional, suppressSimplification, seen);
blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
if (innerResult)
options.insert(*innerResult);
@ -2353,39 +2369,39 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
return {{}, std::nullopt};
}
template <typename TID>
bool ConstraintSolver::unify(NotNull<Scope> scope, Location location, TID subType, TID superType)
template<typename TID>
bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
{
Unifier2 u2{NotNull{arena}, builtinTypes, scope, NotNull{&iceReporter}};
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
const bool ok = u2.unify(subType, superType);
const bool ok = u2.unify(subTy, superTy);
for (ConstraintV& c : u2.incompleteSubtypes)
{
NotNull<Constraint> addition = pushConstraint(constraint->scope, constraint->location, std::move(c));
inheritBlocks(constraint, addition);
}
if (ok)
{
for (const auto& [expanded, additions] : u2.expandedFreeTypes)
{
for (TypeId addition : additions)
upperBoundContributors[expanded].push_back(std::make_pair(location, addition));
upperBoundContributors[expanded].push_back(std::make_pair(constraint->location, addition));
}
}
else
{
reportError(OccursCheckFailed{}, location);
reportError(OccursCheckFailed{}, constraint->location);
return false;
}
unblock(subType, location);
unblock(superType, location);
unblock(subTy, constraint->location);
unblock(superTy, constraint->location);
return true;
}
template<typename TID>
bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
{
return unify(constraint->scope, constraint->location, subTy, superTy);
}
void ConstraintSolver::bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, NotNull<const Constraint> constraint)
{
resultTy = follow(resultTy);
@ -2761,9 +2777,6 @@ LUAU_NOINLINE void ConstraintSolver::throwUserCancelError()
}
// Instantiate private template implementations for external callers
template bool ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypeId subType, TypeId superType);
template bool ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypePackId subType, TypePackId superType);
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypeId subTy, TypeId superTy);
template bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TypePackId subTy, TypePackId superTy);

View file

@ -27,6 +27,10 @@ void collectOperands(DefId def, std::vector<DefId>* operands)
operands->push_back(def);
else if (auto phi = get<Phi>(def))
{
// A trivial phi node has no operands to populate, so we push this definition in directly.
if (phi->operands.empty())
return operands->push_back(def);
for (const Def* operand : phi->operands)
collectOperands(NotNull{operand}, operands);
}

View file

@ -14,10 +14,12 @@
#include "Luau/Scope.h"
#include "Luau/StringUtils.h"
#include "Luau/TimeTrace.h"
#include "Luau/TypeArena.h"
#include "Luau/TypeChecker2.h"
#include "Luau/NonStrictTypeChecker.h"
#include "Luau/TypeInfer.h"
#include "Luau/Variant.h"
#include "Luau/VisitType.h"
#include <algorithm>
#include <chrono>
@ -35,6 +37,7 @@ LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
LUAU_FASTFLAGVARIABLE(DebugLuauForbidInternalTypes, false)
namespace Luau
{
@ -1160,6 +1163,56 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
std::move(prepareModuleScope), options, limits, recordJsonLog, writeJsonLog);
}
struct InternalTypeFinder : TypeOnceVisitor
{
bool visit(TypeId, const ClassType&) override
{
return false;
}
bool visit(TypeId, const BlockedType&) override
{
LUAU_ASSERT(false);
return false;
}
bool visit(TypeId, const FreeType&) override
{
LUAU_ASSERT(false);
return false;
}
bool visit(TypeId, const PendingExpansionType&) override
{
LUAU_ASSERT(false);
return false;
}
bool visit(TypeId, const LocalType&) override
{
LUAU_ASSERT(false);
return false;
}
bool visit(TypePackId, const BlockedTypePack&) override
{
LUAU_ASSERT(false);
return false;
}
bool visit(TypePackId, const FreeTypePack&) override
{
LUAU_ASSERT(false);
return false;
}
bool visit(TypePackId, const TypeFamilyInstanceTypePack&) override
{
LUAU_ASSERT(false);
return false;
}
};
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
@ -1201,12 +1254,15 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
cg.visitModuleRoot(sourceModule.root);
result->errors = std::move(cg.errors);
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cg.rootScope), borrowConstraints(cg.constraints), result->name, moduleResolver,
requireCycles, logger.get(), limits};
ConstraintSolver cs{NotNull{&normalizer}, NotNull(cg.rootScope), borrowConstraints(cg.constraints), result->name, moduleResolver, requireCycles,
logger.get(), limits};
if (options.randomizeConstraintResolutionSeed)
cs.randomize(*options.randomizeConstraintResolutionSeed);
for (TypeId ty : cg.familyInstances)
cs.familyInstances.insert(ty);
try
{
cs.run();
@ -1236,7 +1292,33 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
result->type = sourceModule.type;
result->upperBoundContributors = std::move(cs.upperBoundContributors);
result->clonePublicInterface(builtinTypes, *iceHandler);
if (FFlag::DebugLuauForbidInternalTypes)
{
InternalTypeFinder finder;
finder.traverse(result->returnType);
for (const auto& [_, binding] : result->exportedTypeBindings)
finder.traverse(binding.type);
for (const auto& [_, ty] : result->astTypes)
finder.traverse(ty);
for (const auto& [_, ty] : result->astExpectedTypes)
finder.traverse(ty);
for (const auto& [_, tp] : result->astTypePacks)
finder.traverse(tp);
for (const auto& [_, ty] : result->astResolvedTypes)
finder.traverse(ty);
for (const auto& [_, ty] : result->astOverloadResolvedTypes)
finder.traverse(ty);
for (const auto& [_, tp] : result->astResolvedTypePacks)
finder.traverse(tp);
}
if (result->timeout || result->cancelled)
{
@ -1259,6 +1341,9 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
Luau::check(builtinTypes, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get());
}
unfreeze(result->interfaceTypes);
result->clonePublicInterface(builtinTypes, *iceHandler);
// It would be nice if we could freeze the arenas before doing type
// checking, but we'll have to do some work to get there.
//
@ -1295,8 +1380,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
}
catch (const InternalCompilerError& err)
{
InternalCompilerError augmented = err.location.has_value()
? InternalCompilerError{err.message, sourceModule.name, *err.location}
InternalCompilerError augmented = err.location.has_value() ? InternalCompilerError{err.message, sourceModule.name, *err.location}
: InternalCompilerError{err.message, sourceModule.name};
throw augmented;
}

View file

@ -196,9 +196,6 @@ Module::~Module()
void Module::clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice)
{
LUAU_ASSERT(interfaceTypes.types.empty());
LUAU_ASSERT(interfaceTypes.typePacks.empty());
CloneState cloneState{builtinTypes};
ScopePtr moduleScope = getModuleScope();

View file

@ -430,6 +430,10 @@ bool Normalizer::isInhabited(const NormalizedType* norm)
bool Normalizer::isInhabited(const NormalizedType* norm, Set<TypeId>& seen)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return false;
// If normalization failed, the type is complex, and so is more likely than not to be inhabited.
if (!norm)
return true;
@ -473,6 +477,10 @@ bool Normalizer::isInhabited(TypeId ty)
bool Normalizer::isInhabited(TypeId ty, Set<TypeId>& seen)
{
RecursionCounter _rc(&sharedState->counters.recursionCount);
if (!withinResourceLimits())
return false;
// TODO: use log.follow(ty), CLI-64291
ty = follow(ty);
@ -2999,6 +3007,7 @@ bool Normalizer::intersectNormalWithTy(NormalizedType& here, TypeId there, Set<T
void makeTableShared(TypeId ty)
{
ty = follow(ty);
if (auto tableTy = getMutable<TableType>(ty))
{
for (auto& [_, prop] : tableTy->props)

View file

@ -363,6 +363,31 @@ void OverloadResolver::add(Analysis analysis, TypeId ty, ErrorVec&& errors)
}
}
// we wrap calling the overload resolver in a separate function to reduce overall stack pressure in `solveFunctionCall`.
// this limits the lifetime of `OverloadResolver`, a large type, to only as long as it is actually needed.
std::optional<TypeId> selectOverload(
NotNull<BuiltinTypes> builtinTypes,
NotNull<TypeArena> arena,
NotNull<Normalizer> normalizer,
NotNull<Scope> scope,
NotNull<InternalErrorReporter> iceReporter,
NotNull<TypeCheckLimits> limits,
const Location& location,
TypeId fn,
TypePackId argsPack
)
{
OverloadResolver resolver{builtinTypes, arena, normalizer, scope, iceReporter, limits, location};
auto [status, overload] = resolver.selectOverload(fn, argsPack);
if (status == OverloadResolver::Analysis::Ok)
return overload;
if (get<AnyType>(fn) || get<FreeType>(fn))
return fn;
return {};
}
SolveResult solveFunctionCall(
NotNull<TypeArena> arena,
@ -376,17 +401,8 @@ SolveResult solveFunctionCall(
TypePackId argsPack
)
{
OverloadResolver resolver{
builtinTypes, NotNull{arena}, normalizer, scope, iceReporter, limits, location};
auto [status, overload] = resolver.selectOverload(fn, argsPack);
TypeId overloadToUse = fn;
if (status == OverloadResolver::Analysis::Ok)
overloadToUse = overload;
else if (get<AnyType>(fn) || get<FreeType>(fn))
{
// Nothing. Let's keep going
}
else
std::optional<TypeId> overloadToUse = selectOverload(builtinTypes, arena, normalizer, scope, iceReporter, limits, location, fn, argsPack);
if (!overloadToUse)
return {SolveResult::NoMatchingOverload};
TypePackId resultPack = arena->freshTypePack(scope);
@ -394,7 +410,7 @@ SolveResult solveFunctionCall(
TypeId inferredTy = arena->addType(FunctionType{TypeLevel{}, scope.get(), argsPack, resultPack});
Unifier2 u2{NotNull{arena}, builtinTypes, scope, iceReporter};
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
const bool occursCheckPassed = u2.unify(*overloadToUse, inferredTy);
if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
{
@ -416,7 +432,7 @@ SolveResult solveFunctionCall(
result.typePackId = resultPack;
LUAU_ASSERT(overloadToUse);
result.overloadToUse = overloadToUse;
result.overloadToUse = *overloadToUse;
result.inferredTy = inferredTy;
result.expandedFreeTypes = std::move(u2.expandedFreeTypes);

View file

@ -654,7 +654,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
{
for (size_t i = headSize; i < superHead.size(); ++i)
results.push_back(isCovariantWith(env, vt->ty, superHead[i])
.withSubComponent(TypePath::TypeField::Variadic)
.withSubPath(TypePath::PathBuilder().tail().variadic().build())
.withSuperComponent(TypePath::Index{i}));
}
else if (auto gt = get<GenericTypePack>(*subTail))

View file

@ -21,6 +21,7 @@
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAGVARIABLE(LuauToStringSimpleCompositeTypesSingleLine, false)
LUAU_FASTFLAGVARIABLE(LuauStringifyCyclesRootedAtPacks, false)
/*
* Enables increasing levels of verbosity for Luau type names when stringifying.
@ -1491,11 +1492,22 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
else
tvs.stringify(tp);
if (FFlag::LuauStringifyCyclesRootedAtPacks)
{
if (!cycles.empty() || !cycleTPs.empty())
{
result.cycle = true;
state.emit(" where ");
}
}
else
{
if (!cycles.empty())
{
result.cycle = true;
state.emit(" where ");
}
}
state.exhaustive = true;
@ -1521,6 +1533,32 @@ ToStringResult toStringDetailed(TypePackId tp, ToStringOptions& opts)
semi = true;
}
if (FFlag::LuauStringifyCyclesRootedAtPacks)
{
std::vector<std::pair<TypePackId, std::string>> sortedCycleTpNames{state.cycleTpNames.begin(), state.cycleTpNames.end()};
std::sort(sortedCycleTpNames.begin(), sortedCycleTpNames.end(), [](const auto& a, const auto& b) {
return a.second < b.second;
});
TypePackStringifier tps{tvs.state};
for (const auto& [cycleTp, name] : sortedCycleTpNames)
{
if (semi)
state.emit(" ; ");
state.emit(name);
state.emit(" = ");
Luau::visit(
[&tps, cycleTp = cycleTp](auto t) {
return tps(cycleTp, t);
},
cycleTp->ty);
semi = true;
}
}
if (opts.maxTypeLength > 0 && result.name.length() > opts.maxTypeLength)
{
result.name += "... *TRUNCATED*";
@ -1721,7 +1759,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
{
std::string subStr = tos(c.subPack);
std::string superStr = tos(c.superPack);
return subStr + " <: " + superStr;
return subStr + " <...: " + superStr;
}
else if constexpr (std::is_same_v<T, GeneralizationConstraint>)
{
@ -1782,7 +1820,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
}
else if constexpr (std::is_same_v<T, SetIndexerConstraint>)
{
return tos(c.resultType) + " ~ setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType);
return "setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType);
}
else if constexpr (std::is_same_v<T, SingletonOrTopTypeConstraint>)
{
@ -1795,7 +1833,9 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
return result + " ~ if isSingleton D then D else unknown where D = " + discriminant;
}
else if constexpr (std::is_same_v<T, UnpackConstraint>)
return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack);
return tos(c.resultPack) + " ~ ...unpack " + tos(c.sourcePack);
else if constexpr (std::is_same_v<T, Unpack1Constraint>)
return tos(c.resultType) + " ~ unpack " + tos(c.sourceType);
else if constexpr (std::is_same_v<T, SetOpConstraint>)
{
const char* op = c.mode == SetOpConstraint::Union ? " | " : " & ";

View file

@ -1648,7 +1648,7 @@ struct TypeChecker2
visit(*fn->returnAnnotation);
// If the function type has a family annotation, we need to see if we can suggest an annotation
TypeFamilyReductionGuesser guesser{builtinTypes, NotNull{&normalizer}};
TypeFamilyReductionGuesser guesser{NotNull{&module->internalTypes}, builtinTypes, NotNull{&normalizer}};
for (TypeId retTy : inferredFtv->retTypes)
{
if (get<TypeFamilyInstanceType>(follow(retTy)))

View file

@ -17,23 +17,32 @@
#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFamilyReductionGuesser.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
#include "Luau/VecDeque.h"
#include "Luau/VisitType.h"
// used to control emitting CodeTooComplex warnings on type family reduction
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
// used to control falling back to a more conservative reduction based on guessing
// when this value is set to a negative value, guessing will be totally disabled.
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyUseGuesserDepth, -1);
LUAU_FASTFLAG(DebugLuauLogSolver);
namespace Luau
{
using TypeOrTypePackIdSet = DenseHashSet<const void*>;
struct InstanceCollector : TypeOnceVisitor
{
VecDeque<TypeId> tys;
VecDeque<TypePackId> tps;
TypeOrTypePackIdSet shouldGuess{nullptr};
std::vector<TypeId> cyclicInstance;
bool visit(TypeId ty, const TypeFamilyInstanceType&) override
@ -44,7 +53,12 @@ struct InstanceCollector : TypeOnceVisitor
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
// we want to reduce the innermost Add<number, number> instantiation
// first.
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFamilyDepth > DFInt::LuauTypeFamilyUseGuesserDepth)
shouldGuess.insert(ty);
tys.push_front(ty);
return true;
}
@ -69,7 +83,12 @@ struct InstanceCollector : TypeOnceVisitor
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
// we want to reduce the innermost Add<number, number> instantiation
// first.
if (DFInt::LuauTypeFamilyUseGuesserDepth >= 0 && typeFamilyDepth > DFInt::LuauTypeFamilyUseGuesserDepth)
shouldGuess.insert(tp);
tps.push_front(tp);
return true;
}
};
@ -80,19 +99,21 @@ struct FamilyReducer
VecDeque<TypeId> queuedTys;
VecDeque<TypePackId> queuedTps;
TypeOrTypePackIdSet shouldGuess;
std::vector<TypeId> cyclicTypeFamilies;
DenseHashSet<const void*> irreducible{nullptr};
TypeOrTypePackIdSet irreducible{nullptr};
FamilyGraphReductionResult result;
bool force = false;
// Local to the constraint being reduced.
Location location;
FamilyReducer(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, std::vector<TypeId> cyclicTypes, Location location,
TypeFamilyContext ctx, bool force = false)
FamilyReducer(VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector<TypeId> cyclicTypes,
Location location, TypeFamilyContext ctx, bool force = false)
: ctx(ctx)
, queuedTys(std::move(queuedTys))
, queuedTps(std::move(queuedTps))
, shouldGuess(std::move(shouldGuess))
, cyclicTypeFamilies(std::move(cyclicTypes))
, force(force)
, location(location)
@ -154,13 +175,12 @@ struct FamilyReducer
template<typename T>
void replace(T subject, T replacement)
{
if (FFlag::DebugLuauLogSolver)
printf("%s -> %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str());
// TODO: This should be an ICE (CLI-100942)
if (subject->owningArena != ctx.arena.get())
ctx.ice->ice("Attempting to modify a type family instance from another arena", location);
if (FFlag::DebugLuauLogSolver)
printf("%s -> %s\n", toString(subject, {true}).c_str(), toString(replacement, {true}).c_str());
asMutable(subject)->ty.template emplace<Unifiable::Bound<T>>(replacement);
if constexpr (std::is_same_v<T, TypeId>)
@ -265,6 +285,34 @@ struct FamilyReducer
return true;
}
template<typename TID>
inline bool tryGuessing(TID subject)
{
if (shouldGuess.contains(subject))
{
if (FFlag::DebugLuauLogSolver)
printf("Flagged %s for reduction with guesser.\n", toString(subject, {true}).c_str());
TypeFamilyReductionGuesser guesser{ctx.arena, ctx.builtins, ctx.normalizer};
auto guessed = guesser.guess(subject);
if (guessed)
{
if (FFlag::DebugLuauLogSolver)
printf("Selected %s as the guessed result type.\n", toString(*guessed, {true}).c_str());
replace(subject, *guessed);
return true;
}
if (FFlag::DebugLuauLogSolver)
printf("Failed to produce a guess for the result of %s.\n", toString(subject, {true}).c_str());
}
return false;
}
void stepType()
{
TypeId subject = follow(queuedTys.front());
@ -288,6 +336,9 @@ struct FamilyReducer
return;
}
if (tryGuessing(subject))
return;
TypeFamilyReductionResult<TypeId> result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleFamilyReduction(subject, result);
}
@ -309,6 +360,9 @@ struct FamilyReducer
if (!testParameters(subject, tfit))
return;
if (tryGuessing(subject))
return;
TypeFamilyReductionResult<TypePackId> result = tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
handleFamilyReduction(subject, result);
}
@ -324,9 +378,9 @@ struct FamilyReducer
};
static FamilyGraphReductionResult reduceFamiliesInternal(
VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force)
VecDeque<TypeId> queuedTys, VecDeque<TypePackId> queuedTps, TypeOrTypePackIdSet shouldGuess, std::vector<TypeId> cyclics, Location location, TypeFamilyContext ctx, bool force)
{
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(cyclics), location, ctx, force};
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), std::move(shouldGuess), std::move(cyclics), location, ctx, force};
int iterationCount = 0;
while (!reducer.done())
@ -360,7 +414,7 @@ FamilyGraphReductionResult reduceFamilies(TypeId entrypoint, Location location,
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.cyclicInstance), location, ctx, force);
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), std::move(collector.cyclicInstance), location, ctx, force);
}
FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location location, TypeFamilyContext ctx, bool force)
@ -379,7 +433,7 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati
if (collector.tys.empty() && collector.tps.empty())
return {};
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), {}, location, ctx, force);
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), std::move(collector.shouldGuess), std::move(collector.cyclicInstance), location, ctx, force);
}
bool isPending(TypeId ty, ConstraintSolver* solver)
@ -1495,7 +1549,7 @@ void BuiltinTypeFamilies::addToScope(NotNull<TypeArena> arena, NotNull<Scope> sc
TypeId t = arena->addType(GenericType{"T"});
TypeId u = arena->addType(GenericType{"U"});
GenericTypeDefinition genericT{t};
GenericTypeDefinition genericU{u};
GenericTypeDefinition genericU{u, {t}};
return TypeFun{{genericT, genericU}, arena->addType(TypeFamilyInstanceType{NotNull{family}, {t, u}, {}})};
};

View file

@ -11,6 +11,7 @@
#include "Luau/VisitType.h"
#include <iostream>
#include <optional>
#include <ostream>
namespace Luau
@ -64,8 +65,9 @@ struct InstanceCollector2 : TypeOnceVisitor
TypeFamilyReductionGuesser::TypeFamilyReductionGuesser(NotNull<BuiltinTypes> builtins, NotNull<Normalizer> normalizer)
: builtins(builtins)
TypeFamilyReductionGuesser::TypeFamilyReductionGuesser(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, NotNull<Normalizer> normalizer)
: arena(arena)
, builtins(builtins)
, normalizer(normalizer)
{
}
@ -87,6 +89,44 @@ void TypeFamilyReductionGuesser::dumpGuesses()
printf("Substitute %s for %s\n", toString(t).c_str(), toString(t_).c_str());
}
std::optional<TypeId> TypeFamilyReductionGuesser::guess(TypeId typ)
{
std::optional<TypeId> guessedType = guessType(typ);
if (!guessedType.has_value())
return {};
TypeId guess = follow(*guessedType);
if (get<TypeFamilyInstanceType>(guess))
return {};
return guess;
}
std::optional<TypePackId> TypeFamilyReductionGuesser::guess(TypePackId tp)
{
auto [head, tail] = flatten(tp);
std::vector<TypeId> guessedHead;
guessedHead.reserve(head.size());
for (auto typ : head)
{
std::optional<TypeId> guessedType = guessType(typ);
if (!guessedType.has_value())
return {};
TypeId guess = follow(*guessedType);
if (get<TypeFamilyInstanceType>(guess))
return {};
guessedHead.push_back(*guessedType);
}
return arena->addTypePack(TypePack{guessedHead, tail});
}
TypeFamilyReductionGuessResult TypeFamilyReductionGuesser::guessTypeFamilyReductionForFunction(
const AstExprFunction& expr, const FunctionType* ftv, TypeId retTy)
{

View file

@ -9,6 +9,8 @@
#include "Luau/TypeArena.h"
#include "Luau/TypeCheckLimits.h"
#include "Luau/TypeFamily.h"
#include "Luau/TypeFwd.h"
#include "Luau/TypePack.h"
#include "Luau/TypeUtils.h"
#include "Luau/VisitType.h"
@ -101,6 +103,19 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
if (subTy == superTy)
return true;
// We have potentially done some unifications while dispatching either `SubtypeConstraint` or `PackSubtypeConstraint`,
// so rather than implementing backtracking or traversing the entire type graph multiple times, we could push
// additional constraints as we discover blocked types along with their proper bounds.
//
// But we exclude these two subtyping patterns, they are tautological:
// - never <: *blocked*
// - *blocked* <: unknown
if ((get<BlockedType>(subTy) || get<BlockedType>(superTy)) && !get<NeverType>(subTy) && !get<UnknownType>(superTy))
{
incompleteSubtypes.push_back(SubtypeConstraint{subTy, superTy});
return true;
}
FreeType* subFree = getMutable<FreeType>(subTy);
FreeType* superFree = getMutable<FreeType>(superTy);
@ -124,8 +139,7 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
}
else if (subFree)
{
subFree->upperBound = mkIntersection(subFree->upperBound, superTy);
expandedFreeTypes[subTy].push_back(superTy);
return unifyFreeWithType(subTy, superTy);
}
else if (superFree)
{
@ -229,6 +243,62 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
return true;
}
// If superTy is a function and subTy already has a
// potentially-compatible function in its upper bound, we assume that
// the function is not overloaded and attempt to combine superTy into
// subTy's existing function bound.
bool Unifier2::unifyFreeWithType(TypeId subTy, TypeId superTy)
{
FreeType* subFree = getMutable<FreeType>(subTy);
LUAU_ASSERT(subFree);
auto doDefault = [&]() {
subFree->upperBound = mkIntersection(subFree->upperBound, superTy);
expandedFreeTypes[subTy].push_back(superTy);
return true;
};
TypeId upperBound = follow(subFree->upperBound);
if (get<FunctionType>(upperBound))
return unify(subFree->upperBound, superTy);
const FunctionType* superFunction = get<FunctionType>(superTy);
if (!superFunction)
return doDefault();
const auto [superArgHead, superArgTail] = flatten(superFunction->argTypes);
if (superArgTail)
return doDefault();
const IntersectionType* upperBoundIntersection = get<IntersectionType>(subFree->upperBound);
if (!upperBoundIntersection)
return doDefault();
bool ok = true;
bool foundOne = false;
for (TypeId part : upperBoundIntersection->parts)
{
const FunctionType* ft = get<FunctionType>(follow(part));
if (!ft)
continue;
const auto [subArgHead, subArgTail] = flatten(ft->argTypes);
if (!subArgTail && subArgHead.size() == superArgHead.size())
{
foundOne = true;
ok &= unify(part, superTy);
}
}
if (foundOne)
return ok;
else
return doDefault();
}
bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
{
const FunctionType* subFn = get<FunctionType>(subTy);
@ -403,6 +473,12 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
if (subTp == superTp)
return true;
if (get<BlockedTypePack>(subTp) || get<BlockedTypePack>(superTp))
{
incompleteSubtypes.push_back(PackSubtypeConstraint{subTp, superTp});
return true;
}
const FreeTypePack* subFree = get<FreeTypePack>(subTp);
const FreeTypePack* superFree = get<FreeTypePack>(superTp);

View file

@ -40,6 +40,26 @@ enum class CodeGenCompilationResult
AllocationFailed = 9, // Native codegen failed due to an allocation error
};
struct ProtoCompilationFailure
{
CodeGenCompilationResult result = CodeGenCompilationResult::Success;
std::string debugname;
int line = -1;
};
struct CompilationResult
{
CodeGenCompilationResult result = CodeGenCompilationResult::Success;
std::vector<ProtoCompilationFailure> protoFailures;
[[nodiscard]] bool hasErrors() const
{
return result != CodeGenCompilationResult::Success || !protoFailures.empty();
}
};
struct CompilationStats
{
size_t bytecodeSizeBytes = 0;
@ -65,7 +85,8 @@ void create(lua_State* L);
void setNativeExecutionEnabled(lua_State* L, bool enabled);
// Builds target function and all inner functions
CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr);
CodeGenCompilationResult compile_DEPRECATED(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr);
CompilationResult compile(lua_State* L, int idx, unsigned int flags = 0, CompilationStats* stats = nullptr);
using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos);

View file

@ -41,6 +41,7 @@ struct NativeProtoExecDataDeleter
using NativeProtoExecDataPtr = std::unique_ptr<uint32_t[], NativeProtoExecDataDeleter>;
[[nodiscard]] NativeProtoExecDataPtr createNativeProtoExecData(uint32_t bytecodeInstructionCount);
void destroyNativeProtoExecData(const uint32_t* instructionOffsets) noexcept;
[[nodiscard]] NativeProtoExecDataHeader& getNativeProtoExecDataHeader(uint32_t* instructionOffsets) noexcept;
[[nodiscard]] const NativeProtoExecDataHeader& getNativeProtoExecDataHeader(const uint32_t* instructionOffsets) noexcept;

View file

@ -110,11 +110,15 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
unwind->finalize(unwindData, unwindSize, block, blockSize);
#if defined(_WIN32) && defined(_M_X64)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
if (!RtlAddFunctionTable((RUNTIME_FUNCTION*)block, uint32_t(unwind->getFunctionCount()), uintptr_t(block)))
{
CODEGEN_ASSERT(!"Failed to allocate function table");
return nullptr;
}
#endif
#elif defined(__linux__) || defined(__APPLE__)
if (!__register_frame)
return nullptr;
@ -138,8 +142,12 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
void destroyBlockUnwindInfo(void* context, void* unwindData)
{
#if defined(_WIN32) && defined(_M_X64)
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM)
if (!RtlDeleteFunctionTable((RUNTIME_FUNCTION*)unwindData))
CODEGEN_ASSERT(!"Failed to deallocate function table");
#endif
#elif defined(__linux__) || defined(__APPLE__)
if (!__deregister_frame)
{

View file

@ -43,6 +43,7 @@
LUAU_FASTFLAGVARIABLE(DebugCodegenNoOpt, false)
LUAU_FASTFLAGVARIABLE(DebugCodegenOptSize, false)
LUAU_FASTFLAGVARIABLE(DebugCodegenSkipNumbering, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenDetailedCompilationResult, false)
// Per-module IR instruction count limit
LUAU_FASTINTVARIABLE(CodegenHeuristicsInstructionLimit, 1'048'576) // 1 M
@ -284,7 +285,7 @@ void onDisable(lua_State* L, Proto* proto)
});
}
size_t getMemorySize(lua_State* L, Proto* proto)
static size_t getMemorySize(lua_State* L, Proto* proto)
{
CODEGEN_ASSERT(FFlag::LuauCodegenHeapSizeReport);
ExtraExecData* extra = getExtraExecData(proto, proto->execdata);
@ -410,8 +411,10 @@ void setNativeExecutionEnabled(lua_State* L, bool enabled)
L->global->ecb.enter = enabled ? onEnter : onEnterDisabled;
}
CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats)
CodeGenCompilationResult compile_DEPRECATED(lua_State* L, int idx, unsigned int flags, CompilationStats* stats)
{
CODEGEN_ASSERT(!FFlag::LuauCodegenDetailedCompilationResult);
CODEGEN_ASSERT(lua_isLfunction(L, idx));
const TValue* func = luaA_toobject(L, idx);
@ -564,6 +567,167 @@ CodeGenCompilationResult compile(lua_State* L, int idx, unsigned int flags, Comp
return codeGenCompilationResult;
}
CompilationResult compile(lua_State* L, int idx, unsigned int flags, CompilationStats* stats)
{
CODEGEN_ASSERT(FFlag::LuauCodegenDetailedCompilationResult);
CompilationResult compilationResult;
CODEGEN_ASSERT(lua_isLfunction(L, idx));
const TValue* func = luaA_toobject(L, idx);
Proto* root = clvalue(func)->l.p;
if ((flags & CodeGen_OnlyNativeModules) != 0 && (root->flags & LPF_NATIVE_MODULE) == 0)
{
compilationResult.result = CodeGenCompilationResult::NotNativeModule;
return compilationResult;
}
// If initialization has failed, do not compile any functions
NativeState* data = getNativeState(L);
if (!data)
{
compilationResult.result = CodeGenCompilationResult::CodeGenNotInitialized;
return compilationResult;
}
std::vector<Proto*> protos;
gatherFunctions(protos, root, flags);
// Skip protos that have been compiled during previous invocations of CodeGen::compile
protos.erase(std::remove_if(protos.begin(), protos.end(),
[](Proto* p) {
return p == nullptr || p->execdata != nullptr;
}),
protos.end());
if (protos.empty())
{
compilationResult.result = CodeGenCompilationResult::NothingToCompile;
return compilationResult;
}
if (stats != nullptr)
stats->functionsTotal = uint32_t(protos.size());
#if defined(__aarch64__)
static unsigned int cpuFeatures = getCpuFeaturesA64();
A64::AssemblyBuilderA64 build(/* logText= */ false, cpuFeatures);
#else
X64::AssemblyBuilderX64 build(/* logText= */ false);
#endif
ModuleHelpers helpers;
#if defined(__aarch64__)
A64::assembleHelpers(build, helpers);
#else
X64::assembleHelpers(build, helpers);
#endif
std::vector<OldNativeProto> results;
results.reserve(protos.size());
uint32_t totalIrInstCount = 0;
for (Proto* p : protos)
{
CodeGenCompilationResult protoResult = CodeGenCompilationResult::Success;
if (std::optional<OldNativeProto> np = createNativeFunction(build, helpers, p, totalIrInstCount, protoResult))
results.push_back(*np);
else
compilationResult.protoFailures.push_back({protoResult, p->debugname ? getstr(p->debugname) : "", p->linedefined});
}
// Very large modules might result in overflowing a jump offset; in this case we currently abandon the entire module
if (!build.finalize())
{
for (OldNativeProto result : results)
destroyExecData(result.execdata);
compilationResult.result = CodeGenCompilationResult::CodeGenAssemblerFinalizationFailure;
return compilationResult;
}
// If no functions were assembled, we don't need to allocate/copy executable pages for helpers
if (results.empty())
return compilationResult;
uint8_t* nativeData = nullptr;
size_t sizeNativeData = 0;
uint8_t* codeStart = nullptr;
if (!data->codeAllocator.allocate(build.data.data(), int(build.data.size()), reinterpret_cast<const uint8_t*>(build.code.data()),
int(build.code.size() * sizeof(build.code[0])), nativeData, sizeNativeData, codeStart))
{
for (OldNativeProto result : results)
destroyExecData(result.execdata);
compilationResult.result = CodeGenCompilationResult::AllocationFailed;
return compilationResult;
}
if (FFlag::LuauCodegenHeapSizeReport)
{
if (gPerfLogFn && results.size() > 0)
gPerfLogFn(gPerfLogContext, uintptr_t(codeStart), uint32_t(results[0].exectarget), "<luau helpers>");
for (size_t i = 0; i < results.size(); ++i)
{
uint32_t begin = uint32_t(results[i].exectarget);
uint32_t end = i + 1 < results.size() ? uint32_t(results[i + 1].exectarget) : uint32_t(build.code.size() * sizeof(build.code[0]));
CODEGEN_ASSERT(begin < end);
if (gPerfLogFn)
logPerfFunction(results[i].p, uintptr_t(codeStart) + begin, end - begin);
ExtraExecData* extra = getExtraExecData(results[i].p, results[i].execdata);
extra->codeSize = end - begin;
}
}
else
{
if (gPerfLogFn && results.size() > 0)
{
gPerfLogFn(gPerfLogContext, uintptr_t(codeStart), uint32_t(results[0].exectarget), "<luau helpers>");
for (size_t i = 0; i < results.size(); ++i)
{
uint32_t begin = uint32_t(results[i].exectarget);
uint32_t end = i + 1 < results.size() ? uint32_t(results[i + 1].exectarget) : uint32_t(build.code.size() * sizeof(build.code[0]));
CODEGEN_ASSERT(begin < end);
logPerfFunction(results[i].p, uintptr_t(codeStart) + begin, end - begin);
}
}
}
for (const OldNativeProto& result : results)
{
// the memory is now managed by VM and will be freed via onDestroyFunction
result.p->execdata = result.execdata;
result.p->exectarget = uintptr_t(codeStart) + result.exectarget;
result.p->codeentry = &kCodeEntryInsn;
}
if (stats != nullptr)
{
for (const OldNativeProto& result : results)
{
stats->bytecodeSizeBytes += result.p->sizecode * sizeof(Instruction);
// Account for the native -> bytecode instruction offsets mapping:
stats->nativeMetadataSizeBytes += result.p->sizecode * sizeof(uint32_t);
}
stats->functionsCompiled += uint32_t(results.size());
stats->nativeCodeSizeBytes += build.code.size();
stats->nativeDataSizeBytes += build.data.size();
}
return compilationResult;
}
void setPerfLog(void* context, PerfLogFn logFn)
{
gPerfLogContext = context;

View file

@ -5,6 +5,7 @@
#include "Luau/UnwindBuilder.h"
#include "BitUtils.h"
#include "CodeGenContext.h"
#include "CodeGenUtils.h"
#include "NativeState.h"
#include "EmitCommonA64.h"
@ -290,6 +291,39 @@ bool initHeaderFunctions(NativeState& data)
return true;
}
bool initHeaderFunctions(BaseCodeGenContext& codeGenContext)
{
AssemblyBuilderA64 build(/* logText= */ false);
UnwindBuilder& unwind = *codeGenContext.unwindBuilder.get();
unwind.startInfo(UnwindBuilder::A64);
EntryLocations entryLocations = buildEntryFunction(build, unwind);
build.finalize();
unwind.finishInfo();
CODEGEN_ASSERT(build.data.empty());
uint8_t* codeStart = nullptr;
if (!codeGenContext.codeAllocator.allocate(build.data.data(), int(build.data.size()), reinterpret_cast<const uint8_t*>(build.code.data()),
int(build.code.size() * sizeof(build.code[0])), codeGenContext.gateData, codeGenContext.gateDataSize, codeStart))
{
CODEGEN_ASSERT(!"Failed to create entry function");
return false;
}
// Set the offset at the begining so that functions in new blocks will not overlay the locations
// specified by the unwind information of the entry function
unwind.setBeginOffset(build.getLabelOffset(entryLocations.prologueEnd));
codeGenContext.context.gateEntry = codeStart + build.getLabelOffset(entryLocations.start);
codeGenContext.context.gateExit = codeStart + build.getLabelOffset(entryLocations.epilogueStart);
return true;
}
void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers)
{
if (build.logText)

View file

@ -6,6 +6,7 @@ namespace Luau
namespace CodeGen
{
class BaseCodeGenContext;
struct NativeState;
struct ModuleHelpers;
@ -15,6 +16,7 @@ namespace A64
class AssemblyBuilderA64;
bool initHeaderFunctions(NativeState& data);
bool initHeaderFunctions(BaseCodeGenContext& codeGenContext);
void assembleHelpers(AssemblyBuilderA64& build, ModuleHelpers& helpers);
} // namespace A64

View file

@ -0,0 +1,226 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#include "CodeGenContext.h"
#include "CodeGenA64.h"
#include "CodeGenX64.h"
#include "Luau/CodeBlockUnwind.h"
#include "Luau/UnwindBuilder.h"
#include "Luau/UnwindBuilderDwarf2.h"
#include "Luau/UnwindBuilderWin.h"
LUAU_FASTFLAG(LuauCodegenHeapSizeReport)
LUAU_FASTINT(LuauCodeGenBlockSize)
LUAU_FASTINT(LuauCodeGenMaxTotalSize)
namespace Luau
{
namespace CodeGen
{
// From CodeGen.cpp
extern void* gPerfLogContext;
extern PerfLogFn gPerfLogFn;
BaseCodeGenContext::BaseCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext)
: codeAllocator{blockSize, maxTotalSize, allocationCallback, allocationCallbackContext}
{
CODEGEN_ASSERT(isSupported());
#if defined(_WIN32)
unwindBuilder = std::make_unique<UnwindBuilderWin>();
#else
unwindBuilder = std::make_unique<UnwindBuilderDwarf2>();
#endif
codeAllocator.context = unwindBuilder.get();
codeAllocator.createBlockUnwindInfo = createBlockUnwindInfo;
codeAllocator.destroyBlockUnwindInfo = destroyBlockUnwindInfo;
initFunctions(context);
}
[[nodiscard]] bool BaseCodeGenContext::initHeaderFunctions()
{
#if defined(__x86_64__) || defined(_M_X64)
if (!X64::initHeaderFunctions(*this))
return false;
#elif defined(__aarch64__)
if (!A64::initHeaderFunctions(*this))
return false;
#endif
if (gPerfLogFn)
gPerfLogFn(gPerfLogContext, uintptr_t(context.gateEntry), 4096, "<luau gate>");
return true;
}
StandaloneCodeGenContext::StandaloneCodeGenContext(
size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext)
: BaseCodeGenContext{blockSize, maxTotalSize, allocationCallback, allocationCallbackContext}
{
}
void StandaloneCodeGenContext::compileOrBindModule(const ModuleId&, lua_State*, int, unsigned int, CompilationStats*) {}
void StandaloneCodeGenContext::onCloseState() noexcept
{
// The StandaloneCodeGenContext is owned by the one VM that owns it, so when
// that VM is destroyed, we destroy *this as well:
delete this;
}
void StandaloneCodeGenContext::onDestroyFunction(void* execdata) noexcept
{
destroyNativeProtoExecData(static_cast<uint32_t*>(execdata));
}
SharedCodeGenContext::SharedCodeGenContext(
size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext)
: BaseCodeGenContext{blockSize, maxTotalSize, allocationCallback, allocationCallbackContext}
{
}
void SharedCodeGenContext::compileOrBindModule(const ModuleId&, lua_State*, int, unsigned int, CompilationStats*) {}
void SharedCodeGenContext::onCloseState() noexcept
{
// The lifetime of the SharedCodeGenContext is managed separately from the
// VMs that use it. When a VM is destroyed, we don't need to do anything
// here.
}
void SharedCodeGenContext::onDestroyFunction(void* execdata) noexcept
{
getNativeProtoExecDataHeader(static_cast<const uint32_t*>(execdata)).nativeModule->release();
}
[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext()
{
return createSharedCodeGenContext(size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), nullptr, nullptr);
}
[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext(AllocationCallback* allocationCallback, void* allocationCallbackContext)
{
return createSharedCodeGenContext(
size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext);
}
[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext(
size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext)
{
UniqueSharedCodeGenContext codeGenContext{new SharedCodeGenContext{blockSize, maxTotalSize, nullptr, nullptr}};
if (!codeGenContext->initHeaderFunctions())
return {};
return codeGenContext;
}
void destroySharedCodeGenContext(const SharedCodeGenContext* codeGenContext) noexcept
{
delete codeGenContext;
}
void SharedCodeGenContextDeleter::operator()(const SharedCodeGenContext* codeGenContext) const noexcept
{
destroySharedCodeGenContext(codeGenContext);
}
[[nodiscard]] static BaseCodeGenContext* getCodeGenContext(lua_State* L) noexcept
{
return static_cast<BaseCodeGenContext*>(L->global->ecb.context);
}
static void onCloseState(lua_State* L) noexcept
{
getCodeGenContext(L)->onCloseState();
L->global->ecb = lua_ExecutionCallbacks{};
}
static void onDestroyFunction(lua_State* L, Proto* proto) noexcept
{
getCodeGenContext(L)->onDestroyFunction(proto->execdata);
proto->execdata = nullptr;
proto->exectarget = 0;
proto->codeentry = proto->code;
}
static int onEnter(lua_State* L, Proto* proto)
{
BaseCodeGenContext* codeGenContext = getCodeGenContext(L);
CODEGEN_ASSERT(proto->execdata);
CODEGEN_ASSERT(L->ci->savedpc >= proto->code && L->ci->savedpc < proto->code + proto->sizecode);
uintptr_t target = proto->exectarget + static_cast<uint32_t*>(proto->execdata)[L->ci->savedpc - proto->code];
// Returns 1 to finish the function in the VM
return GateFn(codeGenContext->context.gateEntry)(L, proto, target, &codeGenContext->context);
}
// Defined in CodeGen.cpp
void onDisable(lua_State* L, Proto* proto);
static size_t getMemorySize(lua_State* L, Proto* proto)
{
CODEGEN_ASSERT(FFlag::LuauCodegenHeapSizeReport);
const NativeProtoExecDataHeader& execDataHeader = getNativeProtoExecDataHeader(static_cast<const uint32_t*>(proto->execdata));
const size_t execDataSize = sizeof(NativeProtoExecDataHeader) + execDataHeader.bytecodeInstructionCount * sizeof(Instruction);
// While execDataSize is exactly the size of the allocation we made and hold for 'execdata' field, the code size is approximate
// This is because code+data page is shared and owned by all Proto from a single module and each one can keep the whole region alive
// So individual Proto being freed by GC will not reflect memory use by native code correctly
return execDataSize + execDataHeader.nativeCodeSize;
}
static void initializeExecutionCallbacks(lua_State* L, BaseCodeGenContext* codeGenContext) noexcept
{
lua_ExecutionCallbacks* ecb = &L->global->ecb;
ecb->context = codeGenContext;
ecb->close = onCloseState;
ecb->destroy = onDestroyFunction;
ecb->enter = onEnter;
ecb->disable = onDisable;
if (FFlag::LuauCodegenHeapSizeReport)
ecb->getmemorysize = getMemorySize;
}
void create_NEW(lua_State* L)
{
return create_NEW(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), nullptr, nullptr);
}
void create_NEW(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext)
{
return create_NEW(L, size_t(FInt::LuauCodeGenBlockSize), size_t(FInt::LuauCodeGenMaxTotalSize), allocationCallback, allocationCallbackContext);
}
void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext)
{
std::unique_ptr<StandaloneCodeGenContext> codeGenContext =
std::make_unique<StandaloneCodeGenContext>(blockSize, maxTotalSize, allocationCallback, allocationCallbackContext);
if (!codeGenContext->initHeaderFunctions())
return;
initializeExecutionCallbacks(L, codeGenContext.release());
}
void create_NEW(lua_State* L, SharedCodeGenContext* codeGenContext)
{
initializeExecutionCallbacks(L, codeGenContext);
}
} // namespace CodeGen
} // namespace Luau

View file

@ -0,0 +1,114 @@
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
#pragma once
#include "Luau/SharedCodeAllocator.h"
#include "NativeState.h"
#include <memory>
#include <stdint.h>
namespace Luau
{
namespace CodeGen
{
// The "code-gen context" maintains the native code-gen state. There are two
// implementations. The StandaloneCodeGenContext is a VM-specific context type.
// It is the "simple" implementation that can be used when native code-gen is
// used with a single Luau VM. The SharedCodeGenContext supports use from
// multiple Luau VMs concurrently, and allows for sharing of executable native
// code and related metadata.
class BaseCodeGenContext
{
public:
BaseCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext);
[[nodiscard]] bool initHeaderFunctions();
virtual void compileOrBindModule(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats) = 0;
virtual void onCloseState() noexcept = 0;
virtual void onDestroyFunction(void* execdata) noexcept = 0;
CodeAllocator codeAllocator;
std::unique_ptr<UnwindBuilder> unwindBuilder;
uint8_t* gateData = nullptr;
size_t gateDataSize = 0;
NativeContext context;
};
class StandaloneCodeGenContext final : public BaseCodeGenContext
{
public:
StandaloneCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext);
virtual void compileOrBindModule(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats) override;
virtual void onCloseState() noexcept override;
virtual void onDestroyFunction(void* execdata) noexcept override;
private:
};
class SharedCodeGenContext final : public BaseCodeGenContext
{
public:
SharedCodeGenContext(size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext);
virtual void compileOrBindModule(const ModuleId& moduleId, lua_State* L, int idx, unsigned int flags, CompilationStats* stats) override;
virtual void onCloseState() noexcept override;
virtual void onDestroyFunction(void* execdata) noexcept override;
private:
SharedCodeAllocator sharedAllocator;
};
// The following will become the public interface, and can be moved into
// CodeGen.h after the shared allocator work is complete. When the old
// implementation is removed, the _NEW suffix can be dropped from these
// functions.
class SharedCodeGenContext;
struct SharedCodeGenContextDeleter
{
void operator()(const SharedCodeGenContext* context) const noexcept;
};
using UniqueSharedCodeGenContext = std::unique_ptr<SharedCodeGenContext, SharedCodeGenContextDeleter>;
// Creates a new SharedCodeGenContext that can be used by multiple Luau VMs
// concurrently, using either the default allocator parameters or custom
// allocator parameters.
[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext();
[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext(AllocationCallback* allocationCallback, void* allocationCallbackContext);
[[nodiscard]] UniqueSharedCodeGenContext createSharedCodeGenContext(
size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext);
// Destroys the provided SharedCodeGenContext. All Luau VMs using the
// SharedCodeGenContext must be destroyed before this function is called.
void destroySharedCodeGenContext(const SharedCodeGenContext* codeGenContext) noexcept;
// Initializes native code-gen on the provided Luau VM, using a VM-specific
// code-gen context and either the default allocator parameters or custom
// allocator parameters.
void create_NEW(lua_State* L);
void create_NEW(lua_State* L, AllocationCallback* allocationCallback, void* allocationCallbackContext);
void create_NEW(lua_State* L, size_t blockSize, size_t maxTotalSize, AllocationCallback* allocationCallback, void* allocationCallbackContext);
// Initializes native code-gen on the provided Luau VM, using the provided
// SharedCodeGenContext. Note that after this function is called, the
// SharedCodeGenContext must not be destroyed until after the Luau VM L is
// destroyed via lua_close.
void create_NEW(lua_State* L, SharedCodeGenContext* codeGenContext);
} // namespace CodeGen
} // namespace Luau

View file

@ -4,6 +4,7 @@
#include "Luau/AssemblyBuilderX64.h"
#include "Luau/UnwindBuilder.h"
#include "CodeGenContext.h"
#include "NativeState.h"
#include "EmitCommonX64.h"
@ -218,6 +219,39 @@ bool initHeaderFunctions(NativeState& data)
return true;
}
bool initHeaderFunctions(BaseCodeGenContext& codeGenContext)
{
AssemblyBuilderX64 build(/* logText= */ false);
UnwindBuilder& unwind = *codeGenContext.unwindBuilder.get();
unwind.startInfo(UnwindBuilder::X64);
EntryLocations entryLocations = buildEntryFunction(build, unwind);
build.finalize();
unwind.finishInfo();
CODEGEN_ASSERT(build.data.empty());
uint8_t* codeStart = nullptr;
if (!codeGenContext.codeAllocator.allocate(build.data.data(), int(build.data.size()), build.code.data(), int(build.code.size()),
codeGenContext.gateData, codeGenContext.gateDataSize, codeStart))
{
CODEGEN_ASSERT(!"Failed to create entry function");
return false;
}
// Set the offset at the begining so that functions in new blocks will not overlay the locations
// specified by the unwind information of the entry function
unwind.setBeginOffset(build.getLabelOffset(entryLocations.prologueEnd));
codeGenContext.context.gateEntry = codeStart + build.getLabelOffset(entryLocations.start);
codeGenContext.context.gateExit = codeStart + build.getLabelOffset(entryLocations.epilogueStart);
return true;
}
void assembleHelpers(X64::AssemblyBuilderX64& build, ModuleHelpers& helpers)
{
if (build.logText)

View file

@ -6,6 +6,7 @@ namespace Luau
namespace CodeGen
{
class BaseCodeGenContext;
struct NativeState;
struct ModuleHelpers;
@ -15,6 +16,7 @@ namespace X64
class AssemblyBuilderX64;
bool initHeaderFunctions(NativeState& data);
bool initHeaderFunctions(BaseCodeGenContext& codeGenContext);
void assembleHelpers(AssemblyBuilderX64& build, ModuleHelpers& helpers);
} // namespace X64

View file

@ -11,11 +11,10 @@
#include "lstate.h"
#include "lgc.h"
LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorA64, false)
LUAU_FASTFLAGVARIABLE(LuauCodeGenOptVecA64, false)
LUAU_FASTFLAG(LuauCodegenVectorTag2)
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores4)
LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB)
namespace Luau
{
@ -731,148 +730,35 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
{
inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b});
if (FFlag::LuauCodeGenVectorA64)
{
build.fadd(inst.regA64, regOp(inst.a), regOp(inst.b));
if (!FFlag::LuauCodegenVectorTag2)
{
RegisterA64 tempw = regs.allocTemp(KindA64::w);
build.mov(tempw, LUA_TVECTOR);
build.ins_4s(inst.regA64, tempw, 3);
}
}
else
{
RegisterA64 tempa = regs.allocTemp(KindA64::s);
RegisterA64 tempb = regs.allocTemp(KindA64::s);
for (uint8_t i = 0; i < 3; i++)
{
build.dup_4s(tempa, regOp(inst.a), i);
build.dup_4s(tempb, regOp(inst.b), i);
build.fadd(tempa, tempa, tempb);
build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
}
}
break;
}
case IrCmd::SUB_VEC:
{
inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b});
if (FFlag::LuauCodeGenVectorA64)
{
build.fsub(inst.regA64, regOp(inst.a), regOp(inst.b));
if (!FFlag::LuauCodegenVectorTag2)
{
RegisterA64 tempw = regs.allocTemp(KindA64::w);
build.mov(tempw, LUA_TVECTOR);
build.ins_4s(inst.regA64, tempw, 3);
}
}
else
{
RegisterA64 tempa = regs.allocTemp(KindA64::s);
RegisterA64 tempb = regs.allocTemp(KindA64::s);
for (uint8_t i = 0; i < 3; i++)
{
build.dup_4s(tempa, regOp(inst.a), i);
build.dup_4s(tempb, regOp(inst.b), i);
build.fsub(tempa, tempa, tempb);
build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
}
}
break;
}
case IrCmd::MUL_VEC:
{
inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b});
if (FFlag::LuauCodeGenVectorA64)
{
build.fmul(inst.regA64, regOp(inst.a), regOp(inst.b));
if (!FFlag::LuauCodegenVectorTag2)
{
RegisterA64 tempw = regs.allocTemp(KindA64::w);
build.mov(tempw, LUA_TVECTOR);
build.ins_4s(inst.regA64, tempw, 3);
}
}
else
{
RegisterA64 tempa = regs.allocTemp(KindA64::s);
RegisterA64 tempb = regs.allocTemp(KindA64::s);
for (uint8_t i = 0; i < 3; i++)
{
build.dup_4s(tempa, regOp(inst.a), i);
build.dup_4s(tempb, regOp(inst.b), i);
build.fmul(tempa, tempa, tempb);
build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
}
}
break;
}
case IrCmd::DIV_VEC:
{
inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b});
if (FFlag::LuauCodeGenVectorA64)
{
build.fdiv(inst.regA64, regOp(inst.a), regOp(inst.b));
if (!FFlag::LuauCodegenVectorTag2)
{
RegisterA64 tempw = regs.allocTemp(KindA64::w);
build.mov(tempw, LUA_TVECTOR);
build.ins_4s(inst.regA64, tempw, 3);
}
}
else
{
RegisterA64 tempa = regs.allocTemp(KindA64::s);
RegisterA64 tempb = regs.allocTemp(KindA64::s);
for (uint8_t i = 0; i < 3; i++)
{
build.dup_4s(tempa, regOp(inst.a), i);
build.dup_4s(tempb, regOp(inst.b), i);
build.fdiv(tempa, tempa, tempb);
build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
}
}
break;
}
case IrCmd::UNM_VEC:
{
inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a});
if (FFlag::LuauCodeGenVectorA64)
{
build.fneg(inst.regA64, regOp(inst.a));
if (!FFlag::LuauCodegenVectorTag2)
{
RegisterA64 tempw = regs.allocTemp(KindA64::w);
build.mov(tempw, LUA_TVECTOR);
build.ins_4s(inst.regA64, tempw, 3);
}
}
else
{
RegisterA64 tempa = regs.allocTemp(KindA64::s);
for (uint8_t i = 0; i < 3; i++)
{
build.dup_4s(tempa, regOp(inst.a), i);
build.fneg(tempa, tempa);
build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
}
}
break;
}
case IrCmd::NOT_ANY:
@ -1232,7 +1118,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
{
inst.regA64 = regs.allocReg(KindA64::q, index);
if (FFlag::LuauCodeGenOptVecA64 && FFlag::LuauCodegenVectorTag2 && inst.a.kind == IrOpKind::Constant)
if (FFlag::LuauCodeGenOptVecA64 && inst.a.kind == IrOpKind::Constant)
{
float value = float(doubleOp(inst.a));
uint32_t asU32;
@ -1256,16 +1142,9 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
{
RegisterA64 tempd = tempDouble(inst.a);
RegisterA64 temps = castReg(KindA64::s, tempd);
RegisterA64 tempw = regs.allocTemp(KindA64::w);
build.fcvt(temps, tempd);
build.dup_4s(inst.regA64, castReg(KindA64::q, temps), 0);
if (!FFlag::LuauCodegenVectorTag2)
{
build.mov(tempw, LUA_TVECTOR);
build.ins_4s(inst.regA64, tempw, 3);
}
}
break;
}
@ -1568,7 +1447,15 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
}
// fail to fallback on 'false' boolean value (falsy)
if (!FFlag::LuauCodegenCheckTruthyFormB || inst.b.kind != IrOpKind::Constant)
{
build.cbz(regOp(inst.b), target);
}
else
{
if (intOp(inst.b) == 0)
build.b(target);
}
if (inst.a.kind != IrOpKind::Constant)
build.setLabel(skip);

View file

@ -15,9 +15,9 @@
#include "lstate.h"
#include "lgc.h"
LUAU_FASTFLAG(LuauCodegenVectorTag2)
LUAU_FASTFLAGVARIABLE(LuauCodegenVectorOptAnd, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenSmallerUnm, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenCheckTruthyFormB, false)
namespace Luau
{
@ -631,9 +631,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp());
build.vaddps(inst.regX64, tmp1.reg, tmp2.reg);
}
if (!FFlag::LuauCodegenVectorTag2)
build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp());
break;
}
case IrCmd::SUB_VEC:
@ -660,9 +657,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp());
build.vsubps(inst.regX64, tmp1.reg, tmp2.reg);
}
if (!FFlag::LuauCodegenVectorTag2)
build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp());
break;
}
case IrCmd::MUL_VEC:
@ -689,9 +683,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp());
build.vmulps(inst.regX64, tmp1.reg, tmp2.reg);
}
if (!FFlag::LuauCodegenVectorTag2)
build.vorps(inst.regX64, inst.regX64, vectorOrMaskOp());
break;
}
case IrCmd::DIV_VEC:
@ -718,9 +709,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
build.vandps(tmp2.reg, regOp(inst.b), vectorAndMaskOp());
build.vdivps(inst.regX64, tmp1.reg, tmp2.reg);
}
if (!FFlag::LuauCodegenVectorTag2)
build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3);
break;
}
case IrCmd::UNM_VEC:
@ -745,9 +733,6 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
build.vxorpd(inst.regX64, inst.regX64, build.f32x4(-0.0, -0.0, -0.0, -0.0));
}
}
if (!FFlag::LuauCodegenVectorTag2)
build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3);
break;
}
case IrCmd::NOT_ANY:
@ -1052,18 +1037,12 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
static_assert(sizeof(asU32) == sizeof(value), "Expecting float to be 32-bit");
memcpy(&asU32, &value, sizeof(value));
if (FFlag::LuauCodegenVectorTag2)
build.vmovaps(inst.regX64, build.u32x4(asU32, asU32, asU32, 0));
else
build.vmovaps(inst.regX64, build.u32x4(asU32, asU32, asU32, LUA_TVECTOR));
}
else
{
build.vcvtsd2ss(inst.regX64, inst.regX64, memRegDoubleOp(inst.a));
build.vpshufps(inst.regX64, inst.regX64, inst.regX64, 0b00'00'00'00);
if (!FFlag::LuauCodegenVectorTag2)
build.vpinsrd(inst.regX64, inst.regX64, build.i32(LUA_TVECTOR), 3);
}
break;
case IrCmd::TAG_VECTOR:
@ -1306,8 +1285,16 @@ void IrLoweringX64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
}
// fail to fallback on 'false' boolean value (falsy)
if (!FFlag::LuauCodegenCheckTruthyFormB || inst.b.kind != IrOpKind::Constant)
{
build.cmp(memRegUintOp(inst.b), 0);
jumpOrAbortOnUndef(ConditionX64::Equal, inst.c, next);
}
else
{
if (intOp(inst.b) == 0)
jumpOrAbortOnUndef(inst.c, next);
}
if (inst.a.kind != IrOpKind::Constant)
build.setLabel(skip);
@ -2306,7 +2293,7 @@ OperandX64 IrLoweringX64::bufferAddrOp(IrOp bufferOp, IrOp indexOp)
RegisterX64 IrLoweringX64::vecOp(IrOp op, ScopedRegX64& tmp)
{
if (FFlag::LuauCodegenVectorOptAnd && FFlag::LuauCodegenVectorTag2)
if (FFlag::LuauCodegenVectorOptAnd)
{
IrInst source = function.instOp(op);
CODEGEN_ASSERT(source.cmd != IrCmd::SUBSTITUTE); // we don't process substitutions
@ -2365,16 +2352,6 @@ OperandX64 IrLoweringX64::vectorAndMaskOp()
return vectorAndMask;
}
OperandX64 IrLoweringX64::vectorOrMaskOp()
{
CODEGEN_ASSERT(!FFlag::LuauCodegenVectorTag2);
if (vectorOrMask.base == noreg)
vectorOrMask = build.u32x4(0, 0, 0, LUA_TVECTOR);
return vectorOrMask;
}
} // namespace X64
} // namespace CodeGen
} // namespace Luau

View file

@ -63,7 +63,6 @@ struct IrLoweringX64
Label& labelOp(IrOp op) const;
OperandX64 vectorAndMaskOp();
OperandX64 vectorOrMaskOp();
struct InterruptHandler
{

View file

@ -12,7 +12,6 @@
#include "lstate.h"
#include "ltm.h"
LUAU_FASTFLAGVARIABLE(LuauCodegenVectorTag2, false)
LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTVTag, false)
namespace Luau
@ -388,7 +387,6 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
CODEGEN_ASSERT(!"Unknown TM op");
}
if (FFlag::LuauCodegenVectorTag2)
result = build.inst(IrCmd::TAG_VECTOR, result);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result);
@ -417,7 +415,6 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
CODEGEN_ASSERT(!"Unknown TM op");
}
if (FFlag::LuauCodegenVectorTag2)
result = build.inst(IrCmd::TAG_VECTOR, result);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result);
@ -446,7 +443,6 @@ static void translateInstBinaryNumeric(IrBuilder& build, int ra, int rb, int rc,
CODEGEN_ASSERT(!"Unknown TM op");
}
if (FFlag::LuauCodegenVectorTag2)
result = build.inst(IrCmd::TAG_VECTOR, result);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), result);
@ -589,7 +585,6 @@ void translateInstMinus(IrBuilder& build, const Instruction* pc, int pcpos)
IrOp vb = build.inst(IrCmd::LOAD_TVALUE, build.vmReg(rb));
IrOp va = build.inst(IrCmd::UNM_VEC, vb);
if (FFlag::LuauCodegenVectorTag2)
va = build.inst(IrCmd::TAG_VECTOR, va);
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), va);
return;

View file

@ -17,9 +17,7 @@ namespace CodeGen
void NativeProtoExecDataDeleter::operator()(const uint32_t* instructionOffsets) const noexcept
{
const NativeProtoExecDataHeader* header = &getNativeProtoExecDataHeader(instructionOffsets);
header->~NativeProtoExecDataHeader();
delete[] reinterpret_cast<const uint8_t*>(header);
destroyNativeProtoExecData(instructionOffsets);
}
[[nodiscard]] NativeProtoExecDataPtr createNativeProtoExecData(uint32_t bytecodeInstructionCount)
@ -29,6 +27,13 @@ void NativeProtoExecDataDeleter::operator()(const uint32_t* instructionOffsets)
return NativeProtoExecDataPtr{reinterpret_cast<uint32_t*>(bytes.release() + sizeof(NativeProtoExecDataHeader))};
}
void destroyNativeProtoExecData(const uint32_t* instructionOffsets) noexcept
{
const NativeProtoExecDataHeader* header = &getNativeProtoExecDataHeader(instructionOffsets);
header->~NativeProtoExecDataHeader();
delete[] reinterpret_cast<const uint8_t*>(header);
}
[[nodiscard]] NativeProtoExecDataHeader& getNativeProtoExecDataHeader(uint32_t* instructionOffsets) noexcept
{
return *reinterpret_cast<NativeProtoExecDataHeader*>(reinterpret_cast<uint8_t*>(instructionOffsets) - sizeof(NativeProtoExecDataHeader));
@ -36,7 +41,6 @@ void NativeProtoExecDataDeleter::operator()(const uint32_t* instructionOffsets)
[[nodiscard]] const NativeProtoExecDataHeader& getNativeProtoExecDataHeader(const uint32_t* instructionOffsets) noexcept
{
return *reinterpret_cast<const NativeProtoExecDataHeader*>(
reinterpret_cast<const uint8_t*>(instructionOffsets) - sizeof(NativeProtoExecDataHeader));
}

View file

@ -112,5 +112,83 @@ void initFunctions(NativeState& data)
data.context.executeSETLIST = executeSETLIST;
}
void initFunctions(NativeContext& context)
{
static_assert(sizeof(context.luauF_table) == sizeof(luauF_table), "fastcall tables are not of the same length");
memcpy(context.luauF_table, luauF_table, sizeof(luauF_table));
context.luaV_lessthan = luaV_lessthan;
context.luaV_lessequal = luaV_lessequal;
context.luaV_equalval = luaV_equalval;
context.luaV_doarith = luaV_doarith;
context.luaV_dolen = luaV_dolen;
context.luaV_gettable = luaV_gettable;
context.luaV_settable = luaV_settable;
context.luaV_getimport = luaV_getimport;
context.luaV_concat = luaV_concat;
context.luaH_getn = luaH_getn;
context.luaH_new = luaH_new;
context.luaH_clone = luaH_clone;
context.luaH_resizearray = luaH_resizearray;
context.luaH_setnum = luaH_setnum;
context.luaC_barriertable = luaC_barriertable;
context.luaC_barrierf = luaC_barrierf;
context.luaC_barrierback = luaC_barrierback;
context.luaC_step = luaC_step;
context.luaF_close = luaF_close;
context.luaF_findupval = luaF_findupval;
context.luaF_newLclosure = luaF_newLclosure;
context.luaT_gettm = luaT_gettm;
context.luaT_objtypenamestr = luaT_objtypenamestr;
context.libm_exp = exp;
context.libm_pow = pow;
context.libm_fmod = fmod;
context.libm_log = log;
context.libm_log2 = log2;
context.libm_log10 = log10;
context.libm_ldexp = ldexp;
context.libm_round = round;
context.libm_frexp = frexp;
context.libm_modf = modf;
context.libm_asin = asin;
context.libm_sin = sin;
context.libm_sinh = sinh;
context.libm_acos = acos;
context.libm_cos = cos;
context.libm_cosh = cosh;
context.libm_atan = atan;
context.libm_atan2 = atan2;
context.libm_tan = tan;
context.libm_tanh = tanh;
context.forgLoopTableIter = forgLoopTableIter;
context.forgLoopNodeIter = forgLoopNodeIter;
context.forgLoopNonTableFallback = forgLoopNonTableFallback;
context.forgPrepXnextFallback = forgPrepXnextFallback;
context.callProlog = callProlog;
context.callEpilogC = callEpilogC;
context.callFallback = callFallback;
context.executeGETGLOBAL = executeGETGLOBAL;
context.executeSETGLOBAL = executeSETGLOBAL;
context.executeGETTABLEKS = executeGETTABLEKS;
context.executeSETTABLEKS = executeSETTABLEKS;
context.executeNAMECALL = executeNAMECALL;
context.executeFORGPREP = executeFORGPREP;
context.executeGETVARARGSMultRet = executeGETVARARGSMultRet;
context.executeGETVARARGSConst = executeGETVARARGSConst;
context.executeDUPCLOSURE = executeDUPCLOSURE;
context.executePREPVARARGS = executePREPVARARGS;
context.executeSETLIST = executeSETLIST;
}
} // namespace CodeGen
} // namespace Luau

View file

@ -124,6 +124,7 @@ struct NativeState
};
void initFunctions(NativeState& data);
void initFunctions(NativeContext& context);
} // namespace CodeGen
} // namespace Luau

View file

@ -17,10 +17,10 @@
LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
LUAU_FASTFLAG(LuauCodegenVectorTag2)
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodeGenCoverForgprepEffect, false)
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores4)
LUAU_FASTFLAG(LuauCodegenLoadTVTag)
LUAU_FASTFLAGVARIABLE(LuauCodegenInferNumTag, false)
namespace Luau
{
@ -716,18 +716,9 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
if (tag == 0xff)
{
if (IrInst* arg = function.asInstOp(inst.b))
{
if (FFlag::LuauCodegenVectorTag2)
{
if (arg->cmd == IrCmd::TAG_VECTOR)
tag = LUA_TVECTOR;
}
else
{
if (arg->cmd == IrCmd::ADD_VEC || arg->cmd == IrCmd::SUB_VEC || arg->cmd == IrCmd::MUL_VEC || arg->cmd == IrCmd::DIV_VEC ||
arg->cmd == IrCmd::UNM_VEC)
tag = LUA_TVECTOR;
}
if (FFlag::LuauCodegenLoadTVTag && arg->cmd == IrCmd::LOAD_TVALUE && arg->c.kind != IrOpKind::None)
tag = function.tagOp(arg->c);
@ -906,6 +897,40 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
{
uint8_t b = function.tagOp(inst.b);
if (FFlag::LuauCodegenInferNumTag)
{
uint8_t tag = state.tryGetTag(inst.a);
if (tag == 0xff)
{
if (IrOp value = state.tryGetValue(inst.a); value.kind == IrOpKind::Constant)
{
if (function.constOp(value).kind == IrConstKind::Double)
tag = LUA_TNUMBER;
}
}
if (tag != 0xff)
{
if (tag == b)
{
if (FFlag::DebugLuauAbortingChecks)
replace(function, inst.c, build.undef());
else
kill(function, inst);
}
else
{
replace(function, block, index, {IrCmd::JUMP, inst.c}); // Shows a conflict in assumptions on this path
}
}
else
{
state.updateTag(inst.a, b); // We can assume the tag value going forward
}
}
else
{
if (uint8_t tag = state.tryGetTag(inst.a); tag != 0xff)
{
if (tag == b)
@ -924,6 +949,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
{
state.updateTag(inst.a, b); // We can assume the tag value going forward
}
}
break;
}
case IrCmd::CHECK_TRUTHY:
@ -1296,22 +1322,16 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
case IrCmd::SUB_VEC:
case IrCmd::MUL_VEC:
case IrCmd::DIV_VEC:
if (FFlag::LuauCodegenVectorTag2)
{
if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR)
replace(function, inst.a, a->a);
if (IrInst* b = function.asInstOp(inst.b); b && b->cmd == IrCmd::TAG_VECTOR)
replace(function, inst.b, b->a);
}
break;
case IrCmd::UNM_VEC:
if (FFlag::LuauCodegenVectorTag2)
{
if (IrInst* a = function.asInstOp(inst.a); a && a->cmd == IrCmd::TAG_VECTOR)
replace(function, inst.a, a->a);
}
break;
case IrCmd::CHECK_NODE_NO_NEXT:

View file

@ -10,7 +10,6 @@
#include "lobject.h"
LUAU_FASTFLAGVARIABLE(LuauCodegenRemoveDeadStores4, false)
LUAU_FASTFLAG(LuauCodegenVectorTag2)
LUAU_FASTFLAG(LuauCodegenLoadTVTag)
// TODO: optimization can be improved by knowing which registers are live in at each VM exit
@ -339,18 +338,9 @@ static void markDeadStoresInInst(RemoveDeadStoreState& state, IrBuilder& build,
// Note that for known boolean/number/GCO, we already optimize into STORE_SPLIT_TVALUE form
// TODO (CLI-101027): similar code is used in constant propagation optimization and should be shared in utilities
if (IrInst* arg = function.asInstOp(inst.b))
{
if (FFlag::LuauCodegenVectorTag2)
{
if (arg->cmd == IrCmd::TAG_VECTOR)
regInfo.maybeGco = false;
}
else
{
if (arg->cmd == IrCmd::ADD_VEC || arg->cmd == IrCmd::SUB_VEC || arg->cmd == IrCmd::MUL_VEC || arg->cmd == IrCmd::DIV_VEC ||
arg->cmd == IrCmd::UNM_VEC)
regInfo.maybeGco = false;
}
if (FFlag::LuauCodegenLoadTVTag && arg->cmd == IrCmd::LOAD_TVALUE && arg->c.kind != IrOpKind::None)
regInfo.maybeGco = isGCO(function.tagOp(arg->c));

View file

@ -5,6 +5,8 @@
#include "lapi.h"
LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult)
int luau_codegen_supported()
{
return Luau::CodeGen::isSupported();
@ -17,5 +19,8 @@ void luau_codegen_create(lua_State* L)
void luau_codegen_compile(lua_State* L, int idx)
{
if (FFlag::LuauCodegenDetailedCompilationResult)
Luau::CodeGen::compile(L, idx);
else
Luau::CodeGen::compile_DEPRECATED(L, idx);
}

View file

@ -77,7 +77,7 @@ struct FValue
list = this;
}
operator T() const
LUAU_FORCEINLINE operator T() const
{
return value;
}

View file

@ -107,6 +107,7 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/CodeBlockUnwind.cpp
CodeGen/src/CodeGen.cpp
CodeGen/src/CodeGenAssembly.cpp
CodeGen/src/CodeGenContext.cpp
CodeGen/src/CodeGenUtils.cpp
CodeGen/src/CodeGenA64.cpp
CodeGen/src/CodeGenX64.cpp
@ -139,6 +140,7 @@ target_sources(Luau.CodeGen PRIVATE
CodeGen/src/BitUtils.h
CodeGen/src/ByteUtils.h
CodeGen/src/CodeGenContext.h
CodeGen/src/CodeGenLower.h
CodeGen/src/CodeGenUtils.h
CodeGen/src/CodeGenA64.h
@ -356,13 +358,19 @@ target_sources(isocline PRIVATE
extern/isocline/src/isocline.c
)
# Common sources shared between all CLI apps
target_sources(Luau.CLI.lib PRIVATE
if (TARGET Luau.Repl.CLI OR TARGET Luau.Analyze.CLI OR
TARGET Luau.Ast.CLI OR TARGET Luau.CLI.Test OR
TARGET Luau.Reduce.CLI OR TARGET Luau.Compile.CLI OR
TARGET Luau.Bytecode.CLI)
# Common sources shared between all CLI apps.
target_sources(Luau.CLI.lib PRIVATE
CLI/FileUtils.cpp
CLI/Flags.cpp
CLI/Flags.h
CLI/FileUtils.h
)
)
endif()
if(TARGET Luau.Repl.CLI)
# Luau.Repl.CLI Sources

View file

@ -186,8 +186,16 @@ int lua_getinfo(lua_State* L, int level, const char* what, lua_Debug* ar)
CallInfo* ci = NULL;
if (level < 0)
{
const TValue* func = luaA_toobject(L, level);
api_check(L, ttisfunction(func));
// element has to be within stack
if (-level > L->top - L->base)
return 0;
StkId func = L->top + level;
// and it has to be a function
if (!ttisfunction(func))
return 0;
f = clvalue(func);
}
else if (unsigned(level) < unsigned(L->ci - L->base_ci))

View file

@ -35,6 +35,9 @@ LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
LUAU_FASTFLAG(LuauLoadExceptionSafe)
LUAU_DYNAMIC_FASTFLAG(LuauDebugInfoDupArgLeftovers)
LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals)
LUAU_FASTFLAG(LuauCodegenInferNumTag)
LUAU_FASTFLAG(LuauCodegenDetailedCompilationResult)
LUAU_FASTFLAG(LuauCodegenCheckTruthyFormB)
static lua_CompileOptions defaultOptions()
{
@ -238,7 +241,12 @@ static StateRef runConformance(const char* name, void (*setup)(lua_State* L) = n
free(bytecode);
if (result == 0 && codegen && !skipCodegen && luau_codegen_supported())
{
if (FFlag::LuauCodegenDetailedCompilationResult)
Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions);
else
Luau::CodeGen::compile_DEPRECATED(L, -1, Luau::CodeGen::CodeGen_ColdFunctions);
}
int status = (result == 0) ? lua_resume(L, nullptr, 0) : LUA_ERRSYNTAX;
@ -1825,6 +1833,18 @@ TEST_CASE("LightuserdataApi")
globalState.reset();
}
TEST_CASE("DebugApi")
{
StateRef globalState(luaL_newstate(), lua_close);
lua_State* L = globalState.get();
lua_pushnumber(L, 10);
lua_Debug ar;
CHECK(lua_getinfo(L, -1, "f", &ar) == 0); // number is not a function
CHECK(lua_getinfo(L, -10, "f", &ar) == 0); // not on stack
}
TEST_CASE("Iter")
{
runConformance("iter.lua");
@ -2051,6 +2071,9 @@ TEST_CASE("SafeEnv")
TEST_CASE("Native")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenInferNumTag, true};
ScopedFastFlag luauCodegenCheckTruthyFormB{FFlag::LuauCodegenCheckTruthyFormB, true};
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
if (!codegen || !luau_codegen_supported())
return;
@ -2108,6 +2131,8 @@ TEST_CASE("NativeTypeAnnotations")
TEST_CASE("HugeFunction")
{
ScopedFastFlag luauCodegenDetailedCompilationResult{FFlag::LuauCodegenDetailedCompilationResult, true};
std::string source = makeHugeFunctionSource();
StateRef globalState(luaL_newstate(), lua_close);
@ -2214,6 +2239,7 @@ TEST_CASE("IrInstructionLimit")
return;
ScopedFastInt codegenHeuristicsInstructionLimit{FInt::CodegenHeuristicsInstructionLimit, 50'000};
ScopedFastFlag luauCodegenDetailedCompilationResult{FFlag::LuauCodegenDetailedCompilationResult, true};
std::string source;
@ -2254,10 +2280,18 @@ TEST_CASE("IrInstructionLimit")
REQUIRE(result == 0);
Luau::CodeGen::CompilationStats nativeStats = {};
Luau::CodeGen::CodeGenCompilationResult nativeResult = Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions, &nativeStats);
Luau::CodeGen::CompilationResult nativeResult = Luau::CodeGen::compile(L, -1, Luau::CodeGen::CodeGen_ColdFunctions, &nativeStats);
// Limit is not hit immediately, so with some functions compiled it should be a success
CHECK(nativeResult == Luau::CodeGen::CodeGenCompilationResult::CodeGenOverflowInstructionLimit);
CHECK(nativeResult.result == Luau::CodeGen::CodeGenCompilationResult::Success);
// But it has some failed functions
CHECK(nativeResult.hasErrors());
REQUIRE(!nativeResult.protoFailures.empty());
CHECK(nativeResult.protoFailures.front().result == Luau::CodeGen::CodeGenCompilationResult::CodeGenOverflowInstructionLimit);
CHECK(nativeResult.protoFailures.front().line != -1);
CHECK(nativeResult.protoFailures.front().debugname != "");
// We should be able to compile at least one of our functions
CHECK(nativeStats.functionsCompiled > 0);

View file

@ -631,4 +631,31 @@ print(t.x)
CHECK(phi->operands.at(1) == x2);
}
TEST_CASE_FIXTURE(DataFlowGraphFixture, "insert_trivial_phi_nodes_inside_of_phi_nodes")
{
dfg(R"(
local t = {}
local function f(k: string)
if t[k] ~= nil then
return
end
t[k] = 5
end
)");
DefId t1 = graph->getDef(query<AstStatLocal>(module)->vars.data[0]); // local t = {}
DefId t2 = getDef<AstExprLocal, 1>(); // t[k] ~= nil
DefId t3 = getDef<AstExprLocal, 3>(); // t[k] = 5
CHECK(t1 != t2);
CHECK(t2 == t3);
const Phi* t2phi = get<Phi>(t2);
REQUIRE(t2phi);
CHECK(t2phi->operands.size() == 1);
CHECK(t2phi->operands.at(0) == t1);
}
TEST_SUITE_END();

View file

@ -12,9 +12,9 @@
#include <limits.h>
LUAU_FASTFLAG(LuauCodegenVectorTag2)
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores4)
LUAU_FASTFLAG(DebugLuauAbortingChecks)
LUAU_FASTFLAG(LuauCodegenInferNumTag)
using namespace Luau::CodeGen;
@ -2501,8 +2501,6 @@ bb_fallback_1:
TEST_CASE_FIXTURE(IrBuilderFixture, "TagVectorSkipErrorFix")
{
ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true};
IrOp block = build.block(IrBlockKind::Internal);
build.beginBlock(block);
@ -2631,6 +2629,30 @@ bb_0:
)");
}
TEST_CASE_FIXTURE(IrBuilderFixture, "InferNumberTagFromLimitedContext")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenInferNumTag, true};
IrOp entry = build.block(IrBlockKind::Internal);
build.beginBlock(entry);
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(0), build.constDouble(2.0));
build.inst(IrCmd::CHECK_TAG, build.vmReg(0), build.constTag(ttable), build.vmExit(1));
build.inst(IrCmd::STORE_TVALUE, build.vmReg(1), build.inst(IrCmd::LOAD_TVALUE, build.vmReg(0)));
build.inst(IrCmd::RETURN, build.vmReg(1), build.constInt(1));
updateUseCounts(build.function);
computeCfgInfo(build.function);
constPropInBlockChains(build, true);
CHECK("\n" + toString(build.function, IncludeUseInfo::No) == R"(
bb_0:
STORE_DOUBLE R0, 2
JUMP exit(1)
)");
}
TEST_SUITE_END();
TEST_SUITE_BEGIN("Analysis");

View file

@ -12,7 +12,6 @@
#include <memory>
LUAU_FASTFLAG(LuauCodegenVectorTag2)
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores4)
LUAU_FASTFLAG(LuauCodegenLoadTVTag)
@ -65,8 +64,6 @@ TEST_SUITE_BEGIN("IrLowering");
TEST_CASE("VectorReciprocal")
{
ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function vecrcp(a: vector)
return 1 / a
@ -121,8 +118,6 @@ bb_bytecode_1:
TEST_CASE("VectorAdd")
{
ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function vec3add(a: vector, b: vector)
return a + b
@ -149,8 +144,6 @@ bb_bytecode_1:
TEST_CASE("VectorMinus")
{
ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
local function vec3minus(a: vector)
return -a
@ -175,7 +168,6 @@ bb_bytecode_1:
TEST_CASE("VectorSubMulDiv")
{
ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true};
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores4, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
@ -210,7 +202,6 @@ bb_bytecode_1:
TEST_CASE("VectorSubMulDiv2")
{
ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true};
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores4, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
@ -241,7 +232,6 @@ bb_bytecode_1:
TEST_CASE("VectorMulDivMixed")
{
ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true};
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores4, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(
@ -405,7 +395,6 @@ bb_bytecode_0:
TEST_CASE("VectorConstantTag")
{
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores4, true};
ScopedFastFlag luauCodegenVectorTag2{FFlag::LuauCodegenVectorTag2, true};
ScopedFastFlag luauCodegenLoadTVTag{FFlag::LuauCodegenLoadTVTag, true};
CHECK_EQ("\n" + getCodegenAssembly(R"(

View file

@ -450,6 +450,26 @@ TEST_CASE_FIXTURE(SubtypeFixture, "basic_typefamily_with_generics")
CHECK(result.isSubtype);
}
TEST_CASE_FIXTURE(SubtypeFixture, "variadic_subpath_in_pack")
{
TypePackId subTArgs = arena.addTypePack(TypePack{{builtinTypes->stringType, builtinTypes->stringType}, builtinTypes->anyTypePack});
TypePackId superTArgs = arena.addTypePack(TypePack{{builtinTypes->numberType}, builtinTypes->anyTypePack});
// (string, string, ...any) -> number
TypeId functionSub = arena.addType(FunctionType{subTArgs, arena.addTypePack({builtinTypes->numberType})});
// (number, ...any) -> string
TypeId functionSuper = arena.addType(FunctionType{superTArgs, arena.addTypePack({builtinTypes->stringType})});
SubtypingResult result = isSubtype(functionSub, functionSuper);
CHECK(result.reasoning == std::vector{SubtypingReasoning{TypePath::PathBuilder().rets().index(0).build(),
TypePath::PathBuilder().rets().index(0).build(), SubtypingVariance::Covariant},
SubtypingReasoning{TypePath::PathBuilder().args().index(0).build(), TypePath::PathBuilder().args().index(0).build(),
SubtypingVariance::Contravariant},
SubtypingReasoning{TypePath::PathBuilder().args().index(1).build(),
TypePath::PathBuilder().args().tail().variadic().build(), SubtypingVariance::Contravariant}});
CHECK(!result.isSubtype);
}
TEST_CASE_FIXTURE(SubtypeFixture, "any <!: unknown")
{
CHECK_IS_NOT_SUBTYPE(builtinTypes->anyType, builtinTypes->unknownType);

View file

@ -14,6 +14,7 @@ LUAU_FASTFLAG(LuauRecursiveTypeParameterRestriction);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauCheckedFunctionSyntax);
LUAU_FASTFLAG(DebugLuauSharedSelf);
LUAU_FASTFLAG(LuauStringifyCyclesRootedAtPacks);
TEST_SUITE_BEGIN("ToString");
@ -878,7 +879,11 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_include_self_param")
TypeId parentTy = requireType("foo");
auto ttv = get<TableType>(follow(parentTy));
auto ftv = get<FunctionType>(follow(ttv->props.at("method").type()));
REQUIRE(ttv);
TypeId methodTy = ttv->props.at("method").type();
auto ftv = get<FunctionType>(follow(methodTy));
REQUIRE_MESSAGE(ftv, methodTy);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK_EQ("foo:method(self: unknown, arg: string): ()", toStringNamedFunction("foo:method", *ftv));
@ -998,4 +1003,34 @@ TEST_CASE_FIXTURE(Fixture, "read_only_properties")
CHECK("{ read x: string }" == toString(requireTypeAlias("B"), {true}));
}
TEST_CASE_FIXTURE(Fixture, "cycle_rooted_in_a_pack")
{
ScopedFastFlag sff{FFlag::LuauStringifyCyclesRootedAtPacks, true};
TypeArena arena;
TypePackId thePack = arena.addTypePack({builtinTypes->numberType, builtinTypes->numberType});
TypePack* packPtr = getMutable<TypePack>(thePack);
REQUIRE(packPtr);
const TableType::Props theProps = {
{"BaseField", Property::readonly(builtinTypes->unknownType)},
{"BaseMethod", Property::readonly(arena.addType(
FunctionType{
thePack,
arena.addTypePack({})
}
))}
};
TypeId theTable = arena.addType(TableType{theProps, {}, TypeLevel{}, TableState::Sealed});
packPtr->head[0] = theTable;
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("tp1 where tp1 = { read BaseField: unknown, read BaseMethod: (tp1) -> () }, number" == toString(thePack));
else
CHECK("tp1 where tp1 = {| BaseField: unknown, BaseMethod: (tp1) -> () |}, number" == toString(thePack));
}
TEST_SUITE_END();

View file

@ -552,6 +552,21 @@ TEST_CASE_FIXTURE(ClassFixture, "keyof_type_family_common_subset_if_union_of_dif
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(ClassFixture, "binary_type_family_works_with_default_argument")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
type result = mul<number>
local function thunk(): result return 5 * 4 end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK("() -> number" == toString(requireType("thunk")));
}
TEST_CASE_FIXTURE(ClassFixture, "vector2_multiply_is_overloaded")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)

View file

@ -687,7 +687,7 @@ TEST_CASE_FIXTURE(Fixture, "higher_order_function_3")
REQUIRE_EQ(1, argVec.size());
const TableType* argType = get<TableType>(follow(argVec[0]));
REQUIRE(argType != nullptr);
REQUIRE_MESSAGE(argType != nullptr, argVec[0]);
CHECK(bool(argType->indexer));
}
@ -2422,4 +2422,26 @@ TEST_CASE_FIXTURE(Fixture, "pass_table_literal_to_function_expecting_optional_pr
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(Fixture, "dont_infer_overloaded_functions")
{
CheckResult result = check(R"(
function getR6Attachments(model)
model:FindFirstChild("Right Leg")
model:FindFirstChild("Left Leg")
model:FindFirstChild("Torso")
model:FindFirstChild("Torso")
model:FindFirstChild("Head")
model:FindFirstChild("Left Arm")
model:FindFirstChild("Right Arm")
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
if (FFlag::DebugLuauDeferredConstraintResolution)
CHECK("<a...>(t1) -> () where t1 = { read FindFirstChild: (t1, string) -> (a...) }" == toString(requireType("getR6Attachments")));
else
CHECK("<a...>(t1) -> () where t1 = {+ FindFirstChild: (t1, string) -> (a...) +}" == toString(requireType("getR6Attachments")));
}
TEST_SUITE_END();

View file

@ -402,14 +402,6 @@ TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods")
end
)");
if (FFlag::DebugLuauDeferredConstraintResolution)
{
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("{ f: (t1) -> (), id: <a>(unknown, a) -> a } where t1 = { read id: ((t1, number) -> number) & ((t1, string) -> string) }",
toString(requireType("x"), {true}));
}
else
LUAU_REQUIRE_ERRORS(result);
}

View file

@ -1054,4 +1054,20 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "iterate_array_of_singletons")
LUAU_REQUIRE_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "iter_mm_results_are_lvalue")
{
CheckResult result = check(R"(
local foo = setmetatable({}, {
__iter = function()
return pairs({1, 2, 3})
end,
})
for k, v in foo do
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -2150,5 +2150,23 @@ TEST_CASE_FIXTURE(RefinementClassFixture, "mutate_prop_of_some_refined_symbol_2"
LUAU_REQUIRE_NO_ERRORS(result);
}
TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_t_after_return_references_all_reachable_points")
{
CheckResult result = check(R"(
local t = {}
local function f(k: string)
if t[k] ~= nil then
return
end
t[k] = 5
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("{ [string]: number }", toString(requireTypeAtPosition({8, 12}), {true}));
}
TEST_SUITE_END();

View file

@ -40,7 +40,6 @@ function FadeValue:destroy()
self.finalCallback = nil
end
)");
}
TEST_CASE_FIXTURE(Fixture, "basic")
@ -4247,6 +4246,43 @@ TEST_CASE_FIXTURE(Fixture, "refined_thing_can_be_an_array")
CHECK("<a>({a}, a) -> a" == toString(requireType("foo")));
}
TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_string")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
function f(t)
local s: string = t
t[5] = 7
end
)");
LUAU_REQUIRE_ERROR_COUNT(3, result);
CHECK_EQ("Parameter 't' has been reduced to never. This function is not callable with any possible value.", toString(result.errors[0]));
CHECK_EQ("Parameter 't' is required to be a subtype of 'string' here.", toString(result.errors[1]));
CHECK_EQ("Parameter 't' is required to be a subtype of '{number}' here.", toString(result.errors[2]));
}
TEST_CASE_FIXTURE(Fixture, "parameter_was_set_an_indexer_and_bounded_by_another_parameter")
{
if (!FFlag::DebugLuauDeferredConstraintResolution)
return;
CheckResult result = check(R"(
function f(t1, t2)
t1[5] = 7 -- 't1 <: {number}
t2 = t1 -- 't1 <: 't2
t1[5] = 7 -- 't1 <: {number}
end
)");
LUAU_REQUIRE_NO_ERRORS(result);
CHECK_EQ("({number}, unknown) -> ()", toString(requireType("f")));
}
TEST_CASE_FIXTURE(Fixture, "mymovie_read_write_tables_bug")
{
CheckResult result = check(R"(
@ -4286,6 +4322,4 @@ TEST_CASE_FIXTURE(Fixture, "mymovie_read_write_tables_bug_2")
LUAU_REQUIRE_ERRORS(result);
}
TEST_SUITE_END();

View file

@ -451,5 +451,4 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "typestates_preserve_error_suppression_proper
CHECK("*error-type* | string" == toString(requireTypeAtPosition({3, 16}), {true}));
}
TEST_SUITE_END();

View file

@ -173,6 +173,41 @@ end
assert(pcall(fuzzfail18) == true)
assert(fuzzfail18() == 0)
local function fuzzfail19()
local _ = 2
_ += _
_ = _,_ >= _,{_ >= _,_ >= _,_(),}
local _ = 2
do
_ = assert({n0=_,_,n0=_,}),{_={_[_()],},_,}
end
end
assert(pcall(fuzzfail19) == false)
local function fuzzfail20()
assert(true)
assert(false,(_),true)
_ = nil
end
assert(pcall(fuzzfail20) == false)
local function fuzzfail21(...)
local _ = assert,_
if _ then else return _ / _ end
_(_)
_(_,_)
assert(...,_)
_((not _),_)
_(true,_ / _)
_(_,_())
return _
end
assert(pcall(fuzzfail21) == false)
local function arraySizeInv1()
local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true}

View file

@ -39,9 +39,6 @@ DefinitionTests.class_definition_overload_metamethods
DefinitionTests.class_definition_string_props
DefinitionTests.declaring_generic_functions
DefinitionTests.definition_file_classes
Differ.equal_generictp_cyclic
Differ.generictp_normal
Differ.generictp_normal_2
Differ.metatable_metamissing_left
Differ.metatable_metamissing_right
Differ.metatable_metanormal
@ -79,7 +76,6 @@ GenericsTests.infer_generic_function_function_argument_2
GenericsTests.infer_generic_function_function_argument_3
GenericsTests.infer_generic_function_function_argument_overloaded
GenericsTests.instantiated_function_argument_names
GenericsTests.mutable_state_polymorphism
GenericsTests.no_stack_overflow_from_quantifying
GenericsTests.properties_can_be_instantiated_polytypes
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
@ -115,7 +111,6 @@ IntersectionTypes.union_saturate_overloaded_functions
Linter.FormatStringTyped
Linter.TableOperationsIndexer
ModuleTests.clone_self_property
Negations.cofinite_strings_can_be_compared_for_equality
Negations.negated_string_is_a_subtype_of_string
NonstrictModeTests.inconsistent_module_return_types_are_ok
NonstrictModeTests.infer_nullary_function
@ -202,6 +197,7 @@ TableTests.explicitly_typed_table_with_indexer
TableTests.generalize_table_argument
TableTests.generic_table_instantiation_potential_regression
TableTests.indexer_mismatch
TableTests.indexer_on_sealed_table_must_unify_with_free_table
TableTests.indexers_get_quantified_too
TableTests.infer_indexer_from_array_like_table
TableTests.infer_indexer_from_its_variable_type_and_unifiable
@ -218,12 +214,15 @@ TableTests.meta_add_inferred
TableTests.metatable_mismatch_should_fail
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
TableTests.mixed_tables_with_implicit_numbered_keys
TableTests.nil_assign_doesnt_hit_indexer
TableTests.ok_to_provide_a_subtype_during_construction
TableTests.ok_to_set_nil_even_on_non_lvalue_base_expr
TableTests.okay_to_add_property_to_unsealed_tables_by_assignment
TableTests.okay_to_add_property_to_unsealed_tables_by_function_call
TableTests.only_ascribe_synthetic_names_at_module_scope
TableTests.open_table_unification_2
TableTests.parameter_was_set_an_indexer_and_bounded_by_another_parameter
TableTests.parameter_was_set_an_indexer_and_bounded_by_string
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
TableTests.persistent_sealed_table_is_immutable
@ -256,7 +255,6 @@ TableTests.unification_of_unions_in_a_self_referential_type
TableTests.used_colon_instead_of_dot
TableTests.used_dot_instead_of_colon
TableTests.when_augmenting_an_unsealed_table_with_an_indexer_apply_the_correct_scope_to_the_indexer_type
TableTests.wrong_assign_does_hit_indexer
ToDot.function
ToString.exhaustive_toString_of_cyclic_table
ToString.free_types
@ -267,7 +265,6 @@ ToString.primitive
ToString.tostring_unsee_ttv_if_array
ToString.toStringDetailed2
ToString.toStringErrorPack
ToString.toStringNamedFunction_map
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
TryUnifyTests.uninhabited_table_sub_anything
@ -302,10 +299,10 @@ TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
TypeInfer.dont_ice_when_failing_the_occurs_check
TypeInfer.dont_report_type_errors_within_an_AstExprError
TypeInfer.dont_report_type_errors_within_an_AstStatError
TypeInfer.follow_on_new_types_in_substitution
TypeInfer.globals
TypeInfer.globals2
TypeInfer.globals_are_banned_in_strict_mode
TypeInfer.infer_locals_via_assignment_from_its_call_site
TypeInfer.infer_through_group_expr
TypeInfer.no_stack_overflow_from_isoptional
TypeInfer.promote_tail_type_packs
@ -320,6 +317,8 @@ TypeInferAnyError.any_type_propagates
TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any
TypeInferAnyError.call_to_any_yields_any
TypeInferAnyError.can_subscript_any
TypeInferAnyError.for_in_loop_iterator_is_error
TypeInferAnyError.for_in_loop_iterator_is_error2
TypeInferAnyError.metatable_of_any_can_be_a_table
TypeInferAnyError.quantify_any_does_not_bind_to_itself
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
@ -357,6 +356,7 @@ TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosi
TypeInferFunctions.function_is_supertype_of_concrete_functions
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
TypeInferFunctions.generic_packs_are_not_variadic
TypeInferFunctions.higher_order_function_3
TypeInferFunctions.higher_order_function_4
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
TypeInferFunctions.improved_function_arg_mismatch_errors
@ -419,7 +419,6 @@ TypeInferModules.do_not_modify_imported_types
TypeInferModules.do_not_modify_imported_types_5
TypeInferModules.require
TypeInferOOP.CheckMethodsOfSealed
TypeInferOOP.cycle_between_object_constructor_and_alias
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon