mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 13:00:38 +00:00
Sync to upstream/release/576 (#928)
* `ClassType` can now have an indexer defined on it. This allows custom types to be used in `t[x]` expressions. * Fixed search for closest executable breakpoint line. Previously, breakpoints might have been skipped in `else` blocks at the end of a function * Fixed how unification is performed for two optional types `a? <: b?`, previously it might have unified either 'a' or 'b' with 'nil'. Note that this fix is not enabled by default yet (see the list in `ExperimentalFlags.h`) In the new type solver, a concept of 'Type Families' has been introduced. Type families can be thought of as type aliases with custom type inference/reduction logic included with them. For example, we can have an `Add<T, U>` type family that will resolve the type that is the result of adding two values together. This will help type inference to figure out what 'T' and 'U' might be when explicit type annotations are not provided. In this update we don't define any type families, but they will be added in the near future. It is also possible for Luau embedders to define their own type families in the global/environment scope. Other changes include: * Fixed scope used to find out which generic types should be included in the function generic type list * Fixed a crash after cyclic bound types were created during unification And in native code generation (jit): * Use of arm64 target on M1 now requires macOS 13 * Entry into native code has been optimized. This is especially important for coroutine call/pcall performance as they involve going through a C call frame * LOP_LOADK(X) translation into IR has been improved to enable type tag/constant propagation * arm64 can use integer immediate values to synthesize floating-point values * x64 assembler removes duplicate 64bit numbers from the data section to save space * Linux `perf` can now be used to profile native Luau code (when running with --codegen-perf CLI argument)
This commit is contained in:
parent
8d8c7974f5
commit
97965c7c0a
62 changed files with 2869 additions and 276 deletions
|
@ -198,9 +198,26 @@ struct UnpackConstraint
|
||||||
TypePackId sourcePack;
|
TypePackId sourcePack;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint,
|
// ty ~ reduce ty
|
||||||
BinaryConstraint, IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint,
|
//
|
||||||
HasPropConstraint, SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint>;
|
// Try to reduce ty, if it is a TypeFamilyInstanceType. Otherwise, do nothing.
|
||||||
|
struct ReduceConstraint
|
||||||
|
{
|
||||||
|
TypeId ty;
|
||||||
|
};
|
||||||
|
|
||||||
|
// tp ~ reduce tp
|
||||||
|
//
|
||||||
|
// Analogous to ReduceConstraint, but for type packs.
|
||||||
|
struct ReducePackConstraint
|
||||||
|
{
|
||||||
|
TypePackId tp;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ConstraintV =
|
||||||
|
Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, UnaryConstraint, BinaryConstraint,
|
||||||
|
IterableConstraint, NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, PrimitiveTypeConstraint, HasPropConstraint,
|
||||||
|
SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, ReduceConstraint, ReducePackConstraint>;
|
||||||
|
|
||||||
struct Constraint
|
struct Constraint
|
||||||
{
|
{
|
||||||
|
|
|
@ -121,6 +121,8 @@ struct ConstraintSolver
|
||||||
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
bool tryDispatch(const SetIndexerConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const SingletonOrTopTypeConstraint& c, NotNull<const Constraint> constraint);
|
||||||
bool tryDispatch(const UnpackConstraint& c, NotNull<const Constraint> constraint);
|
bool tryDispatch(const UnpackConstraint& 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);
|
||||||
|
|
||||||
// for a, ... in some_table do
|
// for a, ... in some_table do
|
||||||
// also handles __iter metamethod
|
// also handles __iter metamethod
|
||||||
|
|
|
@ -329,12 +329,27 @@ struct DynamicPropertyLookupOnClassesUnsafe
|
||||||
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const;
|
bool operator==(const DynamicPropertyLookupOnClassesUnsafe& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
|
struct UninhabitedTypeFamily
|
||||||
DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
|
{
|
||||||
IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
|
TypeId ty;
|
||||||
CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
|
|
||||||
DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
|
bool operator==(const UninhabitedTypeFamily& rhs) const;
|
||||||
TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe>;
|
};
|
||||||
|
|
||||||
|
struct UninhabitedTypePackFamily
|
||||||
|
{
|
||||||
|
TypePackId tp;
|
||||||
|
|
||||||
|
bool operator==(const UninhabitedTypePackFamily& rhs) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
using TypeErrorData =
|
||||||
|
Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods, DuplicateTypeDefinition,
|
||||||
|
CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire, IncorrectGenericParameterCount, SyntaxError,
|
||||||
|
CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError, CannotCallNonFunction, ExtraInformation,
|
||||||
|
DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning, DuplicateGenericParameter,
|
||||||
|
CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty, TypesAreUnrelated,
|
||||||
|
NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily, UninhabitedTypePackFamily>;
|
||||||
|
|
||||||
struct TypeErrorSummary
|
struct TypeErrorSummary
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -10,6 +13,29 @@ struct TypeArena;
|
||||||
struct Scope;
|
struct Scope;
|
||||||
|
|
||||||
void quantify(TypeId ty, TypeLevel level);
|
void quantify(TypeId ty, TypeLevel level);
|
||||||
std::optional<TypeId> quantify(TypeArena* arena, TypeId ty, Scope* scope);
|
|
||||||
|
// TODO: This is eerily similar to the pattern that NormalizedClassType
|
||||||
|
// implements. We could, and perhaps should, merge them together.
|
||||||
|
template<typename K, typename V>
|
||||||
|
struct OrderedMap
|
||||||
|
{
|
||||||
|
std::vector<K> keys;
|
||||||
|
DenseHashMap<K, V> pairings{nullptr};
|
||||||
|
|
||||||
|
void push(K k, V v)
|
||||||
|
{
|
||||||
|
keys.push_back(k);
|
||||||
|
pairings[k] = v;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct QuantifierResult
|
||||||
|
{
|
||||||
|
TypeId result;
|
||||||
|
OrderedMap<TypeId, TypeId> insertedGenerics;
|
||||||
|
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope);
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -19,6 +19,10 @@ struct PendingType
|
||||||
// The pending Type state.
|
// The pending Type state.
|
||||||
Type pending;
|
Type pending;
|
||||||
|
|
||||||
|
// On very rare occasions, we need to delete an entry from the TxnLog.
|
||||||
|
// DenseHashMap does not afford that so we note its deadness here.
|
||||||
|
bool dead = false;
|
||||||
|
|
||||||
explicit PendingType(Type state)
|
explicit PendingType(Type state)
|
||||||
: pending(std::move(state))
|
: pending(std::move(state))
|
||||||
{
|
{
|
||||||
|
@ -61,10 +65,11 @@ T* getMutable(PendingTypePack* pending)
|
||||||
// Log of what TypeIds we are rebinding, to be committed later.
|
// Log of what TypeIds we are rebinding, to be committed later.
|
||||||
struct TxnLog
|
struct TxnLog
|
||||||
{
|
{
|
||||||
TxnLog()
|
explicit TxnLog(bool useScopes = false)
|
||||||
: typeVarChanges(nullptr)
|
: typeVarChanges(nullptr)
|
||||||
, typePackChanges(nullptr)
|
, typePackChanges(nullptr)
|
||||||
, ownedSeen()
|
, ownedSeen()
|
||||||
|
, useScopes(useScopes)
|
||||||
, sharedSeen(&ownedSeen)
|
, sharedSeen(&ownedSeen)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -297,6 +302,12 @@ private:
|
||||||
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
void popSeen(TypeOrPackId lhs, TypeOrPackId rhs);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// There is one spot in the code where TxnLog has to reconcile collisions
|
||||||
|
// between parallel logs. In that codepath, we have to work out which of two
|
||||||
|
// FreeTypes subsumes the other. If useScopes is false, the TypeLevel is
|
||||||
|
// used. Else we use the embedded Scope*.
|
||||||
|
bool useScopes = false;
|
||||||
|
|
||||||
// Used to avoid infinite recursion when types are cyclic.
|
// Used to avoid infinite recursion when types are cyclic.
|
||||||
// Shared with all the descendent TxnLogs.
|
// Shared with all the descendent TxnLogs.
|
||||||
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen;
|
std::vector<std::pair<TypeOrPackId, TypeOrPackId>>* sharedSeen;
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
|
|
||||||
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
LUAU_FASTINT(LuauTableTypeMaximumStringifierLength)
|
||||||
LUAU_FASTINT(LuauTypeMaximumStringifierLength)
|
LUAU_FASTINT(LuauTypeMaximumStringifierLength)
|
||||||
|
LUAU_FASTFLAG(LuauTypecheckClassTypeIndexers)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -31,6 +32,8 @@ struct TypeArena;
|
||||||
struct Scope;
|
struct Scope;
|
||||||
using ScopePtr = std::shared_ptr<Scope>;
|
using ScopePtr = std::shared_ptr<Scope>;
|
||||||
|
|
||||||
|
struct TypeFamily;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* There are three kinds of type variables:
|
* There are three kinds of type variables:
|
||||||
* - `Free` variables are metavariables, which stand for unconstrained types.
|
* - `Free` variables are metavariables, which stand for unconstrained types.
|
||||||
|
@ -489,6 +492,7 @@ struct ClassType
|
||||||
Tags tags;
|
Tags tags;
|
||||||
std::shared_ptr<ClassUserData> userData;
|
std::shared_ptr<ClassUserData> userData;
|
||||||
ModuleName definitionModuleName;
|
ModuleName definitionModuleName;
|
||||||
|
std::optional<TableIndexer> indexer;
|
||||||
|
|
||||||
ClassType(Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags,
|
ClassType(Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags,
|
||||||
std::shared_ptr<ClassUserData> userData, ModuleName definitionModuleName)
|
std::shared_ptr<ClassUserData> userData, ModuleName definitionModuleName)
|
||||||
|
@ -501,6 +505,35 @@ struct ClassType
|
||||||
, definitionModuleName(definitionModuleName)
|
, definitionModuleName(definitionModuleName)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClassType(Name name, Props props, std::optional<TypeId> parent, std::optional<TypeId> metatable, Tags tags,
|
||||||
|
std::shared_ptr<ClassUserData> userData, ModuleName definitionModuleName, std::optional<TableIndexer> indexer)
|
||||||
|
: name(name)
|
||||||
|
, props(props)
|
||||||
|
, parent(parent)
|
||||||
|
, metatable(metatable)
|
||||||
|
, tags(tags)
|
||||||
|
, userData(userData)
|
||||||
|
, definitionModuleName(definitionModuleName)
|
||||||
|
, indexer(indexer)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauTypecheckClassTypeIndexers);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of a type family that has not yet been reduced to a more concrete
|
||||||
|
* type. The constraint solver receives a constraint to reduce each
|
||||||
|
* TypeFamilyInstanceType to a concrete type. A design detail is important to
|
||||||
|
* note here: the parameters for this instantiation of the type family are
|
||||||
|
* contained within this type, so that they can be substituted.
|
||||||
|
*/
|
||||||
|
struct TypeFamilyInstanceType
|
||||||
|
{
|
||||||
|
NotNull<TypeFamily> family;
|
||||||
|
|
||||||
|
std::vector<TypeId> typeArguments;
|
||||||
|
std::vector<TypePackId> packArguments;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TypeFun
|
struct TypeFun
|
||||||
|
@ -640,8 +673,9 @@ struct NegationType
|
||||||
|
|
||||||
using ErrorType = Unifiable::Error;
|
using ErrorType = Unifiable::Error;
|
||||||
|
|
||||||
using TypeVariant = Unifiable::Variant<TypeId, FreeType, GenericType, PrimitiveType, BlockedType, PendingExpansionType, SingletonType, FunctionType,
|
using TypeVariant =
|
||||||
TableType, MetatableType, ClassType, AnyType, UnionType, IntersectionType, LazyType, UnknownType, NeverType, NegationType>;
|
Unifiable::Variant<TypeId, FreeType, GenericType, PrimitiveType, BlockedType, PendingExpansionType, SingletonType, FunctionType, TableType,
|
||||||
|
MetatableType, ClassType, AnyType, UnionType, IntersectionType, LazyType, UnknownType, NeverType, NegationType, TypeFamilyInstanceType>;
|
||||||
|
|
||||||
struct Type final
|
struct Type final
|
||||||
{
|
{
|
||||||
|
|
115
Analysis/include/Luau/TypeFamily.h
Normal file
115
Analysis/include/Luau/TypeFamily.h
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Luau/Error.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
|
#include "Luau/Variant.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct Type;
|
||||||
|
using TypeId = const Type*;
|
||||||
|
|
||||||
|
struct TypePackVar;
|
||||||
|
using TypePackId = const TypePackVar*;
|
||||||
|
|
||||||
|
struct TypeArena;
|
||||||
|
struct BuiltinTypes;
|
||||||
|
struct TxnLog;
|
||||||
|
|
||||||
|
/// Represents a reduction result, which may have successfully reduced the type,
|
||||||
|
/// may have concretely failed to reduce the type, or may simply be stuck
|
||||||
|
/// without more information.
|
||||||
|
template<typename Ty>
|
||||||
|
struct TypeFamilyReductionResult
|
||||||
|
{
|
||||||
|
/// The result of the reduction, if any. If this is nullopt, the family
|
||||||
|
/// could not be reduced.
|
||||||
|
std::optional<Ty> result;
|
||||||
|
/// Whether the result is uninhabited: whether we know, unambiguously and
|
||||||
|
/// permanently, whether this type family reduction results in an
|
||||||
|
/// uninhabitable type. This will trigger an error to be reported.
|
||||||
|
bool uninhabited;
|
||||||
|
/// Any types that need to be progressed or mutated before the reduction may
|
||||||
|
/// proceed.
|
||||||
|
std::vector<TypeId> blockedTypes;
|
||||||
|
/// Any type packs that need to be progressed or mutated before the
|
||||||
|
/// reduction may proceed.
|
||||||
|
std::vector<TypePackId> blockedPacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a type function that may be applied to map a series of types and
|
||||||
|
/// type packs to a single output type.
|
||||||
|
struct TypeFamily
|
||||||
|
{
|
||||||
|
/// The human-readable name of the type family. Used to stringify instance
|
||||||
|
/// types.
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
/// The reducer function for the type family.
|
||||||
|
std::function<TypeFamilyReductionResult<TypeId>(
|
||||||
|
std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>, NotNull<const TxnLog> log)>
|
||||||
|
reducer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a type function that may be applied to map a series of types and
|
||||||
|
/// type packs to a single output type pack.
|
||||||
|
struct TypePackFamily
|
||||||
|
{
|
||||||
|
/// The human-readable name of the type pack family. Used to stringify
|
||||||
|
/// instance packs.
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
/// The reducer function for the type pack family.
|
||||||
|
std::function<TypeFamilyReductionResult<TypePackId>(
|
||||||
|
std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeArena>, NotNull<BuiltinTypes>, NotNull<const TxnLog> log)>
|
||||||
|
reducer;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FamilyGraphReductionResult
|
||||||
|
{
|
||||||
|
ErrorVec errors;
|
||||||
|
DenseHashSet<TypeId> blockedTypes{nullptr};
|
||||||
|
DenseHashSet<TypePackId> blockedPacks{nullptr};
|
||||||
|
DenseHashSet<TypeId> reducedTypes{nullptr};
|
||||||
|
DenseHashSet<TypePackId> reducedPacks{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to reduce all instances of any type or type pack family in the type
|
||||||
|
* graph provided.
|
||||||
|
*
|
||||||
|
* @param entrypoint the entry point to the type graph.
|
||||||
|
* @param location the location the reduction is occurring at; used to populate
|
||||||
|
* type errors.
|
||||||
|
* @param arena an arena to allocate types into.
|
||||||
|
* @param builtins the built-in types.
|
||||||
|
* @param log a TxnLog to use. If one is provided, substitution will take place
|
||||||
|
* against the TxnLog, otherwise substitutions will directly mutate the type
|
||||||
|
* graph. Do not provide the empty TxnLog, as a result.
|
||||||
|
*/
|
||||||
|
FamilyGraphReductionResult reduceFamilies(
|
||||||
|
TypeId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, TxnLog* log = nullptr, bool force = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to reduce all instances of any type or type pack family in the type
|
||||||
|
* graph provided.
|
||||||
|
*
|
||||||
|
* @param entrypoint the entry point to the type graph.
|
||||||
|
* @param location the location the reduction is occurring at; used to populate
|
||||||
|
* type errors.
|
||||||
|
* @param arena an arena to allocate types into.
|
||||||
|
* @param builtins the built-in types.
|
||||||
|
* @param log a TxnLog to use. If one is provided, substitution will take place
|
||||||
|
* against the TxnLog, otherwise substitutions will directly mutate the type
|
||||||
|
* graph. Do not provide the empty TxnLog, as a result.
|
||||||
|
*/
|
||||||
|
FamilyGraphReductionResult reduceFamilies(
|
||||||
|
TypePackId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, TxnLog* log = nullptr, bool force = false);
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -12,11 +12,13 @@ namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
struct TypeArena;
|
struct TypeArena;
|
||||||
|
struct TypePackFamily;
|
||||||
struct TxnLog;
|
struct TxnLog;
|
||||||
|
|
||||||
struct TypePack;
|
struct TypePack;
|
||||||
struct VariadicTypePack;
|
struct VariadicTypePack;
|
||||||
struct BlockedTypePack;
|
struct BlockedTypePack;
|
||||||
|
struct TypeFamilyInstanceTypePack;
|
||||||
|
|
||||||
struct TypePackVar;
|
struct TypePackVar;
|
||||||
using TypePackId = const TypePackVar*;
|
using TypePackId = const TypePackVar*;
|
||||||
|
@ -50,10 +52,10 @@ struct GenericTypePack
|
||||||
};
|
};
|
||||||
|
|
||||||
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
using BoundTypePack = Unifiable::Bound<TypePackId>;
|
||||||
|
|
||||||
using ErrorTypePack = Unifiable::Error;
|
using ErrorTypePack = Unifiable::Error;
|
||||||
|
|
||||||
using TypePackVariant = Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack>;
|
using TypePackVariant =
|
||||||
|
Unifiable::Variant<TypePackId, FreeTypePack, GenericTypePack, TypePack, VariadicTypePack, BlockedTypePack, TypeFamilyInstanceTypePack>;
|
||||||
|
|
||||||
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode
|
/* A TypePack is a rope-like string of TypeIds. We use this structure to encode
|
||||||
* notions like packs of unknown length and packs of any length, as well as more
|
* notions like packs of unknown length and packs of any length, as well as more
|
||||||
|
@ -83,6 +85,17 @@ struct BlockedTypePack
|
||||||
static size_t nextIndex;
|
static size_t nextIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analogous to a TypeFamilyInstanceType.
|
||||||
|
*/
|
||||||
|
struct TypeFamilyInstanceTypePack
|
||||||
|
{
|
||||||
|
NotNull<TypePackFamily> family;
|
||||||
|
|
||||||
|
std::vector<TypeId> typeArguments;
|
||||||
|
std::vector<TypePackId> packArguments;
|
||||||
|
};
|
||||||
|
|
||||||
struct TypePackVar
|
struct TypePackVar
|
||||||
{
|
{
|
||||||
explicit TypePackVar(const TypePackVariant& ty);
|
explicit TypePackVar(const TypePackVariant& ty);
|
||||||
|
|
|
@ -64,9 +64,11 @@ struct Unifier
|
||||||
Variance variance = Covariant;
|
Variance variance = Covariant;
|
||||||
bool normalize = true; // Normalize unions and intersections if necessary
|
bool normalize = true; // Normalize unions and intersections if necessary
|
||||||
bool checkInhabited = true; // Normalize types to check if they are inhabited
|
bool checkInhabited = true; // Normalize types to check if they are inhabited
|
||||||
bool useScopes = false; // If true, we use the scope hierarchy rather than TypeLevels
|
|
||||||
CountMismatch::Context ctx = CountMismatch::Arg;
|
CountMismatch::Context ctx = CountMismatch::Arg;
|
||||||
|
|
||||||
|
// If true, generics act as free types when unifying.
|
||||||
|
bool hideousFixMeGenericsAreActuallyFree = false;
|
||||||
|
|
||||||
UnifierSharedState& sharedState;
|
UnifierSharedState& sharedState;
|
||||||
|
|
||||||
// When the Unifier is forced to unify two blocked types (or packs), they
|
// When the Unifier is forced to unify two blocked types (or packs), they
|
||||||
|
@ -78,6 +80,10 @@ struct Unifier
|
||||||
Unifier(
|
Unifier(
|
||||||
NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr);
|
NotNull<Normalizer> normalizer, Mode mode, NotNull<Scope> scope, const Location& location, Variance variance, TxnLog* parentLog = nullptr);
|
||||||
|
|
||||||
|
// Configure the Unifier to test for scope subsumption via embedded Scope
|
||||||
|
// pointers rather than TypeLevels.
|
||||||
|
void enableScopeTests();
|
||||||
|
|
||||||
// Test whether the two type vars unify. Never commits the result.
|
// Test whether the two type vars unify. Never commits the result.
|
||||||
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
ErrorVec canUnify(TypeId subTy, TypeId superTy);
|
||||||
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
ErrorVec canUnify(TypePackId subTy, TypePackId superTy, bool isFunctionCall = false);
|
||||||
|
@ -159,6 +165,9 @@ private:
|
||||||
|
|
||||||
// Available after regular type pack unification errors
|
// Available after regular type pack unification errors
|
||||||
std::optional<int> firstPackErrorPos;
|
std::optional<int> firstPackErrorPos;
|
||||||
|
|
||||||
|
// If true, we use the scope hierarchy rather than TypeLevels
|
||||||
|
bool useScopes = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp);
|
void promoteTypeLevels(TxnLog& log, const TypeArena* arena, TypeLevel minLevel, Scope* outerScope, bool useScope, TypePackId tp);
|
||||||
|
|
|
@ -159,6 +159,10 @@ struct GenericTypeVisitor
|
||||||
{
|
{
|
||||||
return visit(ty);
|
return visit(ty);
|
||||||
}
|
}
|
||||||
|
virtual bool visit(TypeId ty, const TypeFamilyInstanceType& tfit)
|
||||||
|
{
|
||||||
|
return visit(ty);
|
||||||
|
}
|
||||||
|
|
||||||
virtual bool visit(TypePackId tp)
|
virtual bool visit(TypePackId tp)
|
||||||
{
|
{
|
||||||
|
@ -192,6 +196,10 @@ struct GenericTypeVisitor
|
||||||
{
|
{
|
||||||
return visit(tp);
|
return visit(tp);
|
||||||
}
|
}
|
||||||
|
virtual bool visit(TypePackId tp, const TypeFamilyInstanceTypePack& tfitp)
|
||||||
|
{
|
||||||
|
return visit(tp);
|
||||||
|
}
|
||||||
|
|
||||||
void traverse(TypeId ty)
|
void traverse(TypeId ty)
|
||||||
{
|
{
|
||||||
|
@ -272,6 +280,15 @@ struct GenericTypeVisitor
|
||||||
|
|
||||||
if (ctv->metatable)
|
if (ctv->metatable)
|
||||||
traverse(*ctv->metatable);
|
traverse(*ctv->metatable);
|
||||||
|
|
||||||
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
|
{
|
||||||
|
if (ctv->indexer)
|
||||||
|
{
|
||||||
|
traverse(ctv->indexer->indexType);
|
||||||
|
traverse(ctv->indexer->indexResultType);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (auto atv = get<AnyType>(ty))
|
else if (auto atv = get<AnyType>(ty))
|
||||||
|
@ -327,6 +344,17 @@ struct GenericTypeVisitor
|
||||||
if (visit(ty, *ntv))
|
if (visit(ty, *ntv))
|
||||||
traverse(ntv->ty);
|
traverse(ntv->ty);
|
||||||
}
|
}
|
||||||
|
else if (auto tfit = get<TypeFamilyInstanceType>(ty))
|
||||||
|
{
|
||||||
|
if (visit(ty, *tfit))
|
||||||
|
{
|
||||||
|
for (TypeId p : tfit->typeArguments)
|
||||||
|
traverse(p);
|
||||||
|
|
||||||
|
for (TypePackId p : tfit->packArguments)
|
||||||
|
traverse(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(!"GenericTypeVisitor::traverse(TypeId) is not exhaustive!");
|
LUAU_ASSERT(!"GenericTypeVisitor::traverse(TypeId) is not exhaustive!");
|
||||||
|
|
||||||
|
@ -376,6 +404,17 @@ struct GenericTypeVisitor
|
||||||
}
|
}
|
||||||
else if (auto btp = get<BlockedTypePack>(tp))
|
else if (auto btp = get<BlockedTypePack>(tp))
|
||||||
visit(tp, *btp);
|
visit(tp, *btp);
|
||||||
|
else if (auto tfitp = get<TypeFamilyInstanceTypePack>(tp))
|
||||||
|
{
|
||||||
|
if (visit(tp, *tfitp))
|
||||||
|
{
|
||||||
|
for (TypeId t : tfitp->typeArguments)
|
||||||
|
traverse(t);
|
||||||
|
|
||||||
|
for (TypePackId t : tfitp->packArguments)
|
||||||
|
traverse(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(!"GenericTypeVisitor::traverse(TypePackId) is not exhaustive!");
|
LUAU_ASSERT(!"GenericTypeVisitor::traverse(TypePackId) is not exhaustive!");
|
||||||
|
|
|
@ -52,6 +52,12 @@ Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TableIndexer clone(const TableIndexer& indexer, TypeArena& dest, CloneState& cloneState)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(FFlag::LuauTypecheckClassTypeIndexers);
|
||||||
|
return TableIndexer{clone(indexer.indexType, dest, cloneState), clone(indexer.indexResultType, dest, cloneState)};
|
||||||
|
}
|
||||||
|
|
||||||
struct TypePackCloner;
|
struct TypePackCloner;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -98,6 +104,7 @@ struct TypeCloner
|
||||||
void operator()(const UnknownType& t);
|
void operator()(const UnknownType& t);
|
||||||
void operator()(const NeverType& t);
|
void operator()(const NeverType& t);
|
||||||
void operator()(const NegationType& t);
|
void operator()(const NegationType& t);
|
||||||
|
void operator()(const TypeFamilyInstanceType& t);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TypePackCloner
|
struct TypePackCloner
|
||||||
|
@ -171,6 +178,22 @@ struct TypePackCloner
|
||||||
if (t.tail)
|
if (t.tail)
|
||||||
destTp->tail = clone(*t.tail, dest, cloneState);
|
destTp->tail = clone(*t.tail, dest, cloneState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()(const TypeFamilyInstanceTypePack& t)
|
||||||
|
{
|
||||||
|
TypePackId cloned = dest.addTypePack(TypeFamilyInstanceTypePack{t.family, {}, {}});
|
||||||
|
TypeFamilyInstanceTypePack* destTp = getMutable<TypeFamilyInstanceTypePack>(cloned);
|
||||||
|
LUAU_ASSERT(destTp);
|
||||||
|
seenTypePacks[typePackId] = cloned;
|
||||||
|
|
||||||
|
destTp->typeArguments.reserve(t.typeArguments.size());
|
||||||
|
for (TypeId ty : t.typeArguments)
|
||||||
|
destTp->typeArguments.push_back(clone(ty, dest, cloneState));
|
||||||
|
|
||||||
|
destTp->packArguments.reserve(t.packArguments.size());
|
||||||
|
for (TypePackId tp : t.packArguments)
|
||||||
|
destTp->packArguments.push_back(clone(tp, dest, cloneState));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
@ -288,8 +311,16 @@ void TypeCloner::operator()(const TableType& t)
|
||||||
for (const auto& [name, prop] : t.props)
|
for (const auto& [name, prop] : t.props)
|
||||||
ttv->props[name] = clone(prop, dest, cloneState);
|
ttv->props[name] = clone(prop, dest, cloneState);
|
||||||
|
|
||||||
if (t.indexer)
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, cloneState), clone(t.indexer->indexResultType, dest, cloneState)};
|
{
|
||||||
|
if (t.indexer)
|
||||||
|
ttv->indexer = clone(*t.indexer, dest, cloneState);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (t.indexer)
|
||||||
|
ttv->indexer = TableIndexer{clone(t.indexer->indexType, dest, cloneState), clone(t.indexer->indexResultType, dest, cloneState)};
|
||||||
|
}
|
||||||
|
|
||||||
for (TypeId& arg : ttv->instantiatedTypeParams)
|
for (TypeId& arg : ttv->instantiatedTypeParams)
|
||||||
arg = clone(arg, dest, cloneState);
|
arg = clone(arg, dest, cloneState);
|
||||||
|
@ -327,6 +358,12 @@ void TypeCloner::operator()(const ClassType& t)
|
||||||
|
|
||||||
if (t.metatable)
|
if (t.metatable)
|
||||||
ctv->metatable = clone(*t.metatable, dest, cloneState);
|
ctv->metatable = clone(*t.metatable, dest, cloneState);
|
||||||
|
|
||||||
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
|
{
|
||||||
|
if (t.indexer)
|
||||||
|
ctv->indexer = clone(*t.indexer, dest, cloneState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TypeCloner::operator()(const AnyType& t)
|
void TypeCloner::operator()(const AnyType& t)
|
||||||
|
@ -389,6 +426,28 @@ void TypeCloner::operator()(const NegationType& t)
|
||||||
asMutable(result)->ty = NegationType{ty};
|
asMutable(result)->ty = NegationType{ty};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TypeCloner::operator()(const TypeFamilyInstanceType& t)
|
||||||
|
{
|
||||||
|
TypeId result = dest.addType(TypeFamilyInstanceType{
|
||||||
|
t.family,
|
||||||
|
{},
|
||||||
|
{},
|
||||||
|
});
|
||||||
|
|
||||||
|
seenTypes[typeId] = result;
|
||||||
|
|
||||||
|
TypeFamilyInstanceType* tfit = getMutable<TypeFamilyInstanceType>(result);
|
||||||
|
LUAU_ASSERT(tfit != nullptr);
|
||||||
|
|
||||||
|
tfit->typeArguments.reserve(t.typeArguments.size());
|
||||||
|
for (TypeId p : t.typeArguments)
|
||||||
|
tfit->typeArguments.push_back(clone(p, dest, cloneState));
|
||||||
|
|
||||||
|
tfit->packArguments.reserve(t.packArguments.size());
|
||||||
|
for (TypePackId p : t.packArguments)
|
||||||
|
tfit->packArguments.push_back(clone(p, dest, cloneState));
|
||||||
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
|
||||||
|
|
|
@ -728,6 +728,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocalFun
|
||||||
});
|
});
|
||||||
|
|
||||||
addConstraint(scope, std::move(c));
|
addConstraint(scope, std::move(c));
|
||||||
|
module->astTypes[function->func] = functionType;
|
||||||
|
|
||||||
return ControlFlow::None;
|
return ControlFlow::None;
|
||||||
}
|
}
|
||||||
|
@ -1475,7 +1476,7 @@ Inference ConstraintGraphBuilder::check(
|
||||||
Checkpoint endCheckpoint = checkpoint(this);
|
Checkpoint endCheckpoint = checkpoint(this);
|
||||||
|
|
||||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||||
NotNull<Constraint> gc = addConstraint(scope, expr->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
NotNull<Constraint> gc = addConstraint(sig.signatureScope, expr->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
||||||
|
|
||||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc](const ConstraintPtr& constraint) {
|
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc](const ConstraintPtr& constraint) {
|
||||||
gc->dependencies.emplace_back(constraint.get());
|
gc->dependencies.emplace_back(constraint.get());
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/Unifier.h"
|
#include "Luau/Unifier.h"
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolver, false);
|
||||||
LUAU_FASTFLAG(LuauRequirePathTrueModuleName)
|
LUAU_FASTFLAG(LuauRequirePathTrueModuleName)
|
||||||
|
@ -226,6 +227,32 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct InstantiationQueuer : TypeOnceVisitor
|
||||||
|
{
|
||||||
|
ConstraintSolver* solver;
|
||||||
|
NotNull<Scope> scope;
|
||||||
|
Location location;
|
||||||
|
|
||||||
|
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver)
|
||||||
|
: solver(solver)
|
||||||
|
, scope(scope)
|
||||||
|
, location(location)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const PendingExpansionType& petv) override
|
||||||
|
{
|
||||||
|
solver->pushConstraint(scope, location, TypeAliasExpansionConstraint{ty});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const TypeFamilyInstanceType& tfit) override
|
||||||
|
{
|
||||||
|
solver->pushConstraint(scope, location, ReduceConstraint{ty});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
ConstraintSolver::ConstraintSolver(NotNull<Normalizer> normalizer, NotNull<Scope> rootScope, std::vector<NotNull<Constraint>> constraints,
|
||||||
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
|
ModuleName moduleName, NotNull<ModuleResolver> moduleResolver, std::vector<RequireCycle> requireCycles, DcrLogger* logger)
|
||||||
: arena(normalizer->arena)
|
: arena(normalizer->arena)
|
||||||
|
@ -441,6 +468,10 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
||||||
success = tryDispatch(*sottc, constraint);
|
success = tryDispatch(*sottc, constraint);
|
||||||
else if (auto uc = get<UnpackConstraint>(*constraint))
|
else if (auto uc = get<UnpackConstraint>(*constraint))
|
||||||
success = tryDispatch(*uc, constraint);
|
success = tryDispatch(*uc, constraint);
|
||||||
|
else if (auto rc = get<ReduceConstraint>(*constraint))
|
||||||
|
success = tryDispatch(*rc, constraint, force);
|
||||||
|
else if (auto rpc = get<ReducePackConstraint>(*constraint))
|
||||||
|
success = tryDispatch(*rpc, constraint, force);
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(false);
|
LUAU_ASSERT(false);
|
||||||
|
|
||||||
|
@ -479,13 +510,19 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
||||||
else if (get<PendingExpansionType>(generalizedType))
|
else if (get<PendingExpansionType>(generalizedType))
|
||||||
return block(generalizedType, constraint);
|
return block(generalizedType, constraint);
|
||||||
|
|
||||||
std::optional<TypeId> generalized = quantify(arena, c.sourceType, constraint->scope);
|
std::optional<QuantifierResult> generalized = quantify(arena, c.sourceType, constraint->scope);
|
||||||
if (generalized)
|
if (generalized)
|
||||||
{
|
{
|
||||||
if (get<BlockedType>(generalizedType))
|
if (get<BlockedType>(generalizedType))
|
||||||
asMutable(generalizedType)->ty.emplace<BoundType>(*generalized);
|
asMutable(generalizedType)->ty.emplace<BoundType>(generalized->result);
|
||||||
else
|
else
|
||||||
unify(generalizedType, *generalized, constraint->scope);
|
unify(generalizedType, generalized->result, constraint->scope);
|
||||||
|
|
||||||
|
for (auto [free, gen] : generalized->insertedGenerics.pairings)
|
||||||
|
unify(free, gen, constraint->scope);
|
||||||
|
|
||||||
|
for (auto [free, gen] : generalized->insertedGenericPacks.pairings)
|
||||||
|
unify(free, gen, constraint->scope);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -504,6 +541,9 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
|
||||||
if (isBlocked(c.superType))
|
if (isBlocked(c.superType))
|
||||||
return block(c.superType, constraint);
|
return block(c.superType, constraint);
|
||||||
|
|
||||||
|
if (!recursiveBlock(c.superType, constraint))
|
||||||
|
return false;
|
||||||
|
|
||||||
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
Instantiation inst(TxnLog::empty(), arena, TypeLevel{}, constraint->scope);
|
||||||
|
|
||||||
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
std::optional<TypeId> instantiated = inst.substitute(c.superType);
|
||||||
|
@ -512,6 +552,9 @@ bool ConstraintSolver::tryDispatch(const InstantiationConstraint& c, NotNull<con
|
||||||
LUAU_ASSERT(get<BlockedType>(c.subType));
|
LUAU_ASSERT(get<BlockedType>(c.subType));
|
||||||
asMutable(c.subType)->ty.emplace<BoundType>(*instantiated);
|
asMutable(c.subType)->ty.emplace<BoundType>(*instantiated);
|
||||||
|
|
||||||
|
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||||
|
queuer.traverse(c.subType);
|
||||||
|
|
||||||
unblock(c.subType);
|
unblock(c.subType);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -953,26 +996,6 @@ struct InfiniteTypeFinder : TypeOnceVisitor
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InstantiationQueuer : TypeOnceVisitor
|
|
||||||
{
|
|
||||||
ConstraintSolver* solver;
|
|
||||||
NotNull<Scope> scope;
|
|
||||||
Location location;
|
|
||||||
|
|
||||||
explicit InstantiationQueuer(NotNull<Scope> scope, const Location& location, ConstraintSolver* solver)
|
|
||||||
: solver(solver)
|
|
||||||
, scope(scope)
|
|
||||||
, location(location)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool visit(TypeId ty, const PendingExpansionType& petv) override
|
|
||||||
{
|
|
||||||
solver->pushConstraint(scope, location, TypeAliasExpansionConstraint{ty});
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint)
|
bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNull<const Constraint> constraint)
|
||||||
{
|
{
|
||||||
const PendingExpansionType* petv = get<PendingExpansionType>(follow(c.target));
|
const PendingExpansionType* petv = get<PendingExpansionType>(follow(c.target));
|
||||||
|
@ -1246,7 +1269,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
LUAU_ASSERT(instantiated); // TODO FIXME HANDLE THIS
|
||||||
|
|
||||||
Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant};
|
Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant};
|
||||||
u.useScopes = true;
|
u.enableScopeTests();
|
||||||
|
|
||||||
u.tryUnify(*instantiated, inferredTy, /* isFunctionCall */ true);
|
u.tryUnify(*instantiated, inferredTy, /* isFunctionCall */ true);
|
||||||
|
|
||||||
|
@ -1278,8 +1301,12 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
u.log.commit();
|
u.log.commit();
|
||||||
unblock(changedTypes);
|
unblock(changedTypes);
|
||||||
unblock(changedPacks);
|
unblock(changedPacks);
|
||||||
|
|
||||||
unblock(c.result);
|
unblock(c.result);
|
||||||
|
|
||||||
|
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||||
|
queuer.traverse(fn);
|
||||||
|
queuer.traverse(inferredTy);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1295,7 +1322,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
|
|
||||||
// We found no matching overloads.
|
// We found no matching overloads.
|
||||||
Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant};
|
Unifier u{normalizer, Mode::Strict, constraint->scope, Location{}, Covariant};
|
||||||
u.useScopes = true;
|
u.enableScopeTests();
|
||||||
|
|
||||||
u.tryUnify(inferredTy, builtinTypes->anyType);
|
u.tryUnify(inferredTy, builtinTypes->anyType);
|
||||||
u.tryUnify(fn, builtinTypes->anyType);
|
u.tryUnify(fn, builtinTypes->anyType);
|
||||||
|
@ -1305,8 +1332,12 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
|
|
||||||
unblock(changedTypes);
|
unblock(changedTypes);
|
||||||
unblock(changedPacks);
|
unblock(changedPacks);
|
||||||
|
|
||||||
unblock(c.result);
|
unblock(c.result);
|
||||||
|
|
||||||
|
InstantiationQueuer queuer{constraint->scope, constraint->location, this};
|
||||||
|
queuer.traverse(fn);
|
||||||
|
queuer.traverse(inferredTy);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1567,8 +1598,11 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
||||||
}
|
}
|
||||||
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
|
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
|
||||||
{
|
{
|
||||||
|
TypeId promotedIndexTy = arena->freshType(tt->scope);
|
||||||
|
unify(c.indexType, promotedIndexTy, constraint->scope);
|
||||||
|
|
||||||
auto mtt = getMutable<TableType>(subjectType);
|
auto mtt = getMutable<TableType>(subjectType);
|
||||||
mtt->indexer = TableIndexer{c.indexType, c.propType};
|
mtt->indexer = TableIndexer{promotedIndexTy, c.propType};
|
||||||
asMutable(c.propType)->ty.emplace<FreeType>(tt->scope);
|
asMutable(c.propType)->ty.emplace<FreeType>(tt->scope);
|
||||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||||
unblock(c.propType);
|
unblock(c.propType);
|
||||||
|
@ -1666,6 +1700,52 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
|
{
|
||||||
|
TypeId ty = follow(c.ty);
|
||||||
|
FamilyGraphReductionResult result = reduceFamilies(ty, constraint->location, NotNull{arena}, builtinTypes, nullptr, force);
|
||||||
|
|
||||||
|
for (TypeId r : result.reducedTypes)
|
||||||
|
unblock(r);
|
||||||
|
|
||||||
|
for (TypePackId r : result.reducedPacks)
|
||||||
|
unblock(r);
|
||||||
|
|
||||||
|
if (force)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (TypeId b : result.blockedTypes)
|
||||||
|
block(b, constraint);
|
||||||
|
|
||||||
|
for (TypePackId b : result.blockedPacks)
|
||||||
|
block(b, constraint);
|
||||||
|
|
||||||
|
return result.blockedTypes.empty() && result.blockedPacks.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
|
{
|
||||||
|
TypePackId tp = follow(c.tp);
|
||||||
|
FamilyGraphReductionResult result = reduceFamilies(tp, constraint->location, NotNull{arena}, builtinTypes, nullptr, force);
|
||||||
|
|
||||||
|
for (TypeId r : result.reducedTypes)
|
||||||
|
unblock(r);
|
||||||
|
|
||||||
|
for (TypePackId r : result.reducedPacks)
|
||||||
|
unblock(r);
|
||||||
|
|
||||||
|
if (force)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (TypeId b : result.blockedTypes)
|
||||||
|
block(b, constraint);
|
||||||
|
|
||||||
|
for (TypePackId b : result.blockedPacks)
|
||||||
|
block(b, constraint);
|
||||||
|
|
||||||
|
return result.blockedTypes.empty() && result.blockedPacks.empty();
|
||||||
|
}
|
||||||
|
|
||||||
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
auto block_ = [&](auto&& t) {
|
auto block_ = [&](auto&& t) {
|
||||||
|
@ -2031,7 +2111,7 @@ template <typename TID>
|
||||||
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
||||||
{
|
{
|
||||||
Unifier u{normalizer, Mode::Strict, constraint->scope, constraint->location, Covariant};
|
Unifier u{normalizer, Mode::Strict, constraint->scope, constraint->location, Covariant};
|
||||||
u.useScopes = true;
|
u.enableScopeTests();
|
||||||
|
|
||||||
u.tryUnify(subTy, superTy);
|
u.tryUnify(subTy, superTy);
|
||||||
|
|
||||||
|
@ -2195,10 +2275,11 @@ void ConstraintSolver::unblock(NotNull<const Constraint> progressed)
|
||||||
return unblock_(progressed.get());
|
return unblock_(progressed.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConstraintSolver::unblock(TypeId progressed)
|
void ConstraintSolver::unblock(TypeId ty)
|
||||||
{
|
{
|
||||||
DenseHashSet<TypeId> seen{nullptr};
|
DenseHashSet<TypeId> seen{nullptr};
|
||||||
|
|
||||||
|
TypeId progressed = ty;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
if (seen.find(progressed))
|
if (seen.find(progressed))
|
||||||
|
@ -2256,7 +2337,7 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
||||||
void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> scope)
|
void ConstraintSolver::unify(TypeId subType, TypeId superType, NotNull<Scope> scope)
|
||||||
{
|
{
|
||||||
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
||||||
u.useScopes = true;
|
u.enableScopeTests();
|
||||||
|
|
||||||
u.tryUnify(subType, superType);
|
u.tryUnify(subType, superType);
|
||||||
|
|
||||||
|
@ -2279,7 +2360,7 @@ void ConstraintSolver::unify(TypePackId subPack, TypePackId superPack, NotNull<S
|
||||||
{
|
{
|
||||||
UnifierSharedState sharedState{&iceReporter};
|
UnifierSharedState sharedState{&iceReporter};
|
||||||
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
||||||
u.useScopes = true;
|
u.enableScopeTests();
|
||||||
|
|
||||||
u.tryUnify(subPack, superPack);
|
u.tryUnify(subPack, superPack);
|
||||||
|
|
||||||
|
@ -2374,7 +2455,7 @@ TypeId ConstraintSolver::unionOfTypes(TypeId a, TypeId b, NotNull<Scope> scope,
|
||||||
if (unifyFreeTypes && (get<FreeType>(a) || get<FreeType>(b)))
|
if (unifyFreeTypes && (get<FreeType>(a) || get<FreeType>(b)))
|
||||||
{
|
{
|
||||||
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
Unifier u{normalizer, Mode::Strict, scope, Location{}, Covariant};
|
||||||
u.useScopes = true;
|
u.enableScopeTests();
|
||||||
u.tryUnify(b, a);
|
u.tryUnify(b, a);
|
||||||
|
|
||||||
if (u.errors.empty())
|
if (u.errors.empty())
|
||||||
|
|
|
@ -484,6 +484,16 @@ struct ErrorConverter
|
||||||
{
|
{
|
||||||
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
|
return "Attempting a dynamic property access on type '" + Luau::toString(e.ty) + "' is unsafe and may cause exceptions at runtime";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string operator()(const UninhabitedTypeFamily& e) const
|
||||||
|
{
|
||||||
|
return "Type family instance " + Luau::toString(e.ty) + " is uninhabited";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string operator()(const UninhabitedTypePackFamily& e) const
|
||||||
|
{
|
||||||
|
return "Type pack family instance " + Luau::toString(e.tp) + " is uninhabited";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InvalidNameChecker
|
struct InvalidNameChecker
|
||||||
|
@ -786,6 +796,16 @@ bool DynamicPropertyLookupOnClassesUnsafe::operator==(const DynamicPropertyLooku
|
||||||
return ty == rhs.ty;
|
return ty == rhs.ty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UninhabitedTypeFamily::operator==(const UninhabitedTypeFamily& rhs) const
|
||||||
|
{
|
||||||
|
return ty == rhs.ty;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UninhabitedTypePackFamily::operator==(const UninhabitedTypePackFamily& rhs) const
|
||||||
|
{
|
||||||
|
return tp == rhs.tp;
|
||||||
|
}
|
||||||
|
|
||||||
std::string toString(const TypeError& error)
|
std::string toString(const TypeError& error)
|
||||||
{
|
{
|
||||||
return toString(error, TypeErrorToStringOptions{});
|
return toString(error, TypeErrorToStringOptions{});
|
||||||
|
@ -944,6 +964,10 @@ void copyError(T& e, TypeArena& destArena, CloneState cloneState)
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
|
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
|
||||||
e.ty = clone(e.ty);
|
e.ty = clone(e.ty);
|
||||||
|
else if constexpr (std::is_same_v<T, UninhabitedTypeFamily>)
|
||||||
|
e.ty = clone(e.ty);
|
||||||
|
else if constexpr (std::is_same_v<T, UninhabitedTypePackFamily>)
|
||||||
|
e.tp = clone(e.tp);
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||||
}
|
}
|
||||||
|
|
|
@ -192,6 +192,10 @@ static void errorToString(std::ostream& stream, const T& err)
|
||||||
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
|
stream << "TypePackMismatch { wanted = '" + toString(err.wantedTp) + "', given = '" + toString(err.givenTp) + "' }";
|
||||||
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
|
else if constexpr (std::is_same_v<T, DynamicPropertyLookupOnClassesUnsafe>)
|
||||||
stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }";
|
stream << "DynamicPropertyLookupOnClassesUnsafe { " << toString(err.ty) << " }";
|
||||||
|
else if constexpr (std::is_same_v<T, UninhabitedTypeFamily>)
|
||||||
|
stream << "UninhabitedTypeFamily { " << toString(err.ty) << " }";
|
||||||
|
else if constexpr (std::is_same_v<T, UninhabitedTypePackFamily>)
|
||||||
|
stream << "UninhabitedTypePackFamily { " << toString(err.tp) << " }";
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,8 +154,8 @@ void quantify(TypeId ty, TypeLevel level)
|
||||||
struct PureQuantifier : Substitution
|
struct PureQuantifier : Substitution
|
||||||
{
|
{
|
||||||
Scope* scope;
|
Scope* scope;
|
||||||
std::vector<TypeId> insertedGenerics;
|
OrderedMap<TypeId, TypeId> insertedGenerics;
|
||||||
std::vector<TypePackId> insertedGenericPacks;
|
OrderedMap<TypePackId, TypePackId> insertedGenericPacks;
|
||||||
bool seenMutableType = false;
|
bool seenMutableType = false;
|
||||||
bool seenGenericType = false;
|
bool seenGenericType = false;
|
||||||
|
|
||||||
|
@ -203,7 +203,7 @@ struct PureQuantifier : Substitution
|
||||||
if (auto ftv = get<FreeType>(ty))
|
if (auto ftv = get<FreeType>(ty))
|
||||||
{
|
{
|
||||||
TypeId result = arena->addType(GenericType{scope});
|
TypeId result = arena->addType(GenericType{scope});
|
||||||
insertedGenerics.push_back(result);
|
insertedGenerics.push(ty, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
else if (auto ttv = get<TableType>(ty))
|
else if (auto ttv = get<TableType>(ty))
|
||||||
|
@ -217,7 +217,10 @@ struct PureQuantifier : Substitution
|
||||||
resultTable->scope = scope;
|
resultTable->scope = scope;
|
||||||
|
|
||||||
if (ttv->state == TableState::Free)
|
if (ttv->state == TableState::Free)
|
||||||
|
{
|
||||||
resultTable->state = TableState::Generic;
|
resultTable->state = TableState::Generic;
|
||||||
|
insertedGenerics.push(ty, result);
|
||||||
|
}
|
||||||
else if (ttv->state == TableState::Unsealed)
|
else if (ttv->state == TableState::Unsealed)
|
||||||
resultTable->state = TableState::Sealed;
|
resultTable->state = TableState::Sealed;
|
||||||
|
|
||||||
|
@ -231,8 +234,8 @@ struct PureQuantifier : Substitution
|
||||||
{
|
{
|
||||||
if (auto ftp = get<FreeTypePack>(tp))
|
if (auto ftp = get<FreeTypePack>(tp))
|
||||||
{
|
{
|
||||||
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{}});
|
TypePackId result = arena->addTypePack(TypePackVar{GenericTypePack{scope}});
|
||||||
insertedGenericPacks.push_back(result);
|
insertedGenericPacks.push(tp, result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,7 +255,7 @@ struct PureQuantifier : Substitution
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<TypeId> quantify(TypeArena* arena, TypeId ty, Scope* scope)
|
std::optional<QuantifierResult> quantify(TypeArena* arena, TypeId ty, Scope* scope)
|
||||||
{
|
{
|
||||||
PureQuantifier quantifier{arena, scope};
|
PureQuantifier quantifier{arena, scope};
|
||||||
std::optional<TypeId> result = quantifier.substitute(ty);
|
std::optional<TypeId> result = quantifier.substitute(ty);
|
||||||
|
@ -262,11 +265,20 @@ std::optional<TypeId> quantify(TypeArena* arena, TypeId ty, Scope* scope)
|
||||||
FunctionType* ftv = getMutable<FunctionType>(*result);
|
FunctionType* ftv = getMutable<FunctionType>(*result);
|
||||||
LUAU_ASSERT(ftv);
|
LUAU_ASSERT(ftv);
|
||||||
ftv->scope = scope;
|
ftv->scope = scope;
|
||||||
ftv->generics.insert(ftv->generics.end(), quantifier.insertedGenerics.begin(), quantifier.insertedGenerics.end());
|
|
||||||
ftv->genericPacks.insert(ftv->genericPacks.end(), quantifier.insertedGenericPacks.begin(), quantifier.insertedGenericPacks.end());
|
for (auto k : quantifier.insertedGenerics.keys)
|
||||||
|
{
|
||||||
|
TypeId g = quantifier.insertedGenerics.pairings[k];
|
||||||
|
if (get<GenericType>(g))
|
||||||
|
ftv->generics.push_back(g);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto k : quantifier.insertedGenericPacks.keys)
|
||||||
|
ftv->genericPacks.push_back(quantifier.insertedGenericPacks.pairings[k]);
|
||||||
|
|
||||||
ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType;
|
ftv->hasNoGenerics = ftv->generics.empty() && ftv->genericPacks.empty() && !quantifier.seenGenericType && !quantifier.seenMutableType;
|
||||||
|
|
||||||
return *result;
|
return std::optional<QuantifierResult>({*result, std::move(quantifier.insertedGenerics), std::move(quantifier.insertedGenericPacks)});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -78,6 +78,11 @@ static TypeId DEPRECATED_shallowClone(TypeId ty, TypeArena& dest, const TxnLog*
|
||||||
{
|
{
|
||||||
result = dest.addType(NegationType{ntv->ty});
|
result = dest.addType(NegationType{ntv->ty});
|
||||||
}
|
}
|
||||||
|
else if (const TypeFamilyInstanceType* tfit = get<TypeFamilyInstanceType>(ty))
|
||||||
|
{
|
||||||
|
TypeFamilyInstanceType clone{tfit->family, tfit->typeArguments, tfit->packArguments};
|
||||||
|
result = dest.addType(std::move(clone));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
|
@ -168,14 +173,27 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
|
||||||
{
|
{
|
||||||
if (alwaysClone)
|
if (alwaysClone)
|
||||||
{
|
{
|
||||||
ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName};
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
return dest.addType(std::move(clone));
|
{
|
||||||
|
ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName, a.indexer};
|
||||||
|
return dest.addType(std::move(clone));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ClassType clone{a.name, a.props, a.parent, a.metatable, a.tags, a.userData, a.definitionModuleName};
|
||||||
|
return dest.addType(std::move(clone));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return ty;
|
return ty;
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, NegationType>)
|
else if constexpr (std::is_same_v<T, NegationType>)
|
||||||
return dest.addType(NegationType{a.ty});
|
return dest.addType(NegationType{a.ty});
|
||||||
|
else if constexpr (std::is_same_v<T, TypeFamilyInstanceType>)
|
||||||
|
{
|
||||||
|
TypeFamilyInstanceType clone{a.family, a.typeArguments, a.packArguments};
|
||||||
|
return dest.addType(std::move(clone));
|
||||||
|
}
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive shallowClone switch");
|
static_assert(always_false_v<T>, "Non-exhaustive shallowClone switch");
|
||||||
};
|
};
|
||||||
|
@ -255,6 +273,14 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
||||||
for (TypePackId a : petv->packArguments)
|
for (TypePackId a : petv->packArguments)
|
||||||
visitChild(a);
|
visitChild(a);
|
||||||
}
|
}
|
||||||
|
else if (const TypeFamilyInstanceType* tfit = get<TypeFamilyInstanceType>(ty))
|
||||||
|
{
|
||||||
|
for (TypeId a : tfit->typeArguments)
|
||||||
|
visitChild(a);
|
||||||
|
|
||||||
|
for (TypePackId a : tfit->packArguments)
|
||||||
|
visitChild(a);
|
||||||
|
}
|
||||||
else if (const ClassType* ctv = get<ClassType>(ty); FFlag::LuauClassTypeVarsInSubstitution && ctv)
|
else if (const ClassType* ctv = get<ClassType>(ty); FFlag::LuauClassTypeVarsInSubstitution && ctv)
|
||||||
{
|
{
|
||||||
for (const auto& [name, prop] : ctv->props)
|
for (const auto& [name, prop] : ctv->props)
|
||||||
|
@ -265,6 +291,15 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
||||||
|
|
||||||
if (ctv->metatable)
|
if (ctv->metatable)
|
||||||
visitChild(*ctv->metatable);
|
visitChild(*ctv->metatable);
|
||||||
|
|
||||||
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
|
{
|
||||||
|
if (ctv->indexer)
|
||||||
|
{
|
||||||
|
visitChild(ctv->indexer->indexType);
|
||||||
|
visitChild(ctv->indexer->indexResultType);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (const NegationType* ntv = get<NegationType>(ty))
|
else if (const NegationType* ntv = get<NegationType>(ty))
|
||||||
{
|
{
|
||||||
|
@ -669,6 +704,14 @@ TypePackId Substitution::clone(TypePackId tp)
|
||||||
clone.hidden = vtp->hidden;
|
clone.hidden = vtp->hidden;
|
||||||
return addTypePack(std::move(clone));
|
return addTypePack(std::move(clone));
|
||||||
}
|
}
|
||||||
|
else if (const TypeFamilyInstanceTypePack* tfitp = get<TypeFamilyInstanceTypePack>(tp))
|
||||||
|
{
|
||||||
|
TypeFamilyInstanceTypePack clone{
|
||||||
|
tfitp->family, std::vector<TypeId>(tfitp->typeArguments.size()), std::vector<TypePackId>(tfitp->packArguments.size())};
|
||||||
|
clone.typeArguments.assign(tfitp->typeArguments.begin(), tfitp->typeArguments.end());
|
||||||
|
clone.packArguments.assign(tfitp->packArguments.begin(), tfitp->packArguments.end());
|
||||||
|
return addTypePack(std::move(clone));
|
||||||
|
}
|
||||||
else if (FFlag::LuauClonePublicInterfaceLess2)
|
else if (FFlag::LuauClonePublicInterfaceLess2)
|
||||||
{
|
{
|
||||||
return addTypePack(*tp);
|
return addTypePack(*tp);
|
||||||
|
@ -786,6 +829,14 @@ void Substitution::replaceChildren(TypeId ty)
|
||||||
for (TypePackId& a : petv->packArguments)
|
for (TypePackId& a : petv->packArguments)
|
||||||
a = replace(a);
|
a = replace(a);
|
||||||
}
|
}
|
||||||
|
else if (TypeFamilyInstanceType* tfit = getMutable<TypeFamilyInstanceType>(ty))
|
||||||
|
{
|
||||||
|
for (TypeId& a : tfit->typeArguments)
|
||||||
|
a = replace(a);
|
||||||
|
|
||||||
|
for (TypePackId& a : tfit->packArguments)
|
||||||
|
a = replace(a);
|
||||||
|
}
|
||||||
else if (ClassType* ctv = getMutable<ClassType>(ty); FFlag::LuauClassTypeVarsInSubstitution && ctv)
|
else if (ClassType* ctv = getMutable<ClassType>(ty); FFlag::LuauClassTypeVarsInSubstitution && ctv)
|
||||||
{
|
{
|
||||||
for (auto& [name, prop] : ctv->props)
|
for (auto& [name, prop] : ctv->props)
|
||||||
|
@ -796,6 +847,15 @@ void Substitution::replaceChildren(TypeId ty)
|
||||||
|
|
||||||
if (ctv->metatable)
|
if (ctv->metatable)
|
||||||
ctv->metatable = replace(*ctv->metatable);
|
ctv->metatable = replace(*ctv->metatable);
|
||||||
|
|
||||||
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
|
{
|
||||||
|
if (ctv->indexer)
|
||||||
|
{
|
||||||
|
ctv->indexer->indexType = replace(ctv->indexer->indexType);
|
||||||
|
ctv->indexer->indexResultType = replace(ctv->indexer->indexResultType);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (NegationType* ntv = getMutable<NegationType>(ty))
|
else if (NegationType* ntv = getMutable<NegationType>(ty))
|
||||||
{
|
{
|
||||||
|
@ -824,6 +884,14 @@ void Substitution::replaceChildren(TypePackId tp)
|
||||||
{
|
{
|
||||||
vtp->ty = replace(vtp->ty);
|
vtp->ty = replace(vtp->ty);
|
||||||
}
|
}
|
||||||
|
else if (TypeFamilyInstanceTypePack* tfitp = getMutable<TypeFamilyInstanceTypePack>(tp))
|
||||||
|
{
|
||||||
|
for (TypeId& t : tfitp->typeArguments)
|
||||||
|
t = replace(t);
|
||||||
|
|
||||||
|
for (TypePackId& t : tfitp->packArguments)
|
||||||
|
t = replace(t);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -257,6 +257,15 @@ void StateDot::visitChildren(TypeId ty, int index)
|
||||||
|
|
||||||
if (ctv->metatable)
|
if (ctv->metatable)
|
||||||
visitChild(*ctv->metatable, index, "[metatable]");
|
visitChild(*ctv->metatable, index, "[metatable]");
|
||||||
|
|
||||||
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
|
{
|
||||||
|
if (ctv->indexer)
|
||||||
|
{
|
||||||
|
visitChild(ctv->indexer->indexType, index, "[index]");
|
||||||
|
visitChild(ctv->indexer->indexResultType, index, "[value]");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (const SingletonType* stv = get<SingletonType>(ty))
|
else if (const SingletonType* stv = get<SingletonType>(ty))
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -16,11 +17,22 @@
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prefix generic typenames with gen-
|
* Enables increasing levels of verbosity for Luau type names when stringifying.
|
||||||
* Additionally, free types will be prefixed with free- and suffixed with their level. eg free-a-4
|
* After level 2, test cases will break unpredictably because a pointer to their
|
||||||
* Fair warning: Setting this will break a lot of Luau unit tests.
|
* scope will be included in the stringification of generic and free types.
|
||||||
|
*
|
||||||
|
* Supported values:
|
||||||
|
*
|
||||||
|
* 0: Disabled, no changes.
|
||||||
|
*
|
||||||
|
* 1: Prefix free/generic types with free- and gen-, respectively. Also reveal
|
||||||
|
* hidden variadic tails.
|
||||||
|
*
|
||||||
|
* 2: Suffix free/generic types with their scope depth.
|
||||||
|
*
|
||||||
|
* 3: Suffix free/generic types with their scope pointer, if present.
|
||||||
*/
|
*/
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauVerboseTypeNames, false)
|
LUAU_FASTINTVARIABLE(DebugLuauVerboseTypeNames, 0)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauToStringNoLexicalSort, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauToStringNoLexicalSort, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -223,11 +235,15 @@ struct StringifierState
|
||||||
++count;
|
++count;
|
||||||
|
|
||||||
emit(count);
|
emit(count);
|
||||||
emit("-");
|
|
||||||
char buffer[16];
|
if (FInt::DebugLuauVerboseTypeNames >= 3)
|
||||||
uint32_t s = uint32_t(intptr_t(scope) & 0xFFFFFF);
|
{
|
||||||
snprintf(buffer, sizeof(buffer), "0x%x", s);
|
emit("-");
|
||||||
emit(buffer);
|
char buffer[16];
|
||||||
|
uint32_t s = uint32_t(intptr_t(scope) & 0xFFFFFF);
|
||||||
|
snprintf(buffer, sizeof(buffer), "0x%x", s);
|
||||||
|
emit(buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void emit(TypeLevel level)
|
void emit(TypeLevel level)
|
||||||
|
@ -371,11 +387,13 @@ struct TypeStringifier
|
||||||
void operator()(TypeId ty, const FreeType& ftv)
|
void operator()(TypeId ty, const FreeType& ftv)
|
||||||
{
|
{
|
||||||
state.result.invalid = true;
|
state.result.invalid = true;
|
||||||
if (FFlag::DebugLuauVerboseTypeNames)
|
|
||||||
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
state.emit("free-");
|
state.emit("free-");
|
||||||
|
|
||||||
state.emit(state.getName(ty));
|
state.emit(state.getName(ty));
|
||||||
|
|
||||||
if (FFlag::DebugLuauVerboseTypeNames)
|
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||||
{
|
{
|
||||||
state.emit("-");
|
state.emit("-");
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
@ -392,6 +410,9 @@ struct TypeStringifier
|
||||||
|
|
||||||
void operator()(TypeId ty, const GenericType& gtv)
|
void operator()(TypeId ty, const GenericType& gtv)
|
||||||
{
|
{
|
||||||
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
|
state.emit("gen-");
|
||||||
|
|
||||||
if (gtv.explicitName)
|
if (gtv.explicitName)
|
||||||
{
|
{
|
||||||
state.usedNames.insert(gtv.name);
|
state.usedNames.insert(gtv.name);
|
||||||
|
@ -401,7 +422,7 @@ struct TypeStringifier
|
||||||
else
|
else
|
||||||
state.emit(state.getName(ty));
|
state.emit(state.getName(ty));
|
||||||
|
|
||||||
if (FFlag::DebugLuauVerboseTypeNames)
|
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||||
{
|
{
|
||||||
state.emit("-");
|
state.emit("-");
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
@ -871,6 +892,33 @@ struct TypeStringifier
|
||||||
if (parens)
|
if (parens)
|
||||||
state.emit(")");
|
state.emit(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()(TypeId, const TypeFamilyInstanceType& tfitv)
|
||||||
|
{
|
||||||
|
state.emit(tfitv.family->name);
|
||||||
|
state.emit("<");
|
||||||
|
|
||||||
|
bool comma = false;
|
||||||
|
for (TypeId ty : tfitv.typeArguments)
|
||||||
|
{
|
||||||
|
if (comma)
|
||||||
|
state.emit(", ");
|
||||||
|
|
||||||
|
comma = true;
|
||||||
|
stringify(ty);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TypePackId tp : tfitv.packArguments)
|
||||||
|
{
|
||||||
|
if (comma)
|
||||||
|
state.emit(", ");
|
||||||
|
|
||||||
|
comma = true;
|
||||||
|
stringify(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.emit(">");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TypePackStringifier
|
struct TypePackStringifier
|
||||||
|
@ -958,7 +1006,7 @@ struct TypePackStringifier
|
||||||
if (tp.tail && !isEmpty(*tp.tail))
|
if (tp.tail && !isEmpty(*tp.tail))
|
||||||
{
|
{
|
||||||
TypePackId tail = follow(*tp.tail);
|
TypePackId tail = follow(*tp.tail);
|
||||||
if (auto vtp = get<VariadicTypePack>(tail); !vtp || (!FFlag::DebugLuauVerboseTypeNames && !vtp->hidden))
|
if (auto vtp = get<VariadicTypePack>(tail); !vtp || (FInt::DebugLuauVerboseTypeNames < 1 && !vtp->hidden))
|
||||||
{
|
{
|
||||||
if (first)
|
if (first)
|
||||||
first = false;
|
first = false;
|
||||||
|
@ -981,7 +1029,7 @@ struct TypePackStringifier
|
||||||
void operator()(TypePackId, const VariadicTypePack& pack)
|
void operator()(TypePackId, const VariadicTypePack& pack)
|
||||||
{
|
{
|
||||||
state.emit("...");
|
state.emit("...");
|
||||||
if (FFlag::DebugLuauVerboseTypeNames && pack.hidden)
|
if (FInt::DebugLuauVerboseTypeNames >= 1 && pack.hidden)
|
||||||
{
|
{
|
||||||
state.emit("*hidden*");
|
state.emit("*hidden*");
|
||||||
}
|
}
|
||||||
|
@ -990,6 +1038,9 @@ struct TypePackStringifier
|
||||||
|
|
||||||
void operator()(TypePackId tp, const GenericTypePack& pack)
|
void operator()(TypePackId tp, const GenericTypePack& pack)
|
||||||
{
|
{
|
||||||
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
|
state.emit("gen-");
|
||||||
|
|
||||||
if (pack.explicitName)
|
if (pack.explicitName)
|
||||||
{
|
{
|
||||||
state.usedNames.insert(pack.name);
|
state.usedNames.insert(pack.name);
|
||||||
|
@ -1001,7 +1052,7 @@ struct TypePackStringifier
|
||||||
state.emit(state.getName(tp));
|
state.emit(state.getName(tp));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FFlag::DebugLuauVerboseTypeNames)
|
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||||
{
|
{
|
||||||
state.emit("-");
|
state.emit("-");
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
@ -1009,17 +1060,18 @@ struct TypePackStringifier
|
||||||
else
|
else
|
||||||
state.emit(pack.level);
|
state.emit(pack.level);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.emit("...");
|
state.emit("...");
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(TypePackId tp, const FreeTypePack& pack)
|
void operator()(TypePackId tp, const FreeTypePack& pack)
|
||||||
{
|
{
|
||||||
state.result.invalid = true;
|
state.result.invalid = true;
|
||||||
if (FFlag::DebugLuauVerboseTypeNames)
|
if (FInt::DebugLuauVerboseTypeNames >= 1)
|
||||||
state.emit("free-");
|
state.emit("free-");
|
||||||
state.emit(state.getName(tp));
|
state.emit(state.getName(tp));
|
||||||
|
|
||||||
if (FFlag::DebugLuauVerboseTypeNames)
|
if (FInt::DebugLuauVerboseTypeNames >= 2)
|
||||||
{
|
{
|
||||||
state.emit("-");
|
state.emit("-");
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
@ -1042,6 +1094,33 @@ struct TypePackStringifier
|
||||||
state.emit(btp.index);
|
state.emit(btp.index);
|
||||||
state.emit("*");
|
state.emit("*");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()(TypePackId, const TypeFamilyInstanceTypePack& tfitp)
|
||||||
|
{
|
||||||
|
state.emit(tfitp.family->name);
|
||||||
|
state.emit("<");
|
||||||
|
|
||||||
|
bool comma = false;
|
||||||
|
for (TypeId p : tfitp.typeArguments)
|
||||||
|
{
|
||||||
|
if (comma)
|
||||||
|
state.emit(", ");
|
||||||
|
|
||||||
|
comma = true;
|
||||||
|
stringify(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TypePackId p : tfitp.packArguments)
|
||||||
|
{
|
||||||
|
if (comma)
|
||||||
|
state.emit(", ");
|
||||||
|
|
||||||
|
comma = true;
|
||||||
|
stringify(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.emit(">");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void TypeStringifier::stringify(TypePackId tp)
|
void TypeStringifier::stringify(TypePackId tp)
|
||||||
|
@ -1560,6 +1639,12 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
else if constexpr (std::is_same_v<T, UnpackConstraint>)
|
||||||
return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack);
|
return tos(c.resultPack) + " ~ unpack " + tos(c.sourcePack);
|
||||||
|
else if constexpr (std::is_same_v<T, ReduceConstraint>)
|
||||||
|
return "reduce " + tos(c.ty);
|
||||||
|
else if constexpr (std::is_same_v<T, ReducePackConstraint>)
|
||||||
|
{
|
||||||
|
return "reduce " + tos(c.tp);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
|
|
||||||
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
|
@ -8,6 +9,8 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -71,7 +74,11 @@ const TxnLog* TxnLog::empty()
|
||||||
void TxnLog::concat(TxnLog rhs)
|
void TxnLog::concat(TxnLog rhs)
|
||||||
{
|
{
|
||||||
for (auto& [ty, rep] : rhs.typeVarChanges)
|
for (auto& [ty, rep] : rhs.typeVarChanges)
|
||||||
|
{
|
||||||
|
if (rep->dead)
|
||||||
|
continue;
|
||||||
typeVarChanges[ty] = std::move(rep);
|
typeVarChanges[ty] = std::move(rep);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& [tp, rep] : rhs.typePackChanges)
|
for (auto& [tp, rep] : rhs.typePackChanges)
|
||||||
typePackChanges[tp] = std::move(rep);
|
typePackChanges[tp] = std::move(rep);
|
||||||
|
@ -81,7 +88,10 @@ void TxnLog::concatAsIntersections(TxnLog rhs, NotNull<TypeArena> arena)
|
||||||
{
|
{
|
||||||
for (auto& [ty, rightRep] : rhs.typeVarChanges)
|
for (auto& [ty, rightRep] : rhs.typeVarChanges)
|
||||||
{
|
{
|
||||||
if (auto leftRep = typeVarChanges.find(ty))
|
if (rightRep->dead)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (auto leftRep = typeVarChanges.find(ty); leftRep && !(*leftRep)->dead)
|
||||||
{
|
{
|
||||||
TypeId leftTy = arena->addType((*leftRep)->pending);
|
TypeId leftTy = arena->addType((*leftRep)->pending);
|
||||||
TypeId rightTy = arena->addType(rightRep->pending);
|
TypeId rightTy = arena->addType(rightRep->pending);
|
||||||
|
@ -97,16 +107,94 @@ void TxnLog::concatAsIntersections(TxnLog rhs, NotNull<TypeArena> arena)
|
||||||
|
|
||||||
void TxnLog::concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena)
|
void TxnLog::concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena)
|
||||||
{
|
{
|
||||||
for (auto& [ty, rightRep] : rhs.typeVarChanges)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
if (auto leftRep = typeVarChanges.find(ty))
|
/*
|
||||||
|
* Check for cycles.
|
||||||
|
*
|
||||||
|
* We must not combine a log entry that binds 'a to 'b with a log that
|
||||||
|
* binds 'b to 'a.
|
||||||
|
*
|
||||||
|
* Of the two, identify the one with the 'bigger' scope and eliminate the
|
||||||
|
* entry that rebinds it.
|
||||||
|
*/
|
||||||
|
for (const auto& [rightTy, rightRep] : rhs.typeVarChanges)
|
||||||
{
|
{
|
||||||
TypeId leftTy = arena->addType((*leftRep)->pending);
|
if (rightRep->dead)
|
||||||
TypeId rightTy = arena->addType(rightRep->pending);
|
continue;
|
||||||
typeVarChanges[ty]->pending.ty = UnionType{{leftTy, rightTy}};
|
|
||||||
|
// We explicitly use get_if here because we do not wish to do anything
|
||||||
|
// if the uncommitted type is already bound to something else.
|
||||||
|
const FreeType* rf = get_if<FreeType>(&rightTy->ty);
|
||||||
|
if (!rf)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const BoundType* rb = Luau::get<BoundType>(&rightRep->pending);
|
||||||
|
if (!rb)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const TypeId leftTy = rb->boundTo;
|
||||||
|
const FreeType* lf = get_if<FreeType>(&leftTy->ty);
|
||||||
|
if (!lf)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto leftRep = typeVarChanges.find(leftTy);
|
||||||
|
if (!leftRep)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((*leftRep)->dead)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const BoundType* lb = Luau::get<BoundType>(&(*leftRep)->pending);
|
||||||
|
if (!lb)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (lb->boundTo == rightTy)
|
||||||
|
{
|
||||||
|
// leftTy has been bound to rightTy, but rightTy has also been bound
|
||||||
|
// to leftTy. We find the one that belongs to the more deeply nested
|
||||||
|
// scope and remove it from the log.
|
||||||
|
const bool discardLeft = useScopes ? subsumes(lf->scope, rf->scope) : lf->level.subsumes(rf->level);
|
||||||
|
|
||||||
|
if (discardLeft)
|
||||||
|
(*leftRep)->dead = true;
|
||||||
|
else
|
||||||
|
rightRep->dead = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& [ty, rightRep] : rhs.typeVarChanges)
|
||||||
|
{
|
||||||
|
if (rightRep->dead)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (auto leftRep = typeVarChanges.find(ty); leftRep && !(*leftRep)->dead)
|
||||||
|
{
|
||||||
|
TypeId leftTy = arena->addType((*leftRep)->pending);
|
||||||
|
TypeId rightTy = arena->addType(rightRep->pending);
|
||||||
|
|
||||||
|
if (follow(leftTy) == follow(rightTy))
|
||||||
|
typeVarChanges[ty] = std::move(rightRep);
|
||||||
|
else
|
||||||
|
typeVarChanges[ty]->pending.ty = UnionType{{leftTy, rightTy}};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
typeVarChanges[ty] = std::move(rightRep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (auto& [ty, rightRep] : rhs.typeVarChanges)
|
||||||
|
{
|
||||||
|
if (auto leftRep = typeVarChanges.find(ty))
|
||||||
|
{
|
||||||
|
TypeId leftTy = arena->addType((*leftRep)->pending);
|
||||||
|
TypeId rightTy = arena->addType(rightRep->pending);
|
||||||
|
typeVarChanges[ty]->pending.ty = UnionType{{leftTy, rightTy}};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
typeVarChanges[ty] = std::move(rightRep);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
typeVarChanges[ty] = std::move(rightRep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& [tp, rep] : rhs.typePackChanges)
|
for (auto& [tp, rep] : rhs.typePackChanges)
|
||||||
|
@ -116,7 +204,10 @@ void TxnLog::concatAsUnion(TxnLog rhs, NotNull<TypeArena> arena)
|
||||||
void TxnLog::commit()
|
void TxnLog::commit()
|
||||||
{
|
{
|
||||||
for (auto& [ty, rep] : typeVarChanges)
|
for (auto& [ty, rep] : typeVarChanges)
|
||||||
asMutable(ty)->reassign(rep.get()->pending);
|
{
|
||||||
|
if (!rep->dead)
|
||||||
|
asMutable(ty)->reassign(rep.get()->pending);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& [tp, rep] : typePackChanges)
|
for (auto& [tp, rep] : typePackChanges)
|
||||||
asMutable(tp)->reassign(rep.get()->pending);
|
asMutable(tp)->reassign(rep.get()->pending);
|
||||||
|
@ -135,7 +226,10 @@ TxnLog TxnLog::inverse()
|
||||||
TxnLog inversed(sharedSeen);
|
TxnLog inversed(sharedSeen);
|
||||||
|
|
||||||
for (auto& [ty, _rep] : typeVarChanges)
|
for (auto& [ty, _rep] : typeVarChanges)
|
||||||
inversed.typeVarChanges[ty] = std::make_unique<PendingType>(*ty);
|
{
|
||||||
|
if (!_rep->dead)
|
||||||
|
inversed.typeVarChanges[ty] = std::make_unique<PendingType>(*ty);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& [tp, _rep] : typePackChanges)
|
for (auto& [tp, _rep] : typePackChanges)
|
||||||
inversed.typePackChanges[tp] = std::make_unique<PendingTypePack>(*tp);
|
inversed.typePackChanges[tp] = std::make_unique<PendingTypePack>(*tp);
|
||||||
|
@ -204,7 +298,7 @@ PendingType* TxnLog::queue(TypeId ty)
|
||||||
// Explicitly don't look in ancestors. If we have discovered something new
|
// Explicitly don't look in ancestors. If we have discovered something new
|
||||||
// about this type, we don't want to mutate the parent's state.
|
// about this type, we don't want to mutate the parent's state.
|
||||||
auto& pending = typeVarChanges[ty];
|
auto& pending = typeVarChanges[ty];
|
||||||
if (!pending)
|
if (!pending || (*pending).dead)
|
||||||
{
|
{
|
||||||
pending = std::make_unique<PendingType>(*ty);
|
pending = std::make_unique<PendingType>(*ty);
|
||||||
pending->pending.owningArena = nullptr;
|
pending->pending.owningArena = nullptr;
|
||||||
|
@ -237,7 +331,7 @@ PendingType* TxnLog::pending(TypeId ty) const
|
||||||
|
|
||||||
for (const TxnLog* current = this; current; current = current->parent)
|
for (const TxnLog* current = this; current; current = current->parent)
|
||||||
{
|
{
|
||||||
if (auto it = current->typeVarChanges.find(ty))
|
if (auto it = current->typeVarChanges.find(ty); it && !(*it)->dead)
|
||||||
return it->get();
|
return it->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "Luau/TypeInfer.h"
|
#include "Luau/TypeInfer.h"
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
@ -362,6 +363,10 @@ public:
|
||||||
// FIXME: do the same thing we do with ErrorType
|
// FIXME: do the same thing we do with ErrorType
|
||||||
throw InternalCompilerError("Cannot convert NegationType into AstNode");
|
throw InternalCompilerError("Cannot convert NegationType into AstNode");
|
||||||
}
|
}
|
||||||
|
AstType* operator()(const TypeFamilyInstanceType& tfit)
|
||||||
|
{
|
||||||
|
return allocator->alloc<AstTypeReference>(Location(), std::nullopt, AstName{tfit.family->name.c_str()}, std::nullopt, Location());
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Allocator* allocator;
|
Allocator* allocator;
|
||||||
|
@ -432,6 +437,11 @@ public:
|
||||||
return allocator->alloc<AstTypePackGeneric>(Location(), AstName("Unifiable<Error>"));
|
return allocator->alloc<AstTypePackGeneric>(Location(), AstName("Unifiable<Error>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstTypePack* operator()(const TypeFamilyInstanceTypePack& tfitp) const
|
||||||
|
{
|
||||||
|
return allocator->alloc<AstTypePackGeneric>(Location(), AstName(tfitp.family->name.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Allocator* allocator;
|
Allocator* allocator;
|
||||||
SyntheticNames* syntheticNames;
|
SyntheticNames* syntheticNames;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "Luau/TypeReduction.h"
|
#include "Luau/TypeReduction.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Unifier.h"
|
#include "Luau/Unifier.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
@ -113,6 +114,13 @@ struct TypeChecker2
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TypeId checkForFamilyInhabitance(TypeId instance, Location location)
|
||||||
|
{
|
||||||
|
TxnLog fake{};
|
||||||
|
reportErrors(reduceFamilies(instance, location, NotNull{&testArena}, builtinTypes, &fake, true).errors);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
TypePackId lookupPack(AstExpr* expr)
|
TypePackId lookupPack(AstExpr* expr)
|
||||||
{
|
{
|
||||||
// If a type isn't in the type graph, it probably means that a recursion limit was exceeded.
|
// If a type isn't in the type graph, it probably means that a recursion limit was exceeded.
|
||||||
|
@ -132,11 +140,11 @@ struct TypeChecker2
|
||||||
// allows us not to think about this very much in the actual typechecking logic.
|
// allows us not to think about this very much in the actual typechecking logic.
|
||||||
TypeId* ty = module->astTypes.find(expr);
|
TypeId* ty = module->astTypes.find(expr);
|
||||||
if (ty)
|
if (ty)
|
||||||
return follow(*ty);
|
return checkForFamilyInhabitance(follow(*ty), expr->location);
|
||||||
|
|
||||||
TypePackId* tp = module->astTypePacks.find(expr);
|
TypePackId* tp = module->astTypePacks.find(expr);
|
||||||
if (tp)
|
if (tp)
|
||||||
return flattenPack(*tp);
|
return checkForFamilyInhabitance(flattenPack(*tp), expr->location);
|
||||||
|
|
||||||
return builtinTypes->anyType;
|
return builtinTypes->anyType;
|
||||||
}
|
}
|
||||||
|
@ -159,7 +167,7 @@ struct TypeChecker2
|
||||||
|
|
||||||
TypeId* ty = module->astResolvedTypes.find(annotation);
|
TypeId* ty = module->astResolvedTypes.find(annotation);
|
||||||
LUAU_ASSERT(ty);
|
LUAU_ASSERT(ty);
|
||||||
return follow(*ty);
|
return checkForFamilyInhabitance(follow(*ty), annotation->location);
|
||||||
}
|
}
|
||||||
|
|
||||||
TypePackId lookupPackAnnotation(AstTypePack* annotation)
|
TypePackId lookupPackAnnotation(AstTypePack* annotation)
|
||||||
|
@ -311,6 +319,7 @@ struct TypeChecker2
|
||||||
TypePackId actualRetType = reconstructPack(ret->list, *arena);
|
TypePackId actualRetType = reconstructPack(ret->list, *arena);
|
||||||
|
|
||||||
Unifier u{NotNull{&normalizer}, Mode::Strict, stack.back(), ret->location, Covariant};
|
Unifier u{NotNull{&normalizer}, Mode::Strict, stack.back(), ret->location, Covariant};
|
||||||
|
u.hideousFixMeGenericsAreActuallyFree = true;
|
||||||
|
|
||||||
u.tryUnify(actualRetType, expectedRetType);
|
u.tryUnify(actualRetType, expectedRetType);
|
||||||
const bool ok = u.errors.empty() && u.log.empty();
|
const bool ok = u.errors.empty() && u.log.empty();
|
||||||
|
@ -989,8 +998,11 @@ struct TypeChecker2
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TxnLog fake{};
|
||||||
|
|
||||||
LUAU_ASSERT(ftv);
|
LUAU_ASSERT(ftv);
|
||||||
reportErrors(tryUnify(stack.back(), call->location, ftv->retTypes, expectedRetType, CountMismatch::Context::Return));
|
reportErrors(tryUnify(stack.back(), call->location, ftv->retTypes, expectedRetType, CountMismatch::Context::Return, /* genericsOkay */ true));
|
||||||
|
reportErrors(reduceFamilies(ftv->retTypes, call->location, NotNull{&testArena}, builtinTypes, &fake, true).errors);
|
||||||
|
|
||||||
auto it = begin(expectedArgTypes);
|
auto it = begin(expectedArgTypes);
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
@ -1007,7 +1019,8 @@ struct TypeChecker2
|
||||||
|
|
||||||
Location argLoc = argLocs.at(i >= argLocs.size() ? argLocs.size() - 1 : i);
|
Location argLoc = argLocs.at(i >= argLocs.size() ? argLocs.size() - 1 : i);
|
||||||
|
|
||||||
reportErrors(tryUnify(stack.back(), argLoc, expectedArg, arg));
|
reportErrors(tryUnify(stack.back(), argLoc, expectedArg, arg, CountMismatch::Context::Arg, /* genericsOkay */ true));
|
||||||
|
reportErrors(reduceFamilies(arg, argLoc, NotNull{&testArena}, builtinTypes, &fake, true).errors);
|
||||||
|
|
||||||
++it;
|
++it;
|
||||||
++i;
|
++i;
|
||||||
|
@ -1018,7 +1031,8 @@ struct TypeChecker2
|
||||||
if (auto tail = it.tail())
|
if (auto tail = it.tail())
|
||||||
{
|
{
|
||||||
TypePackId remainingArgs = testArena.addTypePack(TypePack{std::move(slice), std::nullopt});
|
TypePackId remainingArgs = testArena.addTypePack(TypePack{std::move(slice), std::nullopt});
|
||||||
reportErrors(tryUnify(stack.back(), argLocs.back(), *tail, remainingArgs));
|
reportErrors(tryUnify(stack.back(), argLocs.back(), *tail, remainingArgs, CountMismatch::Context::Arg, /* genericsOkay */ true));
|
||||||
|
reportErrors(reduceFamilies(remainingArgs, argLocs.back(), NotNull{&testArena}, builtinTypes, &fake, true).errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1344,7 +1358,7 @@ struct TypeChecker2
|
||||||
else if (get<AnyType>(rightType) || get<ErrorType>(rightType))
|
else if (get<AnyType>(rightType) || get<ErrorType>(rightType))
|
||||||
return rightType;
|
return rightType;
|
||||||
|
|
||||||
if ((get<BlockedType>(leftType) || get<FreeType>(leftType)) && !isEquality && !isLogical)
|
if ((get<BlockedType>(leftType) || get<FreeType>(leftType) || get<GenericType>(leftType)) && !isEquality && !isLogical)
|
||||||
{
|
{
|
||||||
auto name = getIdentifierOfBaseVar(expr->left);
|
auto name = getIdentifierOfBaseVar(expr->left);
|
||||||
reportError(CannotInferBinaryOperation{expr->op, name,
|
reportError(CannotInferBinaryOperation{expr->op, name,
|
||||||
|
@ -1591,10 +1605,10 @@ struct TypeChecker2
|
||||||
TypeId computedType = lookupType(expr->expr);
|
TypeId computedType = lookupType(expr->expr);
|
||||||
|
|
||||||
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
// Note: As an optimization, we try 'number <: number | string' first, as that is the more likely case.
|
||||||
if (isSubtype(annotationType, computedType, stack.back()))
|
if (isSubtype(annotationType, computedType, stack.back(), true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isSubtype(computedType, annotationType, stack.back()))
|
if (isSubtype(computedType, annotationType, stack.back(), true))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
|
reportError(TypesAreUnrelated{computedType, annotationType}, expr->location);
|
||||||
|
@ -1679,6 +1693,10 @@ struct TypeChecker2
|
||||||
|
|
||||||
void visit(AstType* ty)
|
void visit(AstType* ty)
|
||||||
{
|
{
|
||||||
|
TypeId* resolvedTy = module->astResolvedTypes.find(ty);
|
||||||
|
if (resolvedTy)
|
||||||
|
checkForFamilyInhabitance(follow(*resolvedTy), ty->location);
|
||||||
|
|
||||||
if (auto t = ty->as<AstTypeReference>())
|
if (auto t = ty->as<AstTypeReference>())
|
||||||
return visit(t);
|
return visit(t);
|
||||||
else if (auto t = ty->as<AstTypeTable>())
|
else if (auto t = ty->as<AstTypeTable>())
|
||||||
|
@ -1989,11 +2007,12 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TID>
|
template<typename TID>
|
||||||
bool isSubtype(TID subTy, TID superTy, NotNull<Scope> scope)
|
bool isSubtype(TID subTy, TID superTy, NotNull<Scope> scope, bool genericsOkay = false)
|
||||||
{
|
{
|
||||||
TypeArena arena;
|
TypeArena arena;
|
||||||
Unifier u{NotNull{&normalizer}, Mode::Strict, scope, Location{}, Covariant};
|
Unifier u{NotNull{&normalizer}, Mode::Strict, scope, Location{}, Covariant};
|
||||||
u.useScopes = true;
|
u.hideousFixMeGenericsAreActuallyFree = genericsOkay;
|
||||||
|
u.enableScopeTests();
|
||||||
|
|
||||||
u.tryUnify(subTy, superTy);
|
u.tryUnify(subTy, superTy);
|
||||||
const bool ok = u.errors.empty() && u.log.empty();
|
const bool ok = u.errors.empty() && u.log.empty();
|
||||||
|
@ -2001,11 +2020,13 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TID>
|
template<typename TID>
|
||||||
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy, CountMismatch::Context context = CountMismatch::Arg)
|
ErrorVec tryUnify(NotNull<Scope> scope, const Location& location, TID subTy, TID superTy, CountMismatch::Context context = CountMismatch::Arg,
|
||||||
|
bool genericsOkay = false)
|
||||||
{
|
{
|
||||||
Unifier u{NotNull{&normalizer}, Mode::Strict, scope, location, Covariant};
|
Unifier u{NotNull{&normalizer}, Mode::Strict, scope, location, Covariant};
|
||||||
u.ctx = context;
|
u.ctx = context;
|
||||||
u.useScopes = true;
|
u.hideousFixMeGenericsAreActuallyFree = genericsOkay;
|
||||||
|
u.enableScopeTests();
|
||||||
u.tryUnify(subTy, superTy);
|
u.tryUnify(subTy, superTy);
|
||||||
|
|
||||||
return std::move(u.errors);
|
return std::move(u.errors);
|
||||||
|
|
310
Analysis/src/TypeFamily.cpp
Normal file
310
Analysis/src/TypeFamily.cpp
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
|
#include "Luau/VisitType.h"
|
||||||
|
#include "Luau/TxnLog.h"
|
||||||
|
#include "Luau/Substitution.h"
|
||||||
|
#include "Luau/ToString.h"
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTINTVARIABLE(LuauTypeFamilyGraphReductionMaximumSteps, 1'000'000);
|
||||||
|
|
||||||
|
namespace Luau
|
||||||
|
{
|
||||||
|
|
||||||
|
struct InstanceCollector : TypeOnceVisitor
|
||||||
|
{
|
||||||
|
std::deque<TypeId> tys;
|
||||||
|
std::deque<TypePackId> tps;
|
||||||
|
|
||||||
|
bool visit(TypeId ty, const TypeFamilyInstanceType&) override
|
||||||
|
{
|
||||||
|
// TypeOnceVisitor performs a depth-first traversal in the absence of
|
||||||
|
// cycles. This means that by pushing to the front of the queue, we will
|
||||||
|
// try to reduce deeper instances first if we start with the first thing
|
||||||
|
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
|
||||||
|
// we want to reduce the innermost Add<number, number> instantiation
|
||||||
|
// first.
|
||||||
|
tys.push_front(ty);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool visit(TypePackId tp, const TypeFamilyInstanceTypePack&) override
|
||||||
|
{
|
||||||
|
// TypeOnceVisitor performs a depth-first traversal in the absence of
|
||||||
|
// cycles. This means that by pushing to the front of the queue, we will
|
||||||
|
// try to reduce deeper instances first if we start with the first thing
|
||||||
|
// in the queue. Consider Add<Add<Add<number, number>, number>, number>:
|
||||||
|
// we want to reduce the innermost Add<number, number> instantiation
|
||||||
|
// first.
|
||||||
|
tps.push_front(tp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FamilyReducer
|
||||||
|
{
|
||||||
|
std::deque<TypeId> queuedTys;
|
||||||
|
std::deque<TypePackId> queuedTps;
|
||||||
|
DenseHashSet<const void*> irreducible{nullptr};
|
||||||
|
FamilyGraphReductionResult result;
|
||||||
|
Location location;
|
||||||
|
NotNull<TypeArena> arena;
|
||||||
|
NotNull<BuiltinTypes> builtins;
|
||||||
|
TxnLog* log = nullptr;
|
||||||
|
NotNull<const TxnLog> reducerLog;
|
||||||
|
bool force = false;
|
||||||
|
|
||||||
|
FamilyReducer(std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location, NotNull<TypeArena> arena,
|
||||||
|
NotNull<BuiltinTypes> builtins, TxnLog* log = nullptr, bool force = false)
|
||||||
|
: queuedTys(std::move(queuedTys))
|
||||||
|
, queuedTps(std::move(queuedTps))
|
||||||
|
, location(location)
|
||||||
|
, arena(arena)
|
||||||
|
, builtins(builtins)
|
||||||
|
, log(log)
|
||||||
|
, reducerLog(NotNull{log ? log : TxnLog::empty()})
|
||||||
|
, force(force)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SkipTestResult
|
||||||
|
{
|
||||||
|
Irreducible,
|
||||||
|
Defer,
|
||||||
|
Okay,
|
||||||
|
};
|
||||||
|
|
||||||
|
SkipTestResult testForSkippability(TypeId ty)
|
||||||
|
{
|
||||||
|
ty = reducerLog->follow(ty);
|
||||||
|
|
||||||
|
if (reducerLog->is<TypeFamilyInstanceType>(ty))
|
||||||
|
{
|
||||||
|
if (!irreducible.contains(ty))
|
||||||
|
return SkipTestResult::Defer;
|
||||||
|
else
|
||||||
|
return SkipTestResult::Irreducible;
|
||||||
|
}
|
||||||
|
else if (reducerLog->is<GenericType>(ty))
|
||||||
|
{
|
||||||
|
return SkipTestResult::Irreducible;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SkipTestResult::Okay;
|
||||||
|
}
|
||||||
|
|
||||||
|
SkipTestResult testForSkippability(TypePackId ty)
|
||||||
|
{
|
||||||
|
ty = reducerLog->follow(ty);
|
||||||
|
|
||||||
|
if (reducerLog->is<TypeFamilyInstanceTypePack>(ty))
|
||||||
|
{
|
||||||
|
if (!irreducible.contains(ty))
|
||||||
|
return SkipTestResult::Defer;
|
||||||
|
else
|
||||||
|
return SkipTestResult::Irreducible;
|
||||||
|
}
|
||||||
|
else if (reducerLog->is<GenericTypePack>(ty))
|
||||||
|
{
|
||||||
|
return SkipTestResult::Irreducible;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SkipTestResult::Okay;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void replace(T subject, T replacement)
|
||||||
|
{
|
||||||
|
if (log)
|
||||||
|
log->replace(subject, Unifiable::Bound{replacement});
|
||||||
|
else
|
||||||
|
asMutable(subject)->ty.template emplace<Unifiable::Bound<T>>(replacement);
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, TypeId>)
|
||||||
|
result.reducedTypes.insert(subject);
|
||||||
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
||||||
|
result.reducedPacks.insert(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void handleFamilyReduction(T subject, TypeFamilyReductionResult<T> reduction)
|
||||||
|
{
|
||||||
|
if (reduction.result)
|
||||||
|
replace(subject, *reduction.result);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
irreducible.insert(subject);
|
||||||
|
|
||||||
|
if (reduction.uninhabited || force)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<T, TypeId>)
|
||||||
|
result.errors.push_back(TypeError{location, UninhabitedTypeFamily{subject}});
|
||||||
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
||||||
|
result.errors.push_back(TypeError{location, UninhabitedTypePackFamily{subject}});
|
||||||
|
}
|
||||||
|
else if (!reduction.uninhabited && !force)
|
||||||
|
{
|
||||||
|
for (TypeId b : reduction.blockedTypes)
|
||||||
|
result.blockedTypes.insert(b);
|
||||||
|
|
||||||
|
for (TypePackId b : reduction.blockedPacks)
|
||||||
|
result.blockedPacks.insert(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool done()
|
||||||
|
{
|
||||||
|
return queuedTys.empty() && queuedTps.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename I>
|
||||||
|
bool testParameters(T subject, const I* tfit)
|
||||||
|
{
|
||||||
|
for (TypeId p : tfit->typeArguments)
|
||||||
|
{
|
||||||
|
SkipTestResult skip = testForSkippability(p);
|
||||||
|
|
||||||
|
if (skip == SkipTestResult::Irreducible)
|
||||||
|
{
|
||||||
|
irreducible.insert(subject);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (skip == SkipTestResult::Defer)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<T, TypeId>)
|
||||||
|
queuedTys.push_back(subject);
|
||||||
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
||||||
|
queuedTps.push_back(subject);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TypePackId p : tfit->packArguments)
|
||||||
|
{
|
||||||
|
SkipTestResult skip = testForSkippability(p);
|
||||||
|
|
||||||
|
if (skip == SkipTestResult::Irreducible)
|
||||||
|
{
|
||||||
|
irreducible.insert(subject);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (skip == SkipTestResult::Defer)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_same_v<T, TypeId>)
|
||||||
|
queuedTys.push_back(subject);
|
||||||
|
else if constexpr (std::is_same_v<T, TypePackId>)
|
||||||
|
queuedTps.push_back(subject);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stepType()
|
||||||
|
{
|
||||||
|
TypeId subject = reducerLog->follow(queuedTys.front());
|
||||||
|
queuedTys.pop_front();
|
||||||
|
|
||||||
|
if (irreducible.contains(subject))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (const TypeFamilyInstanceType* tfit = reducerLog->get<TypeFamilyInstanceType>(subject))
|
||||||
|
{
|
||||||
|
if (!testParameters(subject, tfit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypeId> result = tfit->family->reducer(tfit->typeArguments, tfit->packArguments, arena, builtins, reducerLog);
|
||||||
|
handleFamilyReduction(subject, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stepPack()
|
||||||
|
{
|
||||||
|
TypePackId subject = reducerLog->follow(queuedTps.front());
|
||||||
|
queuedTps.pop_front();
|
||||||
|
|
||||||
|
if (irreducible.contains(subject))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (const TypeFamilyInstanceTypePack* tfit = reducerLog->get<TypeFamilyInstanceTypePack>(subject))
|
||||||
|
{
|
||||||
|
if (!testParameters(subject, tfit))
|
||||||
|
return;
|
||||||
|
|
||||||
|
TypeFamilyReductionResult<TypePackId> result =
|
||||||
|
tfit->family->reducer(tfit->typeArguments, tfit->packArguments, arena, builtins, reducerLog);
|
||||||
|
handleFamilyReduction(subject, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void step()
|
||||||
|
{
|
||||||
|
if (!queuedTys.empty())
|
||||||
|
stepType();
|
||||||
|
else if (!queuedTps.empty())
|
||||||
|
stepPack();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static FamilyGraphReductionResult reduceFamiliesInternal(std::deque<TypeId> queuedTys, std::deque<TypePackId> queuedTps, Location location,
|
||||||
|
NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, TxnLog* log, bool force)
|
||||||
|
{
|
||||||
|
FamilyReducer reducer{std::move(queuedTys), std::move(queuedTps), location, arena, builtins, log, force};
|
||||||
|
int iterationCount = 0;
|
||||||
|
|
||||||
|
while (!reducer.done())
|
||||||
|
{
|
||||||
|
reducer.step();
|
||||||
|
|
||||||
|
++iterationCount;
|
||||||
|
if (iterationCount > DFInt::LuauTypeFamilyGraphReductionMaximumSteps)
|
||||||
|
{
|
||||||
|
reducer.result.errors.push_back(TypeError{location, CodeTooComplex{}});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::move(reducer.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
FamilyGraphReductionResult reduceFamilies(
|
||||||
|
TypeId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, TxnLog* log, bool force)
|
||||||
|
{
|
||||||
|
InstanceCollector collector;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
collector.traverse(entrypoint);
|
||||||
|
}
|
||||||
|
catch (RecursionLimitException&)
|
||||||
|
{
|
||||||
|
return FamilyGraphReductionResult{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, arena, builtins, log, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
FamilyGraphReductionResult reduceFamilies(
|
||||||
|
TypePackId entrypoint, Location location, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins, TxnLog* log, bool force)
|
||||||
|
{
|
||||||
|
InstanceCollector collector;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
collector.traverse(entrypoint);
|
||||||
|
}
|
||||||
|
catch (RecursionLimitException&)
|
||||||
|
{
|
||||||
|
return FamilyGraphReductionResult{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return reduceFamiliesInternal(std::move(collector.tys), std::move(collector.tps), location, arena, builtins, log, force);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Luau
|
|
@ -41,6 +41,7 @@ LUAU_FASTFLAG(LuauOccursIsntAlwaysFailure)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
|
LUAU_FASTFLAGVARIABLE(LuauTypecheckTypeguards, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
LUAU_FASTFLAGVARIABLE(LuauTinyControlFlowAnalysis, false)
|
||||||
LUAU_FASTFLAG(LuauRequirePathTrueModuleName)
|
LUAU_FASTFLAG(LuauRequirePathTrueModuleName)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauTypecheckClassTypeIndexers, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -2104,6 +2105,23 @@ std::optional<TypeId> TypeChecker::getIndexTypeFromTypeImpl(
|
||||||
const Property* prop = lookupClassProp(cls, name);
|
const Property* prop = lookupClassProp(cls, name);
|
||||||
if (prop)
|
if (prop)
|
||||||
return prop->type();
|
return prop->type();
|
||||||
|
|
||||||
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
|
{
|
||||||
|
if (auto indexer = cls->indexer)
|
||||||
|
{
|
||||||
|
// TODO: Property lookup should work with string singletons or unions thereof as the indexer key type.
|
||||||
|
ErrorVec errors = tryUnify(stringType, indexer->indexType, scope, location);
|
||||||
|
|
||||||
|
if (errors.empty())
|
||||||
|
return indexer->indexResultType;
|
||||||
|
|
||||||
|
if (addErrors)
|
||||||
|
reportError(location, UnknownProperty{type, name});
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (const UnionType* utv = get<UnionType>(type))
|
else if (const UnionType* utv = get<UnionType>(type))
|
||||||
{
|
{
|
||||||
|
@ -3295,14 +3313,38 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||||
}
|
}
|
||||||
else if (const ClassType* lhsClass = get<ClassType>(lhs))
|
else if (const ClassType* lhsClass = get<ClassType>(lhs))
|
||||||
{
|
{
|
||||||
const Property* prop = lookupClassProp(lhsClass, name);
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
if (!prop)
|
|
||||||
{
|
{
|
||||||
|
if (const Property* prop = lookupClassProp(lhsClass, name))
|
||||||
|
{
|
||||||
|
return prop->type();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto indexer = lhsClass->indexer)
|
||||||
|
{
|
||||||
|
Unifier state = mkUnifier(scope, expr.location);
|
||||||
|
state.tryUnify(stringType, indexer->indexType);
|
||||||
|
if (state.errors.empty())
|
||||||
|
{
|
||||||
|
state.log.commit();
|
||||||
|
return indexer->indexResultType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reportError(TypeError{expr.location, UnknownProperty{lhs, name}});
|
reportError(TypeError{expr.location, UnknownProperty{lhs, name}});
|
||||||
return errorRecoveryType(scope);
|
return errorRecoveryType(scope);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const Property* prop = lookupClassProp(lhsClass, name);
|
||||||
|
if (!prop)
|
||||||
|
{
|
||||||
|
reportError(TypeError{expr.location, UnknownProperty{lhs, name}});
|
||||||
|
return errorRecoveryType(scope);
|
||||||
|
}
|
||||||
|
|
||||||
return prop->type();
|
return prop->type();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (get<IntersectionType>(lhs))
|
else if (get<IntersectionType>(lhs))
|
||||||
{
|
{
|
||||||
|
@ -3344,23 +3386,57 @@ TypeId TypeChecker::checkLValueBinding(const ScopePtr& scope, const AstExprIndex
|
||||||
{
|
{
|
||||||
if (const ClassType* exprClass = get<ClassType>(exprType))
|
if (const ClassType* exprClass = get<ClassType>(exprType))
|
||||||
{
|
{
|
||||||
const Property* prop = lookupClassProp(exprClass, value->value.data);
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
if (!prop)
|
|
||||||
{
|
{
|
||||||
|
if (const Property* prop = lookupClassProp(exprClass, value->value.data))
|
||||||
|
{
|
||||||
|
return prop->type();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto indexer = exprClass->indexer)
|
||||||
|
{
|
||||||
|
unify(stringType, indexer->indexType, scope, expr.index->location);
|
||||||
|
return indexer->indexResultType;
|
||||||
|
}
|
||||||
|
|
||||||
reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}});
|
reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}});
|
||||||
return errorRecoveryType(scope);
|
return errorRecoveryType(scope);
|
||||||
}
|
}
|
||||||
return prop->type();
|
else
|
||||||
|
{
|
||||||
|
const Property* prop = lookupClassProp(exprClass, value->value.data);
|
||||||
|
if (!prop)
|
||||||
|
{
|
||||||
|
reportError(TypeError{expr.location, UnknownProperty{exprType, value->value.data}});
|
||||||
|
return errorRecoveryType(scope);
|
||||||
|
}
|
||||||
|
return prop->type();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (FFlag::LuauAllowIndexClassParameters)
|
else
|
||||||
{
|
{
|
||||||
if (const ClassType* exprClass = get<ClassType>(exprType))
|
if (FFlag::LuauTypecheckClassTypeIndexers)
|
||||||
{
|
{
|
||||||
if (isNonstrictMode())
|
if (const ClassType* exprClass = get<ClassType>(exprType))
|
||||||
return unknownType;
|
{
|
||||||
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
|
if (auto indexer = exprClass->indexer)
|
||||||
return errorRecoveryType(scope);
|
{
|
||||||
|
unify(indexType, indexer->indexType, scope, expr.index->location);
|
||||||
|
return indexer->indexResultType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FFlag::LuauAllowIndexClassParameters)
|
||||||
|
{
|
||||||
|
if (const ClassType* exprClass = get<ClassType>(exprType))
|
||||||
|
{
|
||||||
|
if (isNonstrictMode())
|
||||||
|
return unknownType;
|
||||||
|
reportError(TypeError{expr.location, DynamicPropertyLookupOnClassesUnsafe{exprType}});
|
||||||
|
return errorRecoveryType(scope);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ LUAU_FASTFLAG(LuauErrorRecoveryType)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
LUAU_FASTFLAGVARIABLE(LuauInstantiateInSubtyping, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
|
LUAU_FASTFLAGVARIABLE(LuauUninhabitedSubAnything2, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauVariadicAnyCanBeGeneric, false)
|
LUAU_FASTFLAGVARIABLE(LuauVariadicAnyCanBeGeneric, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauUnifyTwoOptions, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
|
LUAU_FASTFLAGVARIABLE(LuauMaintainScopesInUnifier, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
|
LUAU_FASTFLAGVARIABLE(LuauTransitiveSubtyping, false)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
|
LUAU_FASTFLAGVARIABLE(LuauOccursIsntAlwaysFailure, false)
|
||||||
|
@ -439,6 +441,30 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
if (superTy == subTy)
|
if (superTy == subTy)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (log.get<TypeFamilyInstanceType>(superTy))
|
||||||
|
{
|
||||||
|
// We do not report errors from reducing here. This is because we will
|
||||||
|
// "double-report" errors in some cases, like when trying to unify
|
||||||
|
// identical type family instantiations like Add<false, false> with
|
||||||
|
// Add<false, false>.
|
||||||
|
reduceFamilies(superTy, location, NotNull(types), builtinTypes, &log);
|
||||||
|
superTy = log.follow(superTy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.get<TypeFamilyInstanceType>(subTy))
|
||||||
|
{
|
||||||
|
reduceFamilies(subTy, location, NotNull(types), builtinTypes, &log);
|
||||||
|
subTy = log.follow(subTy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't reduce the families down and we still have type family types
|
||||||
|
// here, we are stuck. Nothing meaningful can be done here. We don't wish to
|
||||||
|
// report an error, either.
|
||||||
|
if (log.get<TypeFamilyInstanceType>(superTy) || log.get<TypeFamilyInstanceType>(subTy))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto superFree = log.getMutable<FreeType>(superTy);
|
auto superFree = log.getMutable<FreeType>(superTy);
|
||||||
auto subFree = log.getMutable<FreeType>(subTy);
|
auto subFree = log.getMutable<FreeType>(subTy);
|
||||||
|
|
||||||
|
@ -509,6 +535,49 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hideousFixMeGenericsAreActuallyFree)
|
||||||
|
{
|
||||||
|
auto superGeneric = log.getMutable<GenericType>(superTy);
|
||||||
|
auto subGeneric = log.getMutable<GenericType>(subTy);
|
||||||
|
|
||||||
|
if (superGeneric && subGeneric && subsumes(useScopes, superGeneric, subGeneric))
|
||||||
|
{
|
||||||
|
if (!occursCheck(subTy, superTy, /* reversed = */ false))
|
||||||
|
log.replace(subTy, BoundType(superTy));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (superGeneric && subGeneric)
|
||||||
|
{
|
||||||
|
if (!occursCheck(superTy, subTy, /* reversed = */ true))
|
||||||
|
log.replace(superTy, BoundType(subTy));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (superGeneric)
|
||||||
|
{
|
||||||
|
if (!occursCheck(superTy, subTy, /* reversed = */ true))
|
||||||
|
{
|
||||||
|
Widen widen{types, builtinTypes};
|
||||||
|
log.replace(superTy, BoundType(widen(subTy)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (subGeneric)
|
||||||
|
{
|
||||||
|
// Normally, if the subtype is free, it should not be bound to any, unknown, or error types.
|
||||||
|
// But for bug compatibility, we'll only apply this rule to unknown. Doing this will silence cascading type errors.
|
||||||
|
if (log.get<UnknownType>(superTy))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!occursCheck(subTy, superTy, /* reversed = */ false))
|
||||||
|
log.replace(subTy, BoundType(superTy));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (log.get<AnyType>(superTy))
|
if (log.get<AnyType>(superTy))
|
||||||
return tryUnifyWithAny(subTy, builtinTypes->anyType);
|
return tryUnifyWithAny(subTy, builtinTypes->anyType);
|
||||||
|
|
||||||
|
@ -687,8 +756,93 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||||
log.popSeen(superTy, subTy);
|
log.popSeen(superTy, subTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the passed type is an option, strip nil out.
|
||||||
|
*
|
||||||
|
* There is an important subtlety to be observed here:
|
||||||
|
*
|
||||||
|
* We want to do a peephole fix to unify the subtype relation A? <: B? where we
|
||||||
|
* instead peel off the options and relate A <: B instead, but only works if we
|
||||||
|
* are certain that neither A nor B are themselves optional.
|
||||||
|
*
|
||||||
|
* For instance, if we want to test that (boolean?)? <: boolean?, we must peel
|
||||||
|
* off both layers of optionality from the subTy.
|
||||||
|
*
|
||||||
|
* We must also handle unions that have more than two choices.
|
||||||
|
*
|
||||||
|
* eg (string | nil)? <: boolean?
|
||||||
|
*/
|
||||||
|
static std::optional<TypeId> unwrapOption(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, const TxnLog& log, TypeId ty, DenseHashSet<TypeId>& seen)
|
||||||
|
{
|
||||||
|
if (seen.find(ty))
|
||||||
|
return std::nullopt;
|
||||||
|
seen.insert(ty);
|
||||||
|
|
||||||
|
const UnionType* ut = get<UnionType>(follow(ty));
|
||||||
|
if (!ut)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (2 == ut->options.size())
|
||||||
|
{
|
||||||
|
if (isNil(follow(ut->options[0])))
|
||||||
|
{
|
||||||
|
std::optional<TypeId> doubleUnwrapped = unwrapOption(builtinTypes, arena, log, ut->options[1], seen);
|
||||||
|
return doubleUnwrapped.value_or(ut->options[1]);
|
||||||
|
}
|
||||||
|
if (isNil(follow(ut->options[1])))
|
||||||
|
{
|
||||||
|
std::optional<TypeId> doubleUnwrapped = unwrapOption(builtinTypes, arena, log, ut->options[0], seen);
|
||||||
|
return doubleUnwrapped.value_or(ut->options[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<TypeId> newOptions;
|
||||||
|
bool found = false;
|
||||||
|
for (TypeId t : ut)
|
||||||
|
{
|
||||||
|
t = log.follow(t);
|
||||||
|
if (isNil(t))
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
newOptions.insert(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
return std::nullopt;
|
||||||
|
else if (newOptions.empty())
|
||||||
|
return builtinTypes->neverType;
|
||||||
|
else if (1 == newOptions.size())
|
||||||
|
return *begin(newOptions);
|
||||||
|
else
|
||||||
|
return arena->addType(UnionType{std::vector<TypeId>(begin(newOptions), end(newOptions))});
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<TypeId> unwrapOption(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, const TxnLog& log, TypeId ty)
|
||||||
|
{
|
||||||
|
DenseHashSet<TypeId> seen{nullptr};
|
||||||
|
|
||||||
|
return unwrapOption(builtinTypes, arena, log, ty, seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionType* subUnion, TypeId superTy)
|
void Unifier::tryUnifyUnionWithType(TypeId subTy, const UnionType* subUnion, TypeId superTy)
|
||||||
{
|
{
|
||||||
|
// Peephole fix: A? <: B? if A <: B
|
||||||
|
//
|
||||||
|
// This works around issues that can arise if A or B is free. We do not
|
||||||
|
// want either of those types to be bound to nil.
|
||||||
|
if (FFlag::LuauUnifyTwoOptions)
|
||||||
|
{
|
||||||
|
if (auto subOption = unwrapOption(builtinTypes, NotNull{types}, log, subTy))
|
||||||
|
{
|
||||||
|
if (auto superOption = unwrapOption(builtinTypes, NotNull{types}, log, superTy))
|
||||||
|
return tryUnify_(*subOption, *superOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// A | B <: T if and only if A <: T and B <: T
|
// A | B <: T if and only if A <: T and B <: T
|
||||||
bool failed = false;
|
bool failed = false;
|
||||||
bool errorsSuppressed = true;
|
bool errorsSuppressed = true;
|
||||||
|
@ -1205,6 +1359,25 @@ void Unifier::tryUnifyNormalizedTypes(
|
||||||
const ClassType* superCtv = get<ClassType>(superClass);
|
const ClassType* superCtv = get<ClassType>(superClass);
|
||||||
LUAU_ASSERT(superCtv);
|
LUAU_ASSERT(superCtv);
|
||||||
|
|
||||||
|
if (FFlag::LuauUnifyTwoOptions)
|
||||||
|
{
|
||||||
|
if (variance == Invariant)
|
||||||
|
{
|
||||||
|
if (subCtv == superCtv)
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The only way we could care about superNegations is if
|
||||||
|
* one of them was equal to superCtv. However,
|
||||||
|
* normalization ensures that this is impossible.
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isSubclass(subCtv, superCtv))
|
if (isSubclass(subCtv, superCtv))
|
||||||
{
|
{
|
||||||
found = true;
|
found = true;
|
||||||
|
@ -1518,6 +1691,12 @@ struct WeirdIter
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void Unifier::enableScopeTests()
|
||||||
|
{
|
||||||
|
useScopes = true;
|
||||||
|
log.useScopes = true;
|
||||||
|
}
|
||||||
|
|
||||||
ErrorVec Unifier::canUnify(TypeId subTy, TypeId superTy)
|
ErrorVec Unifier::canUnify(TypeId subTy, TypeId superTy)
|
||||||
{
|
{
|
||||||
Unifier s = makeChildUnifier();
|
Unifier s = makeChildUnifier();
|
||||||
|
@ -1597,6 +1776,21 @@ void Unifier::tryUnify_(TypePackId subTp, TypePackId superTp, bool isFunctionCal
|
||||||
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
|
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (hideousFixMeGenericsAreActuallyFree && log.getMutable<GenericTypePack>(superTp))
|
||||||
|
{
|
||||||
|
if (!occursCheck(superTp, subTp, /* reversed = */ true))
|
||||||
|
{
|
||||||
|
Widen widen{types, builtinTypes};
|
||||||
|
log.replace(superTp, Unifiable::Bound<TypePackId>(widen(subTp)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (hideousFixMeGenericsAreActuallyFree && log.getMutable<GenericTypePack>(subTp))
|
||||||
|
{
|
||||||
|
if (!occursCheck(subTp, superTp, /* reversed = */ false))
|
||||||
|
{
|
||||||
|
log.replace(subTp, Unifiable::Bound<TypePackId>(superTp));
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (log.getMutable<Unifiable::Error>(superTp))
|
else if (log.getMutable<Unifiable::Error>(superTp))
|
||||||
tryUnifyWithAny(subTp, superTp);
|
tryUnifyWithAny(subTp, superTp);
|
||||||
else if (log.getMutable<Unifiable::Error>(subTp))
|
else if (log.getMutable<Unifiable::Error>(subTp))
|
||||||
|
@ -2611,7 +2805,10 @@ void Unifier::tryUnifyVariadics(TypePackId subTp, TypePackId superTp, bool rever
|
||||||
}
|
}
|
||||||
else if (get<GenericTypePack>(tail))
|
else if (get<GenericTypePack>(tail))
|
||||||
{
|
{
|
||||||
reportError(location, GenericError{"Cannot unify variadic and generic packs"});
|
if (!hideousFixMeGenericsAreActuallyFree)
|
||||||
|
reportError(location, GenericError{"Cannot unify variadic and generic packs"});
|
||||||
|
else
|
||||||
|
log.replace(tail, BoundTypePack{superTp});
|
||||||
}
|
}
|
||||||
else if (get<Unifiable::Error>(tail))
|
else if (get<Unifiable::Error>(tail))
|
||||||
{
|
{
|
||||||
|
@ -2732,7 +2929,7 @@ std::optional<TypeId> Unifier::findTablePropertyRespectingMeta(TypeId lhsType, N
|
||||||
TxnLog Unifier::combineLogsIntoIntersection(std::vector<TxnLog> logs)
|
TxnLog Unifier::combineLogsIntoIntersection(std::vector<TxnLog> logs)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||||
TxnLog result;
|
TxnLog result(useScopes);
|
||||||
for (TxnLog& log : logs)
|
for (TxnLog& log : logs)
|
||||||
result.concatAsIntersections(std::move(log), NotNull{types});
|
result.concatAsIntersections(std::move(log), NotNull{types});
|
||||||
return result;
|
return result;
|
||||||
|
@ -2741,7 +2938,7 @@ TxnLog Unifier::combineLogsIntoIntersection(std::vector<TxnLog> logs)
|
||||||
TxnLog Unifier::combineLogsIntoUnion(std::vector<TxnLog> logs)
|
TxnLog Unifier::combineLogsIntoUnion(std::vector<TxnLog> logs)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||||
TxnLog result;
|
TxnLog result(useScopes);
|
||||||
for (TxnLog& log : logs)
|
for (TxnLog& log : logs)
|
||||||
result.concatAsUnion(std::move(log), NotNull{types});
|
result.concatAsUnion(std::move(log), NotNull{types});
|
||||||
return result;
|
return result;
|
||||||
|
@ -2807,7 +3004,7 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
|
||||||
if (log.getMutable<ErrorType>(needle))
|
if (log.getMutable<ErrorType>(needle))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!log.getMutable<FreeType>(needle))
|
if (!log.getMutable<FreeType>(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is<GenericType>(needle)))
|
||||||
ice("Expected needle to be free");
|
ice("Expected needle to be free");
|
||||||
|
|
||||||
if (needle == haystack)
|
if (needle == haystack)
|
||||||
|
@ -2821,7 +3018,7 @@ bool Unifier::occursCheck(DenseHashSet<TypeId>& seen, TypeId needle, TypeId hays
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log.getMutable<FreeType>(haystack))
|
if (log.getMutable<FreeType>(haystack) || (hideousFixMeGenericsAreActuallyFree && log.is<GenericType>(haystack)))
|
||||||
return false;
|
return false;
|
||||||
else if (auto a = log.getMutable<UnionType>(haystack))
|
else if (auto a = log.getMutable<UnionType>(haystack))
|
||||||
{
|
{
|
||||||
|
@ -2865,7 +3062,7 @@ bool Unifier::occursCheck(DenseHashSet<TypePackId>& seen, TypePackId needle, Typ
|
||||||
if (log.getMutable<ErrorTypePack>(needle))
|
if (log.getMutable<ErrorTypePack>(needle))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!log.getMutable<FreeTypePack>(needle))
|
if (!log.getMutable<FreeTypePack>(needle) && !(hideousFixMeGenericsAreActuallyFree && log.is<GenericTypePack>(needle)))
|
||||||
ice("Expected needle pack to be free");
|
ice("Expected needle pack to be free");
|
||||||
|
|
||||||
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
|
RecursionLimiter _ra(&sharedState.counters.recursionCount, sharedState.counters.recursionLimit);
|
||||||
|
@ -2900,7 +3097,10 @@ Unifier Unifier::makeChildUnifier()
|
||||||
Unifier u = Unifier{normalizer, mode, scope, location, variance, &log};
|
Unifier u = Unifier{normalizer, mode, scope, location, variance, &log};
|
||||||
u.normalize = normalize;
|
u.normalize = normalize;
|
||||||
u.checkInhabited = checkInhabited;
|
u.checkInhabited = checkInhabited;
|
||||||
u.useScopes = useScopes;
|
|
||||||
|
if (useScopes)
|
||||||
|
u.enableScopeTests();
|
||||||
|
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
28
CLI/Repl.cpp
28
CLI/Repl.cpp
|
@ -27,6 +27,10 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef CALLGRIND
|
#ifdef CALLGRIND
|
||||||
#include <valgrind/callgrind.h>
|
#include <valgrind/callgrind.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -865,6 +869,7 @@ int replMain(int argc, char** argv)
|
||||||
int profile = 0;
|
int profile = 0;
|
||||||
bool coverage = false;
|
bool coverage = false;
|
||||||
bool interactive = false;
|
bool interactive = false;
|
||||||
|
bool codegenPerf = false;
|
||||||
|
|
||||||
// Set the mode if the user has explicitly specified one.
|
// Set the mode if the user has explicitly specified one.
|
||||||
int argStart = 1;
|
int argStart = 1;
|
||||||
|
@ -962,6 +967,11 @@ int replMain(int argc, char** argv)
|
||||||
{
|
{
|
||||||
codegen = true;
|
codegen = true;
|
||||||
}
|
}
|
||||||
|
else if (strcmp(argv[i], "--codegen-perf") == 0)
|
||||||
|
{
|
||||||
|
codegen = true;
|
||||||
|
codegenPerf = true;
|
||||||
|
}
|
||||||
else if (strcmp(argv[i], "--coverage") == 0)
|
else if (strcmp(argv[i], "--coverage") == 0)
|
||||||
{
|
{
|
||||||
coverage = true;
|
coverage = true;
|
||||||
|
@ -998,6 +1008,24 @@ int replMain(int argc, char** argv)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (codegenPerf)
|
||||||
|
{
|
||||||
|
#if __linux__
|
||||||
|
char path[128];
|
||||||
|
snprintf(path, sizeof(path), "/tmp/perf-%d.map", getpid());
|
||||||
|
|
||||||
|
// note, there's no need to close the log explicitly as it will be closed when the process exits
|
||||||
|
FILE* codegenPerfLog = fopen(path, "w");
|
||||||
|
|
||||||
|
Luau::CodeGen::setPerfLog(codegenPerfLog, [](void* context, uintptr_t addr, unsigned size, const char* symbol) {
|
||||||
|
fprintf(static_cast<FILE*>(context), "%016lx %08x %s\n", long(addr), size, symbol);
|
||||||
|
});
|
||||||
|
#else
|
||||||
|
fprintf(stderr, "--codegen-perf option is only supported on Linux\n");
|
||||||
|
return 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<std::string> files = getSourceFiles(argc, argv);
|
const std::vector<std::string> files = getSourceFiles(argc, argv);
|
||||||
if (mode == CliMode::Unknown)
|
if (mode == CliMode::Unknown)
|
||||||
{
|
{
|
||||||
|
|
|
@ -56,7 +56,7 @@ public:
|
||||||
void eor(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0);
|
void eor(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0);
|
||||||
void bic(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0);
|
void bic(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, int shift = 0);
|
||||||
void tst(RegisterA64 src1, RegisterA64 src2, int shift = 0);
|
void tst(RegisterA64 src1, RegisterA64 src2, int shift = 0);
|
||||||
void mvn(RegisterA64 dst, RegisterA64 src);
|
void mvn_(RegisterA64 dst, RegisterA64 src);
|
||||||
|
|
||||||
// Bitwise with immediate
|
// Bitwise with immediate
|
||||||
// Note: immediate must have a single contiguous sequence of 1 bits set of length 1..31
|
// Note: immediate must have a single contiguous sequence of 1 bits set of length 1..31
|
||||||
|
@ -199,7 +199,7 @@ private:
|
||||||
void placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op);
|
void placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op);
|
||||||
void placeI12(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op);
|
void placeI12(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op);
|
||||||
void placeI16(const char* name, RegisterA64 dst, int src, uint8_t op, int shift = 0);
|
void placeI16(const char* name, RegisterA64 dst, int src, uint8_t op, int shift = 0);
|
||||||
void placeA(const char* name, RegisterA64 dst, AddressA64 src, uint8_t op, uint8_t size, int sizelog);
|
void placeA(const char* name, RegisterA64 dst, AddressA64 src, uint16_t opsize, int sizelog);
|
||||||
void placeB(const char* name, Label& label, uint8_t op);
|
void placeB(const char* name, Label& label, uint8_t op);
|
||||||
void placeBC(const char* name, Label& label, uint8_t op, uint8_t cond);
|
void placeBC(const char* name, Label& label, uint8_t op, uint8_t cond);
|
||||||
void placeBCR(const char* name, Label& label, uint8_t op, RegisterA64 cond);
|
void placeBCR(const char* name, Label& label, uint8_t op, RegisterA64 cond);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Label.h"
|
#include "Luau/Label.h"
|
||||||
#include "Luau/ConditionX64.h"
|
#include "Luau/ConditionX64.h"
|
||||||
#include "Luau/OperandX64.h"
|
#include "Luau/OperandX64.h"
|
||||||
|
@ -250,6 +251,8 @@ private:
|
||||||
std::vector<Label> pendingLabels;
|
std::vector<Label> pendingLabels;
|
||||||
std::vector<uint32_t> labelLocations;
|
std::vector<uint32_t> labelLocations;
|
||||||
|
|
||||||
|
DenseHashMap<uint64_t, int32_t> constCache64;
|
||||||
|
|
||||||
bool finalized = false;
|
bool finalized = false;
|
||||||
|
|
||||||
size_t dataPos = 0;
|
size_t dataPos = 0;
|
||||||
|
|
|
@ -13,5 +13,7 @@ namespace CodeGen
|
||||||
void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, size_t& startOffset);
|
void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, size_t& startOffset);
|
||||||
void destroyBlockUnwindInfo(void* context, void* unwindData);
|
void destroyBlockUnwindInfo(void* context, void* unwindData);
|
||||||
|
|
||||||
|
bool isUnwindSupported();
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
struct lua_State;
|
struct lua_State;
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -17,7 +19,7 @@ void create(lua_State* L);
|
||||||
// Builds target function and all inner functions
|
// Builds target function and all inner functions
|
||||||
void compile(lua_State* L, int idx);
|
void compile(lua_State* L, int idx);
|
||||||
|
|
||||||
using annotatorFn = void (*)(void* context, std::string& result, int fid, int instpos);
|
using AnnotatorFn = void (*)(void* context, std::string& result, int fid, int instpos);
|
||||||
|
|
||||||
struct AssemblyOptions
|
struct AssemblyOptions
|
||||||
{
|
{
|
||||||
|
@ -28,12 +30,16 @@ struct AssemblyOptions
|
||||||
bool includeOutlinedCode = false;
|
bool includeOutlinedCode = false;
|
||||||
|
|
||||||
// Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id
|
// Optional annotator function can be provided to describe each instruction, it takes function id and sequential instruction id
|
||||||
annotatorFn annotator = nullptr;
|
AnnotatorFn annotator = nullptr;
|
||||||
void* annotatorContext = nullptr;
|
void* annotatorContext = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generates assembly for target function and all inner functions
|
// Generates assembly for target function and all inner functions
|
||||||
std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {});
|
std::string getAssembly(lua_State* L, int idx, AssemblyOptions options = {});
|
||||||
|
|
||||||
|
using PerfLogFn = void (*)(void* context, uintptr_t addr, unsigned size, const char* symbol);
|
||||||
|
|
||||||
|
void setPerfLog(void* context, PerfLogFn logFn);
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -184,7 +184,7 @@ void AssemblyBuilderA64::tst(RegisterA64 src1, RegisterA64 src2, int shift)
|
||||||
placeSR3("tst", dst, src1, src2, 0b11'01010, shift);
|
placeSR3("tst", dst, src1, src2, 0b11'01010, shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::mvn(RegisterA64 dst, RegisterA64 src)
|
void AssemblyBuilderA64::mvn_(RegisterA64 dst, RegisterA64 src)
|
||||||
{
|
{
|
||||||
placeSR2("mvn", dst, src, 0b01'01010, 0b1);
|
placeSR2("mvn", dst, src, 0b01'01010, 0b1);
|
||||||
}
|
}
|
||||||
|
@ -287,19 +287,19 @@ void AssemblyBuilderA64::ldr(RegisterA64 dst, AddressA64 src)
|
||||||
switch (dst.kind)
|
switch (dst.kind)
|
||||||
{
|
{
|
||||||
case KindA64::w:
|
case KindA64::w:
|
||||||
placeA("ldr", dst, src, 0b11100001, 0b10, /* sizelog= */ 2);
|
placeA("ldr", dst, src, 0b10'11100001, /* sizelog= */ 2);
|
||||||
break;
|
break;
|
||||||
case KindA64::x:
|
case KindA64::x:
|
||||||
placeA("ldr", dst, src, 0b11100001, 0b11, /* sizelog= */ 3);
|
placeA("ldr", dst, src, 0b11'11100001, /* sizelog= */ 3);
|
||||||
break;
|
break;
|
||||||
case KindA64::s:
|
case KindA64::s:
|
||||||
placeA("ldr", dst, src, 0b11110001, 0b10, /* sizelog= */ 2);
|
placeA("ldr", dst, src, 0b10'11110001, /* sizelog= */ 2);
|
||||||
break;
|
break;
|
||||||
case KindA64::d:
|
case KindA64::d:
|
||||||
placeA("ldr", dst, src, 0b11110001, 0b11, /* sizelog= */ 3);
|
placeA("ldr", dst, src, 0b11'11110001, /* sizelog= */ 3);
|
||||||
break;
|
break;
|
||||||
case KindA64::q:
|
case KindA64::q:
|
||||||
placeA("ldr", dst, src, 0b11110011, 0b00, /* sizelog= */ 4);
|
placeA("ldr", dst, src, 0b00'11110011, /* sizelog= */ 4);
|
||||||
break;
|
break;
|
||||||
case KindA64::none:
|
case KindA64::none:
|
||||||
LUAU_ASSERT(!"Unexpected register kind");
|
LUAU_ASSERT(!"Unexpected register kind");
|
||||||
|
@ -310,35 +310,35 @@ void AssemblyBuilderA64::ldrb(RegisterA64 dst, AddressA64 src)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(dst.kind == KindA64::w);
|
LUAU_ASSERT(dst.kind == KindA64::w);
|
||||||
|
|
||||||
placeA("ldrb", dst, src, 0b11100001, 0b00, /* sizelog= */ 0);
|
placeA("ldrb", dst, src, 0b00'11100001, /* sizelog= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::ldrh(RegisterA64 dst, AddressA64 src)
|
void AssemblyBuilderA64::ldrh(RegisterA64 dst, AddressA64 src)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(dst.kind == KindA64::w);
|
LUAU_ASSERT(dst.kind == KindA64::w);
|
||||||
|
|
||||||
placeA("ldrh", dst, src, 0b11100001, 0b01, /* sizelog= */ 1);
|
placeA("ldrh", dst, src, 0b01'11100001, /* sizelog= */ 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::ldrsb(RegisterA64 dst, AddressA64 src)
|
void AssemblyBuilderA64::ldrsb(RegisterA64 dst, AddressA64 src)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w);
|
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w);
|
||||||
|
|
||||||
placeA("ldrsb", dst, src, 0b11100010 | uint8_t(dst.kind == KindA64::w), 0b00, /* sizelog= */ 0);
|
placeA("ldrsb", dst, src, 0b00'11100010 | uint8_t(dst.kind == KindA64::w), /* sizelog= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::ldrsh(RegisterA64 dst, AddressA64 src)
|
void AssemblyBuilderA64::ldrsh(RegisterA64 dst, AddressA64 src)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w);
|
LUAU_ASSERT(dst.kind == KindA64::x || dst.kind == KindA64::w);
|
||||||
|
|
||||||
placeA("ldrsh", dst, src, 0b11100010 | uint8_t(dst.kind == KindA64::w), 0b01, /* sizelog= */ 1);
|
placeA("ldrsh", dst, src, 0b01'11100010 | uint8_t(dst.kind == KindA64::w), /* sizelog= */ 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::ldrsw(RegisterA64 dst, AddressA64 src)
|
void AssemblyBuilderA64::ldrsw(RegisterA64 dst, AddressA64 src)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(dst.kind == KindA64::x);
|
LUAU_ASSERT(dst.kind == KindA64::x);
|
||||||
|
|
||||||
placeA("ldrsw", dst, src, 0b11100010, 0b10, /* sizelog= */ 2);
|
placeA("ldrsw", dst, src, 0b10'11100010, /* sizelog= */ 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::ldp(RegisterA64 dst1, RegisterA64 dst2, AddressA64 src)
|
void AssemblyBuilderA64::ldp(RegisterA64 dst1, RegisterA64 dst2, AddressA64 src)
|
||||||
|
@ -356,19 +356,19 @@ void AssemblyBuilderA64::str(RegisterA64 src, AddressA64 dst)
|
||||||
switch (src.kind)
|
switch (src.kind)
|
||||||
{
|
{
|
||||||
case KindA64::w:
|
case KindA64::w:
|
||||||
placeA("str", src, dst, 0b11100000, 0b10, /* sizelog= */ 2);
|
placeA("str", src, dst, 0b10'11100000, /* sizelog= */ 2);
|
||||||
break;
|
break;
|
||||||
case KindA64::x:
|
case KindA64::x:
|
||||||
placeA("str", src, dst, 0b11100000, 0b11, /* sizelog= */ 3);
|
placeA("str", src, dst, 0b11'11100000, /* sizelog= */ 3);
|
||||||
break;
|
break;
|
||||||
case KindA64::s:
|
case KindA64::s:
|
||||||
placeA("str", src, dst, 0b11110000, 0b10, /* sizelog= */ 2);
|
placeA("str", src, dst, 0b10'11110000, /* sizelog= */ 2);
|
||||||
break;
|
break;
|
||||||
case KindA64::d:
|
case KindA64::d:
|
||||||
placeA("str", src, dst, 0b11110000, 0b11, /* sizelog= */ 3);
|
placeA("str", src, dst, 0b11'11110000, /* sizelog= */ 3);
|
||||||
break;
|
break;
|
||||||
case KindA64::q:
|
case KindA64::q:
|
||||||
placeA("str", src, dst, 0b11110010, 0b00, /* sizelog= */ 4);
|
placeA("str", src, dst, 0b00'11110010, /* sizelog= */ 4);
|
||||||
break;
|
break;
|
||||||
case KindA64::none:
|
case KindA64::none:
|
||||||
LUAU_ASSERT(!"Unexpected register kind");
|
LUAU_ASSERT(!"Unexpected register kind");
|
||||||
|
@ -379,14 +379,14 @@ void AssemblyBuilderA64::strb(RegisterA64 src, AddressA64 dst)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(src.kind == KindA64::w);
|
LUAU_ASSERT(src.kind == KindA64::w);
|
||||||
|
|
||||||
placeA("strb", src, dst, 0b11100000, 0b00, /* sizelog= */ 0);
|
placeA("strb", src, dst, 0b00'11100000, /* sizelog= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::strh(RegisterA64 src, AddressA64 dst)
|
void AssemblyBuilderA64::strh(RegisterA64 src, AddressA64 dst)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(src.kind == KindA64::w);
|
LUAU_ASSERT(src.kind == KindA64::w);
|
||||||
|
|
||||||
placeA("strh", src, dst, 0b11100000, 0b01, /* sizelog= */ 1);
|
placeA("strh", src, dst, 0b01'11100000, /* sizelog= */ 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::stp(RegisterA64 src1, RegisterA64 src2, AddressA64 dst)
|
void AssemblyBuilderA64::stp(RegisterA64 src1, RegisterA64 src2, AddressA64 dst)
|
||||||
|
@ -487,9 +487,12 @@ void AssemblyBuilderA64::adr(RegisterA64 dst, Label& label)
|
||||||
|
|
||||||
void AssemblyBuilderA64::fmov(RegisterA64 dst, RegisterA64 src)
|
void AssemblyBuilderA64::fmov(RegisterA64 dst, RegisterA64 src)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(dst.kind == KindA64::d && src.kind == KindA64::d);
|
LUAU_ASSERT(dst.kind == KindA64::d && (src.kind == KindA64::d || src.kind == KindA64::x));
|
||||||
|
|
||||||
placeR1("fmov", dst, src, 0b000'11110'01'1'0000'00'10000);
|
if (src.kind == KindA64::d)
|
||||||
|
placeR1("fmov", dst, src, 0b000'11110'01'1'0000'00'10000);
|
||||||
|
else
|
||||||
|
placeR1("fmov", dst, src, 0b000'11110'01'1'00'111'000000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::fmov(RegisterA64 dst, double src)
|
void AssemblyBuilderA64::fmov(RegisterA64 dst, double src)
|
||||||
|
@ -825,7 +828,7 @@ void AssemblyBuilderA64::placeI16(const char* name, RegisterA64 dst, int src, ui
|
||||||
commit();
|
commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 src, uint8_t op, uint8_t size, int sizelog)
|
void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 src, uint16_t opsize, int sizelog)
|
||||||
{
|
{
|
||||||
if (logText)
|
if (logText)
|
||||||
log(name, dst, src);
|
log(name, dst, src);
|
||||||
|
@ -833,15 +836,15 @@ void AssemblyBuilderA64::placeA(const char* name, RegisterA64 dst, AddressA64 sr
|
||||||
switch (src.kind)
|
switch (src.kind)
|
||||||
{
|
{
|
||||||
case AddressKindA64::imm:
|
case AddressKindA64::imm:
|
||||||
if (src.data >= 0 && (src.data >> sizelog) < 1024 && (src.data & ((1 << sizelog) - 1)) == 0)
|
if (unsigned(src.data >> sizelog) < 1024 && (src.data & ((1 << sizelog) - 1)) == 0)
|
||||||
place(dst.index | (src.base.index << 5) | ((src.data >> sizelog) << 10) | (op << 22) | (1 << 24) | (size << 30));
|
place(dst.index | (src.base.index << 5) | ((src.data >> sizelog) << 10) | (opsize << 22) | (1 << 24));
|
||||||
else if (src.data >= -256 && src.data <= 255)
|
else if (src.data >= -256 && src.data <= 255)
|
||||||
place(dst.index | (src.base.index << 5) | ((src.data & ((1 << 9) - 1)) << 12) | (op << 22) | (size << 30));
|
place(dst.index | (src.base.index << 5) | ((src.data & ((1 << 9) - 1)) << 12) | (opsize << 22));
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(!"Unable to encode large immediate offset");
|
LUAU_ASSERT(!"Unable to encode large immediate offset");
|
||||||
break;
|
break;
|
||||||
case AddressKindA64::reg:
|
case AddressKindA64::reg:
|
||||||
place(dst.index | (src.base.index << 5) | (0b10 << 10) | (0b011 << 13) | (src.offset.index << 16) | (1 << 21) | (op << 22) | (size << 30));
|
place(dst.index | (src.base.index << 5) | (0b011'0'10 << 10) | (src.offset.index << 16) | (1 << 21) | (opsize << 22));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ static ABIX64 getCurrentX64ABI()
|
||||||
AssemblyBuilderX64::AssemblyBuilderX64(bool logText, ABIX64 abi)
|
AssemblyBuilderX64::AssemblyBuilderX64(bool logText, ABIX64 abi)
|
||||||
: logText(logText)
|
: logText(logText)
|
||||||
, abi(abi)
|
, abi(abi)
|
||||||
|
, constCache64(~0ull)
|
||||||
{
|
{
|
||||||
data.resize(4096);
|
data.resize(4096);
|
||||||
dataPos = data.size(); // data is filled backwards
|
dataPos = data.size(); // data is filled backwards
|
||||||
|
@ -885,9 +886,22 @@ void AssemblyBuilderX64::setLabel(Label& label)
|
||||||
|
|
||||||
OperandX64 AssemblyBuilderX64::i64(int64_t value)
|
OperandX64 AssemblyBuilderX64::i64(int64_t value)
|
||||||
{
|
{
|
||||||
|
uint64_t as64BitKey = value;
|
||||||
|
|
||||||
|
if (as64BitKey != ~0ull)
|
||||||
|
{
|
||||||
|
if (int32_t* prev = constCache64.find(as64BitKey))
|
||||||
|
return OperandX64(SizeX64::qword, noreg, 1, rip, *prev);
|
||||||
|
}
|
||||||
|
|
||||||
size_t pos = allocateData(8, 8);
|
size_t pos = allocateData(8, 8);
|
||||||
writeu64(&data[pos], value);
|
writeu64(&data[pos], value);
|
||||||
return OperandX64(SizeX64::qword, noreg, 1, rip, int32_t(pos - data.size()));
|
int32_t offset = int32_t(pos - data.size());
|
||||||
|
|
||||||
|
if (as64BitKey != ~0ull)
|
||||||
|
constCache64[as64BitKey] = offset;
|
||||||
|
|
||||||
|
return OperandX64(SizeX64::qword, noreg, 1, rip, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
OperandX64 AssemblyBuilderX64::f32(float value)
|
OperandX64 AssemblyBuilderX64::f32(float value)
|
||||||
|
@ -899,9 +913,24 @@ OperandX64 AssemblyBuilderX64::f32(float value)
|
||||||
|
|
||||||
OperandX64 AssemblyBuilderX64::f64(double value)
|
OperandX64 AssemblyBuilderX64::f64(double value)
|
||||||
{
|
{
|
||||||
|
uint64_t as64BitKey;
|
||||||
|
static_assert(sizeof(as64BitKey) == sizeof(value), "Expecting double to be 64-bit");
|
||||||
|
memcpy(&as64BitKey, &value, sizeof(value));
|
||||||
|
|
||||||
|
if (as64BitKey != ~0ull)
|
||||||
|
{
|
||||||
|
if (int32_t* prev = constCache64.find(as64BitKey))
|
||||||
|
return OperandX64(SizeX64::qword, noreg, 1, rip, *prev);
|
||||||
|
}
|
||||||
|
|
||||||
size_t pos = allocateData(8, 8);
|
size_t pos = allocateData(8, 8);
|
||||||
writef64(&data[pos], value);
|
writef64(&data[pos], value);
|
||||||
return OperandX64(SizeX64::qword, noreg, 1, rip, int32_t(pos - data.size()));
|
int32_t offset = int32_t(pos - data.size());
|
||||||
|
|
||||||
|
if (as64BitKey != ~0ull)
|
||||||
|
constCache64[as64BitKey] = offset;
|
||||||
|
|
||||||
|
return OperandX64(SizeX64::qword, noreg, 1, rip, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
OperandX64 AssemblyBuilderX64::f32x4(float x, float y, float z, float w)
|
OperandX64 AssemblyBuilderX64::f32x4(float x, float y, float z, float w)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
#endif
|
#endif
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
#elif !defined(_WIN32)
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
|
|
||||||
// Defined in unwind.h which may not be easily discoverable on various platforms
|
// Defined in unwind.h which may not be easily discoverable on various platforms
|
||||||
extern "C" void __register_frame(const void*);
|
extern "C" void __register_frame(const void*);
|
||||||
|
@ -26,12 +26,16 @@ extern "C" void __unw_add_dynamic_fde() __attribute__((weak));
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && defined(__aarch64__)
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
{
|
{
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
static void visitFdeEntries(char* pos, void (*cb)(const void*))
|
static void visitFdeEntries(char* pos, void (*cb)(const void*))
|
||||||
{
|
{
|
||||||
// When using glibc++ unwinder, we need to call __register_frame/__deregister_frame on the entire .eh_frame data
|
// When using glibc++ unwinder, we need to call __register_frame/__deregister_frame on the entire .eh_frame data
|
||||||
|
@ -78,7 +82,7 @@ void* createBlockUnwindInfo(void* context, uint8_t* block, size_t blockSize, siz
|
||||||
LUAU_ASSERT(!"failed to allocate function table");
|
LUAU_ASSERT(!"failed to allocate function table");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#elif !defined(_WIN32)
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
visitFdeEntries(unwindData, __register_frame);
|
visitFdeEntries(unwindData, __register_frame);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -91,10 +95,26 @@ void destroyBlockUnwindInfo(void* context, void* unwindData)
|
||||||
#if defined(_WIN32) && defined(_M_X64)
|
#if defined(_WIN32) && defined(_M_X64)
|
||||||
if (!RtlDeleteFunctionTable((RUNTIME_FUNCTION*)unwindData))
|
if (!RtlDeleteFunctionTable((RUNTIME_FUNCTION*)unwindData))
|
||||||
LUAU_ASSERT(!"failed to deallocate function table");
|
LUAU_ASSERT(!"failed to deallocate function table");
|
||||||
#elif !defined(_WIN32)
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
visitFdeEntries((char*)unwindData, __deregister_frame);
|
visitFdeEntries((char*)unwindData, __deregister_frame);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isUnwindSupported()
|
||||||
|
{
|
||||||
|
#if defined(_WIN32) && defined(_M_X64)
|
||||||
|
return true;
|
||||||
|
#elif defined(__APPLE__) && defined(__aarch64__)
|
||||||
|
char ver[256];
|
||||||
|
size_t verLength = sizeof(ver);
|
||||||
|
// libunwind on macOS 12 and earlier (which maps to osrelease 21) assumes JIT frames use pointer authentication without a way to override that
|
||||||
|
return sysctlbyname("kern.osrelease", ver, &verLength, NULL, 0) == 0 && atoi(ver) >= 22;
|
||||||
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -58,6 +58,9 @@ namespace Luau
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
{
|
{
|
||||||
|
|
||||||
|
static void* gPerfLogContext = nullptr;
|
||||||
|
static PerfLogFn gPerfLogFn = nullptr;
|
||||||
|
|
||||||
static NativeProto* createNativeProto(Proto* proto, const IrBuilder& ir)
|
static NativeProto* createNativeProto(Proto* proto, const IrBuilder& ir)
|
||||||
{
|
{
|
||||||
int sizecode = proto->sizecode;
|
int sizecode = proto->sizecode;
|
||||||
|
@ -87,6 +90,20 @@ static void destroyNativeProto(NativeProto* nativeProto)
|
||||||
::operator delete(memory);
|
::operator delete(memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void logPerfFunction(Proto* p, uintptr_t addr, unsigned size)
|
||||||
|
{
|
||||||
|
LUAU_ASSERT(p->source);
|
||||||
|
|
||||||
|
const char* source = getstr(p->source);
|
||||||
|
source = (source[0] == '=' || source[0] == '@') ? source + 1 : "[string]";
|
||||||
|
|
||||||
|
char name[256];
|
||||||
|
snprintf(name, sizeof(name), "<luau> %s:%d %s", source, p->linedefined, p->debugname ? getstr(p->debugname) : "");
|
||||||
|
|
||||||
|
if (gPerfLogFn)
|
||||||
|
gPerfLogFn(gPerfLogContext, addr, size, name);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename AssemblyBuilder, typename IrLowering>
|
template<typename AssemblyBuilder, typename IrLowering>
|
||||||
static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options)
|
static bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options)
|
||||||
{
|
{
|
||||||
|
@ -329,24 +346,17 @@ static void onDestroyFunction(lua_State* L, Proto* proto)
|
||||||
|
|
||||||
static int onEnter(lua_State* L, Proto* proto)
|
static int onEnter(lua_State* L, Proto* proto)
|
||||||
{
|
{
|
||||||
if (L->singlestep)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
NativeState* data = getNativeState(L);
|
NativeState* data = getNativeState(L);
|
||||||
|
|
||||||
if (!L->ci->savedpc)
|
|
||||||
L->ci->savedpc = proto->code;
|
|
||||||
|
|
||||||
// We will jump into native code through a gateway
|
|
||||||
bool (*gate)(lua_State*, Proto*, uintptr_t, NativeContext*) = (bool (*)(lua_State*, Proto*, uintptr_t, NativeContext*))data->context.gateEntry;
|
|
||||||
|
|
||||||
NativeProto* nativeProto = getProtoExecData(proto);
|
NativeProto* nativeProto = getProtoExecData(proto);
|
||||||
|
|
||||||
|
LUAU_ASSERT(nativeProto);
|
||||||
|
LUAU_ASSERT(L->ci->savedpc);
|
||||||
|
|
||||||
// instOffsets uses negative indexing for optimal codegen for RETURN opcode
|
// instOffsets uses negative indexing for optimal codegen for RETURN opcode
|
||||||
uintptr_t target = nativeProto->instBase + nativeProto->instOffsets[-(L->ci->savedpc - proto->code)];
|
uintptr_t target = nativeProto->instBase + nativeProto->instOffsets[proto->code - L->ci->savedpc];
|
||||||
|
|
||||||
// Returns 1 to finish the function in the VM
|
// Returns 1 to finish the function in the VM
|
||||||
return gate(L, proto, target, &data->context);
|
return GateFn(data->context.gateEntry)(L, proto, target, &data->context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void onSetBreakpoint(lua_State* L, Proto* proto, int instruction)
|
static void onSetBreakpoint(lua_State* L, Proto* proto, int instruction)
|
||||||
|
@ -375,9 +385,9 @@ static unsigned int getCpuFeaturesA64()
|
||||||
|
|
||||||
bool isSupported()
|
bool isSupported()
|
||||||
{
|
{
|
||||||
#if !LUA_CUSTOM_EXECUTION
|
if (!LUA_CUSTOM_EXECUTION)
|
||||||
return false;
|
return false;
|
||||||
#elif defined(__x86_64__) || defined(_M_X64)
|
|
||||||
if (LUA_EXTRA_SIZE != 1)
|
if (LUA_EXTRA_SIZE != 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -387,6 +397,16 @@ bool isSupported()
|
||||||
if (sizeof(LuaNode) != 32)
|
if (sizeof(LuaNode) != 32)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Windows CRT uses stack unwinding in longjmp so we have to use unwind data; on other platforms, it's only necessary for C++ EH.
|
||||||
|
#if defined(_WIN32)
|
||||||
|
if (!isUnwindSupported())
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
if (!LUA_USE_LONGJMP && !isUnwindSupported())
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__x86_64__) || defined(_M_X64)
|
||||||
int cpuinfo[4] = {};
|
int cpuinfo[4] = {};
|
||||||
#ifdef _MSC_VER
|
#ifdef _MSC_VER
|
||||||
__cpuid(cpuinfo, 1);
|
__cpuid(cpuinfo, 1);
|
||||||
|
@ -402,21 +422,6 @@ bool isSupported()
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#elif defined(__aarch64__)
|
#elif defined(__aarch64__)
|
||||||
if (LUA_EXTRA_SIZE != 1)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (sizeof(TValue) != 16)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (sizeof(LuaNode) != 32)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
// Unwind info is not supported for Windows-on-ARM yet
|
|
||||||
if (!LUA_USE_LONGJMP)
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
|
@ -456,6 +461,9 @@ void create(lua_State* L)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (gPerfLogFn)
|
||||||
|
gPerfLogFn(gPerfLogContext, uintptr_t(data.context.gateEntry), 4096, "<luau gate>");
|
||||||
|
|
||||||
lua_ExecutionCallbacks* ecb = getExecutionCallbacks(L);
|
lua_ExecutionCallbacks* ecb = getExecutionCallbacks(L);
|
||||||
|
|
||||||
ecb->close = onCloseState;
|
ecb->close = onCloseState;
|
||||||
|
@ -540,6 +548,20 @@ void compile(lua_State* L, int idx)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gPerfLogFn && results.size() > 0)
|
||||||
|
{
|
||||||
|
gPerfLogFn(gPerfLogContext, uintptr_t(codeStart), results[0]->instOffsets[0], "<luau helpers>");
|
||||||
|
|
||||||
|
for (size_t i = 0; i < results.size(); ++i)
|
||||||
|
{
|
||||||
|
uint32_t begin = results[i]->instOffsets[0];
|
||||||
|
uint32_t end = i + 1 < results.size() ? results[i + 1]->instOffsets[0] : uint32_t(build.code.size() * sizeof(build.code[0]));
|
||||||
|
LUAU_ASSERT(begin < end);
|
||||||
|
|
||||||
|
logPerfFunction(results[i]->proto, uintptr_t(codeStart) + begin, end - begin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Record instruction base address; at runtime, instOffsets[] will be used as offsets from instBase
|
// Record instruction base address; at runtime, instOffsets[] will be used as offsets from instBase
|
||||||
for (NativeProto* result : results)
|
for (NativeProto* result : results)
|
||||||
{
|
{
|
||||||
|
@ -591,5 +613,11 @@ std::string getAssembly(lua_State* L, int idx, AssemblyOptions options)
|
||||||
return build.text;
|
return build.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setPerfLog(void* context, PerfLogFn logFn)
|
||||||
|
{
|
||||||
|
gPerfLogContext = context;
|
||||||
|
gPerfLogFn = logFn;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace CodeGen
|
} // namespace CodeGen
|
||||||
} // namespace Luau
|
} // namespace Luau
|
||||||
|
|
|
@ -267,7 +267,7 @@ void callStepGc(IrRegAllocX64& regs, AssemblyBuilderX64& build)
|
||||||
void emitExit(AssemblyBuilderX64& build, bool continueInVm)
|
void emitExit(AssemblyBuilderX64& build, bool continueInVm)
|
||||||
{
|
{
|
||||||
if (continueInVm)
|
if (continueInVm)
|
||||||
build.mov(al, 1);
|
build.mov(eax, 1);
|
||||||
else
|
else
|
||||||
build.xor_(eax, eax);
|
build.xor_(eax, eax);
|
||||||
|
|
||||||
|
|
|
@ -1483,7 +1483,7 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, IrBlock& next)
|
||||||
{
|
{
|
||||||
inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a});
|
inst.regA64 = regs.allocReuse(KindA64::w, index, {inst.a});
|
||||||
RegisterA64 temp = tempUint(inst.a);
|
RegisterA64 temp = tempUint(inst.a);
|
||||||
build.mvn(inst.regA64, temp);
|
build.mvn_(inst.regA64, temp);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case IrCmd::BITLSHIFT_UINT:
|
case IrCmd::BITLSHIFT_UINT:
|
||||||
|
@ -1660,8 +1660,28 @@ RegisterA64 IrLoweringA64::tempDouble(IrOp op)
|
||||||
{
|
{
|
||||||
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
|
RegisterA64 temp1 = regs.allocTemp(KindA64::x);
|
||||||
RegisterA64 temp2 = regs.allocTemp(KindA64::d);
|
RegisterA64 temp2 = regs.allocTemp(KindA64::d);
|
||||||
build.adr(temp1, val);
|
|
||||||
build.ldr(temp2, temp1);
|
uint64_t vali;
|
||||||
|
static_assert(sizeof(vali) == sizeof(val), "Expecting double to be 64-bit");
|
||||||
|
memcpy(&vali, &val, sizeof(val));
|
||||||
|
|
||||||
|
if ((vali << 16) == 0)
|
||||||
|
{
|
||||||
|
build.movz(temp1, uint16_t(vali >> 48), 48);
|
||||||
|
build.fmov(temp2, temp1);
|
||||||
|
}
|
||||||
|
else if ((vali << 32) == 0)
|
||||||
|
{
|
||||||
|
build.movz(temp1, uint16_t(vali >> 48), 48);
|
||||||
|
build.movk(temp1, uint16_t(vali >> 32), 32);
|
||||||
|
build.fmov(temp2, temp1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
build.adr(temp1, val);
|
||||||
|
build.ldr(temp2, temp1);
|
||||||
|
}
|
||||||
|
|
||||||
return temp2;
|
return temp2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,21 +65,42 @@ void translateInstLoadN(IrBuilder& build, const Instruction* pc)
|
||||||
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void translateInstLoadConstant(IrBuilder& build, int ra, int k)
|
||||||
|
{
|
||||||
|
TValue protok = build.function.proto->k[k];
|
||||||
|
|
||||||
|
// Compiler only generates LOADK for source-level constants, so dynamic imports are not affected
|
||||||
|
if (protok.tt == LUA_TNIL)
|
||||||
|
{
|
||||||
|
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNIL));
|
||||||
|
}
|
||||||
|
else if (protok.tt == LUA_TBOOLEAN)
|
||||||
|
{
|
||||||
|
build.inst(IrCmd::STORE_INT, build.vmReg(ra), build.constInt(protok.value.b));
|
||||||
|
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TBOOLEAN));
|
||||||
|
}
|
||||||
|
else if (protok.tt == LUA_TNUMBER)
|
||||||
|
{
|
||||||
|
build.inst(IrCmd::STORE_DOUBLE, build.vmReg(ra), build.constDouble(protok.value.n));
|
||||||
|
build.inst(IrCmd::STORE_TAG, build.vmReg(ra), build.constTag(LUA_TNUMBER));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remaining tag here right now is LUA_TSTRING, while it can be transformed to LOAD_POINTER/STORE_POINTER/STORE_TAG, it's not profitable right
|
||||||
|
// now
|
||||||
|
IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(k));
|
||||||
|
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void translateInstLoadK(IrBuilder& build, const Instruction* pc)
|
void translateInstLoadK(IrBuilder& build, const Instruction* pc)
|
||||||
{
|
{
|
||||||
int ra = LUAU_INSN_A(*pc);
|
translateInstLoadConstant(build, LUAU_INSN_A(*pc), LUAU_INSN_D(*pc));
|
||||||
|
|
||||||
IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(LUAU_INSN_D(*pc)));
|
|
||||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void translateInstLoadKX(IrBuilder& build, const Instruction* pc)
|
void translateInstLoadKX(IrBuilder& build, const Instruction* pc)
|
||||||
{
|
{
|
||||||
int ra = LUAU_INSN_A(*pc);
|
translateInstLoadConstant(build, LUAU_INSN_A(*pc), pc[1]);
|
||||||
uint32_t aux = pc[1];
|
|
||||||
|
|
||||||
IrOp load = build.inst(IrCmd::LOAD_TVALUE, build.vmConst(aux));
|
|
||||||
build.inst(IrCmd::STORE_TVALUE, build.vmReg(ra), load);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void translateInstMove(IrBuilder& build, const Instruction* pc)
|
void translateInstMove(IrBuilder& build, const Instruction* pc)
|
||||||
|
|
|
@ -108,6 +108,8 @@ struct NativeContext
|
||||||
luau_FastFunction luauF_table[256] = {};
|
luau_FastFunction luauF_table[256] = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using GateFn = int (*)(lua_State*, Proto*, uintptr_t, NativeContext*);
|
||||||
|
|
||||||
struct NativeState
|
struct NativeState
|
||||||
{
|
{
|
||||||
NativeState();
|
NativeState();
|
||||||
|
|
|
@ -14,6 +14,8 @@ inline bool isFlagExperimental(const char* flag)
|
||||||
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
"LuauInstantiateInSubtyping", // requires some fixes to lua-apps code
|
||||||
"LuauTypecheckTypeguards", // requires some fixes to lua-apps code (CLI-67030)
|
"LuauTypecheckTypeguards", // requires some fixes to lua-apps code (CLI-67030)
|
||||||
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
"LuauTinyControlFlowAnalysis", // waiting for updates to packages depended by internal builtin plugins
|
||||||
|
"LuauUnifyTwoOptions", // requires some fixes to lua-apps code
|
||||||
|
|
||||||
// makes sure we always have at least one entry
|
// makes sure we always have at least one entry
|
||||||
nullptr,
|
nullptr,
|
||||||
};
|
};
|
||||||
|
|
|
@ -180,6 +180,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/include/Luau/TypeAttach.h
|
Analysis/include/Luau/TypeAttach.h
|
||||||
Analysis/include/Luau/TypeChecker2.h
|
Analysis/include/Luau/TypeChecker2.h
|
||||||
Analysis/include/Luau/TypedAllocator.h
|
Analysis/include/Luau/TypedAllocator.h
|
||||||
|
Analysis/include/Luau/TypeFamily.h
|
||||||
Analysis/include/Luau/TypeInfer.h
|
Analysis/include/Luau/TypeInfer.h
|
||||||
Analysis/include/Luau/TypePack.h
|
Analysis/include/Luau/TypePack.h
|
||||||
Analysis/include/Luau/TypeReduction.h
|
Analysis/include/Luau/TypeReduction.h
|
||||||
|
@ -230,6 +231,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/src/TypeAttach.cpp
|
Analysis/src/TypeAttach.cpp
|
||||||
Analysis/src/TypeChecker2.cpp
|
Analysis/src/TypeChecker2.cpp
|
||||||
Analysis/src/TypedAllocator.cpp
|
Analysis/src/TypedAllocator.cpp
|
||||||
|
Analysis/src/TypeFamily.cpp
|
||||||
Analysis/src/TypeInfer.cpp
|
Analysis/src/TypeInfer.cpp
|
||||||
Analysis/src/TypePack.cpp
|
Analysis/src/TypePack.cpp
|
||||||
Analysis/src/TypeReduction.cpp
|
Analysis/src/TypeReduction.cpp
|
||||||
|
@ -382,6 +384,8 @@ if(TARGET Luau.UnitTest)
|
||||||
tests/TopoSort.test.cpp
|
tests/TopoSort.test.cpp
|
||||||
tests/ToString.test.cpp
|
tests/ToString.test.cpp
|
||||||
tests/Transpiler.test.cpp
|
tests/Transpiler.test.cpp
|
||||||
|
tests/TxnLog.test.cpp
|
||||||
|
tests/TypeFamily.test.cpp
|
||||||
tests/TypeInfer.aliases.test.cpp
|
tests/TypeInfer.aliases.test.cpp
|
||||||
tests/TypeInfer.annotations.test.cpp
|
tests/TypeInfer.annotations.test.cpp
|
||||||
tests/TypeInfer.anyerror.test.cpp
|
tests/TypeInfer.anyerror.test.cpp
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
LUAU_FASTFLAGVARIABLE(LuauFixBreakpointLineSearch, false)
|
||||||
|
|
||||||
static const char* getfuncname(Closure* f);
|
static const char* getfuncname(Closure* f);
|
||||||
|
|
||||||
static int currentpc(lua_State* L, CallInfo* ci)
|
static int currentpc(lua_State* L, CallInfo* ci)
|
||||||
|
@ -423,11 +425,23 @@ static int getnextline(Proto* p, int line)
|
||||||
if (LUAU_INSN_OP(p->code[i]) == LOP_PREPVARARGS)
|
if (LUAU_INSN_OP(p->code[i]) == LOP_PREPVARARGS)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
int current = luaG_getline(p, i);
|
int candidate = luaG_getline(p, i);
|
||||||
if (current >= line)
|
|
||||||
|
if (FFlag::LuauFixBreakpointLineSearch)
|
||||||
{
|
{
|
||||||
closest = current;
|
if (candidate == line)
|
||||||
break;
|
return line;
|
||||||
|
|
||||||
|
if (candidate > line && (closest == -1 || candidate < closest))
|
||||||
|
closest = candidate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (candidate >= line)
|
||||||
|
{
|
||||||
|
closest = candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -436,9 +450,21 @@ static int getnextline(Proto* p, int line)
|
||||||
{
|
{
|
||||||
// Find the closest line number to the intended one.
|
// Find the closest line number to the intended one.
|
||||||
int candidate = getnextline(p->p[i], line);
|
int candidate = getnextline(p->p[i], line);
|
||||||
if (closest == -1 || (candidate >= line && candidate < closest))
|
|
||||||
|
if (FFlag::LuauFixBreakpointLineSearch)
|
||||||
{
|
{
|
||||||
closest = candidate;
|
if (candidate == line)
|
||||||
|
return line;
|
||||||
|
|
||||||
|
if (candidate > line && (closest == -1 || candidate < closest))
|
||||||
|
closest = candidate;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (closest == -1 || (candidate >= line && candidate < closest))
|
||||||
|
{
|
||||||
|
closest = candidate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -210,7 +210,7 @@ static void luau_execute(lua_State* L)
|
||||||
#if LUA_CUSTOM_EXECUTION
|
#if LUA_CUSTOM_EXECUTION
|
||||||
Proto* p = clvalue(L->ci->func)->l.p;
|
Proto* p = clvalue(L->ci->func)->l.p;
|
||||||
|
|
||||||
if (p->execdata)
|
if (p->execdata && !SingleStep)
|
||||||
{
|
{
|
||||||
if (L->global->ecb.enter(L, p) == 0)
|
if (L->global->ecb.enter(L, p) == 0)
|
||||||
return;
|
return;
|
||||||
|
@ -952,9 +952,9 @@ reentry:
|
||||||
L->top = p->is_vararg ? argi : ci->top;
|
L->top = p->is_vararg ? argi : ci->top;
|
||||||
|
|
||||||
#if LUA_CUSTOM_EXECUTION
|
#if LUA_CUSTOM_EXECUTION
|
||||||
if (p->execdata)
|
if (LUAU_UNLIKELY(p->execdata && !SingleStep))
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(L->global->ecb.enter);
|
ci->savedpc = p->code;
|
||||||
|
|
||||||
if (L->global->ecb.enter(L, p) == 1)
|
if (L->global->ecb.enter(L, p) == 1)
|
||||||
goto reentry;
|
goto reentry;
|
||||||
|
@ -1050,10 +1050,8 @@ reentry:
|
||||||
Proto* nextproto = nextcl->l.p;
|
Proto* nextproto = nextcl->l.p;
|
||||||
|
|
||||||
#if LUA_CUSTOM_EXECUTION
|
#if LUA_CUSTOM_EXECUTION
|
||||||
if (nextproto->execdata)
|
if (LUAU_UNLIKELY(nextproto->execdata && !SingleStep))
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(L->global->ecb.enter);
|
|
||||||
|
|
||||||
if (L->global->ecb.enter(L, nextproto) == 1)
|
if (L->global->ecb.enter(L, nextproto) == 1)
|
||||||
goto reentry;
|
goto reentry;
|
||||||
else
|
else
|
||||||
|
|
39
bench/tests/matrixmult.lua
Normal file
39
bench/tests/matrixmult.lua
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
||||||
|
|
||||||
|
local function mmul(matrix1, matrix2)
|
||||||
|
local shapeRows = #matrix1
|
||||||
|
local shapeColumns = #matrix2[1]
|
||||||
|
local result = table.create(shapeRows)
|
||||||
|
for i = 1, shapeRows do
|
||||||
|
result[i] = table.create(shapeColumns)
|
||||||
|
for j = 1, shapeColumns do
|
||||||
|
local sum = 0
|
||||||
|
for k = 1, shapeColumns do
|
||||||
|
sum = sum + matrix1[i][k] * matrix2[k][j]
|
||||||
|
end
|
||||||
|
result[i][j] = sum
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function test()
|
||||||
|
local n = 100
|
||||||
|
|
||||||
|
local mat = table.create(n)
|
||||||
|
for i = 1, n do
|
||||||
|
local t = table.create(n)
|
||||||
|
for k = 1, n do
|
||||||
|
t[k] = math.random()
|
||||||
|
end
|
||||||
|
mat[i] = t
|
||||||
|
end
|
||||||
|
|
||||||
|
local startTime = os.clock()
|
||||||
|
|
||||||
|
local result = mmul(mat, mat)
|
||||||
|
|
||||||
|
return os.clock() - startTime
|
||||||
|
end
|
||||||
|
|
||||||
|
bench.runCode(test, "matrixmult")
|
254
bench/tests/mesh-normal-scalar.lua
Normal file
254
bench/tests/mesh-normal-scalar.lua
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
--!strict
|
||||||
|
local bench = script and require(script.Parent.bench_support) or require("bench_support")
|
||||||
|
|
||||||
|
function test()
|
||||||
|
|
||||||
|
type Vertex = {
|
||||||
|
pX: number, pY: number, pZ: number,
|
||||||
|
uvX: number, uvY: number, uvZ: number,
|
||||||
|
nX: number, nY: number, nZ: number,
|
||||||
|
tX: number, tY: number, tZ: number,
|
||||||
|
bX: number, bY: number, bZ: number,
|
||||||
|
h: number
|
||||||
|
}
|
||||||
|
|
||||||
|
local grid_size = 100
|
||||||
|
|
||||||
|
local mesh: {
|
||||||
|
vertices: {Vertex},
|
||||||
|
indices: {number},
|
||||||
|
triangle_cone_p: {{x: number, y: number, z: number}},
|
||||||
|
triangle_cone_n: {{x: number, y: number, z: number}}
|
||||||
|
} = {
|
||||||
|
vertices = table.create(grid_size * grid_size),
|
||||||
|
indices = table.create((grid_size - 1) * (grid_size - 1) * 6),
|
||||||
|
triangle_cone_p = table.create((grid_size - 1) * (grid_size - 1) * 2),
|
||||||
|
triangle_cone_n = table.create((grid_size - 1) * (grid_size - 1) * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
local function init_vertices()
|
||||||
|
local i = 1
|
||||||
|
for y = 1,grid_size do
|
||||||
|
for x = 1,grid_size do
|
||||||
|
local v: Vertex = {}
|
||||||
|
|
||||||
|
v.pX = x
|
||||||
|
v.pY = y
|
||||||
|
v.pZ = math.cos(x) + math.sin(y)
|
||||||
|
|
||||||
|
v.uvX = (x-1)/(grid_size-1)
|
||||||
|
v.uvY = (y-1)/(grid_size-1)
|
||||||
|
v.uvZ = 0
|
||||||
|
|
||||||
|
v.nX = 0
|
||||||
|
v.nY = 0
|
||||||
|
v.nZ = 0
|
||||||
|
|
||||||
|
v.bX = 0
|
||||||
|
v.bY = 0
|
||||||
|
v.bZ = 0
|
||||||
|
|
||||||
|
v.tX = 0
|
||||||
|
v.tY = 0
|
||||||
|
v.tZ = 0
|
||||||
|
|
||||||
|
v.h = 0
|
||||||
|
|
||||||
|
mesh.vertices[i] = v
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function init_indices()
|
||||||
|
local i = 1
|
||||||
|
for y = 1,grid_size-1 do
|
||||||
|
for x = 1,grid_size-1 do
|
||||||
|
mesh.indices[i] = x + (y-1)*grid_size
|
||||||
|
i += 1
|
||||||
|
mesh.indices[i] = x + y*grid_size
|
||||||
|
i += 1
|
||||||
|
mesh.indices[i] = (x+1) + (y-1)*grid_size
|
||||||
|
i += 1
|
||||||
|
mesh.indices[i] = (x+1) + (y-1)*grid_size
|
||||||
|
i += 1
|
||||||
|
mesh.indices[i] = x + y*grid_size
|
||||||
|
i += 1
|
||||||
|
mesh.indices[i] = (x+1) + y*grid_size
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function calculate_normals()
|
||||||
|
local norm_sum = 0
|
||||||
|
|
||||||
|
for i = 1,#mesh.indices,3 do
|
||||||
|
local a = mesh.vertices[mesh.indices[i]]
|
||||||
|
local b = mesh.vertices[mesh.indices[i + 1]]
|
||||||
|
local c = mesh.vertices[mesh.indices[i + 2]]
|
||||||
|
|
||||||
|
local abx = a.pX - b.pX
|
||||||
|
local aby = a.pY - b.pY
|
||||||
|
local abz = a.pZ - b.pZ
|
||||||
|
|
||||||
|
local acx = a.pX - c.pX
|
||||||
|
local acy = a.pY - c.pY
|
||||||
|
local acz = a.pZ - c.pZ
|
||||||
|
|
||||||
|
local nx = aby * acz - abz * acy;
|
||||||
|
local ny = abz * acx - abx * acz;
|
||||||
|
local nz = abx * acy - aby * acx;
|
||||||
|
|
||||||
|
a.nX += nx
|
||||||
|
a.nY += ny
|
||||||
|
a.nZ += nz
|
||||||
|
|
||||||
|
b.nX += nx
|
||||||
|
b.nY += ny
|
||||||
|
b.nZ += nz
|
||||||
|
|
||||||
|
c.nX += nx
|
||||||
|
c.nY += ny
|
||||||
|
c.nZ += nz
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,v in mesh.vertices do
|
||||||
|
local magnitude = math.sqrt(v.nX * v.nX + v.nY * v.nY + v.nZ * v.nZ)
|
||||||
|
|
||||||
|
v.nX /= magnitude
|
||||||
|
v.nY /= magnitude
|
||||||
|
v.nZ /= magnitude
|
||||||
|
|
||||||
|
norm_sum += v.nX * v.nX + v.nY * v.nY + v.nZ * v.nZ
|
||||||
|
end
|
||||||
|
|
||||||
|
return norm_sum
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compute_triangle_cones()
|
||||||
|
local mesh_area = 0
|
||||||
|
|
||||||
|
local pos = 1
|
||||||
|
|
||||||
|
for i = 1,#mesh.indices,3 do
|
||||||
|
local p0 = mesh.vertices[mesh.indices[i]]
|
||||||
|
local p1 = mesh.vertices[mesh.indices[i + 1]]
|
||||||
|
local p2 = mesh.vertices[mesh.indices[i + 2]]
|
||||||
|
|
||||||
|
local p10x = p1.pX - p0.pX
|
||||||
|
local p10y = p1.pY - p0.pY
|
||||||
|
local p10z = p1.pZ - p0.pZ
|
||||||
|
local p20x = p2.pX - p0.pX
|
||||||
|
local p20y = p2.pY - p0.pY
|
||||||
|
local p20z = p2.pZ - p0.pZ
|
||||||
|
|
||||||
|
local normalx = p10y * p20z - p10z * p20y;
|
||||||
|
local normaly = p10z * p20x - p10x * p20z;
|
||||||
|
local normalz = p10x * p20y - p10y * p20x;
|
||||||
|
|
||||||
|
local area = math.sqrt(normalx * normalx + normaly * normaly + normalz * normalz)
|
||||||
|
local invarea = if area == 0 then 0 else 1 / area;
|
||||||
|
|
||||||
|
local rx = (p0.pX + p1.pX + p2.pX) / 3
|
||||||
|
local ry = (p0.pY + p1.pY + p2.pY) / 3
|
||||||
|
local rz = (p0.pZ + p1.pZ + p2.pZ) / 3
|
||||||
|
|
||||||
|
mesh.triangle_cone_p[pos] = { x = rx, y = ry, z = rz }
|
||||||
|
mesh.triangle_cone_n[pos] = { x = normalx * invarea, y = normaly * invarea, z = normalz * invarea}
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
mesh_area += area
|
||||||
|
end
|
||||||
|
|
||||||
|
return mesh_area
|
||||||
|
end
|
||||||
|
|
||||||
|
local function compute_tangent_space()
|
||||||
|
local checksum = 0
|
||||||
|
|
||||||
|
for i = 1,#mesh.indices,3 do
|
||||||
|
local a = mesh.vertices[mesh.indices[i]]
|
||||||
|
local b = mesh.vertices[mesh.indices[i + 1]]
|
||||||
|
local c = mesh.vertices[mesh.indices[i + 2]]
|
||||||
|
|
||||||
|
local x1 = b.pX - a.pX
|
||||||
|
local x2 = c.pX - a.pX
|
||||||
|
local y1 = b.pY - a.pY
|
||||||
|
local y2 = c.pY - a.pY
|
||||||
|
local z1 = b.pZ - a.pZ
|
||||||
|
local z2 = c.pZ - a.pZ
|
||||||
|
|
||||||
|
local s1 = b.uvX - a.uvX
|
||||||
|
local s2 = c.uvX - a.uvX
|
||||||
|
local t1 = b.uvY - a.uvY
|
||||||
|
local t2 = c.uvY - a.uvY
|
||||||
|
|
||||||
|
local r = 1.0 / (s1 * t2 - s2 * t1);
|
||||||
|
local sdirX = (t2 * x1 - t1 * x2) * r
|
||||||
|
local sdirY = (t2 * y1 - t1 * y2) * r
|
||||||
|
local sdirZ = (t2 * z1 - t1 * z2) * r
|
||||||
|
local tdirX = (s1 * x2 - s2 * x1) * r
|
||||||
|
local tdirY = (s1 * y2 - s2 * y1) * r
|
||||||
|
local tdirZ = (s1 * z2 - s2 * z1) * r
|
||||||
|
|
||||||
|
a.tX += sdirX
|
||||||
|
a.tY += sdirY
|
||||||
|
a.tZ += sdirZ
|
||||||
|
b.tX += sdirX
|
||||||
|
b.tY += sdirY
|
||||||
|
b.tZ += sdirZ
|
||||||
|
c.tX += sdirX
|
||||||
|
c.tY += sdirY
|
||||||
|
c.tZ += sdirZ
|
||||||
|
|
||||||
|
a.bX += tdirX
|
||||||
|
a.bY += tdirY
|
||||||
|
a.bZ += tdirZ
|
||||||
|
b.bX += tdirX
|
||||||
|
b.bY += tdirY
|
||||||
|
b.bZ += tdirZ
|
||||||
|
c.bX += tdirX
|
||||||
|
c.bY += tdirY
|
||||||
|
c.bZ += tdirZ
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,v in mesh.vertices do
|
||||||
|
local tX = v.tX
|
||||||
|
local tY = v.tY
|
||||||
|
local tZ = v.tZ
|
||||||
|
|
||||||
|
-- Gram-Schmidt orthogonalize
|
||||||
|
local ndt = v.nX * tX + v.nY * tY + v.nZ * tZ
|
||||||
|
local tmnsX = tX - v.nX * ndt
|
||||||
|
local tmnsY = tY - v.nY * ndt
|
||||||
|
local tmnsZ = tZ - v.nZ * ndt
|
||||||
|
local l = math.sqrt(tmnsX * tmnsX + tmnsY * tmnsY + tmnsZ * tmnsZ)
|
||||||
|
|
||||||
|
local invl = 1 / l
|
||||||
|
v.tX = tmnsX * invl
|
||||||
|
v.tY = tmnsY * invl
|
||||||
|
v.tZ = tmnsZ * invl
|
||||||
|
|
||||||
|
local normalx = v.nY * tZ - v.nZ * tY;
|
||||||
|
local normaly = v.nZ * tX - v.nX * tZ;
|
||||||
|
local normalz = v.nX * tY - v.nY * tX;
|
||||||
|
|
||||||
|
local ht = normalx * v.bX + normaly * v.bY + normalz * v.bZ
|
||||||
|
|
||||||
|
v.h = ht < 0 and -1 or 1
|
||||||
|
|
||||||
|
checksum += v.tX + v.h
|
||||||
|
end
|
||||||
|
|
||||||
|
return checksum
|
||||||
|
end
|
||||||
|
|
||||||
|
init_vertices()
|
||||||
|
init_indices()
|
||||||
|
calculate_normals()
|
||||||
|
compute_triangle_cones()
|
||||||
|
compute_tangent_space()
|
||||||
|
end
|
||||||
|
|
||||||
|
bench.runCode(test, "mesh-normal-scalar")
|
|
@ -70,7 +70,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Unary")
|
||||||
{
|
{
|
||||||
SINGLE_COMPARE(neg(x0, x1), 0xCB0103E0);
|
SINGLE_COMPARE(neg(x0, x1), 0xCB0103E0);
|
||||||
SINGLE_COMPARE(neg(w0, w1), 0x4B0103E0);
|
SINGLE_COMPARE(neg(w0, w1), 0x4B0103E0);
|
||||||
SINGLE_COMPARE(mvn(x0, x1), 0xAA2103E0);
|
SINGLE_COMPARE(mvn_(x0, x1), 0xAA2103E0);
|
||||||
|
|
||||||
SINGLE_COMPARE(clz(x0, x1), 0xDAC01020);
|
SINGLE_COMPARE(clz(x0, x1), 0xDAC01020);
|
||||||
SINGLE_COMPARE(clz(w0, w1), 0x5AC01020);
|
SINGLE_COMPARE(clz(w0, w1), 0x5AC01020);
|
||||||
|
@ -338,6 +338,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "AddressOfLabel")
|
||||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPBasic")
|
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPBasic")
|
||||||
{
|
{
|
||||||
SINGLE_COMPARE(fmov(d0, d1), 0x1E604020);
|
SINGLE_COMPARE(fmov(d0, d1), 0x1E604020);
|
||||||
|
SINGLE_COMPARE(fmov(d0, x1), 0x9E670020);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPMath")
|
TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "FPMath")
|
||||||
|
|
|
@ -684,15 +684,30 @@ TEST_CASE("ConstantStorage")
|
||||||
|
|
||||||
build.finalize();
|
build.finalize();
|
||||||
|
|
||||||
LUAU_ASSERT(build.data.size() == 12004);
|
CHECK(build.data.size() == 12004);
|
||||||
|
|
||||||
for (int i = 0; i <= 3000; i++)
|
for (int i = 0; i <= 3000; i++)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(build.data[i * 4 + 0] == 0x00);
|
CHECK(build.data[i * 4 + 0] == 0x00);
|
||||||
LUAU_ASSERT(build.data[i * 4 + 1] == 0x00);
|
CHECK(build.data[i * 4 + 1] == 0x00);
|
||||||
LUAU_ASSERT(build.data[i * 4 + 2] == 0x80);
|
CHECK(build.data[i * 4 + 2] == 0x80);
|
||||||
LUAU_ASSERT(build.data[i * 4 + 3] == 0x3f);
|
CHECK(build.data[i * 4 + 3] == 0x3f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("ConstantCaching")
|
||||||
|
{
|
||||||
|
AssemblyBuilderX64 build(/* logText= */ false);
|
||||||
|
|
||||||
|
OperandX64 two = build.f64(2);
|
||||||
|
|
||||||
|
// Force data relocation
|
||||||
|
for (int i = 0; i < 4096; i++)
|
||||||
|
build.f64(i);
|
||||||
|
|
||||||
|
CHECK(build.f64(2).imm == two.imm);
|
||||||
|
|
||||||
|
build.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -105,6 +105,19 @@ ClassFixture::ClassFixture()
|
||||||
};
|
};
|
||||||
globals.globalScope->exportedTypeBindings["CallableClass"] = TypeFun{{}, callableClassType};
|
globals.globalScope->exportedTypeBindings["CallableClass"] = TypeFun{{}, callableClassType};
|
||||||
|
|
||||||
|
auto addIndexableClass = [&arena, &globals](const char* className, TypeId keyType, TypeId returnType) {
|
||||||
|
ScopedFastFlag LuauTypecheckClassTypeIndexers("LuauTypecheckClassTypeIndexers", true);
|
||||||
|
TypeId indexableClassMetaType = arena.addType(TableType{});
|
||||||
|
TypeId indexableClassType =
|
||||||
|
arena.addType(ClassType{className, {}, nullopt, indexableClassMetaType, {}, {}, "Test", TableIndexer{keyType, returnType}});
|
||||||
|
globals.globalScope->exportedTypeBindings[className] = TypeFun{{}, indexableClassType};
|
||||||
|
};
|
||||||
|
|
||||||
|
// IndexableClass has a table indexer with a key type of 'number | string' and a return type of 'number'
|
||||||
|
addIndexableClass("IndexableClass", arena.addType(Luau::UnionType{{builtinTypes->stringType, numberType}}), numberType);
|
||||||
|
// IndexableNumericKeyClass has a table indexer with a key type of 'number' and a return type of 'number'
|
||||||
|
addIndexableClass("IndexableNumericKeyClass", numberType, numberType);
|
||||||
|
|
||||||
for (const auto& [name, tf] : globals.globalScope->exportedTypeBindings)
|
for (const auto& [name, tf] : globals.globalScope->exportedTypeBindings)
|
||||||
persist(tf.type);
|
persist(tf.type);
|
||||||
|
|
||||||
|
|
|
@ -646,6 +646,10 @@ static void throwing(int64_t arg)
|
||||||
|
|
||||||
TEST_CASE("GeneratedCodeExecutionWithThrowA64")
|
TEST_CASE("GeneratedCodeExecutionWithThrowA64")
|
||||||
{
|
{
|
||||||
|
// macOS 12 doesn't support JIT frames without pointer authentication
|
||||||
|
if (!isUnwindSupported())
|
||||||
|
return;
|
||||||
|
|
||||||
using namespace A64;
|
using namespace A64;
|
||||||
|
|
||||||
AssemblyBuilderA64 build(/* logText= */ false);
|
AssemblyBuilderA64 build(/* logText= */ false);
|
||||||
|
|
|
@ -561,6 +561,8 @@ TEST_CASE("Debug")
|
||||||
|
|
||||||
TEST_CASE("Debugger")
|
TEST_CASE("Debugger")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag luauFixBreakpointLineSearch{"LuauFixBreakpointLineSearch", true};
|
||||||
|
|
||||||
static int breakhits = 0;
|
static int breakhits = 0;
|
||||||
static lua_State* interruptedthread = nullptr;
|
static lua_State* interruptedthread = nullptr;
|
||||||
static bool singlestep = false;
|
static bool singlestep = false;
|
||||||
|
@ -703,6 +705,15 @@ TEST_CASE("Debugger")
|
||||||
CHECK(lua_tointeger(L, -1) == 9);
|
CHECK(lua_tointeger(L, -1) == 9);
|
||||||
lua_pop(L, 1);
|
lua_pop(L, 1);
|
||||||
}
|
}
|
||||||
|
else if (breakhits == 13)
|
||||||
|
{
|
||||||
|
// validate assignment via lua_getlocal
|
||||||
|
const char* l = lua_getlocal(L, 0, 1);
|
||||||
|
REQUIRE(l);
|
||||||
|
CHECK(strcmp(l, "a") == 0);
|
||||||
|
CHECK(lua_isnil(L, -1));
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
if (interruptedthread)
|
if (interruptedthread)
|
||||||
{
|
{
|
||||||
|
@ -712,7 +723,7 @@ TEST_CASE("Debugger")
|
||||||
},
|
},
|
||||||
nullptr, &copts, /* skipCodegen */ true); // Native code doesn't support debugging yet
|
nullptr, &copts, /* skipCodegen */ true); // Native code doesn't support debugging yet
|
||||||
|
|
||||||
CHECK(breakhits == 12); // 2 hits per breakpoint
|
CHECK(breakhits == 14); // 2 hits per breakpoint
|
||||||
|
|
||||||
if (singlestep)
|
if (singlestep)
|
||||||
CHECK(stephits > 100); // note; this will depend on number of instructions which can vary, so we just make sure the callback gets hit often
|
CHECK(stephits > 100); // note; this will depend on number of instructions which can vary, so we just make sure the callback gets hit often
|
||||||
|
|
113
tests/TxnLog.test.cpp
Normal file
113
tests/TxnLog.test.cpp
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
|
||||||
|
#include "doctest.h"
|
||||||
|
|
||||||
|
#include "Luau/Scope.h"
|
||||||
|
#include "Luau/ToString.h"
|
||||||
|
#include "Luau/TxnLog.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
|
#include "Luau/TypeArena.h"
|
||||||
|
|
||||||
|
#include "ScopedFlags.h"
|
||||||
|
|
||||||
|
using namespace Luau;
|
||||||
|
|
||||||
|
struct TxnLogFixture
|
||||||
|
{
|
||||||
|
TxnLog log{/*useScopes*/ true};
|
||||||
|
TxnLog log2{/*useScopes*/ true};
|
||||||
|
TypeArena arena;
|
||||||
|
BuiltinTypes builtinTypes;
|
||||||
|
|
||||||
|
ScopePtr globalScope = std::make_shared<Scope>(builtinTypes.anyTypePack);
|
||||||
|
ScopePtr childScope = std::make_shared<Scope>(globalScope);
|
||||||
|
|
||||||
|
TypeId a = arena.freshType(globalScope.get());
|
||||||
|
TypeId b = arena.freshType(globalScope.get());
|
||||||
|
TypeId c = arena.freshType(childScope.get());
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_SUITE_BEGIN("TxnLog");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_greater_scope")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||||
|
|
||||||
|
log.replace(c, BoundType{a});
|
||||||
|
log2.replace(a, BoundType{c});
|
||||||
|
|
||||||
|
CHECK(nullptr != log.pending(c));
|
||||||
|
|
||||||
|
log.concatAsUnion(std::move(log2), NotNull{&arena});
|
||||||
|
|
||||||
|
// 'a has greater scope than 'c, so we expect the incoming binding of 'a to
|
||||||
|
// be discarded.
|
||||||
|
|
||||||
|
CHECK(nullptr == log.pending(a));
|
||||||
|
|
||||||
|
const PendingType* pt = log.pending(c);
|
||||||
|
REQUIRE(pt != nullptr);
|
||||||
|
|
||||||
|
CHECK(!pt->dead);
|
||||||
|
const BoundType* bt = get_if<BoundType>(&pt->pending.ty);
|
||||||
|
|
||||||
|
CHECK(a == bt->boundTo);
|
||||||
|
|
||||||
|
log.commit();
|
||||||
|
|
||||||
|
REQUIRE(get<FreeType>(a));
|
||||||
|
|
||||||
|
const BoundType* bound = get<BoundType>(c);
|
||||||
|
REQUIRE(bound);
|
||||||
|
CHECK(a == bound->boundTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_union_incoming_type_has_lesser_scope")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||||
|
|
||||||
|
log.replace(a, BoundType{c});
|
||||||
|
log2.replace(c, BoundType{a});
|
||||||
|
|
||||||
|
CHECK(nullptr != log.pending(a));
|
||||||
|
|
||||||
|
log.concatAsUnion(std::move(log2), NotNull{&arena});
|
||||||
|
|
||||||
|
// 'a has greater scope than 'c, so we expect the binding of 'a to be
|
||||||
|
// discarded, and for that of 'c to be brought in.
|
||||||
|
|
||||||
|
CHECK(nullptr == log.pending(a));
|
||||||
|
|
||||||
|
const PendingType* pt = log.pending(c);
|
||||||
|
REQUIRE(pt != nullptr);
|
||||||
|
|
||||||
|
CHECK(!pt->dead);
|
||||||
|
const BoundType* bt = get_if<BoundType>(&pt->pending.ty);
|
||||||
|
|
||||||
|
CHECK(a == bt->boundTo);
|
||||||
|
|
||||||
|
log.commit();
|
||||||
|
|
||||||
|
REQUIRE(get<FreeType>(a));
|
||||||
|
|
||||||
|
const BoundType* bound = get<BoundType>(c);
|
||||||
|
REQUIRE(bound);
|
||||||
|
CHECK(a == bound->boundTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(TxnLogFixture, "colliding_coincident_logs_do_not_create_degenerate_unions")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||||
|
|
||||||
|
log.replace(a, BoundType{b});
|
||||||
|
log2.replace(a, BoundType{b});
|
||||||
|
|
||||||
|
log.concatAsUnion(std::move(log2), NotNull{&arena});
|
||||||
|
|
||||||
|
log.commit();
|
||||||
|
|
||||||
|
CHECK("a" == toString(a));
|
||||||
|
CHECK("a" == toString(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
205
tests/TypeFamily.test.cpp
Normal file
205
tests/TypeFamily.test.cpp
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
|
#include "Luau/Type.h"
|
||||||
|
|
||||||
|
#include "Fixture.h"
|
||||||
|
|
||||||
|
#include "doctest.h"
|
||||||
|
|
||||||
|
using namespace Luau;
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
|
struct FamilyFixture : Fixture
|
||||||
|
{
|
||||||
|
TypeFamily swapFamily;
|
||||||
|
|
||||||
|
FamilyFixture()
|
||||||
|
: Fixture(true, false)
|
||||||
|
{
|
||||||
|
swapFamily = TypeFamily{/* name */ "Swap",
|
||||||
|
/* reducer */
|
||||||
|
[](std::vector<TypeId> tys, std::vector<TypePackId> tps, NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtins,
|
||||||
|
NotNull<const TxnLog> log) -> TypeFamilyReductionResult<TypeId> {
|
||||||
|
LUAU_ASSERT(tys.size() == 1);
|
||||||
|
TypeId param = log->follow(tys.at(0));
|
||||||
|
|
||||||
|
if (isString(param))
|
||||||
|
{
|
||||||
|
return TypeFamilyReductionResult<TypeId>{builtins->numberType, false, {}, {}};
|
||||||
|
}
|
||||||
|
else if (isNumber(param))
|
||||||
|
{
|
||||||
|
return TypeFamilyReductionResult<TypeId>{builtins->stringType, false, {}, {}};
|
||||||
|
}
|
||||||
|
else if (log->get<BlockedType>(param) || log->get<FreeType>(param) || log->get<PendingExpansionType>(param) ||
|
||||||
|
log->get<TypeFamilyInstanceType>(param))
|
||||||
|
{
|
||||||
|
return TypeFamilyReductionResult<TypeId>{std::nullopt, false, {param}, {}};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TypeFamilyReductionResult<TypeId>{std::nullopt, true, {}, {}};
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
unfreeze(frontend.globals.globalTypes);
|
||||||
|
TypeId t = frontend.globals.globalTypes.addType(GenericType{"T"});
|
||||||
|
GenericTypeDefinition genericT{t};
|
||||||
|
|
||||||
|
ScopePtr globalScope = frontend.globals.globalScope;
|
||||||
|
globalScope->exportedTypeBindings["Swap"] =
|
||||||
|
TypeFun{{genericT}, frontend.globals.globalTypes.addType(TypeFamilyInstanceType{NotNull{&swapFamily}, {t}, {}})};
|
||||||
|
freeze(frontend.globals.globalTypes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_SUITE_BEGIN("TypeFamilyTests");
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FamilyFixture, "basic_type_family")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type A = Swap<number>
|
||||||
|
type B = Swap<string>
|
||||||
|
type C = Swap<boolean>
|
||||||
|
|
||||||
|
local x = 123
|
||||||
|
local y: Swap<typeof(x)> = "foo"
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK("string" == toString(requireTypeAlias("A")));
|
||||||
|
CHECK("number" == toString(requireTypeAlias("B")));
|
||||||
|
CHECK("Swap<boolean>" == toString(requireTypeAlias("C")));
|
||||||
|
CHECK("string" == toString(requireType("y")));
|
||||||
|
CHECK("Type family instance Swap<boolean> is uninhabited" == toString(result.errors[0]));
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FamilyFixture, "type_reduction_reduces_families")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x: Swap<string> & nil
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK("never" == toString(requireType("x")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_ret")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local swapper: <T>(T) -> Swap<T>
|
||||||
|
local a = swapper(123)
|
||||||
|
local b = swapper("foo")
|
||||||
|
local c = swapper(false)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK("string" == toString(requireType("a")));
|
||||||
|
CHECK("number" == toString(requireType("b")));
|
||||||
|
CHECK("Swap<boolean>" == toString(requireType("c")));
|
||||||
|
CHECK("Type family instance Swap<boolean> is uninhabited" == toString(result.errors[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FamilyFixture, "family_as_fn_arg")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local swapper: <T>(Swap<T>) -> T
|
||||||
|
local a = swapper(123)
|
||||||
|
local b = swapper(false)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||||
|
// FIXME: Can we constrain these to `never` or `unknown`?
|
||||||
|
CHECK("a" == toString(requireType("a")));
|
||||||
|
CHECK("a" == toString(requireType("b")));
|
||||||
|
CHECK("Type family instance Swap<a> is uninhabited" == toString(result.errors[0]));
|
||||||
|
CHECK("Type family instance Swap<a> is uninhabited" == toString(result.errors[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FamilyFixture, "resolve_deep_families")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x: Swap<Swap<Swap<string>>>
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
CHECK("number" == toString(requireType("x")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FamilyFixture, "unsolvable_family")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local impossible: <T>(Swap<T>) -> Swap<Swap<T>>
|
||||||
|
local a = impossible(123)
|
||||||
|
local b = impossible(true)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||||
|
for (size_t i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
CHECK(toString(result.errors[i]) == "Type family instance Swap<a> is uninhabited");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FamilyFixture, "table_internal_families")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local t: <T>({T}) -> {Swap<T>}
|
||||||
|
local a = t({1, 2, 3})
|
||||||
|
local b = t({"a", "b", "c"})
|
||||||
|
local c = t({true, false, true})
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK(toString(requireType("a")) == "{string}");
|
||||||
|
CHECK(toString(requireType("b")) == "{number}");
|
||||||
|
CHECK(toString(requireType("c")) == "{Swap<boolean>}");
|
||||||
|
CHECK(toString(result.errors[0]) == "Type family instance Swap<boolean> is uninhabited");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(FamilyFixture, "function_internal_families")
|
||||||
|
{
|
||||||
|
// This test is broken right now, but it's not because of type families. See
|
||||||
|
// CLI-71143.
|
||||||
|
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local f0: <T>(T) -> (() -> T)
|
||||||
|
local f: <T>(T) -> (() -> Swap<T>)
|
||||||
|
local a = f(1)
|
||||||
|
local b = f("a")
|
||||||
|
local c = f(true)
|
||||||
|
local d = f0(1)
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
CHECK(toString(requireType("a")) == "() -> string");
|
||||||
|
CHECK(toString(requireType("b")) == "() -> number");
|
||||||
|
CHECK(toString(requireType("c")) == "() -> Swap<boolean>");
|
||||||
|
CHECK(toString(result.errors[0]) == "Type family instance Swap<boolean> is uninhabited");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_SUITE_END();
|
|
@ -481,4 +481,150 @@ TEST_CASE_FIXTURE(ClassFixture, "callable_classes")
|
||||||
CHECK_EQ("number", toString(requireType("y")));
|
CHECK_EQ("number", toString(requireType("y")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
|
||||||
|
{
|
||||||
|
// Test reading from an index
|
||||||
|
ScopedFastFlag LuauTypecheckClassTypeIndexers("LuauTypecheckClassTypeIndexers", true);
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
local y = x.stringKey
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
local y = x["stringKey"]
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
local str : string
|
||||||
|
local y = x[str] -- Index with a non-const string
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
local y = x[7] -- Index with a numeric key
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test writing to an index
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
x.stringKey = 42
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
x["stringKey"] = 42
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
local str : string
|
||||||
|
x[str] = 42 -- Index with a non-const string
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
x[1] = 42 -- Index with a numeric key
|
||||||
|
)");
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to index the class using an invalid type for the key (key type is 'number | string'.)
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
local y = x[true]
|
||||||
|
)");
|
||||||
|
CHECK_EQ(
|
||||||
|
toString(result.errors[0]), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
x[true] = 42
|
||||||
|
)");
|
||||||
|
CHECK_EQ(
|
||||||
|
toString(result.errors[0]), "Type 'boolean' could not be converted into 'number | string'; none of the union options are compatible");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test type checking for the return type of the indexer (i.e. a number)
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
x.key = "string value"
|
||||||
|
)");
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Type 'string' could not be converted into 'number'");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableClass
|
||||||
|
local str : string = x.key
|
||||||
|
)");
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Type 'number' could not be converted into 'string'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that we string key are rejected if the indexer's key type is not compatible with string
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableNumericKeyClass
|
||||||
|
x.key = 1
|
||||||
|
)");
|
||||||
|
CHECK_EQ(toString(result.errors.at(0)), "Key 'key' not found in class 'IndexableNumericKeyClass'");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableNumericKeyClass
|
||||||
|
x["key"] = 1
|
||||||
|
)");
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Type 'string' could not be converted into 'number'");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableNumericKeyClass
|
||||||
|
local str : string
|
||||||
|
x[str] = 1 -- Index with a non-const string
|
||||||
|
)");
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Type 'string' could not be converted into 'number'");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableNumericKeyClass
|
||||||
|
local y = x.key
|
||||||
|
)");
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Key 'key' not found in class 'IndexableNumericKeyClass'");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableNumericKeyClass
|
||||||
|
local y = x["key"]
|
||||||
|
)");
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Type 'string' could not be converted into 'number'");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local x : IndexableNumericKeyClass
|
||||||
|
local str : string
|
||||||
|
local y = x[str] -- Index with a non-const string
|
||||||
|
)");
|
||||||
|
CHECK_EQ(toString(result.errors[0]), "Type 'string' could not be converted into 'number'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -1952,4 +1952,40 @@ TEST_CASE_FIXTURE(Fixture, "instantiated_type_packs_must_have_a_non_null_scope")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "inner_frees_become_generic_in_dcr")
|
||||||
|
{
|
||||||
|
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function f(x)
|
||||||
|
local z = x
|
||||||
|
return x
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
std::optional<TypeId> ty = findTypeAtPosition(Position{3, 19});
|
||||||
|
REQUIRE(ty);
|
||||||
|
CHECK(get<GenericType>(*ty));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "function_exprs_are_generalized_at_signature_scope_not_enclosing")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local foo
|
||||||
|
local bar
|
||||||
|
|
||||||
|
-- foo being a function expression is deliberate: the bug we're testing
|
||||||
|
-- only existed for function expressions, not for function statements.
|
||||||
|
foo = function(a)
|
||||||
|
return bar
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
// note that b is not in the generic list; it is free, the unconstrained type of `bar`.
|
||||||
|
CHECK(toString(requireType("foo")) == "<a>(a) -> b");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -550,6 +550,8 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables")
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauUnifyTwoOptions", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local x : { p : number?, q : any } & { p : unknown, q : string? }
|
local x : { p : number?, q : any } & { p : unknown, q : string? }
|
||||||
local y : { p : number?, q : string? } = x -- OK
|
local y : { p : number?, q : string? } = x -- OK
|
||||||
|
@ -563,27 +565,19 @@ TEST_CASE_FIXTURE(Fixture, "intersection_of_tables_with_top_properties")
|
||||||
CHECK_EQ(toString(result.errors[0]),
|
CHECK_EQ(toString(result.errors[0]),
|
||||||
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
|
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
|
||||||
"caused by:\n"
|
"caused by:\n"
|
||||||
" Property 'p' is not compatible. Type 'number?' could not be converted into 'string?'\n"
|
" Property 'p' is not compatible. Type 'number' could not be converted into 'string' in an invariant context");
|
||||||
"caused by:\n"
|
|
||||||
" Not all union options are compatible. Type 'number' could not be converted into 'string?'\n"
|
|
||||||
"caused by:\n"
|
|
||||||
" None of the union options are compatible. For example: Type 'number' could not be converted into 'string' in an invariant context");
|
|
||||||
|
|
||||||
CHECK_EQ(toString(result.errors[1]),
|
CHECK_EQ(toString(result.errors[1]),
|
||||||
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
|
"Type '{| p: number?, q: string? |}' could not be converted into '{| p: string?, q: number? |}'\n"
|
||||||
"caused by:\n"
|
"caused by:\n"
|
||||||
" Property 'q' is not compatible. Type 'string?' could not be converted into 'number?'\n"
|
" Property 'q' is not compatible. Type 'string' could not be converted into 'number' in an invariant context");
|
||||||
"caused by:\n"
|
|
||||||
" Not all union options are compatible. Type 'string' could not be converted into 'number?'\n"
|
|
||||||
"caused by:\n"
|
|
||||||
" None of the union options are compatible. For example: Type 'string' could not be converted into 'number' in an invariant context");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
CHECK_EQ(toString(result.errors[0]),
|
CHECK_EQ(toString(result.errors[0]),
|
||||||
"Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into '{| p: string?, "
|
"Type '{| p: number?, q: any |} & {| p: unknown, q: string? |}' could not be converted into "
|
||||||
"q: number? |}'; none of the intersection parts are compatible");
|
"'{| p: string?, q: number? |}'; none of the intersection parts are compatible");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1134,7 +1134,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "luau_polyfill_is_array_simplified")
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
@ -1179,4 +1179,30 @@ end
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "luau-polyfill.Array.startswith")
|
||||||
|
{
|
||||||
|
// This test also exercises whether the binary operator == passes the correct expected type
|
||||||
|
// to it's l,r operands
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
--!strict
|
||||||
|
local function startsWith(value: string, substring: string, position: number?): boolean
|
||||||
|
-- Luau FIXME: we have to use a tmp variable, as Luau doesn't understand the logic below narrow position to `number`
|
||||||
|
local position_
|
||||||
|
if position == nil or position < 1 then
|
||||||
|
position_ = 1
|
||||||
|
else
|
||||||
|
position_ = position
|
||||||
|
end
|
||||||
|
|
||||||
|
return value:find(substring, position_, true) == position_
|
||||||
|
end
|
||||||
|
|
||||||
|
return startsWith
|
||||||
|
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -482,41 +482,6 @@ TEST_CASE_FIXTURE(Fixture, "dcr_can_partially_dispatch_a_constraint")
|
||||||
CHECK("<a>(a, number) -> ()" == toString(requireType("prime_iter")));
|
CHECK("<a>(a, number) -> ()" == toString(requireType("prime_iter")));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "free_options_cannot_be_unified_together")
|
|
||||||
{
|
|
||||||
ScopedFastFlag sff[] = {
|
|
||||||
{"LuauTransitiveSubtyping", true},
|
|
||||||
};
|
|
||||||
|
|
||||||
TypeArena arena;
|
|
||||||
TypeId nilType = builtinTypes->nilType;
|
|
||||||
|
|
||||||
std::unique_ptr scope = std::make_unique<Scope>(builtinTypes->anyTypePack);
|
|
||||||
|
|
||||||
TypeId free1 = arena.addType(FreeType{scope.get()});
|
|
||||||
TypeId option1 = arena.addType(UnionType{{nilType, free1}});
|
|
||||||
|
|
||||||
TypeId free2 = arena.addType(FreeType{scope.get()});
|
|
||||||
TypeId option2 = arena.addType(UnionType{{nilType, free2}});
|
|
||||||
|
|
||||||
InternalErrorReporter iceHandler;
|
|
||||||
UnifierSharedState sharedState{&iceHandler};
|
|
||||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
|
||||||
Unifier u{NotNull{&normalizer}, Mode::Strict, NotNull{scope.get()}, Location{}, Variance::Covariant};
|
|
||||||
|
|
||||||
u.tryUnify(option1, option2);
|
|
||||||
|
|
||||||
CHECK(!u.failure);
|
|
||||||
|
|
||||||
u.log.commit();
|
|
||||||
|
|
||||||
ToStringOptions opts;
|
|
||||||
CHECK("a?" == toString(option1, opts));
|
|
||||||
|
|
||||||
// CHECK("a?" == toString(option2, opts)); // This should hold, but does not.
|
|
||||||
CHECK("b?" == toString(option2, opts)); // This should not hold.
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "for_in_loop_with_zero_iterators")
|
||||||
{
|
{
|
||||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
|
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", false};
|
||||||
|
|
|
@ -390,6 +390,8 @@ TEST_CASE_FIXTURE(Fixture, "widen_the_supertype_if_it_is_free_and_subtype_has_si
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
|
TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{"LuauUnifyTwoOptions", true};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function foo(f, x): "hello"? -- anyone there?
|
local function foo(f, x): "hello"? -- anyone there?
|
||||||
return if x == "hi"
|
return if x == "hi"
|
||||||
|
@ -401,7 +403,9 @@ TEST_CASE_FIXTURE(Fixture, "return_type_of_f_is_not_widened")
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23})));
|
CHECK_EQ(R"("hi")", toString(requireTypeAtPosition({3, 23})));
|
||||||
CHECK_EQ(R"(<a, b, c...>((string) -> (a, c...), b) -> "hello"?)", toString(requireType("foo")));
|
CHECK_EQ(R"(<a, b...>((string) -> ("hello", b...), a) -> "hello"?)", toString(requireType("foo")));
|
||||||
|
|
||||||
|
// This is more accurate but we're not there yet:
|
||||||
// CHECK_EQ(R"(<a, b...>((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo")));
|
// CHECK_EQ(R"(<a, b...>((string) -> ("hello"?, b...), a) -> "hello"?)", toString(requireType("foo")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1233,4 +1233,69 @@ TEST_CASE_FIXTURE(Fixture, "dcr_delays_expansion_of_function_containing_blocked_
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local TRUE: true = true
|
||||||
|
|
||||||
|
local function matches(value, t: true)
|
||||||
|
if value then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function readValue(breakpoint)
|
||||||
|
if matches(breakpoint, TRUE) then
|
||||||
|
readValue(breakpoint)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK("<a>(a) -> ()" == toString(requireType("readValue")));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
local function readValue(breakpoint)
|
||||||
|
if type(breakpoint) == 'number' then
|
||||||
|
readValue(breakpoint)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
CHECK("(number) -> ()" == toString(requireType("readValue")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We got into a case where, as we unified two nearly identical unions with one
|
||||||
|
* another, where we had a concatenated TxnLog that created a cycle between two
|
||||||
|
* free types.
|
||||||
|
*
|
||||||
|
* This code used to crash the type checker. See CLI-71190
|
||||||
|
*/
|
||||||
|
TEST_CASE_FIXTURE(BuiltinsFixture, "convoluted_case_where_two_TypeVars_were_bound_to_each_other")
|
||||||
|
{
|
||||||
|
check(R"(
|
||||||
|
type React_Ref<ElementType> = { current: ElementType } | ((ElementType) -> ())
|
||||||
|
|
||||||
|
type React_AbstractComponent<Config, Instance> = {
|
||||||
|
render: ((ref: React_Ref<Instance>) -> nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
local createElement : <P, T>(React_AbstractComponent<P, T>) -> ()
|
||||||
|
|
||||||
|
function ScrollView:render()
|
||||||
|
local one = table.unpack(
|
||||||
|
if true then a else b
|
||||||
|
)
|
||||||
|
|
||||||
|
createElement(one)
|
||||||
|
createElement(one)
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
// If this code does not crash, we are in good shape.
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -409,4 +409,91 @@ local l0:(any)&(typeof(_)),l0:(any)|(any) = _,_
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static TypeId createTheType(TypeArena& arena, NotNull<BuiltinTypes> builtinTypes, Scope* scope, TypeId freeTy)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
({|
|
||||||
|
render: (
|
||||||
|
(('a) -> ()) | {| current: 'a |}
|
||||||
|
) -> nil
|
||||||
|
|}) -> ()
|
||||||
|
*/
|
||||||
|
TypePackId emptyPack = arena.addTypePack({});
|
||||||
|
|
||||||
|
return arena.addType(FunctionType{
|
||||||
|
arena.addTypePack({arena.addType(TableType{
|
||||||
|
TableType::Props{{{"render",
|
||||||
|
Property(arena.addType(FunctionType{
|
||||||
|
arena.addTypePack({arena.addType(UnionType{{arena.addType(FunctionType{arena.addTypePack({freeTy}), emptyPack}),
|
||||||
|
arena.addType(TableType{TableType::Props{{"current", {freeTy}}}, std::nullopt, TypeLevel{}, scope, TableState::Sealed})}})}),
|
||||||
|
arena.addTypePack({builtinTypes->nilType})}))}}},
|
||||||
|
std::nullopt, TypeLevel{}, scope, TableState::Sealed})}),
|
||||||
|
emptyPack});
|
||||||
|
};
|
||||||
|
|
||||||
|
// See CLI-71190
|
||||||
|
TEST_CASE_FIXTURE(TryUnifyFixture, "unifying_two_unions_under_dcr_does_not_create_a_BoundType_cycle")
|
||||||
|
{
|
||||||
|
const std::shared_ptr<Scope> scope = globalScope;
|
||||||
|
const std::shared_ptr<Scope> nestedScope = std::make_shared<Scope>(scope);
|
||||||
|
|
||||||
|
const TypeId outerType = arena.freshType(scope.get());
|
||||||
|
const TypeId outerType2 = arena.freshType(scope.get());
|
||||||
|
|
||||||
|
const TypeId innerType = arena.freshType(nestedScope.get());
|
||||||
|
|
||||||
|
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||||
|
|
||||||
|
state.enableScopeTests();
|
||||||
|
|
||||||
|
SUBCASE("equal_scopes")
|
||||||
|
{
|
||||||
|
TypeId one = createTheType(arena, builtinTypes, scope.get(), outerType);
|
||||||
|
TypeId two = createTheType(arena, builtinTypes, scope.get(), outerType2);
|
||||||
|
|
||||||
|
state.tryUnify(one, two);
|
||||||
|
state.log.commit();
|
||||||
|
|
||||||
|
ToStringOptions opts;
|
||||||
|
|
||||||
|
CHECK(follow(outerType) == follow(outerType2));
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("outer_scope_is_subtype")
|
||||||
|
{
|
||||||
|
TypeId one = createTheType(arena, builtinTypes, scope.get(), outerType);
|
||||||
|
TypeId two = createTheType(arena, builtinTypes, scope.get(), innerType);
|
||||||
|
|
||||||
|
state.tryUnify(one, two);
|
||||||
|
state.log.commit();
|
||||||
|
|
||||||
|
ToStringOptions opts;
|
||||||
|
|
||||||
|
CHECK(follow(outerType) == follow(innerType));
|
||||||
|
|
||||||
|
// The scope of outerType exceeds that of innerType. The latter should be bound to the former.
|
||||||
|
const BoundType* bt = get_if<BoundType>(&innerType->ty);
|
||||||
|
REQUIRE(bt);
|
||||||
|
CHECK(bt->boundTo == outerType);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUBCASE("outer_scope_is_supertype")
|
||||||
|
{
|
||||||
|
TypeId one = createTheType(arena, builtinTypes, scope.get(), innerType);
|
||||||
|
TypeId two = createTheType(arena, builtinTypes, scope.get(), outerType);
|
||||||
|
|
||||||
|
state.tryUnify(one, two);
|
||||||
|
state.log.commit();
|
||||||
|
|
||||||
|
ToStringOptions opts;
|
||||||
|
|
||||||
|
CHECK(follow(outerType) == follow(innerType));
|
||||||
|
|
||||||
|
// The scope of outerType exceeds that of innerType. The latter should be bound to the former.
|
||||||
|
const BoundType* bt = get_if<BoundType>(&innerType->ty);
|
||||||
|
REQUIRE(bt);
|
||||||
|
CHECK(bt->boundTo == outerType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -792,4 +792,82 @@ TEST_CASE_FIXTURE(Fixture, "lookup_prop_of_intersection_containing_unions")
|
||||||
CHECK("variables" == unknownProp->key);
|
CHECK("variables" == unknownProp->key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "free_options_can_be_unified_together")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[] = {
|
||||||
|
{"LuauTransitiveSubtyping", true},
|
||||||
|
{"LuauUnifyTwoOptions", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
TypeArena arena;
|
||||||
|
TypeId nilType = builtinTypes->nilType;
|
||||||
|
|
||||||
|
std::unique_ptr scope = std::make_unique<Scope>(builtinTypes->anyTypePack);
|
||||||
|
|
||||||
|
TypeId free1 = arena.addType(FreeType{scope.get()});
|
||||||
|
TypeId option1 = arena.addType(UnionType{{nilType, free1}});
|
||||||
|
|
||||||
|
TypeId free2 = arena.addType(FreeType{scope.get()});
|
||||||
|
TypeId option2 = arena.addType(UnionType{{nilType, free2}});
|
||||||
|
|
||||||
|
InternalErrorReporter iceHandler;
|
||||||
|
UnifierSharedState sharedState{&iceHandler};
|
||||||
|
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||||
|
Unifier u{NotNull{&normalizer}, Mode::Strict, NotNull{scope.get()}, Location{}, Variance::Covariant};
|
||||||
|
|
||||||
|
u.tryUnify(option1, option2);
|
||||||
|
|
||||||
|
CHECK(!u.failure);
|
||||||
|
|
||||||
|
u.log.commit();
|
||||||
|
|
||||||
|
ToStringOptions opts;
|
||||||
|
CHECK("a?" == toString(option1, opts));
|
||||||
|
CHECK("a?" == toString(option2, opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "unify_more_complex_unions_that_include_nil")
|
||||||
|
{
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
type Record = {prop: (string | boolean)?}
|
||||||
|
|
||||||
|
function concatPagination(prop: (string | boolean | nil)?): Record
|
||||||
|
return {prop = prop}
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_FIXTURE(Fixture, "optional_class_instances_are_invariant")
|
||||||
|
{
|
||||||
|
ScopedFastFlag sff[] = {
|
||||||
|
{"LuauUnifyTwoOptions", true},
|
||||||
|
{"LuauTypeMismatchInvarianceInError", true}
|
||||||
|
};
|
||||||
|
|
||||||
|
createSomeClasses(&frontend);
|
||||||
|
|
||||||
|
CheckResult result = check(R"(
|
||||||
|
function foo(ref: {current: Parent?})
|
||||||
|
end
|
||||||
|
|
||||||
|
function bar(ref: {current: Child?})
|
||||||
|
foo(ref)
|
||||||
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
// The last line of this error is the most important part. We need to
|
||||||
|
// communicate that this is an invariant context.
|
||||||
|
std::string expectedError =
|
||||||
|
"Type '{| current: Child? |}' could not be converted into '{| current: Parent? |}'\n"
|
||||||
|
"caused by:\n"
|
||||||
|
" Property 'current' is not compatible. Type 'Child' could not be converted into 'Parent' in an invariant context"
|
||||||
|
;
|
||||||
|
|
||||||
|
CHECK(expectedError == toString(result.errors[0]));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -69,4 +69,17 @@ end
|
||||||
|
|
||||||
breakpointSetFromMetamethod()
|
breakpointSetFromMetamethod()
|
||||||
|
|
||||||
|
-- break inside function with non-monotonic line info
|
||||||
|
local function cond(a)
|
||||||
|
if a then
|
||||||
|
print('a')
|
||||||
|
else
|
||||||
|
print('not a')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
breakpoint(77)
|
||||||
|
|
||||||
|
pcall(cond, nil) -- prevent inlining
|
||||||
|
|
||||||
return 'OK'
|
return 'OK'
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
AnnotationTests.too_many_type_params
|
AnnotationTests.too_many_type_params
|
||||||
AstQuery.last_argument_function_call_type
|
AstQuery.last_argument_function_call_type
|
||||||
|
AutocompleteTest.autocomplete_response_perf1
|
||||||
BuiltinTests.aliased_string_format
|
BuiltinTests.aliased_string_format
|
||||||
BuiltinTests.assert_removes_falsy_types
|
BuiltinTests.assert_removes_falsy_types
|
||||||
BuiltinTests.assert_removes_falsy_types2
|
BuiltinTests.assert_removes_falsy_types2
|
||||||
|
@ -34,7 +35,6 @@ GenericsTests.generic_functions_should_be_memory_safe
|
||||||
GenericsTests.generic_type_pack_parentheses
|
GenericsTests.generic_type_pack_parentheses
|
||||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||||
GenericsTests.infer_generic_function_function_argument_2
|
GenericsTests.infer_generic_function_function_argument_2
|
||||||
GenericsTests.infer_generic_function_function_argument_3
|
|
||||||
GenericsTests.infer_generic_function_function_argument_overloaded
|
GenericsTests.infer_generic_function_function_argument_overloaded
|
||||||
GenericsTests.infer_generic_lib_function_function_argument
|
GenericsTests.infer_generic_lib_function_function_argument
|
||||||
GenericsTests.instantiated_function_argument_names
|
GenericsTests.instantiated_function_argument_names
|
||||||
|
@ -47,7 +47,6 @@ ProvisionalTests.assign_table_with_refined_property_with_a_similar_type_is_illeg
|
||||||
ProvisionalTests.bail_early_if_unification_is_too_complicated
|
ProvisionalTests.bail_early_if_unification_is_too_complicated
|
||||||
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
ProvisionalTests.do_not_ice_when_trying_to_pick_first_of_generic_type_pack
|
||||||
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
ProvisionalTests.error_on_eq_metamethod_returning_a_type_other_than_boolean
|
||||||
ProvisionalTests.free_options_cannot_be_unified_together
|
|
||||||
ProvisionalTests.generic_type_leak_to_module_interface_variadic
|
ProvisionalTests.generic_type_leak_to_module_interface_variadic
|
||||||
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
||||||
ProvisionalTests.luau-polyfill.Array.filter
|
ProvisionalTests.luau-polyfill.Array.filter
|
||||||
|
@ -60,7 +59,6 @@ RefinementTest.type_narrow_to_vector
|
||||||
RefinementTest.typeguard_cast_free_table_to_vector
|
RefinementTest.typeguard_cast_free_table_to_vector
|
||||||
RefinementTest.typeguard_in_assert_position
|
RefinementTest.typeguard_in_assert_position
|
||||||
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
RefinementTest.x_as_any_if_x_is_instance_elseif_x_is_table
|
||||||
RuntimeLimits.typescript_port_of_Result_type
|
|
||||||
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
TableTests.a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_compatible
|
||||||
TableTests.checked_prop_too_early
|
TableTests.checked_prop_too_early
|
||||||
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
||||||
|
@ -124,6 +122,7 @@ TypeAliases.type_alias_local_mutation
|
||||||
TypeAliases.type_alias_local_rename
|
TypeAliases.type_alias_local_rename
|
||||||
TypeAliases.type_alias_locations
|
TypeAliases.type_alias_locations
|
||||||
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
TypeAliases.type_alias_of_an_imported_recursive_generic_type
|
||||||
|
TypeFamilyTests.function_internal_families
|
||||||
TypeInfer.check_type_infer_recursion_count
|
TypeInfer.check_type_infer_recursion_count
|
||||||
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||||
|
@ -133,6 +132,7 @@ TypeInfer.fuzz_free_table_type_change_during_index_check
|
||||||
TypeInfer.infer_assignment_value_types_mutable_lval
|
TypeInfer.infer_assignment_value_types_mutable_lval
|
||||||
TypeInfer.no_stack_overflow_from_isoptional
|
TypeInfer.no_stack_overflow_from_isoptional
|
||||||
TypeInfer.no_stack_overflow_from_isoptional2
|
TypeInfer.no_stack_overflow_from_isoptional2
|
||||||
|
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||||
TypeInfer.type_infer_recursion_limit_no_ice
|
TypeInfer.type_infer_recursion_limit_no_ice
|
||||||
TypeInfer.type_infer_recursion_limit_normalizer
|
TypeInfer.type_infer_recursion_limit_normalizer
|
||||||
|
@ -165,9 +165,7 @@ TypeInferFunctions.too_many_return_values_no_function
|
||||||
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
TypeInferLoops.for_in_loop_error_on_factory_not_returning_the_right_amount_of_values
|
||||||
TypeInferLoops.for_in_loop_with_next
|
TypeInferLoops.for_in_loop_with_next
|
||||||
TypeInferLoops.for_in_with_generic_next
|
TypeInferLoops.for_in_with_generic_next
|
||||||
TypeInferLoops.loop_iter_metamethod_ok_with_inference
|
|
||||||
TypeInferLoops.loop_iter_trailing_nil
|
TypeInferLoops.loop_iter_trailing_nil
|
||||||
TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
|
||||||
TypeInferLoops.unreachable_code_after_infinite_loop
|
TypeInferLoops.unreachable_code_after_infinite_loop
|
||||||
TypeInferModules.do_not_modify_imported_types_5
|
TypeInferModules.do_not_modify_imported_types_5
|
||||||
TypeInferModules.module_type_conflict
|
TypeInferModules.module_type_conflict
|
||||||
|
@ -177,7 +175,6 @@ TypeInferOOP.methods_are_topologically_sorted
|
||||||
TypeInferOperators.CallAndOrOfFunctions
|
TypeInferOperators.CallAndOrOfFunctions
|
||||||
TypeInferOperators.CallOrOfFunctions
|
TypeInferOperators.CallOrOfFunctions
|
||||||
TypeInferOperators.cli_38355_recursive_union
|
TypeInferOperators.cli_38355_recursive_union
|
||||||
TypeInferOperators.compound_assign_metatable
|
|
||||||
TypeInferOperators.compound_assign_mismatch_metatable
|
TypeInferOperators.compound_assign_mismatch_metatable
|
||||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||||
TypeInferOperators.operator_eq_completely_incompatible
|
TypeInferOperators.operator_eq_completely_incompatible
|
||||||
|
|
Loading…
Reference in a new issue