mirror of
https://github.com/luau-lang/luau.git
synced 2024-12-12 21:10:37 +00: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
62 changed files with 1835 additions and 725 deletions
|
@ -14,8 +14,16 @@
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum class ValueContext;
|
||||||
struct Scope;
|
struct Scope;
|
||||||
|
|
||||||
|
// if resultType is a freeType, assignmentType <: freeType <: resultType bounds
|
||||||
|
struct EqualityConstraint
|
||||||
|
{
|
||||||
|
TypeId resultType;
|
||||||
|
TypeId assignmentType;
|
||||||
|
};
|
||||||
|
|
||||||
// subType <: superType
|
// subType <: superType
|
||||||
struct SubtypeConstraint
|
struct SubtypeConstraint
|
||||||
{
|
{
|
||||||
|
@ -40,6 +48,8 @@ struct GeneralizationConstraint
|
||||||
{
|
{
|
||||||
TypeId generalizedType;
|
TypeId generalizedType;
|
||||||
TypeId sourceType;
|
TypeId sourceType;
|
||||||
|
|
||||||
|
std::vector<TypeId> interiorTypes;
|
||||||
};
|
};
|
||||||
|
|
||||||
// subType ~ inst superType
|
// subType ~ inst superType
|
||||||
|
@ -145,6 +155,7 @@ struct HasPropConstraint
|
||||||
TypeId resultType;
|
TypeId resultType;
|
||||||
TypeId subjectType;
|
TypeId subjectType;
|
||||||
std::string prop;
|
std::string prop;
|
||||||
|
ValueContext context;
|
||||||
|
|
||||||
// HACK: We presently need types like true|false or string|"hello" when
|
// HACK: We presently need types like true|false or string|"hello" when
|
||||||
// deciding whether a particular literal expression should have a singleton
|
// deciding whether a particular literal expression should have a singleton
|
||||||
|
@ -256,7 +267,8 @@ struct ReducePackConstraint
|
||||||
|
|
||||||
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
|
using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
|
||||||
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint,
|
NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint,
|
||||||
SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
|
SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint,
|
||||||
|
EqualityConstraint>;
|
||||||
|
|
||||||
struct Constraint
|
struct Constraint
|
||||||
{
|
{
|
||||||
|
|
|
@ -130,6 +130,8 @@ struct ConstraintGenerator
|
||||||
void visitModuleRoot(AstStatBlock* block);
|
void visitModuleRoot(AstStatBlock* block);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::vector<std::vector<TypeId>> interiorTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fabricates a new free type belonging to a given scope.
|
* Fabricates a new free type belonging to a given scope.
|
||||||
* @param scope the scope the free type belongs to.
|
* @param scope the scope the free type belongs to.
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Luau/Location.h"
|
#include "Luau/Location.h"
|
||||||
#include "Luau/Module.h"
|
#include "Luau/Module.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
|
#include "Luau/Substitution.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeCheckLimits.h"
|
#include "Luau/TypeCheckLimits.h"
|
||||||
|
@ -20,6 +21,8 @@
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum class ValueContext;
|
||||||
|
|
||||||
struct DcrLogger;
|
struct DcrLogger;
|
||||||
|
|
||||||
// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
|
// TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
|
||||||
|
@ -110,8 +113,6 @@ struct ConstraintSolver
|
||||||
|
|
||||||
bool isDone();
|
bool isDone();
|
||||||
|
|
||||||
void finalizeModule();
|
|
||||||
|
|
||||||
/** Attempt to dispatch a constraint. Returns true if it was successful. If
|
/** Attempt to dispatch a constraint. Returns true if it was successful. If
|
||||||
* tryDispatch() returns false, the constraint remains in the unsolved set
|
* tryDispatch() returns false, the constraint remains in the unsolved set
|
||||||
* and will be retried later.
|
* 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 SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
bool tryDispatch(const ReduceConstraint& 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 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
|
// for a, ... in some_table do
|
||||||
// also handles __iter metamethod
|
// 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);
|
TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
|
||||||
|
|
||||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
|
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(
|
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);
|
void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
|
||||||
/**
|
/**
|
||||||
|
@ -208,23 +210,6 @@ struct ConstraintSolver
|
||||||
*/
|
*/
|
||||||
bool isBlocked(NotNull<const Constraint> constraint);
|
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.
|
/** Pushes a new solver constraint to the solver.
|
||||||
* @param cv the body of the constraint.
|
* @param cv the body of the constraint.
|
||||||
**/
|
**/
|
||||||
|
@ -253,20 +238,33 @@ struct ConstraintSolver
|
||||||
*/
|
*/
|
||||||
bool hasUnresolvedConstraints(TypeId ty);
|
bool hasUnresolvedConstraints(TypeId ty);
|
||||||
|
|
||||||
private:
|
/**
|
||||||
/** Helper used by tryDispatch(SubtypeConstraint) and
|
* Creates a new Unifier and performs a single unification operation.
|
||||||
* tryDispatch(PackSubtypeConstraint)
|
|
||||||
*
|
*
|
||||||
* 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.
|
* 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 fails, replace all free types with errorType.
|
||||||
*
|
*
|
||||||
* If unification succeeds, unblock every type changed by the unification.
|
* 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>
|
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
|
* 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
|
* itself in the case that resultTy == blockedTy. This can happen if we
|
||||||
|
@ -295,6 +293,13 @@ private:
|
||||||
**/
|
**/
|
||||||
void unblock_(BlockedConstraintId progressed);
|
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;
|
TypeId errorRecoveryType() const;
|
||||||
TypePackId errorRecoveryTypePack() const;
|
TypePackId errorRecoveryTypePack() const;
|
||||||
|
|
||||||
|
|
|
@ -380,6 +380,20 @@ struct NonStrictFunctionDefinitionError
|
||||||
bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
|
bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PropertyAccessViolation
|
||||||
|
{
|
||||||
|
TypeId table;
|
||||||
|
Name key;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
CannotRead,
|
||||||
|
CannotWrite
|
||||||
|
} context;
|
||||||
|
|
||||||
|
bool operator==(const PropertyAccessViolation& rhs) const;
|
||||||
|
};
|
||||||
|
|
||||||
struct CheckedFunctionIncorrectArgs
|
struct CheckedFunctionIncorrectArgs
|
||||||
{
|
{
|
||||||
std::string functionName;
|
std::string functionName;
|
||||||
|
@ -388,14 +402,28 @@ struct CheckedFunctionIncorrectArgs
|
||||||
bool operator==(const CheckedFunctionIncorrectArgs& rhs) const;
|
bool operator==(const CheckedFunctionIncorrectArgs& rhs) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
using TypeErrorData =
|
struct UnexpectedTypeInSubtyping
|
||||||
Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods, DuplicateTypeDefinition,
|
{
|
||||||
CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire, IncorrectGenericParameterCount, SyntaxError,
|
TypeId ty;
|
||||||
CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError, CannotCallNonFunction, ExtraInformation,
|
|
||||||
DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning, DuplicateGenericParameter,
|
bool operator==(const UnexpectedTypeInSubtyping& rhs) const;
|
||||||
CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty, TypesAreUnrelated,
|
};
|
||||||
NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily, UninhabitedTypePackFamily,
|
|
||||||
WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError, CheckedFunctionIncorrectArgs>;
|
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
|
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 TypeIds;
|
||||||
class Normalizer;
|
class Normalizer;
|
||||||
struct NormalizedType;
|
|
||||||
struct NormalizedClassType;
|
struct NormalizedClassType;
|
||||||
struct NormalizedStringType;
|
|
||||||
struct NormalizedFunctionType;
|
struct NormalizedFunctionType;
|
||||||
struct TypeArena;
|
struct NormalizedStringType;
|
||||||
struct TypeCheckLimits;
|
struct NormalizedType;
|
||||||
|
struct Property;
|
||||||
struct Scope;
|
struct Scope;
|
||||||
struct TableIndexer;
|
struct TableIndexer;
|
||||||
|
struct TypeArena;
|
||||||
|
struct TypeCheckLimits;
|
||||||
|
|
||||||
enum class SubtypingVariance
|
enum class SubtypingVariance
|
||||||
{
|
{
|
||||||
|
@ -79,6 +80,7 @@ struct SubtypingResult
|
||||||
SubtypingResult& withSubPath(TypePath::Path path);
|
SubtypingResult& withSubPath(TypePath::Path path);
|
||||||
SubtypingResult& withSuperPath(TypePath::Path path);
|
SubtypingResult& withSuperPath(TypePath::Path path);
|
||||||
SubtypingResult& withErrors(ErrorVec& err);
|
SubtypingResult& withErrors(ErrorVec& err);
|
||||||
|
SubtypingResult& withError(TypeError err);
|
||||||
|
|
||||||
// Only negates the `isSubtype`.
|
// Only negates the `isSubtype`.
|
||||||
static SubtypingResult negate(const SubtypingResult& result);
|
static SubtypingResult negate(const SubtypingResult& result);
|
||||||
|
@ -102,6 +104,10 @@ struct SubtypingEnvironment
|
||||||
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
||||||
|
|
||||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
|
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
|
struct Subtyping
|
||||||
|
@ -192,6 +198,7 @@ private:
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable);
|
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 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 NormalizedType* subNorm, const NormalizedType* superNorm);
|
||||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
|
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
|
||||||
|
|
|
@ -414,16 +414,11 @@ struct Property
|
||||||
TypeId type() const;
|
TypeId type() const;
|
||||||
void setType(TypeId ty);
|
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 isShared() const;
|
||||||
|
bool isReadOnly() const;
|
||||||
|
bool isWriteOnly() const;
|
||||||
|
bool isReadWrite() const;
|
||||||
|
|
||||||
private:
|
|
||||||
std::optional<TypeId> readTy;
|
std::optional<TypeId> readTy;
|
||||||
std::optional<TypeId> writeTy;
|
std::optional<TypeId> writeTy;
|
||||||
};
|
};
|
||||||
|
@ -844,6 +839,7 @@ public:
|
||||||
|
|
||||||
const TypePackId emptyTypePack;
|
const TypePackId emptyTypePack;
|
||||||
const TypePackId anyTypePack;
|
const TypePackId anyTypePack;
|
||||||
|
const TypePackId unknownTypePack;
|
||||||
const TypePackId neverTypePack;
|
const TypePackId neverTypePack;
|
||||||
const TypePackId uninhabitableTypePack;
|
const TypePackId uninhabitableTypePack;
|
||||||
const TypePackId errorTypePack;
|
const TypePackId errorTypePack;
|
||||||
|
|
|
@ -30,8 +30,10 @@ struct TypeFamilyContext
|
||||||
|
|
||||||
// nullptr if the type family is being reduced outside of the constraint solver.
|
// nullptr if the type family is being reduced outside of the constraint solver.
|
||||||
ConstraintSolver* 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)
|
: arena(cs->arena)
|
||||||
, builtins(cs->builtinTypes)
|
, builtins(cs->builtinTypes)
|
||||||
, scope(scope)
|
, scope(scope)
|
||||||
|
@ -39,6 +41,7 @@ struct TypeFamilyContext
|
||||||
, ice(NotNull{&cs->iceReporter})
|
, ice(NotNull{&cs->iceReporter})
|
||||||
, limits(NotNull{&cs->limits})
|
, limits(NotNull{&cs->limits})
|
||||||
, solver(cs.get())
|
, solver(cs.get())
|
||||||
|
, constraint(constraint.get())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +54,10 @@ struct TypeFamilyContext
|
||||||
, ice(ice)
|
, ice(ice)
|
||||||
, limits(limits)
|
, limits(limits)
|
||||||
, solver(nullptr)
|
, solver(nullptr)
|
||||||
|
, constraint(nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents a reduction result, which may have successfully reduced the type,
|
/// Represents a reduction result, which may have successfully reduced the type,
|
||||||
/// may have concretely failed to reduce the type, or may simply be stuck
|
/// may have concretely failed to reduce the type, or may simply be stuck
|
||||||
/// without more information.
|
/// without more information.
|
||||||
|
|
|
@ -28,6 +28,8 @@ std::optional<TypeId> findMetatableEntry(
|
||||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location);
|
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location);
|
||||||
std::optional<TypeId> findTablePropertyRespectingMeta(
|
std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location);
|
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.
|
// 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);
|
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};
|
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 recursionCount = 0;
|
||||||
int recursionLimit = 0;
|
int recursionLimit = 0;
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
LUAU_FASTINT(LuauVisitRecursionLimit)
|
LUAU_FASTINT(LuauVisitRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauBoundLazyTypes2)
|
LUAU_FASTFLAG(LuauBoundLazyTypes2)
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -281,12 +280,16 @@ struct GenericTypeVisitor
|
||||||
{
|
{
|
||||||
for (auto& [_name, prop] : ttv->props)
|
for (auto& [_name, prop] : ttv->props)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauReadWriteProperties)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
if (auto ty = prop.readType())
|
if (auto ty = prop.readTy)
|
||||||
traverse(*ty);
|
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);
|
traverse(*ty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -315,12 +318,16 @@ struct GenericTypeVisitor
|
||||||
{
|
{
|
||||||
for (const auto& [name, prop] : ctv->props)
|
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);
|
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);
|
traverse(*ty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
|
||||||
namespace Luau
|
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 (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);
|
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
|
||||||
}
|
}
|
||||||
else
|
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 (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);
|
return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteTableKeysNoInitialCharacter, false);
|
LUAU_FASTFLAGVARIABLE(LuauAutocompleteTableKeysNoInitialCharacter, false);
|
||||||
|
|
||||||
|
@ -277,9 +276,9 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
|
||||||
{
|
{
|
||||||
Luau::TypeId type;
|
Luau::TypeId type;
|
||||||
|
|
||||||
if (FFlag::DebugLuauReadWriteProperties)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
if (auto ty = prop.readType())
|
if (auto ty = prop.readTy)
|
||||||
type = follow(*ty);
|
type = follow(*ty);
|
||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -283,6 +283,26 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
|
||||||
}
|
}
|
||||||
|
|
||||||
attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
|
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, "setmetatable"), magicFunctionSetMetaTable);
|
||||||
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
|
attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
|
||||||
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
|
attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
#include "Luau/TypePack.h"
|
#include "Luau/TypePack.h"
|
||||||
#include "Luau/Unifiable.h"
|
#include "Luau/Unifiable.h"
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
|
||||||
|
|
||||||
|
@ -196,14 +194,14 @@ private:
|
||||||
|
|
||||||
Property shallowClone(const Property& p)
|
Property shallowClone(const Property& p)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauReadWriteProperties)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> cloneReadTy;
|
std::optional<TypeId> cloneReadTy;
|
||||||
if (auto ty = p.readType())
|
if (auto ty = p.readTy)
|
||||||
cloneReadTy = shallowClone(*ty);
|
cloneReadTy = shallowClone(*ty);
|
||||||
|
|
||||||
std::optional<TypeId> cloneWriteTy;
|
std::optional<TypeId> cloneWriteTy;
|
||||||
if (auto ty = p.writeType())
|
if (auto ty = p.writeTy)
|
||||||
cloneWriteTy = shallowClone(*ty);
|
cloneWriteTy = shallowClone(*ty);
|
||||||
|
|
||||||
std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
|
std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
|
||||||
|
@ -460,14 +458,14 @@ namespace
|
||||||
|
|
||||||
Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState)
|
Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauReadWriteProperties)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> cloneReadTy;
|
std::optional<TypeId> cloneReadTy;
|
||||||
if (auto ty = prop.readType())
|
if (auto ty = prop.readTy)
|
||||||
cloneReadTy = clone(*ty, dest, cloneState);
|
cloneReadTy = clone(*ty, dest, cloneState);
|
||||||
|
|
||||||
std::optional<TypeId> cloneWriteTy;
|
std::optional<TypeId> cloneWriteTy;
|
||||||
if (auto ty = prop.writeType())
|
if (auto ty = prop.writeTy)
|
||||||
cloneWriteTy = clone(*ty, dest, cloneState);
|
cloneWriteTy = clone(*ty, dest, cloneState);
|
||||||
|
|
||||||
std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
|
std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
|
||||||
|
|
|
@ -217,9 +217,25 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
|
||||||
|
|
||||||
rootScope->returnType = freshTypePack(scope);
|
rootScope->returnType = freshTypePack(scope);
|
||||||
|
|
||||||
|
TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, rootScope, builtinTypes->anyTypePack, rootScope->returnType});
|
||||||
|
interiorTypes.emplace_back();
|
||||||
|
|
||||||
prepopulateGlobalScope(scope, block);
|
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);
|
fillInInferredBindings(scope, block);
|
||||||
|
|
||||||
|
@ -406,7 +422,7 @@ void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location loca
|
||||||
|
|
||||||
TypeId nextDiscriminantTy = arena->addType(TableType{});
|
TypeId nextDiscriminantTy = arena->addType(TableType{});
|
||||||
NotNull<TableType> table{getMutable<TableType>(nextDiscriminantTy)};
|
NotNull<TableType> table{getMutable<TableType>(nextDiscriminantTy)};
|
||||||
table->props[*key->propName] = {discriminantTy};
|
table->props[*key->propName] = Property::readonly(discriminantTy);
|
||||||
table->scope = scope.get();
|
table->scope = scope.get();
|
||||||
table->state = TableState::Sealed;
|
table->state = TableState::Sealed;
|
||||||
|
|
||||||
|
@ -1894,7 +1910,7 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin
|
||||||
scope->rvalueRefinements[key->def] = result;
|
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)
|
if (key)
|
||||||
return Inference{result, refinementArena.proposition(key, builtinTypes->truthyType)};
|
return Inference{result, refinementArena.proposition(key, builtinTypes->truthyType)};
|
||||||
|
@ -1945,27 +1961,30 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
|
||||||
{
|
{
|
||||||
Checkpoint startCheckpoint = checkpoint(this);
|
Checkpoint startCheckpoint = checkpoint(this);
|
||||||
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
|
||||||
|
|
||||||
|
interiorTypes.push_back(std::vector<TypeId>{});
|
||||||
checkFunctionBody(sig.bodyScope, func);
|
checkFunctionBody(sig.bodyScope, func);
|
||||||
Checkpoint endCheckpoint = checkpoint(this);
|
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))
|
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};
|
return Inference{generalizedTy};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -2529,7 +2548,8 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr,
|
||||||
{
|
{
|
||||||
TypeId segmentTy = arena->addType(BlockedType{});
|
TypeId segmentTy = arena->addType(BlockedType{});
|
||||||
module->astTypes[exprs[i]] = segmentTy;
|
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;
|
prevSegmentTy = segmentTy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2563,6 +2583,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
||||||
ttv->state = TableState::Unsealed;
|
ttv->state = TableState::Unsealed;
|
||||||
ttv->scope = scope.get();
|
ttv->scope = scope.get();
|
||||||
|
|
||||||
|
interiorTypes.back().push_back(ty);
|
||||||
|
|
||||||
auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
|
auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
|
||||||
if (!ttv->indexer)
|
if (!ttv->indexer)
|
||||||
{
|
{
|
||||||
|
@ -2613,7 +2635,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
|
||||||
{
|
{
|
||||||
expectedValueType = arena->addType(BlockedType{});
|
expectedValueType = arena->addType(BlockedType{});
|
||||||
addConstraint(scope, item.value->location,
|
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)
|
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 it is possible for execution to reach the end of the function, the return type must be compatible with ()
|
||||||
|
ControlFlow cf = visitBlockWithoutChildScope(scope, fn->body);
|
||||||
if (nullptr != getFallthrough(fn->body))
|
if (cf == ControlFlow::None)
|
||||||
{
|
addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType});
|
||||||
TypePackId empty = arena->addTypePack({}); // TODO we could have CG retain one of these forever
|
|
||||||
addConstraint(scope, fn->location, PackSubtypeConstraint{scope->returnType, empty});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh)
|
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)
|
for (const AstTableProp& prop : tab->props)
|
||||||
{
|
{
|
||||||
if (prop.access == AstTableAccess::Read)
|
// TODO: Recursion limit.
|
||||||
reportError(prop.accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
|
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
|
||||||
else if (prop.access == AstTableAccess::Write)
|
|
||||||
reportError(prop.accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
|
Property& p = props[prop.name.value];
|
||||||
else if (prop.access == AstTableAccess::ReadWrite)
|
p.typeLocation = prop.location;
|
||||||
|
|
||||||
|
switch (prop.access)
|
||||||
{
|
{
|
||||||
std::string name = prop.name.value;
|
case AstTableAccess::ReadWrite:
|
||||||
// TODO: Recursion limit.
|
p.readTy = propTy;
|
||||||
TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
|
p.writeTy = propTy;
|
||||||
props[name] = {propTy};
|
break;
|
||||||
props[name].typeLocation = prop.location;
|
case AstTableAccess::Read:
|
||||||
}
|
p.readTy = propTy;
|
||||||
else
|
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)));
|
ice->ice("Unexpected property access " + std::to_string(int(prop.access)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AstTableIndexer* astIndexer = tab->indexer)
|
if (AstTableIndexer* astIndexer = tab->indexer)
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "Luau/ConstraintSolver.h"
|
#include "Luau/ConstraintSolver.h"
|
||||||
#include "Luau/DcrLogger.h"
|
#include "Luau/DcrLogger.h"
|
||||||
#include "Luau/Instantiation.h"
|
#include "Luau/Instantiation.h"
|
||||||
|
#include "Luau/Instantiation2.h"
|
||||||
#include "Luau/Location.h"
|
#include "Luau/Location.h"
|
||||||
#include "Luau/ModuleResolver.h"
|
#include "Luau/ModuleResolver.h"
|
||||||
#include "Luau/OverloadResolution.h"
|
#include "Luau/OverloadResolution.h"
|
||||||
|
@ -430,8 +431,6 @@ void ConstraintSolver::run()
|
||||||
progress |= runSolverPass(true);
|
progress |= runSolverPass(true);
|
||||||
} while (progress);
|
} while (progress);
|
||||||
|
|
||||||
finalizeModule();
|
|
||||||
|
|
||||||
if (FFlag::DebugLuauLogSolver)
|
if (FFlag::DebugLuauLogSolver)
|
||||||
{
|
{
|
||||||
dumpBindings(rootScope, opts);
|
dumpBindings(rootScope, opts);
|
||||||
|
@ -477,48 +476,6 @@ struct FreeTypeSearcher : TypeOnceVisitor
|
||||||
|
|
||||||
} // namespace
|
} // 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)
|
bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
if (!force && isBlocked(constraint))
|
if (!force && isBlocked(constraint))
|
||||||
|
@ -562,6 +519,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
|
||||||
success = tryDispatch(*rc, constraint, force);
|
success = tryDispatch(*rc, constraint, force);
|
||||||
else if (auto rpc = get<ReducePackConstraint>(*constraint))
|
else if (auto rpc = get<ReducePackConstraint>(*constraint))
|
||||||
success = tryDispatch(*rpc, constraint, force);
|
success = tryDispatch(*rpc, constraint, force);
|
||||||
|
else if (auto eqc = get<EqualityConstraint>(*constraint))
|
||||||
|
success = tryDispatch(*eqc, constraint, force);
|
||||||
else
|
else
|
||||||
LUAU_ASSERT(false);
|
LUAU_ASSERT(false);
|
||||||
|
|
||||||
|
@ -578,7 +537,9 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
|
||||||
else if (isBlocked(c.superType))
|
else if (isBlocked(c.superType))
|
||||||
return block(c.superType, constraint);
|
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)
|
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))
|
else if (isBlocked(c.superPack))
|
||||||
return block(c.superPack, constraint);
|
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)
|
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))
|
if (get<BlockedType>(generalizedType))
|
||||||
asMutable(generalizedType)->ty.emplace<BoundType>(generalized->result);
|
asMutable(generalizedType)->ty.emplace<BoundType>(generalized->result);
|
||||||
else
|
else
|
||||||
unify(constraint->scope, constraint->location, generalizedType, generalized->result);
|
unify(constraint, generalizedType, generalized->result);
|
||||||
|
|
||||||
for (auto [free, gen] : generalized->insertedGenerics.pairings)
|
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)
|
for (auto [free, gen] : generalized->insertedGenericPacks.pairings)
|
||||||
unify(constraint->scope, constraint->location, free, gen);
|
unify(constraint, free, gen);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -632,6 +595,9 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
|
||||||
unblock(c.generalizedType, constraint->location);
|
unblock(c.generalizedType, constraint->location);
|
||||||
unblock(c.sourceType, constraint->location);
|
unblock(c.sourceType, constraint->location);
|
||||||
|
|
||||||
|
for (TypeId ty : c.interiorTypes)
|
||||||
|
u2.generalize(ty);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -730,7 +696,7 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
|
||||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
|
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
|
||||||
std::optional<TypePackId> anyified = anyify.substitute(c.variables);
|
std::optional<TypePackId> anyified = anyify.substitute(c.variables);
|
||||||
LUAU_ASSERT(anyified);
|
LUAU_ASSERT(anyified);
|
||||||
unify(constraint->scope, constraint->location, *anyified, c.variables);
|
unify(constraint, *anyified, c.variables);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1112,6 +1078,26 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
|
||||||
|
|
||||||
const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
|
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 (const auto& [expanded, additions] : u2.expandedFreeTypes)
|
||||||
{
|
{
|
||||||
for (TypeId addition : additions)
|
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)
|
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);
|
const TypePackId argsPack = follow(c.argsPack);
|
||||||
|
|
||||||
if (isBlocked(fn))
|
if (isBlocked(fn))
|
||||||
|
@ -1156,6 +1142,35 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
|
||||||
if (!ftv)
|
if (!ftv)
|
||||||
return true;
|
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> expectedArgs = flatten(ftv->argTypes).first;
|
||||||
const std::vector<TypeId> argPackHead = flatten(argsPack).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))
|
if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFamilyInstanceType>(subjectType))
|
||||||
return block(subjectType, constraint);
|
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())
|
if (!blocked.empty())
|
||||||
{
|
{
|
||||||
for (TypeId blocked : blocked)
|
for (TypeId blocked : blocked)
|
||||||
|
@ -1304,7 +1319,7 @@ static void updateTheTableType(
|
||||||
for (size_t i = 0; i < path.size() - 1; ++i)
|
for (size_t i = 0; i < path.size() - 1; ++i)
|
||||||
{
|
{
|
||||||
t = follow(t);
|
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();
|
dummy.clear();
|
||||||
|
|
||||||
if (!propTy)
|
if (!propTy)
|
||||||
|
@ -1329,17 +1344,26 @@ static void updateTheTableType(
|
||||||
bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force)
|
bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
TypeId subjectType = follow(c.subjectType);
|
TypeId subjectType = follow(c.subjectType);
|
||||||
|
const TypeId propType = follow(c.propType);
|
||||||
|
|
||||||
if (isBlocked(subjectType))
|
if (isBlocked(subjectType))
|
||||||
return block(subjectType, constraint);
|
return block(subjectType, constraint);
|
||||||
|
|
||||||
std::optional<TypeId> existingPropType = subjectType;
|
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)
|
if (!existingPropType)
|
||||||
break;
|
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())
|
if (!blocked.empty())
|
||||||
{
|
{
|
||||||
for (TypeId blocked : blocked)
|
for (TypeId blocked : blocked)
|
||||||
|
@ -1356,8 +1380,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||||
|
|
||||||
if (existingPropType)
|
if (existingPropType)
|
||||||
{
|
{
|
||||||
if (!isBlocked(c.propType))
|
unify(constraint->scope, constraint->location, propType, *existingPropType);
|
||||||
unify(constraint->scope, constraint->location, c.propType, *existingPropType);
|
unify(constraint->scope, constraint->location, *existingPropType, propType);
|
||||||
bind(c.resultType, c.subjectType);
|
bind(c.resultType, c.subjectType);
|
||||||
unblock(c.resultType, constraint->location);
|
unblock(c.resultType, constraint->location);
|
||||||
return true;
|
return true;
|
||||||
|
@ -1382,7 +1406,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!subjectType->persistent);
|
LUAU_ASSERT(!subjectType->persistent);
|
||||||
|
|
||||||
ttv->props[c.path[0]] = Property{c.propType};
|
ttv->props[c.path[0]] = Property{propType};
|
||||||
bind(c.resultType, subjectType);
|
bind(c.resultType, subjectType);
|
||||||
unblock(c.resultType, constraint->location);
|
unblock(c.resultType, constraint->location);
|
||||||
return true;
|
return true;
|
||||||
|
@ -1391,7 +1415,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!subjectType->persistent);
|
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)
|
if (tt->indexer)
|
||||||
{
|
{
|
||||||
// TODO This probably has to be invariant.
|
// 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.propType)->ty.emplace<BoundType>(tt->indexer->indexResultType);
|
||||||
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
|
||||||
unblock(c.propType, constraint->location);
|
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)
|
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
|
||||||
{
|
{
|
||||||
TypeId promotedIndexTy = freshType(arena, builtinTypes, tt->scope);
|
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);
|
auto mtt = getMutable<TableType>(subjectType);
|
||||||
mtt->indexer = TableIndexer{promotedIndexTy, c.propType};
|
mtt->indexer = TableIndexer{promotedIndexTy, c.propType};
|
||||||
|
@ -1486,6 +1510,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||||
|
|
||||||
if (isBlocked(resultPack))
|
if (isBlocked(resultPack))
|
||||||
{
|
{
|
||||||
|
LUAU_ASSERT(resultPack != sourcePack);
|
||||||
asMutable(resultPack)->ty.emplace<BoundTypePack>(sourcePack);
|
asMutable(resultPack)->ty.emplace<BoundTypePack>(sourcePack);
|
||||||
unblock(resultPack, constraint->location);
|
unblock(resultPack, constraint->location);
|
||||||
return true;
|
return true;
|
||||||
|
@ -1525,7 +1550,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(c.resultIsLValue);
|
LUAU_ASSERT(c.resultIsLValue);
|
||||||
unify(constraint->scope, constraint->location, resultTy, srcTy);
|
unify(constraint, resultTy, srcTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
unblock(resultTy, constraint->location);
|
unblock(resultTy, constraint->location);
|
||||||
|
@ -1553,7 +1578,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||||
apply(resultTy, srcTy);
|
apply(resultTy, srcTy);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
unify(constraint->scope, constraint->location, resultTy, srcTy);
|
unify(constraint, resultTy, srcTy);
|
||||||
|
|
||||||
++resultIter;
|
++resultIter;
|
||||||
++i;
|
++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)
|
bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
TypeId ty = follow(c.ty);
|
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)
|
for (TypeId r : result.reducedTypes)
|
||||||
unblock(r, constraint->location);
|
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)
|
bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
TypePackId tp = follow(c.tp);
|
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)
|
for (TypeId r : result.reducedTypes)
|
||||||
unblock(r, constraint->location);
|
unblock(r, constraint->location);
|
||||||
|
@ -1659,6 +1687,13 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
|
||||||
return result.blockedTypes.empty() && result.blockedPacks.empty();
|
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)
|
bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
|
||||||
{
|
{
|
||||||
auto block_ = [&](auto&& t) {
|
auto block_ = [&](auto&& t) {
|
||||||
|
@ -1721,7 +1756,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||||
if (iteratorTable->indexer)
|
if (iteratorTable->indexer)
|
||||||
{
|
{
|
||||||
TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType});
|
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);
|
auto [variableTys, variablesTail] = flatten(c.variables);
|
||||||
|
|
||||||
|
@ -1755,7 +1790,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||||
if (auto iterFtv = get<FunctionType>(*instantiatedIterFn))
|
if (auto iterFtv = get<FunctionType>(*instantiatedIterFn))
|
||||||
{
|
{
|
||||||
TypePackId expectedIterArgs = arena->addTypePack({iteratorTy});
|
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);
|
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 TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
|
||||||
|
|
||||||
const TypeId expectedNextTy = arena->addType(FunctionType{nextArgPack, nextRetPack});
|
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});
|
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 TypePackId nextRetPack = arena->addTypePack(TypePack{{retIndex}, valueTailTy});
|
||||||
|
|
||||||
const TypeId expectedNextTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope, nextArgPack, nextRetPack});
|
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 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;
|
(*c.astForInNextTypes)[c.nextAstFragment] = expectedNextTy;
|
||||||
else
|
|
||||||
reportError(*error);
|
|
||||||
|
|
||||||
auto it = begin(nextRetPack);
|
auto it = begin(nextRetPack);
|
||||||
std::vector<TypeId> modifiedNextRetHead;
|
std::vector<TypeId> modifiedNextRetHead;
|
||||||
|
@ -1882,14 +1915,14 @@ bool ConstraintSolver::tryDispatchIterableFunction(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(
|
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};
|
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(
|
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))
|
if (seen.contains(subjectType))
|
||||||
return {};
|
return {};
|
||||||
|
@ -1906,19 +1939,58 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||||
else if (auto ttv = getMutable<TableType>(subjectType))
|
else if (auto ttv = getMutable<TableType>(subjectType))
|
||||||
{
|
{
|
||||||
if (auto prop = ttv->props.find(propName); prop != ttv->props.end())
|
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};
|
return {{}, ttv->indexer->indexResultType};
|
||||||
else if (ttv->state == TableState::Free)
|
|
||||||
|
if (ttv->state == TableState::Free)
|
||||||
{
|
{
|
||||||
TypeId result = freshType(arena, builtinTypes, ttv->scope);
|
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};
|
return {{}, result};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (auto mt = get<MetatableType>(subjectType))
|
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)
|
if (!blocked.empty() || result)
|
||||||
return {blocked, result};
|
return {blocked, result};
|
||||||
|
|
||||||
|
@ -1949,13 +2021,13 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return lookupTableProp(indexType, propName, suppressSimplification, seen);
|
return lookupTableProp(indexType, propName, context, suppressSimplification, seen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (auto ct = get<ClassType>(subjectType))
|
else if (auto ct = get<ClassType>(subjectType))
|
||||||
{
|
{
|
||||||
if (auto p = lookupClassProp(ct, propName))
|
if (auto p = lookupClassProp(ct, propName))
|
||||||
return {{}, p->type()};
|
return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy};
|
||||||
if (ct->indexer)
|
if (ct->indexer)
|
||||||
{
|
{
|
||||||
return {{}, ct->indexer->indexResultType};
|
return {{}, ct->indexer->indexResultType};
|
||||||
|
@ -1970,14 +2042,14 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||||
if (indexProp == metatable->props.end())
|
if (indexProp == metatable->props.end())
|
||||||
return {{}, std::nullopt};
|
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))
|
else if (auto ft = get<FreeType>(subjectType))
|
||||||
{
|
{
|
||||||
const TypeId upperBound = follow(ft->upperBound);
|
const TypeId upperBound = follow(ft->upperBound);
|
||||||
|
|
||||||
if (get<TableType>(upperBound) || get<PrimitiveType>(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.
|
// 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);
|
TableType* tt = getMutable<TableType>(newUpperBound);
|
||||||
LUAU_ASSERT(tt);
|
LUAU_ASSERT(tt);
|
||||||
TypeId propType = freshType(arena, builtinTypes, scope);
|
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);
|
unify(scope, Location{}, subjectType, newUpperBound);
|
||||||
|
|
||||||
|
@ -2000,7 +2081,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||||
|
|
||||||
for (TypeId ty : utv)
|
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());
|
blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
|
||||||
if (innerResult)
|
if (innerResult)
|
||||||
options.insert(*innerResult);
|
options.insert(*innerResult);
|
||||||
|
@ -2029,7 +2110,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||||
|
|
||||||
for (TypeId ty : itv)
|
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());
|
blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
|
||||||
if (innerResult)
|
if (innerResult)
|
||||||
options.insert(*innerResult);
|
options.insert(*innerResult);
|
||||||
|
@ -2055,34 +2136,39 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
|
||||||
return {{}, std::nullopt};
|
return {{}, std::nullopt};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename TID>
|
template <typename TID>
|
||||||
bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
|
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 (const auto& [expanded, additions] : u2.expandedFreeTypes)
|
||||||
{
|
{
|
||||||
for (TypeId addition : additions)
|
for (TypeId addition : additions)
|
||||||
upperBoundContributors[expanded].push_back(std::make_pair(constraint->location, addition));
|
upperBoundContributors[expanded].push_back(std::make_pair(location, addition));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Unification only fails when doing so would fail the occurs check.
|
reportError(OccursCheckFailed{}, location);
|
||||||
// ie create a self-bound type or a cyclic type pack
|
return false;
|
||||||
reportError(OccursCheckFailed{}, constraint->location);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unblock(subTy, constraint->location);
|
unblock(subType, location);
|
||||||
unblock(superTy, constraint->location);
|
unblock(superType, location);
|
||||||
|
|
||||||
return true;
|
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)
|
void ConstraintSolver::bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, Location location)
|
||||||
{
|
{
|
||||||
resultTy = follow(resultTy);
|
resultTy = follow(resultTy);
|
||||||
|
@ -2297,6 +2383,21 @@ void ConstraintSolver::unblock(const std::vector<TypePackId>& packs, Location lo
|
||||||
unblock(t, location);
|
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)
|
bool ConstraintSolver::isBlocked(TypeId ty)
|
||||||
{
|
{
|
||||||
ty = follow(ty);
|
ty = follow(ty);
|
||||||
|
@ -2318,39 +2419,6 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
|
||||||
return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
|
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)
|
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));
|
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";
|
"' 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
|
std::string operator()(const CheckedFunctionIncorrectArgs& e) const
|
||||||
{
|
{
|
||||||
return "Checked Function " + e.functionName + " expects " + std::to_string(e.expected) + " arguments, but received " +
|
return "Checked Function " + e.functionName + " expects " + std::to_string(e.expected) + " arguments, but received " +
|
||||||
std::to_string(e.actual);
|
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
|
struct InvalidNameChecker
|
||||||
|
@ -638,6 +663,11 @@ bool UnknownProperty::operator==(const UnknownProperty& rhs) const
|
||||||
return *table == *rhs.table && key == rhs.key;
|
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
|
bool NotATable::operator==(const NotATable& rhs) const
|
||||||
{
|
{
|
||||||
return ty == rhs.ty;
|
return ty == rhs.ty;
|
||||||
|
@ -884,6 +914,16 @@ bool CheckedFunctionIncorrectArgs::operator==(const CheckedFunctionIncorrectArgs
|
||||||
return functionName == rhs.functionName && expected == rhs.expected && actual == rhs.actual;
|
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)
|
std::string toString(const TypeError& error)
|
||||||
{
|
{
|
||||||
return toString(error, TypeErrorToStringOptions{});
|
return toString(error, TypeErrorToStringOptions{});
|
||||||
|
@ -1059,9 +1099,15 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
||||||
{
|
{
|
||||||
e.argumentType = clone(e.argumentType);
|
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, 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
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,9 +32,8 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTINT(LuauTarjanChildLimit)
|
LUAU_FASTINT(LuauTarjanChildLimit)
|
||||||
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
LUAU_FASTFLAG(LuauInferInNoCheckMode)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
|
||||||
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
|
LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -1219,6 +1218,15 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
|
||||||
result->cancelled = true;
|
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)
|
for (TypeError& e : cs.errors)
|
||||||
result->errors.emplace_back(std::move(e));
|
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->internalTypes);
|
||||||
freeze(result->interfaceTypes);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "Luau/Instantiation.h"
|
#include "Luau/Instantiation.h"
|
||||||
|
|
||||||
#include "Luau/Common.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/ToString.h"
|
||||||
#include "Luau/TxnLog.h"
|
#include "Luau/TxnLog.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
|
@ -143,39 +144,6 @@ TypePackId ReplaceGenerics::clean(TypePackId tp)
|
||||||
return addTypePack(TypePackVar(FreeTypePack{scope, level}));
|
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(
|
std::optional<TypeId> instantiate(
|
||||||
NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty)
|
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>)
|
else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
|
||||||
stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
|
stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
|
||||||
"', argumentType = '" + toString(err.argumentType) + "' }";
|
"', 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>)
|
else if constexpr (std::is_same_v<T, CheckedFunctionIncorrectArgs>)
|
||||||
stream << "CheckedFunction { functionName = '" + err.functionName + ", expected = " + std::to_string(err.expected) +
|
stream << "CheckedFunction { functionName = '" + err.functionName + ", expected = " + std::to_string(err.expected) +
|
||||||
", actual = " + std::to_string(err.actual) + "}";
|
", 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
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -1848,6 +1850,49 @@ private:
|
||||||
|
|
||||||
bool visit(AstTypeTable* node) override
|
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{});
|
DenseHashMap<AstName, int> names(AstName{});
|
||||||
|
|
||||||
for (const AstTableProp& item : node->props)
|
for (const AstTableProp& item : node->props)
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "Luau/Common.h"
|
#include "Luau/Common.h"
|
||||||
#include "Luau/RecursionCounter.h"
|
#include "Luau/RecursionCounter.h"
|
||||||
#include "Luau/Set.h"
|
#include "Luau/Set.h"
|
||||||
|
#include "Luau/Simplify.h"
|
||||||
#include "Luau/Subtyping.h"
|
#include "Luau/Subtyping.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeFwd.h"
|
#include "Luau/TypeFwd.h"
|
||||||
|
@ -20,7 +21,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
|
||||||
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
|
||||||
LUAU_FASTFLAG(LuauTransitiveSubtyping)
|
LUAU_FASTFLAG(LuauTransitiveSubtyping)
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
|
||||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -472,11 +472,11 @@ bool Normalizer::isInhabited(TypeId ty, Set<TypeId>& seen)
|
||||||
{
|
{
|
||||||
for (const auto& [_, prop] : ttv->props)
|
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,
|
// 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.
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -2349,12 +2349,61 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
|
||||||
{
|
{
|
||||||
const auto& [_name, tprop] = *tfound;
|
const auto& [_name, tprop] = *tfound;
|
||||||
// TODO: variance issues here, which can't be fixed until we have read/write property types
|
// TODO: variance issues here, which can't be fixed until we have read/write property types
|
||||||
prop.setType(intersectionType(hprop.type(), tprop.type()));
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
hereSubThere &= (prop.type() == hprop.type());
|
{
|
||||||
thereSubHere &= (prop.type() == tprop.type());
|
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
|
// TODO: string indexers
|
||||||
result.props[name] = prop;
|
|
||||||
|
if (prop.readTy || prop.writeTy)
|
||||||
|
result.props[name] = prop;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [name, tprop] : tttv->props)
|
for (const auto& [name, tprop] : tttv->props)
|
||||||
|
@ -2431,8 +2480,10 @@ void Normalizer::intersectTablesWithTable(TypeIds& heres, TypeId there)
|
||||||
{
|
{
|
||||||
TypeIds tmp;
|
TypeIds tmp;
|
||||||
for (TypeId here : heres)
|
for (TypeId here : heres)
|
||||||
|
{
|
||||||
if (std::optional<TypeId> inter = intersectionOfTables(here, there))
|
if (std::optional<TypeId> inter = intersectionOfTables(here, there))
|
||||||
tmp.insert(*inter);
|
tmp.insert(*inter);
|
||||||
|
}
|
||||||
heres.retain(tmp);
|
heres.retain(tmp);
|
||||||
heres.insert(tmp.begin(), tmp.end());
|
heres.insert(tmp.begin(), tmp.end());
|
||||||
}
|
}
|
||||||
|
@ -2441,9 +2492,14 @@ void Normalizer::intersectTables(TypeIds& heres, const TypeIds& theres)
|
||||||
{
|
{
|
||||||
TypeIds tmp;
|
TypeIds tmp;
|
||||||
for (TypeId here : heres)
|
for (TypeId here : heres)
|
||||||
|
{
|
||||||
for (TypeId there : theres)
|
for (TypeId there : theres)
|
||||||
|
{
|
||||||
if (std::optional<TypeId> inter = intersectionOfTables(here, there))
|
if (std::optional<TypeId> inter = intersectionOfTables(here, there))
|
||||||
tmp.insert(*inter);
|
tmp.insert(*inter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
heres.retain(tmp);
|
heres.retain(tmp);
|
||||||
heres.insert(tmp.begin(), tmp.end());
|
heres.insert(tmp.begin(), tmp.end());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,10 @@
|
||||||
#include "Luau/Simplify.h"
|
#include "Luau/Simplify.h"
|
||||||
|
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Normalize.h" // TypeIds
|
|
||||||
#include "Luau/RecursionCounter.h"
|
#include "Luau/RecursionCounter.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/Set.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
|
#include "Luau/TypePairHash.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -17,6 +17,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
|
||||||
|
using SimplifierSeenSet = Set<std::pair<TypeId, TypeId>, TypePairHash>;
|
||||||
|
|
||||||
struct TypeSimplifier
|
struct TypeSimplifier
|
||||||
{
|
{
|
||||||
NotNull<BuiltinTypes> builtinTypes;
|
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);
|
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> leftTable{get<TableType>(left)};
|
||||||
NotNull<const TableType> rightTable{get<TableType>(right)};
|
NotNull<const TableType> rightTable{get<TableType>(right)};
|
||||||
LUAU_ASSERT(1 == rightTable->props.size());
|
LUAU_ASSERT(1 == rightTable->props.size());
|
||||||
// Disjoint props have nothing in common
|
// 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
|
// 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) {
|
bool foundPropFromLeftInRight = std::any_of(begin(leftTable->props), end(leftTable->props),
|
||||||
return rightTable->props.find(prop.first) != end(rightTable->props);
|
[&](auto prop)
|
||||||
});
|
{
|
||||||
bool foundPropFromRightInLeft = std::any_of(begin(rightTable->props), end(rightTable->props), [&](auto prop) {
|
return rightTable->props.count(prop.first) > 0;
|
||||||
return leftTable->props.find(prop.first) != end(leftTable->props);
|
});
|
||||||
});
|
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;
|
return Relation::Disjoint;
|
||||||
|
|
||||||
const auto [propName, rightProp] = *begin(rightTable->props);
|
const auto [propName, rightProp] = *begin(rightTable->props);
|
||||||
|
@ -257,7 +263,10 @@ Relation relateTables(TypeId left, TypeId right)
|
||||||
|
|
||||||
const Property leftProp = it->second;
|
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())
|
if (r == Relation::Coincident && 1 != leftTable->props.size())
|
||||||
{
|
{
|
||||||
// eg {tag: "cat", prop: string} & {tag: "cat"}
|
// eg {tag: "cat", prop: string} & {tag: "cat"}
|
||||||
|
@ -268,7 +277,7 @@ Relation relateTables(TypeId left, TypeId right)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A cheap and approximate subtype test
|
// 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
|
// 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)
|
if (left == right)
|
||||||
return Relation::Coincident;
|
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<UnknownType>(left))
|
||||||
{
|
{
|
||||||
if (get<AnyType>(right))
|
if (get<AnyType>(right))
|
||||||
|
@ -291,7 +308,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get<UnknownType>(right))
|
if (get<UnknownType>(right))
|
||||||
return flip(relate(right, left));
|
return flip(relate(right, left, seen));
|
||||||
|
|
||||||
if (get<AnyType>(left))
|
if (get<AnyType>(left))
|
||||||
{
|
{
|
||||||
|
@ -302,7 +319,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (get<AnyType>(right))
|
if (get<AnyType>(right))
|
||||||
return flip(relate(right, left));
|
return flip(relate(right, left, seen));
|
||||||
|
|
||||||
// Type variables
|
// Type variables
|
||||||
// * FreeType
|
// * FreeType
|
||||||
|
@ -340,7 +357,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
}
|
}
|
||||||
if (get<ErrorType>(right))
|
if (get<ErrorType>(right))
|
||||||
return flip(relate(right, left));
|
return flip(relate(right, left, seen));
|
||||||
|
|
||||||
if (get<NeverType>(left))
|
if (get<NeverType>(left))
|
||||||
{
|
{
|
||||||
|
@ -350,7 +367,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
return Relation::Subset;
|
return Relation::Subset;
|
||||||
}
|
}
|
||||||
if (get<NeverType>(right))
|
if (get<NeverType>(right))
|
||||||
return flip(relate(right, left));
|
return flip(relate(right, left, seen));
|
||||||
|
|
||||||
if (auto ut = get<IntersectionType>(left))
|
if (auto ut = get<IntersectionType>(left))
|
||||||
return Relation::Intersects;
|
return Relation::Intersects;
|
||||||
|
@ -363,14 +380,14 @@ Relation relate(TypeId left, TypeId right)
|
||||||
{
|
{
|
||||||
std::vector<Relation> opts;
|
std::vector<Relation> opts;
|
||||||
for (TypeId part : ut)
|
for (TypeId part : ut)
|
||||||
if (relate(left, part) == Relation::Subset)
|
if (relate(left, part, seen) == Relation::Subset)
|
||||||
return Relation::Subset;
|
return Relation::Subset;
|
||||||
return Relation::Intersects;
|
return Relation::Intersects;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto rnt = get<NegationType>(right))
|
if (auto rnt = get<NegationType>(right))
|
||||||
{
|
{
|
||||||
Relation a = relate(left, rnt->ty);
|
Relation a = relate(left, rnt->ty, seen);
|
||||||
switch (a)
|
switch (a)
|
||||||
{
|
{
|
||||||
case Relation::Coincident:
|
case Relation::Coincident:
|
||||||
|
@ -401,7 +418,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (get<NegationType>(left))
|
else if (get<NegationType>(left))
|
||||||
return flip(relate(right, left));
|
return flip(relate(right, left, seen));
|
||||||
|
|
||||||
if (auto lp = get<PrimitiveType>(left))
|
if (auto lp = get<PrimitiveType>(left))
|
||||||
{
|
{
|
||||||
|
@ -448,7 +465,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
return Relation::Disjoint;
|
return Relation::Disjoint;
|
||||||
|
|
||||||
if (get<PrimitiveType>(right))
|
if (get<PrimitiveType>(right))
|
||||||
return flip(relate(right, left));
|
return flip(relate(right, left, seen));
|
||||||
if (auto rs = get<SingletonType>(right))
|
if (auto rs = get<SingletonType>(right))
|
||||||
{
|
{
|
||||||
if (ls->variant == rs->variant)
|
if (ls->variant == rs->variant)
|
||||||
|
@ -485,7 +502,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
// TODO PROBABLY indexers and metatables.
|
// TODO PROBABLY indexers and metatables.
|
||||||
if (1 == rt->props.size())
|
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
|
* A reduction of these intersections is certainly possible, but
|
||||||
* it would require minting new table types. Also, I don't think
|
* it would require minting new table types. Also, I don't think
|
||||||
|
@ -504,7 +521,7 @@ Relation relate(TypeId left, TypeId right)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
else if (1 == lt->props.size())
|
else if (1 == lt->props.size())
|
||||||
return flip(relate(right, left));
|
return flip(relate(right, left, seen));
|
||||||
else
|
else
|
||||||
return Relation::Intersects;
|
return Relation::Intersects;
|
||||||
}
|
}
|
||||||
|
@ -531,6 +548,13 @@ Relation relate(TypeId left, TypeId right)
|
||||||
return Relation::Intersects;
|
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 TypeSimplifier::mkNegation(TypeId ty)
|
||||||
{
|
{
|
||||||
TypeId result = nullptr;
|
TypeId result = nullptr;
|
||||||
|
@ -1056,7 +1080,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
|
||||||
const auto [propName, leftProp] = *begin(lt->props);
|
const auto [propName, leftProp] = *begin(lt->props);
|
||||||
|
|
||||||
auto it = rt->props.find(propName);
|
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());
|
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())
|
if (1 == tt->props.size())
|
||||||
{
|
{
|
||||||
TypeId propTy = simplify(begin(tt->props)->second.type(), seen);
|
if (std::optional<TypeId> readTy = begin(tt->props)->second.readTy)
|
||||||
if (get<NeverType>(propTy))
|
{
|
||||||
return builtinTypes->neverType;
|
TypeId propTy = simplify(*readTy, seen);
|
||||||
|
if (get<NeverType>(propTy))
|
||||||
|
return builtinTypes->neverType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
LUAU_FASTFLAGVARIABLE(LuauPreallocateTarjanVectors, false);
|
LUAU_FASTFLAGVARIABLE(LuauPreallocateTarjanVectors, false);
|
||||||
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
|
LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
|
||||||
|
|
||||||
|
@ -185,10 +185,10 @@ void Tarjan::visitChildren(TypeId ty, int index)
|
||||||
LUAU_ASSERT(!ttv->boundTo);
|
LUAU_ASSERT(!ttv->boundTo);
|
||||||
for (const auto& [name, prop] : ttv->props)
|
for (const auto& [name, prop] : ttv->props)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauReadWriteProperties)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
visitChild(prop.readType());
|
visitChild(prop.readTy);
|
||||||
visitChild(prop.writeType());
|
visitChild(prop.writeTy);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
visitChild(prop.type());
|
visitChild(prop.type());
|
||||||
|
@ -700,8 +700,8 @@ void Substitution::replaceChildren(TypeId ty)
|
||||||
LUAU_ASSERT(!ttv->boundTo);
|
LUAU_ASSERT(!ttv->boundTo);
|
||||||
for (auto& [name, prop] : ttv->props)
|
for (auto& [name, prop] : ttv->props)
|
||||||
{
|
{
|
||||||
if (FFlag::DebugLuauReadWriteProperties)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
prop = Property::create(replace(prop.readType()), replace(prop.writeType()));
|
prop = Property::create(replace(prop.readTy), replace(prop.writeTy));
|
||||||
else
|
else
|
||||||
prop.setType(replace(prop.type()));
|
prop.setType(replace(prop.type()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,9 @@
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
#include "Luau/Scope.h"
|
#include "Luau/Scope.h"
|
||||||
#include "Luau/StringUtils.h"
|
#include "Luau/StringUtils.h"
|
||||||
|
#include "Luau/Substitution.h"
|
||||||
#include "Luau/ToString.h"
|
#include "Luau/ToString.h"
|
||||||
|
#include "Luau/TxnLog.h"
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypeCheckLimits.h"
|
#include "Luau/TypeCheckLimits.h"
|
||||||
|
@ -217,7 +219,14 @@ SubtypingResult& SubtypingResult::withSuperPath(TypePath::Path path)
|
||||||
|
|
||||||
SubtypingResult& SubtypingResult::withErrors(ErrorVec& err)
|
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;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +254,74 @@ SubtypingResult SubtypingResult::any(const std::vector<SubtypingResult>& results
|
||||||
return acc;
|
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,
|
Subtyping::Subtyping(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> typeArena, NotNull<Normalizer> normalizer,
|
||||||
NotNull<InternalErrorReporter> iceReporter, NotNull<Scope> scope)
|
NotNull<InternalErrorReporter> iceReporter, NotNull<Scope> scope)
|
||||||
: builtinTypes(builtinTypes)
|
: builtinTypes(builtinTypes)
|
||||||
|
@ -493,9 +570,19 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (auto subTypeFamilyInstance = get<TypeFamilyInstanceType>(subTy))
|
else if (auto subTypeFamilyInstance = get<TypeFamilyInstanceType>(subTy))
|
||||||
|
{
|
||||||
|
if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy))
|
||||||
|
subTypeFamilyInstance = get<TypeFamilyInstanceType>(*substSubTy);
|
||||||
|
|
||||||
result = isCovariantWith(env, subTypeFamilyInstance, superTy);
|
result = isCovariantWith(env, subTypeFamilyInstance, superTy);
|
||||||
|
}
|
||||||
else if (auto superTypeFamilyInstance = get<TypeFamilyInstanceType>(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);
|
result = isCovariantWith(env, subTy, superTypeFamilyInstance);
|
||||||
|
}
|
||||||
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
||||||
{
|
{
|
||||||
bool ok = bindGeneric(env, subTy, superTy);
|
bool ok = bindGeneric(env, subTy, superTy);
|
||||||
|
@ -604,7 +691,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
else if (get<ErrorTypePack>(*subTail))
|
else if (get<ErrorTypePack>(*subTail))
|
||||||
return SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail);
|
return SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail);
|
||||||
else
|
else
|
||||||
unexpected(*subTail);
|
return SubtypingResult{false}
|
||||||
|
.withSubComponent(TypePath::PackField::Tail)
|
||||||
|
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -656,7 +745,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
else if (get<ErrorTypePack>(*superTail))
|
else if (get<ErrorTypePack>(*superTail))
|
||||||
return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail);
|
return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail);
|
||||||
else
|
else
|
||||||
unexpected(*superTail);
|
return SubtypingResult{false}
|
||||||
|
.withSuperComponent(TypePath::PackField::Tail)
|
||||||
|
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return {false};
|
return {false};
|
||||||
|
@ -717,8 +808,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
// error type is fine on either side
|
// error type is fine on either side
|
||||||
results.push_back(SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail));
|
results.push_back(SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail));
|
||||||
else
|
else
|
||||||
iceReporter->ice(
|
return SubtypingResult{false}
|
||||||
format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str()));
|
.withBothComponent(TypePath::PackField::Tail)
|
||||||
|
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}})
|
||||||
|
.withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
|
||||||
}
|
}
|
||||||
else if (subTail)
|
else if (subTail)
|
||||||
{
|
{
|
||||||
|
@ -732,7 +825,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
return SubtypingResult{ok}.withSubComponent(TypePath::PackField::Tail);
|
return SubtypingResult{ok}.withSubComponent(TypePath::PackField::Tail);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
unexpected(*subTail);
|
return SubtypingResult{false}
|
||||||
|
.withSubComponent(TypePath::PackField::Tail)
|
||||||
|
.withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
|
||||||
}
|
}
|
||||||
else if (superTail)
|
else if (superTail)
|
||||||
{
|
{
|
||||||
|
@ -759,7 +854,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
|
||||||
results.push_back(SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail));
|
results.push_back(SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail));
|
||||||
}
|
}
|
||||||
else
|
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);
|
SubtypingResult result = SubtypingResult::all(results);
|
||||||
|
@ -1126,18 +1223,32 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
|
||||||
if (subTable->props.empty() && !subTable->indexer && superTable->indexer)
|
if (subTable->props.empty() && !subTable->indexer && superTable->indexer)
|
||||||
return {false};
|
return {false};
|
||||||
|
|
||||||
for (const auto& [name, prop] : superTable->props)
|
for (const auto& [name, superProp] : superTable->props)
|
||||||
{
|
{
|
||||||
std::vector<SubtypingResult> results;
|
std::vector<SubtypingResult> results;
|
||||||
if (auto it = subTable->props.find(name); it != subTable->props.end())
|
if (auto subIter = subTable->props.find(name); subIter != subTable->props.end())
|
||||||
results.push_back(isInvariantWith(env, it->second.type(), prop.type()).withBothComponent(TypePath::Property(name)));
|
results.push_back(isCovariantWith(env, subIter->second, superProp, name));
|
||||||
|
|
||||||
if (subTable->indexer)
|
if (subTable->indexer)
|
||||||
{
|
{
|
||||||
if (isInvariantWith(env, subTable->indexer->indexType, builtinTypes->stringType).isSubtype)
|
if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType).isSubtype)
|
||||||
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, prop.type())
|
{
|
||||||
.withSubComponent(TypePath::TypeField::IndexResult)
|
if (superProp.isShared())
|
||||||
.withSuperComponent(TypePath::Property(name)));
|
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())
|
if (results.empty())
|
||||||
|
@ -1197,7 +1308,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Clas
|
||||||
for (const auto& [name, prop] : superTable->props)
|
for (const auto& [name, prop] : superTable->props)
|
||||||
{
|
{
|
||||||
if (auto classProp = lookupClassProp(subClass, name))
|
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
|
else
|
||||||
return SubtypingResult{false};
|
return SubtypingResult{false};
|
||||||
}
|
}
|
||||||
|
@ -1230,7 +1343,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim
|
||||||
{
|
{
|
||||||
if (auto stringTable = get<TableType>(it->second.type()))
|
if (auto stringTable = get<TableType>(it->second.type()))
|
||||||
result.orElse(
|
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()))
|
if (auto stringTable = get<TableType>(it->second.type()))
|
||||||
result.orElse(
|
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));
|
.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)
|
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||||
{
|
{
|
||||||
if (!subNorm || !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))});
|
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
|
} // namespace Luau
|
||||||
|
|
|
@ -374,7 +374,7 @@ struct TypeStringifier
|
||||||
tv->ty);
|
tv->ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
void stringify(const std::string& name, const Property& prop)
|
void emitKey(const std::string& name)
|
||||||
{
|
{
|
||||||
if (isIdentifier(name))
|
if (isIdentifier(name))
|
||||||
state.emit(name);
|
state.emit(name);
|
||||||
|
@ -385,31 +385,46 @@ struct TypeStringifier
|
||||||
state.emit("\"]");
|
state.emit("\"]");
|
||||||
}
|
}
|
||||||
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.
|
emitKey(name);
|
||||||
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
|
|
||||||
stringify(prop.type());
|
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);
|
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>)
|
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>)
|
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);
|
return "reduce " + tos(c.tp);
|
||||||
}
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, EqualityConstraint>)
|
||||||
|
return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType);
|
||||||
else
|
else
|
||||||
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
|
||||||
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
|
||||||
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
LUAU_FASTFLAG(LuauInstantiateInSubtyping)
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -632,13 +631,10 @@ Property::Property(TypeId readTy, bool deprecated, const std::string& deprecated
|
||||||
, readTy(readTy)
|
, readTy(readTy)
|
||||||
, writeTy(readTy)
|
, writeTy(readTy)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Property Property::readonly(TypeId ty)
|
Property Property::readonly(TypeId ty)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
|
||||||
|
|
||||||
Property p;
|
Property p;
|
||||||
p.readTy = ty;
|
p.readTy = ty;
|
||||||
return p;
|
return p;
|
||||||
|
@ -646,8 +642,6 @@ Property Property::readonly(TypeId ty)
|
||||||
|
|
||||||
Property Property::writeonly(TypeId ty)
|
Property Property::writeonly(TypeId ty)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
|
||||||
|
|
||||||
Property p;
|
Property p;
|
||||||
p.writeTy = ty;
|
p.writeTy = ty;
|
||||||
return p;
|
return p;
|
||||||
|
@ -660,8 +654,6 @@ Property Property::rw(TypeId ty)
|
||||||
|
|
||||||
Property Property::rw(TypeId read, TypeId write)
|
Property Property::rw(TypeId read, TypeId write)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
|
||||||
|
|
||||||
Property p;
|
Property p;
|
||||||
p.readTy = read;
|
p.readTy = read;
|
||||||
p.writeTy = write;
|
p.writeTy = write;
|
||||||
|
@ -683,29 +675,15 @@ Property Property::create(std::optional<TypeId> read, std::optional<TypeId> writ
|
||||||
|
|
||||||
TypeId Property::type() const
|
TypeId Property::type() const
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
|
||||||
LUAU_ASSERT(readTy);
|
LUAU_ASSERT(readTy);
|
||||||
return *readTy;
|
return *readTy;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Property::setType(TypeId ty)
|
void Property::setType(TypeId ty)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
|
||||||
readTy = ty;
|
readTy = ty;
|
||||||
}
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
|
writeTy = 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Property::isShared() const
|
bool Property::isShared() const
|
||||||
|
@ -713,6 +691,21 @@ bool Property::isShared() const
|
||||||
return readTy && writeTy && readTy == writeTy;
|
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)
|
TableType::TableType(TableState state, TypeLevel level, Scope* scope)
|
||||||
: state(state)
|
: state(state)
|
||||||
, level(level)
|
, level(level)
|
||||||
|
@ -961,6 +954,7 @@ BuiltinTypes::BuiltinTypes()
|
||||||
, optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true}))
|
, optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true}))
|
||||||
, emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true}))
|
, emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true}))
|
||||||
, anyTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, /*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}))
|
, neverTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{neverType}, /*persistent*/ true}))
|
||||||
, uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true}))
|
, uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true}))
|
||||||
, errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true}))
|
, errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true}))
|
||||||
|
|
|
@ -1460,7 +1460,7 @@ struct TypeChecker2
|
||||||
{
|
{
|
||||||
visit(expr, ValueContext::RValue);
|
visit(expr, ValueContext::RValue);
|
||||||
TypeId leftType = stripFromNilAndReport(lookupType(expr), location);
|
TypeId leftType = stripFromNilAndReport(lookupType(expr), location);
|
||||||
checkIndexTypeFromType(leftType, propName, location, context, astIndexExprTy);
|
checkIndexTypeFromType(leftType, propName, context, location, astIndexExprTy);
|
||||||
}
|
}
|
||||||
|
|
||||||
void visit(AstExprIndexName* indexName, ValueContext context)
|
void visit(AstExprIndexName* indexName, ValueContext context)
|
||||||
|
@ -1709,8 +1709,8 @@ struct TypeChecker2
|
||||||
|
|
||||||
TypeId visit(AstExprBinary* expr, AstNode* overrideKey = nullptr)
|
TypeId visit(AstExprBinary* expr, AstNode* overrideKey = nullptr)
|
||||||
{
|
{
|
||||||
visit(expr->left, ValueContext::LValue);
|
visit(expr->left, ValueContext::RValue);
|
||||||
visit(expr->right, ValueContext::LValue);
|
visit(expr->right, ValueContext::RValue);
|
||||||
|
|
||||||
NotNull<Scope> scope = stack.back();
|
NotNull<Scope> scope = stack.back();
|
||||||
|
|
||||||
|
@ -2534,20 +2534,16 @@ struct TypeChecker2
|
||||||
reportError(std::move(e));
|
reportError(std::move(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the provided type does not have the named property, report an error.
|
/* A helper for checkIndexTypeFromType.
|
||||||
void checkIndexTypeFromType(TypeId tableTy, const std::string& prop, const Location& location, ValueContext context, TypeId astIndexExprType)
|
*
|
||||||
|
* 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;
|
bool foundOneProp = false;
|
||||||
std::vector<TypeId> typesMissingTheProp;
|
std::vector<TypeId> typesMissingTheProp;
|
||||||
|
|
||||||
|
@ -2556,7 +2552,7 @@ struct TypeChecker2
|
||||||
return;
|
return;
|
||||||
|
|
||||||
DenseHashSet<TypeId> seen{nullptr};
|
DenseHashSet<TypeId> seen{nullptr};
|
||||||
bool found = hasIndexTypeFromType(ty, prop, location, seen, astIndexExprType);
|
bool found = hasIndexTypeFromType(ty, prop, context, location, seen, astIndexExprType, errors);
|
||||||
foundOneProp |= found;
|
foundOneProp |= found;
|
||||||
if (!found)
|
if (!found)
|
||||||
typesMissingTheProp.push_back(ty);
|
typesMissingTheProp.push_back(ty);
|
||||||
|
@ -2601,6 +2597,26 @@ struct TypeChecker2
|
||||||
fetch(tyvar);
|
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 (!typesMissingTheProp.empty())
|
||||||
{
|
{
|
||||||
if (foundOneProp)
|
if (foundOneProp)
|
||||||
|
@ -2611,17 +2627,29 @@ struct TypeChecker2
|
||||||
// the `else` branch.
|
// the `else` branch.
|
||||||
else if (context == ValueContext::LValue && !get<ClassType>(tableTy))
|
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);
|
reportError(NotATable{tableTy}, location);
|
||||||
else
|
else
|
||||||
reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
|
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
|
else
|
||||||
reportError(UnknownProperty{tableTy, prop}, location);
|
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
|
// If we have already encountered this type, we must assume that some
|
||||||
// other codepath will do the right thing and signal false if the
|
// other codepath will do the right thing and signal false if the
|
||||||
|
@ -2635,14 +2663,14 @@ struct TypeChecker2
|
||||||
|
|
||||||
if (isString(ty))
|
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);
|
LUAU_ASSERT(mtIndex);
|
||||||
ty = *mtIndex;
|
ty = *mtIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto tt = getTableType(ty))
|
if (auto tt = getTableType(ty))
|
||||||
{
|
{
|
||||||
if (findTablePropertyRespectingMeta(builtinTypes, module->errors, ty, prop, location))
|
if (findTablePropertyRespectingMeta(builtinTypes, errors, ty, prop, context, location))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (tt->indexer)
|
if (tt->indexer)
|
||||||
|
@ -2674,11 +2702,11 @@ struct TypeChecker2
|
||||||
}
|
}
|
||||||
else if (const UnionType* utv = get<UnionType>(ty))
|
else if (const UnionType* utv = get<UnionType>(ty))
|
||||||
return std::all_of(begin(utv), end(utv), [&](TypeId part) {
|
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))
|
else if (const IntersectionType* itv = get<IntersectionType>(ty))
|
||||||
return std::any_of(begin(itv), end(itv), [&](TypeId part) {
|
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
|
else
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "Luau/DenseHash.h"
|
#include "Luau/DenseHash.h"
|
||||||
#include "Luau/Instantiation.h"
|
#include "Luau/Instantiation.h"
|
||||||
#include "Luau/Normalize.h"
|
#include "Luau/Normalize.h"
|
||||||
|
#include "Luau/NotNull.h"
|
||||||
#include "Luau/Simplify.h"
|
#include "Luau/Simplify.h"
|
||||||
#include "Luau/Substitution.h"
|
#include "Luau/Substitution.h"
|
||||||
#include "Luau/Subtyping.h"
|
#include "Luau/Subtyping.h"
|
||||||
|
@ -853,6 +854,27 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, con
|
||||||
TypeId lhsTy = follow(typeParams.at(0));
|
TypeId lhsTy = follow(typeParams.at(0));
|
||||||
TypeId rhsTy = follow(typeParams.at(1));
|
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
|
// check to see if both operand types are resolved enough, and wait to reduce if not
|
||||||
if (isPending(lhsTy, ctx->solver))
|
if (isPending(lhsTy, ctx->solver))
|
||||||
return {std::nullopt, false, {lhsTy}, {}};
|
return {std::nullopt, false, {lhsTy}, {}};
|
||||||
|
@ -1248,8 +1270,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
|
||||||
if (!normTy)
|
if (!normTy)
|
||||||
return {std::nullopt, false, {}, {}};
|
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
|
// 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
|
||||||
// well)
|
// as well)
|
||||||
if (normTy->hasTables() == normTy->hasClasses())
|
if (normTy->hasTables() == normTy->hasClasses())
|
||||||
return {std::nullopt, true, {}, {}};
|
return {std::nullopt, true, {}, {}};
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
|
||||||
// Maximum number of steps to follow when traversing a path. May not always
|
// 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
|
// equate to the number of components in a path, depending on the traversal
|
||||||
|
@ -29,7 +29,7 @@ namespace TypePath
|
||||||
Property::Property(std::string name)
|
Property::Property(std::string name)
|
||||||
: name(std::move(name))
|
: name(std::move(name))
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
Property Property::read(std::string name)
|
Property Property::read(std::string name)
|
||||||
|
@ -146,21 +146,21 @@ Path PathBuilder::build()
|
||||||
|
|
||||||
PathBuilder& PathBuilder::readProp(std::string name)
|
PathBuilder& PathBuilder::readProp(std::string name)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||||
components.push_back(Property{std::move(name), true});
|
components.push_back(Property{std::move(name), true});
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PathBuilder& PathBuilder::writeProp(std::string name)
|
PathBuilder& PathBuilder::writeProp(std::string name)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
|
LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
|
||||||
components.push_back(Property{std::move(name), false});
|
components.push_back(Property{std::move(name), false});
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
PathBuilder& PathBuilder::prop(std::string name)
|
PathBuilder& PathBuilder::prop(std::string name)
|
||||||
{
|
{
|
||||||
LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
|
LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
|
||||||
components.push_back(Property{std::move(name)});
|
components.push_back(Property{std::move(name)});
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
@ -323,7 +323,7 @@ struct TraversalState
|
||||||
// logic there.
|
// logic there.
|
||||||
updateCurrent(*m);
|
updateCurrent(*m);
|
||||||
|
|
||||||
if (!traverse(TypePath::Property{"__index"}))
|
if (!traverse(TypePath::Property::read("__index")))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return traverse(property);
|
return traverse(property);
|
||||||
|
@ -333,8 +333,8 @@ struct TraversalState
|
||||||
if (prop)
|
if (prop)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> maybeType;
|
std::optional<TypeId> maybeType;
|
||||||
if (FFlag::DebugLuauReadWriteProperties)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
maybeType = property.isRead ? prop->readType() : prop->writeType();
|
maybeType = property.isRead ? prop->readTy : prop->writeTy;
|
||||||
else
|
else
|
||||||
maybeType = prop->type();
|
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>)
|
if constexpr (std::is_same_v<T, TypePath::Property>)
|
||||||
{
|
{
|
||||||
result << '[';
|
result << '[';
|
||||||
if (FFlag::DebugLuauReadWriteProperties)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
if (c.isRead)
|
if (c.isRead)
|
||||||
result << "read ";
|
result << "read ";
|
||||||
|
|
|
@ -44,6 +44,12 @@ std::optional<TypeId> findMetatableEntry(
|
||||||
|
|
||||||
std::optional<TypeId> findTablePropertyRespectingMeta(
|
std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||||
NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location)
|
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))
|
if (get<AnyType>(ty))
|
||||||
return ty;
|
return ty;
|
||||||
|
@ -52,7 +58,20 @@ std::optional<TypeId> findTablePropertyRespectingMeta(
|
||||||
{
|
{
|
||||||
const auto& it = tableType->props.find(name);
|
const auto& it = tableType->props.find(name);
|
||||||
if (it != tableType->props.end())
|
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);
|
std::optional<TypeId> mtIndex = findMetatableEntry(builtinTypes, errors, ty, "__index", location);
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "Luau/Type.h"
|
#include "Luau/Type.h"
|
||||||
#include "Luau/TypeArena.h"
|
#include "Luau/TypeArena.h"
|
||||||
#include "Luau/TypeCheckLimits.h"
|
#include "Luau/TypeCheckLimits.h"
|
||||||
|
#include "Luau/TypeFamily.h"
|
||||||
#include "Luau/TypeUtils.h"
|
#include "Luau/TypeUtils.h"
|
||||||
#include "Luau/VisitType.h"
|
#include "Luau/VisitType.h"
|
||||||
|
|
||||||
|
@ -19,6 +20,59 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
|
||||||
namespace Luau
|
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)
|
Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice)
|
||||||
: arena(arena)
|
: arena(arena)
|
||||||
, builtinTypes(builtinTypes)
|
, builtinTypes(builtinTypes)
|
||||||
|
@ -34,6 +88,12 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||||
subTy = follow(subTy);
|
subTy = follow(subTy);
|
||||||
superTy = follow(superTy);
|
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}))
|
if (seenTypePairings.contains({subTy, superTy}))
|
||||||
return true;
|
return true;
|
||||||
seenTypePairings.insert({subTy, superTy});
|
seenTypePairings.insert({subTy, superTy});
|
||||||
|
@ -44,14 +104,21 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
|
||||||
FreeType* subFree = getMutable<FreeType>(subTy);
|
FreeType* subFree = getMutable<FreeType>(subTy);
|
||||||
FreeType* superFree = getMutable<FreeType>(superTy);
|
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);
|
subFree->upperBound = mkIntersection(subFree->upperBound, superTy);
|
||||||
expandedFreeTypes[subTy].push_back(superTy);
|
expandedFreeTypes[subTy].push_back(superTy);
|
||||||
}
|
}
|
||||||
|
else if (superFree)
|
||||||
if (superFree)
|
{
|
||||||
superFree->lowerBound = mkUnion(superFree->lowerBound, subTy);
|
superFree->lowerBound = mkUnion(superFree->lowerBound, subTy);
|
||||||
|
}
|
||||||
|
|
||||||
if (subFree || superFree)
|
if (subFree || superFree)
|
||||||
return true;
|
return true;
|
||||||
|
@ -159,13 +226,11 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
|
||||||
|
|
||||||
if (shouldInstantiate)
|
if (shouldInstantiate)
|
||||||
{
|
{
|
||||||
std::optional<TypeId> instantiated = instantiate(builtinTypes, arena, NotNull{&limits}, scope, subTy);
|
for (auto generic : subFn->generics)
|
||||||
if (!instantiated)
|
genericSubstitutions[generic] = freshType(arena, builtinTypes, scope);
|
||||||
return false;
|
|
||||||
|
|
||||||
subFn = get<FunctionType>(*instantiated);
|
for (auto genericPack : subFn->genericPacks)
|
||||||
|
genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);
|
||||||
LUAU_ASSERT(subFn); // instantiation should not make a function type _not_ a function type.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool argResult = unify(superFn->argTypes, subFn->argTypes);
|
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
|
// if the occurs check fails for any option, it fails overall
|
||||||
for (auto subOption : subUnion->options)
|
for (auto subOption : subUnion->options)
|
||||||
result &= unify(subOption, superTy);
|
{
|
||||||
|
if (areCompatible(subOption, superTy))
|
||||||
|
result &= unify(subOption, superTy);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
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
|
// if the occurs check fails for any option, it fails overall
|
||||||
for (auto superOption : superUnion->options)
|
for (auto superOption : superUnion->options)
|
||||||
result &= unify(subTy, superOption);
|
{
|
||||||
|
if (areCompatible(subTy, superOption))
|
||||||
|
result &= unify(subTy, superOption);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -228,7 +299,21 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
|
||||||
auto superPropOpt = superTable->props.find(propName);
|
auto superPropOpt = superTable->props.find(propName);
|
||||||
|
|
||||||
if (superPropOpt != superTable->props.end())
|
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();
|
auto subTypeParamsIter = subTable->instantiatedTypeParams.begin();
|
||||||
|
@ -293,10 +378,19 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
|
||||||
subTp = follow(subTp);
|
subTp = follow(subTp);
|
||||||
superTp = follow(superTp);
|
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}))
|
if (seenTypePackPairings.contains({subTp, superTp}))
|
||||||
return true;
|
return true;
|
||||||
seenTypePackPairings.insert({subTp, superTp});
|
seenTypePackPairings.insert({subTp, superTp});
|
||||||
|
|
||||||
|
if (subTp == superTp)
|
||||||
|
return true;
|
||||||
|
|
||||||
const FreeTypePack* subFree = get<FreeTypePack>(subTp);
|
const FreeTypePack* subFree = get<FreeTypePack>(subTp);
|
||||||
const FreeTypePack* superFree = get<FreeTypePack>(superTp);
|
const FreeTypePack* superFree = get<FreeTypePack>(superTp);
|
||||||
|
|
||||||
|
@ -378,11 +472,14 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
enum
|
enum Polarity
|
||||||
{
|
{
|
||||||
Positive,
|
Positive,
|
||||||
Negative
|
Negative,
|
||||||
} polarity = Positive;
|
Both,
|
||||||
|
};
|
||||||
|
|
||||||
|
Polarity polarity = Positive;
|
||||||
|
|
||||||
void flip()
|
void flip()
|
||||||
{
|
{
|
||||||
|
@ -394,6 +491,8 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
case Negative:
|
case Negative:
|
||||||
polarity = Positive;
|
polarity = Positive;
|
||||||
break;
|
break;
|
||||||
|
case Both:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,6 +518,10 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
case Negative:
|
case Negative:
|
||||||
negativeTypes[ty]++;
|
negativeTypes[ty]++;
|
||||||
break;
|
break;
|
||||||
|
case Both:
|
||||||
|
positiveTypes[ty]++;
|
||||||
|
negativeTypes[ty]++;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -436,10 +539,35 @@ struct FreeTypeSearcher : TypeVisitor
|
||||||
case Negative:
|
case Negative:
|
||||||
negativeTypes[ty]++;
|
negativeTypes[ty]++;
|
||||||
break;
|
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
|
bool visit(TypeId ty, const FunctionType& ft) override
|
||||||
|
@ -538,8 +666,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
|
||||||
if (!ft)
|
if (!ft)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const bool positiveCount = getCount(positiveTypes, ty);
|
const size_t positiveCount = getCount(positiveTypes, ty);
|
||||||
const bool negativeCount = getCount(negativeTypes, ty);
|
const size_t negativeCount = getCount(negativeTypes, ty);
|
||||||
|
|
||||||
if (!positiveCount && !negativeCount)
|
if (!positiveCount && !negativeCount)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -18,6 +18,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||||
// See docs/SyntaxChanges.md for an explanation.
|
// See docs/SyntaxChanges.md for an explanation.
|
||||||
LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
|
LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
|
||||||
LUAU_FASTFLAGVARIABLE(LuauReadWritePropertySyntax, false)
|
LUAU_FASTFLAGVARIABLE(LuauReadWritePropertySyntax, false)
|
||||||
|
LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
|
@ -1339,7 +1340,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
|
||||||
AstTableAccess access = AstTableAccess::ReadWrite;
|
AstTableAccess access = AstTableAccess::ReadWrite;
|
||||||
std::optional<Location> accessLocation;
|
std::optional<Location> accessLocation;
|
||||||
|
|
||||||
if (FFlag::LuauReadWritePropertySyntax)
|
if (FFlag::LuauReadWritePropertySyntax || FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
if (lexer.current().type == Lexeme::Name && lexer.lookahead().type != ':')
|
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)
|
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
|
// Treat '-' as a special file whose source is read from stdin
|
||||||
// All other arguments that start with '-' are skipped
|
// All other arguments that start with '-' are skipped
|
||||||
if (argv[i][0] == '-' && argv[i][1] != '\0')
|
if (argv[i][0] == '-' && argv[i][1] != '\0')
|
||||||
|
|
|
@ -152,7 +152,7 @@ struct Reducer
|
||||||
}
|
}
|
||||||
|
|
||||||
#if VERBOSE >= 1
|
#if VERBOSE >= 1
|
||||||
printf("running %s\n", command.c_str());
|
printf("running %s\n", cmd.c_str());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TestResult result = TestResult::NoBug;
|
TestResult result = TestResult::NoBug;
|
||||||
|
@ -160,7 +160,7 @@ struct Reducer
|
||||||
++step;
|
++step;
|
||||||
printf("Step %4d...\n", step);
|
printf("Step %4d...\n", step);
|
||||||
|
|
||||||
FILE* p = popen(command.c_str(), "r");
|
FILE* p = popen(cmd.c_str(), "r");
|
||||||
|
|
||||||
while (!feof(p))
|
while (!feof(p))
|
||||||
{
|
{
|
||||||
|
|
24
CLI/Repl.cpp
24
CLI/Repl.cpp
|
@ -46,6 +46,8 @@ LUAU_FASTFLAGVARIABLE(LuauUpdatedRequireByStringSemantics, false)
|
||||||
constexpr int MaxTraversalLimit = 50;
|
constexpr int MaxTraversalLimit = 50;
|
||||||
|
|
||||||
static bool codegen = false;
|
static bool codegen = false;
|
||||||
|
static int program_argc = 0;
|
||||||
|
char** program_argv = nullptr;
|
||||||
|
|
||||||
// Ctrl-C handling
|
// Ctrl-C handling
|
||||||
static void sigintCallback(lua_State* L, int gc)
|
static void sigintCallback(lua_State* L, int gc)
|
||||||
|
@ -318,6 +320,12 @@ void setupState(lua_State* L)
|
||||||
luaL_sandbox(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 runCode(lua_State* L, const std::string& source)
|
||||||
{
|
{
|
||||||
std::string bytecode = Luau::compile(source, copts());
|
std::string bytecode = Luau::compile(source, copts());
|
||||||
|
@ -668,7 +676,8 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
|
||||||
if (coverageActive())
|
if (coverageActive())
|
||||||
coverageTrack(L, -1);
|
coverageTrack(L, -1);
|
||||||
|
|
||||||
status = lua_resume(L, NULL, 0);
|
setupArguments(L, program_argc, program_argv);
|
||||||
|
status = lua_resume(L, NULL, program_argc);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -704,7 +713,7 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
|
||||||
|
|
||||||
static void displayHelp(const char* argv0)
|
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("\n");
|
||||||
printf("When file list is omitted, an interactive REPL is started instead.\n");
|
printf("When file list is omitted, an interactive REPL is started instead.\n");
|
||||||
printf("\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(" --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(" --timetrace: record compiler time tracing information into trace.json\n");
|
||||||
printf(" --codegen: execute code using native code generation\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)
|
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 coverage = false;
|
||||||
bool interactive = false;
|
bool interactive = false;
|
||||||
bool codegenPerf = false;
|
bool codegenPerf = false;
|
||||||
|
int program_args = argc;
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++)
|
for (int i = 1; i < argc; i++)
|
||||||
{
|
{
|
||||||
|
@ -800,6 +811,11 @@ int replMain(int argc, char** argv)
|
||||||
{
|
{
|
||||||
setLuauFlags(argv[i] + 9);
|
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] == '-')
|
else if (argv[i][0] == '-')
|
||||||
{
|
{
|
||||||
fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
|
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 !defined(LUAU_ENABLE_TIME_TRACE)
|
||||||
if (FFlag::DebugLuauTimeTracing)
|
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
|
// Current value is based on some member variables being limited to 16 bits
|
||||||
LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockInstructionLimit, 65'536) // 64 K
|
LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockInstructionLimit, 65'536) // 64 K
|
||||||
|
|
||||||
LUAU_FASTFLAGVARIABLE(DisableNativeCodegenIfBreakpointIsSet, false)
|
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
|
@ -302,7 +300,7 @@ void create(lua_State* L, AllocationCallback* allocationCallback, void* allocati
|
||||||
ecb->close = onCloseState;
|
ecb->close = onCloseState;
|
||||||
ecb->destroy = onDestroyFunction;
|
ecb->destroy = onDestroyFunction;
|
||||||
ecb->enter = onEnter;
|
ecb->enter = onEnter;
|
||||||
ecb->disable = FFlag::DisableNativeCodegenIfBreakpointIsSet ? onDisable : nullptr;
|
ecb->disable = onDisable;
|
||||||
}
|
}
|
||||||
|
|
||||||
void create(lua_State* L)
|
void create(lua_State* L)
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include "Luau/IrUtils.h"
|
#include "Luau/IrUtils.h"
|
||||||
|
|
||||||
|
LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodegenTrackingMultilocationFix, false)
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
{
|
{
|
||||||
namespace CodeGen
|
namespace CodeGen
|
||||||
|
@ -159,6 +161,9 @@ void IrValueLocationTracking::afterInstLowering(IrInst& inst, uint32_t instIdx)
|
||||||
case IrCmd::LOAD_DOUBLE:
|
case IrCmd::LOAD_DOUBLE:
|
||||||
case IrCmd::LOAD_INT:
|
case IrCmd::LOAD_INT:
|
||||||
case IrCmd::LOAD_TVALUE:
|
case IrCmd::LOAD_TVALUE:
|
||||||
|
if (DFFlag::LuauCodegenTrackingMultilocationFix && inst.a.kind == IrOpKind::VmReg)
|
||||||
|
invalidateRestoreOp(inst.a, /*skipValueInvalidation*/ false);
|
||||||
|
|
||||||
recordRestoreOp(instIdx, inst.a);
|
recordRestoreOp(instIdx, inst.a);
|
||||||
break;
|
break;
|
||||||
case IrCmd::STORE_POINTER:
|
case IrCmd::STORE_POINTER:
|
||||||
|
|
|
@ -176,6 +176,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/include/Luau/GlobalTypes.h
|
Analysis/include/Luau/GlobalTypes.h
|
||||||
Analysis/include/Luau/InsertionOrderedMap.h
|
Analysis/include/Luau/InsertionOrderedMap.h
|
||||||
Analysis/include/Luau/Instantiation.h
|
Analysis/include/Luau/Instantiation.h
|
||||||
|
Analysis/include/Luau/Instantiation2.h
|
||||||
Analysis/include/Luau/IostreamHelpers.h
|
Analysis/include/Luau/IostreamHelpers.h
|
||||||
Analysis/include/Luau/JsonEmitter.h
|
Analysis/include/Luau/JsonEmitter.h
|
||||||
Analysis/include/Luau/Linter.h
|
Analysis/include/Luau/Linter.h
|
||||||
|
@ -242,6 +243,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||||
Analysis/src/Frontend.cpp
|
Analysis/src/Frontend.cpp
|
||||||
Analysis/src/GlobalTypes.cpp
|
Analysis/src/GlobalTypes.cpp
|
||||||
Analysis/src/Instantiation.cpp
|
Analysis/src/Instantiation.cpp
|
||||||
|
Analysis/src/Instantiation2.cpp
|
||||||
Analysis/src/IostreamHelpers.cpp
|
Analysis/src/IostreamHelpers.cpp
|
||||||
Analysis/src/JsonEmitter.cpp
|
Analysis/src/JsonEmitter.cpp
|
||||||
Analysis/src/Linter.cpp
|
Analysis/src/Linter.cpp
|
||||||
|
@ -457,7 +459,6 @@ if(TARGET Luau.UnitTest)
|
||||||
tests/TypeInfer.primitives.test.cpp
|
tests/TypeInfer.primitives.test.cpp
|
||||||
tests/TypeInfer.provisional.test.cpp
|
tests/TypeInfer.provisional.test.cpp
|
||||||
tests/TypeInfer.refinements.test.cpp
|
tests/TypeInfer.refinements.test.cpp
|
||||||
tests/TypeInfer.rwprops.test.cpp
|
|
||||||
tests/TypeInfer.singletons.test.cpp
|
tests/TypeInfer.singletons.test.cpp
|
||||||
tests/TypeInfer.tables.test.cpp
|
tests/TypeInfer.tables.test.cpp
|
||||||
tests/TypeInfer.test.cpp
|
tests/TypeInfer.test.cpp
|
||||||
|
|
|
@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauSciNumberSkipTrailDot)
|
||||||
LUAU_DYNAMIC_FASTFLAG(LuauInterruptablePatternMatch)
|
LUAU_DYNAMIC_FASTFLAG(LuauInterruptablePatternMatch)
|
||||||
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
|
||||||
LUAU_DYNAMIC_FASTFLAG(LuauCodeGenFixBufferLenCheckA64)
|
LUAU_DYNAMIC_FASTFLAG(LuauCodeGenFixBufferLenCheckA64)
|
||||||
|
LUAU_DYNAMIC_FASTFLAG(LuauCodegenTrackingMultilocationFix)
|
||||||
|
|
||||||
static lua_CompileOptions defaultOptions()
|
static lua_CompileOptions defaultOptions()
|
||||||
{
|
{
|
||||||
|
@ -2040,6 +2041,7 @@ TEST_CASE("SafeEnv")
|
||||||
TEST_CASE("Native")
|
TEST_CASE("Native")
|
||||||
{
|
{
|
||||||
ScopedFastFlag luauCodeGenFixBufferLenCheckA64{DFFlag::LuauCodeGenFixBufferLenCheckA64, true};
|
ScopedFastFlag luauCodeGenFixBufferLenCheckA64{DFFlag::LuauCodeGenFixBufferLenCheckA64, true};
|
||||||
|
ScopedFastFlag luauCodegenTrackingMultilocationFix{DFFlag::LuauCodegenTrackingMultilocationFix, true};
|
||||||
|
|
||||||
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
|
// This tests requires code to run natively, otherwise all 'is_native' checks will fail
|
||||||
if (!codegen || !luau_codegen_supported())
|
if (!codegen || !luau_codegen_supported())
|
||||||
|
|
|
@ -17,7 +17,7 @@ static TypeId requireBinding(Scope* scope, const char* name)
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("ConstraintSolver");
|
TEST_SUITE_BEGIN("ConstraintSolver");
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "hello")
|
TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "constraint_basics")
|
||||||
{
|
{
|
||||||
solve(R"(
|
solve(R"(
|
||||||
local a = 55
|
local a = 55
|
||||||
|
@ -58,12 +58,7 @@ TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "proper_let_generalization")
|
||||||
|
|
||||||
TypeId idType = requireBinding(rootScope, "b");
|
TypeId idType = requireBinding(rootScope, "b");
|
||||||
|
|
||||||
ToStringOptions opts;
|
CHECK("(unknown) -> number" == toString(idType));
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
|
||||||
CHECK("(unknown) -> number" == toString(idType, opts));
|
|
||||||
else
|
|
||||||
CHECK("<a>(a) -> number" == toString(idType, opts));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
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")
|
TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_cyclic_tables_are_not_different")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
local function id<a>(x: a): a
|
local function id<a>(x: a): a
|
||||||
return x
|
return x
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
#include "doctest.h"
|
#include "doctest.h"
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
TEST_SUITE_BEGIN("Linter");
|
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");
|
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")
|
TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInTypeAnnotation")
|
||||||
{
|
{
|
||||||
LintResult result = lint(R"(
|
LintResult result = lint(R"(
|
||||||
|
|
|
@ -940,4 +940,26 @@ TEST_CASE_FIXTURE(NormalizeFixture, "normalize_unknown")
|
||||||
CHECK(toString(normalizer.typeFromNormal(*nt)) == "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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
|
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
|
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
|
|
||||||
namespace Luau
|
namespace Luau
|
||||||
|
@ -65,6 +67,8 @@ struct SubtypeFixture : Fixture
|
||||||
UnifierSharedState sharedState{&ice};
|
UnifierSharedState sharedState{&ice};
|
||||||
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
|
||||||
|
|
||||||
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||||
|
|
||||||
ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)};
|
ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)};
|
||||||
ScopePtr moduleScope{new Scope(rootScope)};
|
ScopePtr moduleScope{new Scope(rootScope)};
|
||||||
|
|
||||||
|
@ -220,6 +224,11 @@ struct SubtypeFixture : Fixture
|
||||||
{"Y", builtinTypes->numberType},
|
{"Y", builtinTypes->numberType},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
TypeId readOnlyVec2Class = cls("ReadOnlyVec2", {
|
||||||
|
{"X", Property::readonly(builtinTypes->numberType)},
|
||||||
|
{"Y", Property::readonly(builtinTypes->numberType)},
|
||||||
|
});
|
||||||
|
|
||||||
// "hello" | "hello"
|
// "hello" | "hello"
|
||||||
TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
|
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}}));
|
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 {} }")
|
TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({}));
|
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));
|
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 }")
|
TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: { lower : (string) -> string }")
|
||||||
{
|
{
|
||||||
CHECK_IS_SUBTYPE(helloType, tableWithLower);
|
CHECK_IS_SUBTYPE(helloType, tableWithLower);
|
||||||
|
@ -1217,8 +1276,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "table_property")
|
||||||
|
|
||||||
SubtypingResult result = isSubtype(subTy, superTy);
|
SubtypingResult result = isSubtype(subTy, superTy);
|
||||||
CHECK(!result.isSubtype);
|
CHECK(!result.isSubtype);
|
||||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{/* subPath */ Path(TypePath::Property("X")),
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")),
|
||||||
/* superPath */ Path(TypePath::Property("X")),
|
/* superPath */ Path(TypePath::Property::read("X")),
|
||||||
/* variance */ SubtypingVariance::Invariant}});
|
/* variance */ SubtypingVariance::Invariant}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1317,8 +1376,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "nested_table_properties")
|
||||||
SubtypingResult result = isSubtype(subTy, superTy);
|
SubtypingResult result = isSubtype(subTy, superTy);
|
||||||
CHECK(!result.isSubtype);
|
CHECK(!result.isSubtype);
|
||||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
||||||
/* subPath */ TypePath::PathBuilder().prop("X").prop("Y").prop("Z").build(),
|
/* subPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(),
|
||||||
/* superPath */ TypePath::PathBuilder().prop("X").prop("Y").prop("Z").build(),
|
/* superPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(),
|
||||||
/* variance */ SubtypingVariance::Invariant,
|
/* 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
|
// metatable is empty, and abort there, without looking at the metatable
|
||||||
// properties (because there aren't any).
|
// properties (because there aren't any).
|
||||||
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
CHECK(result.reasoning == std::vector{SubtypingReasoning{
|
||||||
/* subPath */ TypePath::PathBuilder().mt().prop("__index").build(),
|
/* subPath */ TypePath::PathBuilder().mt().readProp("__index").build(),
|
||||||
/* superPath */ TypePath::kEmpty,
|
/* superPath */ TypePath::kEmpty,
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
@ -1360,12 +1419,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings")
|
||||||
|
|
||||||
SubtypingResult result = isSubtype(subTy, superTy);
|
SubtypingResult result = isSubtype(subTy, superTy);
|
||||||
CHECK(!result.isSubtype);
|
CHECK(!result.isSubtype);
|
||||||
CHECK(result.reasoning == std::vector{
|
CHECK(result.reasoning ==
|
||||||
SubtypingReasoning{/* subPath */ Path(TypePath::Property("X")), /* superPath */ Path(TypePath::Property("X")),
|
std::vector{
|
||||||
/* variance */ SubtypingVariance::Invariant},
|
SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), /* superPath */ Path(TypePath::Property::read("X")),
|
||||||
SubtypingReasoning{/* subPath */ Path(TypePath::Property("Y")), /* superPath */ Path(TypePath::Property("Y")),
|
/* variance */ SubtypingVariance::Invariant},
|
||||||
/* variance */ SubtypingVariance::Invariant},
|
SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("Y")), /* superPath */ Path(TypePath::Property::read("Y")),
|
||||||
});
|
/* variance */ SubtypingVariance::Invariant},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -931,18 +931,17 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array")
|
||||||
TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
--!strict
|
--!strict
|
||||||
function f1() : {a : number, b : string, c : { d : number}}
|
function f1() : {a : number, b : string, c : { d : number}}
|
||||||
return { a = 1, b = "a", c = {d = "a"}}
|
return { a = 1, b = "a", c = {d = "a"}}
|
||||||
end
|
end
|
||||||
|
)");
|
||||||
|
|
||||||
)");
|
std::string expected;
|
||||||
//clang-format off
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
std::string expected =
|
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)";
|
||||||
(FFlag::DebugLuauDeferredConstraintResolution)
|
else
|
||||||
? 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)"
|
expected = R"(Type
|
||||||
:
|
|
||||||
R"(Type
|
|
||||||
'{ a: number, b: string, c: { d: string } }'
|
'{ a: number, b: string, c: { d: string } }'
|
||||||
could not be converted into
|
could not be converted into
|
||||||
'{| a: number, b: string, c: {| d: number |} |}'
|
'{| a: number, b: string, c: {| d: number |} |}'
|
||||||
|
@ -955,7 +954,6 @@ could not be converted into
|
||||||
caused by:
|
caused by:
|
||||||
Property 'd' is not compatible.
|
Property 'd' is not compatible.
|
||||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||||
//clang-format on
|
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
|
@ -984,4 +982,20 @@ local f = abs
|
||||||
TypeId fn = requireType("f");
|
TypeId fn = requireType("f");
|
||||||
CHECK("@checked (number) -> number" == toString(fn));
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -202,7 +202,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(result.errors[0].location == Location{{4, 31}, {4, 44}});
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
CHECK_EQ(expected, toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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(result.errors[0].location == Location{{4, 31}, {4, 52}});
|
||||||
CHECK_EQ(expected, toString(result.errors[0]));
|
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);
|
LUAU_REQUIRE_NO_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
CHECK("{ @metatable { }, A } | { @metatable { }, B }" == toString(requireTypeAlias("X")));
|
||||||
CHECK("{ @metatable {| |}, A } | { @metatable {| |}, B }" == toString(requireTypeAlias("X")));
|
|
||||||
else
|
|
||||||
CHECK("{ @metatable { }, A } | { @metatable { }, B }" == toString(requireTypeAlias("X")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_2_args_overload")
|
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")));
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
|
|
@ -463,7 +463,7 @@ local b: B = a
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
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
|
else
|
||||||
{
|
{
|
||||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -27,6 +27,8 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_function")
|
||||||
local y: number = id(37)
|
local y: number = id(37)
|
||||||
)");
|
)");
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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")
|
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)
|
local y: number = id(37)
|
||||||
)");
|
)");
|
||||||
LUAU_REQUIRE_NO_ERRORS(result);
|
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")
|
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);
|
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}));
|
toString(requireType("x"), {true}));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -437,6 +473,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_leak_generic_types")
|
||||||
-- so this assignment should fail
|
-- so this assignment should fail
|
||||||
local b: boolean = f(true)
|
local b: boolean = f(true)
|
||||||
)");
|
)");
|
||||||
|
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -797,8 +834,9 @@ y.a.c = y
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
CHECK(toString(result.errors.at(0)) ==
|
CHECK(
|
||||||
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>))");
|
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
|
else
|
||||||
{
|
{
|
||||||
const std::string expected = R"(Type 'y' could not be converted into 'T<string>'
|
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")));
|
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")
|
TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -1381,4 +1432,20 @@ TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
|
||||||
REQUIRE(get<UnknownSymbol>(result.errors[1]));
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -414,7 +414,7 @@ local b: B.T = a
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
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
|
else
|
||||||
{
|
{
|
||||||
const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
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
|
else
|
||||||
{
|
{
|
||||||
const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
|
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>
|
type Result<O, E> = Ok<O> | Err<E>
|
||||||
|
|
||||||
local a : Result<string, number> = {success = false, result = "hotdogs"}
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
|
@ -357,7 +357,7 @@ TEST_CASE_FIXTURE(Fixture, "open_table_unification_2")
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
TypeError& err = result.errors[0];
|
TypeError& err = result.errors[0];
|
||||||
MissingProperties* error = get<MissingProperties>(err);
|
MissingProperties* error = get<MissingProperties>(err);
|
||||||
REQUIRE(error != nullptr);
|
REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(err));
|
||||||
REQUIRE(error->properties.size() == 1);
|
REQUIRE(error->properties.size() == 1);
|
||||||
|
|
||||||
CHECK_EQ("y", error->properties[0]);
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
MissingProperties* error = get<MissingProperties>(result.errors[0]);
|
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);
|
REQUIRE(error->properties.size() == 1);
|
||||||
|
|
||||||
CHECK_EQ("baz", error->properties[0]);
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
TypeError& err = result.errors[0];
|
TypeError& err = result.errors[0];
|
||||||
MissingProperties* error = get<MissingProperties>(err);
|
MissingProperties* error = get<MissingProperties>(err);
|
||||||
REQUIRE(error != nullptr);
|
REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(err));
|
||||||
REQUIRE(error->properties.size() == 1);
|
REQUIRE(error->properties.size() == 1);
|
||||||
|
|
||||||
CHECK_EQ("baz", error->properties[0]);
|
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}}));
|
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")
|
TEST_CASE_FIXTURE(Fixture, "table_unification_4")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
|
@ -680,7 +634,8 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too")
|
||||||
|
|
||||||
REQUIRE("number" == toString(indexer.indexType));
|
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")
|
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")
|
TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
|
||||||
|
|
||||||
CheckResult result = check(R"(
|
CheckResult result = check(R"(
|
||||||
type VectorMt = { __add: (Vector, number) -> Vector }
|
type VectorMt = { __add: (Vector, number) -> Vector }
|
||||||
local vectorMt: VectorMt
|
local vectorMt: VectorMt
|
||||||
|
@ -1093,6 +1050,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways")
|
||||||
CHECK_EQ(*requireType("a"), *requireType("c"));
|
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
|
// 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.
|
// As a result, type inference crashed with a stack overflow.
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "unification_of_unions_in_a_self_referential_type")
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
MissingProperties* mp = get<MissingProperties>(result.errors[0]);
|
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);
|
CHECK_EQ(mp->context, MissingProperties::Missing);
|
||||||
REQUIRE_EQ(1, mp->properties.size());
|
REQUIRE_EQ(1, mp->properties.size());
|
||||||
CHECK_EQ(mp->properties[0], "a");
|
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)
|
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||||
{
|
{
|
||||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -1685,7 +1666,7 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
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 }",
|
" at [0], { x: number } is not a subtype of { x: number, y: number, z: number }",
|
||||||
toString(result.errors[0]));
|
toString(result.errors[0]));
|
||||||
}
|
}
|
||||||
|
@ -2176,7 +2157,7 @@ local b: B = a
|
||||||
LUAU_REQUIRE_ERRORS(result);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
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
|
else
|
||||||
{
|
{
|
||||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
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);
|
LUAU_REQUIRE_ERRORS(result);
|
||||||
|
|
||||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
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
|
else
|
||||||
{
|
{
|
||||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
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);
|
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||||
|
|
||||||
std::string expected = "Type 'a' could not be converted into 'T'; at [\"a\"], string is not exactly number"
|
std::string expected = "Type 'a' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
|
||||||
"\n\tat [\"b\"], boolean is not exactly string"
|
"\n\tat [read \"b\"], boolean is not exactly string"
|
||||||
"\n\tat [\"c\"], number is not exactly boolean";
|
"\n\tat [read \"c\"], number is not exactly boolean";
|
||||||
CHECK(toString(result.errors[0]) == expected);
|
CHECK(toString(result.errors[0]) == expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
|
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"(
|
CheckResult result = check(R"(
|
||||||
type W = {read x: number}
|
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);
|
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")
|
TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
|
||||||
{
|
{
|
||||||
CheckResult result = check(R"(
|
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();
|
TEST_SUITE_END();
|
||||||
|
|
|
@ -15,8 +15,19 @@
|
||||||
using namespace Luau;
|
using namespace Luau;
|
||||||
using namespace Luau::TypePath;
|
using namespace Luau::TypePath;
|
||||||
|
|
||||||
|
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
|
||||||
LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
|
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_SUITE_BEGIN("TypePathManipulation");
|
||||||
|
|
||||||
TEST_CASE("append")
|
TEST_CASE("append")
|
||||||
|
@ -95,12 +106,12 @@ TEST_SUITE_BEGIN("TypePathTraversal");
|
||||||
LUAU_REQUIRE_NO_ERRORS(result); \
|
LUAU_REQUIRE_NO_ERRORS(result); \
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "empty_traversal")
|
TEST_CASE_FIXTURE(TypePathFixture, "empty_traversal")
|
||||||
{
|
{
|
||||||
CHECK(traverseForType(builtinTypes->numberType, kEmpty, builtinTypes) == builtinTypes->numberType);
|
CHECK(traverseForType(builtinTypes->numberType, kEmpty, builtinTypes) == builtinTypes->numberType);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "table_property")
|
TEST_CASE_FIXTURE(TypePathFixture, "table_property")
|
||||||
{
|
{
|
||||||
TYPESOLVE_CODE(R"(
|
TYPESOLVE_CODE(R"(
|
||||||
local x = { y = 123 }
|
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);
|
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")
|
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")
|
SUBCASE("unions")
|
||||||
{
|
{
|
||||||
|
@ -242,7 +253,7 @@ TEST_CASE_FIXTURE(ClassFixture, "metatables")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "bounds")
|
TEST_CASE_FIXTURE(TypePathFixture, "bounds")
|
||||||
{
|
{
|
||||||
SUBCASE("free_type")
|
SUBCASE("free_type")
|
||||||
{
|
{
|
||||||
|
@ -274,7 +285,7 @@ TEST_CASE_FIXTURE(Fixture, "bounds")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "indexers")
|
TEST_CASE_FIXTURE(TypePathFixture, "indexers")
|
||||||
{
|
{
|
||||||
SUBCASE("table")
|
SUBCASE("table")
|
||||||
{
|
{
|
||||||
|
@ -308,7 +319,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers")
|
||||||
// TODO: Class types
|
// TODO: Class types
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "negated")
|
TEST_CASE_FIXTURE(TypePathFixture, "negated")
|
||||||
{
|
{
|
||||||
SUBCASE("valid")
|
SUBCASE("valid")
|
||||||
{
|
{
|
||||||
|
@ -327,7 +338,7 @@ TEST_CASE_FIXTURE(Fixture, "negated")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "variadic")
|
TEST_CASE_FIXTURE(TypePathFixture, "variadic")
|
||||||
{
|
{
|
||||||
SUBCASE("valid")
|
SUBCASE("valid")
|
||||||
{
|
{
|
||||||
|
@ -346,7 +357,7 @@ TEST_CASE_FIXTURE(Fixture, "variadic")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "arguments")
|
TEST_CASE_FIXTURE(TypePathFixture, "arguments")
|
||||||
{
|
{
|
||||||
SUBCASE("function")
|
SUBCASE("function")
|
||||||
{
|
{
|
||||||
|
@ -368,7 +379,7 @@ TEST_CASE_FIXTURE(Fixture, "arguments")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "returns")
|
TEST_CASE_FIXTURE(TypePathFixture, "returns")
|
||||||
{
|
{
|
||||||
SUBCASE("function")
|
SUBCASE("function")
|
||||||
{
|
{
|
||||||
|
@ -391,7 +402,7 @@ TEST_CASE_FIXTURE(Fixture, "returns")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(Fixture, "tail")
|
TEST_CASE_FIXTURE(TypePathFixture, "tail")
|
||||||
{
|
{
|
||||||
SUBCASE("has_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
|
// This will fail an occurs check, but it's a quick example of a cyclic type
|
||||||
// where there _is_ no traversal.
|
// 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);
|
ScopedFastInt sfi(DFInt::LuauTypePathMaximumTraverseSteps, 2);
|
||||||
|
|
||||||
|
@ -466,12 +477,12 @@ TEST_CASE_FIXTURE(Fixture, "step_limit")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
TypeId root = requireTypeAlias("T");
|
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);
|
auto result = traverseForType(root, path, builtinTypes);
|
||||||
CHECK(!result);
|
CHECK(!result);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
|
TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains")
|
||||||
{
|
{
|
||||||
SUBCASE("add_metamethod_return_type")
|
SUBCASE("add_metamethod_return_type")
|
||||||
{
|
{
|
||||||
|
@ -484,7 +495,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
TypeId root = requireTypeAlias("Tab");
|
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);
|
auto result = traverseForType(root, path, builtinTypes);
|
||||||
CHECK(result == builtinTypes->numberType);
|
CHECK(result == builtinTypes->numberType);
|
||||||
}
|
}
|
||||||
|
@ -498,7 +509,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
|
||||||
)");
|
)");
|
||||||
|
|
||||||
TypeId root = requireTypeAlias("Obj");
|
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);
|
auto result = traverseForType(root, path, builtinTypes);
|
||||||
CHECK(*result == builtinTypes->falseType);
|
CHECK(*result == builtinTypes->falseType);
|
||||||
}
|
}
|
||||||
|
@ -510,6 +521,10 @@ TEST_SUITE_BEGIN("TypePathToString");
|
||||||
|
|
||||||
TEST_CASE("field")
|
TEST_CASE("field")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff[] = {
|
||||||
|
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||||
|
};
|
||||||
|
|
||||||
CHECK(toString(PathBuilder().prop("foo").build()) == R"(["foo"])");
|
CHECK(toString(PathBuilder().prop("foo").build()) == R"(["foo"])");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,10 +550,26 @@ TEST_CASE("empty_path")
|
||||||
|
|
||||||
TEST_CASE("prop")
|
TEST_CASE("prop")
|
||||||
{
|
{
|
||||||
|
ScopedFastFlag sff[] = {
|
||||||
|
{FFlag::DebugLuauDeferredConstraintResolution, false},
|
||||||
|
};
|
||||||
|
|
||||||
Path p = PathBuilder().prop("foo").build();
|
Path p = PathBuilder().prop("foo").build();
|
||||||
CHECK(p == Path(TypePath::Property{"foo"}));
|
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")
|
TEST_CASE("index")
|
||||||
{
|
{
|
||||||
Path p = PathBuilder().index(0).build();
|
Path p = PathBuilder().index(0).build();
|
||||||
|
@ -561,8 +592,10 @@ TEST_CASE("fields")
|
||||||
|
|
||||||
TEST_CASE("chained")
|
TEST_CASE("chained")
|
||||||
{
|
{
|
||||||
CHECK(PathBuilder().index(0).prop("foo").mt().prop("bar").args().index(1).build() ==
|
ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
|
||||||
Path({Index{0}, TypePath::Property{"foo"}, TypeField::Metatable, TypePath::Property{"bar"}, PackField::Arguments, Index{1}}));
|
|
||||||
|
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
|
TEST_SUITE_END(); // TypePathBuilder
|
||||||
|
|
|
@ -76,13 +76,13 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U")
|
||||||
|
|
||||||
CHECK(u2.unify(left, right));
|
CHECK(u2.unify(left, right));
|
||||||
|
|
||||||
CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(left));
|
CHECK("'a" == toString(left));
|
||||||
CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(right));
|
CHECK("'a" == toString(right));
|
||||||
|
|
||||||
CHECK("never" == toString(freeLeft->lowerBound));
|
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));
|
CHECK("unknown" == toString(freeRight->upperBound));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -293,6 +293,24 @@ end
|
||||||
|
|
||||||
assert(loopIteratorProtocol(0, table.create(100, 5)) == 5058)
|
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)
|
local function vec3compsum(a: vector)
|
||||||
return a.X + a.Y + a.Z
|
return a.X + a.Y + a.Z
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
AnnotationTests.typeof_expr
|
|
||||||
AstQuery.last_argument_function_call_type
|
AstQuery.last_argument_function_call_type
|
||||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||||
AutocompleteTest.anonymous_autofilled_generic_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.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
|
||||||
BuiltinTests.bad_select_should_not_crash
|
BuiltinTests.bad_select_should_not_crash
|
||||||
BuiltinTests.coroutine_resume_anything_goes
|
BuiltinTests.coroutine_resume_anything_goes
|
||||||
BuiltinTests.debug_info_is_crazy
|
|
||||||
BuiltinTests.global_singleton_types_are_sealed
|
BuiltinTests.global_singleton_types_are_sealed
|
||||||
BuiltinTests.gmatch_capture_types
|
BuiltinTests.gmatch_capture_types
|
||||||
BuiltinTests.gmatch_capture_types2
|
BuiltinTests.gmatch_capture_types2
|
||||||
|
@ -67,24 +65,12 @@ DefinitionTests.class_definition_string_props
|
||||||
DefinitionTests.declaring_generic_functions
|
DefinitionTests.declaring_generic_functions
|
||||||
DefinitionTests.definition_file_classes
|
DefinitionTests.definition_file_classes
|
||||||
Differ.equal_generictp_cyclic
|
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
|
||||||
Differ.generictp_normal_2
|
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_left
|
||||||
Differ.metatable_metamissing_right
|
Differ.metatable_metamissing_right
|
||||||
Differ.metatable_metanormal
|
Differ.metatable_metanormal
|
||||||
Differ.negation
|
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.accumulate_cached_errors_in_consistent_order
|
||||||
FrontendTest.environments
|
FrontendTest.environments
|
||||||
FrontendTest.imported_table_modification_2
|
FrontendTest.imported_table_modification_2
|
||||||
|
@ -94,47 +80,37 @@ FrontendTest.trace_requires_in_nonstrict_mode
|
||||||
GenericsTests.apply_type_function_nested_generics1
|
GenericsTests.apply_type_function_nested_generics1
|
||||||
GenericsTests.better_mismatch_error_messages
|
GenericsTests.better_mismatch_error_messages
|
||||||
GenericsTests.bound_tables_do_not_clone_original_fields
|
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.correctly_instantiate_polymorphic_member_functions
|
||||||
GenericsTests.do_not_always_instantiate_generic_intersection_types
|
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.dont_substitute_bound_types
|
||||||
GenericsTests.error_detailed_function_mismatch_generic_pack
|
GenericsTests.error_detailed_function_mismatch_generic_pack
|
||||||
GenericsTests.error_detailed_function_mismatch_generic_types
|
GenericsTests.error_detailed_function_mismatch_generic_types
|
||||||
GenericsTests.factories_of_generics
|
GenericsTests.factories_of_generics
|
||||||
GenericsTests.function_arguments_can_be_polytypes
|
|
||||||
GenericsTests.generic_argument_count_too_few
|
GenericsTests.generic_argument_count_too_few
|
||||||
GenericsTests.generic_argument_count_too_many
|
GenericsTests.generic_argument_count_too_many
|
||||||
GenericsTests.generic_factories
|
GenericsTests.generic_factories
|
||||||
GenericsTests.generic_functions_dont_cache_type_parameters
|
GenericsTests.generic_functions_dont_cache_type_parameters
|
||||||
GenericsTests.generic_functions_in_types
|
GenericsTests.generic_functions_in_types
|
||||||
|
GenericsTests.generic_type_families_work_in_subtyping
|
||||||
GenericsTests.generic_type_pack_parentheses
|
GenericsTests.generic_type_pack_parentheses
|
||||||
GenericsTests.generic_type_pack_unification1
|
GenericsTests.generic_type_pack_unification1
|
||||||
GenericsTests.generic_type_pack_unification2
|
GenericsTests.generic_type_pack_unification2
|
||||||
GenericsTests.generic_type_pack_unification3
|
GenericsTests.generic_type_pack_unification3
|
||||||
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
|
||||||
GenericsTests.hof_subtype_instantiation_regression
|
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_2
|
||||||
GenericsTests.infer_generic_function_function_argument_3
|
GenericsTests.infer_generic_function_function_argument_3
|
||||||
GenericsTests.infer_generic_function_function_argument_overloaded
|
GenericsTests.infer_generic_function_function_argument_overloaded
|
||||||
GenericsTests.infer_generic_lib_function_function_argument
|
GenericsTests.infer_generic_lib_function_function_argument
|
||||||
GenericsTests.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.instantiated_function_argument_names
|
||||||
GenericsTests.local_vars_can_be_polytypes
|
GenericsTests.mutable_state_polymorphism
|
||||||
GenericsTests.no_stack_overflow_from_quantifying
|
GenericsTests.no_stack_overflow_from_quantifying
|
||||||
GenericsTests.properties_can_be_instantiated_polytypes
|
GenericsTests.properties_can_be_instantiated_polytypes
|
||||||
GenericsTests.properties_can_be_polytypes
|
|
||||||
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
|
GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
|
||||||
GenericsTests.rank_N_types_via_typeof
|
|
||||||
GenericsTests.self_recursive_instantiated_param
|
GenericsTests.self_recursive_instantiated_param
|
||||||
GenericsTests.type_parameters_can_be_polytypes
|
GenericsTests.type_parameters_can_be_polytypes
|
||||||
GenericsTests.typefuns_sharing_types
|
GenericsTests.typefuns_sharing_types
|
||||||
|
@ -164,7 +140,6 @@ IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
|
||||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
|
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
|
||||||
IntersectionTypes.table_write_sealed_indirect
|
IntersectionTypes.table_write_sealed_indirect
|
||||||
IntersectionTypes.union_saturate_overloaded_functions
|
IntersectionTypes.union_saturate_overloaded_functions
|
||||||
Linter.DeprecatedApiFenv
|
|
||||||
Linter.FormatStringTyped
|
Linter.FormatStringTyped
|
||||||
Linter.TableOperationsIndexer
|
Linter.TableOperationsIndexer
|
||||||
ModuleTests.clone_self_property
|
ModuleTests.clone_self_property
|
||||||
|
@ -210,16 +185,12 @@ RefinementTest.discriminate_from_isa_of_x
|
||||||
RefinementTest.discriminate_from_truthiness_of_x
|
RefinementTest.discriminate_from_truthiness_of_x
|
||||||
RefinementTest.discriminate_tag
|
RefinementTest.discriminate_tag
|
||||||
RefinementTest.discriminate_tag_with_implicit_else
|
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.fail_to_refine_a_property_of_subscript_expression
|
||||||
RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
|
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.globals_can_be_narrowed_too
|
||||||
RefinementTest.impossible_type_narrow_is_not_an_error
|
RefinementTest.impossible_type_narrow_is_not_an_error
|
||||||
RefinementTest.index_on_a_refined_property
|
RefinementTest.index_on_a_refined_property
|
||||||
RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
|
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.narrow_property_of_a_bounded_variable
|
||||||
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||||
RefinementTest.not_t_or_some_prop_of_t
|
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.array_factory_function
|
||||||
TableTests.casting_tables_with_props_into_table_with_indexer2
|
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_indexer3
|
||||||
TableTests.casting_tables_with_props_into_table_with_indexer4
|
|
||||||
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
|
||||||
TableTests.checked_prop_too_early
|
TableTests.checked_prop_too_early
|
||||||
TableTests.cli_84607_missing_prop_in_array_or_dict
|
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_call_tail
|
||||||
TableTests.common_table_element_union_in_prop
|
TableTests.common_table_element_union_in_prop
|
||||||
TableTests.confusing_indexing
|
TableTests.confusing_indexing
|
||||||
TableTests.cyclic_shifted_tables
|
|
||||||
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
|
||||||
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
|
||||||
TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
TableTests.dont_extend_unsealed_tables_in_rvalue_position
|
||||||
TableTests.dont_leak_free_table_props
|
TableTests.dont_leak_free_table_props
|
||||||
TableTests.dont_quantify_table_that_belongs_to_outer_scope
|
|
||||||
TableTests.dont_suggest_exact_match_keys
|
TableTests.dont_suggest_exact_match_keys
|
||||||
TableTests.error_detailed_indexer_key
|
TableTests.error_detailed_indexer_key
|
||||||
TableTests.error_detailed_indexer_value
|
TableTests.error_detailed_indexer_value
|
||||||
|
@ -275,8 +243,8 @@ TableTests.generalize_table_argument
|
||||||
TableTests.generic_table_instantiation_potential_regression
|
TableTests.generic_table_instantiation_potential_regression
|
||||||
TableTests.indexer_mismatch
|
TableTests.indexer_mismatch
|
||||||
TableTests.indexers_get_quantified_too
|
TableTests.indexers_get_quantified_too
|
||||||
|
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
|
||||||
TableTests.inequality_operators_imply_exactly_matching_types
|
TableTests.inequality_operators_imply_exactly_matching_types
|
||||||
TableTests.infer_indexer_from_array_like_table
|
|
||||||
TableTests.infer_indexer_from_its_variable_type_and_unifiable
|
TableTests.infer_indexer_from_its_variable_type_and_unifiable
|
||||||
TableTests.inferred_return_type_of_free_table
|
TableTests.inferred_return_type_of_free_table
|
||||||
TableTests.instantiate_table_cloning_3
|
TableTests.instantiate_table_cloning_3
|
||||||
|
@ -286,7 +254,7 @@ TableTests.length_operator_intersection
|
||||||
TableTests.length_operator_non_table_union
|
TableTests.length_operator_non_table_union
|
||||||
TableTests.length_operator_union
|
TableTests.length_operator_union
|
||||||
TableTests.less_exponential_blowup_please
|
TableTests.less_exponential_blowup_please
|
||||||
TableTests.meta_add_both_ways
|
TableTests.meta_add
|
||||||
TableTests.meta_add_inferred
|
TableTests.meta_add_inferred
|
||||||
TableTests.metatable_mismatch_should_fail
|
TableTests.metatable_mismatch_should_fail
|
||||||
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
||||||
|
@ -300,8 +268,6 @@ TableTests.oop_polymorphic
|
||||||
TableTests.open_table_unification_2
|
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
|
||||||
TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
|
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.persistent_sealed_table_is_immutable
|
||||||
TableTests.prop_access_on_key_whose_types_mismatches
|
TableTests.prop_access_on_key_whose_types_mismatches
|
||||||
TableTests.prop_access_on_unions_of_indexers_where_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.shared_selfs_through_metatables
|
||||||
TableTests.table_call_metamethod_basic
|
TableTests.table_call_metamethod_basic
|
||||||
TableTests.table_call_metamethod_must_be_callable
|
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_2
|
||||||
TableTests.table_param_width_subtyping_3
|
TableTests.table_param_width_subtyping_3
|
||||||
TableTests.table_simple_call
|
TableTests.table_simple_call
|
||||||
TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
|
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_subtyping_with_missing_props_dont_report_multiple_errors2
|
||||||
TableTests.table_unification_4
|
TableTests.table_unification_4
|
||||||
TableTests.table_unifies_into_map
|
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.result_of_failed_typepack_unification_is_constrained
|
||||||
TryUnifyTests.uninhabited_table_sub_anything
|
TryUnifyTests.uninhabited_table_sub_anything
|
||||||
TryUnifyTests.uninhabited_table_sub_never
|
TryUnifyTests.uninhabited_table_sub_never
|
||||||
TryUnifyTests.variadics_should_use_reversed_properly
|
|
||||||
TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
|
TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
|
||||||
TypeAliases.generic_param_remap
|
TypeAliases.generic_param_remap
|
||||||
TypeAliases.mismatched_generic_type_param
|
TypeAliases.mismatched_generic_type_param
|
||||||
|
@ -376,10 +339,10 @@ TypeFamilyTests.internal_families_raise_errors
|
||||||
TypeFamilyTests.table_internal_families
|
TypeFamilyTests.table_internal_families
|
||||||
TypeFamilyTests.type_families_inhabited_with_normalization
|
TypeFamilyTests.type_families_inhabited_with_normalization
|
||||||
TypeFamilyTests.unsolvable_family
|
TypeFamilyTests.unsolvable_family
|
||||||
|
TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
|
||||||
TypeInfer.bidirectional_checking_of_callback_property
|
TypeInfer.bidirectional_checking_of_callback_property
|
||||||
TypeInfer.check_type_infer_recursion_count
|
TypeInfer.check_type_infer_recursion_count
|
||||||
TypeInfer.checking_should_not_ice
|
TypeInfer.checking_should_not_ice
|
||||||
TypeInfer.cli_39932_use_unifier_in_ensure_methods
|
|
||||||
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
|
||||||
TypeInfer.dont_ice_when_failing_the_occurs_check
|
TypeInfer.dont_ice_when_failing_the_occurs_check
|
||||||
TypeInfer.dont_report_type_errors_within_an_AstExprError
|
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_is_supertype_of_concrete_functions
|
||||||
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
|
||||||
TypeInferFunctions.generic_packs_are_not_variadic
|
TypeInferFunctions.generic_packs_are_not_variadic
|
||||||
TypeInferFunctions.higher_order_function_2
|
|
||||||
TypeInferFunctions.higher_order_function_4
|
TypeInferFunctions.higher_order_function_4
|
||||||
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
|
TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
|
||||||
TypeInferFunctions.improved_function_arg_mismatch_errors
|
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
|
||||||
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
|
TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
|
||||||
TypeInferFunctions.record_matching_overload
|
TypeInferFunctions.record_matching_overload
|
||||||
TypeInferFunctions.regex_benchmark_string_format_minimization
|
|
||||||
TypeInferFunctions.report_exiting_without_return_nonstrict
|
TypeInferFunctions.report_exiting_without_return_nonstrict
|
||||||
TypeInferFunctions.return_type_by_overload
|
TypeInferFunctions.return_type_by_overload
|
||||||
TypeInferFunctions.too_few_arguments_variadic
|
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_incompatible_args_to_iterator
|
||||||
TypeInferLoops.for_in_loop_with_next
|
TypeInferLoops.for_in_loop_with_next
|
||||||
TypeInferLoops.for_in_with_an_iterator_of_type_any
|
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.for_loop
|
||||||
TypeInferLoops.ipairs_produces_integral_indices
|
TypeInferLoops.ipairs_produces_integral_indices
|
||||||
TypeInferLoops.iterate_over_free_table
|
TypeInferLoops.iterate_over_free_table
|
||||||
|
@ -511,7 +470,6 @@ TypeInferLoops.properly_infer_iteratee_is_a_free_table
|
||||||
TypeInferLoops.repeat_loop
|
TypeInferLoops.repeat_loop
|
||||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||||
TypeInferLoops.while_loop
|
TypeInferLoops.while_loop
|
||||||
TypeInferModules.bound_free_table_export_is_ok
|
|
||||||
TypeInferModules.custom_require_global
|
TypeInferModules.custom_require_global
|
||||||
TypeInferModules.do_not_modify_imported_types
|
TypeInferModules.do_not_modify_imported_types
|
||||||
TypeInferModules.do_not_modify_imported_types_5
|
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_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_it_wont_help_2
|
||||||
TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
|
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.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||||
TypeInferOOP.methods_are_topologically_sorted
|
TypeInferOOP.methods_are_topologically_sorted
|
||||||
TypeInferOOP.object_constructor_can_refer_to_method_of_self
|
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
|
||||||
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
|
||||||
TypeInferOperators.typecheck_unary_len_error
|
TypeInferOperators.typecheck_unary_len_error
|
||||||
TypeInferOperators.typecheck_unary_minus
|
|
||||||
TypeInferOperators.typecheck_unary_minus_error
|
TypeInferOperators.typecheck_unary_minus_error
|
||||||
TypeInferOperators.UnknownGlobalCompoundAssign
|
TypeInferOperators.UnknownGlobalCompoundAssign
|
||||||
TypeInferPrimitives.CheckMethodsOfNumber
|
TypeInferPrimitives.CheckMethodsOfNumber
|
||||||
|
@ -564,7 +520,6 @@ TypePackTests.pack_tail_unification_check
|
||||||
TypePackTests.type_alias_backwards_compatible
|
TypePackTests.type_alias_backwards_compatible
|
||||||
TypePackTests.type_alias_default_type_errors
|
TypePackTests.type_alias_default_type_errors
|
||||||
TypePackTests.type_alias_type_packs_import
|
TypePackTests.type_alias_type_packs_import
|
||||||
TypePackTests.type_packs_with_tails_in_vararg_adjustment
|
|
||||||
TypePackTests.unify_variadic_tails_in_arguments
|
TypePackTests.unify_variadic_tails_in_arguments
|
||||||
TypeSingletons.enums_using_singletons_mismatch
|
TypeSingletons.enums_using_singletons_mismatch
|
||||||
TypeSingletons.error_detailed_tagged_union_mismatch_bool
|
TypeSingletons.error_detailed_tagged_union_mismatch_bool
|
||||||
|
|
|
@ -385,7 +385,7 @@ def luau_typepath_property_summary(valobj, internal_dict, options):
|
||||||
read_write = False
|
read_write = False
|
||||||
try:
|
try:
|
||||||
fflag_valobj = valobj.GetFrame().GetValueForVariablePath(
|
fflag_valobj = valobj.GetFrame().GetValueForVariablePath(
|
||||||
"FFlag::DebugLuauReadWriteProperties::value")
|
"FFlag::DebugLuauDeferredConstraintResolution::value")
|
||||||
|
|
||||||
read_write = fflag_valobj.GetValue() == "true"
|
read_write = fflag_valobj.GetValue() == "true"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
@ -107,12 +107,6 @@ def main():
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Write a new faillist.txt after running tests.",
|
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(
|
parser.add_argument(
|
||||||
"--ts",
|
"--ts",
|
||||||
dest="suite",
|
dest="suite",
|
||||||
|
@ -135,8 +129,6 @@ def main():
|
||||||
failList = loadFailList()
|
failList = loadFailList()
|
||||||
|
|
||||||
flags = ["true", "DebugLuauDeferredConstraintResolution"]
|
flags = ["true", "DebugLuauDeferredConstraintResolution"]
|
||||||
if args.rwp:
|
|
||||||
flags.append("DebugLuauReadWriteProperties")
|
|
||||||
|
|
||||||
commandLine = [args.path, "--reporters=xml", "--fflags=" + ",".join(flags)]
|
commandLine = [args.path, "--reporters=xml", "--fflags=" + ",".join(flags)]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue