mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/628 (#1278)
### What's new? * Remove a case of unsound `table.move` optimization * Add Luau stack slot reservations that were missing in REPL (fixes #1273) ### New Type Solver * Assignments have been completely reworked to fix a case of cyclic constraint dependency * When indexing, if the fresh type's upper bound already contains a compatible indexer, do not add another upper bound * Distribute type arguments over all type families sans `eq`, `keyof`, `rawkeyof`, and other internal type families * Fix a case where `buffers` component weren't read in two places (fixes #1267) * Fix a case where things that constitutes a strong ref were slightly incorrect * Fix a case where constraint dependencies weren't setup wrt `for ... in` statement ### Native Codegen * Fix an optimization that splits TValue store only when its value and its tag are compatible * Implement a system to plug additional type information for custom host userdata types --- ### 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: Vighnesh Vijay <vvijay@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Vighnesh <vvijay@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
c8fe77c268
commit
daf79328fc
@ -179,23 +179,6 @@ struct HasPropConstraint
|
||||
bool suppressSimplification = false;
|
||||
};
|
||||
|
||||
// result ~ setProp subjectType ["prop", "prop2", ...] propType
|
||||
//
|
||||
// If the subject is a table or table-like thing that already has the named
|
||||
// property chain, we unify propType with that existing property type.
|
||||
//
|
||||
// If the subject is a free table, we augment it in place.
|
||||
//
|
||||
// If the subject is an unsealed table, result is an augmented table that
|
||||
// includes that new prop.
|
||||
struct SetPropConstraint
|
||||
{
|
||||
TypeId resultType;
|
||||
TypeId subjectType;
|
||||
std::vector<std::string> path;
|
||||
TypeId propType;
|
||||
};
|
||||
|
||||
// resultType ~ hasIndexer subjectType indexType
|
||||
//
|
||||
// If the subject type is a table or table-like thing that supports indexing,
|
||||
@ -209,16 +192,37 @@ struct HasIndexerConstraint
|
||||
TypeId indexType;
|
||||
};
|
||||
|
||||
// result ~ setIndexer subjectType indexType propType
|
||||
//
|
||||
// If the subject is a table or table-like thing that already has an indexer,
|
||||
// unify its indexType and propType with those from this constraint.
|
||||
//
|
||||
// If the table is a free or unsealed table, we augment it with a new indexer.
|
||||
struct SetIndexerConstraint
|
||||
struct AssignConstraint
|
||||
{
|
||||
TypeId subjectType;
|
||||
TypeId lhsType;
|
||||
TypeId rhsType;
|
||||
};
|
||||
|
||||
// assign lhsType propName rhsType
|
||||
//
|
||||
// Assign a value of type rhsType into the named property of lhsType.
|
||||
|
||||
struct AssignPropConstraint
|
||||
{
|
||||
TypeId lhsType;
|
||||
std::string propName;
|
||||
TypeId rhsType;
|
||||
|
||||
/// The canonical write type of the property. It is _solely_ used to
|
||||
/// populate astTypes during constraint resolution. Nothing should ever
|
||||
/// block on it.
|
||||
TypeId propType;
|
||||
};
|
||||
|
||||
struct AssignIndexConstraint
|
||||
{
|
||||
TypeId lhsType;
|
||||
TypeId indexType;
|
||||
TypeId rhsType;
|
||||
|
||||
/// The canonical write type of the property. It is _solely_ used to
|
||||
/// populate astTypes during constraint resolution. Nothing should ever
|
||||
/// block on it.
|
||||
TypeId propType;
|
||||
};
|
||||
|
||||
@ -230,25 +234,6 @@ struct UnpackConstraint
|
||||
{
|
||||
TypePackId resultPack;
|
||||
TypePackId sourcePack;
|
||||
|
||||
// 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 ~ 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;
|
||||
};
|
||||
|
||||
// ty ~ reduce ty
|
||||
@ -268,8 +253,8 @@ struct ReducePackConstraint
|
||||
};
|
||||
|
||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, IterableConstraint, NameConstraint,
|
||||
TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint, SetPropConstraint,
|
||||
HasIndexerConstraint, SetIndexerConstraint, UnpackConstraint, Unpack1Constraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
|
||||
TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint, HasIndexerConstraint,
|
||||
AssignConstraint, AssignPropConstraint, AssignIndexConstraint, UnpackConstraint, ReduceConstraint, ReducePackConstraint, EqualityConstraint>;
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
|
@ -254,18 +254,11 @@ 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);
|
||||
|
||||
struct LValueBounds
|
||||
{
|
||||
std::optional<TypeId> annotationTy;
|
||||
std::optional<TypeId> assignedTy;
|
||||
};
|
||||
|
||||
LValueBounds checkLValue(const ScopePtr& scope, AstExpr* expr);
|
||||
LValueBounds checkLValue(const ScopePtr& scope, AstExprLocal* local);
|
||||
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);
|
||||
void visitLValue(const ScopePtr& scope, AstExpr* expr, TypeId rhsType);
|
||||
void visitLValue(const ScopePtr& scope, AstExprLocal* local, TypeId rhsType);
|
||||
void visitLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId rhsType);
|
||||
void visitLValue(const ScopePtr& scope, AstExprIndexName* indexName, TypeId rhsType);
|
||||
void visitLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr, TypeId rhsType);
|
||||
|
||||
struct FunctionSignature
|
||||
{
|
||||
|
@ -134,7 +134,6 @@ 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 tryDispatchHasIndexer(
|
||||
int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen);
|
||||
@ -142,11 +141,13 @@ struct ConstraintSolver
|
||||
|
||||
std::pair<bool, std::optional<TypeId>> 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 tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType, bool resultIsLValue);
|
||||
bool tryDispatch(const AssignConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const AssignPropConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const AssignIndexConstraint& c, NotNull<const Constraint> constraint);
|
||||
|
||||
bool tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultType, TypeId sourceType);
|
||||
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
||||
bool tryDispatch(const Unpack1Constraint& c, NotNull<const Constraint> constraint);
|
||||
|
||||
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
@ -165,6 +166,17 @@ struct ConstraintSolver
|
||||
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);
|
||||
|
||||
/**
|
||||
* Generate constraints to unpack the types of srcTypes and assign each
|
||||
* value to the corresponding LocalType in destTypes.
|
||||
*
|
||||
* @param destTypes A finite TypePack comprised of LocalTypes.
|
||||
* @param srcTypes A TypePack that represents rvalues to be assigned.
|
||||
* @returns The underlying UnpackConstraint. There's a bit of code in
|
||||
* iteration that needs to pass blocks on to this constraint.
|
||||
*/
|
||||
NotNull<const Constraint> unpackAndAssign(TypePackId destTypes, TypePackId srcTypes, NotNull<const Constraint> constraint);
|
||||
|
||||
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
|
||||
/**
|
||||
* Block a constraint on the resolution of a Type.
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include <set>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -19,6 +20,8 @@ struct SimplifyResult
|
||||
};
|
||||
|
||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
|
||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts);
|
||||
|
||||
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, TypeId discriminant);
|
||||
|
||||
enum class Relation
|
||||
|
@ -6,7 +6,6 @@
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
@ -19,22 +18,6 @@ struct TypeArena;
|
||||
struct TxnLog;
|
||||
class Normalizer;
|
||||
|
||||
struct TypeFamilyQueue
|
||||
{
|
||||
NotNull<VecDeque<TypeId>> queuedTys;
|
||||
NotNull<VecDeque<TypePackId>> queuedTps;
|
||||
|
||||
void add(TypeId instanceTy);
|
||||
void add(TypePackId instanceTp);
|
||||
|
||||
template<typename T>
|
||||
void add(const std::vector<T>& ts)
|
||||
{
|
||||
for (const T& t : ts)
|
||||
enqueue(t);
|
||||
}
|
||||
};
|
||||
|
||||
struct TypeFamilyContext
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
@ -99,8 +82,8 @@ struct TypeFamilyReductionResult
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using ReducerFunction = std::function<TypeFamilyReductionResult<T>(
|
||||
T, NotNull<TypeFamilyQueue>, const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)>;
|
||||
using ReducerFunction = std::function<TypeFamilyReductionResult<T>(T, const std::vector<TypeId>&, const std::vector<TypePackId>&,
|
||||
NotNull<TypeFamilyContext>)>;
|
||||
|
||||
/// Represents a type function that may be applied to map a series of types and
|
||||
/// type packs to a single output type.
|
||||
|
@ -55,6 +55,9 @@ struct InConditionalContext
|
||||
|
||||
using ScopePtr = std::shared_ptr<struct Scope>;
|
||||
|
||||
std::optional<Property> findTableProperty(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location);
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||
|
@ -56,6 +56,11 @@ bool isReferenceCountedType(const TypeId typ)
|
||||
|
||||
DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
||||
{
|
||||
// For the purpose of this function and reference counting in general, we are only considering
|
||||
// mutations that affect the _bounds_ of the free type, and not something that may bind the free
|
||||
// type itself to a new type. As such, `ReduceConstraint` and `GeneralizationConstraint` have no
|
||||
// contribution to the output set here.
|
||||
|
||||
DenseHashSet<TypeId> types{{}};
|
||||
ReferenceCountInitializer rci{&types};
|
||||
|
||||
@ -74,11 +79,6 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
||||
rci.traverse(psc->subPack);
|
||||
rci.traverse(psc->superPack);
|
||||
}
|
||||
else if (auto gc = get<GeneralizationConstraint>(*this))
|
||||
{
|
||||
rci.traverse(gc->generalizedType);
|
||||
// `GeneralizationConstraints` should not mutate `sourceType` or `interiorTypes`.
|
||||
}
|
||||
else if (auto itc = get<IterableConstraint>(*this))
|
||||
{
|
||||
rci.traverse(itc->variables);
|
||||
@ -101,36 +101,32 @@ DenseHashSet<TypeId> Constraint::getMaybeMutatedFreeTypes() const
|
||||
rci.traverse(hpc->resultType);
|
||||
// `HasPropConstraints` should not mutate `subjectType`.
|
||||
}
|
||||
else if (auto spc = get<SetPropConstraint>(*this))
|
||||
{
|
||||
rci.traverse(spc->resultType);
|
||||
// `SetPropConstraints` should not mutate `subjectType` or `propType`.
|
||||
// TODO: is this true? it "unifies" with `propType`, so maybe mutates that one too?
|
||||
}
|
||||
else if (auto hic = get<HasIndexerConstraint>(*this))
|
||||
{
|
||||
rci.traverse(hic->resultType);
|
||||
// `HasIndexerConstraint` should not mutate `subjectType` or `indexType`.
|
||||
}
|
||||
else if (auto sic = get<SetIndexerConstraint>(*this))
|
||||
else if (auto ac = get<AssignConstraint>(*this))
|
||||
{
|
||||
rci.traverse(sic->propType);
|
||||
// `SetIndexerConstraints` should not mutate `subjectType` or `indexType`.
|
||||
rci.traverse(ac->lhsType);
|
||||
rci.traverse(ac->rhsType);
|
||||
}
|
||||
else if (auto apc = get<AssignPropConstraint>(*this))
|
||||
{
|
||||
rci.traverse(apc->lhsType);
|
||||
rci.traverse(apc->rhsType);
|
||||
}
|
||||
else if (auto aic = get<AssignIndexConstraint>(*this))
|
||||
{
|
||||
rci.traverse(aic->lhsType);
|
||||
rci.traverse(aic->indexType);
|
||||
rci.traverse(aic->rhsType);
|
||||
}
|
||||
else if (auto uc = get<UnpackConstraint>(*this))
|
||||
{
|
||||
rci.traverse(uc->resultPack);
|
||||
// `UnpackConstraint` should not mutate `sourcePack`.
|
||||
}
|
||||
else if (auto u1c = get<Unpack1Constraint>(*this))
|
||||
{
|
||||
rci.traverse(u1c->resultType);
|
||||
// `Unpack1Constraint` should not mutate `sourceType`.
|
||||
}
|
||||
else if (auto rc = get<ReduceConstraint>(*this))
|
||||
{
|
||||
rci.traverse(rc->ty);
|
||||
}
|
||||
else if (auto rpc = get<ReducePackConstraint>(*this))
|
||||
{
|
||||
rci.traverse(rpc->tp);
|
||||
|
@ -310,7 +310,7 @@ std::optional<TypeId> ConstraintGenerator::lookup(const ScopePtr& scope, Locatio
|
||||
std::optional<TypeId> ty = lookup(scope, location, operand, /*prototype*/ false);
|
||||
if (!ty)
|
||||
{
|
||||
ty = arena->addType(BlockedType{});
|
||||
ty = arena->addType(LocalType{builtinTypes->neverType});
|
||||
rootScope->lvalueTypes[operand] = *ty;
|
||||
}
|
||||
|
||||
@ -739,12 +739,28 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatLocal* stat
|
||||
|
||||
if (hasAnnotation)
|
||||
{
|
||||
for (size_t i = 0; i < statLocal->vars.size; ++i)
|
||||
addConstraint(scope, statLocal->location, AssignConstraint{assignees[i], annotatedTypes[i]});
|
||||
|
||||
TypePackId annotatedPack = arena->addTypePack(std::move(annotatedTypes));
|
||||
addConstraint(scope, statLocal->location, UnpackConstraint{arena->addTypePack(std::move(assignees)), annotatedPack, /*resultIsLValue*/ true});
|
||||
addConstraint(scope, statLocal->location, PackSubtypeConstraint{rvaluePack, annotatedPack});
|
||||
}
|
||||
else
|
||||
addConstraint(scope, statLocal->location, UnpackConstraint{arena->addTypePack(std::move(assignees)), rvaluePack, /*resultIsLValue*/ true});
|
||||
{
|
||||
std::vector<TypeId> valueTypes;
|
||||
valueTypes.reserve(statLocal->vars.size);
|
||||
|
||||
for (size_t i = 0; i < statLocal->vars.size; ++i)
|
||||
valueTypes.push_back(arena->addType(BlockedType{}));
|
||||
|
||||
auto uc = addConstraint(scope, statLocal->location, UnpackConstraint{arena->addTypePack(valueTypes), rvaluePack});
|
||||
|
||||
for (size_t i = 0; i < statLocal->vars.size; ++i)
|
||||
{
|
||||
getMutable<BlockedType>(valueTypes[i])->setOwner(uc);
|
||||
addConstraint(scope, statLocal->location, AssignConstraint{assignees[i], valueTypes[i]});
|
||||
}
|
||||
}
|
||||
|
||||
if (statLocal->vars.size == 1 && statLocal->values.size == 1 && firstValueType && scope.get() == rootScope && !hasAnnotation)
|
||||
{
|
||||
@ -837,7 +853,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFor* for_)
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forIn)
|
||||
{
|
||||
ScopePtr loopScope = childScope(forIn, scope);
|
||||
|
||||
TypePackId iterator = checkPack(scope, forIn->values).tp;
|
||||
|
||||
std::vector<TypeId> variableTypes;
|
||||
@ -862,10 +877,17 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatForIn* forI
|
||||
}
|
||||
|
||||
TypePackId variablePack = arena->addTypePack(std::move(variableTypes));
|
||||
addConstraint(
|
||||
auto iterable = addConstraint(
|
||||
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack, forIn->values.data[0], &module->astForInNextTypes});
|
||||
|
||||
Checkpoint start = checkpoint(this);
|
||||
visit(loopScope, forIn->body);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
// This iter constraint must dispatch first.
|
||||
forEachConstraint(start, end, this, [&iterable](const ConstraintPtr& runLater) {
|
||||
runLater->dependencies.push_back(iterable);
|
||||
});
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
@ -957,67 +979,63 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
||||
// Name could be AstStatLocal, AstStatGlobal, AstStatIndexName.
|
||||
// With or without self
|
||||
|
||||
TypeId generalizedType = arena->addType(BlockedType{});
|
||||
Checkpoint start = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, function->func, /* expectedType */ std::nullopt, function->name->location);
|
||||
bool sigFullyDefined = !hasFreeType(sig.signature);
|
||||
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
TypeId generalizedType = arena->addType(BlockedType{});
|
||||
if (sigFullyDefined)
|
||||
emplaceType<BoundType>(asMutable(generalizedType), sig.signature);
|
||||
else
|
||||
{
|
||||
const ScopePtr& constraintScope = sig.signatureScope ? sig.signatureScope : sig.bodyScope;
|
||||
|
||||
DenseHashSet<Constraint*> excludeList{nullptr};
|
||||
NotNull<Constraint> c = addConstraint(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
|
||||
getMutable<BlockedType>(generalizedType)->setOwner(c);
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(start, end, this, [&c, &previous](const ConstraintPtr& constraint) {
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
|
||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
DefId def = dfg->getDef(function->name);
|
||||
std::optional<TypeId> existingFunctionTy = follow(lookup(scope, function->name->location, def));
|
||||
|
||||
if (get<BlockedType>(existingFunctionTy) && sigFullyDefined)
|
||||
emplaceType<BoundType>(asMutable(*existingFunctionTy), sig.signature);
|
||||
|
||||
if (AstExprLocal* localName = function->name->as<AstExprLocal>())
|
||||
{
|
||||
if (existingFunctionTy)
|
||||
{
|
||||
addConstraint(scope, function->name->location, SubtypeConstraint{generalizedType, *existingFunctionTy});
|
||||
|
||||
Symbol sym{localName->local};
|
||||
scope->bindings[sym].typeId = generalizedType;
|
||||
}
|
||||
else
|
||||
scope->bindings[localName->local] = Binding{generalizedType, localName->location};
|
||||
visitLValue(scope, localName, generalizedType);
|
||||
|
||||
scope->bindings[localName->local] = Binding{sig.signature, localName->location};
|
||||
scope->lvalueTypes[def] = sig.signature;
|
||||
scope->rvalueRefinements[def] = sig.signature;
|
||||
}
|
||||
else if (AstExprGlobal* globalName = function->name->as<AstExprGlobal>())
|
||||
{
|
||||
if (!existingFunctionTy)
|
||||
ice->ice("prepopulateGlobalScope did not populate a global name", globalName->location);
|
||||
|
||||
if (!sigFullyDefined)
|
||||
generalizedType = *existingFunctionTy;
|
||||
// Sketchy: We're specifically looking for BlockedTypes that were
|
||||
// initially created by ConstraintGenerator::prepopulateGlobalScope.
|
||||
if (auto bt = get<BlockedType>(*existingFunctionTy); bt && nullptr == bt->getOwner())
|
||||
emplaceType<BoundType>(asMutable(*existingFunctionTy), generalizedType);
|
||||
|
||||
scope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
|
||||
scope->lvalueTypes[def] = sig.signature;
|
||||
scope->rvalueRefinements[def] = sig.signature;
|
||||
}
|
||||
else if (AstExprIndexName* indexName = function->name->as<AstExprIndexName>())
|
||||
{
|
||||
Checkpoint check1 = checkpoint(this);
|
||||
auto [_, lvalueType] = checkLValue(scope, indexName);
|
||||
Checkpoint check2 = checkpoint(this);
|
||||
|
||||
forEachConstraint(check1, check2, this, [&excludeList](const ConstraintPtr& c) {
|
||||
excludeList.insert(c.get());
|
||||
});
|
||||
|
||||
// TODO figure out how to populate the location field of the table Property.
|
||||
|
||||
if (lvalueType && *lvalueType != generalizedType)
|
||||
{
|
||||
LUAU_ASSERT(get<BlockedType>(lvalueType));
|
||||
emplaceType<BoundType>(asMutable(*lvalueType), generalizedType);
|
||||
}
|
||||
visitLValue(scope, indexName, generalizedType);
|
||||
}
|
||||
else if (AstExprError* err = function->name->as<AstExprError>())
|
||||
{
|
||||
@ -1029,48 +1047,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
|
||||
|
||||
scope->rvalueRefinements[def] = generalizedType;
|
||||
|
||||
checkFunctionBody(sig.bodyScope, function->func);
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
if (!sigFullyDefined)
|
||||
{
|
||||
NotNull<Scope> constraintScope{sig.signatureScope ? sig.signatureScope.get() : sig.bodyScope.get()};
|
||||
std::unique_ptr<Constraint> c =
|
||||
std::make_unique<Constraint>(constraintScope, function->name->location, GeneralizationConstraint{generalizedType, sig.signature});
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(start, end, this, [&c, &excludeList, &previous](const ConstraintPtr& constraint) {
|
||||
if (!excludeList.contains(constraint.get()))
|
||||
c->dependencies.push_back(NotNull{constraint.get()});
|
||||
|
||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// We need to check if the blocked type has no owner here because
|
||||
// if a function is defined twice anywhere in the program like:
|
||||
// `function f() end` and then later like `function f() end`
|
||||
// Then there will be exactly one definition in the scope for it because it's a global
|
||||
// (this is the same as writing f = function() end)
|
||||
// Therefore, when we visit() the multiple different expression of this global variable
|
||||
// They will all be aliased to the same blocked type, which means we can create multiple constraints
|
||||
// for the same blocked type.
|
||||
if (auto blocked = getMutable<BlockedType>(generalizedType); blocked && !blocked->getOwner())
|
||||
blocked->setOwner(addConstraint(scope, std::move(c)));
|
||||
}
|
||||
|
||||
if (BlockedType* bt = getMutable<BlockedType>(follow(existingFunctionTy)); bt && !bt->getOwner())
|
||||
{
|
||||
auto uc = addConstraint(scope, function->name->location, Unpack1Constraint{*existingFunctionTy, generalizedType});
|
||||
bt->setOwner(uc);
|
||||
}
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
|
||||
@ -1124,38 +1100,20 @@ static void bindFreeType(TypeId a, TypeId b)
|
||||
|
||||
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* assign)
|
||||
{
|
||||
std::vector<TypeId> upperBounds;
|
||||
upperBounds.reserve(assign->vars.size);
|
||||
|
||||
std::vector<TypeId> typeStates;
|
||||
typeStates.reserve(assign->vars.size);
|
||||
|
||||
Checkpoint lvalueBeginCheckpoint = checkpoint(this);
|
||||
|
||||
for (AstExpr* lvalue : assign->vars)
|
||||
{
|
||||
auto [upperBound, typeState] = checkLValue(scope, lvalue);
|
||||
upperBounds.push_back(upperBound.value_or(builtinTypes->unknownType));
|
||||
typeStates.push_back(typeState.value_or(builtinTypes->unknownType));
|
||||
}
|
||||
|
||||
Checkpoint lvalueEndCheckpoint = checkpoint(this);
|
||||
|
||||
TypePackId resultPack = checkPack(scope, assign->values).tp;
|
||||
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);
|
||||
std::vector<TypeId> valueTypes;
|
||||
valueTypes.reserve(assign->vars.size);
|
||||
|
||||
for (TypeId assignee : typeStates)
|
||||
for (size_t i = 0; i < assign->vars.size; ++i)
|
||||
valueTypes.push_back(arena->addType(BlockedType{}));
|
||||
|
||||
auto uc = addConstraint(scope, assign->location, UnpackConstraint{arena->addTypePack(valueTypes), resultPack});
|
||||
|
||||
for (size_t i = 0; i < assign->vars.size; ++i)
|
||||
{
|
||||
auto blocked = getMutable<BlockedType>(assignee);
|
||||
|
||||
if (blocked && !blocked->getOwner())
|
||||
blocked->setOwner(uc);
|
||||
getMutable<BlockedType>(valueTypes[i])->setOwner(uc);
|
||||
visitLValue(scope, assign->vars.data[i], valueTypes[i]);
|
||||
}
|
||||
|
||||
return ControlFlow::None;
|
||||
@ -1166,24 +1124,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
|
||||
AstExprBinary binop = AstExprBinary{assign->location, assign->op, assign->var, assign->value};
|
||||
TypeId resultTy = check(scope, &binop).ty;
|
||||
|
||||
auto [upperBound, typeState] = checkLValue(scope, assign->var);
|
||||
|
||||
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;
|
||||
visitLValue(scope, assign->var, resultTy);
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
@ -1897,7 +1838,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprGlobal* globa
|
||||
return Inference{builtinTypes->errorRecoveryType()};
|
||||
}
|
||||
|
||||
Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation)
|
||||
Inference ConstraintGenerator::checkIndexName(
|
||||
const ScopePtr& scope, const RefinementKey* key, AstExpr* indexee, const std::string& index, Location indexLocation)
|
||||
{
|
||||
TypeId obj = check(scope, indexee).ty;
|
||||
TypeId result = arena->addType(BlockedType{});
|
||||
@ -2272,26 +2214,25 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGenerator::checkBinary(
|
||||
}
|
||||
}
|
||||
|
||||
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||
void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExpr* expr, TypeId rhsType)
|
||||
{
|
||||
if (auto local = expr->as<AstExprLocal>())
|
||||
return checkLValue(scope, local);
|
||||
else if (auto global = expr->as<AstExprGlobal>())
|
||||
return checkLValue(scope, global);
|
||||
else if (auto indexName = expr->as<AstExprIndexName>())
|
||||
return checkLValue(scope, indexName);
|
||||
else if (auto indexExpr = expr->as<AstExprIndexExpr>())
|
||||
return checkLValue(scope, indexExpr);
|
||||
else if (auto error = expr->as<AstExprError>())
|
||||
if (auto e = expr->as<AstExprLocal>())
|
||||
visitLValue(scope, e, rhsType);
|
||||
else if (auto e = expr->as<AstExprGlobal>())
|
||||
visitLValue(scope, e, rhsType);
|
||||
else if (auto e = expr->as<AstExprIndexName>())
|
||||
visitLValue(scope, e, rhsType);
|
||||
else if (auto e = expr->as<AstExprIndexExpr>())
|
||||
visitLValue(scope, e, rhsType);
|
||||
else if (auto e = expr->as<AstExprError>())
|
||||
{
|
||||
check(scope, error);
|
||||
return {builtinTypes->errorRecoveryType(), builtinTypes->errorRecoveryType()};
|
||||
// Nothing?
|
||||
}
|
||||
else
|
||||
ice->ice("checkLValue is inexhaustive");
|
||||
ice->ice("Unexpected lvalue expression", expr->location);
|
||||
}
|
||||
|
||||
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local)
|
||||
void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprLocal* local, TypeId rhsType)
|
||||
{
|
||||
std::optional<TypeId> annotatedTy = scope->lookup(local->local);
|
||||
LUAU_ASSERT(annotatedTy);
|
||||
@ -2332,186 +2273,53 @@ ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePt
|
||||
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 = arena->addType(BlockedType{});
|
||||
|
||||
auto unpackC = addConstraint(scope, local->location, Unpack1Constraint{*ty, *assignedTy, /*resultIsLValue*/ true});
|
||||
|
||||
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 {annotatedTy, assignedTy};
|
||||
if (annotatedTy)
|
||||
addConstraint(scope, local->location, SubtypeConstraint{rhsType, *annotatedTy});
|
||||
addConstraint(scope, local->location, AssignConstraint{*ty, rhsType});
|
||||
}
|
||||
|
||||
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprGlobal* global)
|
||||
void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprGlobal* global, TypeId rhsType)
|
||||
{
|
||||
std::optional<TypeId> annotatedTy = scope->lookup(Symbol{global->name});
|
||||
if (annotatedTy)
|
||||
{
|
||||
DefId def = dfg->getDef(global);
|
||||
TypeId assignedTy = arena->addType(BlockedType{});
|
||||
rootScope->lvalueTypes[def] = assignedTy;
|
||||
return {annotatedTy, assignedTy};
|
||||
rootScope->lvalueTypes[def] = rhsType;
|
||||
|
||||
addConstraint(scope, global->location, SubtypeConstraint{rhsType, *annotatedTy});
|
||||
addConstraint(scope, global->location, AssignConstraint{*annotatedTy, rhsType});
|
||||
}
|
||||
else
|
||||
return {annotatedTy, std::nullopt};
|
||||
}
|
||||
|
||||
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexName* indexName)
|
||||
void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprIndexName* expr, TypeId rhsType)
|
||||
{
|
||||
return updateProperty(scope, indexName);
|
||||
TypeId lhsTy = check(scope, expr->expr).ty;
|
||||
TypeId propTy = arena->addType(BlockedType{});
|
||||
module->astTypes[expr] = propTy;
|
||||
addConstraint(scope, expr->location, AssignPropConstraint{lhsTy, expr->index.value, rhsType, propTy});
|
||||
}
|
||||
|
||||
ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr)
|
||||
void ConstraintGenerator::visitLValue(const ScopePtr& scope, AstExprIndexExpr* expr, TypeId rhsType)
|
||||
{
|
||||
return updateProperty(scope, indexExpr);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is mostly about identifying properties that are being inserted into unsealed tables.
|
||||
*
|
||||
* If expr has the form name.a.b.c
|
||||
*/
|
||||
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 = [&]() -> LValueBounds {
|
||||
TypeId resTy = check(scope, expr).ty;
|
||||
return {resTy, std::nullopt};
|
||||
};
|
||||
|
||||
LUAU_ASSERT(expr->is<AstExprIndexName>() || expr->is<AstExprIndexExpr>());
|
||||
|
||||
if (auto indexExpr = expr->as<AstExprIndexExpr>(); indexExpr && !indexExpr->index->is<AstExprConstantString>())
|
||||
if (auto constantString = expr->index->as<AstExprConstantString>())
|
||||
{
|
||||
// An indexer is only interesting in an lvalue-ey way if it is at the
|
||||
// tail of an expression.
|
||||
//
|
||||
// If the indexer is not at the tail, then we are not interested in
|
||||
// augmenting the lhs data structure with a new indexer. Constraint
|
||||
// generation can treat it as an ordinary lvalue.
|
||||
//
|
||||
// eg
|
||||
//
|
||||
// a.b.c[1] = 44 -- lvalue
|
||||
// a.b[4].c = 2 -- rvalue
|
||||
TypeId lhsTy = check(scope, expr->expr).ty;
|
||||
TypeId propTy = arena->addType(BlockedType{});
|
||||
module->astTypes[expr] = propTy;
|
||||
module->astTypes[expr->index] = builtinTypes->stringType; // FIXME? Singleton strings exist.
|
||||
std::string propName{constantString->value.data, constantString->value.size};
|
||||
addConstraint(scope, expr->location, AssignPropConstraint{lhsTy, std::move(propName), rhsType, propTy});
|
||||
|
||||
TypeId subjectType = check(scope, indexExpr->expr).ty;
|
||||
TypeId indexType = check(scope, indexExpr->index).ty;
|
||||
TypeId assignedTy = arena->addType(BlockedType{});
|
||||
auto sic = addConstraint(scope, expr->location, SetIndexerConstraint{subjectType, indexType, assignedTy});
|
||||
getMutable<BlockedType>(assignedTy)->setOwner(sic);
|
||||
|
||||
module->astTypes[expr] = assignedTy;
|
||||
|
||||
return {assignedTy, assignedTy};
|
||||
return;
|
||||
}
|
||||
|
||||
Symbol sym;
|
||||
const Def* def = nullptr;
|
||||
std::vector<std::string> segments;
|
||||
std::vector<AstExpr*> exprs;
|
||||
|
||||
AstExpr* e = expr;
|
||||
while (e)
|
||||
{
|
||||
if (auto global = e->as<AstExprGlobal>())
|
||||
{
|
||||
sym = global->name;
|
||||
def = dfg->getDef(global);
|
||||
break;
|
||||
}
|
||||
else if (auto local = e->as<AstExprLocal>())
|
||||
{
|
||||
sym = local->local;
|
||||
def = dfg->getDef(local);
|
||||
break;
|
||||
}
|
||||
else if (auto indexName = e->as<AstExprIndexName>())
|
||||
{
|
||||
segments.push_back(indexName->index.value);
|
||||
exprs.push_back(e);
|
||||
e = indexName->expr;
|
||||
}
|
||||
else if (auto indexExpr = e->as<AstExprIndexExpr>())
|
||||
{
|
||||
if (auto strIndex = indexExpr->index->as<AstExprConstantString>())
|
||||
{
|
||||
// We need to populate astTypes for the index value.
|
||||
check(scope, indexExpr->index);
|
||||
|
||||
segments.push_back(std::string(strIndex->value.data, strIndex->value.size));
|
||||
exprs.push_back(e);
|
||||
e = indexExpr->expr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fallback();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return fallback();
|
||||
}
|
||||
}
|
||||
|
||||
LUAU_ASSERT(!segments.empty());
|
||||
|
||||
std::reverse(begin(segments), end(segments));
|
||||
std::reverse(begin(exprs), end(exprs));
|
||||
|
||||
LUAU_ASSERT(def);
|
||||
std::optional<std::pair<TypeId, Scope*>> lookupResult = scope->lookupEx(NotNull{def});
|
||||
if (!lookupResult)
|
||||
return fallback();
|
||||
|
||||
const auto [subjectType, subjectScope] = *lookupResult;
|
||||
|
||||
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);
|
||||
|
||||
TypeId prevSegmentTy = updatedType;
|
||||
for (size_t i = 0; i < segments.size(); ++i)
|
||||
{
|
||||
TypeId segmentTy = arena->addType(BlockedType{});
|
||||
module->astTypes[exprs[i]] = segmentTy;
|
||||
ValueContext ctx = i == segments.size() - 1 ? ValueContext::LValue : ValueContext::RValue;
|
||||
auto hasC = addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i], ctx, inConditional(typeContext)});
|
||||
getMutable<BlockedType>(segmentTy)->setOwner(hasC);
|
||||
setC->dependencies.push_back(hasC);
|
||||
prevSegmentTy = segmentTy;
|
||||
}
|
||||
|
||||
module->astTypes[expr] = prevSegmentTy;
|
||||
module->astTypes[e] = updatedType;
|
||||
|
||||
if (!subjectType->persistent)
|
||||
{
|
||||
subjectScope->bindings[sym].typeId = updatedType;
|
||||
|
||||
// This can fail if the user is erroneously trying to augment a builtin
|
||||
// table like os or string.
|
||||
if (auto key = dfg->getRefinementKey(e))
|
||||
{
|
||||
subjectScope->lvalueTypes[key->def] = updatedType;
|
||||
subjectScope->rvalueRefinements[key->def] = updatedType;
|
||||
}
|
||||
}
|
||||
|
||||
return {assignedTy, assignedTy};
|
||||
TypeId lhsTy = check(scope, expr->expr).ty;
|
||||
TypeId indexTy = check(scope, expr->index).ty;
|
||||
TypeId propTy = arena->addType(BlockedType{});
|
||||
module->astTypes[expr] = propTy;
|
||||
addConstraint(scope, expr->location, AssignIndexConstraint{lhsTy, indexTy, rhsType, propTy});
|
||||
}
|
||||
|
||||
Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType)
|
||||
@ -3194,6 +3002,11 @@ void ConstraintGenerator::reportCodeTooComplex(Location location)
|
||||
|
||||
TypeId ConstraintGenerator::makeUnion(const ScopePtr& scope, Location location, TypeId lhs, TypeId rhs)
|
||||
{
|
||||
if (get<NeverType>(follow(lhs)))
|
||||
return rhs;
|
||||
if (get<NeverType>(follow(rhs)))
|
||||
return lhs;
|
||||
|
||||
TypeId resultType = createTypeFamilyInstance(kBuiltinTypeFamilies.unionFamily, {lhs, rhs}, {}, scope, location);
|
||||
|
||||
return resultType;
|
||||
|
@ -206,6 +206,12 @@ static std::pair<std::vector<TypeId>, std::vector<TypePackId>> saturateArguments
|
||||
saturatedPackArguments.push_back(builtinTypes->errorRecoveryTypePack());
|
||||
}
|
||||
|
||||
for (TypeId& arg : saturatedTypeArguments)
|
||||
arg = follow(arg);
|
||||
|
||||
for (TypePackId& pack : saturatedPackArguments)
|
||||
pack = follow(pack);
|
||||
|
||||
// At this point, these two conditions should be true. If they aren't we
|
||||
// will run into access violations.
|
||||
LUAU_ASSERT(saturatedTypeArguments.size() == fn.typeParams.size());
|
||||
@ -407,11 +413,17 @@ void ConstraintSolver::run()
|
||||
// decrement the referenced free types for this constraint if we dispatched successfully!
|
||||
for (auto ty : c->getMaybeMutatedFreeTypes())
|
||||
{
|
||||
// this is a little weird, but because we're only counting free types in subtyping constraints,
|
||||
// some constraints (like unpack) might actually produce _more_ references to a free type.
|
||||
size_t& refCount = unresolvedConstraints[ty];
|
||||
if (refCount > 0)
|
||||
refCount -= 1;
|
||||
|
||||
// We have two constraints that are designed to wait for the
|
||||
// refCount on a free type to be equal to 1: the
|
||||
// PrimitiveTypeConstraint and ReduceConstraint. We
|
||||
// therefore wake any constraint waiting for a free type's
|
||||
// refcount to be 1 or 0.
|
||||
if (refCount <= 1)
|
||||
unblock(ty, Location{});
|
||||
}
|
||||
|
||||
if (logger)
|
||||
@ -518,15 +530,15 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
||||
success = tryDispatch(*fcc, constraint);
|
||||
else if (auto hpc = get<HasPropConstraint>(*constraint))
|
||||
success = tryDispatch(*hpc, constraint);
|
||||
else if (auto spc = get<SetPropConstraint>(*constraint))
|
||||
success = tryDispatch(*spc, constraint);
|
||||
else if (auto spc = get<HasIndexerConstraint>(*constraint))
|
||||
success = tryDispatch(*spc, constraint);
|
||||
else if (auto spc = get<SetIndexerConstraint>(*constraint))
|
||||
success = tryDispatch(*spc, constraint, force);
|
||||
else if (auto uc = get<UnpackConstraint>(*constraint))
|
||||
else if (auto uc = get<AssignConstraint>(*constraint))
|
||||
success = tryDispatch(*uc, constraint);
|
||||
else if (auto uc = get<Unpack1Constraint>(*constraint))
|
||||
else if (auto uc = get<AssignPropConstraint>(*constraint))
|
||||
success = tryDispatch(*uc, constraint);
|
||||
else if (auto uc = get<AssignIndexConstraint>(*constraint))
|
||||
success = tryDispatch(*uc, constraint);
|
||||
else if (auto uc = get<UnpackConstraint>(*constraint))
|
||||
success = tryDispatch(*uc, constraint);
|
||||
else if (auto rc = get<ReduceConstraint>(*constraint))
|
||||
success = tryDispatch(*rc, constraint, force);
|
||||
@ -688,7 +700,18 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
|
||||
getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy};
|
||||
|
||||
pushConstraint(constraint->scope, constraint->location, SubtypeConstraint{nextTy, tableTy});
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, arena->addTypePack({keyTy, valueTy}), /*resultIsLValue=*/true});
|
||||
|
||||
auto it = begin(c.variables);
|
||||
auto endIt = end(c.variables);
|
||||
|
||||
if (it != endIt)
|
||||
{
|
||||
pushConstraint(constraint->scope, constraint->location, AssignConstraint{*it, keyTy});
|
||||
++it;
|
||||
}
|
||||
if (it != endIt)
|
||||
pushConstraint(constraint->scope, constraint->location, AssignConstraint{*it, valueTy});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -915,7 +938,17 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
|
||||
// Type function application will happily give us the exact same type if
|
||||
// there are e.g. generic saturatedTypeArguments that go unused.
|
||||
const TableType* tfTable = getTableType(tf->type);
|
||||
bool needsClone = follow(tf->type) == target || (tfTable != nullptr && tfTable == getTableType(target));
|
||||
|
||||
//clang-format off
|
||||
bool needsClone =
|
||||
follow(tf->type) == target ||
|
||||
(tfTable != nullptr && tfTable == getTableType(target)) ||
|
||||
std::any_of(typeArguments.begin(), typeArguments.end(), [&](const auto& other) {
|
||||
return other == target;
|
||||
}
|
||||
);
|
||||
//clang-format on
|
||||
|
||||
// Only tables have the properties we're trying to set.
|
||||
TableType* ttv = getMutableTableType(target);
|
||||
|
||||
@ -1291,158 +1324,6 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool isUnsealedTable(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
const TableType* ttv = get<TableType>(ty);
|
||||
return ttv && ttv->state == TableState::Unsealed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a path into a set of nested unsealed tables `ty`, insert a new property `replaceTy` as the leaf-most property.
|
||||
*
|
||||
* Fails and does nothing if every table along the way is not unsealed.
|
||||
*
|
||||
* Mutates the innermost table type in-place.
|
||||
*/
|
||||
static void updateTheTableType(
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty, const std::vector<std::string>& path, TypeId replaceTy)
|
||||
{
|
||||
if (path.empty())
|
||||
return;
|
||||
|
||||
// First walk the path and ensure that it's unsealed tables all the way
|
||||
// to the end.
|
||||
{
|
||||
TypeId t = ty;
|
||||
for (size_t i = 0; i < path.size() - 1; ++i)
|
||||
{
|
||||
if (!isUnsealedTable(t))
|
||||
return;
|
||||
|
||||
const TableType* tbl = get<TableType>(t);
|
||||
auto it = tbl->props.find(path[i]);
|
||||
if (it == tbl->props.end())
|
||||
return;
|
||||
|
||||
t = follow(it->second.type());
|
||||
}
|
||||
|
||||
// The last path segment should not be a property of the table at all.
|
||||
// We are not changing property types. We are only admitting this one
|
||||
// new property to be appended.
|
||||
if (!isUnsealedTable(t))
|
||||
return;
|
||||
const TableType* tbl = get<TableType>(t);
|
||||
if (0 != tbl->props.count(path.back()))
|
||||
return;
|
||||
}
|
||||
|
||||
TypeId t = ty;
|
||||
ErrorVec dummy;
|
||||
|
||||
for (size_t i = 0; i < path.size() - 1; ++i)
|
||||
{
|
||||
t = follow(t);
|
||||
auto propTy = findTablePropertyRespectingMeta(builtinTypes, dummy, t, path[i], ValueContext::LValue, Location{});
|
||||
dummy.clear();
|
||||
|
||||
if (!propTy)
|
||||
return;
|
||||
|
||||
t = *propTy;
|
||||
}
|
||||
|
||||
const std::string& lastSegment = path.back();
|
||||
|
||||
t = follow(t);
|
||||
TableType* tt = getMutable<TableType>(t);
|
||||
if (auto mt = get<MetatableType>(t))
|
||||
tt = getMutable<TableType>(mt->table);
|
||||
|
||||
if (!tt)
|
||||
return;
|
||||
|
||||
tt->props[lastSegment].setType(replaceTy);
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
TypeId subjectType = follow(c.subjectType);
|
||||
const TypeId propType = follow(c.propType);
|
||||
|
||||
if (isBlocked(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
|
||||
std::optional<TypeId> existingPropType = subjectType;
|
||||
|
||||
LUAU_ASSERT(!c.path.empty());
|
||||
if (c.path.empty())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < c.path.size(); ++i)
|
||||
{
|
||||
const std::string& segment = c.path[i];
|
||||
if (!existingPropType)
|
||||
break;
|
||||
|
||||
ValueContext ctx = i == c.path.size() - 1 ? ValueContext::LValue : ValueContext::RValue;
|
||||
|
||||
auto [blocked, result] = lookupTableProp(constraint, *existingPropType, segment, ctx);
|
||||
if (!blocked.empty())
|
||||
{
|
||||
for (TypeId blocked : blocked)
|
||||
block(blocked, constraint);
|
||||
return false;
|
||||
}
|
||||
|
||||
existingPropType = result;
|
||||
}
|
||||
|
||||
auto bind = [&](TypeId a, TypeId b) {
|
||||
bindBlockedType(a, b, subjectType, constraint);
|
||||
};
|
||||
|
||||
if (existingPropType)
|
||||
{
|
||||
unify(constraint, propType, *existingPropType);
|
||||
unify(constraint, *existingPropType, propType);
|
||||
bind(c.resultType, c.subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
const TypeId originalSubjectType = subjectType;
|
||||
|
||||
if (auto mt = get<MetatableType>(subjectType))
|
||||
subjectType = follow(mt->table);
|
||||
|
||||
if (get<FreeType>(subjectType))
|
||||
return false;
|
||||
else if (auto ttv = getMutable<TableType>(subjectType))
|
||||
{
|
||||
if (ttv->state == TableState::Free)
|
||||
{
|
||||
LUAU_ASSERT(!subjectType->persistent);
|
||||
|
||||
ttv->props[c.path[0]] = Property{propType};
|
||||
bind(c.resultType, subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
else if (ttv->state == TableState::Unsealed)
|
||||
{
|
||||
LUAU_ASSERT(!subjectType->persistent);
|
||||
|
||||
updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, propType);
|
||||
}
|
||||
}
|
||||
|
||||
bind(c.resultType, originalSubjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatchHasIndexer(
|
||||
int& recursionDepth, NotNull<const Constraint> constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set<TypeId>& seen)
|
||||
{
|
||||
@ -1460,6 +1341,13 @@ bool ConstraintSolver::tryDispatchHasIndexer(
|
||||
|
||||
if (auto ft = get<FreeType>(subjectType))
|
||||
{
|
||||
if (auto tbl = get<TableType>(follow(ft->upperBound)); tbl && tbl->indexer)
|
||||
{
|
||||
unify(constraint, indexType, tbl->indexer->indexType);
|
||||
bindBlockedType(resultType, tbl->indexer->indexResultType, subjectType, constraint);
|
||||
return true;
|
||||
}
|
||||
|
||||
FreeType freeResult{ft->scope, builtinTypes->neverType, builtinTypes->unknownType};
|
||||
emplaceType<FreeType>(asMutable(resultType), freeResult);
|
||||
|
||||
@ -1708,33 +1596,20 @@ std::pair<bool, std::optional<TypeId>> ConstraintSolver::tryDispatchSetIndexer(
|
||||
return {true, std::nullopt};
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
bool ConstraintSolver::tryDispatch(const AssignConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
TypeId subjectType = follow(c.subjectType);
|
||||
if (isBlocked(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
const TypeId lhsTy = follow(c.lhsType);
|
||||
const TypeId rhsTy = follow(c.rhsType);
|
||||
|
||||
auto [dispatched, resultTy] = tryDispatchSetIndexer(constraint, subjectType, c.indexType, c.propType, /*expandFreeTypeBounds=*/true);
|
||||
if (dispatched)
|
||||
{
|
||||
bindBlockedType(c.propType, resultTy.value_or(builtinTypes->errorRecoveryType()), subjectType, constraint);
|
||||
unblock(c.propType, constraint->location);
|
||||
}
|
||||
|
||||
return dispatched;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultTy, TypeId srcTy, bool resultIsLValue)
|
||||
{
|
||||
resultTy = follow(resultTy);
|
||||
LUAU_ASSERT(canMutate(resultTy, constraint));
|
||||
if (!get<LocalType>(lhsTy) && isBlocked(lhsTy))
|
||||
return block(lhsTy, constraint);
|
||||
|
||||
auto tryExpand = [&](TypeId ty) {
|
||||
LocalType* lt = getMutable<LocalType>(ty);
|
||||
if (!lt || !resultIsLValue)
|
||||
if (!lt)
|
||||
return;
|
||||
|
||||
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, srcTy).result;
|
||||
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, rhsTy).result;
|
||||
LUAU_ASSERT(lt->blockCount > 0);
|
||||
--lt->blockCount;
|
||||
|
||||
@ -1745,11 +1620,289 @@ bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint,
|
||||
}
|
||||
};
|
||||
|
||||
if (auto ut = get<UnionType>(resultTy))
|
||||
std::for_each(begin(ut), end(ut), tryExpand);
|
||||
else if (get<LocalType>(resultTy))
|
||||
tryExpand(resultTy);
|
||||
else if (get<BlockedType>(resultTy))
|
||||
if (auto ut = get<UnionType>(lhsTy))
|
||||
{
|
||||
// FIXME: I suspect there's a bug here where lhsTy is a union that contains no LocalTypes.
|
||||
for (TypeId t : ut)
|
||||
tryExpand(t);
|
||||
}
|
||||
else if (get<LocalType>(lhsTy))
|
||||
tryExpand(lhsTy);
|
||||
else
|
||||
unify(constraint, rhsTy, lhsTy);
|
||||
|
||||
unblock(lhsTy, constraint->location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const AssignPropConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
TypeId lhsType = follow(c.lhsType);
|
||||
const std::string& propName = c.propName;
|
||||
const TypeId rhsType = follow(c.rhsType);
|
||||
|
||||
if (isBlocked(lhsType))
|
||||
return block(lhsType, constraint);
|
||||
|
||||
// 1. lhsType is a class that already has the prop
|
||||
// 2. lhsType is a table that already has the prop (or a union or
|
||||
// intersection that has the prop in aggregate)
|
||||
// 3. lhsType has a metatable that already has the prop
|
||||
// 4. lhsType is an unsealed table that does not have the prop, but has a
|
||||
// string indexer
|
||||
// 5. lhsType is an unsealed table that does not have the prop or a string
|
||||
// indexer
|
||||
|
||||
// Important: In every codepath through this function, the type `c.propType`
|
||||
// must be bound to something, even if it's just the errorType.
|
||||
|
||||
if (auto lhsClass = get<ClassType>(lhsType))
|
||||
{
|
||||
const Property* prop = lookupClassProp(lhsClass, propName);
|
||||
if (!prop || !prop->writeTy.has_value())
|
||||
return true;
|
||||
|
||||
emplaceType<BoundType>(asMutable(c.propType), *prop->writeTy);
|
||||
unify(constraint, rhsType, *prop->writeTy);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto lhsFree = getMutable<FreeType>(lhsType))
|
||||
{
|
||||
if (get<TableType>(lhsFree->upperBound) || get<MetatableType>(lhsFree->upperBound))
|
||||
lhsType = lhsFree->upperBound;
|
||||
else
|
||||
{
|
||||
TypeId newUpperBound = arena->addType(TableType{TableState::Free, TypeLevel{}, constraint->scope});
|
||||
TableType* upperTable = getMutable<TableType>(newUpperBound);
|
||||
LUAU_ASSERT(upperTable);
|
||||
|
||||
upperTable->props[c.propName] = rhsType;
|
||||
|
||||
// Food for thought: Could we block if simplification encounters a blocked type?
|
||||
lhsFree->upperBound = simplifyIntersection(builtinTypes, arena, lhsFree->upperBound, newUpperBound).result;
|
||||
|
||||
emplaceType<BoundType>(asMutable(c.propType), rhsType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle the case that lhsType is a table that already has the property or
|
||||
// a matching indexer. This also handles unions and intersections.
|
||||
const auto [blocked, maybeTy] = lookupTableProp(constraint, lhsType, propName, ValueContext::LValue);
|
||||
if (!blocked.empty())
|
||||
{
|
||||
for (TypeId t : blocked)
|
||||
block(t, constraint);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (maybeTy)
|
||||
{
|
||||
const TypeId propTy = *maybeTy;
|
||||
emplaceType<BoundType>(asMutable(c.propType), propTy);
|
||||
unify(constraint, rhsType, propTy);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto lhsMeta = get<MetatableType>(lhsType))
|
||||
lhsType = follow(lhsMeta->table);
|
||||
|
||||
// Handle the case where the lhs type is a table that does not have the
|
||||
// named property. It could be a table with a string indexer, or an unsealed
|
||||
// or free table that can grow.
|
||||
if (auto lhsTable = getMutable<TableType>(lhsType))
|
||||
{
|
||||
if (auto it = lhsTable->props.find(propName); it != lhsTable->props.end())
|
||||
{
|
||||
Property& prop = it->second;
|
||||
|
||||
if (prop.writeTy.has_value())
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(c.propType), *prop.writeTy);
|
||||
unify(constraint, rhsType, *prop.writeTy);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(prop.isReadOnly());
|
||||
if (lhsTable->state == TableState::Unsealed || lhsTable->state == TableState::Free)
|
||||
{
|
||||
prop.writeTy = prop.readTy;
|
||||
emplaceType<BoundType>(asMutable(c.propType), *prop.writeTy);
|
||||
unify(constraint, rhsType, *prop.writeTy);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(c.propType), builtinTypes->errorType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lhsTable->indexer && maybeString(lhsTable->indexer->indexType))
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(c.propType), rhsType);
|
||||
unify(constraint, rhsType, lhsTable->indexer->indexResultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhsTable->state == TableState::Unsealed || lhsTable->state == TableState::Free)
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(c.propType), rhsType);
|
||||
lhsTable->props[propName] = Property::rw(rhsType);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
emplaceType<BoundType>(asMutable(c.propType), builtinTypes->errorType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const AssignIndexConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
const TypeId lhsType = follow(c.lhsType);
|
||||
const TypeId indexType = follow(c.indexType);
|
||||
const TypeId rhsType = follow(c.rhsType);
|
||||
|
||||
if (isBlocked(lhsType))
|
||||
return block(lhsType, constraint);
|
||||
|
||||
// 0. lhsType could be an intersection or union.
|
||||
// 1. lhsType is a class with an indexer
|
||||
// 2. lhsType is a table with an indexer, or it has a metatable that has an indexer
|
||||
// 3. lhsType is a free or unsealed table and can grow an indexer
|
||||
|
||||
// Important: In every codepath through this function, the type `c.propType`
|
||||
// must be bound to something, even if it's just the errorType.
|
||||
|
||||
auto tableStuff = [&](TableType* lhsTable) -> std::optional<bool> {
|
||||
if (lhsTable->indexer)
|
||||
{
|
||||
unify(constraint, indexType, lhsTable->indexer->indexType);
|
||||
unify(constraint, rhsType, lhsTable->indexer->indexResultType);
|
||||
emplaceType<BoundType>(asMutable(c.propType), lhsTable->indexer->indexResultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhsTable->state == TableState::Unsealed || lhsTable->state == TableState::Free)
|
||||
{
|
||||
lhsTable->indexer = TableIndexer{indexType, rhsType};
|
||||
emplaceType<BoundType>(asMutable(c.propType), rhsType);
|
||||
return true;
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
if (auto lhsFree = getMutable<FreeType>(lhsType))
|
||||
{
|
||||
if (auto lhsTable = getMutable<TableType>(lhsFree->upperBound))
|
||||
{
|
||||
if (auto res = tableStuff(lhsTable))
|
||||
return *res;
|
||||
}
|
||||
|
||||
TypeId newUpperBound =
|
||||
arena->addType(TableType{/*props*/ {}, TableIndexer{indexType, rhsType}, TypeLevel{}, constraint->scope, TableState::Free});
|
||||
const TableType* newTable = get<TableType>(newUpperBound);
|
||||
LUAU_ASSERT(newTable);
|
||||
|
||||
unify(constraint, lhsType, newUpperBound);
|
||||
|
||||
LUAU_ASSERT(newTable->indexer);
|
||||
emplaceType<BoundType>(asMutable(c.propType), newTable->indexer->indexResultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto lhsTable = getMutable<TableType>(lhsType))
|
||||
{
|
||||
std::optional<bool> res = tableStuff(lhsTable);
|
||||
if (res.has_value())
|
||||
return *res;
|
||||
}
|
||||
|
||||
if (auto lhsClass = get<ClassType>(lhsType))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (lhsClass->indexer)
|
||||
{
|
||||
unify(constraint, indexType, lhsClass->indexer->indexType);
|
||||
unify(constraint, rhsType, lhsClass->indexer->indexResultType);
|
||||
emplaceType<BoundType>(asMutable(c.propType), lhsClass->indexer->indexResultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lhsClass->parent)
|
||||
lhsClass = get<ClassType>(lhsClass->parent);
|
||||
else
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto lhsIntersection = getMutable<IntersectionType>(lhsType))
|
||||
{
|
||||
std::set<TypeId> parts;
|
||||
|
||||
for (TypeId t : lhsIntersection)
|
||||
{
|
||||
if (auto tbl = getMutable<TableType>(follow(t)))
|
||||
{
|
||||
if (tbl->indexer)
|
||||
{
|
||||
unify(constraint, indexType, tbl->indexer->indexType);
|
||||
parts.insert(tbl->indexer->indexResultType);
|
||||
}
|
||||
|
||||
if (tbl->state == TableState::Unsealed || tbl->state == TableState::Free)
|
||||
{
|
||||
tbl->indexer = TableIndexer{indexType, rhsType};
|
||||
parts.insert(rhsType);
|
||||
}
|
||||
}
|
||||
else if (auto cls = get<ClassType>(follow(t)))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (cls->indexer)
|
||||
{
|
||||
unify(constraint, indexType, cls->indexer->indexType);
|
||||
parts.insert(cls->indexer->indexResultType);
|
||||
break;
|
||||
}
|
||||
|
||||
if (cls->parent)
|
||||
cls = get<ClassType>(cls->parent);
|
||||
else
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeId res = simplifyIntersection(builtinTypes, arena, std::move(parts)).result;
|
||||
|
||||
unify(constraint, rhsType, res);
|
||||
}
|
||||
|
||||
// Other types do not support index assignment.
|
||||
emplaceType<BoundType>(asMutable(c.propType), builtinTypes->errorType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint, TypeId resultTy, TypeId srcTy)
|
||||
{
|
||||
resultTy = follow(resultTy);
|
||||
LUAU_ASSERT(canMutate(resultTy, constraint));
|
||||
|
||||
LUAU_ASSERT(get<BlockedType>(resultTy));
|
||||
|
||||
if (get<BlockedType>(resultTy))
|
||||
{
|
||||
if (follow(srcTy) == resultTy)
|
||||
{
|
||||
@ -1765,10 +1918,7 @@ bool ConstraintSolver::tryDispatchUnpack1(NotNull<const Constraint> constraint,
|
||||
bindBlockedType(resultTy, srcTy, srcTy, constraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(resultIsLValue);
|
||||
unify(constraint, srcTy, resultTy);
|
||||
}
|
||||
|
||||
unblock(resultTy, constraint->location);
|
||||
return true;
|
||||
@ -1804,7 +1954,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||
|
||||
TypeId srcTy = follow(srcPack.head[i]);
|
||||
TypeId resultTy = follow(*resultIter);
|
||||
tryDispatchUnpack1(constraint, resultTy, srcTy, c.resultIsLValue);
|
||||
tryDispatchUnpack1(constraint, resultTy, srcTy);
|
||||
|
||||
++resultIter;
|
||||
++i;
|
||||
@ -1818,19 +1968,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||
{
|
||||
TypeId resultTy = follow(*resultIter);
|
||||
LUAU_ASSERT(canMutate(resultTy, constraint));
|
||||
if (auto lt = getMutable<LocalType>(resultTy); c.resultIsLValue && lt)
|
||||
{
|
||||
lt->domain = simplifyUnion(builtinTypes, arena, lt->domain, builtinTypes->nilType).result;
|
||||
LUAU_ASSERT(0 <= lt->blockCount);
|
||||
--lt->blockCount;
|
||||
|
||||
if (0 == lt->blockCount)
|
||||
{
|
||||
shiftReferences(resultTy, lt->domain);
|
||||
emplaceType<BoundType>(asMutable(resultTy), lt->domain);
|
||||
}
|
||||
}
|
||||
else if (get<BlockedType>(resultTy) || get<PendingExpansionType>(resultTy))
|
||||
if (get<BlockedType>(resultTy) || get<PendingExpansionType>(resultTy))
|
||||
{
|
||||
emplaceType<BoundType>(asMutable(resultTy), builtinTypes->nilType);
|
||||
unblock(resultTy, constraint->location);
|
||||
@ -1842,11 +1980,6 @@ 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 ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
TypeId ty = follow(c.ty);
|
||||
@ -1942,13 +2075,23 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
getMutable<TableType>(tableTy)->indexer = TableIndexer{keyTy, valueTy};
|
||||
|
||||
pushConstraint(constraint->scope, constraint->location, SubtypeConstraint{iteratorTy, tableTy});
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, arena->addTypePack({keyTy, valueTy}), /*resultIsLValue=*/true});
|
||||
|
||||
auto it = begin(c.variables);
|
||||
auto endIt = end(c.variables);
|
||||
if (it != endIt)
|
||||
{
|
||||
pushConstraint(constraint->scope, constraint->location, AssignConstraint{*it, keyTy});
|
||||
++it;
|
||||
}
|
||||
if (it != endIt)
|
||||
pushConstraint(constraint->scope, constraint->location, AssignConstraint{*it, valueTy});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto unpack = [&](TypeId ty) {
|
||||
TypePackId variadic = arena->addTypePack(VariadicTypePack{ty});
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, variadic, /* resultIsLValue */ true});
|
||||
for (TypeId varTy : c.variables)
|
||||
pushConstraint(constraint->scope, constraint->location, AssignConstraint{varTy, ty});
|
||||
};
|
||||
|
||||
if (get<AnyType>(iteratorTy))
|
||||
@ -2043,10 +2186,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
|
||||
// If nextFn is nullptr, then the iterator function has an improper signature.
|
||||
if (nextFn)
|
||||
{
|
||||
const TypePackId nextRetPack = nextFn->retTypes;
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack, /* resultIsLValue=*/true});
|
||||
}
|
||||
unpackAndAssign(c.variables, nextFn->retTypes, constraint);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2119,12 +2259,37 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
||||
modifiedNextRetHead.push_back(*it);
|
||||
|
||||
TypePackId modifiedNextRetPack = arena->addTypePack(std::move(modifiedNextRetHead), it.tail());
|
||||
auto psc = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, modifiedNextRetPack, /* resultIsLValue */ true});
|
||||
inheritBlocks(constraint, psc);
|
||||
|
||||
auto unpackConstraint = unpackAndAssign(c.variables, modifiedNextRetPack, constraint);
|
||||
|
||||
inheritBlocks(constraint, unpackConstraint);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NotNull<const Constraint> ConstraintSolver::unpackAndAssign(TypePackId destTypes, TypePackId srcTypes, NotNull<const Constraint> constraint)
|
||||
{
|
||||
std::vector<TypeId> unpackedTys;
|
||||
for (TypeId _ty : destTypes)
|
||||
{
|
||||
(void) _ty;
|
||||
unpackedTys.push_back(arena->addType(BlockedType{}));
|
||||
}
|
||||
|
||||
TypePackId unpackedTp = arena->addTypePack(TypePack{unpackedTys});
|
||||
auto unpackConstraint = pushConstraint(constraint->scope, constraint->location, UnpackConstraint{unpackedTp, srcTypes});
|
||||
|
||||
size_t i = 0;
|
||||
for (TypeId varTy : destTypes)
|
||||
{
|
||||
pushConstraint(constraint->scope, constraint->location, AssignConstraint{varTy, unpackedTys[i]});
|
||||
getMutable<BlockedType>(unpackedTys[i])->setOwner(unpackConstraint);
|
||||
++i;
|
||||
}
|
||||
|
||||
return unpackConstraint;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@ -2759,8 +2924,13 @@ std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope,
|
||||
if (get<FreeType>(t))
|
||||
{
|
||||
auto refCount = unresolvedConstraints.find(t);
|
||||
if (!refCount || *refCount > 1)
|
||||
if (refCount && *refCount > 0)
|
||||
return {};
|
||||
|
||||
// if no reference count is present, then that means the only constraints referring to
|
||||
// this free type need only for it to be generalized. in principle, this means we could
|
||||
// have actually never generated the free type in the first place, but we couldn't know
|
||||
// that until all constraint generation is complete.
|
||||
}
|
||||
|
||||
return generalize(NotNull{arena}, builtinTypes, scope, type);
|
||||
@ -2769,7 +2939,7 @@ std::optional<TypeId> ConstraintSolver::generalizeFreeType(NotNull<Scope> scope,
|
||||
bool ConstraintSolver::hasUnresolvedConstraints(TypeId ty)
|
||||
{
|
||||
if (auto refCount = unresolvedConstraints.find(ty))
|
||||
return *refCount > 1;
|
||||
return *refCount > 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -1368,6 +1368,17 @@ SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<
|
||||
return SimplifyResult{res, std::move(s.blockedTypes)};
|
||||
}
|
||||
|
||||
SimplifyResult simplifyIntersection(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, std::set<TypeId> parts)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
|
||||
TypeSimplifier s{builtinTypes, arena};
|
||||
|
||||
TypeId res = s.intersectFromParts(std::move(parts));
|
||||
|
||||
return SimplifyResult{res, std::move(s.blockedTypes)};
|
||||
}
|
||||
|
||||
SimplifyResult simplifyUnion(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId left, TypeId right)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
|
@ -1438,6 +1438,7 @@ SubtypingResult Subtyping::isCovariantWith(
|
||||
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->strings));
|
||||
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->tables));
|
||||
result.andAlso(isCovariantWith(env, subNorm->threads, superNorm->threads));
|
||||
result.andAlso(isCovariantWith(env, subNorm->buffers, superNorm->buffers));
|
||||
result.andAlso(isCovariantWith(env, subNorm->tables, superNorm->tables));
|
||||
result.andAlso(isCovariantWith(env, subNorm->functions, superNorm->functions));
|
||||
// isCovariantWith(subNorm->tyvars, superNorm->tyvars);
|
||||
|
@ -1787,23 +1787,18 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
||||
{
|
||||
return tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\" ctx=" + std::to_string(int(c.context));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SetPropConstraint>)
|
||||
{
|
||||
const std::string pathStr = c.path.size() == 1 ? "\"" + c.path[0] + "\"" : "[\"" + join(c.path, "\", \"") + "\"]";
|
||||
return tos(c.resultType) + " ~ setProp " + tos(c.subjectType) + ", " + pathStr + " " + tos(c.propType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, HasIndexerConstraint>)
|
||||
{
|
||||
return tos(c.resultType) + " ~ hasIndexer " + tos(c.subjectType) + " " + tos(c.indexType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SetIndexerConstraint>)
|
||||
{
|
||||
return "setIndexer " + tos(c.subjectType) + " [ " + tos(c.indexType) + " ] " + tos(c.propType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, AssignConstraint>)
|
||||
return "assign " + tos(c.lhsType) + " " + tos(c.rhsType);
|
||||
else if constexpr (std::is_same_v<T, AssignPropConstraint>)
|
||||
return "assignProp " + tos(c.lhsType) + " " + c.propName + " " + tos(c.rhsType);
|
||||
else if constexpr (std::is_same_v<T, AssignIndexConstraint>)
|
||||
return "assignIndex " + tos(c.lhsType) + " " + tos(c.indexType) + " " + tos(c.rhsType);
|
||||
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
||||
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, ReduceConstraint>)
|
||||
return "reduce " + tos(c.ty);
|
||||
else if constexpr (std::is_same_v<T, ReducePackConstraint>)
|
||||
|
@ -1561,6 +1561,18 @@ struct TypeChecker2
|
||||
else
|
||||
reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location);
|
||||
}
|
||||
else if (auto mt = get<MetatableType>(exprType))
|
||||
{
|
||||
const TableType* tt = get<TableType>(follow(mt->table));
|
||||
LUAU_ASSERT(tt);
|
||||
if (tt->indexer)
|
||||
testIsSubtype(indexType, tt->indexer->indexType, indexExpr->index->location);
|
||||
else
|
||||
{
|
||||
// TODO: Maybe the metatable has a suitable indexer?
|
||||
reportError(CannotExtendTable{exprType, CannotExtendTable::Indexer, "indexer??"}, indexExpr->location);
|
||||
}
|
||||
}
|
||||
else if (auto cls = get<ClassType>(exprType))
|
||||
{
|
||||
if (cls->indexer)
|
||||
@ -1581,6 +1593,19 @@ struct TypeChecker2
|
||||
reportError(OptionalValueAccess{exprType}, indexExpr->location);
|
||||
}
|
||||
}
|
||||
else if (auto exprIntersection = get<IntersectionType>(exprType))
|
||||
{
|
||||
for (TypeId part : exprIntersection)
|
||||
{
|
||||
(void)part;
|
||||
}
|
||||
}
|
||||
else if (get<NeverType>(exprType) || isErrorSuppressing(indexExpr->location, exprType))
|
||||
{
|
||||
// Nothing
|
||||
}
|
||||
else
|
||||
reportError(NotATable{exprType}, indexExpr->location);
|
||||
}
|
||||
|
||||
void visit(AstExprFunction* fn)
|
||||
@ -2720,6 +2745,8 @@ struct TypeChecker2
|
||||
fetch(builtinTypes->stringType);
|
||||
if (normValid)
|
||||
fetch(norm->threads);
|
||||
if (normValid)
|
||||
fetch(norm->buffers);
|
||||
|
||||
if (normValid)
|
||||
{
|
||||
|
@ -11,12 +11,10 @@
|
||||
#include "Luau/OverloadResolution.h"
|
||||
#include "Luau/Set.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypeFamilyReductionGuesser.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
@ -346,9 +344,8 @@ struct FamilyReducer
|
||||
if (tryGuessing(subject))
|
||||
return;
|
||||
|
||||
TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}};
|
||||
TypeFamilyReductionResult<TypeId> result =
|
||||
tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||
tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||
handleFamilyReduction(subject, result);
|
||||
}
|
||||
}
|
||||
@ -372,9 +369,8 @@ struct FamilyReducer
|
||||
if (tryGuessing(subject))
|
||||
return;
|
||||
|
||||
TypeFamilyQueue queue{NotNull{&queuedTys}, NotNull{&queuedTps}};
|
||||
TypeFamilyReductionResult<TypePackId> result =
|
||||
tfit->family->reducer(subject, NotNull{&queue}, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||
tfit->family->reducer(subject, tfit->typeArguments, tfit->packArguments, NotNull{&ctx});
|
||||
handleFamilyReduction(subject, result);
|
||||
}
|
||||
}
|
||||
@ -449,24 +445,89 @@ FamilyGraphReductionResult reduceFamilies(TypePackId entrypoint, Location locati
|
||||
std::move(collector.cyclicInstance), location, ctx, force);
|
||||
}
|
||||
|
||||
void TypeFamilyQueue::add(TypeId instanceTy)
|
||||
{
|
||||
LUAU_ASSERT(get<TypeFamilyInstanceType>(instanceTy));
|
||||
queuedTys->push_back(instanceTy);
|
||||
}
|
||||
|
||||
void TypeFamilyQueue::add(TypePackId instanceTp)
|
||||
{
|
||||
LUAU_ASSERT(get<TypeFamilyInstanceTypePack>(instanceTp));
|
||||
queuedTps->push_back(instanceTp);
|
||||
}
|
||||
|
||||
bool isPending(TypeId ty, ConstraintSolver* solver)
|
||||
{
|
||||
return is<BlockedType, PendingExpansionType, TypeFamilyInstanceType, LocalType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
template<typename F, typename... Args>
|
||||
static std::optional<TypeFamilyReductionResult<TypeId>> tryDistributeTypeFamilyApp(F f, TypeId instance,
|
||||
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, Args&& ...args)
|
||||
{
|
||||
// op (a | b) (c | d) ~ (op a (c | d)) | (op b (c | d)) ~ (op a c) | (op a d) | (op b c) | (op b d)
|
||||
bool uninhabited = false;
|
||||
std::vector<TypeId> blockedTypes;
|
||||
std::vector<TypeId> results;
|
||||
size_t cartesianProductSize = 1;
|
||||
|
||||
const UnionType* firstUnion = nullptr;
|
||||
size_t unionIndex;
|
||||
|
||||
std::vector<TypeId> arguments = typeParams;
|
||||
for (size_t i = 0; i < arguments.size(); ++i)
|
||||
{
|
||||
const UnionType* ut = get<UnionType>(follow(arguments[i]));
|
||||
if (!ut)
|
||||
continue;
|
||||
|
||||
// We want to find the first union type in the set of arguments to distribute that one and only that one union.
|
||||
// The function `f` we have is recursive, so `arguments[unionIndex]` will be updated in-place for each option in
|
||||
// the union we've found in this context, so that index will no longer be a union type. Any other arguments at
|
||||
// index + 1 or after will instead be distributed, if those are a union, which will be subjected to the same rules.
|
||||
if (!firstUnion && ut)
|
||||
{
|
||||
firstUnion = ut;
|
||||
unionIndex = i;
|
||||
}
|
||||
|
||||
cartesianProductSize *= std::distance(begin(ut), end(ut));
|
||||
|
||||
// TODO: We'd like to report that the type family application is too complex here.
|
||||
if (size_t(DFInt::LuauTypeFamilyApplicationCartesianProductLimit) <= cartesianProductSize)
|
||||
return {{std::nullopt, true, {}, {}}};
|
||||
}
|
||||
|
||||
if (!firstUnion)
|
||||
{
|
||||
// If we couldn't find any union type argument, we're not distributing.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (TypeId option : firstUnion)
|
||||
{
|
||||
arguments[unionIndex] = option;
|
||||
|
||||
TypeFamilyReductionResult<TypeId> result = f(instance, arguments, packParams, ctx, args...);
|
||||
blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
|
||||
uninhabited |= result.uninhabited;
|
||||
|
||||
if (result.uninhabited || !result.result)
|
||||
break;
|
||||
else
|
||||
results.push_back(*result.result);
|
||||
}
|
||||
|
||||
if (uninhabited || !blockedTypes.empty())
|
||||
return {{std::nullopt, uninhabited, blockedTypes, {}}};
|
||||
|
||||
if (!results.empty())
|
||||
{
|
||||
if (results.size() == 1)
|
||||
return {{results[0], false, {}, {}}};
|
||||
|
||||
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
||||
std::move(results),
|
||||
{},
|
||||
});
|
||||
|
||||
return {{resultTy, false, {}, {}}};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
@ -477,14 +538,20 @@ TypeFamilyReductionResult<TypeId> notFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
|
||||
TypeId ty = follow(typeParams.at(0));
|
||||
|
||||
if (ty == instance)
|
||||
return {ctx->builtins->neverType, false, {}, {}};
|
||||
|
||||
if (isPending(ty, ctx->solver))
|
||||
return {std::nullopt, false, {ty}, {}};
|
||||
|
||||
if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
|
||||
// `not` operates on anything and returns a `boolean` always.
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
@ -495,6 +562,9 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
|
||||
TypeId operandTy = follow(typeParams.at(0));
|
||||
|
||||
if (operandTy == instance)
|
||||
return {ctx->builtins->neverType, false, {}, {}};
|
||||
|
||||
// check to see if the operand type is resolved enough, and wait to reduce if not
|
||||
// the use of `typeFromNormal` later necessitates blocking on local types.
|
||||
if (isPending(operandTy, ctx->solver) || get<LocalType>(operandTy))
|
||||
@ -533,6 +603,9 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
if (normTy->hasTopTable() || get<TableType>(normalizedOperand))
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
@ -570,7 +643,7 @@ TypeFamilyReductionResult<TypeId> lenFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
@ -581,6 +654,9 @@ TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
|
||||
TypeId operandTy = follow(typeParams.at(0));
|
||||
|
||||
if (operandTy == instance)
|
||||
return {ctx->builtins->neverType, false, {}, {}};
|
||||
|
||||
// check to see if the operand type is resolved enough, and wait to reduce if not
|
||||
if (isPending(operandTy, ctx->solver))
|
||||
return {std::nullopt, false, {operandTy}, {}};
|
||||
@ -612,6 +688,9 @@ TypeFamilyReductionResult<TypeId> unmFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
if (normTy->isExactlyNumber())
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
if (auto result = tryDistributeTypeFamilyApp(notFamilyFn, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
@ -664,7 +743,7 @@ NotNull<Constraint> TypeFamilyContext::pushConstraint(ConstraintV&& c)
|
||||
return newConstraint;
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -723,67 +802,8 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
|
||||
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
// op (a | b) (c | d) ~ (op a (c | d)) | (op b (c | d)) ~ (op a c) | (op a d) | (op b c) | (op b d)
|
||||
std::vector<TypeId> results;
|
||||
bool uninhabited = false;
|
||||
std::vector<TypeId> blockedTypes;
|
||||
std::vector<TypeId> arguments = typeParams;
|
||||
auto distributeFamilyApp = [&](const UnionType* ut, size_t argumentIndex) {
|
||||
// Returning true here means we completed the loop without any problems.
|
||||
for (TypeId option : ut)
|
||||
{
|
||||
arguments[argumentIndex] = option;
|
||||
|
||||
TypeFamilyReductionResult<TypeId> result = numericBinopFamilyFn(instance, queue, arguments, packParams, ctx, metamethod);
|
||||
blockedTypes.insert(blockedTypes.end(), result.blockedTypes.begin(), result.blockedTypes.end());
|
||||
uninhabited |= result.uninhabited;
|
||||
|
||||
if (result.uninhabited)
|
||||
return false;
|
||||
else if (!result.result)
|
||||
return false;
|
||||
else
|
||||
results.push_back(*result.result);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const UnionType* lhsUnion = get<UnionType>(lhsTy);
|
||||
const UnionType* rhsUnion = get<UnionType>(rhsTy);
|
||||
if (lhsUnion || rhsUnion)
|
||||
{
|
||||
// TODO: We'd like to report that the type family application is too complex here.
|
||||
size_t lhsUnionSize = lhsUnion ? std::distance(begin(lhsUnion), end(lhsUnion)) : 1;
|
||||
size_t rhsUnionSize = rhsUnion ? std::distance(begin(rhsUnion), end(rhsUnion)) : 1;
|
||||
if (size_t(DFInt::LuauTypeFamilyApplicationCartesianProductLimit) <= lhsUnionSize * rhsUnionSize)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
if (lhsUnion && !distributeFamilyApp(lhsUnion, 0))
|
||||
return {std::nullopt, uninhabited, std::move(blockedTypes), {}};
|
||||
|
||||
if (rhsUnion && !distributeFamilyApp(rhsUnion, 1))
|
||||
return {std::nullopt, uninhabited, std::move(blockedTypes), {}};
|
||||
|
||||
if (results.empty())
|
||||
{
|
||||
// If this happens, it means `distributeFamilyApp` has improperly returned `true` even
|
||||
// though there exists no arm of the union that is inhabited or have a reduced type.
|
||||
ctx->ice->ice("`distributeFamilyApp` failed to add any types to the results vector?");
|
||||
}
|
||||
|
||||
if (results.size() == 1)
|
||||
return {results[0], false, {}, {}};
|
||||
|
||||
TypeId resultTy = ctx->arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.unionFamily},
|
||||
std::move(results),
|
||||
{},
|
||||
});
|
||||
|
||||
queue->add(resultTy);
|
||||
return {resultTy, false, {}, {}};
|
||||
}
|
||||
if (auto result = tryDistributeTypeFamilyApp(numericBinopFamilyFn, instance, typeParams, packParams, ctx, metamethod))
|
||||
return *result;
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
@ -826,7 +846,7 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(TypeId instance, NotNull<
|
||||
return {extracted.head.front(), false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -835,10 +855,10 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__add");
|
||||
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__add");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -847,10 +867,10 @@ TypeFamilyReductionResult<TypeId> subFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__sub");
|
||||
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__sub");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -859,10 +879,10 @@ TypeFamilyReductionResult<TypeId> mulFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mul");
|
||||
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mul");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -871,10 +891,10 @@ TypeFamilyReductionResult<TypeId> divFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__div");
|
||||
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__div");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -883,10 +903,10 @@ TypeFamilyReductionResult<TypeId> idivFamilyFn(TypeId instance, NotNull<TypeFami
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__idiv");
|
||||
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__idiv");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -895,10 +915,10 @@ TypeFamilyReductionResult<TypeId> powFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__pow");
|
||||
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__pow");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -907,10 +927,10 @@ TypeFamilyReductionResult<TypeId> modFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return numericBinopFamilyFn(instance, queue, typeParams, packParams, ctx, "__mod");
|
||||
return numericBinopFamilyFn(instance, typeParams, packParams, ctx, "__mod");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -922,6 +942,10 @@ TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, NotNull<TypeFa
|
||||
TypeId lhsTy = follow(typeParams.at(0));
|
||||
TypeId rhsTy = follow(typeParams.at(1));
|
||||
|
||||
// isPending of `lhsTy` or `rhsTy` would return true, even if it cycles. We want a different answer for that.
|
||||
if (lhsTy == instance || rhsTy == instance)
|
||||
return {ctx->builtins->neverType, false, {}, {}};
|
||||
|
||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {lhsTy}, {}};
|
||||
@ -962,6 +986,9 @@ TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, NotNull<TypeFa
|
||||
if ((normLhsTy->isSubtypeOfString() || normLhsTy->isExactlyNumber()) && (normRhsTy->isSubtypeOfString() || normRhsTy->isExactlyNumber()))
|
||||
return {ctx->builtins->stringType, false, {}, {}};
|
||||
|
||||
if (auto result = tryDistributeTypeFamilyApp(concatFamilyFn, instance, typeParams, packParams, ctx))
|
||||
return *result;
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
@ -1011,7 +1038,7 @@ TypeFamilyReductionResult<TypeId> concatFamilyFn(TypeId instance, NotNull<TypeFa
|
||||
return {ctx->builtins->stringType, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -1062,7 +1089,7 @@ TypeFamilyReductionResult<TypeId> andFamilyFn(TypeId instance, NotNull<TypeFamil
|
||||
return {overallResult.result, false, std::move(blockedTypes), {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> orFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> orFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -1113,7 +1140,7 @@ TypeFamilyReductionResult<TypeId> orFamilyFn(TypeId instance, NotNull<TypeFamily
|
||||
return {overallResult.result, false, std::move(blockedTypes), {}};
|
||||
}
|
||||
|
||||
static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
||||
{
|
||||
|
||||
@ -1126,6 +1153,9 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, Not
|
||||
TypeId lhsTy = follow(typeParams.at(0));
|
||||
TypeId rhsTy = follow(typeParams.at(1));
|
||||
|
||||
if (lhsTy == instance || rhsTy == instance)
|
||||
return {ctx->builtins->neverType, false, {}, {}};
|
||||
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {lhsTy}, {}};
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
@ -1207,6 +1237,9 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, Not
|
||||
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
|
||||
if (auto result = tryDistributeTypeFamilyApp(comparisonFamilyFn, instance, typeParams, packParams, ctx, metamethod))
|
||||
return *result;
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
@ -1246,7 +1279,7 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, Not
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -1255,10 +1288,10 @@ TypeFamilyReductionResult<TypeId> ltFamilyFn(TypeId instance, NotNull<TypeFamily
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__lt");
|
||||
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__lt");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -1267,10 +1300,10 @@ TypeFamilyReductionResult<TypeId> leFamilyFn(TypeId instance, NotNull<TypeFamily
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return comparisonFamilyFn(instance, queue, typeParams, packParams, ctx, "__le");
|
||||
return comparisonFamilyFn(instance, typeParams, packParams, ctx, "__le");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> eqFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> eqFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -1407,7 +1440,7 @@ struct FindRefinementBlockers : TypeOnceVisitor
|
||||
};
|
||||
|
||||
|
||||
TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
@ -1480,7 +1513,7 @@ TypeFamilyReductionResult<TypeId> refineFamilyFn(TypeId instance, NotNull<TypeFa
|
||||
return {resultTy, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
@ -1517,7 +1550,7 @@ TypeFamilyReductionResult<TypeId> singletonFamilyFn(TypeId instance, NotNull<Typ
|
||||
return {ctx->builtins->unknownType, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> unionFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> unionFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (!packParams.empty())
|
||||
@ -1578,7 +1611,7 @@ TypeFamilyReductionResult<TypeId> unionFamilyFn(TypeId instance, NotNull<TypeFam
|
||||
}
|
||||
|
||||
|
||||
TypeFamilyReductionResult<TypeId> intersectFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> intersectFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (!packParams.empty())
|
||||
@ -1802,7 +1835,7 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
|
||||
return {ctx->arena->addType(UnionType{singletons}), false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
@ -1814,7 +1847,7 @@ TypeFamilyReductionResult<TypeId> keyofFamilyFn(TypeId instance, NotNull<TypeFam
|
||||
return keyofFamilyImpl(typeParams, packParams, ctx, /* isRaw */ false);
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& typeParams,
|
||||
TypeFamilyReductionResult<TypeId> rawkeyofFamilyFn(TypeId instance, const std::vector<TypeId>& typeParams,
|
||||
const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
|
@ -38,6 +38,59 @@ bool occursCheck(TypeId needle, TypeId haystack)
|
||||
return false;
|
||||
}
|
||||
|
||||
// FIXME: Property is quite large.
|
||||
//
|
||||
// Returning it on the stack like this isn't great. We'd like to just return a
|
||||
// const Property*, but we mint a property of type any if the subject type is
|
||||
// any.
|
||||
std::optional<Property> findTableProperty(NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location)
|
||||
{
|
||||
if (get<AnyType>(ty))
|
||||
return Property::rw(ty);
|
||||
|
||||
if (const TableType* tableType = getTableType(ty))
|
||||
{
|
||||
const auto& it = tableType->props.find(name);
|
||||
if (it != tableType->props.end())
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::optional<TypeId> mtIndex = findMetatableEntry(builtinTypes, errors, ty, "__index", location);
|
||||
int count = 0;
|
||||
while (mtIndex)
|
||||
{
|
||||
TypeId index = follow(*mtIndex);
|
||||
|
||||
if (count >= 100)
|
||||
return std::nullopt;
|
||||
|
||||
++count;
|
||||
|
||||
if (const auto& itt = getTableType(index))
|
||||
{
|
||||
const auto& fit = itt->props.find(name);
|
||||
if (fit != itt->props.end())
|
||||
return fit->second.type();
|
||||
}
|
||||
else if (const auto& itf = get<FunctionType>(index))
|
||||
{
|
||||
std::optional<TypeId> r = first(follow(itf->retTypes));
|
||||
if (!r)
|
||||
return builtinTypes->nilType;
|
||||
else
|
||||
return *r;
|
||||
}
|
||||
else if (get<AnyType>(index))
|
||||
return builtinTypes->anyType;
|
||||
else
|
||||
errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}});
|
||||
|
||||
mtIndex = findMetatableEntry(builtinTypes, errors, *mtIndex, "__index", location);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TypeId> findMetatableEntry(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location)
|
||||
{
|
||||
|
@ -256,12 +256,16 @@ void setupState(lua_State* L)
|
||||
|
||||
void setupArguments(lua_State* L, int argc, char** argv)
|
||||
{
|
||||
lua_checkstack(L, argc);
|
||||
|
||||
for (int i = 0; i < argc; ++i)
|
||||
lua_pushstring(L, argv[i]);
|
||||
}
|
||||
|
||||
std::string runCode(lua_State* L, const std::string& source)
|
||||
{
|
||||
lua_checkstack(L, LUA_MINSTACK);
|
||||
|
||||
std::string bytecode = Luau::compile(source, copts());
|
||||
|
||||
if (luau_load(L, "=stdin", bytecode.data(), bytecode.size(), 0) != 0)
|
||||
@ -432,6 +436,8 @@ static void completeIndexer(lua_State* L, const std::string& editBuffer, const A
|
||||
std::string_view lookup = editBuffer;
|
||||
bool completeOnlyFunctions = false;
|
||||
|
||||
lua_checkstack(L, LUA_MINSTACK);
|
||||
|
||||
// Push the global variable table to begin the search
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
|
||||
|
@ -92,7 +92,7 @@ struct HostIrHooks
|
||||
// Guards should take a VM exit to 'pcpos'
|
||||
HostVectorAccessHandler vectorAccess = nullptr;
|
||||
|
||||
// Handle namecalled performed on a vector value
|
||||
// Handle namecall performed on a vector value
|
||||
// 'sourceReg' (self argument) is guaranteed to be a vector
|
||||
// All other arguments can be of any type
|
||||
// Guards should take a VM exit to 'pcpos'
|
||||
@ -103,6 +103,9 @@ struct CompilationOptions
|
||||
{
|
||||
unsigned int flags = 0;
|
||||
HostIrHooks hooks;
|
||||
|
||||
// null-terminated array of userdata types names that might have custom lowering
|
||||
const char* const* userdataTypes = nullptr;
|
||||
};
|
||||
|
||||
struct CompilationStats
|
||||
@ -163,6 +166,12 @@ void create(lua_State* L, SharedCodeGenContext* codeGenContext);
|
||||
// Enable or disable native execution according to `enabled` argument
|
||||
void setNativeExecutionEnabled(lua_State* L, bool enabled);
|
||||
|
||||
// Given a name, this function must return the index of the type which matches the type array used all CompilationOptions and AssemblyOptions
|
||||
// If the type is unknown, 0xff has to be returned
|
||||
using UserdataRemapperCallback = uint8_t(void* context, const char* name, size_t nameLength);
|
||||
|
||||
void setUserdataRemapper(lua_State* L, void* context, UserdataRemapperCallback cb);
|
||||
|
||||
using ModuleId = std::array<uint8_t, 16>;
|
||||
|
||||
// Builds target function and all inner functions
|
||||
|
@ -31,9 +31,11 @@ void toString(IrToStringContext& ctx, IrOp op);
|
||||
|
||||
void toString(std::string& result, IrConst constant);
|
||||
|
||||
const char* getBytecodeTypeName(uint8_t type);
|
||||
const char* getBytecodeTypeName_DEPRECATED(uint8_t type);
|
||||
const char* getBytecodeTypeName(uint8_t type, const char* const* userdataTypes);
|
||||
|
||||
void toString(std::string& result, const BytecodeTypes& bcTypes);
|
||||
void toString_DEPRECATED(std::string& result, const BytecodeTypes& bcTypes);
|
||||
void toString(std::string& result, const BytecodeTypes& bcTypes, const char* const* userdataTypes);
|
||||
|
||||
void toStringDetailed(
|
||||
IrToStringContext& ctx, const IrBlock& block, uint32_t blockIdx, const IrInst& inst, uint32_t instIdx, IncludeUseInfo includeUseInfo);
|
||||
|
@ -241,6 +241,10 @@ IrValueKind getCmdValueKind(IrCmd cmd);
|
||||
|
||||
bool isGCO(uint8_t tag);
|
||||
|
||||
// Optional bit has to be cleared at call site, otherwise, this will return 'false' for 'userdata?'
|
||||
bool isUserdataBytecodeType(uint8_t ty);
|
||||
bool isCustomUserdataBytecodeType(uint8_t ty);
|
||||
|
||||
// Manually add or remove use of an operand
|
||||
void addUse(IrFunction& function, IrOp op);
|
||||
void removeUse(IrFunction& function, IrOp op);
|
||||
|
@ -14,8 +14,6 @@
|
||||
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
|
||||
LUAU_FASTFLAG(LuauLoadTypeInfo) // Because new VM typeinfo load changes the format used by Codegen, same flag is used
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenTypeInfo, false) // New analysis is flagged separately
|
||||
LUAU_FASTFLAG(LuauTypeInfoLookupImprovement)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenVectorMispredictFix, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenAnalyzeHostVectorOps, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenLoadTypeUpvalCheck, false)
|
||||
|
||||
@ -68,21 +66,13 @@ void loadBytecodeTypeInfo(IrFunction& function)
|
||||
|
||||
Proto* proto = function.proto;
|
||||
|
||||
if (FFlag::LuauTypeInfoLookupImprovement)
|
||||
{
|
||||
if (!proto)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!proto || !proto->typeinfo)
|
||||
return;
|
||||
}
|
||||
if (!proto)
|
||||
return;
|
||||
|
||||
BytecodeTypeInfo& typeInfo = function.bcTypeInfo;
|
||||
|
||||
// If there is no typeinfo, we generate default values for arguments and upvalues
|
||||
if (FFlag::LuauTypeInfoLookupImprovement && !proto->typeinfo)
|
||||
if (!proto->typeinfo)
|
||||
{
|
||||
typeInfo.argumentTypes.resize(proto->numparams, LBC_TYPE_ANY);
|
||||
typeInfo.upvalueTypes.resize(proto->nups, LBC_TYPE_ANY);
|
||||
@ -150,8 +140,6 @@ void loadBytecodeTypeInfo(IrFunction& function)
|
||||
|
||||
static void prepareRegTypeInfoLookups(BytecodeTypeInfo& typeInfo)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauTypeInfoLookupImprovement);
|
||||
|
||||
// Sort by register first, then by end PC
|
||||
std::sort(typeInfo.regTypes.begin(), typeInfo.regTypes.end(), [](const BytecodeRegTypeInfo& a, const BytecodeRegTypeInfo& b) {
|
||||
if (a.reg != b.reg)
|
||||
@ -186,39 +174,26 @@ static BytecodeRegTypeInfo* findRegType(BytecodeTypeInfo& info, uint8_t reg, int
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenTypeInfo);
|
||||
|
||||
if (FFlag::LuauTypeInfoLookupImprovement)
|
||||
{
|
||||
auto b = info.regTypes.begin() + info.regTypeOffsets[reg];
|
||||
auto e = info.regTypes.begin() + info.regTypeOffsets[reg + 1];
|
||||
|
||||
// Doen't have info
|
||||
if (b == e)
|
||||
return nullptr;
|
||||
|
||||
// No info after the last live range
|
||||
if (pc >= (e - 1)->endpc)
|
||||
return nullptr;
|
||||
|
||||
for (auto it = b; it != e; ++it)
|
||||
{
|
||||
CODEGEN_ASSERT(it->reg == reg);
|
||||
|
||||
if (pc >= it->startpc && pc < it->endpc)
|
||||
return &*it;
|
||||
}
|
||||
auto b = info.regTypes.begin() + info.regTypeOffsets[reg];
|
||||
auto e = info.regTypes.begin() + info.regTypeOffsets[reg + 1];
|
||||
|
||||
// Doen't have info
|
||||
if (b == e)
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (BytecodeRegTypeInfo& el : info.regTypes)
|
||||
{
|
||||
if (reg == el.reg && pc >= el.startpc && pc < el.endpc)
|
||||
return ⪙
|
||||
}
|
||||
|
||||
// No info after the last live range
|
||||
if (pc >= (e - 1)->endpc)
|
||||
return nullptr;
|
||||
|
||||
for (auto it = b; it != e; ++it)
|
||||
{
|
||||
CODEGEN_ASSERT(it->reg == reg);
|
||||
|
||||
if (pc >= it->startpc && pc < it->endpc)
|
||||
return &*it;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void refineRegType(BytecodeTypeInfo& info, uint8_t reg, int pc, uint8_t ty)
|
||||
@ -233,7 +208,7 @@ static void refineRegType(BytecodeTypeInfo& info, uint8_t reg, int pc, uint8_t t
|
||||
if (regType->type == LBC_TYPE_ANY)
|
||||
regType->type = ty;
|
||||
}
|
||||
else if (FFlag::LuauTypeInfoLookupImprovement && reg < info.argumentTypes.size())
|
||||
else if (reg < info.argumentTypes.size())
|
||||
{
|
||||
if (info.argumentTypes[reg] == LBC_TYPE_ANY)
|
||||
info.argumentTypes[reg] = ty;
|
||||
@ -627,8 +602,7 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
|
||||
|
||||
BytecodeTypeInfo& bcTypeInfo = function.bcTypeInfo;
|
||||
|
||||
if (FFlag::LuauTypeInfoLookupImprovement)
|
||||
prepareRegTypeInfoLookups(bcTypeInfo);
|
||||
prepareRegTypeInfoLookups(bcTypeInfo);
|
||||
|
||||
// Setup our current knowledge of type tags based on arguments
|
||||
uint8_t regTags[256];
|
||||
@ -786,32 +760,22 @@ void analyzeBytecodeTypes(IrFunction& function, const HostIrHooks& hostHooks)
|
||||
|
||||
regTags[ra] = LBC_TYPE_ANY;
|
||||
|
||||
if (FFlag::LuauCodegenVectorMispredictFix)
|
||||
if (bcType.a == LBC_TYPE_VECTOR)
|
||||
{
|
||||
if (bcType.a == LBC_TYPE_VECTOR)
|
||||
TString* str = gco2ts(function.proto->k[kc].value.gc);
|
||||
const char* field = getstr(str);
|
||||
|
||||
if (str->len == 1)
|
||||
{
|
||||
TString* str = gco2ts(function.proto->k[kc].value.gc);
|
||||
const char* field = getstr(str);
|
||||
// Same handling as LOP_GETTABLEKS block in lvmexecute.cpp - case-insensitive comparison with "X" / "Y" / "Z"
|
||||
char ch = field[0] | ' ';
|
||||
|
||||
if (str->len == 1)
|
||||
{
|
||||
// Same handling as LOP_GETTABLEKS block in lvmexecute.cpp - case-insensitive comparison with "X" / "Y" / "Z"
|
||||
char ch = field[0] | ' ';
|
||||
|
||||
if (ch == 'x' || ch == 'y' || ch == 'z')
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
}
|
||||
|
||||
if (FFlag::LuauCodegenAnalyzeHostVectorOps && regTags[ra] == LBC_TYPE_ANY && hostHooks.vectorAccessBytecodeType)
|
||||
regTags[ra] = hostHooks.vectorAccessBytecodeType(field, str->len);
|
||||
if (ch == 'x' || ch == 'y' || ch == 'z')
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assuming that vector component is being indexed
|
||||
// TODO: check what key is used
|
||||
if (bcType.a == LBC_TYPE_VECTOR)
|
||||
regTags[ra] = LBC_TYPE_NUMBER;
|
||||
|
||||
if (FFlag::LuauCodegenAnalyzeHostVectorOps && regTags[ra] == LBC_TYPE_ANY && hostHooks.vectorAccessBytecodeType)
|
||||
regTags[ra] = hostHooks.vectorAccessBytecodeType(field, str->len);
|
||||
}
|
||||
|
||||
bcType.result = regTags[ra];
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "lapi.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauCodegenTypeInfo)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenIrTypeNames, false)
|
||||
LUAU_FASTFLAG(LuauLoadUserdataInfo)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -22,8 +22,6 @@ namespace CodeGen
|
||||
|
||||
static const LocVar* tryFindLocal(const Proto* proto, int reg, int pcpos)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenIrTypeNames);
|
||||
|
||||
for (int i = 0; i < proto->sizelocvars; i++)
|
||||
{
|
||||
const LocVar& local = proto->locvars[i];
|
||||
@ -37,8 +35,6 @@ static const LocVar* tryFindLocal(const Proto* proto, int reg, int pcpos)
|
||||
|
||||
const char* tryFindLocalName(const Proto* proto, int reg, int pcpos)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenIrTypeNames);
|
||||
|
||||
const LocVar* var = tryFindLocal(proto, reg, pcpos);
|
||||
|
||||
if (var && var->varname)
|
||||
@ -49,8 +45,6 @@ const char* tryFindLocalName(const Proto* proto, int reg, int pcpos)
|
||||
|
||||
const char* tryFindUpvalueName(const Proto* proto, int upval)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenIrTypeNames);
|
||||
|
||||
if (proto->upvalues)
|
||||
{
|
||||
CODEGEN_ASSERT(upval < proto->sizeupvalues);
|
||||
@ -72,22 +66,10 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
|
||||
|
||||
for (int i = 0; i < proto->numparams; i++)
|
||||
{
|
||||
if (FFlag::LuauCodegenIrTypeNames)
|
||||
{
|
||||
if (const char* name = tryFindLocalName(proto, i, 0))
|
||||
build.logAppend("%s%s", i == 0 ? "" : ", ", name);
|
||||
else
|
||||
build.logAppend("%s$arg%d", i == 0 ? "" : ", ", i);
|
||||
}
|
||||
if (const char* name = tryFindLocalName(proto, i, 0))
|
||||
build.logAppend("%s%s", i == 0 ? "" : ", ", name);
|
||||
else
|
||||
{
|
||||
LocVar* var = proto->locvars ? &proto->locvars[proto->sizelocvars - proto->numparams + i] : nullptr;
|
||||
|
||||
if (var && var->varname)
|
||||
build.logAppend("%s%s", i == 0 ? "" : ", ", getstr(var->varname));
|
||||
else
|
||||
build.logAppend("%s$arg%d", i == 0 ? "" : ", ", i);
|
||||
}
|
||||
build.logAppend("%s$arg%d", i == 0 ? "" : ", ", i);
|
||||
}
|
||||
|
||||
if (proto->numparams != 0 && proto->is_vararg)
|
||||
@ -102,9 +84,10 @@ static void logFunctionHeader(AssemblyBuilder& build, Proto* proto)
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder>
|
||||
static void logFunctionTypes(AssemblyBuilder& build, const IrFunction& function)
|
||||
static void logFunctionTypes_DEPRECATED(AssemblyBuilder& build, const IrFunction& function)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenTypeInfo);
|
||||
CODEGEN_ASSERT(!FFlag::LuauLoadUserdataInfo);
|
||||
|
||||
const BytecodeTypeInfo& typeInfo = function.bcTypeInfo;
|
||||
|
||||
@ -112,20 +95,12 @@ static void logFunctionTypes(AssemblyBuilder& build, const IrFunction& function)
|
||||
{
|
||||
uint8_t ty = typeInfo.argumentTypes[i];
|
||||
|
||||
if (FFlag::LuauCodegenIrTypeNames)
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
{
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
{
|
||||
if (const char* name = tryFindLocalName(function.proto, int(i), 0))
|
||||
build.logAppend("; R%d: %s [argument '%s']\n", int(i), getBytecodeTypeName(ty), name);
|
||||
else
|
||||
build.logAppend("; R%d: %s [argument]\n", int(i), getBytecodeTypeName(ty));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
build.logAppend("; R%d: %s [argument]\n", int(i), getBytecodeTypeName(ty));
|
||||
if (const char* name = tryFindLocalName(function.proto, int(i), 0))
|
||||
build.logAppend("; R%d: %s [argument '%s']\n", int(i), getBytecodeTypeName_DEPRECATED(ty), name);
|
||||
else
|
||||
build.logAppend("; R%d: %s [argument]\n", int(i), getBytecodeTypeName_DEPRECATED(ty));
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,38 +108,76 @@ static void logFunctionTypes(AssemblyBuilder& build, const IrFunction& function)
|
||||
{
|
||||
uint8_t ty = typeInfo.upvalueTypes[i];
|
||||
|
||||
if (FFlag::LuauCodegenIrTypeNames)
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
{
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
{
|
||||
if (const char* name = tryFindUpvalueName(function.proto, int(i)))
|
||||
build.logAppend("; U%d: %s ['%s']\n", int(i), getBytecodeTypeName(ty), name);
|
||||
else
|
||||
build.logAppend("; U%d: %s\n", int(i), getBytecodeTypeName(ty));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
build.logAppend("; U%d: %s\n", int(i), getBytecodeTypeName(ty));
|
||||
if (const char* name = tryFindUpvalueName(function.proto, int(i)))
|
||||
build.logAppend("; U%d: %s ['%s']\n", int(i), getBytecodeTypeName_DEPRECATED(ty), name);
|
||||
else
|
||||
build.logAppend("; U%d: %s\n", int(i), getBytecodeTypeName_DEPRECATED(ty));
|
||||
}
|
||||
}
|
||||
|
||||
for (const BytecodeRegTypeInfo& el : typeInfo.regTypes)
|
||||
{
|
||||
if (FFlag::LuauCodegenIrTypeNames)
|
||||
{
|
||||
// Using last active position as the PC because 'startpc' for type info is before local is initialized
|
||||
if (const char* name = tryFindLocalName(function.proto, el.reg, el.endpc - 1))
|
||||
build.logAppend("; R%d: %s from %d to %d [local '%s']\n", el.reg, getBytecodeTypeName(el.type), el.startpc, el.endpc, name);
|
||||
else
|
||||
build.logAppend("; R%d: %s from %d to %d\n", el.reg, getBytecodeTypeName(el.type), el.startpc, el.endpc);
|
||||
}
|
||||
// Using last active position as the PC because 'startpc' for type info is before local is initialized
|
||||
if (const char* name = tryFindLocalName(function.proto, el.reg, el.endpc - 1))
|
||||
build.logAppend("; R%d: %s from %d to %d [local '%s']\n", el.reg, getBytecodeTypeName_DEPRECATED(el.type), el.startpc, el.endpc, name);
|
||||
else
|
||||
build.logAppend("; R%d: %s from %d to %d\n", el.reg, getBytecodeTypeName_DEPRECATED(el.type), el.startpc, el.endpc);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder>
|
||||
static void logFunctionTypes(AssemblyBuilder& build, const IrFunction& function, const char* const* userdataTypes)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauCodegenTypeInfo);
|
||||
CODEGEN_ASSERT(FFlag::LuauLoadUserdataInfo);
|
||||
|
||||
const BytecodeTypeInfo& typeInfo = function.bcTypeInfo;
|
||||
|
||||
for (size_t i = 0; i < typeInfo.argumentTypes.size(); i++)
|
||||
{
|
||||
uint8_t ty = typeInfo.argumentTypes[i];
|
||||
|
||||
const char* type = getBytecodeTypeName(ty, userdataTypes);
|
||||
const char* optional = (ty & LBC_TYPE_OPTIONAL_BIT) != 0 ? "?" : "";
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
{
|
||||
build.logAppend("; R%d: %s from %d to %d\n", el.reg, getBytecodeTypeName(el.type), el.startpc, el.endpc);
|
||||
if (const char* name = tryFindLocalName(function.proto, int(i), 0))
|
||||
build.logAppend("; R%d: %s%s [argument '%s']\n", int(i), type, optional, name);
|
||||
else
|
||||
build.logAppend("; R%d: %s%s [argument]\n", int(i), type, optional);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < typeInfo.upvalueTypes.size(); i++)
|
||||
{
|
||||
uint8_t ty = typeInfo.upvalueTypes[i];
|
||||
|
||||
const char* type = getBytecodeTypeName(ty, userdataTypes);
|
||||
const char* optional = (ty & LBC_TYPE_OPTIONAL_BIT) != 0 ? "?" : "";
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
{
|
||||
if (const char* name = tryFindUpvalueName(function.proto, int(i)))
|
||||
build.logAppend("; U%d: %s%s ['%s']\n", int(i), type, optional, name);
|
||||
else
|
||||
build.logAppend("; U%d: %s%s\n", int(i), type, optional);
|
||||
}
|
||||
}
|
||||
|
||||
for (const BytecodeRegTypeInfo& el : typeInfo.regTypes)
|
||||
{
|
||||
const char* type = getBytecodeTypeName(el.type, userdataTypes);
|
||||
const char* optional = (el.type & LBC_TYPE_OPTIONAL_BIT) != 0 ? "?" : "";
|
||||
|
||||
// Using last active position as the PC because 'startpc' for type info is before local is initialized
|
||||
if (const char* name = tryFindLocalName(function.proto, el.reg, el.endpc - 1))
|
||||
build.logAppend("; R%d: %s%s from %d to %d [local '%s']\n", el.reg, type, optional, el.startpc, el.endpc, name);
|
||||
else
|
||||
build.logAppend("; R%d: %s%s from %d to %d\n", el.reg, type, optional, el.startpc, el.endpc);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned getInstructionCount(const Instruction* insns, const unsigned size)
|
||||
@ -224,7 +237,12 @@ static std::string getAssemblyImpl(AssemblyBuilder& build, const TValue* func, A
|
||||
logFunctionHeader(build, p);
|
||||
|
||||
if (FFlag::LuauCodegenTypeInfo && options.includeIrTypes)
|
||||
logFunctionTypes(build, ir.function);
|
||||
{
|
||||
if (FFlag::LuauLoadUserdataInfo)
|
||||
logFunctionTypes(build, ir.function, options.compilationOptions.userdataTypes);
|
||||
else
|
||||
logFunctionTypes_DEPRECATED(build, ir.function);
|
||||
}
|
||||
|
||||
CodeGenCompilationResult result = CodeGenCompilationResult::Success;
|
||||
|
||||
|
@ -612,5 +612,29 @@ void setNativeExecutionEnabled(lua_State* L, bool enabled)
|
||||
L->global->ecb.enter = enabled ? onEnter : onEnterDisabled;
|
||||
}
|
||||
|
||||
static uint8_t userdataRemapperWrap(lua_State* L, const char* str, size_t len)
|
||||
{
|
||||
if (BaseCodeGenContext* codegenCtx = getCodeGenContext(L))
|
||||
{
|
||||
uint8_t index = codegenCtx->userdataRemapper(codegenCtx->userdataRemappingContext, str, len);
|
||||
|
||||
if (index < (LBC_TYPE_TAGGED_USERDATA_END - LBC_TYPE_TAGGED_USERDATA_BASE))
|
||||
return LBC_TYPE_TAGGED_USERDATA_BASE + index;
|
||||
}
|
||||
|
||||
return LBC_TYPE_USERDATA;
|
||||
}
|
||||
|
||||
void setUserdataRemapper(lua_State* L, void* context, UserdataRemapperCallback cb)
|
||||
{
|
||||
if (BaseCodeGenContext* codegenCtx = getCodeGenContext(L))
|
||||
{
|
||||
codegenCtx->userdataRemappingContext = context;
|
||||
codegenCtx->userdataRemapper = cb;
|
||||
|
||||
L->global->ecb.gettypemapping = cb ? userdataRemapperWrap : nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -50,6 +50,9 @@ public:
|
||||
uint8_t* gateData = nullptr;
|
||||
size_t gateDataSize = 0;
|
||||
|
||||
void* userdataRemappingContext = nullptr;
|
||||
UserdataRemapperCallback* userdataRemapper = nullptr;
|
||||
|
||||
NativeContext context;
|
||||
};
|
||||
|
||||
|
@ -28,6 +28,7 @@ LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_FASTINT(CodegenHeuristicsBlockLimit)
|
||||
LUAU_FASTINT(CodegenHeuristicsBlockInstructionLimit)
|
||||
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
|
||||
LUAU_FASTFLAG(LuauLoadUserdataInfo)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -149,7 +150,11 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
|
||||
if (bcTypes.result != LBC_TYPE_ANY || bcTypes.a != LBC_TYPE_ANY || bcTypes.b != LBC_TYPE_ANY || bcTypes.c != LBC_TYPE_ANY)
|
||||
{
|
||||
toString(ctx.result, bcTypes);
|
||||
if (FFlag::LuauLoadUserdataInfo)
|
||||
toString(ctx.result, bcTypes, options.compilationOptions.userdataTypes);
|
||||
else
|
||||
toString_DEPRECATED(ctx.result, bcTypes);
|
||||
|
||||
build.logAppend("\n");
|
||||
}
|
||||
}
|
||||
|
@ -14,8 +14,8 @@
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauLoadTypeInfo) // Because new VM typeinfo load changes the format used by Codegen, same flag is used
|
||||
LUAU_FASTFLAG(LuauTypeInfoLookupImprovement)
|
||||
LUAU_FASTFLAG(LuauCodegenAnalyzeHostVectorOps)
|
||||
LUAU_FASTFLAG(LuauLoadUserdataInfo)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -119,20 +119,13 @@ static bool hasTypedParameters(const BytecodeTypeInfo& typeInfo)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauLoadTypeInfo);
|
||||
|
||||
if (FFlag::LuauTypeInfoLookupImprovement)
|
||||
for (auto el : typeInfo.argumentTypes)
|
||||
{
|
||||
for (auto el : typeInfo.argumentTypes)
|
||||
{
|
||||
if (el != LBC_TYPE_ANY)
|
||||
return true;
|
||||
}
|
||||
if (el != LBC_TYPE_ANY)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return !typeInfo.argumentTypes.empty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void buildArgumentTypeChecks(IrBuilder& build)
|
||||
@ -197,6 +190,19 @@ static void buildArgumentTypeChecks(IrBuilder& build)
|
||||
case LBC_TYPE_BUFFER:
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TBUFFER), build.vmExit(kVmExitEntryGuardPc));
|
||||
break;
|
||||
default:
|
||||
if (FFlag::LuauLoadUserdataInfo)
|
||||
{
|
||||
if (tag >= LBC_TYPE_TAGGED_USERDATA_BASE && tag < LBC_TYPE_TAGGED_USERDATA_END)
|
||||
{
|
||||
build.inst(IrCmd::CHECK_TAG, load, build.constTag(LUA_TUSERDATA), build.vmExit(kVmExitEntryGuardPc));
|
||||
}
|
||||
else
|
||||
{
|
||||
CODEGEN_ASSERT(!"unknown argument type tag");
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (optional)
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauLoadUserdataInfo)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -480,8 +482,10 @@ void toString(std::string& result, IrConst constant)
|
||||
}
|
||||
}
|
||||
|
||||
const char* getBytecodeTypeName(uint8_t type)
|
||||
const char* getBytecodeTypeName_DEPRECATED(uint8_t type)
|
||||
{
|
||||
CODEGEN_ASSERT(!FFlag::LuauLoadUserdataInfo);
|
||||
|
||||
switch (type & ~LBC_TYPE_OPTIONAL_BIT)
|
||||
{
|
||||
case LBC_TYPE_NIL:
|
||||
@ -512,13 +516,78 @@ const char* getBytecodeTypeName(uint8_t type)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void toString(std::string& result, const BytecodeTypes& bcTypes)
|
||||
const char* getBytecodeTypeName(uint8_t type, const char* const* userdataTypes)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauLoadUserdataInfo);
|
||||
|
||||
// Optional bit should be handled externally
|
||||
type = type & ~LBC_TYPE_OPTIONAL_BIT;
|
||||
|
||||
if (type >= LBC_TYPE_TAGGED_USERDATA_BASE && type < LBC_TYPE_TAGGED_USERDATA_END)
|
||||
{
|
||||
if (userdataTypes)
|
||||
return userdataTypes[type - LBC_TYPE_TAGGED_USERDATA_BASE];
|
||||
|
||||
return "userdata";
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case LBC_TYPE_NIL:
|
||||
return "nil";
|
||||
case LBC_TYPE_BOOLEAN:
|
||||
return "boolean";
|
||||
case LBC_TYPE_NUMBER:
|
||||
return "number";
|
||||
case LBC_TYPE_STRING:
|
||||
return "string";
|
||||
case LBC_TYPE_TABLE:
|
||||
return "table";
|
||||
case LBC_TYPE_FUNCTION:
|
||||
return "function";
|
||||
case LBC_TYPE_THREAD:
|
||||
return "thread";
|
||||
case LBC_TYPE_USERDATA:
|
||||
return "userdata";
|
||||
case LBC_TYPE_VECTOR:
|
||||
return "vector";
|
||||
case LBC_TYPE_BUFFER:
|
||||
return "buffer";
|
||||
case LBC_TYPE_ANY:
|
||||
return "any";
|
||||
}
|
||||
|
||||
CODEGEN_ASSERT(!"Unhandled type in getBytecodeTypeName");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void toString_DEPRECATED(std::string& result, const BytecodeTypes& bcTypes)
|
||||
{
|
||||
CODEGEN_ASSERT(!FFlag::LuauLoadUserdataInfo);
|
||||
|
||||
if (bcTypes.c != LBC_TYPE_ANY)
|
||||
append(result, "%s <- %s, %s, %s", getBytecodeTypeName(bcTypes.result), getBytecodeTypeName(bcTypes.a), getBytecodeTypeName(bcTypes.b),
|
||||
getBytecodeTypeName(bcTypes.c));
|
||||
append(result, "%s <- %s, %s, %s", getBytecodeTypeName_DEPRECATED(bcTypes.result), getBytecodeTypeName_DEPRECATED(bcTypes.a),
|
||||
getBytecodeTypeName_DEPRECATED(bcTypes.b), getBytecodeTypeName_DEPRECATED(bcTypes.c));
|
||||
else
|
||||
append(result, "%s <- %s, %s", getBytecodeTypeName(bcTypes.result), getBytecodeTypeName(bcTypes.a), getBytecodeTypeName(bcTypes.b));
|
||||
append(result, "%s <- %s, %s", getBytecodeTypeName_DEPRECATED(bcTypes.result), getBytecodeTypeName_DEPRECATED(bcTypes.a),
|
||||
getBytecodeTypeName_DEPRECATED(bcTypes.b));
|
||||
}
|
||||
|
||||
void toString(std::string& result, const BytecodeTypes& bcTypes, const char* const* userdataTypes)
|
||||
{
|
||||
CODEGEN_ASSERT(FFlag::LuauLoadUserdataInfo);
|
||||
|
||||
append(result, "%s%s", getBytecodeTypeName(bcTypes.result, userdataTypes), (bcTypes.result & LBC_TYPE_OPTIONAL_BIT) != 0 ? "?" : "");
|
||||
append(result, " <- ");
|
||||
append(result, "%s%s", getBytecodeTypeName(bcTypes.a, userdataTypes), (bcTypes.a & LBC_TYPE_OPTIONAL_BIT) != 0 ? "?" : "");
|
||||
append(result, ", ");
|
||||
append(result, "%s%s", getBytecodeTypeName(bcTypes.b, userdataTypes), (bcTypes.b & LBC_TYPE_OPTIONAL_BIT) != 0 ? "?" : "");
|
||||
|
||||
if (bcTypes.c != LBC_TYPE_ANY)
|
||||
{
|
||||
append(result, ", ");
|
||||
append(result, "%s%s", getBytecodeTypeName(bcTypes.c, userdataTypes), (bcTypes.c & LBC_TYPE_OPTIONAL_BIT) != 0 ? "?" : "");
|
||||
}
|
||||
}
|
||||
|
||||
static void appendBlockSet(IrToStringContext& ctx, BlockIteratorWrapper blocks)
|
||||
|
@ -14,7 +14,6 @@
|
||||
#include "ltm.h"
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenDirectUserdataFlow, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenFixVectorFields, false)
|
||||
LUAU_FASTFLAG(LuauCodegenAnalyzeHostVectorOps)
|
||||
|
||||
namespace Luau
|
||||
@ -1200,19 +1199,19 @@ void translateInstGetTableKS(IrBuilder& build, const Instruction* pc, int pcpos)
|
||||
TString* str = gco2ts(build.function.proto->k[aux].value.gc);
|
||||
const char* field = getstr(str);
|
||||
|
||||
if ((!FFlag::LuauCodegenFixVectorFields || str->len == 1) && (*field == 'X' || *field == 'x'))
|
||||
if (str->len == 1 && (*field == 'X' || *field == 'x'))
|
||||
{
|
||||
IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(0));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
}
|
||||
else if ((!FFlag::LuauCodegenFixVectorFields || str->len == 1) && (*field == 'Y' || *field == 'y'))
|
||||
else if (str->len == 1 && (*field == 'Y' || *field == 'y'))
|
||||
{
|
||||
IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(4));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||
}
|
||||
else if ((!FFlag::LuauCodegenFixVectorFields || str->len == 1) && (*field == 'Z' || *field == 'z'))
|
||||
else if (str->len == 1 && (*field == 'Z' || *field == 'z'))
|
||||
{
|
||||
IrOp value = build.inst(IrCmd::LOAD_FLOAT, build.vmReg(rb), build.constInt(8));
|
||||
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), value);
|
||||
|
@ -252,6 +252,16 @@ bool isGCO(uint8_t tag)
|
||||
return tag >= LUA_TSTRING;
|
||||
}
|
||||
|
||||
bool isUserdataBytecodeType(uint8_t ty)
|
||||
{
|
||||
return ty == LBC_TYPE_USERDATA || isCustomUserdataBytecodeType(ty);
|
||||
}
|
||||
|
||||
bool isCustomUserdataBytecodeType(uint8_t ty)
|
||||
{
|
||||
return ty >= LBC_TYPE_TAGGED_USERDATA_BASE && ty < LBC_TYPE_TAGGED_USERDATA_END;
|
||||
}
|
||||
|
||||
void kill(IrFunction& function, IrInst& inst)
|
||||
{
|
||||
CODEGEN_ASSERT(inst.useCount == 0);
|
||||
|
@ -18,7 +18,7 @@ LUAU_FASTINTVARIABLE(LuauCodeGenMinLinearBlockPath, 3)
|
||||
LUAU_FASTINTVARIABLE(LuauCodeGenReuseSlotLimit, 64)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauAbortingChecks, false)
|
||||
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenLoadPropCheckRegLinkInTv, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCodegenFixSplitStoreConstMismatch, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -739,7 +739,7 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
|
||||
// If we know the tag, we can try extracting the value from a register used by LOAD_TVALUE
|
||||
// To do that, we have to ensure that the register link of the source value is still valid
|
||||
if (tag != 0xff && (!FFlag::LuauCodegenLoadPropCheckRegLinkInTv || state.tryGetRegLink(inst.b) != nullptr))
|
||||
if (tag != 0xff && state.tryGetRegLink(inst.b) != nullptr)
|
||||
{
|
||||
if (IrInst* arg = function.asInstOp(inst.b); arg && arg->cmd == IrCmd::LOAD_TVALUE && arg->a.kind == IrOpKind::VmReg)
|
||||
{
|
||||
@ -750,18 +750,48 @@ static void constPropInInst(ConstPropState& state, IrBuilder& build, IrFunction&
|
||||
}
|
||||
}
|
||||
|
||||
// If we have constant tag and value, replace TValue store with tag/value pair store
|
||||
if (tag != 0xff && value.kind != IrOpKind::None && (tag == LUA_TBOOLEAN || tag == LUA_TNUMBER || isGCO(tag)))
|
||||
if (FFlag::LuauCodegenFixSplitStoreConstMismatch)
|
||||
{
|
||||
replace(function, block, index, {IrCmd::STORE_SPLIT_TVALUE, inst.a, build.constTag(tag), value, inst.c});
|
||||
// If we have constant tag and value, replace TValue store with tag/value pair store
|
||||
bool canSplitTvalueStore = false;
|
||||
|
||||
// Value can be propagated to future loads of the same register
|
||||
if (inst.a.kind == IrOpKind::VmReg && activeLoadValue != kInvalidInstIdx)
|
||||
state.valueMap[state.versionedVmRegLoad(activeLoadCmd, inst.a)] = activeLoadValue;
|
||||
if (tag == LUA_TBOOLEAN &&
|
||||
(value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Int)))
|
||||
canSplitTvalueStore = true;
|
||||
else if (tag == LUA_TNUMBER &&
|
||||
(value.kind == IrOpKind::Inst || (value.kind == IrOpKind::Constant && function.constOp(value).kind == IrConstKind::Double)))
|
||||
canSplitTvalueStore = true;
|
||||
else if (tag != 0xff && isGCO(tag) && value.kind == IrOpKind::Inst)
|
||||
canSplitTvalueStore = true;
|
||||
|
||||
if (canSplitTvalueStore)
|
||||
{
|
||||
replace(function, block, index, {IrCmd::STORE_SPLIT_TVALUE, inst.a, build.constTag(tag), value, inst.c});
|
||||
|
||||
// Value can be propagated to future loads of the same register
|
||||
if (inst.a.kind == IrOpKind::VmReg && activeLoadValue != kInvalidInstIdx)
|
||||
state.valueMap[state.versionedVmRegLoad(activeLoadCmd, inst.a)] = activeLoadValue;
|
||||
}
|
||||
else if (inst.a.kind == IrOpKind::VmReg)
|
||||
{
|
||||
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_TVALUE);
|
||||
}
|
||||
}
|
||||
else if (inst.a.kind == IrOpKind::VmReg)
|
||||
else
|
||||
{
|
||||
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_TVALUE);
|
||||
// If we have constant tag and value, replace TValue store with tag/value pair store
|
||||
if (tag != 0xff && value.kind != IrOpKind::None && (tag == LUA_TBOOLEAN || tag == LUA_TNUMBER || isGCO(tag)))
|
||||
{
|
||||
replace(function, block, index, {IrCmd::STORE_SPLIT_TVALUE, inst.a, build.constTag(tag), value, inst.c});
|
||||
|
||||
// Value can be propagated to future loads of the same register
|
||||
if (inst.a.kind == IrOpKind::VmReg && activeLoadValue != kInvalidInstIdx)
|
||||
state.valueMap[state.versionedVmRegLoad(activeLoadCmd, inst.a)] = activeLoadValue;
|
||||
}
|
||||
else if (inst.a.kind == IrOpKind::VmReg)
|
||||
{
|
||||
state.forwardVmRegStoreToLoad(inst, IrCmd::LOAD_TVALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -438,7 +438,9 @@ enum LuauBytecodeTag
|
||||
LBC_VERSION_TARGET = 5,
|
||||
// Type encoding version
|
||||
LBC_TYPE_VERSION_DEPRECATED = 1,
|
||||
LBC_TYPE_VERSION = 2,
|
||||
LBC_TYPE_VERSION_MIN = 1,
|
||||
LBC_TYPE_VERSION_MAX = 3,
|
||||
LBC_TYPE_VERSION_TARGET = 3,
|
||||
// Types of constant table entries
|
||||
LBC_CONSTANT_NIL = 0,
|
||||
LBC_CONSTANT_BOOLEAN,
|
||||
@ -465,6 +467,10 @@ enum LuauBytecodeType
|
||||
LBC_TYPE_BUFFER,
|
||||
|
||||
LBC_TYPE_ANY = 15,
|
||||
|
||||
LBC_TYPE_TAGGED_USERDATA_BASE = 64,
|
||||
LBC_TYPE_TAGGED_USERDATA_END = 64 + 32,
|
||||
|
||||
LBC_TYPE_OPTIONAL_BIT = 1 << 7,
|
||||
|
||||
LBC_TYPE_INVALID = 256,
|
||||
|
@ -79,6 +79,9 @@ public:
|
||||
void pushLocalTypeInfo(LuauBytecodeType type, uint8_t reg, uint32_t startpc, uint32_t endpc);
|
||||
void pushUpvalTypeInfo(LuauBytecodeType type);
|
||||
|
||||
uint32_t addUserdataType(const char* name);
|
||||
void useUserdataType(uint32_t index);
|
||||
|
||||
void setDebugFunctionName(StringRef name);
|
||||
void setDebugFunctionLineDefined(int line);
|
||||
void setDebugLine(int line);
|
||||
@ -229,6 +232,13 @@ private:
|
||||
LuauBytecodeType type;
|
||||
};
|
||||
|
||||
struct UserdataType
|
||||
{
|
||||
std::string name;
|
||||
uint32_t nameRef = 0;
|
||||
bool used = false;
|
||||
};
|
||||
|
||||
struct Jump
|
||||
{
|
||||
uint32_t source;
|
||||
@ -277,6 +287,8 @@ private:
|
||||
std::vector<TypedLocal> typedLocals;
|
||||
std::vector<TypedUpval> typedUpvals;
|
||||
|
||||
std::vector<UserdataType> userdataTypes;
|
||||
|
||||
DenseHashMap<StringRef, unsigned int, StringRefHash> stringTable;
|
||||
std::vector<StringRef> debugStrings;
|
||||
|
||||
@ -308,6 +320,8 @@ private:
|
||||
|
||||
int32_t addConstant(const ConstantKey& key, const Constant& value);
|
||||
unsigned int addStringTableEntry(StringRef value);
|
||||
|
||||
const char* tryGetUserdataTypeName(LuauBytecodeType type) const;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -46,6 +46,9 @@ struct CompileOptions
|
||||
|
||||
// null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these
|
||||
const char* const* mutableGlobals = nullptr;
|
||||
|
||||
// null-terminated array of userdata types that will be included in the type information
|
||||
const char* const* userdataTypes = nullptr;
|
||||
};
|
||||
|
||||
class CompileError : public std::exception
|
||||
|
@ -42,6 +42,9 @@ struct lua_CompileOptions
|
||||
|
||||
// null-terminated array of globals that are mutable; disables the import optimization for fields accessed through these
|
||||
const char* const* mutableGlobals;
|
||||
|
||||
// null-terminated array of userdata types that will be included in the type information
|
||||
const char* const* userdataTypes = nullptr;
|
||||
};
|
||||
|
||||
// compile source to bytecode; when source compilation fails, the resulting bytecode contains the encoded error. use free() to destroy
|
||||
|
@ -7,9 +7,8 @@
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileNoJumpLineRetarget, false)
|
||||
LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileTypeInfo, false)
|
||||
LUAU_FASTFLAG(LuauCompileUserdataInfo)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -335,6 +334,18 @@ unsigned int BytecodeBuilder::addStringTableEntry(StringRef value)
|
||||
return index;
|
||||
}
|
||||
|
||||
const char* BytecodeBuilder::tryGetUserdataTypeName(LuauBytecodeType type) const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileUserdataInfo);
|
||||
|
||||
unsigned index = unsigned((type & ~LBC_TYPE_OPTIONAL_BIT) - LBC_TYPE_TAGGED_USERDATA_BASE);
|
||||
|
||||
if (index < userdataTypes.size())
|
||||
return userdataTypes[index].name.c_str();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int32_t BytecodeBuilder::addConstantNil()
|
||||
{
|
||||
Constant c = {Constant::Type_Nil};
|
||||
@ -567,6 +578,25 @@ void BytecodeBuilder::pushUpvalTypeInfo(LuauBytecodeType type)
|
||||
typedUpvals.push_back(upval);
|
||||
}
|
||||
|
||||
uint32_t BytecodeBuilder::addUserdataType(const char* name)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileUserdataInfo);
|
||||
|
||||
UserdataType ty;
|
||||
|
||||
ty.name = name;
|
||||
|
||||
userdataTypes.push_back(std::move(ty));
|
||||
return uint32_t(userdataTypes.size() - 1);
|
||||
}
|
||||
|
||||
void BytecodeBuilder::useUserdataType(uint32_t index)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileUserdataInfo);
|
||||
|
||||
userdataTypes[index].used = true;
|
||||
}
|
||||
|
||||
void BytecodeBuilder::setDebugFunctionName(StringRef name)
|
||||
{
|
||||
unsigned int index = addStringTableEntry(name);
|
||||
@ -648,6 +678,15 @@ void BytecodeBuilder::finalize()
|
||||
{
|
||||
LUAU_ASSERT(bytecode.empty());
|
||||
|
||||
if (FFlag::LuauCompileUserdataInfo)
|
||||
{
|
||||
for (auto& ty : userdataTypes)
|
||||
{
|
||||
if (ty.used)
|
||||
ty.nameRef = addStringTableEntry(StringRef({ty.name.c_str(), ty.name.length()}));
|
||||
}
|
||||
}
|
||||
|
||||
// preallocate space for bytecode blob
|
||||
size_t capacity = 16;
|
||||
|
||||
@ -666,10 +705,24 @@ void BytecodeBuilder::finalize()
|
||||
bytecode = char(version);
|
||||
|
||||
uint8_t typesversion = getTypeEncodingVersion();
|
||||
LUAU_ASSERT(typesversion >= LBC_TYPE_VERSION_MIN && typesversion <= LBC_TYPE_VERSION_MAX);
|
||||
writeByte(bytecode, typesversion);
|
||||
|
||||
writeStringTable(bytecode);
|
||||
|
||||
if (FFlag::LuauCompileTypeInfo && FFlag::LuauCompileUserdataInfo)
|
||||
{
|
||||
// Write the mapping between used type name indices and their name
|
||||
for (uint32_t i = 0; i < uint32_t(userdataTypes.size()); i++)
|
||||
{
|
||||
writeByte(bytecode, i + 1);
|
||||
writeVarInt(bytecode, userdataTypes[i].nameRef);
|
||||
}
|
||||
|
||||
// 0 marks the end of the mapping
|
||||
writeByte(bytecode, 0);
|
||||
}
|
||||
|
||||
writeVarInt(bytecode, uint32_t(functions.size()));
|
||||
|
||||
for (const Function& func : functions)
|
||||
@ -1036,11 +1089,6 @@ void BytecodeBuilder::foldJumps()
|
||||
if (LUAU_INSN_OP(jumpInsn) == LOP_JUMP && LUAU_INSN_OP(targetInsn) == LOP_RETURN)
|
||||
{
|
||||
insns[jumpLabel] = targetInsn;
|
||||
|
||||
if (!FFlag::LuauCompileNoJumpLineRetarget)
|
||||
{
|
||||
lines[jumpLabel] = lines[targetLabel];
|
||||
}
|
||||
}
|
||||
else if (int16_t(offset) == offset)
|
||||
{
|
||||
@ -1198,7 +1246,10 @@ uint8_t BytecodeBuilder::getVersion()
|
||||
|
||||
uint8_t BytecodeBuilder::getTypeEncodingVersion()
|
||||
{
|
||||
return FFlag::LuauCompileTypeInfo ? LBC_TYPE_VERSION : LBC_TYPE_VERSION_DEPRECATED;
|
||||
if (FFlag::LuauCompileTypeInfo && FFlag::LuauCompileUserdataInfo)
|
||||
return LBC_TYPE_VERSION_TARGET;
|
||||
|
||||
return FFlag::LuauCompileTypeInfo ? 2 : LBC_TYPE_VERSION_DEPRECATED;
|
||||
}
|
||||
|
||||
#ifdef LUAU_ASSERTENABLED
|
||||
@ -2275,7 +2326,7 @@ std::string BytecodeBuilder::dumpCurrentFunction(std::vector<int>& dumpinstoffs)
|
||||
{
|
||||
const DebugLocal& l = debugLocals[i];
|
||||
|
||||
if (FFlag::LuauCompileRepeatUntilSkippedLocals && l.startpc == l.endpc)
|
||||
if (l.startpc == l.endpc)
|
||||
{
|
||||
LUAU_ASSERT(l.startpc < lines.size());
|
||||
|
||||
@ -2301,35 +2352,74 @@ std::string BytecodeBuilder::dumpCurrentFunction(std::vector<int>& dumpinstoffs)
|
||||
{
|
||||
const std::string& typeinfo = functions.back().typeinfo;
|
||||
|
||||
// Arguments start from third byte in function typeinfo string
|
||||
for (uint8_t i = 2; i < typeinfo.size(); ++i)
|
||||
if (FFlag::LuauCompileUserdataInfo)
|
||||
{
|
||||
uint8_t et = typeinfo[i];
|
||||
// Arguments start from third byte in function typeinfo string
|
||||
for (uint8_t i = 2; i < typeinfo.size(); ++i)
|
||||
{
|
||||
uint8_t et = typeinfo[i];
|
||||
|
||||
const char* base = getBaseTypeString(et);
|
||||
const char* optional = (et & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
const char* userdata = tryGetUserdataTypeName(LuauBytecodeType(et));
|
||||
const char* name = userdata ? userdata : getBaseTypeString(et);
|
||||
const char* optional = (et & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
|
||||
formatAppend(result, "R%d: %s%s [argument]\n", i - 2, base, optional);
|
||||
formatAppend(result, "R%d: %s%s [argument]\n", i - 2, name, optional);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < typedUpvals.size(); ++i)
|
||||
{
|
||||
const TypedUpval& l = typedUpvals[i];
|
||||
|
||||
const char* userdata = tryGetUserdataTypeName(l.type);
|
||||
const char* name = userdata ? userdata : getBaseTypeString(l.type);
|
||||
const char* optional = (l.type & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
|
||||
formatAppend(result, "U%d: %s%s\n", int(i), name, optional);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < typedLocals.size(); ++i)
|
||||
{
|
||||
const TypedLocal& l = typedLocals[i];
|
||||
|
||||
const char* userdata = tryGetUserdataTypeName(l.type);
|
||||
const char* name = userdata ? userdata : getBaseTypeString(l.type);
|
||||
const char* optional = (l.type & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
|
||||
formatAppend(result, "R%d: %s%s from %d to %d\n", l.reg, name, optional, l.startpc, l.endpc);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < typedUpvals.size(); ++i)
|
||||
else
|
||||
{
|
||||
const TypedUpval& l = typedUpvals[i];
|
||||
// Arguments start from third byte in function typeinfo string
|
||||
for (uint8_t i = 2; i < typeinfo.size(); ++i)
|
||||
{
|
||||
uint8_t et = typeinfo[i];
|
||||
|
||||
const char* base = getBaseTypeString(l.type);
|
||||
const char* optional = (l.type & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
const char* base = getBaseTypeString(et);
|
||||
const char* optional = (et & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
|
||||
formatAppend(result, "U%d: %s%s\n", int(i), base, optional);
|
||||
}
|
||||
formatAppend(result, "R%d: %s%s [argument]\n", i - 2, base, optional);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < typedLocals.size(); ++i)
|
||||
{
|
||||
const TypedLocal& l = typedLocals[i];
|
||||
for (size_t i = 0; i < typedUpvals.size(); ++i)
|
||||
{
|
||||
const TypedUpval& l = typedUpvals[i];
|
||||
|
||||
const char* base = getBaseTypeString(l.type);
|
||||
const char* optional = (l.type & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
const char* base = getBaseTypeString(l.type);
|
||||
const char* optional = (l.type & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
|
||||
formatAppend(result, "R%d: %s%s from %d to %d\n", l.reg, base, optional, l.startpc, l.endpc);
|
||||
formatAppend(result, "U%d: %s%s\n", int(i), base, optional);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < typedLocals.size(); ++i)
|
||||
{
|
||||
const TypedLocal& l = typedLocals[i];
|
||||
|
||||
const char* base = getBaseTypeString(l.type);
|
||||
const char* optional = (l.type & LBC_TYPE_OPTIONAL_BIT) ? "?" : "";
|
||||
|
||||
formatAppend(result, "R%d: %s%s from %d to %d\n", l.reg, base, optional, l.startpc, l.endpc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,10 +26,9 @@ LUAU_FASTINTVARIABLE(LuauCompileInlineThreshold, 25)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineThresholdMaxBoost, 300)
|
||||
LUAU_FASTINTVARIABLE(LuauCompileInlineDepth, 5)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileRepeatUntilSkippedLocals, false)
|
||||
LUAU_FASTFLAG(LuauCompileTypeInfo)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypeInfoLookupImprovement, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileTempTypeInfo, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileUserdataInfo, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -107,6 +106,7 @@ struct Compiler
|
||||
, locstants(nullptr)
|
||||
, tableShapes(nullptr)
|
||||
, builtins(nullptr)
|
||||
, userdataTypes(AstName())
|
||||
, functionTypes(nullptr)
|
||||
, localTypes(nullptr)
|
||||
, exprTypes(nullptr)
|
||||
@ -677,10 +677,7 @@ struct Compiler
|
||||
// if the argument is a local that isn't mutated, we will simply reuse the existing register
|
||||
if (int reg = le ? getExprLocalReg(le) : -1; reg >= 0 && (!lv || !lv->written))
|
||||
{
|
||||
if (FFlag::LuauTypeInfoLookupImprovement)
|
||||
args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc});
|
||||
else
|
||||
args.push_back({var, uint8_t(reg)});
|
||||
args.push_back({var, uint8_t(reg), {Constant::Type_Unknown}, kDefaultAllocPc});
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2771,16 +2768,14 @@ struct Compiler
|
||||
{
|
||||
validateContinueUntil(loops.back().continueUsed, stat->condition, body, i + 1);
|
||||
continueValidated = true;
|
||||
|
||||
if (FFlag::LuauCompileRepeatUntilSkippedLocals)
|
||||
conditionLocals = localStack.size();
|
||||
conditionLocals = localStack.size();
|
||||
}
|
||||
}
|
||||
|
||||
// if continue was used, some locals might not have had their initialization completed
|
||||
// the lifetime of these locals has to end before the condition is executed
|
||||
// because referencing skipped locals is not possible from the condition, this earlier closure doesn't affect upvalues
|
||||
if (FFlag::LuauCompileRepeatUntilSkippedLocals && continueValidated)
|
||||
if (continueValidated)
|
||||
{
|
||||
// if continueValidated is set, it means we have visited at least one body node and size > 0
|
||||
setDebugLineEnd(body->body.data[body->body.size - 1]);
|
||||
@ -4094,6 +4089,7 @@ struct Compiler
|
||||
DenseHashMap<AstLocal*, Constant> locstants;
|
||||
DenseHashMap<AstExprTable*, TableShape> tableShapes;
|
||||
DenseHashMap<AstExprCall*, int> builtins;
|
||||
DenseHashMap<AstName, uint8_t> userdataTypes;
|
||||
DenseHashMap<AstExprFunction*, std::string> functionTypes;
|
||||
DenseHashMap<AstLocal*, LuauBytecodeType> localTypes;
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType> exprTypes;
|
||||
@ -4190,18 +4186,34 @@ void compileOrThrow(BytecodeBuilder& bytecode, const ParseResult& parseResult, c
|
||||
Compiler::FunctionVisitor functionVisitor(&compiler, functions);
|
||||
root->visit(&functionVisitor);
|
||||
|
||||
if (FFlag::LuauCompileUserdataInfo)
|
||||
{
|
||||
if (const char* const* ptr = options.userdataTypes)
|
||||
{
|
||||
for (; *ptr; ++ptr)
|
||||
{
|
||||
// Type will only resolve to an AstName if it is actually mentioned in the source
|
||||
if (AstName name = names.get(*ptr); name.value)
|
||||
compiler.userdataTypes[name] = bytecode.addUserdataType(name.value);
|
||||
}
|
||||
|
||||
if (uintptr_t(ptr - options.userdataTypes) > (LBC_TYPE_TAGGED_USERDATA_END - LBC_TYPE_TAGGED_USERDATA_BASE))
|
||||
CompileError::raise(root->location, "Exceeded userdata type limit in the compilation options");
|
||||
}
|
||||
}
|
||||
|
||||
// computes type information for all functions based on type annotations
|
||||
if (FFlag::LuauCompileTypeInfo)
|
||||
{
|
||||
if (options.typeInfoLevel >= 1)
|
||||
buildTypeMap(compiler.functionTypes, compiler.localTypes, compiler.exprTypes, root, options.vectorType, compiler.builtinTypes,
|
||||
compiler.builtins, compiler.globals);
|
||||
buildTypeMap(compiler.functionTypes, compiler.localTypes, compiler.exprTypes, root, options.vectorType, compiler.userdataTypes,
|
||||
compiler.builtinTypes, compiler.builtins, compiler.globals, bytecode);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (functionVisitor.hasTypes)
|
||||
buildTypeMap(compiler.functionTypes, compiler.localTypes, compiler.exprTypes, root, options.vectorType, compiler.builtinTypes,
|
||||
compiler.builtins, compiler.globals);
|
||||
buildTypeMap(compiler.functionTypes, compiler.localTypes, compiler.exprTypes, root, options.vectorType, compiler.userdataTypes,
|
||||
compiler.builtinTypes, compiler.builtins, compiler.globals, bytecode);
|
||||
}
|
||||
|
||||
for (AstExprFunction* expr : functions)
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauCompileTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCompileTempTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCompileUserdataInfo)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -39,7 +40,8 @@ static LuauBytecodeType getPrimitiveType(AstName name)
|
||||
}
|
||||
|
||||
static LuauBytecodeType getType(const AstType* ty, const AstArray<AstGenericType>& generics,
|
||||
const DenseHashMap<AstName, AstStatTypeAlias*>& typeAliases, bool resolveAliases, const char* vectorType)
|
||||
const DenseHashMap<AstName, AstStatTypeAlias*>& typeAliases, bool resolveAliases, const char* vectorType,
|
||||
const DenseHashMap<AstName, uint8_t>& userdataTypes, BytecodeBuilder& bytecode)
|
||||
{
|
||||
if (const AstTypeReference* ref = ty->as<AstTypeReference>())
|
||||
{
|
||||
@ -50,7 +52,7 @@ static LuauBytecodeType getType(const AstType* ty, const AstArray<AstGenericType
|
||||
{
|
||||
// note: we only resolve aliases to the depth of 1 to avoid dealing with recursive aliases
|
||||
if (resolveAliases)
|
||||
return getType((*alias)->type, (*alias)->generics, typeAliases, /* resolveAliases= */ false, vectorType);
|
||||
return getType((*alias)->type, (*alias)->generics, typeAliases, /* resolveAliases= */ false, vectorType, userdataTypes, bytecode);
|
||||
else
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
@ -64,6 +66,15 @@ static LuauBytecodeType getType(const AstType* ty, const AstArray<AstGenericType
|
||||
if (LuauBytecodeType prim = getPrimitiveType(ref->name); prim != LBC_TYPE_INVALID)
|
||||
return prim;
|
||||
|
||||
if (FFlag::LuauCompileUserdataInfo)
|
||||
{
|
||||
if (const uint8_t* userdataIndex = userdataTypes.find(ref->name))
|
||||
{
|
||||
bytecode.useUserdataType(*userdataIndex);
|
||||
return LuauBytecodeType(LBC_TYPE_TAGGED_USERDATA_BASE + *userdataIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// not primitive or alias or generic => host-provided, we assume userdata for now
|
||||
return LBC_TYPE_USERDATA;
|
||||
}
|
||||
@ -82,7 +93,7 @@ static LuauBytecodeType getType(const AstType* ty, const AstArray<AstGenericType
|
||||
|
||||
for (AstType* ty : un->types)
|
||||
{
|
||||
LuauBytecodeType et = getType(ty, generics, typeAliases, resolveAliases, vectorType);
|
||||
LuauBytecodeType et = getType(ty, generics, typeAliases, resolveAliases, vectorType, userdataTypes, bytecode);
|
||||
|
||||
if (et == LBC_TYPE_NIL)
|
||||
{
|
||||
@ -113,7 +124,8 @@ static LuauBytecodeType getType(const AstType* ty, const AstArray<AstGenericType
|
||||
return LBC_TYPE_ANY;
|
||||
}
|
||||
|
||||
static std::string getFunctionType(const AstExprFunction* func, const DenseHashMap<AstName, AstStatTypeAlias*>& typeAliases, const char* vectorType)
|
||||
static std::string getFunctionType(const AstExprFunction* func, const DenseHashMap<AstName, AstStatTypeAlias*>& typeAliases, const char* vectorType,
|
||||
const DenseHashMap<AstName, uint8_t>& userdataTypes, BytecodeBuilder& bytecode)
|
||||
{
|
||||
bool self = func->self != 0;
|
||||
|
||||
@ -130,7 +142,8 @@ static std::string getFunctionType(const AstExprFunction* func, const DenseHashM
|
||||
for (AstLocal* arg : func->args)
|
||||
{
|
||||
LuauBytecodeType ty =
|
||||
arg->annotation ? getType(arg->annotation, func->generics, typeAliases, /* resolveAliases= */ true, vectorType) : LBC_TYPE_ANY;
|
||||
arg->annotation ? getType(arg->annotation, func->generics, typeAliases, /* resolveAliases= */ true, vectorType, userdataTypes, bytecode)
|
||||
: LBC_TYPE_ANY;
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
haveNonAnyParam = true;
|
||||
@ -161,9 +174,11 @@ struct TypeMapVisitor : AstVisitor
|
||||
DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes;
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes;
|
||||
const char* vectorType;
|
||||
const DenseHashMap<AstName, uint8_t>& userdataTypes;
|
||||
const BuiltinTypes& builtinTypes;
|
||||
const DenseHashMap<AstExprCall*, int>& builtinCalls;
|
||||
const DenseHashMap<AstName, Compile::Global>& globals;
|
||||
BytecodeBuilder& bytecode;
|
||||
|
||||
DenseHashMap<AstName, AstStatTypeAlias*> typeAliases;
|
||||
std::vector<std::pair<AstName, AstStatTypeAlias*>> typeAliasStack;
|
||||
@ -171,15 +186,18 @@ struct TypeMapVisitor : AstVisitor
|
||||
DenseHashMap<AstExpr*, const AstType*> resolvedExprs;
|
||||
|
||||
TypeMapVisitor(DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes,
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, const char* vectorType, const BuiltinTypes& builtinTypes,
|
||||
const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals)
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, const char* vectorType, const DenseHashMap<AstName, uint8_t>& userdataTypes,
|
||||
const BuiltinTypes& builtinTypes, const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals,
|
||||
BytecodeBuilder& bytecode)
|
||||
: functionTypes(functionTypes)
|
||||
, localTypes(localTypes)
|
||||
, exprTypes(exprTypes)
|
||||
, vectorType(vectorType)
|
||||
, userdataTypes(userdataTypes)
|
||||
, builtinTypes(builtinTypes)
|
||||
, builtinCalls(builtinCalls)
|
||||
, globals(globals)
|
||||
, bytecode(bytecode)
|
||||
, typeAliases(AstName())
|
||||
, resolvedLocals(nullptr)
|
||||
, resolvedExprs(nullptr)
|
||||
@ -250,7 +268,7 @@ struct TypeMapVisitor : AstVisitor
|
||||
|
||||
resolvedExprs[expr] = ty;
|
||||
|
||||
LuauBytecodeType bty = getType(ty, {}, typeAliases, /* resolveAliases= */ true, vectorType);
|
||||
LuauBytecodeType bty = getType(ty, {}, typeAliases, /* resolveAliases= */ true, vectorType, userdataTypes, bytecode);
|
||||
exprTypes[expr] = bty;
|
||||
return bty;
|
||||
}
|
||||
@ -263,7 +281,7 @@ struct TypeMapVisitor : AstVisitor
|
||||
|
||||
resolvedLocals[local] = ty;
|
||||
|
||||
LuauBytecodeType bty = getType(ty, {}, typeAliases, /* resolveAliases= */ true, vectorType);
|
||||
LuauBytecodeType bty = getType(ty, {}, typeAliases, /* resolveAliases= */ true, vectorType, userdataTypes, bytecode);
|
||||
|
||||
if (bty != LBC_TYPE_ANY)
|
||||
localTypes[local] = bty;
|
||||
@ -354,7 +372,7 @@ struct TypeMapVisitor : AstVisitor
|
||||
|
||||
bool visit(AstExprFunction* node) override
|
||||
{
|
||||
std::string type = getFunctionType(node, typeAliases, vectorType);
|
||||
std::string type = getFunctionType(node, typeAliases, vectorType, userdataTypes, bytecode);
|
||||
|
||||
if (!type.empty())
|
||||
functionTypes[node] = std::move(type);
|
||||
@ -393,7 +411,7 @@ struct TypeMapVisitor : AstVisitor
|
||||
|
||||
if (AstType* annotation = local->annotation)
|
||||
{
|
||||
LuauBytecodeType ty = getType(annotation, {}, typeAliases, /* resolveAliases= */ true, vectorType);
|
||||
LuauBytecodeType ty = getType(annotation, {}, typeAliases, /* resolveAliases= */ true, vectorType, userdataTypes, bytecode);
|
||||
|
||||
if (ty != LBC_TYPE_ANY)
|
||||
localTypes[local] = ty;
|
||||
@ -754,10 +772,11 @@ struct TypeMapVisitor : AstVisitor
|
||||
};
|
||||
|
||||
void buildTypeMap(DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes,
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, AstNode* root, const char* vectorType, const BuiltinTypes& builtinTypes,
|
||||
const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals)
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, AstNode* root, const char* vectorType, const DenseHashMap<AstName, uint8_t>& userdataTypes,
|
||||
const BuiltinTypes& builtinTypes, const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals,
|
||||
BytecodeBuilder& bytecode)
|
||||
{
|
||||
TypeMapVisitor visitor(functionTypes, localTypes, exprTypes, vectorType, builtinTypes, builtinCalls, globals);
|
||||
TypeMapVisitor visitor(functionTypes, localTypes, exprTypes, vectorType, userdataTypes, builtinTypes, builtinCalls, globals, bytecode);
|
||||
root->visit(&visitor);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
class BytecodeBuilder;
|
||||
|
||||
struct BuiltinTypes
|
||||
{
|
||||
@ -26,7 +27,8 @@ struct BuiltinTypes
|
||||
};
|
||||
|
||||
void buildTypeMap(DenseHashMap<AstExprFunction*, std::string>& functionTypes, DenseHashMap<AstLocal*, LuauBytecodeType>& localTypes,
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, AstNode* root, const char* vectorType, const BuiltinTypes& builtinTypes,
|
||||
const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals);
|
||||
DenseHashMap<AstExpr*, LuauBytecodeType>& exprTypes, AstNode* root, const char* vectorType, const DenseHashMap<AstName, uint8_t>& userdataTypes,
|
||||
const BuiltinTypes& builtinTypes, const DenseHashMap<AstExprCall*, int>& builtinCalls, const DenseHashMap<AstName, Compile::Global>& globals,
|
||||
BytecodeBuilder& bytecode);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -156,6 +156,7 @@ struct lua_ExecutionCallbacks
|
||||
int (*enter)(lua_State* L, Proto* proto); // called when function is about to start/resume (when execdata is present), return 0 to exit VM
|
||||
void (*disable)(lua_State* L, Proto* proto); // called when function has to be switched from native to bytecode in the debugger
|
||||
size_t (*getmemorysize)(lua_State* L, Proto* proto); // called to request the size of memory associated with native part of the Proto
|
||||
uint8_t (*gettypemapping)(lua_State* L, const char* str, size_t len); // called to get the userdata type index
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -11,7 +11,6 @@
|
||||
#include "ldebug.h"
|
||||
#include "lvm.h"
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastCrossTableMove, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFastTableMaxn, false)
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauFasterConcat, false)
|
||||
|
||||
@ -145,68 +144,6 @@ static void moveelements(lua_State* L, int srct, int dstt, int f, int e, int t)
|
||||
|
||||
luaC_barrierfast(L, dst);
|
||||
}
|
||||
else if (DFFlag::LuauFastCrossTableMove && dst != src)
|
||||
{
|
||||
// compute the array slice we have to copy over
|
||||
int slicestart = f < 1 ? 0 : (f > src->sizearray ? src->sizearray : f - 1);
|
||||
int sliceend = e < 1 ? 0 : (e > src->sizearray ? src->sizearray : e);
|
||||
LUAU_ASSERT(slicestart <= sliceend);
|
||||
|
||||
int slicecount = sliceend - slicestart;
|
||||
|
||||
if (slicecount > 0)
|
||||
{
|
||||
// array slice starting from INT_MIN is impossible, so we don't have to worry about int overflow
|
||||
int dstslicestart = f < 1 ? -f + 1 : 0;
|
||||
|
||||
// copy over the slice
|
||||
for (int i = 0; i < slicecount; ++i)
|
||||
{
|
||||
lua_rawgeti(L, srct, slicestart + i + 1);
|
||||
lua_rawseti(L, dstt, dstslicestart + t + i);
|
||||
}
|
||||
}
|
||||
|
||||
// copy the remaining elements that could be in the hash part
|
||||
int hashpartsize = sizenode(src);
|
||||
|
||||
// select the strategy with the least amount of steps
|
||||
if (n <= hashpartsize)
|
||||
{
|
||||
for (int i = 0; i < n; ++i)
|
||||
{
|
||||
// skip array slice elements that were already copied over
|
||||
if (cast_to(unsigned int, f + i - 1) < cast_to(unsigned int, src->sizearray))
|
||||
continue;
|
||||
|
||||
lua_rawgeti(L, srct, f + i);
|
||||
lua_rawseti(L, dstt, t + i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// source and destination tables are different, so we can iterate over source hash part directly
|
||||
int i = hashpartsize;
|
||||
|
||||
while (i--)
|
||||
{
|
||||
LuaNode* node = gnode(src, i);
|
||||
if (ttisnumber(gkey(node)))
|
||||
{
|
||||
double n = nvalue(gkey(node));
|
||||
|
||||
int k;
|
||||
luai_num2int(k, n);
|
||||
|
||||
if (luai_numeq(cast_num(k), n) && k >= f && k <= e)
|
||||
{
|
||||
lua_rawgeti(L, srct, k);
|
||||
lua_rawseti(L, dstt, t - f + k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (t > e || t <= f || dst != src)
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <string.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauLoadTypeInfo)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLoadUserdataInfo, false)
|
||||
|
||||
// TODO: RAII deallocation doesn't work for longjmp builds if a memory error happens
|
||||
template<typename T>
|
||||
@ -187,6 +188,65 @@ static void resolveImportSafe(lua_State* L, Table* env, TValue* k, uint32_t id)
|
||||
}
|
||||
}
|
||||
|
||||
static void remapUserdataTypes(char* data, size_t size, uint8_t* userdataRemapping, uint32_t count)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauLoadUserdataInfo);
|
||||
|
||||
size_t offset = 0;
|
||||
|
||||
uint32_t typeSize = readVarInt(data, size, offset);
|
||||
uint32_t upvalCount = readVarInt(data, size, offset);
|
||||
uint32_t localCount = readVarInt(data, size, offset);
|
||||
|
||||
if (typeSize != 0)
|
||||
{
|
||||
uint8_t* types = (uint8_t*)data + offset;
|
||||
|
||||
// Skip two bytes of function type introduction
|
||||
for (uint32_t i = 2; i < typeSize; i++)
|
||||
{
|
||||
uint32_t index = uint32_t(types[i] - LBC_TYPE_TAGGED_USERDATA_BASE);
|
||||
|
||||
if (index < count)
|
||||
types[i] = userdataRemapping[index];
|
||||
}
|
||||
|
||||
offset += typeSize;
|
||||
}
|
||||
|
||||
if (upvalCount != 0)
|
||||
{
|
||||
uint8_t* types = (uint8_t*)data + offset;
|
||||
|
||||
for (uint32_t i = 0; i < upvalCount; i++)
|
||||
{
|
||||
uint32_t index = uint32_t(types[i] - LBC_TYPE_TAGGED_USERDATA_BASE);
|
||||
|
||||
if (index < count)
|
||||
types[i] = userdataRemapping[index];
|
||||
}
|
||||
|
||||
offset += upvalCount;
|
||||
}
|
||||
|
||||
if (localCount != 0)
|
||||
{
|
||||
for (uint32_t i = 0; i < localCount; i++)
|
||||
{
|
||||
uint32_t index = uint32_t(data[offset] - LBC_TYPE_TAGGED_USERDATA_BASE);
|
||||
|
||||
if (index < count)
|
||||
data[offset] = userdataRemapping[index];
|
||||
|
||||
offset += 2;
|
||||
readVarInt(data, size, offset);
|
||||
readVarInt(data, size, offset);
|
||||
}
|
||||
}
|
||||
|
||||
LUAU_ASSERT(offset == size);
|
||||
}
|
||||
|
||||
int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size, int env)
|
||||
{
|
||||
size_t offset = 0;
|
||||
@ -227,6 +287,18 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
||||
if (version >= 4)
|
||||
{
|
||||
typesversion = read<uint8_t>(data, size, offset);
|
||||
|
||||
if (FFlag::LuauLoadUserdataInfo)
|
||||
{
|
||||
if (typesversion < LBC_TYPE_VERSION_MIN || typesversion > LBC_TYPE_VERSION_MAX)
|
||||
{
|
||||
char chunkbuf[LUA_IDSIZE];
|
||||
const char* chunkid = luaO_chunkid(chunkbuf, sizeof(chunkbuf), chunkname, strlen(chunkname));
|
||||
lua_pushfstring(L, "%s: bytecode type version mismatch (expected [%d..%d], got %d)", chunkid, LBC_TYPE_VERSION_MIN,
|
||||
LBC_TYPE_VERSION_MAX, typesversion);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// string table
|
||||
@ -241,6 +313,31 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
||||
offset += length;
|
||||
}
|
||||
|
||||
// userdata type remapping table
|
||||
// for unknown userdata types, the entry will remap to common 'userdata' type
|
||||
const uint32_t userdataTypeLimit = LBC_TYPE_TAGGED_USERDATA_END - LBC_TYPE_TAGGED_USERDATA_BASE;
|
||||
uint8_t userdataRemapping[userdataTypeLimit];
|
||||
|
||||
if (FFlag::LuauLoadUserdataInfo && typesversion == 3)
|
||||
{
|
||||
memset(userdataRemapping, LBC_TYPE_USERDATA, userdataTypeLimit);
|
||||
|
||||
uint8_t index = read<uint8_t>(data, size, offset);
|
||||
|
||||
while (index != 0)
|
||||
{
|
||||
TString* name = readString(strings, data, size, offset);
|
||||
|
||||
if (uint32_t(index - 1) < userdataTypeLimit)
|
||||
{
|
||||
if (auto cb = L->global->ecb.gettypemapping)
|
||||
userdataRemapping[index - 1] = cb(L, getstr(name), name->len);
|
||||
}
|
||||
|
||||
index = read<uint8_t>(data, size, offset);
|
||||
}
|
||||
}
|
||||
|
||||
// proto table
|
||||
unsigned int protoCount = readVarInt(data, size, offset);
|
||||
TempBuffer<Proto*> protos(L, protoCount);
|
||||
@ -299,7 +396,7 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
||||
|
||||
offset += typesize;
|
||||
}
|
||||
else if (typesversion == 2)
|
||||
else if (typesversion == 2 || (FFlag::LuauLoadUserdataInfo && typesversion == 3))
|
||||
{
|
||||
uint32_t typesize = readVarInt(data, size, offset);
|
||||
|
||||
@ -311,6 +408,11 @@ int luau_load(lua_State* L, const char* chunkname, const char* data, size_t size
|
||||
p->sizetypeinfo = typesize;
|
||||
memcpy(p->typeinfo, types, typesize);
|
||||
offset += typesize;
|
||||
|
||||
if (FFlag::LuauLoadUserdataInfo && typesversion == 3)
|
||||
{
|
||||
remapUserdataTypes((char*)(uint8_t*)p->typeinfo, p->sizetypeinfo, userdataRemapping, userdataTypeLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,9 @@ LUAU_FASTINT(LuauCompileLoopUnrollThreshold)
|
||||
LUAU_FASTINT(LuauCompileLoopUnrollThresholdMaxBoost)
|
||||
LUAU_FASTINT(LuauRecursionLimit)
|
||||
|
||||
LUAU_FASTFLAG(LuauCompileNoJumpLineRetarget)
|
||||
LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals)
|
||||
LUAU_FASTFLAG(LuauCompileTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCompileTempTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCompileUserdataInfo)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
@ -2106,8 +2107,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueEarlyCleanup")
|
||||
{
|
||||
ScopedFastFlag luauCompileRepeatUntilSkippedLocals{FFlag::LuauCompileRepeatUntilSkippedLocals, true};
|
||||
|
||||
// locals after a potential 'continue' are not accessible inside the condition and can be closed at the end of a block
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
local y
|
||||
@ -2788,8 +2787,6 @@ end
|
||||
|
||||
TEST_CASE("DebugLineInfoWhile")
|
||||
{
|
||||
ScopedFastFlag luauCompileNoJumpLineRetarget{FFlag::LuauCompileNoJumpLineRetarget, true};
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Lines);
|
||||
Luau::compileOrThrow(bcb, R"(
|
||||
@ -3136,8 +3133,6 @@ local 8: reg 3, start pc 35 line 21, end pc 35 line 21
|
||||
|
||||
TEST_CASE("DebugLocals2")
|
||||
{
|
||||
ScopedFastFlag luauCompileRepeatUntilSkippedLocals{FFlag::LuauCompileRepeatUntilSkippedLocals, true};
|
||||
|
||||
const char* source = R"(
|
||||
function foo(x)
|
||||
repeat
|
||||
@ -3167,9 +3162,6 @@ local 2: reg 0, start pc 0 line 4, end pc 2 line 6
|
||||
|
||||
TEST_CASE("DebugLocals3")
|
||||
{
|
||||
ScopedFastFlag luauCompileRepeatUntilSkippedLocals{FFlag::LuauCompileRepeatUntilSkippedLocals, true};
|
||||
ScopedFastFlag luauCompileNoJumpLineRetarget{FFlag::LuauCompileNoJumpLineRetarget, true};
|
||||
|
||||
const char* source = R"(
|
||||
function foo(x)
|
||||
repeat
|
||||
@ -3203,6 +3195,7 @@ local 4: reg 0, start pc 0 line 4, end pc 5 line 8
|
||||
8: RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("DebugRemarks")
|
||||
{
|
||||
Luau::BytecodeBuilder bcb;
|
||||
@ -3230,6 +3223,80 @@ RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("DebugTypes")
|
||||
{
|
||||
ScopedFastFlag luauCompileTypeInfo{FFlag::LuauCompileTypeInfo, true};
|
||||
ScopedFastFlag luauCompileTempTypeInfo{FFlag::LuauCompileTempTypeInfo, true};
|
||||
ScopedFastFlag luauCompileUserdataInfo{FFlag::LuauCompileUserdataInfo, true};
|
||||
|
||||
const char* source = R"(
|
||||
local up: number = 2
|
||||
|
||||
function foo(e: vector, f: mat3, g: sequence)
|
||||
local h = e * e
|
||||
|
||||
for i=1,3 do
|
||||
print(i)
|
||||
end
|
||||
|
||||
print(e * f)
|
||||
print(g)
|
||||
print(h)
|
||||
|
||||
up += a
|
||||
return a
|
||||
end
|
||||
)";
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
bcb.setDumpFlags(Luau::BytecodeBuilder::Dump_Code | Luau::BytecodeBuilder::Dump_Types);
|
||||
bcb.setDumpSource(source);
|
||||
|
||||
Luau::CompileOptions options;
|
||||
options.vectorCtor = "vector";
|
||||
options.vectorType = "vector";
|
||||
|
||||
options.typeInfoLevel = 1;
|
||||
|
||||
static const char* kUserdataCompileTypes[] = {"vec2", "color", "mat3", nullptr};
|
||||
options.userdataTypes = kUserdataCompileTypes;
|
||||
|
||||
Luau::compileOrThrow(bcb, source, options);
|
||||
|
||||
CHECK_EQ("\n" + bcb.dumpFunction(0), R"(
|
||||
R0: vector [argument]
|
||||
R1: mat3 [argument]
|
||||
R2: userdata [argument]
|
||||
U0: number
|
||||
R6: any from 1 to 9
|
||||
R3: vector from 0 to 30
|
||||
MUL R3 R0 R0
|
||||
LOADN R6 1
|
||||
LOADN R4 3
|
||||
LOADN R5 1
|
||||
FORNPREP R4 L1
|
||||
L0: GETIMPORT R7 1 [print]
|
||||
MOVE R8 R6
|
||||
CALL R7 1 0
|
||||
FORNLOOP R4 L0
|
||||
L1: GETIMPORT R4 1 [print]
|
||||
MUL R5 R0 R1
|
||||
CALL R4 1 0
|
||||
GETIMPORT R4 1 [print]
|
||||
MOVE R5 R2
|
||||
CALL R4 1 0
|
||||
GETIMPORT R4 1 [print]
|
||||
MOVE R5 R3
|
||||
CALL R4 1 0
|
||||
GETUPVAL R4 0
|
||||
GETIMPORT R5 3 [a]
|
||||
ADD R4 R4 R5
|
||||
SETUPVAL R4 0
|
||||
GETIMPORT R4 3 [a]
|
||||
RETURN R4 1
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("SourceRemarks")
|
||||
{
|
||||
const char* source = R"(
|
||||
@ -4158,8 +4225,6 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("Coverage")
|
||||
{
|
||||
ScopedFastFlag luauCompileNoJumpLineRetarget{FFlag::LuauCompileNoJumpLineRetarget, true};
|
||||
|
||||
// basic statement coverage
|
||||
CHECK_EQ("\n" + compileFunction0Coverage(R"(
|
||||
print(1)
|
||||
|
@ -33,8 +33,7 @@ void luaC_validate(lua_State* L);
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauAbortingChecks)
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_FASTFLAG(LuauCompileRepeatUntilSkippedLocals)
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauFastCrossTableMove)
|
||||
LUAU_FASTFLAG(LuauCodegenFixSplitStoreConstMismatch)
|
||||
|
||||
static lua_CompileOptions defaultOptions()
|
||||
{
|
||||
@ -443,8 +442,6 @@ TEST_CASE("Sort")
|
||||
|
||||
TEST_CASE("Move")
|
||||
{
|
||||
ScopedFastFlag luauFastCrossTableMove{DFFlag::LuauFastCrossTableMove, true};
|
||||
|
||||
runConformance("move.lua");
|
||||
}
|
||||
|
||||
@ -717,8 +714,6 @@ TEST_CASE("Debugger")
|
||||
static bool singlestep = false;
|
||||
static int stephits = 0;
|
||||
|
||||
ScopedFastFlag luauCompileRepeatUntilSkippedLocals{FFlag::LuauCompileRepeatUntilSkippedLocals, true};
|
||||
|
||||
SUBCASE("")
|
||||
{
|
||||
singlestep = false;
|
||||
@ -2140,6 +2135,8 @@ TEST_CASE("Native")
|
||||
if (!codegen || !luau_codegen_supported())
|
||||
return;
|
||||
|
||||
ScopedFastFlag luauCodegenFixSplitStoreConstMismatch{FFlag::LuauCodegenFixSplitStoreConstMismatch, true};
|
||||
|
||||
SUBCASE("Checked")
|
||||
{
|
||||
FFlag::DebugLuauAbortingChecks.value = true;
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "Luau/IrBuilder.h"
|
||||
|
||||
static const char* kUserdataRunTypes[] = {"extra", "color", "vec2", "mat3", nullptr};
|
||||
|
||||
inline uint8_t vectorAccessBytecodeType(const char* member, size_t memberLength)
|
||||
{
|
||||
using namespace Luau::CodeGen;
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
|
||||
LUAU_FASTFLAG(DebugLuauAbortingChecks)
|
||||
LUAU_FASTFLAG(LuauCodegenLoadPropCheckRegLinkInTv)
|
||||
LUAU_FASTFLAG(LuauCodegenFixSplitStoreConstMismatch)
|
||||
|
||||
using namespace Luau::CodeGen;
|
||||
|
||||
@ -2658,6 +2658,60 @@ bb_0:
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore1")
|
||||
{
|
||||
ScopedFastFlag luauCodegenFixSplitStoreConstMismatch{FFlag::LuauCodegenFixSplitStoreConstMismatch, true};
|
||||
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
|
||||
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_INT R0, 1i
|
||||
CHECK_TAG R0, ttable, exit(1)
|
||||
%2 = LOAD_TVALUE R0
|
||||
STORE_TVALUE R1, %2
|
||||
RETURN R1, 1i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "DoNotProduceInvalidSplitStore2")
|
||||
{
|
||||
ScopedFastFlag luauCodegenFixSplitStoreConstMismatch{FFlag::LuauCodegenFixSplitStoreConstMismatch, true};
|
||||
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
build.inst(IrCmd::STORE_INT, build.vmReg(0), build.constInt(1));
|
||||
build.inst(IrCmd::CHECK_TAG, build.vmReg(0), build.constTag(tnumber), 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_INT R0, 1i
|
||||
CHECK_TAG R0, tnumber, exit(1)
|
||||
%2 = LOAD_TVALUE R0
|
||||
STORE_TVALUE R1, %2
|
||||
RETURN R1, 1i
|
||||
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
||||
TEST_SUITE_BEGIN("Analysis");
|
||||
@ -3475,8 +3529,6 @@ bb_1:
|
||||
|
||||
TEST_CASE_FIXTURE(IrBuilderFixture, "TaggedValuePropagationIntoTvalueChecksRegisterVersion")
|
||||
{
|
||||
ScopedFastFlag luauCodegenLoadPropCheckRegLinkInTv{FFlag::LuauCodegenLoadPropCheckRegLinkInTv, true};
|
||||
|
||||
IrOp entry = build.block(IrBlockKind::Internal);
|
||||
|
||||
build.beginBlock(entry);
|
||||
|
@ -13,18 +13,17 @@
|
||||
#include "ConformanceIrHooks.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
LUAU_FASTFLAG(LuauCodegenRemoveDeadStores5)
|
||||
LUAU_FASTFLAG(LuauCodegenDirectUserdataFlow)
|
||||
LUAU_FASTFLAG(LuauCompileTypeInfo)
|
||||
LUAU_FASTFLAG(LuauLoadTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCodegenTypeInfo)
|
||||
LUAU_FASTFLAG(LuauTypeInfoLookupImprovement)
|
||||
LUAU_FASTFLAG(LuauCodegenIrTypeNames)
|
||||
LUAU_FASTFLAG(LuauCompileTempTypeInfo)
|
||||
LUAU_FASTFLAG(LuauCodegenFixVectorFields)
|
||||
LUAU_FASTFLAG(LuauCodegenVectorMispredictFix)
|
||||
LUAU_FASTFLAG(LuauCodegenAnalyzeHostVectorOps)
|
||||
LUAU_FASTFLAG(LuauCompileUserdataInfo)
|
||||
LUAU_FASTFLAG(LuauLoadUserdataInfo)
|
||||
|
||||
static std::string getCodegenAssembly(const char* source, bool includeIrTypes = false, int debugLevel = 1)
|
||||
{
|
||||
@ -64,6 +63,9 @@ static std::string getCodegenAssembly(const char* source, bool includeIrTypes =
|
||||
copts.vectorCtor = "vector";
|
||||
copts.vectorType = "vector";
|
||||
|
||||
static const char* kUserdataCompileTypes[] = {"vec2", "color", "mat3", nullptr};
|
||||
copts.userdataTypes = kUserdataCompileTypes;
|
||||
|
||||
Luau::BytecodeBuilder bcb;
|
||||
Luau::compileOrThrow(bcb, result, names, copts);
|
||||
|
||||
@ -71,6 +73,33 @@ static std::string getCodegenAssembly(const char* source, bool includeIrTypes =
|
||||
std::unique_ptr<lua_State, void (*)(lua_State*)> globalState(luaL_newstate(), lua_close);
|
||||
lua_State* L = globalState.get();
|
||||
|
||||
// Runtime mapping is specifically created to NOT match the compilation mapping
|
||||
options.compilationOptions.userdataTypes = kUserdataRunTypes;
|
||||
|
||||
if (Luau::CodeGen::isSupported())
|
||||
{
|
||||
// Type remapper requires the codegen runtime
|
||||
Luau::CodeGen::create(L);
|
||||
|
||||
Luau::CodeGen::setUserdataRemapper(L, kUserdataRunTypes, [](void* context, const char* str, size_t len) -> uint8_t {
|
||||
const char** types = (const char**)context;
|
||||
|
||||
uint8_t index = 0;
|
||||
|
||||
std::string_view sv{str, len};
|
||||
|
||||
for (; *types; ++types)
|
||||
{
|
||||
if (sv == *types)
|
||||
return index;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return 0xff;
|
||||
});
|
||||
}
|
||||
|
||||
if (luau_load(L, "name", bytecode.data(), bytecode.size(), 0) == 0)
|
||||
return Luau::CodeGen::getAssembly(L, -1, options, nullptr);
|
||||
|
||||
@ -480,8 +509,6 @@ bb_bytecode_1:
|
||||
TEST_CASE("VectorRandomProp")
|
||||
{
|
||||
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
|
||||
ScopedFastFlag luauCodegenFixVectorFields{FFlag::LuauCodegenFixVectorFields, true};
|
||||
ScopedFastFlag luauCodegenVectorMispredictFix{FFlag::LuauCodegenVectorMispredictFix, true};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function foo(a: vector)
|
||||
@ -524,7 +551,6 @@ bb_6:
|
||||
TEST_CASE("VectorCustomAccess")
|
||||
{
|
||||
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
|
||||
ScopedFastFlag luauCodegenVectorMispredictFix{FFlag::LuauCodegenVectorMispredictFix, true};
|
||||
ScopedFastFlag luauCodegenAnalyzeHostVectorOps{FFlag::LuauCodegenAnalyzeHostVectorOps, true};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
@ -600,7 +626,6 @@ bb_bytecode_1:
|
||||
TEST_CASE("VectorCustomAccessChain")
|
||||
{
|
||||
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
|
||||
ScopedFastFlag luauCodegenVectorMispredictFix{FFlag::LuauCodegenVectorMispredictFix, true};
|
||||
ScopedFastFlag LuauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||
ScopedFastFlag luauCodegenAnalyzeHostVectorOps{FFlag::LuauCodegenAnalyzeHostVectorOps, true};
|
||||
|
||||
@ -655,7 +680,6 @@ bb_bytecode_1:
|
||||
TEST_CASE("VectorCustomNamecallChain")
|
||||
{
|
||||
ScopedFastFlag luauCodegenRemoveDeadStores{FFlag::LuauCodegenRemoveDeadStores5, true};
|
||||
ScopedFastFlag luauCodegenVectorMispredictFix{FFlag::LuauCodegenVectorMispredictFix, true};
|
||||
ScopedFastFlag LuauCodegenDirectUserdataFlow{FFlag::LuauCodegenDirectUserdataFlow, true};
|
||||
ScopedFastFlag luauCodegenAnalyzeHostVectorOps{FFlag::LuauCodegenAnalyzeHostVectorOps, true};
|
||||
|
||||
@ -717,8 +741,8 @@ bb_bytecode_1:
|
||||
TEST_CASE("VectorCustomNamecallChain2")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCompileTempTypeInfo, true},
|
||||
{FFlag::LuauCodegenVectorMispredictFix, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenAnalyzeHostVectorOps, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true},
|
||||
{FFlag::LuauCodegenAnalyzeHostVectorOps, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
type Vertex = {n: vector, b: vector}
|
||||
@ -1048,7 +1072,7 @@ bb_bytecode_1:
|
||||
TEST_CASE("LoadAndMoveTypePropagation")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function getsum(n)
|
||||
@ -1116,7 +1140,7 @@ bb_bytecode_4:
|
||||
TEST_CASE("ArgumentTypeRefinement")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function getsum(x, y)
|
||||
@ -1155,7 +1179,7 @@ bb_bytecode_0:
|
||||
TEST_CASE("InlineFunctionType")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function inl(v: vector, s: number)
|
||||
@ -1204,8 +1228,7 @@ bb_bytecode_0:
|
||||
TEST_CASE("ResolveTablePathTypes")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
@ -1260,8 +1283,7 @@ bb_6:
|
||||
TEST_CASE("ResolvableSimpleMath")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
type Vertex = { p: vector, uv: vector, n: vector, t: vector, b: vector, h: number }
|
||||
@ -1318,8 +1340,8 @@ end
|
||||
TEST_CASE("ResolveVectorNamecalls")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true}, {FFlag::LuauCodegenAnalyzeHostVectorOps, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCodegenDirectUserdataFlow, true},
|
||||
{FFlag::LuauCodegenAnalyzeHostVectorOps, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
@ -1384,8 +1406,7 @@ bb_6:
|
||||
TEST_CASE("ImmediateTypeAnnotationHelp")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
local function foo(arr, i)
|
||||
@ -1424,8 +1445,7 @@ bb_2:
|
||||
TEST_CASE("UnaryTypeResolve")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
local function foo(a, b: vector, c)
|
||||
@ -1448,8 +1468,7 @@ end
|
||||
TEST_CASE("ForInManualAnnotation")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenAssembly(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
@ -1545,8 +1564,7 @@ bb_12:
|
||||
TEST_CASE("ForInAutoAnnotationIpairs")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
@ -1574,8 +1592,7 @@ end
|
||||
TEST_CASE("ForInAutoAnnotationPairs")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
@ -1603,8 +1620,7 @@ end
|
||||
TEST_CASE("ForInAutoAnnotationGeneric")
|
||||
{
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauTypeInfoLookupImprovement, true}, {FFlag::LuauCodegenIrTypeNames, true},
|
||||
{FFlag::LuauCompileTempTypeInfo, true}};
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
type Vertex = {pos: vector, normal: vector}
|
||||
@ -1629,4 +1645,49 @@ end
|
||||
)");
|
||||
}
|
||||
|
||||
// Temporary test, when we don't compile new typeinfo, but support loading it
|
||||
TEST_CASE("CustomUserdataTypesTemp")
|
||||
{
|
||||
// This test requires runtime component to be present
|
||||
if (!Luau::CodeGen::isSupported())
|
||||
return;
|
||||
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, false},
|
||||
{FFlag::LuauLoadUserdataInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
local function foo(v: vec2, x: mat3)
|
||||
return v.X * x
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function foo(v, x) line 2
|
||||
; R0: userdata [argument 'v']
|
||||
; R1: userdata [argument 'x']
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("CustomUserdataTypes")
|
||||
{
|
||||
// This test requires runtime component to be present
|
||||
if (!Luau::CodeGen::isSupported())
|
||||
return;
|
||||
|
||||
ScopedFastFlag sffs[]{{FFlag::LuauLoadTypeInfo, true}, {FFlag::LuauCompileTypeInfo, true}, {FFlag::LuauCodegenTypeInfo, true},
|
||||
{FFlag::LuauCodegenRemoveDeadStores5, true}, {FFlag::LuauCompileTempTypeInfo, true}, {FFlag::LuauCompileUserdataInfo, true},
|
||||
{FFlag::LuauLoadUserdataInfo, true}};
|
||||
|
||||
CHECK_EQ("\n" + getCodegenHeader(R"(
|
||||
local function foo(v: vec2, x: mat3)
|
||||
return v.X * x
|
||||
end
|
||||
)"),
|
||||
R"(
|
||||
; function foo(v, x) line 2
|
||||
; R0: vec2 [argument 'v']
|
||||
; R1: mat3 [argument 'x']
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -556,4 +556,22 @@ local E = require(script.Parent.A)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "nonstrict_shouldnt_warn_on_valid_buffer_use")
|
||||
{
|
||||
loadDefinition(R"(
|
||||
declare buffer: {
|
||||
create: @checked (size: number) -> buffer,
|
||||
readi8: @checked (b: buffer, offset: number) -> number,
|
||||
writef64: @checked (b: buffer, offset: number, value: number) -> (),
|
||||
}
|
||||
)");
|
||||
|
||||
CheckResult result = checkNonStrict(R"(
|
||||
local b = buffer.create(100)
|
||||
buffer.writef64(b, 0, 5)
|
||||
buffer.readi8(b, 0)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -420,4 +420,22 @@ print(NewProxyOne.HelloICauseACrash)
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplFixture, "InteractiveStackReserve1")
|
||||
{
|
||||
// Reset stack reservation
|
||||
lua_resume(L, nullptr, 0);
|
||||
|
||||
runCode(L, R"(
|
||||
local t = {}
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReplFixture, "InteractiveStackReserve2")
|
||||
{
|
||||
// Reset stack reservation
|
||||
lua_resume(L, nullptr, 0);
|
||||
|
||||
getCompletionSet("a");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -915,6 +915,7 @@ TEST_IS_SUBTYPE(numberToNumberType, negate(builtinTypes->classType));
|
||||
TEST_IS_NOT_SUBTYPE(numberToNumberType, negate(builtinTypes->functionType));
|
||||
|
||||
// Negated supertypes: Primitives and singletons
|
||||
TEST_IS_NOT_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->stringType));
|
||||
TEST_IS_SUBTYPE(builtinTypes->stringType, negate(builtinTypes->numberType));
|
||||
TEST_IS_SUBTYPE(str("foo"), meet(builtinTypes->stringType, negate(str("bar"))));
|
||||
TEST_IS_NOT_SUBTYPE(builtinTypes->trueType, negate(builtinTypes->booleanType));
|
||||
|
@ -782,7 +782,10 @@ TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_map")
|
||||
TypeId ty = requireType("map");
|
||||
const FunctionType* ftv = get<FunctionType>(follow(ty));
|
||||
|
||||
CHECK_EQ("map<a, b>(arr: {a}, fn: (a) -> b): {b}", toStringNamedFunction("map", *ftv));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("map<a, b>(arr: {a}, fn: (a) -> (b, ...unknown)): {b}", toStringNamedFunction("map", *ftv));
|
||||
else
|
||||
CHECK_EQ("map<a, b>(arr: {a}, fn: (a) -> b): {b}", toStringNamedFunction("map", *ftv));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "toStringNamedFunction_generic_pack")
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
|
||||
#include "ClassFixture.h"
|
||||
@ -14,6 +13,7 @@
|
||||
using namespace Luau;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypeFamilyApplicationCartesianProductLimit)
|
||||
|
||||
struct FamilyFixture : Fixture
|
||||
{
|
||||
@ -24,7 +24,7 @@ struct FamilyFixture : Fixture
|
||||
{
|
||||
swapFamily = TypeFamily{/* name */ "Swap",
|
||||
/* reducer */
|
||||
[](TypeId instance, NotNull<TypeFamilyQueue> queue, const std::vector<TypeId>& tys, const std::vector<TypePackId>& tps,
|
||||
[](TypeId instance, const std::vector<TypeId>& tys, const std::vector<TypePackId>& tps,
|
||||
NotNull<TypeFamilyContext> ctx) -> TypeFamilyReductionResult<TypeId> {
|
||||
LUAU_ASSERT(tys.size() == 1);
|
||||
TypeId param = follow(tys.at(0));
|
||||
@ -716,4 +716,117 @@ _(setmetatable(_,{[...]=_,}))
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cyclic_concat_family_at_work")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = concat<string | T, string>
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireTypeAlias("T")) == "string");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "exceeded_distributivity_limits")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
ScopedFastInt sfi{DFInt::LuauTypeFamilyApplicationCartesianProductLimit, 10};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class A
|
||||
function __mul(self, rhs: unknown): A
|
||||
end
|
||||
|
||||
declare class B
|
||||
function __mul(self, rhs: unknown): B
|
||||
end
|
||||
|
||||
declare class C
|
||||
function __mul(self, rhs: unknown): C
|
||||
end
|
||||
|
||||
declare class D
|
||||
function __mul(self, rhs: unknown): D
|
||||
end
|
||||
)");
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = mul<A | B | C | D, A | B | C | D>
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(get<UninhabitedTypeFamily>(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "didnt_quite_exceed_distributivity_limits")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
// We duplicate the test here because we want to make sure the test failed
|
||||
// due to exceeding the limits specifically, rather than any possible reasons.
|
||||
ScopedFastInt sfi{DFInt::LuauTypeFamilyApplicationCartesianProductLimit, 20};
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class A
|
||||
function __mul(self, rhs: unknown): A
|
||||
end
|
||||
|
||||
declare class B
|
||||
function __mul(self, rhs: unknown): B
|
||||
end
|
||||
|
||||
declare class C
|
||||
function __mul(self, rhs: unknown): C
|
||||
end
|
||||
|
||||
declare class D
|
||||
function __mul(self, rhs: unknown): D
|
||||
end
|
||||
)");
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = mul<A | B | C | D, A | B | C | D>
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "ensure_equivalence_with_distributivity")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
loadDefinition(R"(
|
||||
declare class A
|
||||
function __mul(self, rhs: unknown): A
|
||||
end
|
||||
|
||||
declare class B
|
||||
function __mul(self, rhs: unknown): B
|
||||
end
|
||||
|
||||
declare class C
|
||||
function __mul(self, rhs: unknown): C
|
||||
end
|
||||
|
||||
declare class D
|
||||
function __mul(self, rhs: unknown): D
|
||||
end
|
||||
)");
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = mul<A | B, C | D>
|
||||
type U = mul<A, C> | mul<A, D> | mul<B, C> | mul<B, D>
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireTypeAlias("T")) == "A | B");
|
||||
CHECK(toString(requireTypeAlias("U")) == "A | A | B | B");
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -32,15 +32,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// Bug: We do not simplify at the right time
|
||||
CHECK_EQ("any?", toString(requireType("a")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ(builtinTypes->anyType, requireType("a"));
|
||||
}
|
||||
CHECK(builtinTypes->anyType == requireType("a"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any2")
|
||||
@ -58,15 +50,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_returns_any2")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// Bug: We do not simplify at the right time
|
||||
CHECK_EQ("any?", toString(requireType("a")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("any", toString(requireType("a")));
|
||||
}
|
||||
CHECK("any" == toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any")
|
||||
@ -82,15 +66,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// Bug: We do not simplify at the right time
|
||||
CHECK_EQ("any?", toString(requireType("a")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("any", toString(requireType("a")));
|
||||
}
|
||||
CHECK("any" == toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any2")
|
||||
@ -104,17 +80,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any2")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// Bug: We do not simplify at the right time
|
||||
CHECK_EQ("any?", toString(requireType("a")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("any", toString(requireType("a")));
|
||||
}
|
||||
CHECK("any" == toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any_pack")
|
||||
@ -130,15 +96,7 @@ TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_any_pack")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// Bug: We do not simplify at the right time
|
||||
CHECK_EQ("any?", toString(requireType("a")));
|
||||
}
|
||||
else
|
||||
{
|
||||
CHECK_EQ("any", toString(requireType("a")));
|
||||
}
|
||||
CHECK("any" == toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "for_in_loop_iterator_is_error")
|
||||
|
@ -1582,7 +1582,7 @@ TEST_CASE_FIXTURE(Fixture, "inferred_higher_order_functions_are_quantified_at_th
|
||||
if (!result.errors.empty())
|
||||
{
|
||||
for (const auto& e : result.errors)
|
||||
printf("%s %s: %s\n", e.moduleName.c_str(), toString(e.location).c_str(), toString(e).c_str());
|
||||
MESSAGE(e.moduleName << " " << toString(e.location) << ": " << toString(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -433,9 +433,53 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "varlist_declared_by_for_in_loop_should_be_fr
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
auto err = get<TypeMismatch>(result.errors[0]);
|
||||
CHECK(err != nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "iter_constraint_before_loop_body")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local T = {
|
||||
fields = {},
|
||||
}
|
||||
|
||||
function f()
|
||||
for u, v in pairs(T.fields) do
|
||||
T.fields[u] = nil
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "rbxl_place_file_crash_for_wrong_constraints")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local VehicleParameters = {
|
||||
-- These are default values in the case the package structure is broken
|
||||
StrutSpringStiffnessFront = 28000,
|
||||
}
|
||||
|
||||
local function updateFromConfiguration()
|
||||
for property, value in pairs(VehicleParameters) do
|
||||
VehicleParameters[property] = value
|
||||
end
|
||||
end
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "properly_infer_iteratee_is_a_free_table")
|
||||
{
|
||||
// In this case, we cannot know the element type of the table {}. It could be anything.
|
||||
|
@ -732,7 +732,11 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "and_binexps_dont_unify")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
// This infers a type for `t` of `{unknown}`, and so it makes sense that `t[1].test` would error.
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
else
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operators")
|
||||
|
@ -101,4 +101,14 @@ TEST_CASE("singleton_types")
|
||||
CHECK(result.errors.empty());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "property_of_buffers")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local b = buffer.create(100)
|
||||
print(b.foo)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -44,6 +44,20 @@ TEST_CASE_FIXTURE(Fixture, "string_singletons")
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "string_singleton_function_call")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local x = "a"
|
||||
function f(x: "a") end
|
||||
f(x)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bool_singletons_mismatch")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -2462,10 +2462,7 @@ local x: {number} | number | string
|
||||
local y = #x
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "dont_hang_when_trying_to_look_up_in_cyclic_metatable_index")
|
||||
@ -2973,7 +2970,7 @@ c = b
|
||||
|
||||
const TableType* ttv = get<TableType>(*ty);
|
||||
REQUIRE(ttv);
|
||||
CHECK(ttv->instantiatedTypeParams.empty());
|
||||
CHECK(0 == ttv->instantiatedTypeParams.size());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_indexing_error_location")
|
||||
@ -4355,19 +4352,6 @@ TEST_CASE_FIXTURE(Fixture, "mymovie_read_write_tables_bug_2")
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "setindexer_always_transmute")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(x)
|
||||
(5)[5] = x
|
||||
end
|
||||
)");
|
||||
|
||||
CHECK_EQ("(*error-type*) -> ()", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "instantiated_metatable_frozen_table_clone_mutation")
|
||||
{
|
||||
ScopedFastFlag luauMetatableInstantiationCloneCheck{FFlag::LuauMetatableInstantiationCloneCheck, true};
|
||||
@ -4412,6 +4396,21 @@ TEST_CASE_FIXTURE(Fixture, "setprop_on_a_mutating_local_in_both_loops_and_functi
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cant_index_this")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local a: number = 9
|
||||
a[18] = "tomfoolery"
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
NotATable* notATable = get<NotATable>(result.errors[0]);
|
||||
REQUIRE(notATable);
|
||||
|
||||
CHECK("number" == toString(notATable->ty));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "setindexer_multiple_tables_intersection")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
@ -4423,8 +4422,8 @@ TEST_CASE_FIXTURE(Fixture, "setindexer_multiple_tables_intersection")
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("({ [string]: number } & { [thread]: boolean }, boolean | number) -> ()" == toString(requireType("f")));
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
CHECK("({ [string]: number } & { [thread]: boolean }, never) -> ()" == toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "insert_a_and_f_of_a_into_table_res_in_a_loop")
|
||||
|
@ -406,6 +406,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "prototyped_recursive_functions_but_has_futur
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK("((() -> ()) | number)?" == toString(requireType("f")));
|
||||
}
|
||||
|
||||
|
@ -606,13 +606,7 @@ TEST_CASE_FIXTURE(Fixture, "indexing_into_a_cyclic_union_doesnt_crash")
|
||||
end
|
||||
)");
|
||||
|
||||
// The old solver has a bug: It doesn't consider this goofy thing to be a
|
||||
// table. It's not really important. What's important is that we don't
|
||||
// crash, hang, or ICE.
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
else
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_union_write_indirect")
|
||||
|
@ -65,30 +65,6 @@ do
|
||||
a = table.move({[minI] = 100}, minI, minI, maxI)
|
||||
eqT(a, {[minI] = 100, [maxI] = 100})
|
||||
|
||||
-- moving small amount of elements (array/hash) using a wide range
|
||||
a = {}
|
||||
table.move({1, 2, 3, 4, 5}, -100000000, 100000000, -100000000, a)
|
||||
eqT(a, {1, 2, 3, 4, 5})
|
||||
|
||||
a = {}
|
||||
table.move({1, 2}, -100000000, 100000000, 0, a)
|
||||
eqT(a, {[100000001] = 1, [100000002] = 2})
|
||||
|
||||
-- hash part copy
|
||||
a = {}
|
||||
table.move({[-1000000] = 1, [-100] = 2, [100] = 3, [100000] = 4}, -100000000, 100000000, 0, a)
|
||||
eqT(a, {[99000000] = 1, [99999900] = 2, [100000100] = 3, [100100000] = 4})
|
||||
|
||||
-- precise hash part bounds
|
||||
a = {}
|
||||
table.move({[-100000000 - 1] = -1, [-100000000] = 1, [-100] = 2, [100] = 3, [100000000] = 4, [100000000 + 1] = -1}, -100000000, 100000000, 0, a)
|
||||
eqT(a, {[0] = 1, [99999900] = 2, [100000100] = 3, [200000000] = 4})
|
||||
|
||||
-- no integer undeflow in corner hash part case
|
||||
a = {}
|
||||
table.move({[minI] = 100, [-100] = 2}, minI, minI + 100000000, minI, a)
|
||||
eqT(a, {[minI] = 100})
|
||||
|
||||
-- hash part skips array slice
|
||||
a = {}
|
||||
table.move({[-1] = 1, [0] = 2, [1] = 3, [2] = 4}, -1, 3, 1, a)
|
||||
@ -97,6 +73,19 @@ do
|
||||
a = {}
|
||||
table.move({[-1] = 1, [0] = 2, [1] = 3, [2] = 4, [10] = 5, [100] = 6, [1000] = 7}, -1, 3, 1, a)
|
||||
eqT(a, {[1] = 1, [2] = 2, [3] = 3, [4] = 4})
|
||||
|
||||
-- moving ranges containing nil values into tables with values
|
||||
a = {1, 2, 3, 4, 5}
|
||||
table.move({10}, 1, 3, 2, a)
|
||||
eqT(a, {1, 10, nil, nil, 5})
|
||||
|
||||
a = {1, 2, 3, 4, 5}
|
||||
table.move({10}, -1, 1, 2, a)
|
||||
eqT(a, {1, nil, nil, 10, 5})
|
||||
|
||||
a = {[-1000] = 1, [1000] = 2, [1] = 3}
|
||||
table.move({10}, -1000, 1000, -1000, a)
|
||||
eqT(a, {10})
|
||||
end
|
||||
|
||||
checkerror("too many", table.move, {}, 0, maxI, 1)
|
||||
|
@ -208,6 +208,35 @@ end
|
||||
|
||||
assert(pcall(fuzzfail21) == false)
|
||||
|
||||
local function fuzzfail22(...)
|
||||
local _ = {false,},true,...,l0
|
||||
while _ do
|
||||
_ = true,{unpack(0,_),},l0
|
||||
_.n126 = nil
|
||||
_ = {not _,_=not _,n0=_,_,n0=not _,},_ < _
|
||||
return _ > _
|
||||
end
|
||||
return `""`
|
||||
end
|
||||
|
||||
assert(pcall(fuzzfail22) == false)
|
||||
|
||||
local function fuzzfail23(...)
|
||||
local _ = {false,},_,...,l0
|
||||
while _ do
|
||||
_ = true,{unpack(_),},l0
|
||||
_ = {{[_]=nil,_=not _,_,true,_=nil,},not _,not _,_,bxor=- _,}
|
||||
do end
|
||||
break
|
||||
end
|
||||
do end
|
||||
local _ = _,true
|
||||
do end
|
||||
local _ = _,true
|
||||
end
|
||||
|
||||
assert(pcall(fuzzfail23) == false)
|
||||
|
||||
local function arraySizeInv1()
|
||||
local t = {1, 2, nil, nil, nil, nil, nil, nil, nil, true}
|
||||
|
||||
|
@ -71,7 +71,6 @@ GenericsTests.no_stack_overflow_from_quantifying
|
||||
GenericsTests.properties_can_be_instantiated_polytypes
|
||||
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
|
||||
GenericsTests.self_recursive_instantiated_param
|
||||
IntersectionTypes.CLI-44817
|
||||
IntersectionTypes.error_detailed_intersection_all
|
||||
IntersectionTypes.error_detailed_intersection_part
|
||||
IntersectionTypes.intersect_bool_and_false
|
||||
@ -134,11 +133,8 @@ RefinementTest.call_an_incompatible_function_after_using_typeguard
|
||||
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
||||
RefinementTest.discriminate_from_isa_of_x
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.free_type_is_equal_to_an_lvalue
|
||||
RefinementTest.globals_can_be_narrowed_too
|
||||
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||
RefinementTest.luau_polyfill_isindexkey_refine_conjunction
|
||||
RefinementTest.luau_polyfill_isindexkey_refine_conjunction_variant
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_param_that_got_resolved_during_constraint_solving_stage
|
||||
RefinementTest.refine_a_property_of_some_global
|
||||
@ -157,7 +153,6 @@ TableTests.a_free_shape_can_turn_into_a_scalar_if_it_is_compatible
|
||||
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||
TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict_mode
|
||||
TableTests.array_factory_function
|
||||
TableTests.cannot_augment_sealed_table
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer2
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer3
|
||||
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
||||
@ -181,20 +176,18 @@ TableTests.generalize_table_argument
|
||||
TableTests.generic_table_instantiation_potential_regression
|
||||
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||
TableTests.indexers_get_quantified_too
|
||||
TableTests.inequality_operators_imply_exactly_matching_types
|
||||
TableTests.infer_array
|
||||
TableTests.infer_indexer_from_array_like_table
|
||||
TableTests.infer_indexer_from_its_variable_type_and_unifiable
|
||||
TableTests.inferred_return_type_of_free_table
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
|
||||
TableTests.length_operator_union
|
||||
TableTests.less_exponential_blowup_please
|
||||
TableTests.meta_add
|
||||
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
|
||||
@ -202,7 +195,6 @@ 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
|
||||
@ -210,7 +202,6 @@ TableTests.quantify_even_that_table_was_never_exported_at_all
|
||||
TableTests.quantify_metatables_of_metatables_of_table
|
||||
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
||||
TableTests.recursive_metatable_type_call
|
||||
TableTests.refined_thing_can_be_an_array
|
||||
TableTests.right_table_missing_key2
|
||||
TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||
@ -228,12 +219,10 @@ TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2
|
||||
TableTests.table_unification_4
|
||||
TableTests.table_unifies_into_map
|
||||
TableTests.table_writes_introduce_write_properties
|
||||
TableTests.type_mismatch_on_massive_table_is_cut_short
|
||||
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
|
||||
@ -274,6 +263,7 @@ 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.infer_through_group_expr
|
||||
@ -285,9 +275,10 @@ TypeInfer.type_infer_recursion_limit_no_ice
|
||||
TypeInfer.type_infer_recursion_limit_normalizer
|
||||
TypeInfer.unify_nearly_identical_recursive_types
|
||||
TypeInferAnyError.can_subscript_any
|
||||
TypeInferAnyError.for_in_loop_iterator_is_error
|
||||
TypeInferAnyError.for_in_loop_iterator_is_error2
|
||||
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
||||
TypeInferAnyError.for_in_loop_iterator_is_any
|
||||
TypeInferAnyError.for_in_loop_iterator_is_any2
|
||||
TypeInferAnyError.for_in_loop_iterator_is_any_pack
|
||||
TypeInferAnyError.for_in_loop_iterator_returns_any2
|
||||
TypeInferClasses.callable_classes
|
||||
TypeInferClasses.cannot_unify_class_instance_with_primitive
|
||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||
@ -317,10 +308,8 @@ TypeInferFunctions.function_does_not_return_enough_values
|
||||
TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosing
|
||||
TypeInferFunctions.function_is_supertype_of_concrete_functions
|
||||
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
||||
TypeInferFunctions.fuzzer_missing_follow_in_ast_stat_fun
|
||||
TypeInferFunctions.generic_packs_are_not_variadic
|
||||
TypeInferFunctions.higher_order_function_2
|
||||
TypeInferFunctions.higher_order_function_3
|
||||
TypeInferFunctions.higher_order_function_4
|
||||
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
|
||||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
||||
@ -338,7 +327,6 @@ TypeInferFunctions.occurs_check_failure_in_function_return_type
|
||||
TypeInferFunctions.other_things_are_not_related_to_function
|
||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible
|
||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
|
||||
TypeInferFunctions.regex_benchmark_string_format_minimization
|
||||
TypeInferFunctions.report_exiting_without_return_nonstrict
|
||||
TypeInferFunctions.return_type_by_overload
|
||||
TypeInferFunctions.tf_suggest_return_type
|
||||
@ -370,9 +358,7 @@ TypeInferLoops.loop_iter_trailing_nil
|
||||
TypeInferLoops.loop_typecheck_crash_on_empty_optional
|
||||
TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||
TypeInferLoops.repeat_loop
|
||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||
TypeInferLoops.while_loop
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
TypeInferModules.require
|
||||
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
|
||||
@ -396,7 +382,6 @@ TypeInferOperators.typecheck_unary_len_error
|
||||
TypeInferOperators.typecheck_unary_minus_error
|
||||
TypeInferOperators.UnknownGlobalCompoundAssign
|
||||
TypeInferPrimitives.CheckMethodsOfNumber
|
||||
TypeInferPrimitives.string_index
|
||||
TypeInferUnknownNever.assign_to_local_which_is_never
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
||||
@ -414,6 +399,7 @@ TypeSingletons.error_detailed_tagged_union_mismatch_string
|
||||
TypeSingletons.overloaded_function_call_with_singletons_mismatch
|
||||
TypeSingletons.return_type_of_f_is_not_widened
|
||||
TypeSingletons.singletons_stick_around_under_assignment
|
||||
TypeSingletons.string_singleton_function_call
|
||||
TypeSingletons.table_properties_type_error_escapes
|
||||
TypeSingletons.widen_the_supertype_if_it_is_free_and_subtype_has_singleton
|
||||
TypeStatesTest.typestates_preserve_error_suppression_properties
|
||||
@ -423,7 +409,6 @@ UnionTypes.generic_function_with_optional_arg
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.less_greedy_unification_with_union_types
|
||||
UnionTypes.optional_arguments_table
|
||||
UnionTypes.optional_length_error
|
||||
UnionTypes.optional_union_functions
|
||||
UnionTypes.optional_union_members
|
||||
UnionTypes.optional_union_methods
|
||||
|
Loading…
Reference in New Issue
Block a user