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