mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/614 (#1173)
# What's changed? Add program argument passing to scripts run using the Luau REPL! You can now pass `--program-args` (or shorthand `-a`) to the REPL which will treat all remaining arguments as arguments to pass to executed scripts. These values can be accessed through variadic argument expansion. You can read these values like so: ``` local args = {...} -- gets you an array of all the arguments ``` For example if we run the following script like `luau test.lua -a test1 test2 test3`: ``` -- test.lua print(...) ``` you should get the output: ``` test1 test2 test3 ``` ### Native Code Generation * Improve A64 lowering for vector operations by using vector instructions * Fix lowering issue in IR value location tracking! - A developer reported a divergence between code run in the VM and Native Code Generation which we have now fixed ### New Type Solver * Apply substitution to type families, and emit new constraints to reduce those further * More progress on reducing comparison (`lt/le`)type families * Resolve two major sources of cyclic types in the new solver ### Miscellaneous * Turned internal compiler errors (ICE's) into warnings and errors ------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> --------- Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aviral Goel <agoel@roblox.com> Co-authored-by: David Cope <dcope@roblox.com> Co-authored-by: Lily Brown <lbrown@roblox.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com>
This commit is contained in:
parent
80928acb92
commit
3b0e93bec9
@ -14,8 +14,16 @@
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum class ValueContext;
|
||||
struct Scope;
|
||||
|
||||
// if resultType is a freeType, assignmentType <: freeType <: resultType bounds
|
||||
struct EqualityConstraint
|
||||
{
|
||||
TypeId resultType;
|
||||
TypeId assignmentType;
|
||||
};
|
||||
|
||||
// subType <: superType
|
||||
struct SubtypeConstraint
|
||||
{
|
||||
@ -40,6 +48,8 @@ struct GeneralizationConstraint
|
||||
{
|
||||
TypeId generalizedType;
|
||||
TypeId sourceType;
|
||||
|
||||
std::vector<TypeId> interiorTypes;
|
||||
};
|
||||
|
||||
// subType ~ inst superType
|
||||
@ -145,6 +155,7 @@ struct HasPropConstraint
|
||||
TypeId resultType;
|
||||
TypeId subjectType;
|
||||
std::string prop;
|
||||
ValueContext context;
|
||||
|
||||
// HACK: We presently need types like true|false or string|"hello" when
|
||||
// deciding whether a particular literal expression should have a singleton
|
||||
@ -256,7 +267,8 @@ struct ReducePackConstraint
|
||||
|
||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
|
||||
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint,
|
||||
SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
|
||||
SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint,
|
||||
EqualityConstraint>;
|
||||
|
||||
struct Constraint
|
||||
{
|
||||
|
@ -130,6 +130,8 @@ struct ConstraintGenerator
|
||||
void visitModuleRoot(AstStatBlock* block);
|
||||
|
||||
private:
|
||||
std::vector<std::vector<TypeId>> interiorTypes;
|
||||
|
||||
/**
|
||||
* Fabricates a new free type belonging to a given scope.
|
||||
* @param scope the scope the free type belongs to.
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
@ -20,6 +21,8 @@
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
enum class ValueContext;
|
||||
|
||||
struct DcrLogger;
|
||||
|
||||
// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
|
||||
@ -110,8 +113,6 @@ struct ConstraintSolver
|
||||
|
||||
bool isDone();
|
||||
|
||||
void finalizeModule();
|
||||
|
||||
/** Attempt to dispatch a constraint. Returns true if it was successful. If
|
||||
* tryDispatch() returns false, the constraint remains in the unsolved set
|
||||
* and will be retried later.
|
||||
@ -136,6 +137,7 @@ struct ConstraintSolver
|
||||
bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
||||
// for a, ... in some_table do
|
||||
// also handles __iter metamethod
|
||||
@ -146,9 +148,9 @@ struct ConstraintSolver
|
||||
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
|
||||
TypeId subjectType, const std::string& propName, bool suppressSimplification = false);
|
||||
TypeId subjectType, const std::string& propName, ValueContext context, bool suppressSimplification = false);
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
|
||||
TypeId subjectType, const std::string& propName, bool suppressSimplification, DenseHashSet<TypeId>& seen);
|
||||
TypeId subjectType, const std::string& propName, ValueContext context, bool suppressSimplification, DenseHashSet<TypeId>& seen);
|
||||
|
||||
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
|
||||
/**
|
||||
@ -208,23 +210,6 @@ struct ConstraintSolver
|
||||
*/
|
||||
bool isBlocked(NotNull<const Constraint> constraint);
|
||||
|
||||
/**
|
||||
* Creates a new Unifier and performs a single unification operation. Commits
|
||||
* the result.
|
||||
* @param subType the sub-type to unify.
|
||||
* @param superType the super-type to unify.
|
||||
* @returns optionally a unification too complex error if unification failed
|
||||
*/
|
||||
std::optional<TypeError> unify(NotNull<Scope> scope, Location location, TypeId subType, TypeId superType);
|
||||
|
||||
/**
|
||||
* Creates a new Unifier and performs a single unification operation. Commits
|
||||
* the result.
|
||||
* @param subPack the sub-type pack to unify.
|
||||
* @param superPack the super-type pack to unify.
|
||||
*/
|
||||
ErrorVec unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack);
|
||||
|
||||
/** Pushes a new solver constraint to the solver.
|
||||
* @param cv the body of the constraint.
|
||||
**/
|
||||
@ -253,20 +238,33 @@ struct ConstraintSolver
|
||||
*/
|
||||
bool hasUnresolvedConstraints(TypeId ty);
|
||||
|
||||
private:
|
||||
/** Helper used by tryDispatch(SubtypeConstraint) and
|
||||
* tryDispatch(PackSubtypeConstraint)
|
||||
/**
|
||||
* Creates a new Unifier and performs a single unification operation.
|
||||
*
|
||||
* Attempts to unify subTy with superTy. If doing so would require unifying
|
||||
* @param subType the sub-type to unify.
|
||||
* @param superType the super-type to unify.
|
||||
* @returns true if the unification succeeded. False if the unification was
|
||||
* too complex.
|
||||
*/
|
||||
template <typename TID>
|
||||
bool unify(NotNull<Scope> scope, Location location, TID subType, TID superType);
|
||||
|
||||
/** Attempts to unify subTy with superTy. If doing so would require unifying
|
||||
* BlockedTypes, fail and block the constraint on those BlockedTypes.
|
||||
*
|
||||
* Note: TID can only be TypeId or TypePackId.
|
||||
*
|
||||
* If unification fails, replace all free types with errorType.
|
||||
*
|
||||
* If unification succeeds, unblock every type changed by the unification.
|
||||
*
|
||||
* @returns true if the unification succeeded. False if the unification was
|
||||
* too complex.
|
||||
*/
|
||||
template<typename TID>
|
||||
bool tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
|
||||
bool unify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Bind a BlockedType to another type while taking care not to bind it to
|
||||
* itself in the case that resultTy == blockedTy. This can happen if we
|
||||
@ -295,6 +293,13 @@ private:
|
||||
**/
|
||||
void unblock_(BlockedConstraintId progressed);
|
||||
|
||||
/**
|
||||
* Reproduces any constraints necessary for new types that are copied when applying a substitution.
|
||||
* At the time of writing, this pertains only to type families.
|
||||
* @param subst the substitution that was applied
|
||||
**/
|
||||
void reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst);
|
||||
|
||||
TypeId errorRecoveryType() const;
|
||||
TypePackId errorRecoveryTypePack() const;
|
||||
|
||||
|
@ -380,6 +380,20 @@ struct NonStrictFunctionDefinitionError
|
||||
bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
|
||||
};
|
||||
|
||||
struct PropertyAccessViolation
|
||||
{
|
||||
TypeId table;
|
||||
Name key;
|
||||
|
||||
enum
|
||||
{
|
||||
CannotRead,
|
||||
CannotWrite
|
||||
} context;
|
||||
|
||||
bool operator==(const PropertyAccessViolation& rhs) const;
|
||||
};
|
||||
|
||||
struct CheckedFunctionIncorrectArgs
|
||||
{
|
||||
std::string functionName;
|
||||
@ -388,14 +402,28 @@ struct CheckedFunctionIncorrectArgs
|
||||
bool operator==(const CheckedFunctionIncorrectArgs& 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,
|
||||
WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError, CheckedFunctionIncorrectArgs>;
|
||||
struct UnexpectedTypeInSubtyping
|
||||
{
|
||||
TypeId ty;
|
||||
|
||||
bool operator==(const UnexpectedTypeInSubtyping& rhs) const;
|
||||
};
|
||||
|
||||
struct UnexpectedTypePackInSubtyping
|
||||
{
|
||||
TypePackId tp;
|
||||
|
||||
bool operator==(const UnexpectedTypePackInSubtyping& 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, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError,
|
||||
PropertyAccessViolation, CheckedFunctionIncorrectArgs, UnexpectedTypeInSubtyping, UnexpectedTypePackInSubtyping>;
|
||||
|
||||
struct TypeErrorSummary
|
||||
{
|
||||
|
70
Analysis/include/Luau/Instantiation2.h
Normal file
70
Analysis/include/Luau/Instantiation2.h
Normal file
@ -0,0 +1,70 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypeArena;
|
||||
struct TypeCheckLimits;
|
||||
|
||||
struct Replacer : Substitution
|
||||
{
|
||||
DenseHashMap<TypeId, TypeId> replacements;
|
||||
DenseHashMap<TypePackId, TypePackId> replacementPacks;
|
||||
|
||||
Replacer(NotNull<TypeArena> arena, DenseHashMap<TypeId, TypeId> replacements, DenseHashMap<TypePackId, TypePackId> replacementPacks)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, replacements(std::move(replacements))
|
||||
, replacementPacks(std::move(replacementPacks))
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
return replacements.find(ty) != nullptr;
|
||||
}
|
||||
|
||||
bool isDirty(TypePackId tp) override
|
||||
{
|
||||
return replacementPacks.find(tp) != nullptr;
|
||||
}
|
||||
|
||||
TypeId clean(TypeId ty) override
|
||||
{
|
||||
return replacements[ty];
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
{
|
||||
return replacementPacks[tp];
|
||||
}
|
||||
};
|
||||
|
||||
// A substitution which replaces generic functions by monomorphic functions
|
||||
struct Instantiation2 : Substitution
|
||||
{
|
||||
// Mapping from generic types to free types to be used in instantiation.
|
||||
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
||||
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
|
||||
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
||||
|
||||
Instantiation2(TypeArena* arena, DenseHashMap<TypeId, TypeId> genericSubstitutions, DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, genericSubstitutions(std::move(genericSubstitutions))
|
||||
, genericPackSubstitutions(std::move(genericPackSubstitutions))
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override;
|
||||
bool isDirty(TypePackId tp) override;
|
||||
TypeId clean(TypeId ty) override;
|
||||
TypePackId clean(TypePackId tp) override;
|
||||
};
|
||||
|
||||
} // namespace Luau
|
@ -21,14 +21,15 @@ struct InternalErrorReporter;
|
||||
|
||||
class TypeIds;
|
||||
class Normalizer;
|
||||
struct NormalizedType;
|
||||
struct NormalizedClassType;
|
||||
struct NormalizedStringType;
|
||||
struct NormalizedFunctionType;
|
||||
struct TypeArena;
|
||||
struct TypeCheckLimits;
|
||||
struct NormalizedStringType;
|
||||
struct NormalizedType;
|
||||
struct Property;
|
||||
struct Scope;
|
||||
struct TableIndexer;
|
||||
struct TypeArena;
|
||||
struct TypeCheckLimits;
|
||||
|
||||
enum class SubtypingVariance
|
||||
{
|
||||
@ -79,6 +80,7 @@ struct SubtypingResult
|
||||
SubtypingResult& withSubPath(TypePath::Path path);
|
||||
SubtypingResult& withSuperPath(TypePath::Path path);
|
||||
SubtypingResult& withErrors(ErrorVec& err);
|
||||
SubtypingResult& withError(TypeError err);
|
||||
|
||||
// Only negates the `isSubtype`.
|
||||
static SubtypingResult negate(const SubtypingResult& result);
|
||||
@ -102,6 +104,10 @@ struct SubtypingEnvironment
|
||||
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
||||
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
|
||||
|
||||
/// Applies `mappedGenerics` to the given type.
|
||||
/// This is used specifically to substitute for generics in type family instances.
|
||||
std::optional<TypeId> applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
|
||||
};
|
||||
|
||||
struct Subtyping
|
||||
@ -192,6 +198,7 @@ private:
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable);
|
||||
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name);
|
||||
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
|
||||
|
@ -414,16 +414,11 @@ struct Property
|
||||
TypeId type() const;
|
||||
void setType(TypeId ty);
|
||||
|
||||
// Should only be called in RWP!
|
||||
// We do not assert that `readTy` nor `writeTy` are nullopt or not.
|
||||
// The invariant is that at least one of them mustn't be nullopt, which we do assert here.
|
||||
// TODO: Kill this in favor of exposing `readTy`/`writeTy` directly? If we do, we'll lose the asserts which will be useful while debugging.
|
||||
std::optional<TypeId> readType() const;
|
||||
std::optional<TypeId> writeType() const;
|
||||
|
||||
bool isShared() const;
|
||||
bool isReadOnly() const;
|
||||
bool isWriteOnly() const;
|
||||
bool isReadWrite() const;
|
||||
|
||||
private:
|
||||
std::optional<TypeId> readTy;
|
||||
std::optional<TypeId> writeTy;
|
||||
};
|
||||
@ -844,6 +839,7 @@ public:
|
||||
|
||||
const TypePackId emptyTypePack;
|
||||
const TypePackId anyTypePack;
|
||||
const TypePackId unknownTypePack;
|
||||
const TypePackId neverTypePack;
|
||||
const TypePackId uninhabitableTypePack;
|
||||
const TypePackId errorTypePack;
|
||||
|
@ -30,8 +30,10 @@ struct TypeFamilyContext
|
||||
|
||||
// nullptr if the type family is being reduced outside of the constraint solver.
|
||||
ConstraintSolver* solver;
|
||||
// The constraint being reduced in this run of the reduction
|
||||
const Constraint* constraint;
|
||||
|
||||
TypeFamilyContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope)
|
||||
TypeFamilyContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
|
||||
: arena(cs->arena)
|
||||
, builtins(cs->builtinTypes)
|
||||
, scope(scope)
|
||||
@ -39,6 +41,7 @@ struct TypeFamilyContext
|
||||
, ice(NotNull{&cs->iceReporter})
|
||||
, limits(NotNull{&cs->limits})
|
||||
, solver(cs.get())
|
||||
, constraint(constraint.get())
|
||||
{
|
||||
}
|
||||
|
||||
@ -51,10 +54,10 @@ struct TypeFamilyContext
|
||||
, ice(ice)
|
||||
, limits(limits)
|
||||
, solver(nullptr)
|
||||
, constraint(nullptr)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
/// 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.
|
||||
|
@ -28,6 +28,8 @@ std::optional<TypeId> findMetatableEntry(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location);
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, ValueContext context, Location location);
|
||||
|
||||
// Returns the minimum and maximum number of types the argument list can accept.
|
||||
std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp, bool includeHiddenVariadics = false);
|
||||
|
@ -38,6 +38,11 @@ struct Unifier2
|
||||
|
||||
DenseHashMap<TypeId, std::vector<TypeId>> expandedFreeTypes{nullptr};
|
||||
|
||||
// Mapping from generic types to free types to be used in instantiation.
|
||||
DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
|
||||
// Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
|
||||
DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
|
||||
|
||||
int recursionCount = 0;
|
||||
int recursionLimit = 0;
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauBoundLazyTypes2)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -281,12 +280,16 @@ struct GenericTypeVisitor
|
||||
{
|
||||
for (auto& [_name, prop] : ttv->props)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (auto ty = prop.readType())
|
||||
if (auto ty = prop.readTy)
|
||||
traverse(*ty);
|
||||
|
||||
if (auto ty = prop.writeType())
|
||||
// In the case that the readType and the writeType
|
||||
// are the same pointer, just traverse once.
|
||||
// Traversing each property twice has pretty
|
||||
// significant performance consequences.
|
||||
if (auto ty = prop.writeTy; ty && !prop.isShared())
|
||||
traverse(*ty);
|
||||
}
|
||||
else
|
||||
@ -315,12 +318,16 @@ struct GenericTypeVisitor
|
||||
{
|
||||
for (const auto& [name, prop] : ctv->props)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (auto ty = prop.readType())
|
||||
if (auto ty = prop.readTy)
|
||||
traverse(*ty);
|
||||
|
||||
if (auto ty = prop.writeType())
|
||||
// In the case that the readType and the writeType are
|
||||
// the same pointer, just traverse once. Traversing each
|
||||
// property twice would have pretty significant
|
||||
// performance consequences.
|
||||
if (auto ty = prop.writeTy; ty && !prop.isShared())
|
||||
traverse(*ty);
|
||||
}
|
||||
else
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -524,9 +524,9 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
||||
{
|
||||
if (auto propIt = ttv->props.find(indexName->index.value); propIt != ttv->props.end())
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (auto ty = propIt->second.readType())
|
||||
if (auto ty = propIt->second.readTy)
|
||||
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
|
||||
}
|
||||
else
|
||||
@ -537,9 +537,9 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
|
||||
{
|
||||
if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (auto ty = propIt->second.readType())
|
||||
if (auto ty = propIt->second.readTy)
|
||||
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
|
||||
}
|
||||
else
|
||||
|
@ -14,7 +14,6 @@
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteTableKeysNoInitialCharacter, false);
|
||||
|
||||
@ -277,9 +276,9 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
||||
{
|
||||
Luau::TypeId type;
|
||||
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (auto ty = prop.readType())
|
||||
if (auto ty = prop.readTy)
|
||||
type = follow(*ty);
|
||||
else
|
||||
continue;
|
||||
|
@ -283,6 +283,26 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
|
||||
TypeId genericT = arena.addType(GenericType{"T"});
|
||||
TypeId refinedTy = arena.addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.intersectFamily},
|
||||
{genericT, arena.addType(NegationType{builtinTypes->falsyType})},
|
||||
{}
|
||||
});
|
||||
|
||||
TypeId assertTy = arena.addType(FunctionType{
|
||||
{genericT},
|
||||
{},
|
||||
arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}),
|
||||
arena.addTypePack(TypePack{{refinedTy}})
|
||||
});
|
||||
addGlobalBinding(globals, "assert", assertTy, "@luau");
|
||||
}
|
||||
|
||||
attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
|
||||
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
|
||||
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
|
||||
|
@ -8,8 +8,6 @@
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/Unifiable.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||
|
||||
@ -196,14 +194,14 @@ private:
|
||||
|
||||
Property shallowClone(const Property& p)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
std::optional<TypeId> cloneReadTy;
|
||||
if (auto ty = p.readType())
|
||||
if (auto ty = p.readTy)
|
||||
cloneReadTy = shallowClone(*ty);
|
||||
|
||||
std::optional<TypeId> cloneWriteTy;
|
||||
if (auto ty = p.writeType())
|
||||
if (auto ty = p.writeTy)
|
||||
cloneWriteTy = shallowClone(*ty);
|
||||
|
||||
std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
|
||||
@ -460,14 +458,14 @@ namespace
|
||||
|
||||
Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
std::optional<TypeId> cloneReadTy;
|
||||
if (auto ty = prop.readType())
|
||||
if (auto ty = prop.readTy)
|
||||
cloneReadTy = clone(*ty, dest, cloneState);
|
||||
|
||||
std::optional<TypeId> cloneWriteTy;
|
||||
if (auto ty = prop.writeType())
|
||||
if (auto ty = prop.writeTy)
|
||||
cloneWriteTy = clone(*ty, dest, cloneState);
|
||||
|
||||
std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
|
||||
|
@ -217,9 +217,25 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
||||
|
||||
rootScope->returnType = freshTypePack(scope);
|
||||
|
||||
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, rootScope, builtinTypes->anyTypePack, rootScope->returnType});
|
||||
interiorTypes.emplace_back();
|
||||
|
||||
prepopulateGlobalScope(scope, block);
|
||||
|
||||
visitBlockWithoutChildScope(scope, block);
|
||||
Checkpoint start = checkpoint(this);
|
||||
|
||||
ControlFlow cf = visitBlockWithoutChildScope(scope, block);
|
||||
if (cf == ControlFlow::None)
|
||||
addConstraint(scope, block->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, rootScope->returnType});
|
||||
|
||||
Checkpoint end = checkpoint(this);
|
||||
|
||||
NotNull<Constraint> genConstraint = addConstraint(scope, block->location, GeneralizationConstraint{arena->addType(BlockedType{}), moduleFnTy, std::move(interiorTypes.back())});
|
||||
forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) {
|
||||
genConstraint->dependencies.push_back(NotNull{c.get()});
|
||||
});
|
||||
|
||||
interiorTypes.pop_back();
|
||||
|
||||
fillInInferredBindings(scope, block);
|
||||
|
||||
@ -406,7 +422,7 @@ void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location loca
|
||||
|
||||
TypeId nextDiscriminantTy = arena->addType(TableType{});
|
||||
NotNull<TableType> table{getMutable<TableType>(nextDiscriminantTy)};
|
||||
table->props[*key->propName] = {discriminantTy};
|
||||
table->props[*key->propName] = Property::readonly(discriminantTy);
|
||||
table->scope = scope.get();
|
||||
table->state = TableState::Sealed;
|
||||
|
||||
@ -1894,7 +1910,7 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin
|
||||
scope->rvalueRefinements[key->def] = result;
|
||||
}
|
||||
|
||||
addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index)});
|
||||
addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue});
|
||||
|
||||
if (key)
|
||||
return Inference{result, refinementArena.proposition(key, builtinTypes->truthyType)};
|
||||
@ -1945,27 +1961,30 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
|
||||
{
|
||||
Checkpoint startCheckpoint = checkpoint(this);
|
||||
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
||||
|
||||
interiorTypes.push_back(std::vector<TypeId>{});
|
||||
checkFunctionBody(sig.bodyScope, func);
|
||||
Checkpoint endCheckpoint = checkpoint(this);
|
||||
|
||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())});
|
||||
interiorTypes.pop_back();
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc, &previous](const ConstraintPtr& constraint) {
|
||||
gc->dependencies.emplace_back(constraint.get());
|
||||
|
||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
});
|
||||
|
||||
if (generalize && hasFreeType(sig.signature))
|
||||
{
|
||||
TypeId generalizedTy = arena->addType(BlockedType{});
|
||||
NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature});
|
||||
|
||||
Constraint* previous = nullptr;
|
||||
forEachConstraint(startCheckpoint, endCheckpoint, this, [gc, &previous](const ConstraintPtr& constraint) {
|
||||
gc->dependencies.emplace_back(constraint.get());
|
||||
|
||||
if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
|
||||
{
|
||||
if (previous)
|
||||
constraint->dependencies.push_back(NotNull{previous});
|
||||
|
||||
previous = constraint.get();
|
||||
}
|
||||
});
|
||||
|
||||
return Inference{generalizedTy};
|
||||
}
|
||||
else
|
||||
@ -2529,7 +2548,8 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr,
|
||||
{
|
||||
TypeId segmentTy = arena->addType(BlockedType{});
|
||||
module->astTypes[exprs[i]] = segmentTy;
|
||||
addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i]});
|
||||
ValueContext ctx = i == segments.size() - 1 ? ValueContext::LValue : ValueContext::RValue;
|
||||
addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i], ctx});
|
||||
prevSegmentTy = segmentTy;
|
||||
}
|
||||
|
||||
@ -2563,6 +2583,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
||||
ttv->state = TableState::Unsealed;
|
||||
ttv->scope = scope.get();
|
||||
|
||||
interiorTypes.back().push_back(ty);
|
||||
|
||||
auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
|
||||
if (!ttv->indexer)
|
||||
{
|
||||
@ -2613,7 +2635,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
||||
{
|
||||
expectedValueType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, item.value->location,
|
||||
HasPropConstraint{*expectedValueType, *expectedType, stringKey->value.data, /*suppressSimplification*/ true});
|
||||
HasPropConstraint{
|
||||
*expectedValueType, *expectedType, stringKey->value.data, ValueContext::RValue, /*suppressSimplification*/ true});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2876,15 +2899,10 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
|
||||
|
||||
void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn)
|
||||
{
|
||||
visitBlockWithoutChildScope(scope, fn->body);
|
||||
|
||||
// If it is possible for execution to reach the end of the function, the return type must be compatible with ()
|
||||
|
||||
if (nullptr != getFallthrough(fn->body))
|
||||
{
|
||||
TypePackId empty = arena->addTypePack({}); // TODO we could have CG retain one of these forever
|
||||
addConstraint(scope, fn->location, PackSubtypeConstraint{scope->returnType, empty});
|
||||
}
|
||||
ControlFlow cf = visitBlockWithoutChildScope(scope, fn->body);
|
||||
if (cf == ControlFlow::None)
|
||||
addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType});
|
||||
}
|
||||
|
||||
TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh)
|
||||
@ -2977,20 +2995,30 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
|
||||
|
||||
for (const AstTableProp& prop : tab->props)
|
||||
{
|
||||
if (prop.access == AstTableAccess::Read)
|
||||
reportError(prop.accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
|
||||
else if (prop.access == AstTableAccess::Write)
|
||||
reportError(prop.accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
|
||||
else if (prop.access == AstTableAccess::ReadWrite)
|
||||
// TODO: Recursion limit.
|
||||
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
|
||||
|
||||
Property& p = props[prop.name.value];
|
||||
p.typeLocation = prop.location;
|
||||
|
||||
switch (prop.access)
|
||||
{
|
||||
std::string name = prop.name.value;
|
||||
// TODO: Recursion limit.
|
||||
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
|
||||
props[name] = {propTy};
|
||||
props[name].typeLocation = prop.location;
|
||||
}
|
||||
else
|
||||
case AstTableAccess::ReadWrite:
|
||||
p.readTy = propTy;
|
||||
p.writeTy = propTy;
|
||||
break;
|
||||
case AstTableAccess::Read:
|
||||
p.readTy = propTy;
|
||||
break;
|
||||
case AstTableAccess::Write:
|
||||
reportError(*prop.accessLocation, GenericError{"write keyword is illegal here"});
|
||||
p.readTy = propTy;
|
||||
p.writeTy = propTy;
|
||||
break;
|
||||
default:
|
||||
ice->ice("Unexpected property access " + std::to_string(int(prop.access)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (AstTableIndexer* astIndexer = tab->indexer)
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "Luau/ConstraintSolver.h"
|
||||
#include "Luau/DcrLogger.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
#include "Luau/Instantiation2.h"
|
||||
#include "Luau/Location.h"
|
||||
#include "Luau/ModuleResolver.h"
|
||||
#include "Luau/OverloadResolution.h"
|
||||
@ -430,8 +431,6 @@ void ConstraintSolver::run()
|
||||
progress |= runSolverPass(true);
|
||||
} while (progress);
|
||||
|
||||
finalizeModule();
|
||||
|
||||
if (FFlag::DebugLuauLogSolver)
|
||||
{
|
||||
dumpBindings(rootScope, opts);
|
||||
@ -477,48 +476,6 @@ struct FreeTypeSearcher : TypeOnceVisitor
|
||||
|
||||
} // namespace
|
||||
|
||||
void ConstraintSolver::finalizeModule()
|
||||
{
|
||||
Anyification a{arena, rootScope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack};
|
||||
std::optional<TypePackId> returnType = a.substitute(rootScope->returnType);
|
||||
if (!returnType)
|
||||
{
|
||||
reportError(CodeTooComplex{}, Location{});
|
||||
rootScope->returnType = builtinTypes->errorTypePack;
|
||||
}
|
||||
else
|
||||
{
|
||||
rootScope->returnType = anyifyModuleReturnTypePackGenerics(*returnType);
|
||||
}
|
||||
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, rootScope, NotNull{&iceReporter}};
|
||||
|
||||
VecDeque<TypeAndLocation> queue;
|
||||
for (auto& [name, binding] : rootScope->bindings)
|
||||
queue.push_back({binding.typeId, binding.location});
|
||||
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
TypeAndLocation binding = queue.front();
|
||||
queue.pop_front();
|
||||
|
||||
TypeId ty = follow(binding.typeId);
|
||||
|
||||
if (seen.find(ty))
|
||||
continue;
|
||||
seen.insert(ty);
|
||||
|
||||
FreeTypeSearcher fts{&queue, binding.location};
|
||||
fts.traverse(ty);
|
||||
|
||||
auto result = u2.generalize(ty);
|
||||
if (!result)
|
||||
reportError(CodeTooComplex{}, binding.location);
|
||||
}
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
if (!force && isBlocked(constraint))
|
||||
@ -562,6 +519,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
||||
success = tryDispatch(*rc, constraint, force);
|
||||
else if (auto rpc = get<ReducePackConstraint>(*constraint))
|
||||
success = tryDispatch(*rpc, constraint, force);
|
||||
else if (auto eqc = get<EqualityConstraint>(*constraint))
|
||||
success = tryDispatch(*eqc, constraint, force);
|
||||
else
|
||||
LUAU_ASSERT(false);
|
||||
|
||||
@ -578,7 +537,9 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
|
||||
else if (isBlocked(c.superType))
|
||||
return block(c.superType, constraint);
|
||||
|
||||
return tryUnify(constraint, c.subType, c.superType);
|
||||
unify(constraint, c.subType, c.superType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
@ -588,7 +549,9 @@ bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const
|
||||
else if (isBlocked(c.superPack))
|
||||
return block(c.superPack, constraint);
|
||||
|
||||
return tryUnify(constraint, c.subPack, c.superPack);
|
||||
unify(constraint, c.subPack, c.superPack);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
@ -615,13 +578,13 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
||||
if (get<BlockedType>(generalizedType))
|
||||
asMutable(generalizedType)->ty.emplace<BoundType>(generalized->result);
|
||||
else
|
||||
unify(constraint->scope, constraint->location, generalizedType, generalized->result);
|
||||
unify(constraint, generalizedType, generalized->result);
|
||||
|
||||
for (auto [free, gen] : generalized->insertedGenerics.pairings)
|
||||
unify(constraint->scope, constraint->location, free, gen);
|
||||
unify(constraint, free, gen);
|
||||
|
||||
for (auto [free, gen] : generalized->insertedGenericPacks.pairings)
|
||||
unify(constraint->scope, constraint->location, free, gen);
|
||||
unify(constraint, free, gen);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -632,6 +595,9 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
||||
unblock(c.generalizedType, constraint->location);
|
||||
unblock(c.sourceType, constraint->location);
|
||||
|
||||
for (TypeId ty : c.interiorTypes)
|
||||
u2.generalize(ty);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -730,7 +696,7 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
|
||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
|
||||
std::optional<TypePackId> anyified = anyify.substitute(c.variables);
|
||||
LUAU_ASSERT(anyified);
|
||||
unify(constraint->scope, constraint->location, *anyified, c.variables);
|
||||
unify(constraint, *anyified, c.variables);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1112,6 +1078,26 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||
|
||||
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
|
||||
|
||||
if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
|
||||
{
|
||||
Instantiation2 instantiation{arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions)};
|
||||
|
||||
std::optional<TypePackId> subst = instantiation.substitute(result);
|
||||
|
||||
if (!subst)
|
||||
{
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
result = builtinTypes->errorTypePack;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = *subst;
|
||||
}
|
||||
|
||||
if (c.result != result)
|
||||
asMutable(c.result)->ty.emplace<BoundTypePack>(result);
|
||||
}
|
||||
|
||||
for (const auto& [expanded, additions] : u2.expandedFreeTypes)
|
||||
{
|
||||
for (TypeId addition : additions)
|
||||
@ -1132,7 +1118,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
|
||||
{
|
||||
const TypeId fn = follow(c.fn);
|
||||
TypeId fn = follow(c.fn);
|
||||
const TypePackId argsPack = follow(c.argsPack);
|
||||
|
||||
if (isBlocked(fn))
|
||||
@ -1156,6 +1142,35 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||
if (!ftv)
|
||||
return true;
|
||||
|
||||
DenseHashMap<TypeId, TypeId> replacements{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
|
||||
|
||||
for (auto generic : ftv->generics)
|
||||
replacements[generic] = builtinTypes->unknownType;
|
||||
|
||||
for (auto genericPack : ftv->genericPacks)
|
||||
replacementPacks[genericPack] = builtinTypes->unknownTypePack;
|
||||
|
||||
// If the type of the function has generics, we don't actually want to push any of the generics themselves
|
||||
// into the argument types as expected types because this creates an unnecessary loop. Instead, we want to
|
||||
// replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle.
|
||||
if (!replacements.empty() || !replacementPacks.empty())
|
||||
{
|
||||
Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
|
||||
|
||||
std::optional<TypeId> res = replacer.substitute(fn);
|
||||
if (res)
|
||||
{
|
||||
fn = *res;
|
||||
ftv = get<FunctionType>(*res);
|
||||
LUAU_ASSERT(ftv);
|
||||
|
||||
// we've potentially copied type families here, so we need to reproduce their reduce constraint.
|
||||
reproduceConstraints(constraint->scope, constraint->location, replacer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
|
||||
const std::vector<TypeId> argPackHead = flatten(argsPack).first;
|
||||
|
||||
@ -1237,7 +1252,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
|
||||
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFamilyInstanceType>(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
|
||||
auto [blocked, result] = lookupTableProp(subjectType, c.prop, c.suppressSimplification);
|
||||
auto [blocked, result] = lookupTableProp(subjectType, c.prop, c.context, c.suppressSimplification);
|
||||
if (!blocked.empty())
|
||||
{
|
||||
for (TypeId blocked : blocked)
|
||||
@ -1304,7 +1319,7 @@ static void updateTheTableType(
|
||||
for (size_t i = 0; i < path.size() - 1; ++i)
|
||||
{
|
||||
t = follow(t);
|
||||
auto propTy = findTablePropertyRespectingMeta(builtinTypes, dummy, t, path[i], Location{});
|
||||
auto propTy = findTablePropertyRespectingMeta(builtinTypes, dummy, t, path[i], ValueContext::LValue, Location{});
|
||||
dummy.clear();
|
||||
|
||||
if (!propTy)
|
||||
@ -1329,17 +1344,26 @@ static void updateTheTableType(
|
||||
bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
TypeId subjectType = follow(c.subjectType);
|
||||
const TypeId propType = follow(c.propType);
|
||||
|
||||
if (isBlocked(subjectType))
|
||||
return block(subjectType, constraint);
|
||||
|
||||
std::optional<TypeId> existingPropType = subjectType;
|
||||
for (const std::string& segment : c.path)
|
||||
|
||||
LUAU_ASSERT(!c.path.empty());
|
||||
if (c.path.empty())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < c.path.size(); ++i)
|
||||
{
|
||||
const std::string& segment = c.path[i];
|
||||
if (!existingPropType)
|
||||
break;
|
||||
|
||||
auto [blocked, result] = lookupTableProp(*existingPropType, segment);
|
||||
ValueContext ctx = i == c.path.size() - 1 ? ValueContext::LValue : ValueContext::RValue;
|
||||
|
||||
auto [blocked, result] = lookupTableProp(*existingPropType, segment, ctx);
|
||||
if (!blocked.empty())
|
||||
{
|
||||
for (TypeId blocked : blocked)
|
||||
@ -1356,8 +1380,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
|
||||
if (existingPropType)
|
||||
{
|
||||
if (!isBlocked(c.propType))
|
||||
unify(constraint->scope, constraint->location, c.propType, *existingPropType);
|
||||
unify(constraint->scope, constraint->location, propType, *existingPropType);
|
||||
unify(constraint->scope, constraint->location, *existingPropType, propType);
|
||||
bind(c.resultType, c.subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
@ -1382,7 +1406,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
{
|
||||
LUAU_ASSERT(!subjectType->persistent);
|
||||
|
||||
ttv->props[c.path[0]] = Property{c.propType};
|
||||
ttv->props[c.path[0]] = Property{propType};
|
||||
bind(c.resultType, subjectType);
|
||||
unblock(c.resultType, constraint->location);
|
||||
return true;
|
||||
@ -1391,7 +1415,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||
{
|
||||
LUAU_ASSERT(!subjectType->persistent);
|
||||
|
||||
updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, c.propType);
|
||||
updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, propType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1425,7 +1449,7 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
||||
if (tt->indexer)
|
||||
{
|
||||
// TODO This probably has to be invariant.
|
||||
unify(constraint->scope, constraint->location, c.indexType, tt->indexer->indexType);
|
||||
unify(constraint, c.indexType, tt->indexer->indexType);
|
||||
asMutable(c.propType)->ty.emplace<BoundType>(tt->indexer->indexResultType);
|
||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||
unblock(c.propType, constraint->location);
|
||||
@ -1435,7 +1459,7 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
|
||||
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
|
||||
{
|
||||
TypeId promotedIndexTy = freshType(arena, builtinTypes, tt->scope);
|
||||
unify(constraint->scope, constraint->location, c.indexType, promotedIndexTy);
|
||||
unify(constraint, c.indexType, promotedIndexTy);
|
||||
|
||||
auto mtt = getMutable<TableType>(subjectType);
|
||||
mtt->indexer = TableIndexer{promotedIndexTy, c.propType};
|
||||
@ -1486,6 +1510,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||
|
||||
if (isBlocked(resultPack))
|
||||
{
|
||||
LUAU_ASSERT(resultPack != sourcePack);
|
||||
asMutable(resultPack)->ty.emplace<BoundTypePack>(sourcePack);
|
||||
unblock(resultPack, constraint->location);
|
||||
return true;
|
||||
@ -1525,7 +1550,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(c.resultIsLValue);
|
||||
unify(constraint->scope, constraint->location, resultTy, srcTy);
|
||||
unify(constraint, resultTy, srcTy);
|
||||
}
|
||||
|
||||
unblock(resultTy, constraint->location);
|
||||
@ -1553,7 +1578,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||
apply(resultTy, srcTy);
|
||||
}
|
||||
else
|
||||
unify(constraint->scope, constraint->location, resultTy, srcTy);
|
||||
unify(constraint, resultTy, srcTy);
|
||||
|
||||
++resultIter;
|
||||
++i;
|
||||
@ -1616,7 +1641,9 @@ bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Const
|
||||
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
TypeId ty = follow(c.ty);
|
||||
FamilyGraphReductionResult result = reduceFamilies(ty, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope}, force);
|
||||
FamilyGraphReductionResult result =
|
||||
reduceFamilies(ty, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope, constraint}, force);
|
||||
|
||||
|
||||
for (TypeId r : result.reducedTypes)
|
||||
unblock(r, constraint->location);
|
||||
@ -1639,7 +1666,8 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
|
||||
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
TypePackId tp = follow(c.tp);
|
||||
FamilyGraphReductionResult result = reduceFamilies(tp, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope}, force);
|
||||
FamilyGraphReductionResult result =
|
||||
reduceFamilies(tp, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope, constraint}, force);
|
||||
|
||||
for (TypeId r : result.reducedTypes)
|
||||
unblock(r, constraint->location);
|
||||
@ -1659,6 +1687,13 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
|
||||
return result.blockedTypes.empty() && result.blockedPacks.empty();
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
unify(constraint->scope, constraint->location, c.resultType, c.assignmentType);
|
||||
unify(constraint->scope, constraint->location, c.assignmentType, c.resultType);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||
{
|
||||
auto block_ = [&](auto&& t) {
|
||||
@ -1721,7 +1756,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (iteratorTable->indexer)
|
||||
{
|
||||
TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType});
|
||||
unify(constraint->scope, constraint->location, c.variables, expectedVariablePack);
|
||||
unify(constraint, c.variables, expectedVariablePack);
|
||||
|
||||
auto [variableTys, variablesTail] = flatten(c.variables);
|
||||
|
||||
@ -1755,7 +1790,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (auto iterFtv = get<FunctionType>(*instantiatedIterFn))
|
||||
{
|
||||
TypePackId expectedIterArgs = arena->addTypePack({iteratorTy});
|
||||
unify(constraint->scope, constraint->location, iterFtv->argTypes, expectedIterArgs);
|
||||
unify(constraint, iterFtv->argTypes, expectedIterArgs);
|
||||
|
||||
TypePack iterRets = extendTypePack(*arena, builtinTypes, iterFtv->retTypes, 2);
|
||||
|
||||
@ -1779,7 +1814,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
|
||||
|
||||
const TypeId expectedNextTy = arena->addType(FunctionType{nextArgPack, nextRetPack});
|
||||
unify(constraint->scope, constraint->location, *instantiatedNextFn, expectedNextTy);
|
||||
unify(constraint, *instantiatedNextFn, expectedNextTy);
|
||||
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack});
|
||||
}
|
||||
@ -1849,13 +1884,11 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
||||
const TypePackId nextRetPack = arena->addTypePack(TypePack{{retIndex}, valueTailTy});
|
||||
|
||||
const TypeId expectedNextTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope, nextArgPack, nextRetPack});
|
||||
std::optional<TypeError> error = unify(constraint->scope, constraint->location, nextTy, expectedNextTy);
|
||||
bool ok = unify(constraint, nextTy, expectedNextTy);
|
||||
|
||||
// if there are no errors from unifying the two, we can pass forward the expected type as our selected resolution.
|
||||
if (!error)
|
||||
if (ok)
|
||||
(*c.astForInNextTypes)[c.nextAstFragment] = expectedNextTy;
|
||||
else
|
||||
reportError(*error);
|
||||
|
||||
auto it = begin(nextRetPack);
|
||||
std::vector<TypeId> modifiedNextRetHead;
|
||||
@ -1882,14 +1915,14 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
||||
}
|
||||
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(
|
||||
TypeId subjectType, const std::string& propName, bool suppressSimplification)
|
||||
TypeId subjectType, const std::string& propName, ValueContext context, bool suppressSimplification)
|
||||
{
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
return lookupTableProp(subjectType, propName, suppressSimplification, seen);
|
||||
return lookupTableProp(subjectType, propName, context, suppressSimplification, seen);
|
||||
}
|
||||
|
||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(
|
||||
TypeId subjectType, const std::string& propName, bool suppressSimplification, DenseHashSet<TypeId>& seen)
|
||||
TypeId subjectType, const std::string& propName, ValueContext context, bool suppressSimplification, DenseHashSet<TypeId>& seen)
|
||||
{
|
||||
if (seen.contains(subjectType))
|
||||
return {};
|
||||
@ -1906,19 +1939,58 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
else if (auto ttv = getMutable<TableType>(subjectType))
|
||||
{
|
||||
if (auto prop = ttv->props.find(propName); prop != ttv->props.end())
|
||||
return {{}, FFlag::DebugLuauReadWriteProperties ? prop->second.readType() : prop->second.type()};
|
||||
else if (ttv->indexer && maybeString(ttv->indexer->indexType))
|
||||
{
|
||||
switch (context)
|
||||
{
|
||||
case ValueContext::RValue:
|
||||
if (auto rt = prop->second.readTy)
|
||||
return {{}, rt};
|
||||
break;
|
||||
case ValueContext::LValue:
|
||||
if (auto wt = prop->second.writeTy)
|
||||
return {{}, wt};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ttv->indexer && maybeString(ttv->indexer->indexType))
|
||||
return {{}, ttv->indexer->indexResultType};
|
||||
else if (ttv->state == TableState::Free)
|
||||
|
||||
if (ttv->state == TableState::Free)
|
||||
{
|
||||
TypeId result = freshType(arena, builtinTypes, ttv->scope);
|
||||
ttv->props[propName] = Property{result};
|
||||
switch (context)
|
||||
{
|
||||
case ValueContext::RValue:
|
||||
ttv->props[propName].readTy = result;
|
||||
break;
|
||||
case ValueContext::LValue:
|
||||
if (auto it = ttv->props.find(propName); it != ttv->props.end() && it->second.isReadOnly())
|
||||
{
|
||||
// We do infer read-only properties, but we do not infer
|
||||
// separate read and write types.
|
||||
//
|
||||
// If we encounter a case where a free table has a read-only
|
||||
// property that we subsequently sense a write to, we make
|
||||
// the judgement that the property is read-write and that
|
||||
// both the read and write types are the same.
|
||||
|
||||
Property& prop = it->second;
|
||||
|
||||
prop.writeTy = prop.readTy;
|
||||
return {{}, *prop.readTy};
|
||||
}
|
||||
else
|
||||
ttv->props[propName] = Property::rw(result);
|
||||
|
||||
break;
|
||||
}
|
||||
return {{}, result};
|
||||
}
|
||||
}
|
||||
else if (auto mt = get<MetatableType>(subjectType))
|
||||
{
|
||||
auto [blocked, result] = lookupTableProp(mt->table, propName, suppressSimplification, seen);
|
||||
auto [blocked, result] = lookupTableProp(mt->table, propName, context, suppressSimplification, seen);
|
||||
if (!blocked.empty() || result)
|
||||
return {blocked, result};
|
||||
|
||||
@ -1949,13 +2021,13 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
}
|
||||
}
|
||||
else
|
||||
return lookupTableProp(indexType, propName, suppressSimplification, seen);
|
||||
return lookupTableProp(indexType, propName, context, suppressSimplification, seen);
|
||||
}
|
||||
}
|
||||
else if (auto ct = get<ClassType>(subjectType))
|
||||
{
|
||||
if (auto p = lookupClassProp(ct, propName))
|
||||
return {{}, p->type()};
|
||||
return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy};
|
||||
if (ct->indexer)
|
||||
{
|
||||
return {{}, ct->indexer->indexResultType};
|
||||
@ -1970,14 +2042,14 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
if (indexProp == metatable->props.end())
|
||||
return {{}, std::nullopt};
|
||||
|
||||
return lookupTableProp(indexProp->second.type(), propName, suppressSimplification, seen);
|
||||
return lookupTableProp(indexProp->second.type(), propName, context, suppressSimplification, seen);
|
||||
}
|
||||
else if (auto ft = get<FreeType>(subjectType))
|
||||
{
|
||||
const TypeId upperBound = follow(ft->upperBound);
|
||||
|
||||
if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
|
||||
return lookupTableProp(upperBound, propName, suppressSimplification, seen);
|
||||
return lookupTableProp(upperBound, propName, context, suppressSimplification, seen);
|
||||
|
||||
// TODO: The upper bound could be an intersection that contains suitable tables or classes.
|
||||
|
||||
@ -1987,7 +2059,16 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
TableType* tt = getMutable<TableType>(newUpperBound);
|
||||
LUAU_ASSERT(tt);
|
||||
TypeId propType = freshType(arena, builtinTypes, scope);
|
||||
tt->props[propName] = Property{propType};
|
||||
|
||||
switch (context)
|
||||
{
|
||||
case ValueContext::RValue:
|
||||
tt->props[propName] = Property::readonly(propType);
|
||||
break;
|
||||
case ValueContext::LValue:
|
||||
tt->props[propName] = Property::rw(propType);
|
||||
break;
|
||||
}
|
||||
|
||||
unify(scope, Location{}, subjectType, newUpperBound);
|
||||
|
||||
@ -2000,7 +2081,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
|
||||
for (TypeId ty : utv)
|
||||
{
|
||||
auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, suppressSimplification, seen);
|
||||
auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, context, suppressSimplification, seen);
|
||||
blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
|
||||
if (innerResult)
|
||||
options.insert(*innerResult);
|
||||
@ -2029,7 +2110,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
|
||||
for (TypeId ty : itv)
|
||||
{
|
||||
auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, suppressSimplification, seen);
|
||||
auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, context, suppressSimplification, seen);
|
||||
blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
|
||||
if (innerResult)
|
||||
options.insert(*innerResult);
|
||||
@ -2055,34 +2136,39 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||
return {{}, std::nullopt};
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
||||
template <typename TID>
|
||||
bool ConstraintSolver::unify(NotNull<Scope> scope, Location location, TID subType, TID superType)
|
||||
{
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, scope, NotNull{&iceReporter}};
|
||||
|
||||
bool success = u2.unify(subTy, superTy);
|
||||
const bool ok = u2.unify(subType, superType);
|
||||
|
||||
if (success)
|
||||
if (ok)
|
||||
{
|
||||
for (const auto& [expanded, additions] : u2.expandedFreeTypes)
|
||||
{
|
||||
for (TypeId addition : additions)
|
||||
upperBoundContributors[expanded].push_back(std::make_pair(constraint->location, addition));
|
||||
upperBoundContributors[expanded].push_back(std::make_pair(location, addition));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unification only fails when doing so would fail the occurs check.
|
||||
// ie create a self-bound type or a cyclic type pack
|
||||
reportError(OccursCheckFailed{}, constraint->location);
|
||||
reportError(OccursCheckFailed{}, location);
|
||||
return false;
|
||||
}
|
||||
|
||||
unblock(subTy, constraint->location);
|
||||
unblock(superTy, constraint->location);
|
||||
unblock(subType, location);
|
||||
unblock(superType, location);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TID>
|
||||
bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
||||
{
|
||||
return unify(constraint->scope, constraint->location, subTy, superTy);
|
||||
}
|
||||
|
||||
void ConstraintSolver::bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, Location location)
|
||||
{
|
||||
resultTy = follow(resultTy);
|
||||
@ -2297,6 +2383,21 @@ void ConstraintSolver::unblock(const std::vector<TypePackId>& packs, Location lo
|
||||
unblock(t, location);
|
||||
}
|
||||
|
||||
void ConstraintSolver::reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst)
|
||||
{
|
||||
for (auto [_, newTy] : subst.newTypes)
|
||||
{
|
||||
if (get<TypeFamilyInstanceType>(newTy))
|
||||
pushConstraint(scope, location, ReduceConstraint{newTy});
|
||||
}
|
||||
|
||||
for (auto [_, newPack] : subst.newPacks)
|
||||
{
|
||||
if (get<TypeFamilyInstanceTypePack>(newPack))
|
||||
pushConstraint(scope, location, ReducePackConstraint{newPack});
|
||||
}
|
||||
}
|
||||
|
||||
bool ConstraintSolver::isBlocked(TypeId ty)
|
||||
{
|
||||
ty = follow(ty);
|
||||
@ -2318,39 +2419,6 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
||||
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
|
||||
}
|
||||
|
||||
std::optional<TypeError> ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypeId subType, TypeId superType)
|
||||
{
|
||||
Unifier2 u2{NotNull{arena}, builtinTypes, scope, NotNull{&iceReporter}};
|
||||
|
||||
const bool ok = u2.unify(subType, superType);
|
||||
|
||||
if (!ok)
|
||||
return {{location, UnificationTooComplex{}}};
|
||||
|
||||
unblock(subType, Location{});
|
||||
unblock(superType, Location{});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack)
|
||||
{
|
||||
Unifier2 u{arena, builtinTypes, scope, NotNull{&iceReporter}};
|
||||
|
||||
u.unify(subPack, superPack);
|
||||
|
||||
for (const auto& [expanded, additions] : u.expandedFreeTypes)
|
||||
{
|
||||
for (TypeId addition : additions)
|
||||
upperBoundContributors[expanded].push_back(std::make_pair(location, addition));
|
||||
}
|
||||
|
||||
unblock(subPack, Location{});
|
||||
unblock(superPack, Location{});
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
NotNull<Constraint> ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
|
||||
{
|
||||
std::unique_ptr<Constraint> c = std::make_unique<Constraint>(scope, location, std::move(cv));
|
||||
|
@ -541,11 +541,36 @@ struct ErrorConverter
|
||||
"' is used in a way that will run time error";
|
||||
}
|
||||
|
||||
std::string operator()(const PropertyAccessViolation& e) const
|
||||
{
|
||||
const std::string stringKey = isIdentifier(e.key) ? e.key : "\"" + e.key + "\"";
|
||||
switch (e.context)
|
||||
{
|
||||
case PropertyAccessViolation::CannotRead:
|
||||
return "Property " + stringKey + " of table '" + toString(e.table) + "' is write-only";
|
||||
case PropertyAccessViolation::CannotWrite:
|
||||
return "Property " + stringKey + " of table '" + toString(e.table) + "' is read-only";
|
||||
}
|
||||
|
||||
LUAU_UNREACHABLE();
|
||||
return "<Invalid PropertyAccessViolation>";
|
||||
}
|
||||
|
||||
std::string operator()(const CheckedFunctionIncorrectArgs& e) const
|
||||
{
|
||||
return "Checked Function " + e.functionName + " expects " + std::to_string(e.expected) + " arguments, but received " +
|
||||
std::to_string(e.actual);
|
||||
}
|
||||
|
||||
std::string operator()(const UnexpectedTypeInSubtyping& e) const
|
||||
{
|
||||
return "Encountered an unexpected type in subtyping: " + toString(e.ty);
|
||||
}
|
||||
|
||||
std::string operator()(const UnexpectedTypePackInSubtyping& e) const
|
||||
{
|
||||
return "Encountered an unexpected type pack in subtyping: " + toString(e.tp);
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
@ -638,6 +663,11 @@ bool UnknownProperty::operator==(const UnknownProperty& rhs) const
|
||||
return *table == *rhs.table && key == rhs.key;
|
||||
}
|
||||
|
||||
bool PropertyAccessViolation::operator==(const PropertyAccessViolation& rhs) const
|
||||
{
|
||||
return *table == *rhs.table && key == rhs.key && context == rhs.context;
|
||||
}
|
||||
|
||||
bool NotATable::operator==(const NotATable& rhs) const
|
||||
{
|
||||
return ty == rhs.ty;
|
||||
@ -884,6 +914,16 @@ bool CheckedFunctionIncorrectArgs::operator==(const CheckedFunctionIncorrectArgs
|
||||
return functionName == rhs.functionName && expected == rhs.expected && actual == rhs.actual;
|
||||
}
|
||||
|
||||
bool UnexpectedTypeInSubtyping::operator==(const UnexpectedTypeInSubtyping& rhs) const
|
||||
{
|
||||
return ty == rhs.ty;
|
||||
}
|
||||
|
||||
bool UnexpectedTypePackInSubtyping::operator==(const UnexpectedTypePackInSubtyping& rhs) const
|
||||
{
|
||||
return tp == rhs.tp;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
return toString(error, TypeErrorToStringOptions{});
|
||||
@ -1059,9 +1099,15 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
||||
{
|
||||
e.argumentType = clone(e.argumentType);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, PropertyAccessViolation>)
|
||||
e.table = clone(e.table);
|
||||
else if constexpr (std::is_same_v<T, CheckedFunctionIncorrectArgs>)
|
||||
{
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, UnexpectedTypeInSubtyping>)
|
||||
e.ty = clone(e.ty);
|
||||
else if constexpr (std::is_same_v<T, UnexpectedTypePackInSubtyping>)
|
||||
e.tp = clone(e.tp);
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -32,9 +32,8 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
|
||||
|
||||
namespace Luau
|
||||
@ -1219,6 +1218,15 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
|
||||
result->cancelled = true;
|
||||
}
|
||||
|
||||
if (recordJsonLog)
|
||||
{
|
||||
std::string output = logger->compileOutput();
|
||||
if (FFlag::DebugLuauLogSolverToJsonFile && writeJsonLog)
|
||||
writeJsonLog(sourceModule.name, std::move(output));
|
||||
else
|
||||
printf("%s\n", output.c_str());
|
||||
}
|
||||
|
||||
for (TypeError& e : cs.errors)
|
||||
result->errors.emplace_back(std::move(e));
|
||||
|
||||
@ -1263,15 +1271,6 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
|
||||
freeze(result->internalTypes);
|
||||
freeze(result->interfaceTypes);
|
||||
|
||||
if (recordJsonLog)
|
||||
{
|
||||
std::string output = logger->compileOutput();
|
||||
if (FFlag::DebugLuauLogSolverToJsonFile && writeJsonLog)
|
||||
writeJsonLog(sourceModule.name, std::move(output));
|
||||
else
|
||||
printf("%s\n", output.c_str());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "Luau/Instantiation.h"
|
||||
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Instantiation2.h" // including for `Replacer` which was stolen since it will be kept in the new solver
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
@ -143,39 +144,6 @@ TypePackId ReplaceGenerics::clean(TypePackId tp)
|
||||
return addTypePack(TypePackVar(FreeTypePack{scope, level}));
|
||||
}
|
||||
|
||||
struct Replacer : Substitution
|
||||
{
|
||||
DenseHashMap<TypeId, TypeId> replacements;
|
||||
DenseHashMap<TypePackId, TypePackId> replacementPacks;
|
||||
|
||||
Replacer(NotNull<TypeArena> arena, DenseHashMap<TypeId, TypeId> replacements, DenseHashMap<TypePackId, TypePackId> replacementPacks)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, replacements(std::move(replacements))
|
||||
, replacementPacks(std::move(replacementPacks))
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
return replacements.find(ty) != nullptr;
|
||||
}
|
||||
|
||||
bool isDirty(TypePackId tp) override
|
||||
{
|
||||
return replacementPacks.find(tp) != nullptr;
|
||||
}
|
||||
|
||||
TypeId clean(TypeId ty) override
|
||||
{
|
||||
return replacements[ty];
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
{
|
||||
return replacementPacks[tp];
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<TypeId> instantiate(
|
||||
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty)
|
||||
{
|
||||
|
38
Analysis/src/Instantiation2.cpp
Normal file
38
Analysis/src/Instantiation2.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/Instantiation2.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
bool Instantiation2::isDirty(TypeId ty)
|
||||
{
|
||||
return get<GenericType>(ty) && genericSubstitutions.contains(ty);
|
||||
}
|
||||
|
||||
bool Instantiation2::isDirty(TypePackId tp)
|
||||
{
|
||||
return get<GenericTypePack>(tp) && genericPackSubstitutions.contains(tp);
|
||||
}
|
||||
|
||||
TypeId Instantiation2::clean(TypeId ty)
|
||||
{
|
||||
TypeId substTy = genericSubstitutions[ty];
|
||||
const FreeType* ft = get<FreeType>(substTy);
|
||||
|
||||
// violation of the substitution invariant if this is not a free type.
|
||||
LUAU_ASSERT(ft);
|
||||
|
||||
// if we didn't learn anything about the lower bound, we pick the upper bound instead.
|
||||
if (get<NeverType>(ft->lowerBound))
|
||||
return ft->upperBound;
|
||||
|
||||
// we default to the lower bound which represents the most specific type for the free type.
|
||||
return ft->lowerBound;
|
||||
}
|
||||
|
||||
TypePackId Instantiation2::clean(TypePackId tp)
|
||||
{
|
||||
return genericPackSubstitutions[tp];
|
||||
}
|
||||
|
||||
} // namespace Luau
|
@ -207,9 +207,15 @@ static void errorToString(std::ostream& stream, const T& err)
|
||||
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
|
||||
stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
|
||||
"', argumentType = '" + toString(err.argumentType) + "' }";
|
||||
else if constexpr (std::is_same_v<T, PropertyAccessViolation>)
|
||||
stream << "PropertyAccessViolation { table = " << toString(err.table) << ", prop = '" << err.key << "', context = " << err.context << " }";
|
||||
else if constexpr (std::is_same_v<T, CheckedFunctionIncorrectArgs>)
|
||||
stream << "CheckedFunction { functionName = '" + err.functionName + ", expected = " + std::to_string(err.expected) +
|
||||
", actual = " + std::to_string(err.actual) + "}";
|
||||
else if constexpr (std::is_same_v<T, UnexpectedTypeInSubtyping>)
|
||||
stream << "UnexpectedTypeInSubtyping { ty = '" + toString(err.ty) + "' }";
|
||||
else if constexpr (std::is_same_v<T, UnexpectedTypePackInSubtyping>)
|
||||
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -1848,6 +1850,49 @@ private:
|
||||
|
||||
bool visit(AstTypeTable* node) override
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
struct Rec
|
||||
{
|
||||
AstTableAccess access;
|
||||
Location location;
|
||||
};
|
||||
DenseHashMap<AstName, Rec> names(AstName{});
|
||||
|
||||
for (const AstTableProp& item : node->props)
|
||||
{
|
||||
Rec* rec = names.find(item.name);
|
||||
if (!rec)
|
||||
{
|
||||
names[item.name] = Rec{item.access, item.location};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (int(rec->access) & int(item.access))
|
||||
{
|
||||
if (rec->access == item.access)
|
||||
emitWarning(*context, LintWarning::Code_TableLiteral, item.location,
|
||||
"Table type field '%s' is a duplicate; previously defined at line %d", item.name.value, rec->location.begin.line + 1);
|
||||
else if (rec->access == AstTableAccess::ReadWrite)
|
||||
emitWarning(*context, LintWarning::Code_TableLiteral, item.location,
|
||||
"Table type field '%s' is already read-write; previously defined at line %d", item.name.value,
|
||||
rec->location.begin.line + 1);
|
||||
else if (rec->access == AstTableAccess::Read)
|
||||
emitWarning(*context, LintWarning::Code_TableLiteral, rec->location,
|
||||
"Table type field '%s' already has a read type defined at line %d", item.name.value, rec->location.begin.line + 1);
|
||||
else if (rec->access == AstTableAccess::Write)
|
||||
emitWarning(*context, LintWarning::Code_TableLiteral, rec->location,
|
||||
"Table type field '%s' already has a write type defined at line %d", item.name.value, rec->location.begin.line + 1);
|
||||
else
|
||||
LUAU_ASSERT(!"Unreachable");
|
||||
}
|
||||
else
|
||||
rec->access = AstTableAccess(int(rec->access) | int(item.access));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
DenseHashMap<AstName, int> names(AstName{});
|
||||
|
||||
for (const AstTableProp& item : node->props)
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/Set.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeFwd.h"
|
||||
@ -20,7 +21,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||
LUAU_FASTFLAG(LuauTransitiveSubtyping)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
namespace Luau
|
||||
@ -472,11 +472,11 @@ bool Normalizer::isInhabited(TypeId ty, Set<TypeId>& seen)
|
||||
{
|
||||
for (const auto& [_, prop] : ttv->props)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
// A table enclosing a read property whose type is uninhabitable is also itself uninhabitable,
|
||||
// but not its write property. That just means the write property doesn't exist, and so is readonly.
|
||||
if (auto ty = prop.readType(); ty && !isInhabited(*ty, seen))
|
||||
if (auto ty = prop.readTy; ty && !isInhabited(*ty, seen))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@ -2349,12 +2349,61 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||
{
|
||||
const auto& [_name, tprop] = *tfound;
|
||||
// TODO: variance issues here, which can't be fixed until we have read/write property types
|
||||
prop.setType(intersectionType(hprop.type(), tprop.type()));
|
||||
hereSubThere &= (prop.type() == hprop.type());
|
||||
thereSubHere &= (prop.type() == tprop.type());
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (hprop.readTy.has_value())
|
||||
{
|
||||
if (tprop.readTy.has_value())
|
||||
{
|
||||
TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
|
||||
prop.readTy = ty;
|
||||
hereSubThere &= (ty == hprop.readTy);
|
||||
thereSubHere &= (ty == tprop.readTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
prop.readTy = *hprop.readTy;
|
||||
thereSubHere = false;
|
||||
}
|
||||
}
|
||||
else if (tprop.readTy.has_value())
|
||||
{
|
||||
prop.readTy = *tprop.readTy;
|
||||
hereSubThere = false;
|
||||
}
|
||||
|
||||
if (hprop.writeTy.has_value())
|
||||
{
|
||||
if (tprop.writeTy.has_value())
|
||||
{
|
||||
prop.writeTy = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.writeTy, *tprop.writeTy).result;
|
||||
hereSubThere &= (prop.writeTy == hprop.writeTy);
|
||||
thereSubHere &= (prop.writeTy == tprop.writeTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
prop.writeTy = *hprop.writeTy;
|
||||
thereSubHere = false;
|
||||
}
|
||||
}
|
||||
else if (tprop.writeTy.has_value())
|
||||
{
|
||||
prop.writeTy = *tprop.writeTy;
|
||||
hereSubThere = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prop.setType(intersectionType(hprop.type(), tprop.type()));
|
||||
hereSubThere &= (prop.type() == hprop.type());
|
||||
thereSubHere &= (prop.type() == tprop.type());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: string indexers
|
||||
result.props[name] = prop;
|
||||
|
||||
if (prop.readTy || prop.writeTy)
|
||||
result.props[name] = prop;
|
||||
}
|
||||
|
||||
for (const auto& [name, tprop] : tttv->props)
|
||||
@ -2431,8 +2480,10 @@ void Normalizer::intersectTablesWithTable(TypeIds& heres, TypeId there)
|
||||
{
|
||||
TypeIds tmp;
|
||||
for (TypeId here : heres)
|
||||
{
|
||||
if (std::optional<TypeId> inter = intersectionOfTables(here, there))
|
||||
tmp.insert(*inter);
|
||||
}
|
||||
heres.retain(tmp);
|
||||
heres.insert(tmp.begin(), tmp.end());
|
||||
}
|
||||
@ -2441,9 +2492,14 @@ void Normalizer::intersectTables(TypeIds& heres, const TypeIds& theres)
|
||||
{
|
||||
TypeIds tmp;
|
||||
for (TypeId here : heres)
|
||||
{
|
||||
for (TypeId there : theres)
|
||||
{
|
||||
if (std::optional<TypeId> inter = intersectionOfTables(here, there))
|
||||
tmp.insert(*inter);
|
||||
}
|
||||
}
|
||||
|
||||
heres.retain(tmp);
|
||||
heres.insert(tmp.begin(), tmp.end());
|
||||
}
|
||||
|
@ -3,10 +3,10 @@
|
||||
#include "Luau/Simplify.h"
|
||||
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Normalize.h" // TypeIds
|
||||
#include "Luau/RecursionCounter.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/Set.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypePairHash.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
|
||||
#include <algorithm>
|
||||
@ -17,6 +17,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
using SimplifierSeenSet = Set<std::pair<TypeId, TypeId>, TypePairHash>;
|
||||
|
||||
struct TypeSimplifier
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
@ -226,23 +228,27 @@ static bool isTypeVariable(TypeId ty)
|
||||
return get<FreeType>(ty) || get<GenericType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty);
|
||||
}
|
||||
|
||||
Relation relate(TypeId left, TypeId right);
|
||||
Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen);
|
||||
|
||||
Relation relateTables(TypeId left, TypeId right)
|
||||
Relation relateTables(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||
{
|
||||
NotNull<const TableType> leftTable{get<TableType>(left)};
|
||||
NotNull<const TableType> rightTable{get<TableType>(right)};
|
||||
LUAU_ASSERT(1 == rightTable->props.size());
|
||||
// Disjoint props have nothing in common
|
||||
// t1 with props p1's cannot appear in t2 and t2 with props p2's cannot appear in t1
|
||||
bool foundPropFromLeftInRight = std::any_of(begin(leftTable->props), end(leftTable->props), [&](auto prop) {
|
||||
return rightTable->props.find(prop.first) != end(rightTable->props);
|
||||
});
|
||||
bool foundPropFromRightInLeft = std::any_of(begin(rightTable->props), end(rightTable->props), [&](auto prop) {
|
||||
return leftTable->props.find(prop.first) != end(leftTable->props);
|
||||
});
|
||||
bool foundPropFromLeftInRight = std::any_of(begin(leftTable->props), end(leftTable->props),
|
||||
[&](auto prop)
|
||||
{
|
||||
return rightTable->props.count(prop.first) > 0;
|
||||
});
|
||||
bool foundPropFromRightInLeft = std::any_of(begin(rightTable->props), end(rightTable->props),
|
||||
[&](auto prop)
|
||||
{
|
||||
return leftTable->props.count(prop.first) > 0;
|
||||
});
|
||||
|
||||
if (!(foundPropFromLeftInRight || foundPropFromRightInLeft) && leftTable->props.size() >= 1 && rightTable->props.size() >= 1)
|
||||
if (!foundPropFromLeftInRight && !foundPropFromRightInLeft && leftTable->props.size() >= 1 && rightTable->props.size() >= 1)
|
||||
return Relation::Disjoint;
|
||||
|
||||
const auto [propName, rightProp] = *begin(rightTable->props);
|
||||
@ -257,7 +263,10 @@ Relation relateTables(TypeId left, TypeId right)
|
||||
|
||||
const Property leftProp = it->second;
|
||||
|
||||
Relation r = relate(leftProp.type(), rightProp.type());
|
||||
if (!leftProp.isShared() || !rightProp.isShared())
|
||||
return Relation::Intersects;
|
||||
|
||||
Relation r = relate(leftProp.type(), rightProp.type(), seen);
|
||||
if (r == Relation::Coincident && 1 != leftTable->props.size())
|
||||
{
|
||||
// eg {tag: "cat", prop: string} & {tag: "cat"}
|
||||
@ -268,7 +277,7 @@ Relation relateTables(TypeId left, TypeId right)
|
||||
}
|
||||
|
||||
// A cheap and approximate subtype test
|
||||
Relation relate(TypeId left, TypeId right)
|
||||
Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
|
||||
{
|
||||
// TODO nice to have: Relate functions of equal argument and return arity
|
||||
|
||||
@ -278,6 +287,14 @@ Relation relate(TypeId left, TypeId right)
|
||||
if (left == right)
|
||||
return Relation::Coincident;
|
||||
|
||||
std::pair<TypeId, TypeId> typePair{left, right};
|
||||
if (!seen.insert(typePair))
|
||||
{
|
||||
// TODO: is this right at all?
|
||||
// The thinking here is that this is a cycle if we get here, and therefore its coincident.
|
||||
return Relation::Coincident;
|
||||
}
|
||||
|
||||
if (get<UnknownType>(left))
|
||||
{
|
||||
if (get<AnyType>(right))
|
||||
@ -291,7 +308,7 @@ Relation relate(TypeId left, TypeId right)
|
||||
}
|
||||
|
||||
if (get<UnknownType>(right))
|
||||
return flip(relate(right, left));
|
||||
return flip(relate(right, left, seen));
|
||||
|
||||
if (get<AnyType>(left))
|
||||
{
|
||||
@ -302,7 +319,7 @@ Relation relate(TypeId left, TypeId right)
|
||||
}
|
||||
|
||||
if (get<AnyType>(right))
|
||||
return flip(relate(right, left));
|
||||
return flip(relate(right, left, seen));
|
||||
|
||||
// Type variables
|
||||
// * FreeType
|
||||
@ -340,7 +357,7 @@ Relation relate(TypeId left, TypeId right)
|
||||
return Relation::Disjoint;
|
||||
}
|
||||
if (get<ErrorType>(right))
|
||||
return flip(relate(right, left));
|
||||
return flip(relate(right, left, seen));
|
||||
|
||||
if (get<NeverType>(left))
|
||||
{
|
||||
@ -350,7 +367,7 @@ Relation relate(TypeId left, TypeId right)
|
||||
return Relation::Subset;
|
||||
}
|
||||
if (get<NeverType>(right))
|
||||
return flip(relate(right, left));
|
||||
return flip(relate(right, left, seen));
|
||||
|
||||
if (auto ut = get<IntersectionType>(left))
|
||||
return Relation::Intersects;
|
||||
@ -363,14 +380,14 @@ Relation relate(TypeId left, TypeId right)
|
||||
{
|
||||
std::vector<Relation> opts;
|
||||
for (TypeId part : ut)
|
||||
if (relate(left, part) == Relation::Subset)
|
||||
if (relate(left, part, seen) == Relation::Subset)
|
||||
return Relation::Subset;
|
||||
return Relation::Intersects;
|
||||
}
|
||||
|
||||
if (auto rnt = get<NegationType>(right))
|
||||
{
|
||||
Relation a = relate(left, rnt->ty);
|
||||
Relation a = relate(left, rnt->ty, seen);
|
||||
switch (a)
|
||||
{
|
||||
case Relation::Coincident:
|
||||
@ -401,7 +418,7 @@ Relation relate(TypeId left, TypeId right)
|
||||
}
|
||||
}
|
||||
else if (get<NegationType>(left))
|
||||
return flip(relate(right, left));
|
||||
return flip(relate(right, left, seen));
|
||||
|
||||
if (auto lp = get<PrimitiveType>(left))
|
||||
{
|
||||
@ -448,7 +465,7 @@ Relation relate(TypeId left, TypeId right)
|
||||
return Relation::Disjoint;
|
||||
|
||||
if (get<PrimitiveType>(right))
|
||||
return flip(relate(right, left));
|
||||
return flip(relate(right, left, seen));
|
||||
if (auto rs = get<SingletonType>(right))
|
||||
{
|
||||
if (ls->variant == rs->variant)
|
||||
@ -485,7 +502,7 @@ Relation relate(TypeId left, TypeId right)
|
||||
// TODO PROBABLY indexers and metatables.
|
||||
if (1 == rt->props.size())
|
||||
{
|
||||
Relation r = relateTables(left, right);
|
||||
Relation r = relateTables(left, right, seen);
|
||||
/*
|
||||
* A reduction of these intersections is certainly possible, but
|
||||
* it would require minting new table types. Also, I don't think
|
||||
@ -504,7 +521,7 @@ Relation relate(TypeId left, TypeId right)
|
||||
return r;
|
||||
}
|
||||
else if (1 == lt->props.size())
|
||||
return flip(relate(right, left));
|
||||
return flip(relate(right, left, seen));
|
||||
else
|
||||
return Relation::Intersects;
|
||||
}
|
||||
@ -531,6 +548,13 @@ Relation relate(TypeId left, TypeId right)
|
||||
return Relation::Intersects;
|
||||
}
|
||||
|
||||
// A cheap and approximate subtype test
|
||||
Relation relate(TypeId left, TypeId right)
|
||||
{
|
||||
SimplifierSeenSet seen{{}};
|
||||
return relate(left, right, seen);
|
||||
}
|
||||
|
||||
TypeId TypeSimplifier::mkNegation(TypeId ty)
|
||||
{
|
||||
TypeId result = nullptr;
|
||||
@ -1056,7 +1080,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
|
||||
const auto [propName, leftProp] = *begin(lt->props);
|
||||
|
||||
auto it = rt->props.find(propName);
|
||||
if (it != rt->props.end())
|
||||
if (it != rt->props.end() && leftProp.isShared() && it->second.isShared())
|
||||
{
|
||||
Relation r = relate(leftProp.type(), it->second.type());
|
||||
|
||||
@ -1266,9 +1290,12 @@ TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
|
||||
{
|
||||
if (1 == tt->props.size())
|
||||
{
|
||||
TypeId propTy = simplify(begin(tt->props)->second.type(), seen);
|
||||
if (get<NeverType>(propTy))
|
||||
return builtinTypes->neverType;
|
||||
if (std::optional<TypeId> readTy = begin(tt->props)->second.readTy)
|
||||
{
|
||||
TypeId propTy = simplify(*readTy, seen);
|
||||
if (get<NeverType>(propTy))
|
||||
return builtinTypes->neverType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include <stdexcept>
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_FASTFLAGVARIABLE(LuauPreallocateTarjanVectors, false);
|
||||
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
|
||||
|
||||
@ -185,10 +185,10 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
||||
LUAU_ASSERT(!ttv->boundTo);
|
||||
for (const auto& [name, prop] : ttv->props)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
visitChild(prop.readType());
|
||||
visitChild(prop.writeType());
|
||||
visitChild(prop.readTy);
|
||||
visitChild(prop.writeTy);
|
||||
}
|
||||
else
|
||||
visitChild(prop.type());
|
||||
@ -700,8 +700,8 @@ void Substitution::replaceChildren(TypeId ty)
|
||||
LUAU_ASSERT(!ttv->boundTo);
|
||||
for (auto& [name, prop] : ttv->props)
|
||||
{
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
prop = Property::create(replace(prop.readType()), replace(prop.writeType()));
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
prop = Property::create(replace(prop.readTy), replace(prop.writeTy));
|
||||
else
|
||||
prop.setType(replace(prop.type()));
|
||||
}
|
||||
|
@ -7,7 +7,9 @@
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Scope.h"
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
@ -217,7 +219,14 @@ SubtypingResult& SubtypingResult::withSuperPath(TypePath::Path path)
|
||||
|
||||
SubtypingResult& SubtypingResult::withErrors(ErrorVec& err)
|
||||
{
|
||||
errors = std::move(err);
|
||||
for (TypeError& e : err)
|
||||
errors.emplace_back(e);
|
||||
return *this;
|
||||
}
|
||||
|
||||
SubtypingResult& SubtypingResult::withError(TypeError err)
|
||||
{
|
||||
errors.push_back(std::move(err));
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -245,6 +254,74 @@ SubtypingResult SubtypingResult::any(const std::vector<SubtypingResult>& results
|
||||
return acc;
|
||||
}
|
||||
|
||||
struct ApplyMappedGenerics : Substitution
|
||||
{
|
||||
using MappedGenerics = DenseHashMap<TypeId, SubtypingEnvironment::GenericBounds>;
|
||||
using MappedGenericPacks = DenseHashMap<TypePackId, TypePackId>;
|
||||
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
NotNull<TypeArena> arena;
|
||||
|
||||
MappedGenerics& mappedGenerics;
|
||||
MappedGenericPacks& mappedGenericPacks;
|
||||
|
||||
|
||||
ApplyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks)
|
||||
: Substitution(TxnLog::empty(), arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
, arena(arena)
|
||||
, mappedGenerics(mappedGenerics)
|
||||
, mappedGenericPacks(mappedGenericPacks)
|
||||
{
|
||||
}
|
||||
|
||||
bool isDirty(TypeId ty) override
|
||||
{
|
||||
return mappedGenerics.contains(ty);
|
||||
}
|
||||
|
||||
bool isDirty(TypePackId tp) override
|
||||
{
|
||||
return mappedGenericPacks.contains(tp);
|
||||
}
|
||||
|
||||
TypeId clean(TypeId ty) override
|
||||
{
|
||||
const auto& bounds = mappedGenerics[ty];
|
||||
|
||||
if (bounds.upperBound.empty())
|
||||
return builtinTypes->unknownType;
|
||||
|
||||
if (bounds.upperBound.size() == 1)
|
||||
return *begin(bounds.upperBound);
|
||||
|
||||
return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
|
||||
}
|
||||
|
||||
TypePackId clean(TypePackId tp) override
|
||||
{
|
||||
return mappedGenericPacks[tp];
|
||||
}
|
||||
|
||||
bool ignoreChildren(TypeId ty) override
|
||||
{
|
||||
if (get<ClassType>(ty))
|
||||
return true;
|
||||
|
||||
return ty->persistent;
|
||||
}
|
||||
bool ignoreChildren(TypePackId ty) override
|
||||
{
|
||||
return ty->persistent;
|
||||
}
|
||||
};
|
||||
|
||||
std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty)
|
||||
{
|
||||
ApplyMappedGenerics amg{builtinTypes, arena, mappedGenerics, mappedGenericPacks};
|
||||
return amg.substitute(ty);
|
||||
}
|
||||
|
||||
Subtyping::Subtyping(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> typeArena, NotNull<Normalizer> normalizer,
|
||||
NotNull<InternalErrorReporter> iceReporter, NotNull<Scope> scope)
|
||||
: builtinTypes(builtinTypes)
|
||||
@ -493,9 +570,19 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||
}
|
||||
}
|
||||
else if (auto subTypeFamilyInstance = get<TypeFamilyInstanceType>(subTy))
|
||||
{
|
||||
if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy))
|
||||
subTypeFamilyInstance = get<TypeFamilyInstanceType>(*substSubTy);
|
||||
|
||||
result = isCovariantWith(env, subTypeFamilyInstance, superTy);
|
||||
}
|
||||
else if (auto superTypeFamilyInstance = get<TypeFamilyInstanceType>(superTy))
|
||||
{
|
||||
if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy))
|
||||
superTypeFamilyInstance = get<TypeFamilyInstanceType>(*substSuperTy);
|
||||
|
||||
result = isCovariantWith(env, subTy, superTypeFamilyInstance);
|
||||
}
|
||||
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
||||
{
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
@ -604,7 +691,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
else if (get<ErrorTypePack>(*subTail))
|
||||
return SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail);
|
||||
else
|
||||
unexpected(*subTail);
|
||||
return SubtypingResult{false}
|
||||
.withSubComponent(TypePath::PackField::Tail)
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -656,7 +745,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
else if (get<ErrorTypePack>(*superTail))
|
||||
return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail);
|
||||
else
|
||||
unexpected(*superTail);
|
||||
return SubtypingResult{false}
|
||||
.withSuperComponent(TypePath::PackField::Tail)
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
|
||||
}
|
||||
else
|
||||
return {false};
|
||||
@ -717,8 +808,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
// error type is fine on either side
|
||||
results.push_back(SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail));
|
||||
else
|
||||
iceReporter->ice(
|
||||
format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str()));
|
||||
return SubtypingResult{false}
|
||||
.withBothComponent(TypePath::PackField::Tail)
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}})
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
|
||||
}
|
||||
else if (subTail)
|
||||
{
|
||||
@ -732,7 +825,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
return SubtypingResult{ok}.withSubComponent(TypePath::PackField::Tail);
|
||||
}
|
||||
else
|
||||
unexpected(*subTail);
|
||||
return SubtypingResult{false}
|
||||
.withSubComponent(TypePath::PackField::Tail)
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
|
||||
}
|
||||
else if (superTail)
|
||||
{
|
||||
@ -759,7 +854,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||
results.push_back(SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail));
|
||||
}
|
||||
else
|
||||
iceReporter->ice("Subtyping test encountered the unexpected type pack: " + toString(*superTail));
|
||||
return SubtypingResult{false}
|
||||
.withSuperComponent(TypePath::PackField::Tail)
|
||||
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
|
||||
}
|
||||
|
||||
SubtypingResult result = SubtypingResult::all(results);
|
||||
@ -1126,18 +1223,32 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
||||
if (subTable->props.empty() && !subTable->indexer && superTable->indexer)
|
||||
return {false};
|
||||
|
||||
for (const auto& [name, prop] : superTable->props)
|
||||
for (const auto& [name, superProp] : superTable->props)
|
||||
{
|
||||
std::vector<SubtypingResult> results;
|
||||
if (auto it = subTable->props.find(name); it != subTable->props.end())
|
||||
results.push_back(isInvariantWith(env, it->second.type(), prop.type()).withBothComponent(TypePath::Property(name)));
|
||||
if (auto subIter = subTable->props.find(name); subIter != subTable->props.end())
|
||||
results.push_back(isCovariantWith(env, subIter->second, superProp, name));
|
||||
|
||||
if (subTable->indexer)
|
||||
{
|
||||
if (isInvariantWith(env, subTable->indexer->indexType, builtinTypes->stringType).isSubtype)
|
||||
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, prop.type())
|
||||
.withSubComponent(TypePath::TypeField::IndexResult)
|
||||
.withSuperComponent(TypePath::Property(name)));
|
||||
if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType).isSubtype)
|
||||
{
|
||||
if (superProp.isShared())
|
||||
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type())
|
||||
.withSubComponent(TypePath::TypeField::IndexResult)
|
||||
.withSuperComponent(TypePath::Property::read(name)));
|
||||
else
|
||||
{
|
||||
if (superProp.readTy)
|
||||
results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy)
|
||||
.withSubComponent(TypePath::TypeField::IndexResult)
|
||||
.withSuperComponent(TypePath::Property::read(name)));
|
||||
if (superProp.writeTy)
|
||||
results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy)
|
||||
.withSubComponent(TypePath::TypeField::IndexResult)
|
||||
.withSuperComponent(TypePath::Property::write(name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (results.empty())
|
||||
@ -1197,7 +1308,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Clas
|
||||
for (const auto& [name, prop] : superTable->props)
|
||||
{
|
||||
if (auto classProp = lookupClassProp(subClass, name))
|
||||
result.andAlso(isInvariantWith(env, prop.type(), classProp->type()).withBothComponent(TypePath::Property(name)));
|
||||
{
|
||||
result.andAlso(isCovariantWith(env, *classProp, prop, name));
|
||||
}
|
||||
else
|
||||
return SubtypingResult{false};
|
||||
}
|
||||
@ -1230,7 +1343,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim
|
||||
{
|
||||
if (auto stringTable = get<TableType>(it->second.type()))
|
||||
result.orElse(
|
||||
isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().prop("__index").build()));
|
||||
isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1252,7 +1365,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing
|
||||
{
|
||||
if (auto stringTable = get<TableType>(it->second.type()))
|
||||
result.orElse(
|
||||
isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().prop("__index").build()));
|
||||
isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1267,6 +1380,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
||||
.andAlso(isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType).withBothComponent(TypePath::TypeField::IndexResult));
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Property& subProp, const Property& superProp, const std::string& name)
|
||||
{
|
||||
SubtypingResult res{true};
|
||||
|
||||
if (superProp.isShared() && subProp.isShared())
|
||||
res.andAlso(isInvariantWith(env, subProp.type(), superProp.type()).withBothComponent(TypePath::Property::read(name)));
|
||||
else
|
||||
{
|
||||
if (superProp.readTy.has_value() && subProp.readTy.has_value())
|
||||
res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy).withBothComponent(TypePath::Property::read(name)));
|
||||
if (superProp.writeTy.has_value() && subProp.writeTy.has_value())
|
||||
res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy).withBothComponent(TypePath::Property::write(name)));
|
||||
|
||||
if (superProp.isReadWrite())
|
||||
{
|
||||
if (subProp.isReadOnly())
|
||||
res.andAlso(SubtypingResult{false}.withBothComponent(TypePath::Property::read(name)));
|
||||
else if (subProp.isWriteOnly())
|
||||
res.andAlso(SubtypingResult{false}.withBothComponent(TypePath::Property::write(name)));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||
{
|
||||
if (!subNorm || !superNorm)
|
||||
@ -1473,14 +1611,4 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
|
||||
return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
|
||||
}
|
||||
|
||||
void Subtyping::unexpected(TypeId ty)
|
||||
{
|
||||
iceReporter->ice(format("Unexpected type %s", toString(ty).c_str()));
|
||||
}
|
||||
|
||||
void Subtyping::unexpected(TypePackId tp)
|
||||
{
|
||||
iceReporter->ice(format("Unexpected type pack %s", toString(tp).c_str()));
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -374,7 +374,7 @@ struct TypeStringifier
|
||||
tv->ty);
|
||||
}
|
||||
|
||||
void stringify(const std::string& name, const Property& prop)
|
||||
void emitKey(const std::string& name)
|
||||
{
|
||||
if (isIdentifier(name))
|
||||
state.emit(name);
|
||||
@ -385,31 +385,46 @@ struct TypeStringifier
|
||||
state.emit("\"]");
|
||||
}
|
||||
state.emit(": ");
|
||||
}
|
||||
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
void _newStringify(const std::string& name, const Property& prop)
|
||||
{
|
||||
bool comma = false;
|
||||
if (prop.isShared())
|
||||
{
|
||||
// We special case the stringification if the property's read and write types are shared.
|
||||
if (prop.isShared())
|
||||
return stringify(*prop.readType());
|
||||
|
||||
// Otherwise emit them separately.
|
||||
if (auto ty = prop.readType())
|
||||
{
|
||||
state.emit("read ");
|
||||
stringify(*ty);
|
||||
}
|
||||
|
||||
if (prop.readType() && prop.writeType())
|
||||
state.emit(" + ");
|
||||
|
||||
if (auto ty = prop.writeType())
|
||||
{
|
||||
state.emit("write ");
|
||||
stringify(*ty);
|
||||
}
|
||||
}
|
||||
else
|
||||
emitKey(name);
|
||||
stringify(prop.type());
|
||||
return;
|
||||
}
|
||||
|
||||
if (prop.readTy)
|
||||
{
|
||||
state.emit("read ");
|
||||
emitKey(name);
|
||||
stringify(*prop.readTy);
|
||||
comma = true;
|
||||
}
|
||||
if (prop.writeTy)
|
||||
{
|
||||
if (comma)
|
||||
{
|
||||
state.emit(",");
|
||||
state.newline();
|
||||
}
|
||||
|
||||
state.emit("write ");
|
||||
emitKey(name);
|
||||
stringify(*prop.writeTy);
|
||||
}
|
||||
}
|
||||
|
||||
void stringify(const std::string& name, const Property& prop)
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return _newStringify(name, prop);
|
||||
|
||||
emitKey(name);
|
||||
stringify(prop.type());
|
||||
}
|
||||
|
||||
void stringify(TypePackId tp);
|
||||
@ -1755,7 +1770,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, HasPropConstraint>)
|
||||
{
|
||||
return tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\"";
|
||||
return tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\" ctx=" + std::to_string(int(c.context));
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, SetPropConstraint>)
|
||||
{
|
||||
@ -1801,6 +1816,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
|
||||
{
|
||||
return "reduce " + tos(c.tp);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, EqualityConstraint>)
|
||||
return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType);
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
||||
};
|
||||
|
@ -26,7 +26,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -632,13 +631,10 @@ Property::Property(TypeId readTy, bool deprecated, const std::string& deprecated
|
||||
, readTy(readTy)
|
||||
, writeTy(readTy)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
||||
}
|
||||
|
||||
Property Property::readonly(TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
||||
|
||||
Property p;
|
||||
p.readTy = ty;
|
||||
return p;
|
||||
@ -646,8 +642,6 @@ Property Property::readonly(TypeId ty)
|
||||
|
||||
Property Property::writeonly(TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
||||
|
||||
Property p;
|
||||
p.writeTy = ty;
|
||||
return p;
|
||||
@ -660,8 +654,6 @@ Property Property::rw(TypeId ty)
|
||||
|
||||
Property Property::rw(TypeId read, TypeId write)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
||||
|
||||
Property p;
|
||||
p.readTy = read;
|
||||
p.writeTy = write;
|
||||
@ -683,29 +675,15 @@ Property Property::create(std::optional<TypeId> read, std::optional<TypeId> writ
|
||||
|
||||
TypeId Property::type() const
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
||||
LUAU_ASSERT(readTy);
|
||||
return *readTy;
|
||||
}
|
||||
|
||||
void Property::setType(TypeId ty)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
||||
readTy = ty;
|
||||
}
|
||||
|
||||
std::optional<TypeId> Property::readType() const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
||||
LUAU_ASSERT(!(bool(readTy) && bool(writeTy)));
|
||||
return readTy;
|
||||
}
|
||||
|
||||
std::optional<TypeId> Property::writeType() const
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
||||
LUAU_ASSERT(!(bool(readTy) && bool(writeTy)));
|
||||
return writeTy;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
writeTy = ty;
|
||||
}
|
||||
|
||||
bool Property::isShared() const
|
||||
@ -713,6 +691,21 @@ bool Property::isShared() const
|
||||
return readTy && writeTy && readTy == writeTy;
|
||||
}
|
||||
|
||||
bool Property::isReadOnly() const
|
||||
{
|
||||
return readTy && !writeTy;
|
||||
}
|
||||
|
||||
bool Property::isWriteOnly() const
|
||||
{
|
||||
return !readTy && writeTy;
|
||||
}
|
||||
|
||||
bool Property::isReadWrite() const
|
||||
{
|
||||
return readTy && writeTy;
|
||||
}
|
||||
|
||||
TableType::TableType(TableState state, TypeLevel level, Scope* scope)
|
||||
: state(state)
|
||||
, level(level)
|
||||
@ -961,6 +954,7 @@ BuiltinTypes::BuiltinTypes()
|
||||
, optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true}))
|
||||
, emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true}))
|
||||
, anyTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, /*persistent*/ true}))
|
||||
, unknownTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{unknownType}, /*persistent*/ true}))
|
||||
, neverTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{neverType}, /*persistent*/ true}))
|
||||
, uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true}))
|
||||
, errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true}))
|
||||
|
@ -1460,7 +1460,7 @@ struct TypeChecker2
|
||||
{
|
||||
visit(expr, ValueContext::RValue);
|
||||
TypeId leftType = stripFromNilAndReport(lookupType(expr), location);
|
||||
checkIndexTypeFromType(leftType, propName, location, context, astIndexExprTy);
|
||||
checkIndexTypeFromType(leftType, propName, context, location, astIndexExprTy);
|
||||
}
|
||||
|
||||
void visit(AstExprIndexName* indexName, ValueContext context)
|
||||
@ -1709,8 +1709,8 @@ struct TypeChecker2
|
||||
|
||||
TypeId visit(AstExprBinary* expr, AstNode* overrideKey = nullptr)
|
||||
{
|
||||
visit(expr->left, ValueContext::LValue);
|
||||
visit(expr->right, ValueContext::LValue);
|
||||
visit(expr->left, ValueContext::RValue);
|
||||
visit(expr->right, ValueContext::RValue);
|
||||
|
||||
NotNull<Scope> scope = stack.back();
|
||||
|
||||
@ -2534,20 +2534,16 @@ struct TypeChecker2
|
||||
reportError(std::move(e));
|
||||
}
|
||||
|
||||
// If the provided type does not have the named property, report an error.
|
||||
void checkIndexTypeFromType(TypeId tableTy, const std::string& prop, const Location& location, ValueContext context, TypeId astIndexExprType)
|
||||
/* A helper for checkIndexTypeFromType.
|
||||
*
|
||||
* Returns a pair:
|
||||
* * A boolean indicating that at least one of the constituent types
|
||||
* contains the prop, and
|
||||
* * A vector of types that do not contain the prop.
|
||||
*/
|
||||
std::pair<bool, std::vector<TypeId>> lookupProp(const NormalizedType* norm, const std::string& prop, ValueContext context,
|
||||
const Location& location, TypeId astIndexExprType, std::vector<TypeError>& errors)
|
||||
{
|
||||
const NormalizedType* norm = normalizer.normalize(tableTy);
|
||||
if (!norm)
|
||||
{
|
||||
reportError(NormalizationTooComplex{}, location);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the type is error suppressing, we don't actually have any work left to do.
|
||||
if (norm->shouldSuppressErrors())
|
||||
return;
|
||||
|
||||
bool foundOneProp = false;
|
||||
std::vector<TypeId> typesMissingTheProp;
|
||||
|
||||
@ -2556,7 +2552,7 @@ struct TypeChecker2
|
||||
return;
|
||||
|
||||
DenseHashSet<TypeId> seen{nullptr};
|
||||
bool found = hasIndexTypeFromType(ty, prop, location, seen, astIndexExprType);
|
||||
bool found = hasIndexTypeFromType(ty, prop, context, location, seen, astIndexExprType, errors);
|
||||
foundOneProp |= found;
|
||||
if (!found)
|
||||
typesMissingTheProp.push_back(ty);
|
||||
@ -2601,6 +2597,26 @@ struct TypeChecker2
|
||||
fetch(tyvar);
|
||||
}
|
||||
|
||||
return {foundOneProp, typesMissingTheProp};
|
||||
}
|
||||
|
||||
// If the provided type does not have the named property, report an error.
|
||||
void checkIndexTypeFromType(TypeId tableTy, const std::string& prop, ValueContext context, const Location& location, TypeId astIndexExprType)
|
||||
{
|
||||
const NormalizedType* norm = normalizer.normalize(tableTy);
|
||||
if (!norm)
|
||||
{
|
||||
reportError(NormalizationTooComplex{}, location);
|
||||
return;
|
||||
}
|
||||
|
||||
// if the type is error suppressing, we don't actually have any work left to do.
|
||||
if (norm->shouldSuppressErrors())
|
||||
return;
|
||||
|
||||
std::vector<TypeError> dummy;
|
||||
const auto [foundOneProp, typesMissingTheProp] = lookupProp(norm, prop, context, location, astIndexExprType, module->errors);
|
||||
|
||||
if (!typesMissingTheProp.empty())
|
||||
{
|
||||
if (foundOneProp)
|
||||
@ -2611,17 +2627,29 @@ struct TypeChecker2
|
||||
// the `else` branch.
|
||||
else if (context == ValueContext::LValue && !get<ClassType>(tableTy))
|
||||
{
|
||||
if (get<PrimitiveType>(tableTy) || get<FunctionType>(tableTy))
|
||||
const auto [lvFoundOneProp, lvTypesMissingTheProp] = lookupProp(norm, prop, ValueContext::RValue, location, astIndexExprType, dummy);
|
||||
if (lvFoundOneProp && lvTypesMissingTheProp.empty())
|
||||
reportError(PropertyAccessViolation{tableTy, prop, PropertyAccessViolation::CannotWrite}, location);
|
||||
else if (get<PrimitiveType>(tableTy) || get<FunctionType>(tableTy))
|
||||
reportError(NotATable{tableTy}, location);
|
||||
else
|
||||
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
|
||||
}
|
||||
else if (context == ValueContext::RValue && !get<ClassType>(tableTy))
|
||||
{
|
||||
const auto [rvFoundOneProp, rvTypesMissingTheProp] = lookupProp(norm, prop, ValueContext::LValue, location, astIndexExprType, dummy);
|
||||
if (rvFoundOneProp && rvTypesMissingTheProp.empty())
|
||||
reportError(PropertyAccessViolation{tableTy, prop, PropertyAccessViolation::CannotRead}, location);
|
||||
else
|
||||
reportError(UnknownProperty{tableTy, prop}, location);
|
||||
}
|
||||
else
|
||||
reportError(UnknownProperty{tableTy, prop}, location);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasIndexTypeFromType(TypeId ty, const std::string& prop, const Location& location, DenseHashSet<TypeId>& seen, TypeId astIndexExprType)
|
||||
bool hasIndexTypeFromType(TypeId ty, const std::string& prop, ValueContext context, const Location& location, DenseHashSet<TypeId>& seen,
|
||||
TypeId astIndexExprType, std::vector<TypeError>& errors)
|
||||
{
|
||||
// If we have already encountered this type, we must assume that some
|
||||
// other codepath will do the right thing and signal false if the
|
||||
@ -2635,14 +2663,14 @@ struct TypeChecker2
|
||||
|
||||
if (isString(ty))
|
||||
{
|
||||
std::optional<TypeId> mtIndex = Luau::findMetatableEntry(builtinTypes, module->errors, builtinTypes->stringType, "__index", location);
|
||||
std::optional<TypeId> mtIndex = Luau::findMetatableEntry(builtinTypes, errors, builtinTypes->stringType, "__index", location);
|
||||
LUAU_ASSERT(mtIndex);
|
||||
ty = *mtIndex;
|
||||
}
|
||||
|
||||
if (auto tt = getTableType(ty))
|
||||
{
|
||||
if (findTablePropertyRespectingMeta(builtinTypes, module->errors, ty, prop, location))
|
||||
if (findTablePropertyRespectingMeta(builtinTypes, errors, ty, prop, context, location))
|
||||
return true;
|
||||
|
||||
if (tt->indexer)
|
||||
@ -2674,11 +2702,11 @@ struct TypeChecker2
|
||||
}
|
||||
else if (const UnionType* utv = get<UnionType>(ty))
|
||||
return std::all_of(begin(utv), end(utv), [&](TypeId part) {
|
||||
return hasIndexTypeFromType(part, prop, location, seen, astIndexExprType);
|
||||
return hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors);
|
||||
});
|
||||
else if (const IntersectionType* itv = get<IntersectionType>(ty))
|
||||
return std::any_of(begin(itv), end(itv), [&](TypeId part) {
|
||||
return hasIndexTypeFromType(part, prop, location, seen, astIndexExprType);
|
||||
return hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors);
|
||||
});
|
||||
else
|
||||
return false;
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/Instantiation.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
@ -853,6 +854,27 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, con
|
||||
TypeId lhsTy = follow(typeParams.at(0));
|
||||
TypeId rhsTy = follow(typeParams.at(1));
|
||||
|
||||
// Algebra Reduction Rules for comparison family functions
|
||||
// Note that comparing to never tells you nothing about the other operand
|
||||
// lt< 'a , never> -> continue
|
||||
// lt< never, 'a> -> continue
|
||||
// lt< 'a, t> -> 'a is t - we'll solve the constraint, return and solve lt<t, t> -> bool
|
||||
// lt< t, 'a> -> same as above
|
||||
bool canSubmitConstraint = ctx->solver && ctx->constraint;
|
||||
if (canSubmitConstraint)
|
||||
{
|
||||
if (get<FreeType>(lhsTy) && get<NeverType>(rhsTy) == nullptr)
|
||||
{
|
||||
auto c1 = ctx->solver->pushConstraint(ctx->scope, {}, EqualityConstraint{lhsTy, rhsTy});
|
||||
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
|
||||
}
|
||||
else if (get<FreeType>(rhsTy) && get<NeverType>(lhsTy) == nullptr)
|
||||
{
|
||||
auto c1 = ctx->solver->pushConstraint(ctx->scope, {}, EqualityConstraint{rhsTy, lhsTy});
|
||||
const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {lhsTy}, {}};
|
||||
@ -1248,8 +1270,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
|
||||
if (!normTy)
|
||||
return {std::nullopt, false, {}, {}};
|
||||
|
||||
// if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes as
|
||||
// well)
|
||||
// if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes
|
||||
// as well)
|
||||
if (normTy->hasTables() == normTy->hasClasses())
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
// Maximum number of steps to follow when traversing a path. May not always
|
||||
// equate to the number of components in a path, depending on the traversal
|
||||
@ -29,7 +29,7 @@ namespace TypePath
|
||||
Property::Property(std::string name)
|
||||
: name(std::move(name))
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
||||
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||
}
|
||||
|
||||
Property Property::read(std::string name)
|
||||
@ -146,21 +146,21 @@ Path PathBuilder::build()
|
||||
|
||||
PathBuilder& PathBuilder::readProp(std::string name)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
components.push_back(Property{std::move(name), true});
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::writeProp(std::string name)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
||||
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||
components.push_back(Property{std::move(name), false});
|
||||
return *this;
|
||||
}
|
||||
|
||||
PathBuilder& PathBuilder::prop(std::string name)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
||||
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||
components.push_back(Property{std::move(name)});
|
||||
return *this;
|
||||
}
|
||||
@ -323,7 +323,7 @@ struct TraversalState
|
||||
// logic there.
|
||||
updateCurrent(*m);
|
||||
|
||||
if (!traverse(TypePath::Property{"__index"}))
|
||||
if (!traverse(TypePath::Property::read("__index")))
|
||||
return false;
|
||||
|
||||
return traverse(property);
|
||||
@ -333,8 +333,8 @@ struct TraversalState
|
||||
if (prop)
|
||||
{
|
||||
std::optional<TypeId> maybeType;
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
maybeType = property.isRead ? prop->readType() : prop->writeType();
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
maybeType = property.isRead ? prop->readTy : prop->writeTy;
|
||||
else
|
||||
maybeType = prop->type();
|
||||
|
||||
@ -514,7 +514,7 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
|
||||
if constexpr (std::is_same_v<T, TypePath::Property>)
|
||||
{
|
||||
result << '[';
|
||||
if (FFlag::DebugLuauReadWriteProperties)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (c.isRead)
|
||||
result << "read ";
|
||||
|
@ -44,6 +44,12 @@ std::optional<TypeId> findMetatableEntry(
|
||||
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location)
|
||||
{
|
||||
return findTablePropertyRespectingMeta(builtinTypes, errors, ty, name, ValueContext::RValue, location);
|
||||
}
|
||||
|
||||
std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, ValueContext context, Location location)
|
||||
{
|
||||
if (get<AnyType>(ty))
|
||||
return ty;
|
||||
@ -52,7 +58,20 @@ std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||
{
|
||||
const auto& it = tableType->props.find(name);
|
||||
if (it != tableType->props.end())
|
||||
return it->second.type();
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
switch (context)
|
||||
{
|
||||
case ValueContext::RValue:
|
||||
return it->second.readTy;
|
||||
case ValueContext::LValue:
|
||||
return it->second.writeTy;
|
||||
}
|
||||
}
|
||||
else
|
||||
return it->second.type();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<TypeId> mtIndex = findMetatableEntry(builtinTypes, errors, ty, "__index", location);
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/VisitType.h"
|
||||
|
||||
@ -19,6 +20,59 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
static bool areCompatible(TypeId left, TypeId right)
|
||||
{
|
||||
auto p = get2<TableType, TableType>(follow(left), follow(right));
|
||||
if (!p)
|
||||
return true;
|
||||
|
||||
const TableType* leftTable = p.first;
|
||||
LUAU_ASSERT(leftTable);
|
||||
const TableType* rightTable = p.second;
|
||||
LUAU_ASSERT(rightTable);
|
||||
|
||||
const auto missingPropIsCompatible = [](const Property& leftProp, const TableType* rightTable) {
|
||||
// Two tables may be compatible even if their shapes aren't exactly the
|
||||
// same if the extra property is optional, free (and therefore
|
||||
// potentially optional), or if the right table has an indexer. Or if
|
||||
// the right table is free (and therefore potentially has an indexer or
|
||||
// a compatible property)
|
||||
|
||||
LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared());
|
||||
|
||||
const TypeId leftType = follow(
|
||||
leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type()
|
||||
);
|
||||
|
||||
if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
for (const auto& [name, leftProp]: leftTable->props)
|
||||
{
|
||||
auto it = rightTable->props.find(name);
|
||||
if (it == rightTable->props.end())
|
||||
{
|
||||
if (!missingPropIsCompatible(leftProp, rightTable))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [name, rightProp]: rightTable->props)
|
||||
{
|
||||
auto it = leftTable->props.find(name);
|
||||
if (it == leftTable->props.end())
|
||||
{
|
||||
if (!missingPropIsCompatible(rightProp, leftTable))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice)
|
||||
: arena(arena)
|
||||
, builtinTypes(builtinTypes)
|
||||
@ -34,6 +88,12 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||
subTy = follow(subTy);
|
||||
superTy = follow(superTy);
|
||||
|
||||
if (auto subGen = genericSubstitutions.find(subTy))
|
||||
return unify(*subGen, superTy);
|
||||
|
||||
if (auto superGen = genericSubstitutions.find(superTy))
|
||||
return unify(subTy, *superGen);
|
||||
|
||||
if (seenTypePairings.contains({subTy, superTy}))
|
||||
return true;
|
||||
seenTypePairings.insert({subTy, superTy});
|
||||
@ -44,14 +104,21 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||
FreeType* subFree = getMutable<FreeType>(subTy);
|
||||
FreeType* superFree = getMutable<FreeType>(superTy);
|
||||
|
||||
if (subFree)
|
||||
if (subFree && superFree)
|
||||
{
|
||||
superFree->lowerBound = mkUnion(subFree->lowerBound, superFree->lowerBound);
|
||||
superFree->upperBound = mkIntersection(subFree->upperBound, superFree->upperBound);
|
||||
asMutable(subTy)->ty.emplace<BoundType>(superTy);
|
||||
}
|
||||
else if (subFree)
|
||||
{
|
||||
subFree->upperBound = mkIntersection(subFree->upperBound, superTy);
|
||||
expandedFreeTypes[subTy].push_back(superTy);
|
||||
}
|
||||
|
||||
if (superFree)
|
||||
else if (superFree)
|
||||
{
|
||||
superFree->lowerBound = mkUnion(superFree->lowerBound, subTy);
|
||||
}
|
||||
|
||||
if (subFree || superFree)
|
||||
return true;
|
||||
@ -159,13 +226,11 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
|
||||
|
||||
if (shouldInstantiate)
|
||||
{
|
||||
std::optional<TypeId> instantiated = instantiate(builtinTypes, arena, NotNull{&limits}, scope, subTy);
|
||||
if (!instantiated)
|
||||
return false;
|
||||
for (auto generic : subFn->generics)
|
||||
genericSubstitutions[generic] = freshType(arena, builtinTypes, scope);
|
||||
|
||||
subFn = get<FunctionType>(*instantiated);
|
||||
|
||||
LUAU_ASSERT(subFn); // instantiation should not make a function type _not_ a function type.
|
||||
for (auto genericPack : subFn->genericPacks)
|
||||
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);
|
||||
}
|
||||
|
||||
bool argResult = unify(superFn->argTypes, subFn->argTypes);
|
||||
@ -179,7 +244,10 @@ bool Unifier2::unify(const UnionType* subUnion, TypeId superTy)
|
||||
|
||||
// if the occurs check fails for any option, it fails overall
|
||||
for (auto subOption : subUnion->options)
|
||||
result &= unify(subOption, superTy);
|
||||
{
|
||||
if (areCompatible(subOption, superTy))
|
||||
result &= unify(subOption, superTy);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -190,7 +258,10 @@ bool Unifier2::unify(TypeId subTy, const UnionType* superUnion)
|
||||
|
||||
// if the occurs check fails for any option, it fails overall
|
||||
for (auto superOption : superUnion->options)
|
||||
result &= unify(subTy, superOption);
|
||||
{
|
||||
if (areCompatible(subTy, superOption))
|
||||
result &= unify(subTy, superOption);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -228,7 +299,21 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
||||
auto superPropOpt = superTable->props.find(propName);
|
||||
|
||||
if (superPropOpt != superTable->props.end())
|
||||
result &= unify(subProp.type(), superPropOpt->second.type());
|
||||
{
|
||||
const Property& superProp = superPropOpt->second;
|
||||
|
||||
if (subProp.isReadOnly() && superProp.isReadOnly())
|
||||
result &= unify(*subProp.readTy, *superPropOpt->second.readTy);
|
||||
else if (subProp.isReadOnly())
|
||||
result &= unify(*subProp.readTy, superProp.type());
|
||||
else if (superProp.isReadOnly())
|
||||
result &= unify(subProp.type(), *superProp.readTy);
|
||||
else
|
||||
{
|
||||
result &= unify(subProp.type(), superProp.type());
|
||||
result &= unify(superProp.type(), subProp.type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto subTypeParamsIter = subTable->instantiatedTypeParams.begin();
|
||||
@ -293,10 +378,19 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||
subTp = follow(subTp);
|
||||
superTp = follow(superTp);
|
||||
|
||||
if (auto subGen = genericPackSubstitutions.find(subTp))
|
||||
return unify(*subGen, superTp);
|
||||
|
||||
if (auto superGen = genericPackSubstitutions.find(superTp))
|
||||
return unify(subTp, *superGen);
|
||||
|
||||
if (seenTypePackPairings.contains({subTp, superTp}))
|
||||
return true;
|
||||
seenTypePackPairings.insert({subTp, superTp});
|
||||
|
||||
if (subTp == superTp)
|
||||
return true;
|
||||
|
||||
const FreeTypePack* subFree = get<FreeTypePack>(subTp);
|
||||
const FreeTypePack* superFree = get<FreeTypePack>(superTp);
|
||||
|
||||
@ -378,11 +472,14 @@ struct FreeTypeSearcher : TypeVisitor
|
||||
{
|
||||
}
|
||||
|
||||
enum
|
||||
enum Polarity
|
||||
{
|
||||
Positive,
|
||||
Negative
|
||||
} polarity = Positive;
|
||||
Negative,
|
||||
Both,
|
||||
};
|
||||
|
||||
Polarity polarity = Positive;
|
||||
|
||||
void flip()
|
||||
{
|
||||
@ -394,6 +491,8 @@ struct FreeTypeSearcher : TypeVisitor
|
||||
case Negative:
|
||||
polarity = Positive;
|
||||
break;
|
||||
case Both:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -419,6 +518,10 @@ struct FreeTypeSearcher : TypeVisitor
|
||||
case Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Both:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -436,10 +539,35 @@ struct FreeTypeSearcher : TypeVisitor
|
||||
case Negative:
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
case Both:
|
||||
positiveTypes[ty]++;
|
||||
negativeTypes[ty]++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
for (const auto& [_name, prop] : tt.props)
|
||||
{
|
||||
if (prop.isReadOnly())
|
||||
traverse(*prop.readTy);
|
||||
else
|
||||
{
|
||||
LUAU_ASSERT(prop.isShared());
|
||||
|
||||
Polarity p = polarity;
|
||||
polarity = Both;
|
||||
traverse(prop.type());
|
||||
polarity = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (tt.indexer)
|
||||
{
|
||||
traverse(tt.indexer->indexType);
|
||||
traverse(tt.indexer->indexResultType);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool visit(TypeId ty, const FunctionType& ft) override
|
||||
@ -538,8 +666,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
||||
if (!ft)
|
||||
return false;
|
||||
|
||||
const bool positiveCount = getCount(positiveTypes, ty);
|
||||
const bool negativeCount = getCount(negativeTypes, ty);
|
||||
const size_t positiveCount = getCount(positiveTypes, ty);
|
||||
const size_t negativeCount = getCount(negativeTypes, ty);
|
||||
|
||||
if (!positiveCount && !negativeCount)
|
||||
return false;
|
||||
|
@ -18,6 +18,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
// See docs/SyntaxChanges.md for an explanation.
|
||||
LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
|
||||
LUAU_FASTFLAGVARIABLE(LuauReadWritePropertySyntax, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1339,7 +1340,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
|
||||
AstTableAccess access = AstTableAccess::ReadWrite;
|
||||
std::optional<Location> accessLocation;
|
||||
|
||||
if (FFlag::LuauReadWritePropertySyntax)
|
||||
if (FFlag::LuauReadWritePropertySyntax || FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
if (lexer.current().type == Lexeme::Name && lexer.lookahead().type != ':')
|
||||
{
|
||||
|
@ -431,6 +431,10 @@ std::vector<std::string> getSourceFiles(int argc, char** argv)
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
// Early out once we reach --program-args,-a since the remaining args are passed to lua
|
||||
if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
|
||||
return files;
|
||||
|
||||
// Treat '-' as a special file whose source is read from stdin
|
||||
// All other arguments that start with '-' are skipped
|
||||
if (argv[i][0] == '-' && argv[i][1] != '\0')
|
||||
|
@ -152,7 +152,7 @@ struct Reducer
|
||||
}
|
||||
|
||||
#if VERBOSE >= 1
|
||||
printf("running %s\n", command.c_str());
|
||||
printf("running %s\n", cmd.c_str());
|
||||
#endif
|
||||
|
||||
TestResult result = TestResult::NoBug;
|
||||
@ -160,7 +160,7 @@ struct Reducer
|
||||
++step;
|
||||
printf("Step %4d...\n", step);
|
||||
|
||||
FILE* p = popen(command.c_str(), "r");
|
||||
FILE* p = popen(cmd.c_str(), "r");
|
||||
|
||||
while (!feof(p))
|
||||
{
|
||||
|
24
CLI/Repl.cpp
24
CLI/Repl.cpp
@ -46,6 +46,8 @@ LUAU_FASTFLAGVARIABLE(LuauUpdatedRequireByStringSemantics, false)
|
||||
constexpr int MaxTraversalLimit = 50;
|
||||
|
||||
static bool codegen = false;
|
||||
static int program_argc = 0;
|
||||
char** program_argv = nullptr;
|
||||
|
||||
// Ctrl-C handling
|
||||
static void sigintCallback(lua_State* L, int gc)
|
||||
@ -318,6 +320,12 @@ void setupState(lua_State* L)
|
||||
luaL_sandbox(L);
|
||||
}
|
||||
|
||||
void setupArguments(lua_State* L, int argc, char** argv)
|
||||
{
|
||||
for (int i = 0; i < argc; ++i)
|
||||
lua_pushstring(L, argv[i]);
|
||||
}
|
||||
|
||||
std::string runCode(lua_State* L, const std::string& source)
|
||||
{
|
||||
std::string bytecode = Luau::compile(source, copts());
|
||||
@ -668,7 +676,8 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
|
||||
if (coverageActive())
|
||||
coverageTrack(L, -1);
|
||||
|
||||
status = lua_resume(L, NULL, 0);
|
||||
setupArguments(L, program_argc, program_argv);
|
||||
status = lua_resume(L, NULL, program_argc);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -704,7 +713,7 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
|
||||
|
||||
static void displayHelp(const char* argv0)
|
||||
{
|
||||
printf("Usage: %s [options] [file list]\n", argv0);
|
||||
printf("Usage: %s [options] [file list] [-a] [arg list]\n", argv0);
|
||||
printf("\n");
|
||||
printf("When file list is omitted, an interactive REPL is started instead.\n");
|
||||
printf("\n");
|
||||
@ -717,6 +726,7 @@ static void displayHelp(const char* argv0)
|
||||
printf(" --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
|
||||
printf(" --timetrace: record compiler time tracing information into trace.json\n");
|
||||
printf(" --codegen: execute code using native code generation\n");
|
||||
printf(" --program-args,-a: declare start of arguments to be passed to the Luau program");
|
||||
}
|
||||
|
||||
static int assertionHandler(const char* expr, const char* file, int line, const char* function)
|
||||
@ -739,6 +749,7 @@ int replMain(int argc, char** argv)
|
||||
bool coverage = false;
|
||||
bool interactive = false;
|
||||
bool codegenPerf = false;
|
||||
int program_args = argc;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
@ -800,6 +811,11 @@ int replMain(int argc, char** argv)
|
||||
{
|
||||
setLuauFlags(argv[i] + 9);
|
||||
}
|
||||
else if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
|
||||
{
|
||||
program_args = i + 1;
|
||||
break;
|
||||
}
|
||||
else if (argv[i][0] == '-')
|
||||
{
|
||||
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
|
||||
@ -808,6 +824,10 @@ int replMain(int argc, char** argv)
|
||||
}
|
||||
}
|
||||
|
||||
program_argc = argc - program_args;
|
||||
program_argv = &argv[program_args];
|
||||
|
||||
|
||||
#if !defined(LUAU_ENABLE_TIME_TRACE)
|
||||
if (FFlag::DebugLuauTimeTracing)
|
||||
{
|
||||
|
@ -57,8 +57,6 @@ LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockLimit, 32'768) // 32 K
|
||||
// Current value is based on some member variables being limited to 16 bits
|
||||
LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockInstructionLimit, 65'536) // 64 K
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(DisableNativeCodegenIfBreakpointIsSet, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -302,7 +300,7 @@ void create(lua_State* L, AllocationCallback* allocationCallback, void* allocati
|
||||
ecb->close = onCloseState;
|
||||
ecb->destroy = onDestroyFunction;
|
||||
ecb->enter = onEnter;
|
||||
ecb->disable = FFlag::DisableNativeCodegenIfBreakpointIsSet ? onDisable : nullptr;
|
||||
ecb->disable = onDisable;
|
||||
}
|
||||
|
||||
void create(lua_State* L)
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
#include "Luau/IrUtils.h"
|
||||
|
||||
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodegenTrackingMultilocationFix, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
namespace CodeGen
|
||||
@ -159,6 +161,9 @@ void IrValueLocationTracking::afterInstLowering(IrInst& inst, uint32_t instIdx)
|
||||
case IrCmd::LOAD_DOUBLE:
|
||||
case IrCmd::LOAD_INT:
|
||||
case IrCmd::LOAD_TVALUE:
|
||||
if (DFFlag::LuauCodegenTrackingMultilocationFix && inst.a.kind == IrOpKind::VmReg)
|
||||
invalidateRestoreOp(inst.a, /*skipValueInvalidation*/ false);
|
||||
|
||||
recordRestoreOp(instIdx, inst.a);
|
||||
break;
|
||||
case IrCmd::STORE_POINTER:
|
||||
|
@ -176,6 +176,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/GlobalTypes.h
|
||||
Analysis/include/Luau/InsertionOrderedMap.h
|
||||
Analysis/include/Luau/Instantiation.h
|
||||
Analysis/include/Luau/Instantiation2.h
|
||||
Analysis/include/Luau/IostreamHelpers.h
|
||||
Analysis/include/Luau/JsonEmitter.h
|
||||
Analysis/include/Luau/Linter.h
|
||||
@ -242,6 +243,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/src/Frontend.cpp
|
||||
Analysis/src/GlobalTypes.cpp
|
||||
Analysis/src/Instantiation.cpp
|
||||
Analysis/src/Instantiation2.cpp
|
||||
Analysis/src/IostreamHelpers.cpp
|
||||
Analysis/src/JsonEmitter.cpp
|
||||
Analysis/src/Linter.cpp
|
||||
@ -457,7 +459,6 @@ if(TARGET Luau.UnitTest)
|
||||
tests/TypeInfer.primitives.test.cpp
|
||||
tests/TypeInfer.provisional.test.cpp
|
||||
tests/TypeInfer.refinements.test.cpp
|
||||
tests/TypeInfer.rwprops.test.cpp
|
||||
tests/TypeInfer.singletons.test.cpp
|
||||
tests/TypeInfer.tables.test.cpp
|
||||
tests/TypeInfer.test.cpp
|
||||
|
@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauSciNumberSkipTrailDot)
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauInterruptablePatternMatch)
|
||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauCodeGenFixBufferLenCheckA64)
|
||||
LUAU_DYNAMIC_FASTFLAG(LuauCodegenTrackingMultilocationFix)
|
||||
|
||||
static lua_CompileOptions defaultOptions()
|
||||
{
|
||||
@ -2040,6 +2041,7 @@ TEST_CASE("SafeEnv")
|
||||
TEST_CASE("Native")
|
||||
{
|
||||
ScopedFastFlag luauCodeGenFixBufferLenCheckA64{DFFlag::LuauCodeGenFixBufferLenCheckA64, true};
|
||||
ScopedFastFlag luauCodegenTrackingMultilocationFix{DFFlag::LuauCodegenTrackingMultilocationFix, true};
|
||||
|
||||
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
|
||||
if (!codegen || !luau_codegen_supported())
|
||||
|
@ -17,7 +17,7 @@ static TypeId requireBinding(Scope* scope, const char* name)
|
||||
|
||||
TEST_SUITE_BEGIN("ConstraintSolver");
|
||||
|
||||
TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "hello")
|
||||
TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "constraint_basics")
|
||||
{
|
||||
solve(R"(
|
||||
local a = 55
|
||||
@ -58,12 +58,7 @@ TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "proper_let_generalization")
|
||||
|
||||
TypeId idType = requireBinding(rootScope, "b");
|
||||
|
||||
ToStringOptions opts;
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("(unknown) -> number" == toString(idType, opts));
|
||||
else
|
||||
CHECK("<a>(a) -> number" == toString(idType, opts));
|
||||
CHECK("(unknown) -> number" == toString(idType));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -204,6 +204,8 @@ TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_property_wrong")
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_cyclic_tables_are_not_different")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x: a): a
|
||||
return x
|
||||
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
TEST_SUITE_BEGIN("Linter");
|
||||
@ -1246,6 +1248,30 @@ _ = {
|
||||
CHECK_EQ(result.warnings[5].text, "Table index 1 is a duplicate; previously defined at line 36");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "read_write_table_props")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
LintResult result = lint(R"(-- line 1
|
||||
type A = {x: number}
|
||||
type B = {read x: number, write x: number}
|
||||
type C = {x: number, read x: number} -- line 4
|
||||
type D = {x: number, write x: number}
|
||||
type E = {read x: number, x: boolean}
|
||||
type F = {read x: number, read x: number}
|
||||
type G = {write x: number, x: boolean}
|
||||
type H = {write x: number, write x: boolean}
|
||||
)");
|
||||
|
||||
REQUIRE(6 == result.warnings.size());
|
||||
CHECK(result.warnings[0].text == "Table type field 'x' is already read-write; previously defined at line 4");
|
||||
CHECK(result.warnings[1].text == "Table type field 'x' is already read-write; previously defined at line 5");
|
||||
CHECK(result.warnings[2].text == "Table type field 'x' already has a read type defined at line 6");
|
||||
CHECK(result.warnings[3].text == "Table type field 'x' is a duplicate; previously defined at line 7");
|
||||
CHECK(result.warnings[4].text == "Table type field 'x' already has a write type defined at line 8");
|
||||
CHECK(result.warnings[5].text == "Table type field 'x' is a duplicate; previously defined at line 9");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInTypeAnnotation")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
|
@ -940,4 +940,26 @@ TEST_CASE_FIXTURE(NormalizeFixture, "normalize_unknown")
|
||||
CHECK(toString(normalizer.typeFromNormal(*nt)) == "unknown");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CHECK("{ x: string }" == toString(normal("{ read x: string } & { x: string }"), {true}));
|
||||
CHECK("{ x: string }" == toString(normal("{ x: string } & { read x: string }"), {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props_2")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CHECK(R"({ x: never })" == toString(normal(R"({ x: "hello" } & { x: "world" })"), {true}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props_3")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CHECK("{ read x: never }" == toString(normal(R"({ read x: "hello" } & { read x: "world" })"), {true}));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -15,6 +15,8 @@
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
namespace Luau
|
||||
@ -65,6 +67,8 @@ struct SubtypeFixture : Fixture
|
||||
UnifierSharedState sharedState{&ice};
|
||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)};
|
||||
ScopePtr moduleScope{new Scope(rootScope)};
|
||||
|
||||
@ -220,6 +224,11 @@ struct SubtypeFixture : Fixture
|
||||
{"Y", builtinTypes->numberType},
|
||||
});
|
||||
|
||||
TypeId readOnlyVec2Class = cls("ReadOnlyVec2", {
|
||||
{"X", Property::readonly(builtinTypes->numberType)},
|
||||
{"Y", Property::readonly(builtinTypes->numberType)},
|
||||
});
|
||||
|
||||
// "hello" | "hello"
|
||||
TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
|
||||
|
||||
@ -787,6 +796,34 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{x: <T>(T) -> ()} <: {x: <U>(U) -> ()}")
|
||||
CHECK_IS_SUBTYPE(tbl({{"x", genericTToNothingType}}), tbl({{"x", genericUToNothingType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ x: number } <: { read x: number }")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({{"x", Property::readonly(builtinTypes->numberType)}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ x: number } <: { write x: number }")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({{"x", Property::writeonly(builtinTypes->numberType)}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ x: \"hello\" } <: { read x: string }")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CHECK_IS_SUBTYPE(tbl({{"x", helloType}}), tbl({{"x", Property::readonly(builtinTypes->stringType)}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ x: string } <: { write x: string }")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->stringType}}), tbl({{"x", Property::writeonly(builtinTypes->stringType)}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({}));
|
||||
@ -1027,6 +1064,28 @@ TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 <!: table & { X: number, Y: number }")
|
||||
CHECK_IS_NOT_SUBTYPE(vec2Class, meet(builtinTypes->tableType, xy));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "ReadOnlyVec2 <!: { X: number, Y: number}")
|
||||
{
|
||||
CHECK_IS_NOT_SUBTYPE(readOnlyVec2Class, tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "ReadOnlyVec2 <: { read X: number, read Y: number}")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(
|
||||
readOnlyVec2Class, tbl({{"X", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}}));
|
||||
}
|
||||
|
||||
TEST_IS_SUBTYPE(vec2Class, tbl({{"X", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}}));
|
||||
|
||||
TEST_IS_NOT_SUBTYPE(tbl({{"P", grandchildOneClass}}), tbl({{"P", Property::rw(rootClass)}}));
|
||||
TEST_IS_SUBTYPE(tbl({{"P", grandchildOneClass}}), tbl({{"P", Property::readonly(rootClass)}}));
|
||||
TEST_IS_SUBTYPE(tbl({{"P", rootClass}}), tbl({{"P", Property::writeonly(grandchildOneClass)}}));
|
||||
|
||||
TEST_IS_NOT_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", rootClass}}));
|
||||
TEST_IS_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", Property::readonly(rootClass)}}));
|
||||
TEST_IS_NOT_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", grandchildOneClass}}));
|
||||
TEST_IS_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", Property::writeonly(grandchildOneClass)}}));
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: { lower : (string) -> string }")
|
||||
{
|
||||
CHECK_IS_SUBTYPE(helloType, tableWithLower);
|
||||
@ -1217,8 +1276,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "table_property")
|
||||
|
||||
SubtypingResult result = isSubtype(subTy, superTy);
|
||||
CHECK(!result.isSubtype);
|
||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{/* subPath */ Path(TypePath::Property("X")),
|
||||
/* superPath */ Path(TypePath::Property("X")),
|
||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")),
|
||||
/* superPath */ Path(TypePath::Property::read("X")),
|
||||
/* variance */ SubtypingVariance::Invariant}});
|
||||
}
|
||||
|
||||
@ -1317,8 +1376,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "nested_table_properties")
|
||||
SubtypingResult result = isSubtype(subTy, superTy);
|
||||
CHECK(!result.isSubtype);
|
||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
||||
/* subPath */ TypePath::PathBuilder().prop("X").prop("Y").prop("Z").build(),
|
||||
/* superPath */ TypePath::PathBuilder().prop("X").prop("Y").prop("Z").build(),
|
||||
/* subPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(),
|
||||
/* superPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(),
|
||||
/* variance */ SubtypingVariance::Invariant,
|
||||
}});
|
||||
}
|
||||
@ -1335,7 +1394,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "string_table_mt")
|
||||
// metatable is empty, and abort there, without looking at the metatable
|
||||
// properties (because there aren't any).
|
||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
||||
/* subPath */ TypePath::PathBuilder().mt().prop("__index").build(),
|
||||
/* subPath */ TypePath::PathBuilder().mt().readProp("__index").build(),
|
||||
/* superPath */ TypePath::kEmpty,
|
||||
}});
|
||||
}
|
||||
@ -1360,12 +1419,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings")
|
||||
|
||||
SubtypingResult result = isSubtype(subTy, superTy);
|
||||
CHECK(!result.isSubtype);
|
||||
CHECK(result.reasoning == std::vector{
|
||||
SubtypingReasoning{/* subPath */ Path(TypePath::Property("X")), /* superPath */ Path(TypePath::Property("X")),
|
||||
/* variance */ SubtypingVariance::Invariant},
|
||||
SubtypingReasoning{/* subPath */ Path(TypePath::Property("Y")), /* superPath */ Path(TypePath::Property("Y")),
|
||||
/* variance */ SubtypingVariance::Invariant},
|
||||
});
|
||||
CHECK(result.reasoning ==
|
||||
std::vector{
|
||||
SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), /* superPath */ Path(TypePath::Property::read("X")),
|
||||
/* variance */ SubtypingVariance::Invariant},
|
||||
SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("Y")), /* superPath */ Path(TypePath::Property::read("Y")),
|
||||
/* variance */ SubtypingVariance::Invariant},
|
||||
});
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -931,18 +931,17 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array")
|
||||
TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
--!strict
|
||||
function f1() : {a : number, b : string, c : { d : number}}
|
||||
return { a = 1, b = "a", c = {d = "a"}}
|
||||
end
|
||||
--!strict
|
||||
function f1() : {a : number, b : string, c : { d : number}}
|
||||
return { a = 1, b = "a", c = {d = "a"}}
|
||||
end
|
||||
)");
|
||||
|
||||
)");
|
||||
//clang-format off
|
||||
std::string expected =
|
||||
(FFlag::DebugLuauDeferredConstraintResolution)
|
||||
? R"(Type pack '{| a: number, b: string, c: {| d: string |} |}' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0]["c"]["d"], string is not exactly number)"
|
||||
:
|
||||
R"(Type
|
||||
std::string expected;
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
expected = R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)";
|
||||
else
|
||||
expected = R"(Type
|
||||
'{ a: number, b: string, c: { d: string } }'
|
||||
could not be converted into
|
||||
'{| a: number, b: string, c: {| d: number |} |}'
|
||||
@ -955,7 +954,6 @@ could not be converted into
|
||||
caused by:
|
||||
Property 'd' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
//clang-format on
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
@ -984,4 +982,20 @@ local f = abs
|
||||
TypeId fn = requireType("f");
|
||||
CHECK("@checked (number) -> number" == toString(fn));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "read_only_properties")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type A = {x: string}
|
||||
type B = {read x: string}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK("{ x: string }" == toString(requireTypeAlias("A"), {true}));
|
||||
CHECK("{ read x: string }" == toString(requireTypeAlias("B"), {true}));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -202,7 +202,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'bad' could not be converted into 'T<number>'; at ["v"], string is not exactly number)";
|
||||
const std::string expected = R"(Type 'bad' could not be converted into 'T<number>'; at [read "v"], string is not exactly number)";
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -221,7 +221,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'; at ["t"]["v"], string is not exactly number)";
|
||||
const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
|
||||
|
||||
CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
|
@ -386,10 +386,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables")
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK("{ @metatable {| |}, A } | { @metatable {| |}, B }" == toString(requireTypeAlias("X")));
|
||||
else
|
||||
CHECK("{ @metatable { }, A } | { @metatable { }, B }" == toString(requireTypeAlias("X")));
|
||||
CHECK("{ @metatable { }, A } | { @metatable { }, B }" == toString(requireTypeAlias("X")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_2_args_overload")
|
||||
@ -1012,6 +1009,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
|
||||
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types3")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: (number | boolean)?)
|
||||
assert(x)
|
||||
return x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
|
||||
else // without the annotation, the old solver doesn't infer the best return type here
|
||||
CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -463,7 +463,7 @@ local b: B = a
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'B'; at [\"x\"], ChildClass is not exactly BaseClass");
|
||||
CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'B'; at [read \"x\"], ChildClass is not exactly BaseClass");
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
@ -639,4 +639,58 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "read_write_class_properties")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
TypeArena& arena = frontend.globals.globalTypes;
|
||||
|
||||
unfreeze(arena);
|
||||
|
||||
TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
getMutable<ClassType>(instanceType)->props = {
|
||||
{"Parent", Property::rw(instanceType)}
|
||||
};
|
||||
|
||||
//
|
||||
|
||||
TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"});
|
||||
|
||||
TypeId scriptType = arena.addType(ClassType{
|
||||
"Script", {
|
||||
{"Parent", Property::rw(workspaceType, instanceType)}
|
||||
},
|
||||
instanceType, nullopt, {}, {}, "Test"
|
||||
});
|
||||
|
||||
TypeId partType = arena.addType(ClassType{
|
||||
"Part", {
|
||||
{"BrickColor", Property::rw(builtinTypes->stringType)},
|
||||
{"Parent", Property::rw(workspaceType, instanceType)}
|
||||
},
|
||||
instanceType, nullopt, {}, {}, "Test"});
|
||||
|
||||
getMutable<ClassType>(workspaceType)->props = {
|
||||
{"Script", Property::readonly(scriptType)},
|
||||
{"Part", Property::readonly(partType)}
|
||||
};
|
||||
|
||||
frontend.globals.globalScope->bindings[frontend.globals.globalNames.names->getOrAdd("script")] = Binding{scriptType};
|
||||
|
||||
freeze(arena);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
script.Parent.Part.BrickColor = 0xFFFFFF
|
||||
script.Parent.Part.Parent = script
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK(Location{{1, 40}, {1, 48}} == result.errors[0].location);
|
||||
TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
|
||||
REQUIRE(tm);
|
||||
CHECK(builtinTypes->stringType == tm->wantedType);
|
||||
CHECK(builtinTypes->numberType == tm->givenType);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -27,6 +27,8 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_function")
|
||||
local y: number = id(37)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(builtinTypes->stringType, requireType("x"));
|
||||
CHECK_EQ(builtinTypes->numberType, requireType("y"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "check_generic_local_function")
|
||||
@ -39,6 +41,40 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_local_function")
|
||||
local y: number = id(37)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(builtinTypes->stringType, requireType("x"));
|
||||
CHECK_EQ(builtinTypes->numberType, requireType("y"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "check_generic_local_function2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function id<a>(x:a): a
|
||||
return x
|
||||
end
|
||||
local x = id("hi")
|
||||
local y = id(37)
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK_EQ(builtinTypes->stringType, requireType("x"));
|
||||
CHECK_EQ(builtinTypes->numberType, requireType("y"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "unions_and_generics")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
type foo = <T>(T | {T}) -> T
|
||||
local foo = (nil :: any) :: foo
|
||||
|
||||
type Test = number | {number}
|
||||
local res = foo(1 :: Test)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("number | {number}", toString(requireType("res")));
|
||||
else // in the old solver, this just totally falls apart
|
||||
CHECK_EQ("a", toString(requireType("res")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "check_generic_typepack_function")
|
||||
@ -370,7 +406,7 @@ TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods")
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("{ f: (t1) -> (), id: <a>(unknown, a) -> a } where t1 = { id: ((t1, number) -> number) & ((t1, string) -> string) }",
|
||||
CHECK_EQ("{ f: (t1) -> (), id: <a>(unknown, a) -> a } where t1 = { read id: ((t1, number) -> number) & ((t1, string) -> string) }",
|
||||
toString(requireType("x"), {true}));
|
||||
}
|
||||
else
|
||||
@ -437,6 +473,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_leak_generic_types")
|
||||
-- so this assignment should fail
|
||||
local b: boolean = f(true)
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
@ -797,8 +834,9 @@ y.a.c = y
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(result.errors.at(0)) ==
|
||||
R"(Type 'x' could not be converted into 'T<number>'; type x["a"]["c"] (nil) is not exactly T<number>["a"]["c"][0] (T<number>))");
|
||||
CHECK(
|
||||
toString(result.errors.at(0)) ==
|
||||
R"(Type 'x' could not be converted into 'T<number>'; type x[read "a"][read "c"] (nil) is not exactly T<number>[read "a"][read "c"][0] (T<number>))");
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type 'y' could not be converted into 'T<string>'
|
||||
@ -1369,6 +1407,19 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_and_generalization_play_nice"
|
||||
CHECK("string" == toString(requireType("b")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_no_cyclic_intersections")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local f, t, n = pairs({"foo"})
|
||||
local k, v = f(t)
|
||||
)");
|
||||
|
||||
CHECK("({string}, number?) -> (number?, string)" == toString(requireType("f")));
|
||||
CHECK("{string}" == toString(requireType("t")));
|
||||
CHECK("number?" == toString(requireType("k")));
|
||||
CHECK("string" == toString(requireType("v")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -1381,4 +1432,20 @@ TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
|
||||
REQUIRE(get<UnknownSymbol>(result.errors[1]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_families_work_in_subtyping")
|
||||
{
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local function addOne<T>(x: T): add<T, number> return x + 1 end
|
||||
|
||||
local function six(): number
|
||||
return addOne(5)
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -414,7 +414,7 @@ local b: B.T = a
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'T'; at [\"x\"], number is not exactly string");
|
||||
CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'T'; at [read \"x\"], number is not exactly string");
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
|
||||
@ -455,7 +455,7 @@ local b: B.T = a
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'T'; at [\"x\"], number is not exactly string");
|
||||
CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'T'; at [read \"x\"], number is not exactly string");
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
|
||||
|
@ -1,71 +0,0 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Fixture.h"
|
||||
|
||||
#include "doctest.h"
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct ReadWriteFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag dcr{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
ReadWriteFixture()
|
||||
: Fixture()
|
||||
{
|
||||
if (!FFlag::DebugLuauReadWriteProperties)
|
||||
return;
|
||||
|
||||
TypeArena* arena = &frontend.globals.globalTypes;
|
||||
NotNull<Scope> globalScope{frontend.globals.globalScope.get()};
|
||||
|
||||
unfreeze(*arena);
|
||||
|
||||
TypeId genericT = arena->addType(GenericType{"T"});
|
||||
|
||||
TypeId readonlyX = arena->addType(TableType{TableState::Sealed, TypeLevel{}, globalScope});
|
||||
getMutable<TableType>(readonlyX)->props["x"] = Property::readonly(genericT);
|
||||
globalScope->addBuiltinTypeBinding("ReadonlyX", TypeFun{{{genericT}}, readonlyX});
|
||||
|
||||
freeze(*arena);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_SUITE_BEGIN("ReadWriteProperties");
|
||||
|
||||
TEST_CASE_FIXTURE(ReadWriteFixture, "read_from_a_readonly_prop")
|
||||
{
|
||||
if (!FFlag::DebugLuauReadWriteProperties)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(rx: ReadonlyX<string>)
|
||||
local x = rx.x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(ReadWriteFixture, "write_to_a_readonly_prop")
|
||||
{
|
||||
if (!FFlag::DebugLuauReadWriteProperties)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(rx: ReadonlyX<string>)
|
||||
rx.x = "hello!" -- error
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -360,7 +360,7 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
|
||||
type Result<O, E> = Ok<O> | Err<E>
|
||||
|
||||
local a : Result<string, number> = {success = false, result = "hotdogs"}
|
||||
local b : Result<string, number> = {success = true, result = "hotdogs"}
|
||||
-- local b : Result<string, number> = {success = true, result = "hotdogs"}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
@ -357,7 +357,7 @@ TEST_CASE_FIXTURE(Fixture, "open_table_unification_2")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypeError& err = result.errors[0];
|
||||
MissingProperties* error = get<MissingProperties>(err);
|
||||
REQUIRE(error != nullptr);
|
||||
REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(err));
|
||||
REQUIRE(error->properties.size() == 1);
|
||||
|
||||
CHECK_EQ("y", error->properties[0]);
|
||||
@ -426,7 +426,7 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_2")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
MissingProperties* error = get<MissingProperties>(result.errors[0]);
|
||||
REQUIRE(error != nullptr);
|
||||
REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(result.errors[0]));
|
||||
REQUIRE(error->properties.size() == 1);
|
||||
|
||||
CHECK_EQ("baz", error->properties[0]);
|
||||
@ -446,7 +446,7 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_3")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
TypeError& err = result.errors[0];
|
||||
MissingProperties* error = get<MissingProperties>(err);
|
||||
REQUIRE(error != nullptr);
|
||||
REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(err));
|
||||
REQUIRE(error->properties.size() == 1);
|
||||
|
||||
CHECK_EQ("baz", error->properties[0]);
|
||||
@ -461,52 +461,6 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_3")
|
||||
CHECK_EQ(err.location, (Location{Position{6, 8}, Position{6, 9}}));
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_2")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
function id(x)
|
||||
return x
|
||||
end
|
||||
|
||||
function foo(o)
|
||||
id(o.x)
|
||||
id(o.y)
|
||||
return o
|
||||
end
|
||||
|
||||
local a = {x=55, y=nil, w=3.14159}
|
||||
local b = {}
|
||||
b.x = 1
|
||||
b.y = 'hello'
|
||||
b.z = 'something extra!'
|
||||
|
||||
local q = foo(a) -- line 17
|
||||
local w = foo(b) -- line 18
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
TypeId qType = requireType("q");
|
||||
const TableType* qTable = get<TableType>(qType);
|
||||
REQUIRE(qType != nullptr);
|
||||
|
||||
CHECK(qTable->props.find("x") != qTable->props.end());
|
||||
CHECK(qTable->props.find("y") != qTable->props.end());
|
||||
CHECK(qTable->props.find("z") == qTable->props.end());
|
||||
CHECK(qTable->props.find("w") != qTable->props.end());
|
||||
|
||||
TypeId wType = requireType("w");
|
||||
const TableType* wTable = get<TableType>(wType);
|
||||
REQUIRE(wTable != nullptr);
|
||||
|
||||
CHECK(wTable->props.find("x") != wTable->props.end());
|
||||
CHECK(wTable->props.find("y") != wTable->props.end());
|
||||
CHECK(wTable->props.find("z") != wTable->props.end());
|
||||
CHECK(wTable->props.find("w") == wTable->props.end());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_unification_4")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -680,7 +634,8 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too")
|
||||
|
||||
REQUIRE("number" == toString(indexer.indexType));
|
||||
|
||||
REQUIRE(nullptr != get<GenericType>(follow(indexer.indexResultType)));
|
||||
TypeId indexResultType = follow(indexer.indexResultType);
|
||||
REQUIRE_MESSAGE(get<GenericType>(indexResultType), "Expected generic but got " << toString(indexResultType));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "indexers_quantification_2")
|
||||
@ -1077,6 +1032,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_inferred")
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type VectorMt = { __add: (Vector, number) -> Vector }
|
||||
local vectorMt: VectorMt
|
||||
@ -1093,6 +1050,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways")
|
||||
CHECK_EQ(*requireType("a"), *requireType("c"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways_lti")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local vectorMt = {}
|
||||
|
||||
function vectorMt.__add(self: Vector, other: number)
|
||||
return self
|
||||
end
|
||||
|
||||
type Vector = typeof(setmetatable({}, vectorMt))
|
||||
local a: Vector = setmetatable({}, vectorMt)
|
||||
|
||||
local b = a + 2
|
||||
local c = 2 + a
|
||||
)");
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("Vector", toString(requireType("a")));
|
||||
CHECK_EQ(*requireType("a"), *requireType("b"));
|
||||
CHECK_EQ(*requireType("a"), *requireType("c"));
|
||||
}
|
||||
|
||||
// This test exposed a bug where we let go of the "seen" stack while unifying table types
|
||||
// As a result, type inference crashed with a stack overflow.
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "unification_of_unions_in_a_self_referential_type")
|
||||
@ -1570,7 +1551,7 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
MissingProperties* mp = get<MissingProperties>(result.errors[0]);
|
||||
REQUIRE(mp);
|
||||
REQUIRE_MESSAGE(mp, "Expected MissingProperties but got " << toString(result.errors[0]));
|
||||
CHECK_EQ(mp->context, MissingProperties::Missing);
|
||||
REQUIRE_EQ(1, mp->properties.size());
|
||||
CHECK_EQ(mp->properties[0], "a");
|
||||
@ -1664,7 +1645,7 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer4")
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK(toString(result.errors[0]) == "Type 'string' could not be converted into 'number'");
|
||||
CHECK(toString(result.errors[0]) == "Type 'number' could not be converted into 'string' in an invariant context");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1685,7 +1666,7 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
CHECK_EQ("Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }'"
|
||||
CHECK_EQ("Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }';"
|
||||
" at [0], { x: number } is not a subtype of { x: number, y: number, z: number }",
|
||||
toString(result.errors[0]));
|
||||
}
|
||||
@ -2176,7 +2157,7 @@ local b: B = a
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(result.errors.at(0)) == R"(Type 'a' could not be converted into 'B'; at ["y"], number is not exactly string)");
|
||||
CHECK(toString(result.errors.at(0)) == R"(Type 'a' could not be converted into 'B'; at [read "y"], number is not exactly string)");
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
@ -2203,7 +2184,7 @@ local b: B = a
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(toString(result.errors.at(0)) == R"(Type 'a' could not be converted into 'B'; at ["b"]["y"], number is not exactly string)");
|
||||
CHECK(toString(result.errors.at(0)) == R"(Type 'a' could not be converted into 'B'; at [read "b"][read "y"], number is not exactly string)");
|
||||
else
|
||||
{
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
@ -3979,15 +3960,18 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
std::string expected = "Type 'a' could not be converted into 'T'; at [\"a\"], string is not exactly number"
|
||||
"\n\tat [\"b\"], boolean is not exactly string"
|
||||
"\n\tat [\"c\"], number is not exactly boolean";
|
||||
std::string expected = "Type 'a' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
|
||||
"\n\tat [read \"b\"], boolean is not exactly string"
|
||||
"\n\tat [read \"c\"], number is not exactly boolean";
|
||||
CHECK(toString(result.errors[0]) == expected);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::LuauReadWritePropertySyntax, true};
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauReadWritePropertySyntax, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type W = {read x: number}
|
||||
@ -4026,6 +4010,22 @@ TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
|
||||
CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_write_property")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(t)
|
||||
t.y = 1
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
// CHECK("({ y: number }) -> ()" == toString(requireType("f")));
|
||||
CHECK("({ y: number & unknown }) -> ()" == toString(requireType("f")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
@ -4057,4 +4057,132 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "write_to_read_only_property")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(t: {read x: number})
|
||||
t.x = 5
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK("Property x of table '{ read x: number }' is read-only" == toString(result.errors[0]));
|
||||
|
||||
PropertyAccessViolation* pav = get<PropertyAccessViolation>(result.errors[0]);
|
||||
REQUIRE(pav);
|
||||
|
||||
CHECK("{ read x: number }" == toString(pav->table, {true}));
|
||||
CHECK("x" == pav->key);
|
||||
CHECK(PropertyAccessViolation::CannotWrite == pav->context);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "write_to_unusually_named_read_only_property")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(t: {read ["hello world"]: number})
|
||||
t["hello world"] = 5
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK("Property \"hello world\" of table '{ read [\"hello world\"]: number }' is read-only" == toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "write_annotations_are_unsupported_even_with_the_new_solver")
|
||||
{
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function f(t: {write foo: number})
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
CHECK("write keyword is illegal here" == toString(result.errors[0]));
|
||||
CHECK(Location{{1, 23}, {1, 28}} == result.errors[0].location);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauReadWritePropertySyntax, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type W = {read x: number}
|
||||
type X = {write x: boolean}
|
||||
|
||||
type Y = {read ["prop"]: boolean}
|
||||
type Z = {write ["prop"]: string}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(4, result);
|
||||
|
||||
CHECK("read keyword is illegal here" == toString(result.errors[0]));
|
||||
CHECK(Location{{1, 18}, {1, 22}} == result.errors[0].location);
|
||||
CHECK("write keyword is illegal here" == toString(result.errors[1]));
|
||||
CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
|
||||
CHECK("read keyword is illegal here" == toString(result.errors[2]));
|
||||
CHECK(Location{{4, 18}, {4, 22}} == result.errors[2].location);
|
||||
CHECK("write keyword is illegal here" == toString(result.errors[3]));
|
||||
CHECK(Location{{5, 18}, {5, 23}} == result.errors[3].location);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauReadWritePropertySyntax, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type T = {read [string]: number}
|
||||
type U = {write [string]: boolean}
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(2, result);
|
||||
|
||||
CHECK("read keyword is illegal here" == toString(result.errors[0]));
|
||||
CHECK(Location{{1, 18}, {1, 22}} == result.errors[0].location);
|
||||
CHECK("write keyword is illegal here" == toString(result.errors[1]));
|
||||
CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::LuauReadWritePropertySyntax, true},
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, true}
|
||||
};
|
||||
|
||||
CheckResult result = check(R"(
|
||||
function oc(player, speaker)
|
||||
local head = speaker.Character:FindFirstChild('Head')
|
||||
speaker.Character = player[1].Character
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK("<a, b, c...>({{ read Character: a }}, { Character: t1 }) -> () "
|
||||
"where "
|
||||
"t1 = a & { read FindFirstChild: (t1, string) -> (b, c...) }" == toString(requireType("oc")));
|
||||
|
||||
// We currently get
|
||||
// <a, b, c...>({{ read Character: a }}, { Character: t1 }) -> () where t1 = { read FindFirstChild: (t1, string) -> (b, c...) }
|
||||
|
||||
// But we'd like to see
|
||||
// <a, b...>({{ read Character: t1 }}, { Character: t1 }) -> () where t1 = { read FindFirstChild: (t1, string) -> (a, b...) }
|
||||
|
||||
// The type of speaker.Character should be the same as player[1].Character
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -15,8 +15,19 @@
|
||||
using namespace Luau;
|
||||
using namespace Luau::TypePath;
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||
LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
|
||||
|
||||
struct TypePathFixture : Fixture
|
||||
{
|
||||
ScopedFastFlag sff1{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
};
|
||||
|
||||
struct TypePathBuiltinsFixture : BuiltinsFixture
|
||||
{
|
||||
ScopedFastFlag sff1{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
};
|
||||
|
||||
TEST_SUITE_BEGIN("TypePathManipulation");
|
||||
|
||||
TEST_CASE("append")
|
||||
@ -95,12 +106,12 @@ TEST_SUITE_BEGIN("TypePathTraversal");
|
||||
LUAU_REQUIRE_NO_ERRORS(result); \
|
||||
} while (false);
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "empty_traversal")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "empty_traversal")
|
||||
{
|
||||
CHECK(traverseForType(builtinTypes->numberType, kEmpty, builtinTypes) == builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "table_property")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "table_property")
|
||||
{
|
||||
TYPESOLVE_CODE(R"(
|
||||
local x = { y = 123 }
|
||||
@ -114,7 +125,7 @@ TEST_CASE_FIXTURE(ClassFixture, "class_property")
|
||||
CHECK(traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), builtinTypes) == builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_property")
|
||||
TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "metatable_property")
|
||||
{
|
||||
SUBCASE("meta_does_not_contribute")
|
||||
{
|
||||
@ -138,10 +149,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_property")
|
||||
)");
|
||||
}
|
||||
|
||||
CHECK(traverseForType(requireType("x"), Path(TypePath::Property("x")), builtinTypes) == builtinTypes->numberType);
|
||||
CHECK(traverseForType(requireType("x"), Path(TypePath::Property::read("x")), builtinTypes) == builtinTypes->numberType);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "index")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "index")
|
||||
{
|
||||
SUBCASE("unions")
|
||||
{
|
||||
@ -242,7 +253,7 @@ TEST_CASE_FIXTURE(ClassFixture, "metatables")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "bounds")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "bounds")
|
||||
{
|
||||
SUBCASE("free_type")
|
||||
{
|
||||
@ -274,7 +285,7 @@ TEST_CASE_FIXTURE(Fixture, "bounds")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "indexers")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "indexers")
|
||||
{
|
||||
SUBCASE("table")
|
||||
{
|
||||
@ -308,7 +319,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers")
|
||||
// TODO: Class types
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "negated")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "negated")
|
||||
{
|
||||
SUBCASE("valid")
|
||||
{
|
||||
@ -327,7 +338,7 @@ TEST_CASE_FIXTURE(Fixture, "negated")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "variadic")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "variadic")
|
||||
{
|
||||
SUBCASE("valid")
|
||||
{
|
||||
@ -346,7 +357,7 @@ TEST_CASE_FIXTURE(Fixture, "variadic")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "arguments")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "arguments")
|
||||
{
|
||||
SUBCASE("function")
|
||||
{
|
||||
@ -368,7 +379,7 @@ TEST_CASE_FIXTURE(Fixture, "arguments")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "returns")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "returns")
|
||||
{
|
||||
SUBCASE("function")
|
||||
{
|
||||
@ -391,7 +402,7 @@ TEST_CASE_FIXTURE(Fixture, "returns")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "tail")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "tail")
|
||||
{
|
||||
SUBCASE("has_tail")
|
||||
{
|
||||
@ -422,7 +433,7 @@ TEST_CASE_FIXTURE(Fixture, "tail")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cycles" * doctest::timeout(0.5))
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5))
|
||||
{
|
||||
// This will fail an occurs check, but it's a quick example of a cyclic type
|
||||
// where there _is_ no traversal.
|
||||
@ -451,7 +462,7 @@ TEST_CASE_FIXTURE(Fixture, "cycles" * doctest::timeout(0.5))
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "step_limit")
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "step_limit")
|
||||
{
|
||||
ScopedFastInt sfi(DFInt::LuauTypePathMaximumTraverseSteps, 2);
|
||||
|
||||
@ -466,12 +477,12 @@ TEST_CASE_FIXTURE(Fixture, "step_limit")
|
||||
)");
|
||||
|
||||
TypeId root = requireTypeAlias("T");
|
||||
Path path = PathBuilder().prop("x").prop("y").prop("z").build();
|
||||
Path path = PathBuilder().readProp("x").readProp("y").readProp("z").build();
|
||||
auto result = traverseForType(root, path, builtinTypes);
|
||||
CHECK(!result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
|
||||
TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains")
|
||||
{
|
||||
SUBCASE("add_metamethod_return_type")
|
||||
{
|
||||
@ -484,7 +495,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
|
||||
)");
|
||||
|
||||
TypeId root = requireTypeAlias("Tab");
|
||||
Path path = PathBuilder().mt().prop("__add").rets().index(0).build();
|
||||
Path path = PathBuilder().mt().readProp("__add").rets().index(0).build();
|
||||
auto result = traverseForType(root, path, builtinTypes);
|
||||
CHECK(result == builtinTypes->numberType);
|
||||
}
|
||||
@ -498,7 +509,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
|
||||
)");
|
||||
|
||||
TypeId root = requireTypeAlias("Obj");
|
||||
Path path = PathBuilder().prop("method").index(0).args().index(1).build();
|
||||
Path path = PathBuilder().readProp("method").index(0).args().index(1).build();
|
||||
auto result = traverseForType(root, path, builtinTypes);
|
||||
CHECK(*result == builtinTypes->falseType);
|
||||
}
|
||||
@ -510,6 +521,10 @@ TEST_SUITE_BEGIN("TypePathToString");
|
||||
|
||||
TEST_CASE("field")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
CHECK(toString(PathBuilder().prop("foo").build()) == R"(["foo"])");
|
||||
}
|
||||
|
||||
@ -535,10 +550,26 @@ TEST_CASE("empty_path")
|
||||
|
||||
TEST_CASE("prop")
|
||||
{
|
||||
ScopedFastFlag sff[] = {
|
||||
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||
};
|
||||
|
||||
Path p = PathBuilder().prop("foo").build();
|
||||
CHECK(p == Path(TypePath::Property{"foo"}));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "readProp")
|
||||
{
|
||||
Path p = PathBuilder().readProp("foo").build();
|
||||
CHECK(p == Path(TypePath::Property::read("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypePathFixture, "writeProp")
|
||||
{
|
||||
Path p = PathBuilder().writeProp("foo").build();
|
||||
CHECK(p == Path(TypePath::Property::write("foo")));
|
||||
}
|
||||
|
||||
TEST_CASE("index")
|
||||
{
|
||||
Path p = PathBuilder().index(0).build();
|
||||
@ -561,8 +592,10 @@ TEST_CASE("fields")
|
||||
|
||||
TEST_CASE("chained")
|
||||
{
|
||||
CHECK(PathBuilder().index(0).prop("foo").mt().prop("bar").args().index(1).build() ==
|
||||
Path({Index{0}, TypePath::Property{"foo"}, TypeField::Metatable, TypePath::Property{"bar"}, PackField::Arguments, Index{1}}));
|
||||
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||
|
||||
CHECK(PathBuilder().index(0).readProp("foo").mt().readProp("bar").args().index(1).build() ==
|
||||
Path({Index{0}, TypePath::Property::read("foo"), TypeField::Metatable, TypePath::Property::read("bar"), PackField::Arguments, Index{1}}));
|
||||
}
|
||||
|
||||
TEST_SUITE_END(); // TypePathBuilder
|
||||
|
@ -76,13 +76,13 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U")
|
||||
|
||||
CHECK(u2.unify(left, right));
|
||||
|
||||
CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(left));
|
||||
CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(right));
|
||||
CHECK("'a" == toString(left));
|
||||
CHECK("'a" == toString(right));
|
||||
|
||||
CHECK("never" == toString(freeLeft->lowerBound));
|
||||
CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(freeLeft->upperBound));
|
||||
CHECK("unknown" == toString(freeLeft->upperBound));
|
||||
|
||||
CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(freeRight->lowerBound));
|
||||
CHECK("never" == toString(freeRight->lowerBound));
|
||||
CHECK("unknown" == toString(freeRight->upperBound));
|
||||
}
|
||||
|
||||
|
@ -293,6 +293,24 @@ end
|
||||
|
||||
assert(loopIteratorProtocol(0, table.create(100, 5)) == 5058)
|
||||
|
||||
function valueTrackingIssue1()
|
||||
local b = buffer.create(1)
|
||||
buffer.writeu8(b, 0, 0)
|
||||
local v1
|
||||
|
||||
local function closure()
|
||||
assert(type(b) == "buffer") -- b is the first upvalue
|
||||
v1 = nil -- v1 is the second upvalue
|
||||
|
||||
-- prevent inlining
|
||||
for i = 1, 100 do print(`{b} is {b}`) end
|
||||
end
|
||||
|
||||
closure()
|
||||
end
|
||||
|
||||
valueTrackingIssue1()
|
||||
|
||||
local function vec3compsum(a: vector)
|
||||
return a.X + a.Y + a.Z
|
||||
end
|
||||
|
@ -1,4 +1,3 @@
|
||||
AnnotationTests.typeof_expr
|
||||
AstQuery.last_argument_function_call_type
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
@ -13,7 +12,6 @@ BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_th
|
||||
BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
|
||||
BuiltinTests.bad_select_should_not_crash
|
||||
BuiltinTests.coroutine_resume_anything_goes
|
||||
BuiltinTests.debug_info_is_crazy
|
||||
BuiltinTests.global_singleton_types_are_sealed
|
||||
BuiltinTests.gmatch_capture_types
|
||||
BuiltinTests.gmatch_capture_types2
|
||||
@ -67,24 +65,12 @@ DefinitionTests.class_definition_string_props
|
||||
DefinitionTests.declaring_generic_functions
|
||||
DefinitionTests.definition_file_classes
|
||||
Differ.equal_generictp_cyclic
|
||||
Differ.equal_table_A_B_C
|
||||
Differ.equal_table_cyclic_diamonds_unraveled
|
||||
Differ.equal_table_kind_A
|
||||
Differ.equal_table_kind_B
|
||||
Differ.equal_table_kind_C
|
||||
Differ.equal_table_kind_D
|
||||
Differ.equal_table_measuring_tapes
|
||||
Differ.equal_table_two_shifted_circles_are_not_different
|
||||
Differ.generictp_normal
|
||||
Differ.generictp_normal_2
|
||||
Differ.left_cyclic_table_right_table_missing_property
|
||||
Differ.left_cyclic_table_right_table_property_wrong
|
||||
Differ.metatable_metamissing_left
|
||||
Differ.metatable_metamissing_right
|
||||
Differ.metatable_metanormal
|
||||
Differ.negation
|
||||
Differ.right_cyclic_table_left_table_property_wrong
|
||||
Differ.table_left_circle_right_measuring_tape
|
||||
FrontendTest.accumulate_cached_errors_in_consistent_order
|
||||
FrontendTest.environments
|
||||
FrontendTest.imported_table_modification_2
|
||||
@ -94,47 +80,37 @@ FrontendTest.trace_requires_in_nonstrict_mode
|
||||
GenericsTests.apply_type_function_nested_generics1
|
||||
GenericsTests.better_mismatch_error_messages
|
||||
GenericsTests.bound_tables_do_not_clone_original_fields
|
||||
GenericsTests.check_generic_function
|
||||
GenericsTests.check_generic_local_function
|
||||
GenericsTests.check_mutual_generic_functions
|
||||
GenericsTests.check_mutual_generic_functions_errors
|
||||
GenericsTests.check_mutual_generic_functions_unannotated
|
||||
GenericsTests.check_nested_generic_function
|
||||
GenericsTests.check_recursive_generic_function
|
||||
GenericsTests.correctly_instantiate_polymorphic_member_functions
|
||||
GenericsTests.do_not_always_instantiate_generic_intersection_types
|
||||
GenericsTests.do_not_infer_generic_functions
|
||||
GenericsTests.dont_leak_generic_types
|
||||
GenericsTests.dont_leak_inferred_generic_types
|
||||
GenericsTests.dont_substitute_bound_types
|
||||
GenericsTests.error_detailed_function_mismatch_generic_pack
|
||||
GenericsTests.error_detailed_function_mismatch_generic_types
|
||||
GenericsTests.factories_of_generics
|
||||
GenericsTests.function_arguments_can_be_polytypes
|
||||
GenericsTests.generic_argument_count_too_few
|
||||
GenericsTests.generic_argument_count_too_many
|
||||
GenericsTests.generic_factories
|
||||
GenericsTests.generic_functions_dont_cache_type_parameters
|
||||
GenericsTests.generic_functions_in_types
|
||||
GenericsTests.generic_type_families_work_in_subtyping
|
||||
GenericsTests.generic_type_pack_parentheses
|
||||
GenericsTests.generic_type_pack_unification1
|
||||
GenericsTests.generic_type_pack_unification2
|
||||
GenericsTests.generic_type_pack_unification3
|
||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||
GenericsTests.hof_subtype_instantiation_regression
|
||||
GenericsTests.infer_generic_function
|
||||
GenericsTests.infer_generic_function_function_argument
|
||||
GenericsTests.infer_generic_function_function_argument_2
|
||||
GenericsTests.infer_generic_function_function_argument_3
|
||||
GenericsTests.infer_generic_function_function_argument_overloaded
|
||||
GenericsTests.infer_generic_lib_function_function_argument
|
||||
GenericsTests.infer_generic_local_function
|
||||
GenericsTests.infer_generic_property
|
||||
GenericsTests.infer_nested_generic_function
|
||||
GenericsTests.inferred_local_vars_can_be_polytypes
|
||||
GenericsTests.instantiated_function_argument_names
|
||||
GenericsTests.local_vars_can_be_polytypes
|
||||
GenericsTests.mutable_state_polymorphism
|
||||
GenericsTests.no_stack_overflow_from_quantifying
|
||||
GenericsTests.properties_can_be_instantiated_polytypes
|
||||
GenericsTests.properties_can_be_polytypes
|
||||
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
|
||||
GenericsTests.rank_N_types_via_typeof
|
||||
GenericsTests.self_recursive_instantiated_param
|
||||
GenericsTests.type_parameters_can_be_polytypes
|
||||
GenericsTests.typefuns_sharing_types
|
||||
@ -164,7 +140,6 @@ IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
|
||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
|
||||
IntersectionTypes.table_write_sealed_indirect
|
||||
IntersectionTypes.union_saturate_overloaded_functions
|
||||
Linter.DeprecatedApiFenv
|
||||
Linter.FormatStringTyped
|
||||
Linter.TableOperationsIndexer
|
||||
ModuleTests.clone_self_property
|
||||
@ -210,16 +185,12 @@ RefinementTest.discriminate_from_isa_of_x
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
RefinementTest.discriminate_tag
|
||||
RefinementTest.discriminate_tag_with_implicit_else
|
||||
RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
|
||||
RefinementTest.fail_to_refine_a_property_of_subscript_expression
|
||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
||||
RefinementTest.function_call_with_colon_after_refining_not_to_be_nil
|
||||
RefinementTest.globals_can_be_narrowed_too
|
||||
RefinementTest.impossible_type_narrow_is_not_an_error
|
||||
RefinementTest.index_on_a_refined_property
|
||||
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
||||
RefinementTest.luau_polyfill_isindexkey_refine_conjunction
|
||||
RefinementTest.luau_polyfill_isindexkey_refine_conjunction_variant
|
||||
RefinementTest.narrow_property_of_a_bounded_variable
|
||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
@ -245,7 +216,6 @@ TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict
|
||||
TableTests.array_factory_function
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer2
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer3
|
||||
TableTests.casting_tables_with_props_into_table_with_indexer4
|
||||
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
||||
TableTests.checked_prop_too_early
|
||||
TableTests.cli_84607_missing_prop_in_array_or_dict
|
||||
@ -258,12 +228,10 @@ TableTests.common_table_element_union_in_call
|
||||
TableTests.common_table_element_union_in_call_tail
|
||||
TableTests.common_table_element_union_in_prop
|
||||
TableTests.confusing_indexing
|
||||
TableTests.cyclic_shifted_tables
|
||||
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
||||
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||
TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
||||
TableTests.dont_leak_free_table_props
|
||||
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
||||
TableTests.dont_suggest_exact_match_keys
|
||||
TableTests.error_detailed_indexer_key
|
||||
TableTests.error_detailed_indexer_value
|
||||
@ -275,8 +243,8 @@ TableTests.generalize_table_argument
|
||||
TableTests.generic_table_instantiation_potential_regression
|
||||
TableTests.indexer_mismatch
|
||||
TableTests.indexers_get_quantified_too
|
||||
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
|
||||
TableTests.inequality_operators_imply_exactly_matching_types
|
||||
TableTests.infer_indexer_from_array_like_table
|
||||
TableTests.infer_indexer_from_its_variable_type_and_unifiable
|
||||
TableTests.inferred_return_type_of_free_table
|
||||
TableTests.instantiate_table_cloning_3
|
||||
@ -286,7 +254,7 @@ TableTests.length_operator_intersection
|
||||
TableTests.length_operator_non_table_union
|
||||
TableTests.length_operator_union
|
||||
TableTests.less_exponential_blowup_please
|
||||
TableTests.meta_add_both_ways
|
||||
TableTests.meta_add
|
||||
TableTests.meta_add_inferred
|
||||
TableTests.metatable_mismatch_should_fail
|
||||
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
||||
@ -300,8 +268,6 @@ TableTests.oop_polymorphic
|
||||
TableTests.open_table_unification_2
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
|
||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
||||
TableTests.pass_incompatible_union_to_a_generic_table_without_crashing
|
||||
TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
|
||||
TableTests.persistent_sealed_table_is_immutable
|
||||
TableTests.prop_access_on_key_whose_types_mismatches
|
||||
TableTests.prop_access_on_unions_of_indexers_where_key_whose_types_mismatches
|
||||
@ -320,12 +286,10 @@ TableTests.shared_selfs_from_free_param
|
||||
TableTests.shared_selfs_through_metatables
|
||||
TableTests.table_call_metamethod_basic
|
||||
TableTests.table_call_metamethod_must_be_callable
|
||||
TableTests.table_function_check_use_after_free
|
||||
TableTests.table_param_width_subtyping_2
|
||||
TableTests.table_param_width_subtyping_3
|
||||
TableTests.table_simple_call
|
||||
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
|
||||
TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2
|
||||
TableTests.table_unification_4
|
||||
TableTests.table_unifies_into_map
|
||||
@ -351,7 +315,6 @@ TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||
TryUnifyTests.uninhabited_table_sub_anything
|
||||
TryUnifyTests.uninhabited_table_sub_never
|
||||
TryUnifyTests.variadics_should_use_reversed_properly
|
||||
TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
|
||||
TypeAliases.generic_param_remap
|
||||
TypeAliases.mismatched_generic_type_param
|
||||
@ -376,10 +339,10 @@ TypeFamilyTests.internal_families_raise_errors
|
||||
TypeFamilyTests.table_internal_families
|
||||
TypeFamilyTests.type_families_inhabited_with_normalization
|
||||
TypeFamilyTests.unsolvable_family
|
||||
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
|
||||
TypeInfer.bidirectional_checking_of_callback_property
|
||||
TypeInfer.check_type_infer_recursion_count
|
||||
TypeInfer.checking_should_not_ice
|
||||
TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
||||
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||
TypeInfer.dont_ice_when_failing_the_occurs_check
|
||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
||||
@ -447,7 +410,6 @@ TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosi
|
||||
TypeInferFunctions.function_is_supertype_of_concrete_functions
|
||||
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
||||
TypeInferFunctions.generic_packs_are_not_variadic
|
||||
TypeInferFunctions.higher_order_function_2
|
||||
TypeInferFunctions.higher_order_function_4
|
||||
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
|
||||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
||||
@ -469,7 +431,6 @@ TypeInferFunctions.other_things_are_not_related_to_function
|
||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible
|
||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
|
||||
TypeInferFunctions.record_matching_overload
|
||||
TypeInferFunctions.regex_benchmark_string_format_minimization
|
||||
TypeInferFunctions.report_exiting_without_return_nonstrict
|
||||
TypeInferFunctions.return_type_by_overload
|
||||
TypeInferFunctions.too_few_arguments_variadic
|
||||
@ -492,8 +453,6 @@ TypeInferLoops.for_in_loop_with_custom_iterator
|
||||
TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
|
||||
TypeInferLoops.for_in_loop_with_next
|
||||
TypeInferLoops.for_in_with_an_iterator_of_type_any
|
||||
TypeInferLoops.for_in_with_generic_next
|
||||
TypeInferLoops.for_in_with_just_one_iterator_is_ok
|
||||
TypeInferLoops.for_loop
|
||||
TypeInferLoops.ipairs_produces_integral_indices
|
||||
TypeInferLoops.iterate_over_free_table
|
||||
@ -511,7 +470,6 @@ TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||
TypeInferLoops.repeat_loop
|
||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||
TypeInferLoops.while_loop
|
||||
TypeInferModules.bound_free_table_export_is_ok
|
||||
TypeInferModules.custom_require_global
|
||||
TypeInferModules.do_not_modify_imported_types
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
@ -521,7 +479,6 @@ TypeInferOOP.cycle_between_object_constructor_and_alias
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
|
||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
|
||||
TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table
|
||||
TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||
TypeInferOOP.methods_are_topologically_sorted
|
||||
TypeInferOOP.object_constructor_can_refer_to_method_of_self
|
||||
@ -547,7 +504,6 @@ TypeInferOperators.strict_binary_op_where_lhs_unknown
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
|
||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
||||
TypeInferOperators.typecheck_unary_len_error
|
||||
TypeInferOperators.typecheck_unary_minus
|
||||
TypeInferOperators.typecheck_unary_minus_error
|
||||
TypeInferOperators.UnknownGlobalCompoundAssign
|
||||
TypeInferPrimitives.CheckMethodsOfNumber
|
||||
@ -564,7 +520,6 @@ TypePackTests.pack_tail_unification_check
|
||||
TypePackTests.type_alias_backwards_compatible
|
||||
TypePackTests.type_alias_default_type_errors
|
||||
TypePackTests.type_alias_type_packs_import
|
||||
TypePackTests.type_packs_with_tails_in_vararg_adjustment
|
||||
TypePackTests.unify_variadic_tails_in_arguments
|
||||
TypeSingletons.enums_using_singletons_mismatch
|
||||
TypeSingletons.error_detailed_tagged_union_mismatch_bool
|
||||
|
@ -385,7 +385,7 @@ def luau_typepath_property_summary(valobj, internal_dict, options):
|
||||
read_write = False
|
||||
try:
|
||||
fflag_valobj = valobj.GetFrame().GetValueForVariablePath(
|
||||
"FFlag::DebugLuauReadWriteProperties::value")
|
||||
"FFlag::DebugLuauDeferredConstraintResolution::value")
|
||||
|
||||
read_write = fflag_valobj.GetValue() == "true"
|
||||
except Exception as e:
|
||||
|
@ -107,12 +107,6 @@ def main():
|
||||
action="store_true",
|
||||
help="Write a new faillist.txt after running tests.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rwp",
|
||||
dest="rwp",
|
||||
action="store_true",
|
||||
help="Run the tests with read-write properties enabled.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ts",
|
||||
dest="suite",
|
||||
@ -135,8 +129,6 @@ def main():
|
||||
failList = loadFailList()
|
||||
|
||||
flags = ["true", "DebugLuauDeferredConstraintResolution"]
|
||||
if args.rwp:
|
||||
flags.append("DebugLuauReadWriteProperties")
|
||||
|
||||
commandLine = [args.path, "--reporters=xml", "--fflags=" + ",".join(flags)]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user