mirror of
https://github.com/luau-lang/luau.git
synced 2024-11-15 06:15:44 +08:00
Sync to upstream/release/599 (#1069)
## What's Changed - Improve POSIX compliance in `CLI/FileUtils.cpp` by @SamuraiCrow #1064 - `AstStat*::hasEnd` is deprecated; use `AstStatBlock::hasEnd` instead - Added a lint for common misuses of the `#` operator - Luau now issues deprecated diagnostics for some uses of `getfenv` and `setfenv` - Fixed a case where we included a trailing space in some error stringifications ### Compiler - Do not do further analysis in O2 on self functions - Improve detection of invalid repeat..until expressions vs continue ### New Type Solver - We now cache subtype test results to improve performance - Improved operator inference mechanics (aka type families) - Further work towards type states - Work towards [new non-strict mode](https://github.com/Roblox/luau/blob/master/rfcs/new-nonstrict.md) continues ### Native Codegen - Instruction last use locations should follow the order in which blocks are lowered - Add a bonus assertion to IrLoweringA64::tempAddr --- Co-authored-by: Arseny Kapoulkine <arseny.kapoulkine@gmail.com> Co-authored-by: Vyacheslav Egorov <vegorov@roblox.com> Co-authored-by: Andy Friesen <afriesen@roblox.com> Co-authored-by: Aaron Weiss <aaronweiss@roblox.com> Co-authored-by: Alexander McCord <amccord@roblox.com> Co-authored-by: Vighnesh Vijay <vvijay@roblox.com>
This commit is contained in:
parent
5c94984935
commit
24fdac4c05
@ -69,6 +69,12 @@ struct ConstraintGraphBuilder
|
||||
// This is null when the CGB is initially constructed.
|
||||
Scope* rootScope;
|
||||
|
||||
// During constraint generation, we only populate the Scope::bindings
|
||||
// property for annotated symbols. Unannotated symbols must be handled in a
|
||||
// postprocessing step because we do not yet have the full breadcrumb graph.
|
||||
// We queue them up here.
|
||||
std::vector<std::tuple<Scope*, Symbol, BreadcrumbId>> inferredBindings;
|
||||
|
||||
// Constraints that go straight to the solver.
|
||||
std::vector<ConstraintPtr> constraints;
|
||||
|
||||
@ -205,8 +211,6 @@ private:
|
||||
Inference check(const ScopePtr& scope, AstExprTable* expr, std::optional<TypeId> expectedType);
|
||||
std::tuple<TypeId, TypeId, RefinementId> checkBinary(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType);
|
||||
|
||||
std::vector<TypeId> checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs);
|
||||
|
||||
TypeId checkLValue(const ScopePtr& scope, AstExpr* expr);
|
||||
TypeId checkLValue(const ScopePtr& scope, AstExprLocal* local);
|
||||
TypeId checkLValue(const ScopePtr& scope, AstExprGlobal* global);
|
||||
@ -303,6 +307,8 @@ private:
|
||||
*/
|
||||
void prepopulateGlobalScope(const ScopePtr& globalScope, AstStatBlock* program);
|
||||
|
||||
void fillInInferredBindings(const ScopePtr& globalScope, AstStatBlock* block);
|
||||
|
||||
/** Given a function type annotation, return a vector describing the expected types of the calls to the function
|
||||
* For example, calling a function with annotation ((number) -> string & ((string) -> number))
|
||||
* yields a vector of size 1, with value: [number | string]
|
||||
|
@ -120,12 +120,12 @@ private:
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprInterpString* i);
|
||||
BreadcrumbId visitExpr(DfgScope* scope, AstExprError* error);
|
||||
|
||||
void visitLValue(DfgScope* scope, AstExpr* e);
|
||||
void visitLValue(DfgScope* scope, AstExprLocal* l);
|
||||
void visitLValue(DfgScope* scope, AstExprGlobal* g);
|
||||
void visitLValue(DfgScope* scope, AstExprIndexName* i);
|
||||
void visitLValue(DfgScope* scope, AstExprIndexExpr* i);
|
||||
void visitLValue(DfgScope* scope, AstExprError* e);
|
||||
void visitLValue(DfgScope* scope, AstExpr* e, BreadcrumbId bc);
|
||||
void visitLValue(DfgScope* scope, AstExprLocal* l, BreadcrumbId bc);
|
||||
void visitLValue(DfgScope* scope, AstExprGlobal* g, BreadcrumbId bc);
|
||||
void visitLValue(DfgScope* scope, AstExprIndexName* i, BreadcrumbId bc);
|
||||
void visitLValue(DfgScope* scope, AstExprIndexExpr* i, BreadcrumbId bc);
|
||||
void visitLValue(DfgScope* scope, AstExprError* e, BreadcrumbId bc);
|
||||
|
||||
void visitType(DfgScope* scope, AstType* t);
|
||||
void visitType(DfgScope* scope, AstTypeReference* r);
|
||||
|
@ -358,13 +358,23 @@ struct PackWhereClauseNeeded
|
||||
bool operator==(const PackWhereClauseNeeded& rhs) const;
|
||||
};
|
||||
|
||||
struct CheckedFunctionCallError
|
||||
{
|
||||
TypeId expected;
|
||||
TypeId passed;
|
||||
std::string checkedFunctionName;
|
||||
// TODO: make this a vector<argumentIndices>
|
||||
size_t argumentIndex;
|
||||
bool operator==(const CheckedFunctionCallError& 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>;
|
||||
UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError>;
|
||||
|
||||
struct TypeErrorSummary
|
||||
{
|
||||
|
@ -245,12 +245,12 @@ public:
|
||||
std::vector<ModuleName> moduleQueue;
|
||||
};
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
TypeCheckLimits limits);
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& globalScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
TypeCheckLimits limits, bool recordJsonLog);
|
||||
|
@ -3,13 +3,17 @@
|
||||
|
||||
#include "Luau/Module.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct BuiltinTypes;
|
||||
struct UnifierSharedState;
|
||||
struct TypeCheckLimits;
|
||||
|
||||
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<const DataFlowGraph> dfg, NotNull<TypeCheckLimits> limits, const SourceModule& sourceModule, Module* module);
|
||||
|
||||
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, Module* module);
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/TypePack.h"
|
||||
#include "Luau/TypePairHash.h"
|
||||
#include "Luau/UnifierSharedState.h"
|
||||
|
||||
#include <vector>
|
||||
@ -28,6 +29,7 @@ struct SubtypingResult
|
||||
bool isSubtype = false;
|
||||
bool isErrorSuppressing = false;
|
||||
bool normalizationTooComplex = false;
|
||||
bool isCacheable = true;
|
||||
|
||||
SubtypingResult& andAlso(const SubtypingResult& other);
|
||||
SubtypingResult& orElse(const SubtypingResult& other);
|
||||
@ -38,6 +40,24 @@ struct SubtypingResult
|
||||
static SubtypingResult any(const std::vector<SubtypingResult>& results);
|
||||
};
|
||||
|
||||
struct SubtypingEnvironment
|
||||
{
|
||||
struct GenericBounds
|
||||
{
|
||||
DenseHashSet<TypeId> lowerBound{nullptr};
|
||||
DenseHashSet<TypeId> upperBound{nullptr};
|
||||
};
|
||||
|
||||
/*
|
||||
* When we encounter a generic over the course of a subtyping test, we need
|
||||
* to tentatively map that generic onto a type on the other side.
|
||||
*/
|
||||
DenseHashMap<TypeId, GenericBounds> mappedGenerics{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
||||
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
|
||||
};
|
||||
|
||||
struct Subtyping
|
||||
{
|
||||
NotNull<BuiltinTypes> builtinTypes;
|
||||
@ -55,29 +75,25 @@ struct Subtyping
|
||||
|
||||
Variance variance = Variance::Covariant;
|
||||
|
||||
struct GenericBounds
|
||||
{
|
||||
DenseHashSet<TypeId> lowerBound{nullptr};
|
||||
DenseHashSet<TypeId> upperBound{nullptr};
|
||||
};
|
||||
|
||||
/*
|
||||
* When we encounter a generic over the course of a subtyping test, we need
|
||||
* to tentatively map that generic onto a type on the other side.
|
||||
*/
|
||||
DenseHashMap<TypeId, GenericBounds> mappedGenerics{nullptr};
|
||||
DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
|
||||
|
||||
using SeenSet = std::unordered_set<std::pair<TypeId, TypeId>, TypeIdPairHash>;
|
||||
|
||||
SeenSet seenTypes;
|
||||
|
||||
Subtyping(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> typeArena, NotNull<Normalizer> normalizer,
|
||||
NotNull<InternalErrorReporter> iceReporter, NotNull<Scope> scope);
|
||||
|
||||
Subtyping(const Subtyping&) = delete;
|
||||
Subtyping& operator=(const Subtyping&) = delete;
|
||||
|
||||
Subtyping(Subtyping&&) = default;
|
||||
Subtyping& operator=(Subtyping&&) = default;
|
||||
|
||||
// Only used by unit tests to test that the cache works.
|
||||
const DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash>& peekCache() const
|
||||
{
|
||||
return resultCache;
|
||||
}
|
||||
|
||||
// TODO cache
|
||||
// TODO cyclic types
|
||||
// TODO recursion limits
|
||||
@ -86,58 +102,63 @@ struct Subtyping
|
||||
SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy);
|
||||
|
||||
private:
|
||||
SubtypingResult isCovariantWith(TypeId subTy, TypeId superTy);
|
||||
SubtypingResult isCovariantWith(TypePackId subTy, TypePackId superTy);
|
||||
DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> resultCache{{}};
|
||||
|
||||
SubtypingResult cache(SubtypingEnvironment& env, SubtypingResult res, TypeId subTy, TypeId superTy);
|
||||
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTy, TypePackId superTy);
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult isContravariantWith(SubTy&& subTy, SuperTy&& superTy);
|
||||
SubtypingResult isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy);
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult isInvariantWith(SubTy&& subTy, SuperTy&& superTy);
|
||||
SubtypingResult isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy);
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult isCovariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult isContravariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||
SubtypingResult isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult isInvariantWith(const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||
SubtypingResult isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair);
|
||||
|
||||
SubtypingResult isCovariantWith(TypeId subTy, const UnionType* superUnion);
|
||||
SubtypingResult isCovariantWith(const UnionType* subUnion, TypeId superTy);
|
||||
SubtypingResult isCovariantWith(TypeId subTy, const IntersectionType* superIntersection);
|
||||
SubtypingResult isCovariantWith(const IntersectionType* subIntersection, TypeId superTy);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy);
|
||||
|
||||
SubtypingResult isCovariantWith(const NegationType* subNegation, TypeId superTy);
|
||||
SubtypingResult isCovariantWith(const TypeId subTy, const NegationType* superNegation);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation);
|
||||
|
||||
SubtypingResult isCovariantWith(const PrimitiveType* subPrim, const PrimitiveType* superPrim);
|
||||
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim);
|
||||
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton);
|
||||
SubtypingResult isCovariantWith(const TableType* subTable, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(const MetatableType* subMt, const MetatableType* superMt);
|
||||
SubtypingResult isCovariantWith(const MetatableType* subMt, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(const ClassType* subClass, const ClassType* superClass);
|
||||
SubtypingResult isCovariantWith(const ClassType* subClass, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction);
|
||||
SubtypingResult isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(const SingletonType* subSingleton, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const PrimitiveType* superPrim);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const SingletonType* superSingleton);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const FunctionType* subFunction, const FunctionType* superFunction);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable);
|
||||
|
||||
SubtypingResult isCovariantWith(const TableIndexer& subIndexer, const TableIndexer& superIndexer);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer);
|
||||
|
||||
SubtypingResult isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm);
|
||||
SubtypingResult isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass);
|
||||
SubtypingResult isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables);
|
||||
SubtypingResult isCovariantWith(const NormalizedStringType& subString, const NormalizedStringType& superString);
|
||||
SubtypingResult isCovariantWith(const NormalizedStringType& subString, const TypeIds& superTables);
|
||||
SubtypingResult isCovariantWith(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction);
|
||||
SubtypingResult isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const TypeIds& superTables);
|
||||
SubtypingResult isCovariantWith(
|
||||
SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes);
|
||||
|
||||
SubtypingResult isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
|
||||
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic);
|
||||
|
||||
bool bindGeneric(TypeId subTp, TypeId superTp);
|
||||
bool bindGeneric(TypePackId subTp, TypePackId superTp);
|
||||
bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp);
|
||||
bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp);
|
||||
|
||||
template<typename T, typename Container>
|
||||
TypeId makeAggregateType(const Container& container, TypeId orElse);
|
||||
|
@ -93,7 +93,7 @@ struct TypeFamily
|
||||
std::string name;
|
||||
|
||||
/// The reducer function for the type family.
|
||||
std::function<TypeFamilyReductionResult<TypeId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeFamilyContext>)> reducer;
|
||||
std::function<TypeFamilyReductionResult<TypeId>(const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)> reducer;
|
||||
};
|
||||
|
||||
/// Represents a type function that may be applied to map a series of types and
|
||||
@ -105,7 +105,7 @@ struct TypePackFamily
|
||||
std::string name;
|
||||
|
||||
/// The reducer function for the type pack family.
|
||||
std::function<TypeFamilyReductionResult<TypePackId>(std::vector<TypeId>, std::vector<TypePackId>, NotNull<TypeFamilyContext>)> reducer;
|
||||
std::function<TypeFamilyReductionResult<TypePackId>(const std::vector<TypeId>&, const std::vector<TypePackId>&, NotNull<TypeFamilyContext>)> reducer;
|
||||
};
|
||||
|
||||
struct FamilyGraphReductionResult
|
||||
@ -149,6 +149,8 @@ struct BuiltinTypeFamilies
|
||||
{
|
||||
BuiltinTypeFamilies();
|
||||
|
||||
TypeFamily notFamily;
|
||||
|
||||
TypeFamily addFamily;
|
||||
TypeFamily subFamily;
|
||||
TypeFamily mulFamily;
|
||||
@ -157,9 +159,15 @@ struct BuiltinTypeFamilies
|
||||
TypeFamily powFamily;
|
||||
TypeFamily modFamily;
|
||||
|
||||
TypeFamily concatFamily;
|
||||
|
||||
TypeFamily andFamily;
|
||||
TypeFamily orFamily;
|
||||
|
||||
TypeFamily ltFamily;
|
||||
TypeFamily leFamily;
|
||||
TypeFamily eqFamily;
|
||||
|
||||
void addToScope(NotNull<TypeArena> arena, NotNull<Scope> scope) const;
|
||||
};
|
||||
|
||||
|
34
Analysis/include/Luau/TypePairHash.h
Normal file
34
Analysis/include/Luau/TypePairHash.h
Normal file
@ -0,0 +1,34 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#pragma once
|
||||
|
||||
#include "Type.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
struct TypePairHash
|
||||
{
|
||||
size_t hashOne(TypeId key) const
|
||||
{
|
||||
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
|
||||
}
|
||||
|
||||
size_t hashOne(TypePackId key) const
|
||||
{
|
||||
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
|
||||
}
|
||||
|
||||
size_t operator()(const std::pair<TypeId, TypeId>& x) const
|
||||
{
|
||||
return hashOne(x.first) ^ (hashOne(x.second) << 1);
|
||||
}
|
||||
|
||||
size_t operator()(const std::pair<TypePackId, TypePackId>& x) const
|
||||
{
|
||||
return hashOne(x.first) ^ (hashOne(x.second) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Luau
|
@ -5,6 +5,7 @@
|
||||
#include "Luau/DenseHash.h"
|
||||
#include "Luau/NotNull.h"
|
||||
#include "Type.h"
|
||||
#include "TypePairHash.h"
|
||||
#include "TypeCheckLimits.h"
|
||||
#include "TypeChecker2.h"
|
||||
|
||||
@ -29,29 +30,6 @@ enum class OccursCheckResult
|
||||
Fail
|
||||
};
|
||||
|
||||
struct TypePairHash
|
||||
{
|
||||
size_t hashOne(Luau::TypeId key) const
|
||||
{
|
||||
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
|
||||
}
|
||||
|
||||
size_t hashOne(Luau::TypePackId key) const
|
||||
{
|
||||
return (uintptr_t(key) >> 4) ^ (uintptr_t(key) >> 9);
|
||||
}
|
||||
|
||||
size_t operator()(const std::pair<Luau::TypeId, Luau::TypeId>& x) const
|
||||
{
|
||||
return hashOne(x.first) ^ (hashOne(x.second) << 1);
|
||||
}
|
||||
|
||||
size_t operator()(const std::pair<Luau::TypePackId, Luau::TypePackId>& x) const
|
||||
{
|
||||
return hashOne(x.first) ^ (hashOne(x.second) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
struct Unifier2
|
||||
{
|
||||
NotNull<TypeArena> arena;
|
||||
|
@ -8,7 +8,8 @@
|
||||
|
||||
#include <math.h>
|
||||
|
||||
LUAU_FASTFLAG(LuauFloorDivision)
|
||||
LUAU_FASTFLAG(LuauFloorDivision);
|
||||
LUAU_FASTFLAG(LuauClipExtraHasEndProps);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -376,7 +377,8 @@ struct AstJsonEncoder : public AstVisitor
|
||||
PROP(body);
|
||||
PROP(functionDepth);
|
||||
PROP(debugname);
|
||||
PROP(hasEnd);
|
||||
if (!FFlag::LuauClipExtraHasEndProps)
|
||||
write("hasEnd", node->DEPRECATED_hasEnd);
|
||||
});
|
||||
}
|
||||
|
||||
@ -574,6 +576,11 @@ struct AstJsonEncoder : public AstVisitor
|
||||
void write(class AstStatBlock* node)
|
||||
{
|
||||
writeNode(node, "AstStatBlock", [&]() {
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
{
|
||||
writeRaw(",\"hasEnd\":");
|
||||
write(node->hasEnd);
|
||||
}
|
||||
writeRaw(",\"body\":[");
|
||||
bool comma = false;
|
||||
for (AstStat* stat : node->body)
|
||||
@ -597,7 +604,8 @@ struct AstJsonEncoder : public AstVisitor
|
||||
if (node->elsebody)
|
||||
PROP(elsebody);
|
||||
write("hasThen", node->thenLocation.has_value());
|
||||
PROP(hasEnd);
|
||||
if (!FFlag::LuauClipExtraHasEndProps)
|
||||
write("hasEnd", node->DEPRECATED_hasEnd);
|
||||
});
|
||||
}
|
||||
|
||||
@ -607,7 +615,8 @@ struct AstJsonEncoder : public AstVisitor
|
||||
PROP(condition);
|
||||
PROP(body);
|
||||
PROP(hasDo);
|
||||
PROP(hasEnd);
|
||||
if (!FFlag::LuauClipExtraHasEndProps)
|
||||
write("hasEnd", node->DEPRECATED_hasEnd);
|
||||
});
|
||||
}
|
||||
|
||||
@ -616,7 +625,8 @@ struct AstJsonEncoder : public AstVisitor
|
||||
writeNode(node, "AstStatRepeat", [&]() {
|
||||
PROP(condition);
|
||||
PROP(body);
|
||||
PROP(hasUntil);
|
||||
if (!FFlag::LuauClipExtraHasEndProps)
|
||||
write("hasUntil", node->DEPRECATED_hasUntil);
|
||||
});
|
||||
}
|
||||
|
||||
@ -662,7 +672,8 @@ struct AstJsonEncoder : public AstVisitor
|
||||
PROP(step);
|
||||
PROP(body);
|
||||
PROP(hasDo);
|
||||
PROP(hasEnd);
|
||||
if (!FFlag::LuauClipExtraHasEndProps)
|
||||
write("hasEnd", node->DEPRECATED_hasEnd);
|
||||
});
|
||||
}
|
||||
|
||||
@ -674,7 +685,8 @@ struct AstJsonEncoder : public AstVisitor
|
||||
PROP(body);
|
||||
PROP(hasIn);
|
||||
PROP(hasDo);
|
||||
PROP(hasEnd);
|
||||
if (!FFlag::LuauClipExtraHasEndProps)
|
||||
write("hasEnd", node->DEPRECATED_hasEnd);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,9 @@
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false)
|
||||
LUAU_FASTFLAG(DebugLuauReadWriteProperties);
|
||||
LUAU_FASTFLAG(LuauClipExtraHasEndProps);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteDoEnd, false);
|
||||
LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);
|
||||
|
||||
static const std::unordered_set<std::string> kStatementStartingKeywords = {
|
||||
@ -1055,22 +1056,56 @@ static AutocompleteEntryMap autocompleteStatement(
|
||||
for (const auto& kw : kStatementStartingKeywords)
|
||||
result.emplace(kw, AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
|
||||
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
{
|
||||
if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstStatIf* statIf = (*it)->as<AstStatIf>(); statIf && !statIf->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
if (FFlag::LuauAutocompleteDoEnd)
|
||||
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
|
||||
{
|
||||
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
|
||||
if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->body->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->body->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstStatIf* statIf = (*it)->as<AstStatIf>())
|
||||
{
|
||||
bool hasEnd = statIf->thenbody->hasEnd;
|
||||
if (statIf->elsebody)
|
||||
{
|
||||
if (AstStatBlock* elseBlock = statIf->elsebody->as<AstStatBlock>())
|
||||
hasEnd = elseBlock->hasEnd;
|
||||
}
|
||||
|
||||
if (!hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->body->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->body->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
if (FFlag::LuauAutocompleteDoEnd)
|
||||
{
|
||||
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto it = ancestry.rbegin(); it != ancestry.rend(); ++it)
|
||||
{
|
||||
if (AstStatForIn* statForIn = (*it)->as<AstStatForIn>(); statForIn && !statForIn->DEPRECATED_hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstStatFor* statFor = (*it)->as<AstStatFor>(); statFor && !statFor->DEPRECATED_hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstStatIf* statIf = (*it)->as<AstStatIf>(); statIf && !statIf->DEPRECATED_hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstStatWhile* statWhile = (*it)->as<AstStatWhile>(); statWhile && !statWhile->DEPRECATED_hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
else if (AstExprFunction* exprFunction = (*it)->as<AstExprFunction>(); exprFunction && !exprFunction->DEPRECATED_hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
if (FFlag::LuauAutocompleteDoEnd)
|
||||
{
|
||||
if (AstStatBlock* exprBlock = (*it)->as<AstStatBlock>(); exprBlock && !exprBlock->hasEnd)
|
||||
result.emplace("end", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1086,8 +1121,16 @@ static AutocompleteEntryMap autocompleteStatement(
|
||||
}
|
||||
}
|
||||
|
||||
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->hasUntil)
|
||||
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
{
|
||||
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->body->hasEnd)
|
||||
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AstStatRepeat* statRepeat = parent->as<AstStatRepeat>(); statRepeat && !statRepeat->DEPRECATED_hasUntil)
|
||||
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
}
|
||||
|
||||
if (ancestry.size() >= 4)
|
||||
@ -1101,8 +1144,16 @@ static AutocompleteEntryMap autocompleteStatement(
|
||||
}
|
||||
}
|
||||
|
||||
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->hasUntil)
|
||||
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
{
|
||||
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->body->hasEnd)
|
||||
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (AstStatRepeat* statRepeat = extractStat<AstStatRepeat>(ancestry); statRepeat && !statRepeat->DEPRECATED_hasUntil)
|
||||
result.emplace("until", AutocompleteEntry{AutocompleteEntryKind::Keyword});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -175,6 +175,8 @@ void ConstraintGraphBuilder::visitModuleRoot(AstStatBlock* block)
|
||||
|
||||
visitBlockWithoutChildScope(scope, block);
|
||||
|
||||
fillInInferredBindings(scope, block);
|
||||
|
||||
if (logger)
|
||||
logger->captureGenerationModule(module);
|
||||
}
|
||||
@ -586,90 +588,41 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStat* stat)
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* local)
|
||||
{
|
||||
std::vector<TypeId> varTypes;
|
||||
std::vector<std::optional<TypeId>> varTypes;
|
||||
varTypes.reserve(local->vars.size);
|
||||
|
||||
std::vector<TypeId> assignees;
|
||||
assignees.reserve(local->vars.size);
|
||||
|
||||
// Used to name the first value type, even if it's not placed in varTypes,
|
||||
// for the purpose of synthetic name attribution.
|
||||
std::optional<TypeId> firstValueType;
|
||||
|
||||
for (AstLocal* local : local->vars)
|
||||
{
|
||||
TypeId ty = nullptr;
|
||||
TypeId assignee = arena->addType(BlockedType{});
|
||||
assignees.push_back(assignee);
|
||||
|
||||
if (!firstValueType)
|
||||
firstValueType = assignee;
|
||||
|
||||
if (local->annotation)
|
||||
ty = resolveType(scope, local->annotation, /* inTypeArguments */ false);
|
||||
|
||||
varTypes.push_back(ty);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < local->values.size; ++i)
|
||||
{
|
||||
AstExpr* value = local->values.data[i];
|
||||
const bool hasAnnotation = i < local->vars.size && nullptr != local->vars.data[i]->annotation;
|
||||
|
||||
if (value->is<AstExprConstantNil>())
|
||||
{
|
||||
// HACK: we leave nil-initialized things floating under the
|
||||
// assumption that they will later be populated.
|
||||
//
|
||||
// See the test TypeInfer/infer_locals_with_nil_value. Better flow
|
||||
// awareness should make this obsolete.
|
||||
TypeId annotationTy = resolveType(scope, local->annotation, /* inTypeArguments */ false);
|
||||
varTypes.push_back(annotationTy);
|
||||
|
||||
if (i < varTypes.size() && !varTypes[i])
|
||||
varTypes[i] = freshType(scope);
|
||||
}
|
||||
// Only function calls and vararg expressions can produce packs. All
|
||||
// other expressions produce exactly one value.
|
||||
else if (i != local->values.size - 1 || (!value->is<AstExprCall>() && !value->is<AstExprVarargs>()))
|
||||
{
|
||||
std::optional<TypeId> expectedType;
|
||||
if (hasAnnotation)
|
||||
expectedType = varTypes.at(i);
|
||||
|
||||
TypeId exprType = check(scope, value, expectedType, /*forceSingleton*/ false, /*generalize*/ true).ty;
|
||||
if (i < varTypes.size())
|
||||
{
|
||||
if (varTypes[i])
|
||||
addConstraint(scope, local->location, SubtypeConstraint{exprType, varTypes[i]});
|
||||
else
|
||||
varTypes[i] = exprType;
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
firstValueType = exprType;
|
||||
addConstraint(scope, local->location, SubtypeConstraint{assignee, annotationTy});
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::optional<TypeId>> expectedTypes;
|
||||
if (hasAnnotation)
|
||||
expectedTypes.insert(begin(expectedTypes), begin(varTypes) + i, end(varTypes));
|
||||
varTypes.push_back(std::nullopt);
|
||||
|
||||
TypePackId exprPack = checkPack(scope, value, expectedTypes, /*generalize*/ true).tp;
|
||||
|
||||
if (i < local->vars.size)
|
||||
{
|
||||
TypePack packTypes = extendTypePack(*arena, builtinTypes, exprPack, varTypes.size() - i);
|
||||
|
||||
// fill out missing values in varTypes with values from exprPack
|
||||
for (size_t j = i; j < varTypes.size(); ++j)
|
||||
{
|
||||
if (!varTypes[j])
|
||||
{
|
||||
if (j - i < packTypes.head.size())
|
||||
varTypes[j] = packTypes.head[j - i];
|
||||
else
|
||||
varTypes[j] = arena->addType(BlockedType{});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TypeId> tailValues{varTypes.begin() + i, varTypes.end()};
|
||||
TypePackId tailPack = arena->addTypePack(std::move(tailValues));
|
||||
addConstraint(scope, local->location, UnpackConstraint{tailPack, exprPack});
|
||||
}
|
||||
}
|
||||
BreadcrumbId bc = dfg->getBreadcrumb(local);
|
||||
scope->lvalueTypes[bc->def] = assignee;
|
||||
}
|
||||
|
||||
TypePackId resultPack = checkPack(scope, local->values, varTypes).tp;
|
||||
addConstraint(scope, local->location, UnpackConstraint{arena->addTypePack(std::move(assignees)), resultPack});
|
||||
|
||||
if (local->vars.size == 1 && local->values.size == 1 && firstValueType && scope.get() == rootScope)
|
||||
{
|
||||
AstLocal* var = local->vars.data[0];
|
||||
@ -691,16 +644,16 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||
AstLocal* l = local->vars.data[i];
|
||||
Location location = l->location;
|
||||
|
||||
if (!varTypes[i])
|
||||
varTypes[i] = freshType(scope);
|
||||
|
||||
scope->bindings[l] = Binding{varTypes[i], location};
|
||||
|
||||
// HACK: In the greedy solver, we say the type state of a variable is the type annotation itself, but
|
||||
// the actual type state is the corresponding initializer expression (if it exists) or nil otherwise.
|
||||
std::optional<TypeId> annotation = varTypes[i];
|
||||
BreadcrumbId bc = dfg->getBreadcrumb(l);
|
||||
scope->lvalueTypes[bc->def] = varTypes[i];
|
||||
scope->rvalueRefinements[bc->def] = varTypes[i];
|
||||
|
||||
if (annotation)
|
||||
scope->bindings[l] = Binding{*annotation, location};
|
||||
else
|
||||
{
|
||||
scope->bindings[l] = Binding{builtinTypes->neverType, location};
|
||||
inferredBindings.emplace_back(scope.get(), l, bc);
|
||||
}
|
||||
}
|
||||
|
||||
if (local->values.size > 0)
|
||||
@ -712,30 +665,32 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatLocal* l
|
||||
if (!call)
|
||||
continue;
|
||||
|
||||
if (auto maybeRequire = matchRequire(*call))
|
||||
auto maybeRequire = matchRequire(*call);
|
||||
if (!maybeRequire)
|
||||
continue;
|
||||
|
||||
AstExpr* require = *maybeRequire;
|
||||
|
||||
auto moduleInfo = moduleResolver->resolveModuleInfo(module->name, *require);
|
||||
if (!moduleInfo)
|
||||
continue;
|
||||
|
||||
ModulePtr module = moduleResolver->getModule(moduleInfo->name);
|
||||
if (!module)
|
||||
continue;
|
||||
|
||||
const Name name{local->vars.data[i]->name.value};
|
||||
scope->importedTypeBindings[name] = module->exportedTypeBindings;
|
||||
scope->importedModules[name] = moduleInfo->name;
|
||||
|
||||
// Imported types of requires that transitively refer to current module have to be replaced with 'any'
|
||||
for (const auto& [location, path] : requireCycles)
|
||||
{
|
||||
AstExpr* require = *maybeRequire;
|
||||
if (path.empty() || path.front() != moduleInfo->name)
|
||||
continue;
|
||||
|
||||
if (auto moduleInfo = moduleResolver->resolveModuleInfo(module->name, *require))
|
||||
{
|
||||
const Name name{local->vars.data[i]->name.value};
|
||||
|
||||
if (ModulePtr module = moduleResolver->getModule(moduleInfo->name))
|
||||
{
|
||||
scope->importedTypeBindings[name] = module->exportedTypeBindings;
|
||||
scope->importedModules[name] = moduleInfo->name;
|
||||
|
||||
// Imported types of requires that transitively refer to current module have to be replaced with 'any'
|
||||
for (const auto& [location, path] : requireCycles)
|
||||
{
|
||||
if (!path.empty() && path.front() == moduleInfo->name)
|
||||
{
|
||||
for (auto& [name, tf] : scope->importedTypeBindings[name])
|
||||
tf = TypeFun{{}, {}, builtinTypes->anyType};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& [name, tf] : scope->importedTypeBindings[name])
|
||||
tf = TypeFun{{}, {}, builtinTypes->anyType};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -781,6 +736,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
|
||||
|
||||
std::vector<TypeId> variableTypes;
|
||||
variableTypes.reserve(forIn->vars.size);
|
||||
|
||||
for (AstLocal* var : forIn->vars)
|
||||
{
|
||||
TypeId ty = nullptr;
|
||||
@ -790,18 +746,18 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
|
||||
ty = freshType(loopScope);
|
||||
|
||||
loopScope->bindings[var] = Binding{ty, var->location};
|
||||
variableTypes.push_back(ty);
|
||||
|
||||
TypeId assignee = arena->addType(BlockedType{});
|
||||
variableTypes.push_back(assignee);
|
||||
|
||||
BreadcrumbId bc = dfg->getBreadcrumb(var);
|
||||
loopScope->lvalueTypes[bc->def] = ty;
|
||||
loopScope->rvalueRefinements[bc->def] = ty;
|
||||
loopScope->lvalueTypes[bc->def] = assignee;
|
||||
}
|
||||
|
||||
// It is always ok to provide too few variables, so we give this pack a free tail.
|
||||
TypePackId variablePack = arena->addTypePack(std::move(variableTypes), freshTypePack(loopScope));
|
||||
|
||||
TypePackId variablePack = arena->addTypePack(std::move(variableTypes));
|
||||
addConstraint(
|
||||
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack, forIn->values.data[0], &module->astForInNextTypes});
|
||||
|
||||
visit(loopScope, forIn->body);
|
||||
|
||||
return ControlFlow::None;
|
||||
@ -1033,24 +989,30 @@ static void bindFreeType(TypeId a, TypeId b)
|
||||
|
||||
ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatAssign* assign)
|
||||
{
|
||||
std::vector<TypeId> varTypes = checkLValues(scope, assign->vars);
|
||||
|
||||
std::vector<std::optional<TypeId>> expectedTypes;
|
||||
expectedTypes.reserve(varTypes.size());
|
||||
expectedTypes.reserve(assign->vars.size);
|
||||
|
||||
for (TypeId ty : varTypes)
|
||||
std::vector<TypeId> assignees;
|
||||
assignees.reserve(assign->vars.size);
|
||||
|
||||
for (AstExpr* lvalue : assign->vars)
|
||||
{
|
||||
ty = follow(ty);
|
||||
if (get<FreeType>(ty))
|
||||
TypeId upperBound = follow(checkLValue(scope, lvalue));
|
||||
if (get<FreeType>(upperBound))
|
||||
expectedTypes.push_back(std::nullopt);
|
||||
else
|
||||
expectedTypes.push_back(ty);
|
||||
expectedTypes.push_back(upperBound);
|
||||
|
||||
TypeId assignee = arena->addType(BlockedType{});
|
||||
assignees.push_back(assignee);
|
||||
addConstraint(scope, lvalue->location, SubtypeConstraint{assignee, upperBound});
|
||||
|
||||
if (NullableBreadcrumbId bc = dfg->getBreadcrumb(lvalue))
|
||||
scope->lvalueTypes[bc->def] = assignee;
|
||||
}
|
||||
|
||||
TypePackId exprPack = checkPack(scope, assign->values, expectedTypes).tp;
|
||||
TypePackId varPack = arena->addTypePack({varTypes});
|
||||
|
||||
addConstraint(scope, assign->location, PackSubtypeConstraint{exprPack, varPack});
|
||||
TypePackId resultPack = checkPack(scope, assign->values, expectedTypes).tp;
|
||||
addConstraint(scope, assign->location, UnpackConstraint{arena->addTypePack(std::move(assignees)), resultPack});
|
||||
|
||||
return ControlFlow::None;
|
||||
}
|
||||
@ -1729,7 +1691,7 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprLocal* loc
|
||||
if (auto ty = scope->lookup(bc->def))
|
||||
return Inference{*ty, refinementArena.proposition(bc, builtinTypes->truthyType)};
|
||||
else
|
||||
ice->ice("AstExprLocal came before its declaration?");
|
||||
ice->ice("CGB: AstExprLocal came before its declaration?");
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprGlobal* global)
|
||||
@ -1837,13 +1799,26 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprFunction*
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprUnary* unary)
|
||||
{
|
||||
auto [operandType, refinement] = check(scope, unary->expr);
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, unary->location, UnaryConstraint{unary->op, operandType, resultType});
|
||||
|
||||
if (unary->op == AstExprUnary::Not)
|
||||
switch (unary->op)
|
||||
{
|
||||
case AstExprUnary::Op::Not:
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.notFamily},
|
||||
{operandType},
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, unary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, refinementArena.negation(refinement)};
|
||||
else
|
||||
}
|
||||
default:
|
||||
{
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
addConstraint(scope, unary->location, UnaryConstraint{unary->op, operandType, resultType});
|
||||
return Inference{resultType};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* binary, std::optional<TypeId> expectedType)
|
||||
@ -1922,6 +1897,16 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* bi
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::Concat:
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.concatFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::And:
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
@ -1942,6 +1927,57 @@ Inference ConstraintGraphBuilder::check(const ScopePtr& scope, AstExprBinary* bi
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareLt:
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.ltFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareGe:
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.ltFamily},
|
||||
{rightType, leftType}, // lua decided that `__ge(a, b)` is instead just `__lt(b, a)`
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareLe:
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.leFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareGt:
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.leFamily},
|
||||
{rightType, leftType}, // lua decided that `__gt(a, b)` is instead just `__le(b, a)`
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
case AstExprBinary::Op::CompareEq:
|
||||
case AstExprBinary::Op::CompareNe:
|
||||
{
|
||||
TypeId resultType = arena->addType(TypeFamilyInstanceType{
|
||||
NotNull{&kBuiltinTypeFamilies.eqFamily},
|
||||
{leftType, rightType},
|
||||
{},
|
||||
});
|
||||
addConstraint(scope, binary->location, ReduceConstraint{resultType});
|
||||
return Inference{resultType, std::move(refinement)};
|
||||
}
|
||||
default:
|
||||
{
|
||||
TypeId resultType = arena->addType(BlockedType{});
|
||||
@ -2099,17 +2135,6 @@ std::tuple<TypeId, TypeId, RefinementId> ConstraintGraphBuilder::checkBinary(
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TypeId> ConstraintGraphBuilder::checkLValues(const ScopePtr& scope, AstArray<AstExpr*> exprs)
|
||||
{
|
||||
std::vector<TypeId> types;
|
||||
types.reserve(exprs.size);
|
||||
|
||||
for (AstExpr* expr : exprs)
|
||||
types.push_back(checkLValue(scope, expr));
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
TypeId ConstraintGraphBuilder::checkLValue(const ScopePtr& scope, AstExpr* expr)
|
||||
{
|
||||
if (auto local = expr->as<AstExprLocal>())
|
||||
@ -2201,6 +2226,7 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
|
||||
return check(scope, expr).ty;
|
||||
|
||||
Symbol sym;
|
||||
NullableBreadcrumbId bc = nullptr;
|
||||
std::vector<std::string> segments;
|
||||
std::vector<AstExpr*> exprs;
|
||||
|
||||
@ -2210,11 +2236,13 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
|
||||
if (auto global = e->as<AstExprGlobal>())
|
||||
{
|
||||
sym = global->name;
|
||||
bc = dfg->getBreadcrumb(global);
|
||||
break;
|
||||
}
|
||||
else if (auto local = e->as<AstExprLocal>())
|
||||
{
|
||||
sym = local->local;
|
||||
bc = dfg->getBreadcrumb(local);
|
||||
break;
|
||||
}
|
||||
else if (auto indexName = e->as<AstExprIndexName>())
|
||||
@ -2251,7 +2279,16 @@ TypeId ConstraintGraphBuilder::updateProperty(const ScopePtr& scope, AstExpr* ex
|
||||
if (!lookupResult)
|
||||
return check(scope, expr).ty;
|
||||
const auto [subjectBinding, symbolScope] = std::move(*lookupResult);
|
||||
TypeId subjectType = subjectBinding->typeId;
|
||||
|
||||
LUAU_ASSERT(bc);
|
||||
std::optional<TypeId> subjectTy = scope->lookup(bc->def);
|
||||
|
||||
/* If we have a breadcrumb but no type, it can only mean that we're setting
|
||||
* a property of some builtin table. This isn't legal, but we still want to
|
||||
* wire up the constraints properly so that we can report why it is not
|
||||
* legal.
|
||||
*/
|
||||
TypeId subjectType = subjectTy.value_or(subjectBinding->typeId);
|
||||
|
||||
TypeId propTy = freshType(scope);
|
||||
|
||||
@ -2502,7 +2539,6 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||
argTy = resolveType(signatureScope, local->annotation, /* inTypeArguments */ false, /* replaceErrorWithFresh*/ true);
|
||||
else
|
||||
{
|
||||
|
||||
if (i < expectedArgPack.head.size())
|
||||
argTy = expectedArgPack.head[i];
|
||||
else
|
||||
@ -2511,7 +2547,15 @@ ConstraintGraphBuilder::FunctionSignature ConstraintGraphBuilder::checkFunctionS
|
||||
|
||||
argTypes.push_back(argTy);
|
||||
argNames.emplace_back(FunctionArgument{local->name.value, local->location});
|
||||
signatureScope->bindings[local] = Binding{argTy, local->location};
|
||||
|
||||
if (local->annotation)
|
||||
signatureScope->bindings[local] = Binding{argTy, local->location};
|
||||
else
|
||||
{
|
||||
BreadcrumbId bc = dfg->getBreadcrumb(local);
|
||||
signatureScope->bindings[local] = Binding{builtinTypes->neverType, local->location};
|
||||
inferredBindings.emplace_back(signatureScope.get(), local, bc);
|
||||
}
|
||||
|
||||
BreadcrumbId bc = dfg->getBreadcrumb(local);
|
||||
signatureScope->lvalueTypes[bc->def] = argTy;
|
||||
@ -3018,6 +3062,37 @@ void ConstraintGraphBuilder::prepopulateGlobalScope(const ScopePtr& globalScope,
|
||||
program->visit(&gp);
|
||||
}
|
||||
|
||||
void ConstraintGraphBuilder::fillInInferredBindings(const ScopePtr& globalScope, AstStatBlock* block)
|
||||
{
|
||||
std::deque<BreadcrumbId> queue;
|
||||
|
||||
for (const auto& [scope, symbol, breadcrumb] : inferredBindings)
|
||||
{
|
||||
LUAU_ASSERT(queue.empty());
|
||||
|
||||
queue.push_back(breadcrumb);
|
||||
|
||||
TypeId ty = builtinTypes->neverType;
|
||||
|
||||
while (!queue.empty())
|
||||
{
|
||||
const BreadcrumbId bc = queue.front();
|
||||
queue.pop_front();
|
||||
|
||||
TypeId* lvalueType = scope->lvalueTypes.find(bc->def);
|
||||
if (!lvalueType)
|
||||
continue;
|
||||
|
||||
ty = simplifyUnion(builtinTypes, arena, ty, *lvalueType).result;
|
||||
|
||||
for (BreadcrumbId child : bc->children)
|
||||
queue.push_back(child);
|
||||
}
|
||||
|
||||
scope->bindings[symbol].typeId = ty;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::optional<TypeId>> ConstraintGraphBuilder::getExpectedCallTypesForFunctionOverloads(const TypeId fnType)
|
||||
{
|
||||
std::vector<TypeId> funTys;
|
||||
|
@ -1785,13 +1785,13 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
|
||||
|
||||
// We know that resultPack does not have a tail, but we don't know if
|
||||
// sourcePack is long enough to fill every value. Replace every remaining
|
||||
// result TypeId with the error recovery type.
|
||||
// result TypeId with `nil`.
|
||||
|
||||
while (destIter != destEnd)
|
||||
{
|
||||
if (isBlocked(*destIter))
|
||||
{
|
||||
asMutable(*destIter)->ty.emplace<BoundType>(builtinTypes->errorRecoveryType());
|
||||
asMutable(*destIter)->ty.emplace<BoundType>(builtinTypes->nilType);
|
||||
unblock(*destIter, constraint->location);
|
||||
}
|
||||
|
||||
@ -2013,57 +2013,26 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
if (get<FreeType>(iteratorTy))
|
||||
return block_(iteratorTy);
|
||||
|
||||
auto anyify = [&](auto ty) {
|
||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack};
|
||||
std::optional anyified = anyify.substitute(ty);
|
||||
if (!anyified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(constraint->scope, constraint->location, *anyified, ty);
|
||||
};
|
||||
|
||||
auto unknownify = [&](auto ty) {
|
||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, builtinTypes->unknownType, builtinTypes->anyTypePack};
|
||||
std::optional anyified = anyify.substitute(ty);
|
||||
if (!anyified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(constraint->scope, constraint->location, *anyified, ty);
|
||||
};
|
||||
|
||||
auto errorify = [&](auto ty) {
|
||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
|
||||
std::optional errorified = anyify.substitute(ty);
|
||||
if (!errorified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(constraint->scope, constraint->location, *errorified, ty);
|
||||
};
|
||||
|
||||
auto neverify = [&](auto ty) {
|
||||
Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, builtinTypes->neverType, builtinTypes->neverTypePack};
|
||||
std::optional neverified = anyify.substitute(ty);
|
||||
if (!neverified)
|
||||
reportError(CodeTooComplex{}, constraint->location);
|
||||
else
|
||||
unify(constraint->scope, constraint->location, *neverified, ty);
|
||||
auto unpack = [&](TypeId ty) {
|
||||
TypePackId variadic = arena->addTypePack(VariadicTypePack{ty});
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, variadic});
|
||||
};
|
||||
|
||||
if (get<AnyType>(iteratorTy))
|
||||
{
|
||||
anyify(c.variables);
|
||||
unpack(builtinTypes->anyType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (get<ErrorType>(iteratorTy))
|
||||
{
|
||||
errorify(c.variables);
|
||||
unpack(builtinTypes->errorType);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (get<NeverType>(iteratorTy))
|
||||
{
|
||||
neverify(c.variables);
|
||||
unpack(builtinTypes->neverType);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2088,7 +2057,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
unify(constraint->scope, constraint->location, c.variables, expectedVariablePack);
|
||||
}
|
||||
else
|
||||
errorify(c.variables);
|
||||
unpack(builtinTypes->errorType);
|
||||
}
|
||||
else if (std::optional<TypeId> iterFn = findMetatableEntry(builtinTypes, errors, iteratorTy, "__iter", Location{}))
|
||||
{
|
||||
@ -2128,7 +2097,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
const TypeId expectedNextTy = arena->addType(FunctionType{nextArgPack, nextRetPack});
|
||||
unify(constraint->scope, constraint->location, *instantiatedNextFn, expectedNextTy);
|
||||
|
||||
pushConstraint(constraint->scope, constraint->location, PackSubtypeConstraint{c.variables, nextRetPack});
|
||||
pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack});
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2154,9 +2123,9 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
else if (auto primitiveTy = get<PrimitiveType>(iteratorTy); primitiveTy && primitiveTy->type == PrimitiveType::Type::Table)
|
||||
unknownify(c.variables);
|
||||
unpack(builtinTypes->unknownType);
|
||||
else
|
||||
errorify(c.variables);
|
||||
unpack(builtinTypes->errorType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/DataFlowGraph.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Breadcrumb.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/Refinement.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
LUAU_FASTFLAG(DebugLuauFreezeArena)
|
||||
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
@ -278,11 +281,12 @@ void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatForIn* f)
|
||||
|
||||
void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatAssign* a)
|
||||
{
|
||||
for (AstExpr* r : a->values)
|
||||
visitExpr(scope, r);
|
||||
|
||||
for (AstExpr* l : a->vars)
|
||||
visitLValue(scope, l);
|
||||
for (size_t i = 0; i < std::max(a->vars.size, a->values.size); ++i)
|
||||
{
|
||||
BreadcrumbId bc = i < a->values.size ? visitExpr(scope, a->values.data[i]) : breadcrumbs->add(nullptr, defs->freshCell());
|
||||
if (i < a->vars.size)
|
||||
visitLValue(scope, a->vars.data[i], bc);
|
||||
}
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatCompoundAssign* c)
|
||||
@ -291,12 +295,11 @@ void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatCompoundAssign* c)
|
||||
// but the `c->var` only has one pointer address, so we need to come up with a way to store both.
|
||||
// For now, it's not important because we don't have type states, but it is going to be important, e.g.
|
||||
//
|
||||
// local a = 5 -- a[1]
|
||||
// a += 5 -- a[2] = a[1] + 5
|
||||
// local a = 5 -- a-1
|
||||
// a += 5 -- a-2 = a-1 + 5
|
||||
//
|
||||
// We can't just visit `c->var` as a rvalue and then separately traverse `c->var` as an lvalue, since that's O(n^2).
|
||||
visitLValue(scope, c->var);
|
||||
visitExpr(scope, c->value);
|
||||
visitLValue(scope, c->var, visitExpr(scope, c->value));
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFunction* f)
|
||||
@ -311,17 +314,14 @@ void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatFunction* f)
|
||||
//
|
||||
// which is evidence that references to variables must be a phi node of all possible definitions,
|
||||
// but for bug compatibility, we'll assume the same thing here.
|
||||
visitLValue(scope, f->name);
|
||||
visitExpr(scope, f->func);
|
||||
visitLValue(scope, f->name, visitExpr(scope, f->func));
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatLocalFunction* l)
|
||||
{
|
||||
BreadcrumbId bc = breadcrumbs->add(nullptr, defs->freshCell());
|
||||
BreadcrumbId bc = visitExpr(scope, l->func);
|
||||
graph.localBreadcrumbs[l->name] = bc;
|
||||
scope->bindings[l->name] = bc;
|
||||
|
||||
visitExpr(scope, l->func);
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visit(DfgScope* scope, AstStatTypeAlias* t)
|
||||
@ -423,7 +423,7 @@ BreadcrumbId DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprLocal* l)
|
||||
{
|
||||
NullableBreadcrumbId breadcrumb = scope->lookup(l->local);
|
||||
if (!breadcrumb)
|
||||
handle->ice("AstExprLocal came before its declaration?");
|
||||
handle->ice("DFG: AstExprLocal came before its declaration?");
|
||||
|
||||
graph.astBreadcrumbs[l] = breadcrumb;
|
||||
return NotNull{breadcrumb};
|
||||
@ -591,81 +591,69 @@ BreadcrumbId DataFlowGraphBuilder::visitExpr(DfgScope* scope, AstExprError* erro
|
||||
return breadcrumbs->add(nullptr, defs->freshCell());
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e)
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExpr* e, BreadcrumbId bc)
|
||||
{
|
||||
if (auto l = e->as<AstExprLocal>())
|
||||
return visitLValue(scope, l);
|
||||
return visitLValue(scope, l, bc);
|
||||
else if (auto g = e->as<AstExprGlobal>())
|
||||
return visitLValue(scope, g);
|
||||
return visitLValue(scope, g, bc);
|
||||
else if (auto i = e->as<AstExprIndexName>())
|
||||
return visitLValue(scope, i);
|
||||
return visitLValue(scope, i, bc);
|
||||
else if (auto i = e->as<AstExprIndexExpr>())
|
||||
return visitLValue(scope, i);
|
||||
return visitLValue(scope, i, bc);
|
||||
else if (auto error = e->as<AstExprError>())
|
||||
{
|
||||
visitExpr(scope, error); // TODO: is this right?
|
||||
return;
|
||||
}
|
||||
return visitLValue(scope, error, bc);
|
||||
else
|
||||
handle->ice("Unknown AstExpr in DataFlowGraphBuilder::visitLValue");
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l)
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprLocal* l, BreadcrumbId bc)
|
||||
{
|
||||
// Bug compatibility: we don't support type states yet, so we need to do this.
|
||||
NullableBreadcrumbId bc = scope->lookup(l->local);
|
||||
LUAU_ASSERT(bc);
|
||||
|
||||
graph.astBreadcrumbs[l] = bc;
|
||||
scope->bindings[l->local] = bc;
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent breadcrumb
|
||||
// as well as the def that was about to be assigned onto this lvalue. However, we want to
|
||||
// copy the metadata so that refinements can be consistent.
|
||||
BreadcrumbId updated = breadcrumbs->add(scope->lookup(l->local), defs->freshCell(), bc->metadata);
|
||||
graph.astBreadcrumbs[l] = updated;
|
||||
scope->bindings[l->local] = updated;
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g)
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprGlobal* g, BreadcrumbId bc)
|
||||
{
|
||||
// Bug compatibility: we don't support type states yet, so we need to do this.
|
||||
NullableBreadcrumbId bc = scope->lookup(g->name);
|
||||
if (!bc)
|
||||
bc = breadcrumbs->add(nullptr, defs->freshCell());
|
||||
|
||||
graph.astBreadcrumbs[g] = bc;
|
||||
scope->bindings[g->name] = bc;
|
||||
// In order to avoid alias tracking, we need to clip the reference to the parent breadcrumb
|
||||
// as well as the def that was about to be assigned onto this lvalue. However, we want to
|
||||
// copy the metadata so that refinements can be consistent.
|
||||
BreadcrumbId updated = breadcrumbs->add(scope->lookup(g->name), defs->freshCell(), bc->metadata);
|
||||
graph.astBreadcrumbs[g] = updated;
|
||||
scope->bindings[g->name] = updated;
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexName* i)
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexName* i, BreadcrumbId bc)
|
||||
{
|
||||
// Bug compatibility: we don't support type states yet, so we need to do this.
|
||||
BreadcrumbId parentBreadcrumb = visitExpr(scope, i->expr);
|
||||
|
||||
std::string key = i->index.value;
|
||||
NullableBreadcrumbId propBreadcrumb = scope->lookup(parentBreadcrumb->def, key);
|
||||
if (!propBreadcrumb)
|
||||
{
|
||||
propBreadcrumb = breadcrumbs->emplace<FieldMetadata>(parentBreadcrumb, defs->freshCell(), key);
|
||||
moduleScope->props[parentBreadcrumb->def][key] = propBreadcrumb;
|
||||
}
|
||||
|
||||
graph.astBreadcrumbs[i] = propBreadcrumb;
|
||||
BreadcrumbId updated = breadcrumbs->add(scope->props[parentBreadcrumb->def][i->index.value], defs->freshCell(), bc->metadata);
|
||||
graph.astBreadcrumbs[i] = updated;
|
||||
scope->props[parentBreadcrumb->def][i->index.value] = updated;
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i)
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprIndexExpr* i, BreadcrumbId bc)
|
||||
{
|
||||
BreadcrumbId parentBreadcrumb = visitExpr(scope, i->expr);
|
||||
visitExpr(scope, i->index);
|
||||
|
||||
if (auto string = i->index->as<AstExprConstantString>())
|
||||
{
|
||||
std::string key{string->value.data, string->value.size};
|
||||
NullableBreadcrumbId propBreadcrumb = scope->lookup(parentBreadcrumb->def, key);
|
||||
if (!propBreadcrumb)
|
||||
{
|
||||
propBreadcrumb = breadcrumbs->add(parentBreadcrumb, parentBreadcrumb->def);
|
||||
moduleScope->props[parentBreadcrumb->def][key] = propBreadcrumb;
|
||||
}
|
||||
|
||||
graph.astBreadcrumbs[i] = propBreadcrumb;
|
||||
BreadcrumbId updated = breadcrumbs->add(scope->props[parentBreadcrumb->def][string->value.data], defs->freshCell(), bc->metadata);
|
||||
graph.astBreadcrumbs[i] = updated;
|
||||
scope->props[parentBreadcrumb->def][string->value.data] = updated;
|
||||
}
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitLValue(DfgScope* scope, AstExprError* error, BreadcrumbId bc)
|
||||
{
|
||||
visitExpr(scope, error);
|
||||
}
|
||||
|
||||
void DataFlowGraphBuilder::visitType(DfgScope* scope, AstType* t)
|
||||
{
|
||||
if (auto r = t->as<AstTypeReference>())
|
||||
|
@ -112,7 +112,7 @@ struct ErrorConverter
|
||||
result += "\ncaused by:\n ";
|
||||
|
||||
if (!tm.reason.empty())
|
||||
result += tm.reason + " \n";
|
||||
result += tm.reason + "\n";
|
||||
|
||||
result += Luau::toString(*tm.error, TypeErrorToStringOptions{fileResolver});
|
||||
}
|
||||
@ -521,6 +521,13 @@ struct ErrorConverter
|
||||
" depends on generic function parameters but does not appear in the function signature; this construct cannot be type-checked at this "
|
||||
"time";
|
||||
}
|
||||
|
||||
std::string operator()(const CheckedFunctionCallError& e) const
|
||||
{
|
||||
// TODO: What happens if checkedFunctionName cannot be found??
|
||||
return "Function '" + e.checkedFunctionName + "' expects '" + toString(e.expected) + "' at argument #" + std::to_string(e.argumentIndex) +
|
||||
", but got '" + Luau::toString(e.passed) + "'";
|
||||
}
|
||||
};
|
||||
|
||||
struct InvalidNameChecker
|
||||
@ -843,6 +850,12 @@ bool PackWhereClauseNeeded::operator==(const PackWhereClauseNeeded& rhs) const
|
||||
return tp == rhs.tp;
|
||||
}
|
||||
|
||||
bool CheckedFunctionCallError::operator==(const CheckedFunctionCallError& rhs) const
|
||||
{
|
||||
return *expected == *rhs.expected && *passed == *rhs.passed && checkedFunctionName == rhs.checkedFunctionName &&
|
||||
argumentIndex == rhs.argumentIndex;
|
||||
}
|
||||
|
||||
std::string toString(const TypeError& error)
|
||||
{
|
||||
return toString(error, TypeErrorToStringOptions{});
|
||||
@ -1009,6 +1022,11 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
|
||||
e.ty = clone(e.ty);
|
||||
else if constexpr (std::is_same_v<T, PackWhereClauseNeeded>)
|
||||
e.tp = clone(e.tp);
|
||||
else if constexpr (std::is_same_v<T, CheckedFunctionCallError>)
|
||||
{
|
||||
e.expected = clone(e.expected);
|
||||
e.passed = clone(e.passed);
|
||||
}
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "Luau/StringUtils.h"
|
||||
#include "Luau/TimeTrace.h"
|
||||
#include "Luau/TypeChecker2.h"
|
||||
#include "Luau/NonStrictTypeChecker.h"
|
||||
#include "Luau/TypeInfer.h"
|
||||
#include "Luau/Variant.h"
|
||||
|
||||
@ -37,7 +38,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauTypecheckLimitControls, false)
|
||||
LUAU_FASTFLAGVARIABLE(CorrectEarlyReturnInMarkDirty, false)
|
||||
LUAU_FASTFLAGVARIABLE(DebugLuauNewNonStrictMode, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -1212,17 +1212,17 @@ const SourceModule* Frontend::getSourceModule(const ModuleName& moduleName) cons
|
||||
return const_cast<Frontend*>(this)->getSourceModule(moduleName);
|
||||
}
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
TypeCheckLimits limits)
|
||||
{
|
||||
const bool recordJsonLog = FFlag::DebugLuauLogSolverToJson;
|
||||
return check(sourceModule, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope, std::move(prepareModuleScope),
|
||||
options, limits, recordJsonLog);
|
||||
return check(sourceModule, mode, requireCycles, builtinTypes, iceHandler, moduleResolver, fileResolver, parentScope,
|
||||
std::move(prepareModuleScope), options, limits, recordJsonLog);
|
||||
}
|
||||
|
||||
ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<RequireCycle>& requireCycles, NotNull<BuiltinTypes> builtinTypes,
|
||||
NotNull<InternalErrorReporter> iceHandler, NotNull<ModuleResolver> moduleResolver, NotNull<FileResolver> fileResolver,
|
||||
const ScopePtr& parentScope, std::function<void(const ModuleName&, const ScopePtr&)> prepareModuleScope, FrontendOptions options,
|
||||
TypeCheckLimits limits, bool recordJsonLog)
|
||||
@ -1303,7 +1303,10 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
||||
}
|
||||
else
|
||||
{
|
||||
Luau::check(builtinTypes, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get());
|
||||
if (mode == Mode::Nonstrict)
|
||||
Luau::checkNonStrict(builtinTypes, iceHandler, NotNull{&unifierState}, NotNull{&dfg}, NotNull{&limits}, sourceModule, result.get());
|
||||
else
|
||||
Luau::check(builtinTypes, NotNull{&unifierState}, NotNull{&limits}, logger.get(), sourceModule, result.get());
|
||||
}
|
||||
|
||||
// It would be nice if we could freeze the arenas before doing type
|
||||
@ -1332,7 +1335,7 @@ ModulePtr check(const SourceModule& sourceModule, const std::vector<RequireCycle
|
||||
ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vector<RequireCycle> requireCycles,
|
||||
std::optional<ScopePtr> environmentScope, bool forAutocomplete, bool recordJsonLog, TypeCheckLimits typeCheckLimits)
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution && mode == Mode::Strict)
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
auto prepareModuleScopeWrap = [this, forAutocomplete](const ModuleName& name, const ScopePtr& scope) {
|
||||
if (prepareModuleScope)
|
||||
@ -1341,7 +1344,7 @@ ModulePtr Frontend::check(const SourceModule& sourceModule, Mode mode, std::vect
|
||||
|
||||
try
|
||||
{
|
||||
return Luau::check(sourceModule, requireCycles, builtinTypes, NotNull{&iceHandler},
|
||||
return Luau::check(sourceModule, mode, requireCycles, builtinTypes, NotNull{&iceHandler},
|
||||
NotNull{forAutocomplete ? &moduleResolverForAutocomplete : &moduleResolver}, NotNull{fileResolver},
|
||||
environmentScope ? *environmentScope : globals.globalScope, prepareModuleScopeWrap, options, typeCheckLimits, recordJsonLog);
|
||||
}
|
||||
|
@ -200,6 +200,9 @@ static void errorToString(std::ostream& stream, const T& err)
|
||||
stream << "WhereClauseNeeded { " << toString(err.ty) << " }";
|
||||
else if constexpr (std::is_same_v<T, PackWhereClauseNeeded>)
|
||||
stream << "PackWhereClauseNeeded { " << toString(err.tp) << " }";
|
||||
else if constexpr (std::is_same_v<T, CheckedFunctionCallError>)
|
||||
stream << "CheckedFunctionCallError { expected = '" << toString(err.expected) << "', passed = '" << toString(err.passed)
|
||||
<< "', checkedFunctionName = " << err.checkedFunctionName << ", argumentIndex = " << std::to_string(err.argumentIndex) << " }";
|
||||
else
|
||||
static_assert(always_false_v<T>, "Non-exhaustive type switch");
|
||||
}
|
||||
|
@ -14,6 +14,9 @@
|
||||
|
||||
LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintDeprecatedFenv, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauLintTableIndexer, false)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
@ -2085,6 +2088,32 @@ private:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExprCall* node) override
|
||||
{
|
||||
// getfenv/setfenv are deprecated, however they are still used in some test frameworks and don't have a great general replacement
|
||||
// for now we warn about the deprecation only when they are used with a numeric first argument; this produces fewer warnings and makes use
|
||||
// of getfenv/setfenv a little more localized
|
||||
if (FFlag::LuauLintDeprecatedFenv && !node->self && node->args.size >= 1)
|
||||
{
|
||||
if (AstExprGlobal* fenv = node->func->as<AstExprGlobal>(); fenv && (fenv->name == "getfenv" || fenv->name == "setfenv"))
|
||||
{
|
||||
AstExpr* level = node->args.data[0];
|
||||
std::optional<TypeId> ty = context->getType(level);
|
||||
|
||||
if ((ty && isNumber(*ty)) || level->is<AstExprConstantNumber>())
|
||||
{
|
||||
// some common uses of getfenv(n) can be replaced by debug.info if the goal is to get the caller's identity
|
||||
const char* suggestion = (fenv->name == "getfenv") ? "; consider using 'debug.info' instead" : "";
|
||||
|
||||
emitWarning(
|
||||
*context, LintWarning::Code_DeprecatedApi, node->location, "Function '%s' is deprecated%s", fenv->name.value, suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void check(AstExprIndexName* node, TypeId ty)
|
||||
{
|
||||
if (const ClassType* cty = get<ClassType>(ty))
|
||||
@ -2154,16 +2183,50 @@ private:
|
||||
{
|
||||
}
|
||||
|
||||
bool visit(AstExprUnary* node) override
|
||||
{
|
||||
if (FFlag::LuauLintTableIndexer && node->op == AstExprUnary::Len)
|
||||
checkIndexer(node, node->expr, "#");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool visit(AstExprCall* node) override
|
||||
{
|
||||
AstExprIndexName* func = node->func->as<AstExprIndexName>();
|
||||
if (!func)
|
||||
return true;
|
||||
if (AstExprGlobal* func = node->func->as<AstExprGlobal>())
|
||||
{
|
||||
if (FFlag::LuauLintTableIndexer && func->name == "ipairs" && node->args.size == 1)
|
||||
checkIndexer(node, node->args.data[0], "ipairs");
|
||||
}
|
||||
else if (AstExprIndexName* func = node->func->as<AstExprIndexName>())
|
||||
{
|
||||
if (AstExprGlobal* tablib = func->expr->as<AstExprGlobal>(); tablib && tablib->name == "table")
|
||||
checkTableCall(node, func);
|
||||
}
|
||||
|
||||
AstExprGlobal* tablib = func->expr->as<AstExprGlobal>();
|
||||
if (!tablib || tablib->name != "table")
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void checkIndexer(AstExpr* node, AstExpr* expr, const char* op)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauLintTableIndexer);
|
||||
|
||||
std::optional<Luau::TypeId> ty = context->getType(expr);
|
||||
if (!ty)
|
||||
return;
|
||||
|
||||
const TableType* tty = get<TableType>(follow(*ty));
|
||||
if (!tty)
|
||||
return;
|
||||
|
||||
if (!tty->indexer && !tty->props.empty() && tty->state != TableState::Generic)
|
||||
emitWarning(*context, LintWarning::Code_TableOperations, node->location, "Using '%s' on a table without an array part is likely a bug", op);
|
||||
else if (tty->indexer && isString(tty->indexer->indexType)) // note: to avoid complexity of subtype tests we just check if the key is a string
|
||||
emitWarning(*context, LintWarning::Code_TableOperations, node->location, "Using '%s' on a table with string keys is likely a bug", op);
|
||||
}
|
||||
|
||||
void checkTableCall(AstExprCall* node, AstExprIndexName* func)
|
||||
{
|
||||
AstExpr** args = node->args.data;
|
||||
|
||||
if (func->index == "insert" && node->args.size == 2)
|
||||
@ -2245,8 +2308,6 @@ private:
|
||||
emitWarning(*context, LintWarning::Code_TableOperations, as->expr->location,
|
||||
"table.create with a table literal will reuse the same object for all elements; consider using a for loop instead");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isConstant(AstExpr* expr, double value)
|
||||
|
@ -1,19 +1,60 @@
|
||||
// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
|
||||
#include "Luau/NonStrictTypeChecker.h"
|
||||
|
||||
#include "Luau/Ast.h"
|
||||
#include "Luau/Common.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Error.h"
|
||||
#include "Luau/TypeArena.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
#include "Luau/Def.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
/* Push a scope onto the end of a stack for the lifetime of the StackPusher instance.
|
||||
* NonStrictTypeChecker uses this to maintain knowledge about which scope encloses every
|
||||
* given AstNode.
|
||||
*/
|
||||
struct StackPusher
|
||||
{
|
||||
std::vector<NotNull<Scope>>* stack;
|
||||
NotNull<Scope> scope;
|
||||
|
||||
explicit StackPusher(std::vector<NotNull<Scope>>& stack, Scope* scope)
|
||||
: stack(&stack)
|
||||
, scope(scope)
|
||||
{
|
||||
stack.push_back(NotNull{scope});
|
||||
}
|
||||
|
||||
~StackPusher()
|
||||
{
|
||||
if (stack)
|
||||
{
|
||||
LUAU_ASSERT(stack->back() == scope);
|
||||
stack->pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
StackPusher(const StackPusher&) = delete;
|
||||
StackPusher&& operator=(const StackPusher&) = delete;
|
||||
|
||||
StackPusher(StackPusher&& other)
|
||||
: stack(std::exchange(other.stack, nullptr))
|
||||
, scope(other.scope)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct NonStrictContext
|
||||
{
|
||||
std::unordered_map<DefId, TypeId> context;
|
||||
std::unordered_map<const Def*, TypeId> context;
|
||||
|
||||
NonStrictContext() = default;
|
||||
|
||||
@ -38,24 +79,14 @@ struct NonStrictContext
|
||||
// TODO: unimplemented
|
||||
}
|
||||
|
||||
std::optional<TypeId> find(const DefId& def)
|
||||
std::optional<TypeId> find(const DefId& def) const
|
||||
{
|
||||
// TODO: unimplemented
|
||||
const Def* d = def.get();
|
||||
auto it = context.find(d);
|
||||
if (it != context.end())
|
||||
return {it->second};
|
||||
return {};
|
||||
}
|
||||
|
||||
// Satisfies means that for a given DefId n, and an actual type t for `n`, t satisfies the context if t <: context[n]
|
||||
// ice if the DefId is not in the context
|
||||
bool satisfies(const DefId& def, TypeId inferredType)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
return false;
|
||||
}
|
||||
|
||||
bool willRunTimeError(const DefId& def, TypeId inferredType)
|
||||
{
|
||||
return satisfies(def, inferredType);
|
||||
}
|
||||
};
|
||||
|
||||
struct NonStrictTypeChecker
|
||||
@ -67,21 +98,341 @@ struct NonStrictTypeChecker
|
||||
Module* module;
|
||||
Normalizer normalizer;
|
||||
Subtyping subtyping;
|
||||
NotNull<const DataFlowGraph> dfg;
|
||||
DenseHashSet<TypeId> noTypeFamilyErrors{nullptr};
|
||||
std::vector<NotNull<Scope>> stack;
|
||||
|
||||
const NotNull<TypeCheckLimits> limits;
|
||||
|
||||
NonStrictTypeChecker(NotNull<BuiltinTypes> builtinTypes, Subtyping subtyping, const NotNull<InternalErrorReporter> ice,
|
||||
NotNull<UnifierSharedState> unifierState, Module* module)
|
||||
NonStrictTypeChecker(NotNull<BuiltinTypes> builtinTypes, const NotNull<InternalErrorReporter> ice, NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<const DataFlowGraph> dfg, NotNull<TypeCheckLimits> limits, Module* module)
|
||||
: builtinTypes(builtinTypes)
|
||||
, ice(ice)
|
||||
, module(module)
|
||||
, normalizer{&arena, builtinTypes, unifierState, /* cache inhabitance */ true}
|
||||
, subtyping{builtinTypes, NotNull{&arena}, NotNull(&normalizer), ice, NotNull{module->getModuleScope().get()}}
|
||||
, dfg(dfg)
|
||||
, limits(limits)
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<StackPusher> pushStack(AstNode* node)
|
||||
{
|
||||
if (Scope** scope = module->astScopes.find(node))
|
||||
return StackPusher{stack, *scope};
|
||||
else
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
TypeId flattenPack(TypePackId pack)
|
||||
{
|
||||
pack = follow(pack);
|
||||
|
||||
if (auto fst = first(pack, /*ignoreHiddenVariadics*/ false))
|
||||
return *fst;
|
||||
else if (auto ftp = get<FreeTypePack>(pack))
|
||||
{
|
||||
TypeId result = arena.addType(FreeType{ftp->scope});
|
||||
TypePackId freeTail = arena.addTypePack(FreeTypePack{ftp->scope});
|
||||
|
||||
TypePack& resultPack = asMutable(pack)->ty.emplace<TypePack>();
|
||||
resultPack.head.assign(1, result);
|
||||
resultPack.tail = freeTail;
|
||||
|
||||
return result;
|
||||
}
|
||||
else if (get<Unifiable::Error>(pack))
|
||||
return builtinTypes->errorRecoveryType();
|
||||
else if (finite(pack) && size(pack) == 0)
|
||||
return builtinTypes->nilType; // `(f())` where `f()` returns no values is coerced into `nil`
|
||||
else
|
||||
ice->ice("flattenPack got a weird pack!");
|
||||
}
|
||||
|
||||
|
||||
TypeId checkForFamilyInhabitance(TypeId instance, Location location)
|
||||
{
|
||||
if (noTypeFamilyErrors.find(instance))
|
||||
return instance;
|
||||
|
||||
ErrorVec errors = reduceFamilies(
|
||||
instance, location, TypeFamilyContext{NotNull{&arena}, builtinTypes, stack.back(), NotNull{&normalizer}, ice, limits}, true)
|
||||
.errors;
|
||||
|
||||
if (errors.empty())
|
||||
noTypeFamilyErrors.insert(instance);
|
||||
// TODO??
|
||||
// if (!isErrorSuppressing(location, instance))
|
||||
// reportErrors(std::move(errors));
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
TypeId lookupType(AstExpr* expr)
|
||||
{
|
||||
TypeId* ty = module->astTypes.find(expr);
|
||||
if (ty)
|
||||
return checkForFamilyInhabitance(follow(*ty), expr->location);
|
||||
|
||||
TypePackId* tp = module->astTypePacks.find(expr);
|
||||
if (tp)
|
||||
return checkForFamilyInhabitance(flattenPack(*tp), expr->location);
|
||||
return builtinTypes->anyType;
|
||||
}
|
||||
|
||||
|
||||
void visit(AstStat* stat)
|
||||
{
|
||||
NonStrictContext fresh{};
|
||||
visit(stat, fresh);
|
||||
}
|
||||
|
||||
void visit(AstStat* stat, NonStrictContext& context)
|
||||
{
|
||||
auto pusher = pushStack(stat);
|
||||
if (auto s = stat->as<AstStatBlock>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatIf>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatWhile>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatRepeat>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatBreak>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatContinue>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatReturn>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatExpr>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatLocal>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatFor>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatForIn>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatAssign>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatCompoundAssign>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatFunction>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatLocalFunction>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatTypeAlias>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatDeclareFunction>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatDeclareGlobal>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatDeclareClass>())
|
||||
return visit(s, context);
|
||||
else if (auto s = stat->as<AstStatError>())
|
||||
return visit(s, context);
|
||||
else
|
||||
LUAU_ASSERT(!"NonStrictTypeChecker encountered an unknown node type");
|
||||
}
|
||||
|
||||
void visit(AstStatBlock* block, NonStrictContext& context)
|
||||
{
|
||||
auto StackPusher = pushStack(block);
|
||||
for (AstStat* statement : block->body)
|
||||
visit(statement, context);
|
||||
}
|
||||
|
||||
void visit(AstStatIf* ifStatement, NonStrictContext& context) {}
|
||||
void visit(AstStatWhile* whileStatement, NonStrictContext& context) {}
|
||||
void visit(AstStatRepeat* repeatStatement, NonStrictContext& context) {}
|
||||
void visit(AstStatBreak* breakStatement, NonStrictContext& context) {}
|
||||
void visit(AstStatContinue* continueStatement, NonStrictContext& context) {}
|
||||
void visit(AstStatReturn* returnStatement, NonStrictContext& context) {}
|
||||
void visit(AstStatExpr* expr, NonStrictContext& context)
|
||||
{
|
||||
visit(expr->expr, context);
|
||||
}
|
||||
void visit(AstStatLocal* local, NonStrictContext& context) {}
|
||||
void visit(AstStatFor* forStatement, NonStrictContext& context) {}
|
||||
void visit(AstStatForIn* forInStatement, NonStrictContext& context) {}
|
||||
void visit(AstStatAssign* assign, NonStrictContext& context) {}
|
||||
void visit(AstStatCompoundAssign* compoundAssign, NonStrictContext& context) {}
|
||||
void visit(AstStatFunction* statFn, NonStrictContext& context) {}
|
||||
void visit(AstStatLocalFunction* localFn, NonStrictContext& context) {}
|
||||
void visit(AstStatTypeAlias* typeAlias, NonStrictContext& context) {}
|
||||
void visit(AstStatDeclareFunction* declFn, NonStrictContext& context) {}
|
||||
void visit(AstStatDeclareGlobal* declGlobal, NonStrictContext& context) {}
|
||||
void visit(AstStatDeclareClass* declClass, NonStrictContext& context) {}
|
||||
void visit(AstStatError* error, NonStrictContext& context) {}
|
||||
|
||||
void visit(AstExpr* expr, NonStrictContext& context)
|
||||
{
|
||||
auto pusher = pushStack(expr);
|
||||
if (auto e = expr->as<AstExprGroup>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprConstantNil>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprConstantBool>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprConstantNumber>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprConstantString>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprLocal>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprGlobal>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprVarargs>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprCall>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprIndexName>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprIndexExpr>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprFunction>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprTable>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprUnary>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprBinary>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprTypeAssertion>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprIfElse>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprInterpString>())
|
||||
return visit(e, context);
|
||||
else if (auto e = expr->as<AstExprError>())
|
||||
return visit(e, context);
|
||||
else
|
||||
LUAU_ASSERT(!"NonStrictTypeChecker encountered an unknown expression type");
|
||||
}
|
||||
|
||||
void visit(AstExprGroup* group, NonStrictContext& context) {}
|
||||
void visit(AstExprConstantNil* expr, NonStrictContext& context) {}
|
||||
void visit(AstExprConstantBool* expr, NonStrictContext& context) {}
|
||||
void visit(AstExprConstantNumber* expr, NonStrictContext& context) {}
|
||||
void visit(AstExprConstantString* expr, NonStrictContext& context) {}
|
||||
void visit(AstExprLocal* local, NonStrictContext& context) {}
|
||||
void visit(AstExprGlobal* global, NonStrictContext& context) {}
|
||||
void visit(AstExprVarargs* global, NonStrictContext& context) {}
|
||||
|
||||
void visit(AstExprCall* call, NonStrictContext& context)
|
||||
{
|
||||
TypeId* originalCallTy = module->astOriginalCallTypes.find(call);
|
||||
if (!originalCallTy)
|
||||
return;
|
||||
|
||||
TypeId fnTy = *originalCallTy;
|
||||
NonStrictContext fresh{};
|
||||
if (auto fn = get<FunctionType>(follow(fnTy)))
|
||||
{
|
||||
if (fn->isCheckedFunction)
|
||||
{
|
||||
// We know fn is a checked function, which means it looks like:
|
||||
// (S1, ... SN) -> T &
|
||||
// (~S1, unknown^N-1) -> error &
|
||||
// (unknown, ~S2, unknown^N-2) -> error
|
||||
// ...
|
||||
// ...
|
||||
// (unknown^N-1, ~S_N) -> error
|
||||
std::vector<TypeId> argTypes;
|
||||
for (TypeId ty : fn->argTypes)
|
||||
argTypes.push_back(ty);
|
||||
// For a checked function, these gotta be the same size
|
||||
LUAU_ASSERT(call->args.size == argTypes.size());
|
||||
for (size_t i = 0; i < call->args.size; i++)
|
||||
{
|
||||
// For example, if the arg is "hi"
|
||||
// The actual arg type is string
|
||||
// The expected arg type is number
|
||||
// The type of the argument in the overload is ~number
|
||||
// We will compare arg and ~number
|
||||
AstExpr* arg = call->args.data[i];
|
||||
TypeId expectedArgType = argTypes[i];
|
||||
NullableBreadcrumbId bc = dfg->getBreadcrumb(arg);
|
||||
// TODO: Cache negations created here!!!
|
||||
// See Jira Ticket: https://roblox.atlassian.net/browse/CLI-87539
|
||||
if (bc)
|
||||
{
|
||||
TypeId runTimeErrorTy = arena.addType(NegationType{expectedArgType});
|
||||
DefId def = bc->def;
|
||||
fresh.context[def.get()] = runTimeErrorTy;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "bad" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the context and now iterate through each of the arguments to the call to find out if we satisfy the types
|
||||
for (size_t i = 0; i < call->args.size; i++)
|
||||
{
|
||||
AstExpr* arg = call->args.data[i];
|
||||
// TODO: pipe in name of checked function to report Error
|
||||
if (auto runTimeFailureType = willRunTimeError(arg, fresh))
|
||||
reportError(CheckedFunctionCallError{argTypes[i], *runTimeFailureType, "", i}, arg->location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void visit(AstExprIndexName* indexName, NonStrictContext& context) {}
|
||||
void visit(AstExprIndexExpr* indexExpr, NonStrictContext& context) {}
|
||||
void visit(AstExprFunction* exprFn, NonStrictContext& context)
|
||||
{
|
||||
auto pusher = pushStack(exprFn);
|
||||
}
|
||||
void visit(AstExprTable* table, NonStrictContext& context) {}
|
||||
void visit(AstExprUnary* unary, NonStrictContext& context) {}
|
||||
void visit(AstExprBinary* binary, NonStrictContext& context) {}
|
||||
void visit(AstExprTypeAssertion* typeAssertion, NonStrictContext& context) {}
|
||||
void visit(AstExprIfElse* ifElse, NonStrictContext& context) {}
|
||||
void visit(AstExprInterpString* interpString, NonStrictContext& context) {}
|
||||
void visit(AstExprError* error, NonStrictContext& context) {}
|
||||
|
||||
void reportError(TypeErrorData data, const Location& location)
|
||||
{
|
||||
module->errors.emplace_back(location, module->name, std::move(data));
|
||||
// TODO: weave in logger here?
|
||||
}
|
||||
|
||||
// If this fragment of the ast will run time error, return the type that causes this
|
||||
std::optional<TypeId> willRunTimeError(AstExpr* fragment, const NonStrictContext& context)
|
||||
{
|
||||
|
||||
if (NullableBreadcrumbId bc = dfg->getBreadcrumb(fragment))
|
||||
{
|
||||
std::optional<TypeId> contextTy = context.find(bc->def);
|
||||
if (contextTy)
|
||||
{
|
||||
|
||||
TypeId actualType = lookupType(fragment);
|
||||
SubtypingResult r = subtyping.isSubtype(actualType, *contextTy);
|
||||
if (r.normalizationTooComplex)
|
||||
reportError(NormalizationTooComplex{}, fragment->location);
|
||||
|
||||
if (!r.isSubtype && !r.isErrorSuppressing)
|
||||
reportError(TypeMismatch{actualType, *contextTy}, fragment->location);
|
||||
|
||||
if (r.isSubtype)
|
||||
return {actualType};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, Module* module)
|
||||
void checkNonStrict(NotNull<BuiltinTypes> builtinTypes, NotNull<InternalErrorReporter> ice, NotNull<UnifierSharedState> unifierState,
|
||||
NotNull<const DataFlowGraph> dfg, NotNull<TypeCheckLimits> limits, const SourceModule& sourceModule, Module* module)
|
||||
{
|
||||
// TODO: unimplemented
|
||||
NonStrictTypeChecker typeChecker{builtinTypes, ice, unifierState, dfg, limits, module};
|
||||
typeChecker.visit(sourceModule.root);
|
||||
unfreeze(module->interfaceTypes);
|
||||
copyErrors(module->errors, module->interfaceTypes, builtinTypes);
|
||||
freeze(module->interfaceTypes);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -50,6 +50,8 @@ SubtypingResult& SubtypingResult::andAlso(const SubtypingResult& other)
|
||||
// `|=` is intentional here, we want to preserve error related flags.
|
||||
isErrorSuppressing |= other.isErrorSuppressing;
|
||||
normalizationTooComplex |= other.normalizationTooComplex;
|
||||
isCacheable &= other.isCacheable;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -58,6 +60,8 @@ SubtypingResult& SubtypingResult::orElse(const SubtypingResult& other)
|
||||
isSubtype |= other.isSubtype;
|
||||
isErrorSuppressing |= other.isErrorSuppressing;
|
||||
normalizationTooComplex |= other.normalizationTooComplex;
|
||||
isCacheable &= other.isCacheable;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -86,14 +90,23 @@ SubtypingResult SubtypingResult::any(const std::vector<SubtypingResult>& results
|
||||
return acc;
|
||||
}
|
||||
|
||||
Subtyping::Subtyping(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> typeArena, NotNull<Normalizer> normalizer,
|
||||
NotNull<InternalErrorReporter> iceReporter, NotNull<Scope> scope)
|
||||
: builtinTypes(builtinTypes)
|
||||
, arena(typeArena)
|
||||
, normalizer(normalizer)
|
||||
, iceReporter(iceReporter)
|
||||
, scope(scope)
|
||||
{
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
||||
{
|
||||
mappedGenerics.clear();
|
||||
mappedGenericPacks.clear();
|
||||
SubtypingEnvironment env;
|
||||
|
||||
SubtypingResult result = isCovariantWith(subTy, superTy);
|
||||
SubtypingResult result = isCovariantWith(env, subTy, superTy);
|
||||
|
||||
for (const auto& [subTy, bounds] : mappedGenerics)
|
||||
for (const auto& [subTy, bounds] : env.mappedGenerics)
|
||||
{
|
||||
const auto& lb = bounds.lowerBound;
|
||||
const auto& ub = bounds.upperBound;
|
||||
@ -122,15 +135,42 @@ SubtypingResult Subtyping::isSubtype(TypeId subTy, TypeId superTy)
|
||||
result.isSubtype = false;
|
||||
}
|
||||
|
||||
result.andAlso(isCovariantWith(lowerBound, upperBound));
|
||||
result.andAlso(isCovariantWith(env, lowerBound, upperBound));
|
||||
}
|
||||
|
||||
/* TODO: We presently don't store subtype test results in the persistent
|
||||
* cache if the left-side type is a generic function.
|
||||
*
|
||||
* The implementation would be a bit tricky and we haven't seen any material
|
||||
* impact on benchmarks.
|
||||
*
|
||||
* What we would want to do is to remember points within the type where
|
||||
* mapped generics are introduced. When all the contingent generics are
|
||||
* introduced at which we're doing the test, we can mark the result as
|
||||
* cacheable.
|
||||
*/
|
||||
|
||||
if (result.isCacheable)
|
||||
resultCache[{subTy, superTy}] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isSubtype(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
return isCovariantWith(subTp, superTp);
|
||||
SubtypingEnvironment env;
|
||||
return isCovariantWith(env, subTp, superTp);
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::cache(SubtypingEnvironment& env, SubtypingResult result, TypeId subTy, TypeId superTy)
|
||||
{
|
||||
const std::pair<TypeId, TypeId> p{subTy, superTy};
|
||||
if (result.isCacheable)
|
||||
resultCache[p] = result;
|
||||
else
|
||||
env.ephemeralCache[p] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace
|
||||
@ -153,11 +193,19 @@ struct SeenSetPopper
|
||||
};
|
||||
} // namespace
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy)
|
||||
{
|
||||
subTy = follow(subTy);
|
||||
superTy = follow(superTy);
|
||||
|
||||
SubtypingResult* cachedResult = resultCache.find({subTy, superTy});
|
||||
if (cachedResult)
|
||||
return *cachedResult;
|
||||
|
||||
cachedResult = env.ephemeralCache.find({subTy, superTy});
|
||||
if (cachedResult)
|
||||
return *cachedResult;
|
||||
|
||||
// TODO: Do we care about returning a proof that this is error-suppressing?
|
||||
// e.g. given `a | error <: a | error` where both operands are pointer equal,
|
||||
// then should it also carry the information that it's error-suppressing?
|
||||
@ -167,7 +215,29 @@ SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy)
|
||||
|
||||
std::pair<TypeId, TypeId> typePair{subTy, superTy};
|
||||
if (!seenTypes.insert(typePair).second)
|
||||
return {true};
|
||||
{
|
||||
/* TODO: Caching results for recursive types is really tricky to think
|
||||
* about.
|
||||
*
|
||||
* We'd like to cache at the outermost level where we encounter the
|
||||
* recursive type, but we do not want to cache interior results that
|
||||
* involve the cycle.
|
||||
*
|
||||
* Presently, we stop at cycles and assume that the subtype check will
|
||||
* succeed because we'll eventually get there if it won't. However, if
|
||||
* that cyclic type turns out not to have the asked-for subtyping
|
||||
* relation, then all the intermediate cached results that were
|
||||
* contingent on that assumption need to be evicted from the cache, or
|
||||
* not entered into the cache, or something.
|
||||
*
|
||||
* For now, we do the conservative thing and refuse to cache anything
|
||||
* that touches a cycle.
|
||||
*/
|
||||
SubtypingResult res;
|
||||
res.isSubtype = true;
|
||||
res.isCacheable = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
SeenSetPopper ssp{&seenTypes, typePair};
|
||||
|
||||
@ -175,31 +245,31 @@ SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy)
|
||||
// tested as though it were its upper bounds. We do not yet support bounded
|
||||
// generics, so the upper bound is always unknown.
|
||||
if (auto subGeneric = get<GenericType>(subTy); subGeneric && subsumes(subGeneric->scope, scope))
|
||||
return isCovariantWith(builtinTypes->unknownType, superTy);
|
||||
return isCovariantWith(env, builtinTypes->unknownType, superTy);
|
||||
if (auto superGeneric = get<GenericType>(superTy); superGeneric && subsumes(superGeneric->scope, scope))
|
||||
return isCovariantWith(subTy, builtinTypes->unknownType);
|
||||
return isCovariantWith(env, subTy, builtinTypes->unknownType);
|
||||
|
||||
SubtypingResult result;
|
||||
|
||||
if (auto subUnion = get<UnionType>(subTy))
|
||||
return isCovariantWith(subUnion, superTy);
|
||||
result = isCovariantWith(env, subUnion, superTy);
|
||||
else if (auto superUnion = get<UnionType>(superTy))
|
||||
return isCovariantWith(subTy, superUnion);
|
||||
result = isCovariantWith(env, subTy, superUnion);
|
||||
else if (auto superIntersection = get<IntersectionType>(superTy))
|
||||
return isCovariantWith(subTy, superIntersection);
|
||||
result = isCovariantWith(env, subTy, superIntersection);
|
||||
else if (auto subIntersection = get<IntersectionType>(subTy))
|
||||
{
|
||||
SubtypingResult result = isCovariantWith(subIntersection, superTy);
|
||||
if (result.isSubtype || result.isErrorSuppressing || result.normalizationTooComplex)
|
||||
return result;
|
||||
else
|
||||
return isCovariantWith(normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
result = isCovariantWith(env, subIntersection, superTy);
|
||||
if (!result.isSubtype && !result.isErrorSuppressing && !result.normalizationTooComplex)
|
||||
result = isCovariantWith(env, normalizer->normalize(subTy), normalizer->normalize(superTy));
|
||||
}
|
||||
else if (get<AnyType>(superTy))
|
||||
return {true}; // This is always true.
|
||||
result = {true};
|
||||
else if (get<AnyType>(subTy))
|
||||
{
|
||||
// any = unknown | error, so we rewrite this to match.
|
||||
// As per TAPL: A | B <: T iff A <: T && B <: T
|
||||
return isCovariantWith(builtinTypes->unknownType, superTy).andAlso(isCovariantWith(builtinTypes->errorType, superTy));
|
||||
result = isCovariantWith(env, builtinTypes->unknownType, superTy).andAlso(isCovariantWith(env, builtinTypes->errorType, superTy));
|
||||
}
|
||||
else if (get<UnknownType>(superTy))
|
||||
{
|
||||
@ -208,57 +278,59 @@ SubtypingResult Subtyping::isCovariantWith(TypeId subTy, TypeId superTy)
|
||||
LUAU_ASSERT(!get<IntersectionType>(subTy)); // TODO: replace with ice.
|
||||
|
||||
bool errorSuppressing = get<ErrorType>(subTy);
|
||||
return {!errorSuppressing, errorSuppressing};
|
||||
result = {!errorSuppressing, errorSuppressing};
|
||||
}
|
||||
else if (get<NeverType>(subTy))
|
||||
return {true};
|
||||
result = {true};
|
||||
else if (get<ErrorType>(superTy))
|
||||
return {false, true};
|
||||
result = {false, true};
|
||||
else if (get<ErrorType>(subTy))
|
||||
return {false, true};
|
||||
result = {false, true};
|
||||
else if (auto p = get2<NegationType, NegationType>(subTy, superTy))
|
||||
return isCovariantWith(p.first->ty, p.second->ty);
|
||||
result = isCovariantWith(env, p.first->ty, p.second->ty);
|
||||
else if (auto subNegation = get<NegationType>(subTy))
|
||||
return isCovariantWith(subNegation, superTy);
|
||||
result = isCovariantWith(env, subNegation, superTy);
|
||||
else if (auto superNegation = get<NegationType>(superTy))
|
||||
return isCovariantWith(subTy, superNegation);
|
||||
result = isCovariantWith(env, subTy, superNegation);
|
||||
else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
|
||||
{
|
||||
bool ok = bindGeneric(subTy, superTy);
|
||||
return {ok};
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
result.isSubtype = ok;
|
||||
result.isCacheable = false;
|
||||
}
|
||||
else if (auto superGeneric = get<GenericType>(superTy); superGeneric && variance == Variance::Contravariant)
|
||||
{
|
||||
bool ok = bindGeneric(subTy, superTy);
|
||||
return {ok};
|
||||
bool ok = bindGeneric(env, subTy, superTy);
|
||||
result.isSubtype = ok;
|
||||
result.isCacheable = false;
|
||||
}
|
||||
else if (auto p = get2<PrimitiveType, PrimitiveType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<SingletonType, PrimitiveType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<SingletonType, SingletonType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<FunctionType, FunctionType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<TableType, TableType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<MetatableType, MetatableType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<MetatableType, TableType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<ClassType, ClassType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<ClassType, TableType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<PrimitiveType, TableType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
else if (auto p = get2<SingletonType, TableType>(subTy, superTy))
|
||||
return isCovariantWith(p);
|
||||
result = isCovariantWith(env, p);
|
||||
|
||||
return {false};
|
||||
return cache(env, result, subTy, superTy);
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
subTp = follow(subTp);
|
||||
superTp = follow(superTp);
|
||||
@ -278,7 +350,7 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
|
||||
for (size_t i = 0; i < headSize; ++i)
|
||||
{
|
||||
results.push_back(isCovariantWith(subHead[i], superHead[i]));
|
||||
results.push_back(isCovariantWith(env, subHead[i], superHead[i]));
|
||||
if (!results.back().isSubtype)
|
||||
return {false};
|
||||
}
|
||||
@ -292,7 +364,7 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
if (auto vt = get<VariadicTypePack>(*subTail))
|
||||
{
|
||||
for (size_t i = headSize; i < superHead.size(); ++i)
|
||||
results.push_back(isCovariantWith(vt->ty, superHead[i]));
|
||||
results.push_back(isCovariantWith(env, vt->ty, superHead[i]));
|
||||
}
|
||||
else if (auto gt = get<GenericTypePack>(*subTail))
|
||||
{
|
||||
@ -306,10 +378,10 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
std::vector<TypeId> headSlice(begin(superHead), begin(superHead) + headSize);
|
||||
TypePackId superTailPack = arena->addTypePack(std::move(headSlice), superTail);
|
||||
|
||||
if (TypePackId* other = mappedGenericPacks.find(*subTail))
|
||||
results.push_back(isCovariantWith(*other, superTailPack));
|
||||
if (TypePackId* other = env.mappedGenericPacks.find(*subTail))
|
||||
results.push_back(isCovariantWith(env, *other, superTailPack));
|
||||
else
|
||||
mappedGenericPacks.try_insert(*subTail, superTailPack);
|
||||
env.mappedGenericPacks.try_insert(*subTail, superTailPack);
|
||||
|
||||
// FIXME? Not a fan of the early return here. It makes the
|
||||
// control flow harder to reason about.
|
||||
@ -337,7 +409,7 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
if (auto vt = get<VariadicTypePack>(*superTail))
|
||||
{
|
||||
for (size_t i = headSize; i < subHead.size(); ++i)
|
||||
results.push_back(isCovariantWith(subHead[i], vt->ty));
|
||||
results.push_back(isCovariantWith(env, subHead[i], vt->ty));
|
||||
}
|
||||
else if (auto gt = get<GenericTypePack>(*superTail))
|
||||
{
|
||||
@ -351,10 +423,10 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
std::vector<TypeId> headSlice(begin(subHead), begin(subHead) + headSize);
|
||||
TypePackId subTailPack = arena->addTypePack(std::move(headSlice), subTail);
|
||||
|
||||
if (TypePackId* other = mappedGenericPacks.find(*superTail))
|
||||
results.push_back(isCovariantWith(*other, subTailPack));
|
||||
if (TypePackId* other = env.mappedGenericPacks.find(*superTail))
|
||||
results.push_back(isCovariantWith(env, *other, subTailPack));
|
||||
else
|
||||
mappedGenericPacks.try_insert(*superTail, subTailPack);
|
||||
env.mappedGenericPacks.try_insert(*superTail, subTailPack);
|
||||
|
||||
// FIXME? Not a fan of the early return here. It makes the
|
||||
// control flow harder to reason about.
|
||||
@ -381,11 +453,11 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
if (auto p = get2<VariadicTypePack, VariadicTypePack>(*subTail, *superTail))
|
||||
{
|
||||
results.push_back(isCovariantWith(p));
|
||||
results.push_back(isCovariantWith(env, p));
|
||||
}
|
||||
else if (auto p = get2<GenericTypePack, GenericTypePack>(*subTail, *superTail))
|
||||
{
|
||||
bool ok = bindGeneric(*subTail, *superTail);
|
||||
bool ok = bindGeneric(env, *subTail, *superTail);
|
||||
results.push_back({ok});
|
||||
}
|
||||
else if (get2<VariadicTypePack, GenericTypePack>(*subTail, *superTail))
|
||||
@ -393,7 +465,7 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
if (variance == Variance::Contravariant)
|
||||
{
|
||||
// <A...>(A...) -> number <: (...number) -> number
|
||||
bool ok = bindGeneric(*subTail, *superTail);
|
||||
bool ok = bindGeneric(env, *subTail, *superTail);
|
||||
results.push_back({ok});
|
||||
}
|
||||
else
|
||||
@ -412,7 +484,7 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
else
|
||||
{
|
||||
// <A...>() -> A... <: () -> ...number
|
||||
bool ok = bindGeneric(*subTail, *superTail);
|
||||
bool ok = bindGeneric(env, *subTail, *superTail);
|
||||
results.push_back({ok});
|
||||
}
|
||||
}
|
||||
@ -428,7 +500,7 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
}
|
||||
else if (get<GenericTypePack>(*subTail))
|
||||
{
|
||||
bool ok = bindGeneric(*subTail, builtinTypes->emptyTypePack);
|
||||
bool ok = bindGeneric(env, *subTail, builtinTypes->emptyTypePack);
|
||||
return {ok};
|
||||
}
|
||||
else
|
||||
@ -452,7 +524,7 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
if (variance == Variance::Contravariant)
|
||||
{
|
||||
bool ok = bindGeneric(builtinTypes->emptyTypePack, *superTail);
|
||||
bool ok = bindGeneric(env, builtinTypes->emptyTypePack, *superTail);
|
||||
results.push_back({ok});
|
||||
}
|
||||
else
|
||||
@ -466,33 +538,33 @@ SubtypingResult Subtyping::isCovariantWith(TypePackId subTp, TypePackId superTp)
|
||||
}
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isContravariantWith(SubTy&& subTy, SuperTy&& superTy)
|
||||
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
|
||||
{
|
||||
return isCovariantWith(superTy, subTy);
|
||||
return isCovariantWith(env, superTy, subTy);
|
||||
}
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isInvariantWith(SubTy&& subTy, SuperTy&& superTy)
|
||||
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy)
|
||||
{
|
||||
return isCovariantWith(subTy, superTy).andAlso(isContravariantWith(subTy, superTy));
|
||||
return isCovariantWith(env, subTy, superTy).andAlso(isContravariantWith(env, subTy, superTy));
|
||||
}
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isCovariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||
{
|
||||
return isCovariantWith(pair.first, pair.second);
|
||||
return isCovariantWith(env, pair.first, pair.second);
|
||||
}
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isContravariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||
SubtypingResult Subtyping::isContravariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||
{
|
||||
return isCovariantWith(pair.second, pair.first);
|
||||
return isCovariantWith(env, pair.second, pair.first);
|
||||
}
|
||||
|
||||
template<typename SubTy, typename SuperTy>
|
||||
SubtypingResult Subtyping::isInvariantWith(const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||
SubtypingResult Subtyping::isInvariantWith(SubtypingEnvironment& env, const TryPair<const SubTy*, const SuperTy*>& pair)
|
||||
{
|
||||
return isCovariantWith(pair).andAlso(isContravariantWith(pair));
|
||||
return isCovariantWith(env, pair).andAlso(isContravariantWith(pair));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -526,43 +598,43 @@ SubtypingResult Subtyping::isInvariantWith(const TryPair<const SubTy*, const Sup
|
||||
* other just asks for boolean ~ 'b. We can dispatch this and only commit
|
||||
* boolean ~ 'b. This constraint does not teach us anything about 'a.
|
||||
*/
|
||||
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const UnionType* superUnion)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion)
|
||||
{
|
||||
// As per TAPL: T <: A | B iff T <: A || T <: B
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
for (TypeId ty : superUnion)
|
||||
subtypings.push_back(isCovariantWith(subTy, ty));
|
||||
subtypings.push_back(isCovariantWith(env, subTy, ty));
|
||||
return SubtypingResult::any(subtypings);
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const UnionType* subUnion, TypeId superTy)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy)
|
||||
{
|
||||
// As per TAPL: A | B <: T iff A <: T && B <: T
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
for (TypeId ty : subUnion)
|
||||
subtypings.push_back(isCovariantWith(ty, superTy));
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy));
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(TypeId subTy, const IntersectionType* superIntersection)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection)
|
||||
{
|
||||
// As per TAPL: T <: A & B iff T <: A && T <: B
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
for (TypeId ty : superIntersection)
|
||||
subtypings.push_back(isCovariantWith(subTy, ty));
|
||||
subtypings.push_back(isCovariantWith(env, subTy, ty));
|
||||
return SubtypingResult::all(subtypings);
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const IntersectionType* subIntersection, TypeId superTy)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy)
|
||||
{
|
||||
// As per TAPL: A & B <: T iff A <: T || B <: T
|
||||
std::vector<SubtypingResult> subtypings;
|
||||
for (TypeId ty : subIntersection)
|
||||
subtypings.push_back(isCovariantWith(ty, superTy));
|
||||
subtypings.push_back(isCovariantWith(env, ty, superTy));
|
||||
return SubtypingResult::any(subtypings);
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const NegationType* subNegation, TypeId superTy)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy)
|
||||
{
|
||||
TypeId negatedTy = follow(subNegation->ty);
|
||||
|
||||
@ -572,17 +644,17 @@ SubtypingResult Subtyping::isCovariantWith(const NegationType* subNegation, Type
|
||||
if (is<NeverType>(negatedTy))
|
||||
{
|
||||
// ¬never ~ unknown
|
||||
return isCovariantWith(builtinTypes->unknownType, superTy);
|
||||
return isCovariantWith(env, builtinTypes->unknownType, superTy);
|
||||
}
|
||||
else if (is<UnknownType>(negatedTy))
|
||||
{
|
||||
// ¬unknown ~ never
|
||||
return isCovariantWith(builtinTypes->neverType, superTy);
|
||||
return isCovariantWith(env, builtinTypes->neverType, superTy);
|
||||
}
|
||||
else if (is<AnyType>(negatedTy))
|
||||
{
|
||||
// ¬any ~ any
|
||||
return isCovariantWith(negatedTy, superTy);
|
||||
return isCovariantWith(env, negatedTy, superTy);
|
||||
}
|
||||
else if (auto u = get<UnionType>(negatedTy))
|
||||
{
|
||||
@ -593,7 +665,7 @@ SubtypingResult Subtyping::isCovariantWith(const NegationType* subNegation, Type
|
||||
for (TypeId ty : u)
|
||||
{
|
||||
NegationType negatedTmp{ty};
|
||||
subtypings.push_back(isCovariantWith(&negatedTmp, superTy));
|
||||
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy));
|
||||
}
|
||||
|
||||
return SubtypingResult::all(subtypings);
|
||||
@ -607,11 +679,11 @@ SubtypingResult Subtyping::isCovariantWith(const NegationType* subNegation, Type
|
||||
for (TypeId ty : i)
|
||||
{
|
||||
if (auto negatedPart = get<NegationType>(follow(ty)))
|
||||
subtypings.push_back(isCovariantWith(negatedPart->ty, superTy));
|
||||
subtypings.push_back(isCovariantWith(env, negatedPart->ty, superTy));
|
||||
else
|
||||
{
|
||||
NegationType negatedTmp{ty};
|
||||
subtypings.push_back(isCovariantWith(&negatedTmp, superTy));
|
||||
subtypings.push_back(isCovariantWith(env, &negatedTmp, superTy));
|
||||
}
|
||||
}
|
||||
|
||||
@ -629,19 +701,19 @@ SubtypingResult Subtyping::isCovariantWith(const NegationType* subNegation, Type
|
||||
}
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const TypeId subTy, const NegationType* superNegation)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation)
|
||||
{
|
||||
TypeId negatedTy = follow(superNegation->ty);
|
||||
|
||||
if (is<NeverType>(negatedTy))
|
||||
{
|
||||
// ¬never ~ unknown
|
||||
return isCovariantWith(subTy, builtinTypes->unknownType);
|
||||
return isCovariantWith(env, subTy, builtinTypes->unknownType);
|
||||
}
|
||||
else if (is<UnknownType>(negatedTy))
|
||||
{
|
||||
// ¬unknown ~ never
|
||||
return isCovariantWith(subTy, builtinTypes->neverType);
|
||||
return isCovariantWith(env, subTy, builtinTypes->neverType);
|
||||
}
|
||||
else if (is<AnyType>(negatedTy))
|
||||
{
|
||||
@ -657,11 +729,11 @@ SubtypingResult Subtyping::isCovariantWith(const TypeId subTy, const NegationTyp
|
||||
for (TypeId ty : u)
|
||||
{
|
||||
if (auto negatedPart = get<NegationType>(follow(ty)))
|
||||
subtypings.push_back(isCovariantWith(subTy, negatedPart->ty));
|
||||
subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty));
|
||||
else
|
||||
{
|
||||
NegationType negatedTmp{ty};
|
||||
subtypings.push_back(isCovariantWith(subTy, &negatedTmp));
|
||||
subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp));
|
||||
}
|
||||
}
|
||||
|
||||
@ -676,11 +748,11 @@ SubtypingResult Subtyping::isCovariantWith(const TypeId subTy, const NegationTyp
|
||||
for (TypeId ty : i)
|
||||
{
|
||||
if (auto negatedPart = get<NegationType>(follow(ty)))
|
||||
subtypings.push_back(isCovariantWith(subTy, negatedPart->ty));
|
||||
subtypings.push_back(isCovariantWith(env, subTy, negatedPart->ty));
|
||||
else
|
||||
{
|
||||
NegationType negatedTmp{ty};
|
||||
subtypings.push_back(isCovariantWith(subTy, &negatedTmp));
|
||||
subtypings.push_back(isCovariantWith(env, subTy, &negatedTmp));
|
||||
}
|
||||
}
|
||||
|
||||
@ -724,7 +796,7 @@ SubtypingResult Subtyping::isCovariantWith(const TypeId subTy, const NegationTyp
|
||||
else if (auto p = get2<SingletonType, SingletonType>(subTy, negatedTy))
|
||||
return {*p.first != *p.second};
|
||||
else if (auto p = get2<ClassType, ClassType>(subTy, negatedTy))
|
||||
return SubtypingResult::negate(isCovariantWith(p.first, p.second));
|
||||
return SubtypingResult::negate(isCovariantWith(env, p.first, p.second));
|
||||
else if (get2<FunctionType, ClassType>(subTy, negatedTy))
|
||||
return {true};
|
||||
else if (is<ErrorType, FunctionType, TableType, MetatableType>(negatedTy))
|
||||
@ -733,12 +805,12 @@ SubtypingResult Subtyping::isCovariantWith(const TypeId subTy, const NegationTyp
|
||||
return {false};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const PrimitiveType* superPrim)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim)
|
||||
{
|
||||
return {subPrim->type == superPrim->type};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const PrimitiveType* superPrim)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const PrimitiveType* superPrim)
|
||||
{
|
||||
if (get<StringSingleton>(subSingleton) && superPrim->type == PrimitiveType::String)
|
||||
return {true};
|
||||
@ -748,12 +820,12 @@ SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, co
|
||||
return {false};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const SingletonType* superSingleton)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const SingletonType* superSingleton)
|
||||
{
|
||||
return {*subSingleton == *superSingleton};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const TableType* subTable, const TableType* superTable)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable)
|
||||
{
|
||||
SubtypingResult result{true};
|
||||
|
||||
@ -764,12 +836,12 @@ SubtypingResult Subtyping::isCovariantWith(const TableType* subTable, const Tabl
|
||||
{
|
||||
std::vector<SubtypingResult> results;
|
||||
if (auto it = subTable->props.find(name); it != subTable->props.end())
|
||||
results.push_back(isInvariantWith(it->second.type(), prop.type()));
|
||||
results.push_back(isInvariantWith(env, it->second.type(), prop.type()));
|
||||
|
||||
if (subTable->indexer)
|
||||
{
|
||||
if (isInvariantWith(subTable->indexer->indexType, builtinTypes->stringType).isSubtype)
|
||||
results.push_back(isInvariantWith(subTable->indexer->indexResultType, prop.type()));
|
||||
if (isInvariantWith(env, subTable->indexer->indexType, builtinTypes->stringType).isSubtype)
|
||||
results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, prop.type()));
|
||||
}
|
||||
|
||||
if (results.empty())
|
||||
@ -781,7 +853,7 @@ SubtypingResult Subtyping::isCovariantWith(const TableType* subTable, const Tabl
|
||||
if (superTable->indexer)
|
||||
{
|
||||
if (subTable->indexer)
|
||||
result.andAlso(isInvariantWith(*subTable->indexer, *superTable->indexer));
|
||||
result.andAlso(isInvariantWith(env, *subTable->indexer, *superTable->indexer));
|
||||
else
|
||||
return {false};
|
||||
}
|
||||
@ -789,12 +861,12 @@ SubtypingResult Subtyping::isCovariantWith(const TableType* subTable, const Tabl
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const MetatableType* superMt)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt)
|
||||
{
|
||||
return isCovariantWith(subMt->table, superMt->table).andAlso(isCovariantWith(subMt->metatable, superMt->metatable));
|
||||
return isCovariantWith(env, subMt->table, superMt->table).andAlso(isCovariantWith(env, subMt->metatable, superMt->metatable));
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const TableType* superTable)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable)
|
||||
{
|
||||
if (auto subTable = get<TableType>(subMt->table))
|
||||
{
|
||||
@ -807,7 +879,7 @@ SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const Tab
|
||||
// that the metatable isn't a subtype of the table, even though they have
|
||||
// compatible properties/shapes. We'll revisit this later when we have a
|
||||
// better understanding of how important this is.
|
||||
return isCovariantWith(subTable, superTable);
|
||||
return isCovariantWith(env, subTable, superTable);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -816,19 +888,19 @@ SubtypingResult Subtyping::isCovariantWith(const MetatableType* subMt, const Tab
|
||||
}
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const ClassType* superClass)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass)
|
||||
{
|
||||
return {isSubclass(subClass, superClass)};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const TableType* superTable)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const TableType* superTable)
|
||||
{
|
||||
SubtypingResult result{true};
|
||||
|
||||
for (const auto& [name, prop] : superTable->props)
|
||||
{
|
||||
if (auto classProp = lookupClassProp(subClass, name))
|
||||
result.andAlso(isInvariantWith(prop.type(), classProp->type()));
|
||||
result.andAlso(isInvariantWith(env, prop.type(), classProp->type()));
|
||||
else
|
||||
return SubtypingResult{false};
|
||||
}
|
||||
@ -836,20 +908,20 @@ SubtypingResult Subtyping::isCovariantWith(const ClassType* subClass, const Tabl
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const FunctionType* subFunction, const FunctionType* superFunction)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const FunctionType* subFunction, const FunctionType* superFunction)
|
||||
{
|
||||
SubtypingResult result;
|
||||
{
|
||||
VarianceFlipper vf{&variance};
|
||||
result.orElse(isContravariantWith(subFunction->argTypes, superFunction->argTypes));
|
||||
result.orElse(isContravariantWith(env, subFunction->argTypes, superFunction->argTypes));
|
||||
}
|
||||
|
||||
result.andAlso(isCovariantWith(subFunction->retTypes, superFunction->retTypes));
|
||||
result.andAlso(isCovariantWith(env, subFunction->retTypes, superFunction->retTypes));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const TableType* superTable)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable)
|
||||
{
|
||||
SubtypingResult result{false};
|
||||
if (subPrim->type == PrimitiveType::String)
|
||||
@ -861,7 +933,7 @@ SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const T
|
||||
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
|
||||
{
|
||||
if (auto stringTable = get<TableType>(it->second.type()))
|
||||
result.orElse(isCovariantWith(stringTable, superTable));
|
||||
result.orElse(isCovariantWith(env, stringTable, superTable));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -870,7 +942,7 @@ SubtypingResult Subtyping::isCovariantWith(const PrimitiveType* subPrim, const T
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, const TableType* superTable)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable)
|
||||
{
|
||||
SubtypingResult result{false};
|
||||
if (auto stringleton = get<StringSingleton>(subSingleton))
|
||||
@ -882,7 +954,7 @@ SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, co
|
||||
if (auto it = mttv->props.find("__index"); it != mttv->props.end())
|
||||
{
|
||||
if (auto stringTable = get<TableType>(it->second.type()))
|
||||
result.orElse(isCovariantWith(stringTable, superTable));
|
||||
result.orElse(isCovariantWith(env, stringTable, superTable));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -890,32 +962,33 @@ SubtypingResult Subtyping::isCovariantWith(const SingletonType* subSingleton, co
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const TableIndexer& subIndexer, const TableIndexer& superIndexer)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer)
|
||||
{
|
||||
return isInvariantWith(subIndexer.indexType, superIndexer.indexType).andAlso(isInvariantWith(superIndexer.indexResultType, subIndexer.indexResultType));
|
||||
return isInvariantWith(env, subIndexer.indexType, superIndexer.indexType)
|
||||
.andAlso(isInvariantWith(env, superIndexer.indexResultType, subIndexer.indexResultType));
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm)
|
||||
{
|
||||
if (!subNorm || !superNorm)
|
||||
return {false, true, true};
|
||||
|
||||
SubtypingResult result = isCovariantWith(subNorm->tops, superNorm->tops);
|
||||
result.andAlso(isCovariantWith(subNorm->booleans, superNorm->booleans));
|
||||
result.andAlso(isCovariantWith(subNorm->classes, superNorm->classes).orElse(isCovariantWith(subNorm->classes, superNorm->tables)));
|
||||
result.andAlso(isCovariantWith(subNorm->errors, superNorm->errors));
|
||||
result.andAlso(isCovariantWith(subNorm->nils, superNorm->nils));
|
||||
result.andAlso(isCovariantWith(subNorm->numbers, superNorm->numbers));
|
||||
result.andAlso(isCovariantWith(subNorm->strings, superNorm->strings));
|
||||
result.andAlso(isCovariantWith(subNorm->strings, superNorm->tables));
|
||||
result.andAlso(isCovariantWith(subNorm->threads, superNorm->threads));
|
||||
result.andAlso(isCovariantWith(subNorm->tables, superNorm->tables));
|
||||
result.andAlso(isCovariantWith(subNorm->functions, superNorm->functions));
|
||||
SubtypingResult result = isCovariantWith(env, subNorm->tops, superNorm->tops);
|
||||
result.andAlso(isCovariantWith(env, subNorm->booleans, superNorm->booleans));
|
||||
result.andAlso(isCovariantWith(env, subNorm->classes, superNorm->classes).orElse(isCovariantWith(env, subNorm->classes, superNorm->tables)));
|
||||
result.andAlso(isCovariantWith(env, subNorm->errors, superNorm->errors));
|
||||
result.andAlso(isCovariantWith(env, subNorm->nils, superNorm->nils));
|
||||
result.andAlso(isCovariantWith(env, subNorm->numbers, superNorm->numbers));
|
||||
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->strings));
|
||||
result.andAlso(isCovariantWith(env, subNorm->strings, superNorm->tables));
|
||||
result.andAlso(isCovariantWith(env, subNorm->threads, superNorm->threads));
|
||||
result.andAlso(isCovariantWith(env, subNorm->tables, superNorm->tables));
|
||||
result.andAlso(isCovariantWith(env, subNorm->functions, superNorm->functions));
|
||||
// isCovariantWith(subNorm->tyvars, superNorm->tyvars);
|
||||
return result;
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const NormalizedClassType& superClass)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass)
|
||||
{
|
||||
for (const auto& [subClassTy, _] : subClass.classes)
|
||||
{
|
||||
@ -923,13 +996,13 @@ SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass,
|
||||
|
||||
for (const auto& [superClassTy, superNegations] : superClass.classes)
|
||||
{
|
||||
result.orElse(isCovariantWith(subClassTy, superClassTy));
|
||||
result.orElse(isCovariantWith(env, subClassTy, superClassTy));
|
||||
if (!result.isSubtype)
|
||||
continue;
|
||||
|
||||
for (TypeId negation : superNegations)
|
||||
{
|
||||
result.andAlso(SubtypingResult::negate(isCovariantWith(subClassTy, negation)));
|
||||
result.andAlso(SubtypingResult::negate(isCovariantWith(env, subClassTy, negation)));
|
||||
if (result.isSubtype)
|
||||
break;
|
||||
}
|
||||
@ -942,14 +1015,14 @@ SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass,
|
||||
return {true};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass, const TypeIds& superTables)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables)
|
||||
{
|
||||
for (const auto& [subClassTy, _] : subClass.classes)
|
||||
{
|
||||
SubtypingResult result;
|
||||
|
||||
for (TypeId superTableTy : superTables)
|
||||
result.orElse(isCovariantWith(subClassTy, superTableTy));
|
||||
result.orElse(isCovariantWith(env, subClassTy, superTableTy));
|
||||
|
||||
if (!result.isSubtype)
|
||||
return result;
|
||||
@ -958,13 +1031,13 @@ SubtypingResult Subtyping::isCovariantWith(const NormalizedClassType& subClass,
|
||||
return {true};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString, const NormalizedStringType& superString)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString)
|
||||
{
|
||||
bool isSubtype = Luau::isSubtype(subString, superString);
|
||||
return {isSubtype};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString, const TypeIds& superTables)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const TypeIds& superTables)
|
||||
{
|
||||
if (subString.isNever())
|
||||
return {true};
|
||||
@ -974,7 +1047,7 @@ SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString
|
||||
SubtypingResult result;
|
||||
for (const auto& superTable : superTables)
|
||||
{
|
||||
result.orElse(isCovariantWith(builtinTypes->stringType, superTable));
|
||||
result.orElse(isCovariantWith(env, builtinTypes->stringType, superTable));
|
||||
if (result.isSubtype)
|
||||
return result;
|
||||
}
|
||||
@ -990,7 +1063,7 @@ SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString
|
||||
SubtypingResult result{true};
|
||||
for (const auto& [_, subString] : subString.singletons)
|
||||
{
|
||||
result.andAlso(isCovariantWith(subString, superTable));
|
||||
result.andAlso(isCovariantWith(env, subString, superTable));
|
||||
if (!result.isSubtype)
|
||||
break;
|
||||
}
|
||||
@ -1004,17 +1077,18 @@ SubtypingResult Subtyping::isCovariantWith(const NormalizedStringType& subString
|
||||
return {false};
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction)
|
||||
SubtypingResult Subtyping::isCovariantWith(
|
||||
SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction)
|
||||
{
|
||||
if (subFunction.isNever())
|
||||
return {true};
|
||||
else if (superFunction.isTop)
|
||||
return {true};
|
||||
else
|
||||
return isCovariantWith(subFunction.parts, superFunction.parts);
|
||||
return isCovariantWith(env, subFunction.parts, superFunction.parts);
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const TypeIds& subTypes, const TypeIds& superTypes)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes)
|
||||
{
|
||||
std::vector<SubtypingResult> results;
|
||||
|
||||
@ -1022,32 +1096,32 @@ SubtypingResult Subtyping::isCovariantWith(const TypeIds& subTypes, const TypeId
|
||||
{
|
||||
results.emplace_back();
|
||||
for (TypeId superTy : superTypes)
|
||||
results.back().orElse(isCovariantWith(subTy, superTy));
|
||||
results.back().orElse(isCovariantWith(env, subTy, superTy));
|
||||
}
|
||||
|
||||
return SubtypingResult::all(results);
|
||||
}
|
||||
|
||||
SubtypingResult Subtyping::isCovariantWith(const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic)
|
||||
SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic)
|
||||
{
|
||||
return isCovariantWith(subVariadic->ty, superVariadic->ty);
|
||||
return isCovariantWith(env, subVariadic->ty, superVariadic->ty);
|
||||
}
|
||||
|
||||
bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy)
|
||||
bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypeId subTy, TypeId superTy)
|
||||
{
|
||||
if (variance == Variance::Covariant)
|
||||
{
|
||||
if (!get<GenericType>(subTy))
|
||||
return false;
|
||||
|
||||
mappedGenerics[subTy].upperBound.insert(superTy);
|
||||
env.mappedGenerics[subTy].upperBound.insert(superTy);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!get<GenericType>(superTy))
|
||||
return false;
|
||||
|
||||
mappedGenerics[superTy].lowerBound.insert(subTy);
|
||||
env.mappedGenerics[superTy].lowerBound.insert(subTy);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1058,7 +1132,7 @@ bool Subtyping::bindGeneric(TypeId subTy, TypeId superTy)
|
||||
* side, it is permissible to tentatively bind that generic to the right side
|
||||
* type.
|
||||
*/
|
||||
bool Subtyping::bindGeneric(TypePackId subTp, TypePackId superTp)
|
||||
bool Subtyping::bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp)
|
||||
{
|
||||
if (variance == Variance::Contravariant)
|
||||
std::swap(superTp, subTp);
|
||||
@ -1066,10 +1140,10 @@ bool Subtyping::bindGeneric(TypePackId subTp, TypePackId superTp)
|
||||
if (!get<GenericTypePack>(subTp))
|
||||
return false;
|
||||
|
||||
if (TypePackId* m = mappedGenericPacks.find(subTp))
|
||||
if (TypePackId* m = env.mappedGenericPacks.find(subTp))
|
||||
return *m == superTp;
|
||||
|
||||
mappedGenericPacks[subTp] = superTp;
|
||||
env.mappedGenericPacks[subTp] = superTp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Luau/Normalize.h"
|
||||
#include "Luau/Simplify.h"
|
||||
#include "Luau/Substitution.h"
|
||||
#include "Luau/Subtyping.h"
|
||||
#include "Luau/ToString.h"
|
||||
#include "Luau/TxnLog.h"
|
||||
#include "Luau/TypeCheckLimits.h"
|
||||
@ -320,8 +321,25 @@ bool isPending(TypeId ty, ConstraintSolver* solver)
|
||||
return is<BlockedType>(ty) || is<PendingExpansionType>(ty) || is<TypeFamilyInstanceType>(ty) || (solver && solver->hasUnresolvedConstraints(ty));
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> notFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 1 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("not type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
TypeId ty = follow(typeParams.at(0));
|
||||
|
||||
if (isPending(ty, ctx->solver))
|
||||
return {std::nullopt, false, {ty}, {}};
|
||||
|
||||
// `not` operates on anything and returns a `boolean` always.
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(
|
||||
std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
||||
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -333,34 +351,28 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(
|
||||
TypeId rhsTy = follow(typeParams.at(1));
|
||||
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||
|
||||
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
if (!normLhsTy || !normRhsTy)
|
||||
{
|
||||
return {std::nullopt, false, {}, {}};
|
||||
}
|
||||
else if (is<AnyType>(normLhsTy->tops) || is<AnyType>(normRhsTy->tops))
|
||||
{
|
||||
|
||||
// if one of the types is error suppressing, we can reduce to `any` since we should suppress errors in the result of the usage.
|
||||
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
||||
return {ctx->builtins->anyType, false, {}, {}};
|
||||
}
|
||||
else if ((normLhsTy->hasNumbers() || normLhsTy->hasTops()) && (normRhsTy->hasNumbers() || normRhsTy->hasTops()))
|
||||
{
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
}
|
||||
else if (is<ErrorType>(lhsTy) || is<ErrorType>(rhsTy))
|
||||
{
|
||||
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||
}
|
||||
else if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
||||
{
|
||||
|
||||
// if we have a `never`, we can never observe that the numeric operator didn't work.
|
||||
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
||||
return {ctx->builtins->neverType, false, {}, {}};
|
||||
}
|
||||
else if (isPending(lhsTy, ctx->solver))
|
||||
{
|
||||
|
||||
// if we're adding two `number` types, the result is `number`.
|
||||
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
|
||||
return {ctx->builtins->numberType, false, {}, {}};
|
||||
|
||||
// otherwise, check if we need to wait on either type to be further resolved
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {lhsTy}, {}};
|
||||
}
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
{
|
||||
return {std::nullopt, false, {rhsTy}, {}};
|
||||
}
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
@ -385,39 +397,32 @@ TypeFamilyReductionResult<TypeId> numericBinopFamilyFn(
|
||||
if (!mmFtv)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
if (std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType))
|
||||
{
|
||||
if (const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType))
|
||||
{
|
||||
std::vector<TypeId> inferredArgs;
|
||||
if (!reversed)
|
||||
inferredArgs = {lhsTy, rhsTy};
|
||||
else
|
||||
inferredArgs = {rhsTy, lhsTy};
|
||||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs));
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, true, {}, {}}; // occurs check failed
|
||||
|
||||
if (std::optional<TypeId> ret = first(instantiatedMmFtv->retTypes))
|
||||
return {*ret, false, {}, {}};
|
||||
else
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
else
|
||||
{
|
||||
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Not the nicest logic here.
|
||||
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
||||
if (!instantiatedMmType)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
||||
if (!instantiatedMmFtv)
|
||||
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||
|
||||
std::vector<TypeId> inferredArgs;
|
||||
if (!reversed)
|
||||
inferredArgs = {lhsTy, rhsTy};
|
||||
else
|
||||
inferredArgs = {rhsTy, lhsTy};
|
||||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs));
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, true, {}, {}}; // occurs check failed
|
||||
|
||||
if (std::optional<TypeId> ret = first(instantiatedMmFtv->retTypes))
|
||||
return {*ret, false, {}, {}};
|
||||
else
|
||||
return {std::nullopt, true, {}, {}};
|
||||
}
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> addFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -428,7 +433,7 @@ TypeFamilyReductionResult<TypeId> addFamilyFn(std::vector<TypeId> typeParams, st
|
||||
return numericBinopFamilyFn(typeParams, packParams, ctx, "__add");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> subFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> subFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -439,7 +444,7 @@ TypeFamilyReductionResult<TypeId> subFamilyFn(std::vector<TypeId> typeParams, st
|
||||
return numericBinopFamilyFn(typeParams, packParams, ctx, "__sub");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> mulFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> mulFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -450,7 +455,7 @@ TypeFamilyReductionResult<TypeId> mulFamilyFn(std::vector<TypeId> typeParams, st
|
||||
return numericBinopFamilyFn(typeParams, packParams, ctx, "__mul");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> divFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> divFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -461,7 +466,7 @@ TypeFamilyReductionResult<TypeId> divFamilyFn(std::vector<TypeId> typeParams, st
|
||||
return numericBinopFamilyFn(typeParams, packParams, ctx, "__div");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> idivFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> idivFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -472,7 +477,7 @@ TypeFamilyReductionResult<TypeId> idivFamilyFn(std::vector<TypeId> typeParams, s
|
||||
return numericBinopFamilyFn(typeParams, packParams, ctx, "__idiv");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> powFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> powFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -483,7 +488,7 @@ TypeFamilyReductionResult<TypeId> powFamilyFn(std::vector<TypeId> typeParams, st
|
||||
return numericBinopFamilyFn(typeParams, packParams, ctx, "__pow");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> modFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> modFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -494,7 +499,91 @@ TypeFamilyReductionResult<TypeId> modFamilyFn(std::vector<TypeId> typeParams, st
|
||||
return numericBinopFamilyFn(typeParams, packParams, ctx, "__mod");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> andFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> concatFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("concat type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
TypeId lhsTy = follow(typeParams.at(0));
|
||||
TypeId rhsTy = follow(typeParams.at(1));
|
||||
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||
|
||||
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
if (!normLhsTy || !normRhsTy)
|
||||
return {std::nullopt, false, {}, {}};
|
||||
|
||||
// if one of the types is error suppressing, we can reduce to `any` since we should suppress errors in the result of the usage.
|
||||
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
||||
return {ctx->builtins->anyType, false, {}, {}};
|
||||
|
||||
// if we have a `never`, we can never observe that the numeric operator didn't work.
|
||||
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
||||
return {ctx->builtins->neverType, false, {}, {}};
|
||||
|
||||
// if we're concatenating two elements that are either strings or numbers, the result is `string`.
|
||||
if ((normLhsTy->isSubtypeOfString() || normLhsTy->isExactlyNumber()) && (normRhsTy->isSubtypeOfString() || normRhsTy->isExactlyNumber()))
|
||||
return {ctx->builtins->stringType, false, {}, {}};
|
||||
|
||||
// otherwise, check if we need to wait on either type to be further resolved
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {lhsTy}, {}};
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {rhsTy}, {}};
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
|
||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, "__concat", Location{});
|
||||
bool reversed = false;
|
||||
if (!mmType)
|
||||
{
|
||||
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, "__concat", Location{});
|
||||
reversed = true;
|
||||
}
|
||||
|
||||
if (!mmType)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
mmType = follow(*mmType);
|
||||
if (isPending(*mmType, ctx->solver))
|
||||
return {std::nullopt, false, {*mmType}, {}};
|
||||
|
||||
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
||||
if (!mmFtv)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
||||
if (!instantiatedMmType)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
||||
if (!instantiatedMmFtv)
|
||||
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||
|
||||
std::vector<TypeId> inferredArgs;
|
||||
if (!reversed)
|
||||
inferredArgs = {lhsTy, rhsTy};
|
||||
else
|
||||
inferredArgs = {rhsTy, lhsTy};
|
||||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack(std::move(inferredArgs));
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, true, {}, {}}; // occurs check failed
|
||||
|
||||
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
|
||||
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
return {ctx->builtins->stringType, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> andFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -522,7 +611,7 @@ TypeFamilyReductionResult<TypeId> andFamilyFn(std::vector<TypeId> typeParams, st
|
||||
return {overallResult.result, false, std::move(blockedTypes), {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> orFamilyFn(std::vector<TypeId> typeParams, std::vector<TypePackId> packParams, NotNull<TypeFamilyContext> ctx)
|
||||
TypeFamilyReductionResult<TypeId> orFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
@ -550,16 +639,196 @@ TypeFamilyReductionResult<TypeId> orFamilyFn(std::vector<TypeId> typeParams, std
|
||||
return {overallResult.result, false, std::move(blockedTypes), {}};
|
||||
}
|
||||
|
||||
static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(
|
||||
const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx, const std::string metamethod)
|
||||
{
|
||||
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
TypeId lhsTy = follow(typeParams.at(0));
|
||||
TypeId rhsTy = follow(typeParams.at(1));
|
||||
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||
|
||||
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
if (!normLhsTy || !normRhsTy)
|
||||
return {std::nullopt, false, {}, {}};
|
||||
|
||||
// if one of the types is error suppressing, we can just go ahead and reduce.
|
||||
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
|
||||
// if we have a `never`, we can never observe that the comparison didn't work.
|
||||
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
|
||||
// If both types are some strict subset of `string`, we can reduce now.
|
||||
if (normLhsTy->isSubtypeOfString() && normRhsTy->isSubtypeOfString())
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
|
||||
// If both types are exactly `number`, we can reduce now.
|
||||
if (normLhsTy->isExactlyNumber() && normRhsTy->isExactlyNumber())
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
|
||||
// otherwise, check if we need to wait on either type to be further resolved
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {lhsTy}, {}};
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {rhsTy}, {}};
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
|
||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, metamethod, Location{});
|
||||
if (!mmType)
|
||||
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, metamethod, Location{});
|
||||
|
||||
if (!mmType)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
mmType = follow(*mmType);
|
||||
if (isPending(*mmType, ctx->solver))
|
||||
return {std::nullopt, false, {*mmType}, {}};
|
||||
|
||||
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
||||
if (!mmFtv)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
||||
if (!instantiatedMmType)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
||||
if (!instantiatedMmFtv)
|
||||
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack({lhsTy, rhsTy});
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, true, {}, {}}; // occurs check failed
|
||||
|
||||
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
|
||||
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> ltFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("lt type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return comparisonFamilyFn(typeParams, packParams, ctx, "__lt");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> leFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("le type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
return comparisonFamilyFn(typeParams, packParams, ctx, "__le");
|
||||
}
|
||||
|
||||
TypeFamilyReductionResult<TypeId> eqFamilyFn(const std::vector<TypeId>& typeParams, const std::vector<TypePackId>& packParams, NotNull<TypeFamilyContext> ctx)
|
||||
{
|
||||
if (typeParams.size() != 2 || !packParams.empty())
|
||||
{
|
||||
ctx->ice->ice("eq type family: encountered a type family instance without the required argument structure");
|
||||
LUAU_ASSERT(false);
|
||||
}
|
||||
|
||||
TypeId lhsTy = follow(typeParams.at(0));
|
||||
TypeId rhsTy = follow(typeParams.at(1));
|
||||
const NormalizedType* normLhsTy = ctx->normalizer->normalize(lhsTy);
|
||||
const NormalizedType* normRhsTy = ctx->normalizer->normalize(rhsTy);
|
||||
|
||||
// if either failed to normalize, we can't reduce, but know nothing about inhabitance.
|
||||
if (!normLhsTy || !normRhsTy)
|
||||
return {std::nullopt, false, {}, {}};
|
||||
|
||||
// if one of the types is error suppressing, we can just go ahead and reduce.
|
||||
if (normLhsTy->shouldSuppressErrors() || normRhsTy->shouldSuppressErrors())
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
|
||||
// if we have a `never`, we can never observe that the comparison didn't work.
|
||||
if (is<NeverType>(lhsTy) || is<NeverType>(rhsTy))
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
|
||||
// otherwise, check if we need to wait on either type to be further resolved
|
||||
if (isPending(lhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {lhsTy}, {}};
|
||||
else if (isPending(rhsTy, ctx->solver))
|
||||
return {std::nullopt, false, {rhsTy}, {}};
|
||||
|
||||
// findMetatableEntry demands the ability to emit errors, so we must give it
|
||||
// the necessary state to do that, even if we intend to just eat the errors.
|
||||
ErrorVec dummy;
|
||||
|
||||
std::optional<TypeId> mmType = findMetatableEntry(ctx->builtins, dummy, lhsTy, "__eq", Location{});
|
||||
if (!mmType)
|
||||
mmType = findMetatableEntry(ctx->builtins, dummy, rhsTy, "__eq", Location{});
|
||||
|
||||
// if neither type has a metatable entry for `__eq`, then we'll check for inhabitance of the intersection!
|
||||
if (!mmType && ctx->normalizer->isIntersectionInhabited(lhsTy, rhsTy))
|
||||
return {ctx->builtins->booleanType, false, {}, {}}; // if it's inhabited, everything is okay!
|
||||
else if (!mmType)
|
||||
return {std::nullopt, true, {}, {}}; // if it's not, then this family is irreducible!
|
||||
|
||||
mmType = follow(*mmType);
|
||||
if (isPending(*mmType, ctx->solver))
|
||||
return {std::nullopt, false, {*mmType}, {}};
|
||||
|
||||
const FunctionType* mmFtv = get<FunctionType>(*mmType);
|
||||
if (!mmFtv)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
std::optional<TypeId> instantiatedMmType = instantiate(ctx->builtins, ctx->arena, ctx->limits, ctx->scope, *mmType);
|
||||
if (!instantiatedMmType)
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
const FunctionType* instantiatedMmFtv = get<FunctionType>(*instantiatedMmType);
|
||||
if (!instantiatedMmFtv)
|
||||
return {ctx->builtins->errorRecoveryType(), false, {}, {}};
|
||||
|
||||
TypePackId inferredArgPack = ctx->arena->addTypePack({lhsTy, rhsTy});
|
||||
Unifier2 u2{ctx->arena, ctx->builtins, ctx->scope, ctx->ice};
|
||||
if (!u2.unify(inferredArgPack, instantiatedMmFtv->argTypes))
|
||||
return {std::nullopt, true, {}, {}}; // occurs check failed
|
||||
|
||||
Subtyping subtyping{ctx->builtins, ctx->arena, ctx->normalizer, ctx->ice, ctx->scope};
|
||||
if (!subtyping.isSubtype(inferredArgPack, instantiatedMmFtv->argTypes).isSubtype) // TODO: is this the right variance?
|
||||
return {std::nullopt, true, {}, {}};
|
||||
|
||||
return {ctx->builtins->booleanType, false, {}, {}};
|
||||
}
|
||||
|
||||
BuiltinTypeFamilies::BuiltinTypeFamilies()
|
||||
: addFamily{"Add", addFamilyFn}
|
||||
, subFamily{"Sub", subFamilyFn}
|
||||
, mulFamily{"Mul", mulFamilyFn}
|
||||
, divFamily{"Div", divFamilyFn}
|
||||
, idivFamily{"FloorDiv", idivFamilyFn}
|
||||
, powFamily{"Exp", powFamilyFn}
|
||||
, modFamily{"Mod", modFamilyFn}
|
||||
, andFamily{"And", andFamilyFn}
|
||||
, orFamily{"Or", orFamilyFn}
|
||||
: notFamily{"not", notFamilyFn}
|
||||
, addFamily{"add", addFamilyFn}
|
||||
, subFamily{"sub", subFamilyFn}
|
||||
, mulFamily{"mul", mulFamilyFn}
|
||||
, divFamily{"div", divFamilyFn}
|
||||
, idivFamily{"idiv", idivFamilyFn}
|
||||
, powFamily{"pow", powFamilyFn}
|
||||
, modFamily{"mod", modFamilyFn}
|
||||
, concatFamily{"concat", concatFamilyFn}
|
||||
, andFamily{"and", andFamilyFn}
|
||||
, orFamily{"or", orFamilyFn}
|
||||
, ltFamily{"lt", ltFamilyFn}
|
||||
, leFamily{"le", leFamilyFn}
|
||||
, eqFamily{"eq", eqFamilyFn}
|
||||
{
|
||||
}
|
||||
|
||||
@ -582,6 +851,11 @@ void BuiltinTypeFamilies::addToScope(NotNull<TypeArena> arena, NotNull<Scope> sc
|
||||
scope->exportedTypeBindings[idivFamily.name] = mkBinaryTypeFamily(&idivFamily);
|
||||
scope->exportedTypeBindings[powFamily.name] = mkBinaryTypeFamily(&powFamily);
|
||||
scope->exportedTypeBindings[modFamily.name] = mkBinaryTypeFamily(&modFamily);
|
||||
scope->exportedTypeBindings[concatFamily.name] = mkBinaryTypeFamily(&concatFamily);
|
||||
|
||||
scope->exportedTypeBindings[ltFamily.name] = mkBinaryTypeFamily(<Family);
|
||||
scope->exportedTypeBindings[leFamily.name] = mkBinaryTypeFamily(&leFamily);
|
||||
scope->exportedTypeBindings[eqFamily.name] = mkBinaryTypeFamily(&eqFamily);
|
||||
}
|
||||
|
||||
} // namespace Luau
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include "Luau/TypeUtils.h"
|
||||
#include "Luau/Type.h"
|
||||
#include "Luau/VisitType.h"
|
||||
#include "Luau/TypeFamily.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
@ -453,32 +452,10 @@ void Unifier::tryUnify_(TypeId subTy, TypeId superTy, bool isFunctionCall, bool
|
||||
blockedTypes.push_back(superTy);
|
||||
|
||||
if (log.get<TypeFamilyInstanceType>(superTy))
|
||||
{
|
||||
// FIXME: we should be be ICEing here because the old unifier is legacy and should not interact with type families at all.
|
||||
// Unfortunately, there are, at the time of writing, still uses of the old unifier under local type inference.
|
||||
TypeCheckLimits limits;
|
||||
reduceFamilies(
|
||||
superTy, location, TypeFamilyContext{NotNull(types), builtinTypes, scope, normalizer, NotNull{sharedState.iceHandler}, NotNull{&limits}});
|
||||
superTy = log.follow(superTy);
|
||||
}
|
||||
ice("Unexpected TypeFamilyInstanceType superTy");
|
||||
|
||||
if (log.get<TypeFamilyInstanceType>(subTy))
|
||||
{
|
||||
// FIXME: we should be be ICEing here because the old unifier is legacy and should not interact with type families at all.
|
||||
// Unfortunately, there are, at the time of writing, still uses of the old unifier under local type inference.
|
||||
TypeCheckLimits limits;
|
||||
reduceFamilies(
|
||||
subTy, location, TypeFamilyContext{NotNull(types), builtinTypes, scope, normalizer, NotNull{sharedState.iceHandler}, NotNull{&limits}});
|
||||
subTy = log.follow(subTy);
|
||||
}
|
||||
|
||||
// If we can't reduce the families down and we still have type family types
|
||||
// here, we are stuck. Nothing meaningful can be done here. We don't wish to
|
||||
// report an error, either.
|
||||
if (log.get<TypeFamilyInstanceType>(superTy) || log.get<TypeFamilyInstanceType>(subTy))
|
||||
{
|
||||
return;
|
||||
}
|
||||
ice("Unexpected TypeFamilyInstanceType subTy");
|
||||
|
||||
auto superFree = log.getMutable<FreeType>(superTy);
|
||||
auto subFree = log.getMutable<FreeType>(subTy);
|
||||
|
@ -374,7 +374,7 @@ public:
|
||||
AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||
AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, const Location& varargLocation, AstStatBlock* body, size_t functionDepth,
|
||||
const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation = {}, AstTypePack* varargAnnotation = nullptr,
|
||||
bool hasEnd = false, const std::optional<Location>& argLocation = std::nullopt);
|
||||
bool DEPRECATED_hasEnd = false, const std::optional<Location>& argLocation = std::nullopt);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
@ -393,7 +393,8 @@ public:
|
||||
|
||||
AstName debugname;
|
||||
|
||||
bool hasEnd = false;
|
||||
// TODO clip with FFlag::LuauClipExtraHasEndProps
|
||||
bool DEPRECATED_hasEnd = false;
|
||||
std::optional<Location> argLocation;
|
||||
};
|
||||
|
||||
@ -539,6 +540,17 @@ public:
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
AstArray<AstStat*> body;
|
||||
|
||||
/* Indicates whether or not this block has been terminated in a
|
||||
* syntactically valid way.
|
||||
*
|
||||
* This is usually but not always done with the 'end' keyword. AstStatIf
|
||||
* and AstStatRepeat are the two main exceptions to this.
|
||||
*
|
||||
* The 'then' clause of an if statement can properly be closed by the
|
||||
* keywords 'else' or 'elseif'. A 'repeat' loop's body is closed with the
|
||||
* 'until' keyword.
|
||||
*/
|
||||
bool hasEnd = false;
|
||||
};
|
||||
|
||||
@ -548,7 +560,7 @@ public:
|
||||
LUAU_RTTI(AstStatIf)
|
||||
|
||||
AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody, const std::optional<Location>& thenLocation,
|
||||
const std::optional<Location>& elseLocation, bool hasEnd);
|
||||
const std::optional<Location>& elseLocation, bool DEPRECATED_hasEnd);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
@ -561,7 +573,8 @@ public:
|
||||
// Active for 'elseif' as well
|
||||
std::optional<Location> elseLocation;
|
||||
|
||||
bool hasEnd = false;
|
||||
// TODO clip with FFlag::LuauClipExtraHasEndProps
|
||||
bool DEPRECATED_hasEnd = false;
|
||||
};
|
||||
|
||||
class AstStatWhile : public AstStat
|
||||
@ -569,7 +582,7 @@ class AstStatWhile : public AstStat
|
||||
public:
|
||||
LUAU_RTTI(AstStatWhile)
|
||||
|
||||
AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation, bool hasEnd);
|
||||
AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation, bool DEPRECATED_hasEnd);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
@ -579,7 +592,8 @@ public:
|
||||
bool hasDo = false;
|
||||
Location doLocation;
|
||||
|
||||
bool hasEnd = false;
|
||||
// TODO clip with FFlag::LuauClipExtraHasEndProps
|
||||
bool DEPRECATED_hasEnd = false;
|
||||
};
|
||||
|
||||
class AstStatRepeat : public AstStat
|
||||
@ -587,14 +601,14 @@ class AstStatRepeat : public AstStat
|
||||
public:
|
||||
LUAU_RTTI(AstStatRepeat)
|
||||
|
||||
AstStatRepeat(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasUntil);
|
||||
AstStatRepeat(const Location& location, AstExpr* condition, AstStatBlock* body, bool DEPRECATED_hasUntil);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
AstExpr* condition;
|
||||
AstStatBlock* body;
|
||||
|
||||
bool hasUntil = false;
|
||||
bool DEPRECATED_hasUntil = false;
|
||||
};
|
||||
|
||||
class AstStatBreak : public AstStat
|
||||
@ -663,7 +677,7 @@ public:
|
||||
LUAU_RTTI(AstStatFor)
|
||||
|
||||
AstStatFor(const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStatBlock* body, bool hasDo,
|
||||
const Location& doLocation, bool hasEnd);
|
||||
const Location& doLocation, bool DEPRECATED_hasEnd);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
@ -676,7 +690,8 @@ public:
|
||||
bool hasDo = false;
|
||||
Location doLocation;
|
||||
|
||||
bool hasEnd = false;
|
||||
// TODO clip with FFlag::LuauClipExtraHasEndProps
|
||||
bool DEPRECATED_hasEnd = false;
|
||||
};
|
||||
|
||||
class AstStatForIn : public AstStat
|
||||
@ -685,7 +700,7 @@ public:
|
||||
LUAU_RTTI(AstStatForIn)
|
||||
|
||||
AstStatForIn(const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, AstStatBlock* body, bool hasIn,
|
||||
const Location& inLocation, bool hasDo, const Location& doLocation, bool hasEnd);
|
||||
const Location& inLocation, bool hasDo, const Location& doLocation, bool DEPRECATED_hasEnd);
|
||||
|
||||
void visit(AstVisitor* visitor) override;
|
||||
|
||||
@ -699,7 +714,8 @@ public:
|
||||
bool hasDo = false;
|
||||
Location doLocation;
|
||||
|
||||
bool hasEnd = false;
|
||||
// TODO clip with FFlag::LuauClipExtraHasEndProps
|
||||
bool DEPRECATED_hasEnd = false;
|
||||
};
|
||||
|
||||
class AstStatAssign : public AstStat
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "Luau/Common.h"
|
||||
|
||||
LUAU_FASTFLAG(LuauFloorDivision)
|
||||
LUAU_FASTFLAG(LuauFloorDivision);
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
@ -164,7 +164,7 @@ void AstExprIndexExpr::visit(AstVisitor* visitor)
|
||||
|
||||
AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGenericType>& generics, const AstArray<AstGenericTypePack>& genericPacks,
|
||||
AstLocal* self, const AstArray<AstLocal*>& args, bool vararg, const Location& varargLocation, AstStatBlock* body, size_t functionDepth,
|
||||
const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation, AstTypePack* varargAnnotation, bool hasEnd,
|
||||
const AstName& debugname, const std::optional<AstTypeList>& returnAnnotation, AstTypePack* varargAnnotation, bool DEPRECATED_hasEnd,
|
||||
const std::optional<Location>& argLocation)
|
||||
: AstExpr(ClassIndex(), location)
|
||||
, generics(generics)
|
||||
@ -178,7 +178,7 @@ AstExprFunction::AstExprFunction(const Location& location, const AstArray<AstGen
|
||||
, body(body)
|
||||
, functionDepth(functionDepth)
|
||||
, debugname(debugname)
|
||||
, hasEnd(hasEnd)
|
||||
, DEPRECATED_hasEnd(DEPRECATED_hasEnd)
|
||||
, argLocation(argLocation)
|
||||
{
|
||||
}
|
||||
@ -397,14 +397,14 @@ void AstStatBlock::visit(AstVisitor* visitor)
|
||||
}
|
||||
|
||||
AstStatIf::AstStatIf(const Location& location, AstExpr* condition, AstStatBlock* thenbody, AstStat* elsebody,
|
||||
const std::optional<Location>& thenLocation, const std::optional<Location>& elseLocation, bool hasEnd)
|
||||
const std::optional<Location>& thenLocation, const std::optional<Location>& elseLocation, bool DEPRECATED_hasEnd)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, condition(condition)
|
||||
, thenbody(thenbody)
|
||||
, elsebody(elsebody)
|
||||
, thenLocation(thenLocation)
|
||||
, elseLocation(elseLocation)
|
||||
, hasEnd(hasEnd)
|
||||
, DEPRECATED_hasEnd(DEPRECATED_hasEnd)
|
||||
{
|
||||
}
|
||||
|
||||
@ -420,13 +420,13 @@ void AstStatIf::visit(AstVisitor* visitor)
|
||||
}
|
||||
}
|
||||
|
||||
AstStatWhile::AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation, bool hasEnd)
|
||||
AstStatWhile::AstStatWhile(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasDo, const Location& doLocation, bool DEPRECATED_hasEnd)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, condition(condition)
|
||||
, body(body)
|
||||
, hasDo(hasDo)
|
||||
, doLocation(doLocation)
|
||||
, hasEnd(hasEnd)
|
||||
, DEPRECATED_hasEnd(DEPRECATED_hasEnd)
|
||||
{
|
||||
}
|
||||
|
||||
@ -439,11 +439,11 @@ void AstStatWhile::visit(AstVisitor* visitor)
|
||||
}
|
||||
}
|
||||
|
||||
AstStatRepeat::AstStatRepeat(const Location& location, AstExpr* condition, AstStatBlock* body, bool hasUntil)
|
||||
AstStatRepeat::AstStatRepeat(const Location& location, AstExpr* condition, AstStatBlock* body, bool DEPRECATED_hasUntil)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, condition(condition)
|
||||
, body(body)
|
||||
, hasUntil(hasUntil)
|
||||
, DEPRECATED_hasUntil(DEPRECATED_hasUntil)
|
||||
{
|
||||
}
|
||||
|
||||
@ -528,7 +528,7 @@ void AstStatLocal::visit(AstVisitor* visitor)
|
||||
}
|
||||
|
||||
AstStatFor::AstStatFor(const Location& location, AstLocal* var, AstExpr* from, AstExpr* to, AstExpr* step, AstStatBlock* body, bool hasDo,
|
||||
const Location& doLocation, bool hasEnd)
|
||||
const Location& doLocation, bool DEPRECATED_hasEnd)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, var(var)
|
||||
, from(from)
|
||||
@ -537,7 +537,7 @@ AstStatFor::AstStatFor(const Location& location, AstLocal* var, AstExpr* from, A
|
||||
, body(body)
|
||||
, hasDo(hasDo)
|
||||
, doLocation(doLocation)
|
||||
, hasEnd(hasEnd)
|
||||
, DEPRECATED_hasEnd(DEPRECATED_hasEnd)
|
||||
{
|
||||
}
|
||||
|
||||
@ -559,7 +559,7 @@ void AstStatFor::visit(AstVisitor* visitor)
|
||||
}
|
||||
|
||||
AstStatForIn::AstStatForIn(const Location& location, const AstArray<AstLocal*>& vars, const AstArray<AstExpr*>& values, AstStatBlock* body,
|
||||
bool hasIn, const Location& inLocation, bool hasDo, const Location& doLocation, bool hasEnd)
|
||||
bool hasIn, const Location& inLocation, bool hasDo, const Location& doLocation, bool DEPRECATED_hasEnd)
|
||||
: AstStat(ClassIndex(), location)
|
||||
, vars(vars)
|
||||
, values(values)
|
||||
@ -568,7 +568,7 @@ AstStatForIn::AstStatForIn(const Location& location, const AstArray<AstLocal*>&
|
||||
, inLocation(inLocation)
|
||||
, hasDo(hasDo)
|
||||
, doLocation(doLocation)
|
||||
, hasEnd(hasEnd)
|
||||
, DEPRECATED_hasEnd(DEPRECATED_hasEnd)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
LUAU_FASTINTVARIABLE(LuauRecursionLimit, 1000)
|
||||
LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
|
||||
LUAU_FASTFLAGVARIABLE(LuauParseDeclareClassIndexer, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauClipExtraHasEndProps, false)
|
||||
LUAU_FASTFLAG(LuauFloorDivision)
|
||||
LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
|
||||
|
||||
@ -371,16 +372,18 @@ AstStat* Parser::parseIf()
|
||||
AstStat* elsebody = nullptr;
|
||||
Location end = start;
|
||||
std::optional<Location> elseLocation;
|
||||
bool hasEnd = false;
|
||||
bool DEPRECATED_hasEnd = false;
|
||||
|
||||
if (lexer.current().type == Lexeme::ReservedElseif)
|
||||
{
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
thenbody->hasEnd = true;
|
||||
unsigned int recursionCounterOld = recursionCounter;
|
||||
incrementRecursionCounter("elseif");
|
||||
elseLocation = lexer.current().location;
|
||||
elsebody = parseIf();
|
||||
end = elsebody->location;
|
||||
hasEnd = elsebody->as<AstStatIf>()->hasEnd;
|
||||
DEPRECATED_hasEnd = elsebody->as<AstStatIf>()->DEPRECATED_hasEnd;
|
||||
recursionCounter = recursionCounterOld;
|
||||
}
|
||||
else
|
||||
@ -389,6 +392,8 @@ AstStat* Parser::parseIf()
|
||||
|
||||
if (lexer.current().type == Lexeme::ReservedElse)
|
||||
{
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
thenbody->hasEnd = true;
|
||||
elseLocation = lexer.current().location;
|
||||
matchThenElse = lexer.current();
|
||||
nextLexeme();
|
||||
@ -399,10 +404,22 @@ AstStat* Parser::parseIf()
|
||||
|
||||
end = lexer.current().location;
|
||||
|
||||
hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchThenElse);
|
||||
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchThenElse);
|
||||
DEPRECATED_hasEnd = hasEnd;
|
||||
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
{
|
||||
if (elsebody)
|
||||
{
|
||||
if (AstStatBlock* elseBlock = elsebody->as<AstStatBlock>())
|
||||
elseBlock->hasEnd = hasEnd;
|
||||
}
|
||||
else
|
||||
thenbody->hasEnd = hasEnd;
|
||||
}
|
||||
}
|
||||
|
||||
return allocator.alloc<AstStatIf>(Location(start, end), cond, thenbody, elsebody, thenLocation, elseLocation, hasEnd);
|
||||
return allocator.alloc<AstStatIf>(Location(start, end), cond, thenbody, elsebody, thenLocation, elseLocation, DEPRECATED_hasEnd);
|
||||
}
|
||||
|
||||
// while exp do block end
|
||||
@ -426,6 +443,8 @@ AstStat* Parser::parseWhile()
|
||||
Location end = lexer.current().location;
|
||||
|
||||
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
body->hasEnd = hasEnd;
|
||||
|
||||
return allocator.alloc<AstStatWhile>(Location(start, end), cond, body, hasDo, matchDo.location, hasEnd);
|
||||
}
|
||||
@ -447,6 +466,8 @@ AstStat* Parser::parseRepeat()
|
||||
functionStack.back().loopDepth--;
|
||||
|
||||
bool hasUntil = expectMatchEndAndConsume(Lexeme::ReservedUntil, matchRepeat);
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
body->hasEnd = hasUntil;
|
||||
|
||||
AstExpr* cond = parseExpr();
|
||||
|
||||
@ -543,6 +564,8 @@ AstStat* Parser::parseFor()
|
||||
Location end = lexer.current().location;
|
||||
|
||||
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
body->hasEnd = hasEnd;
|
||||
|
||||
return allocator.alloc<AstStatFor>(Location(start, end), var, from, to, step, body, hasDo, matchDo.location, hasEnd);
|
||||
}
|
||||
@ -585,6 +608,8 @@ AstStat* Parser::parseFor()
|
||||
Location end = lexer.current().location;
|
||||
|
||||
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchDo);
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
body->hasEnd = hasEnd;
|
||||
|
||||
return allocator.alloc<AstStatForIn>(
|
||||
Location(start, end), copy(vars), copy(values), body, hasIn, inLocation, hasDo, matchDo.location, hasEnd);
|
||||
@ -1074,6 +1099,8 @@ std::pair<AstExprFunction*, AstLocal*> Parser::parseFunctionBody(
|
||||
Location end = lexer.current().location;
|
||||
|
||||
bool hasEnd = expectMatchEndAndConsume(Lexeme::ReservedEnd, matchFunction);
|
||||
if (FFlag::LuauClipExtraHasEndProps)
|
||||
body->hasEnd = hasEnd;
|
||||
|
||||
return {allocator.alloc<AstExprFunction>(Location(start, end), generics, genericPacks, self, vars, vararg, varargLocation, body,
|
||||
functionStack.size(), debugname, typelist, varargAnnotation, hasEnd, argLocation),
|
||||
|
@ -20,7 +20,7 @@ struct IrFunction;
|
||||
|
||||
void updateUseCounts(IrFunction& function);
|
||||
|
||||
void updateLastUseLocations(IrFunction& function);
|
||||
void updateLastUseLocations(IrFunction& function, const std::vector<uint32_t>& sortedBlocks);
|
||||
|
||||
uint32_t getNextInstUse(IrFunction& function, uint32_t targetInstIdx, uint32_t startInstIdx);
|
||||
|
||||
|
@ -271,7 +271,7 @@ std::vector<uint32_t> getSortedBlockOrder(IrFunction& function);
|
||||
|
||||
// Returns first non-dead block that comes after block at index 'i' in the sorted blocks array
|
||||
// 'dummy' block is returned if the end of array was reached
|
||||
IrBlock& getNextBlock(IrFunction& function, std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i);
|
||||
IrBlock& getNextBlock(IrFunction& function, const std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i);
|
||||
|
||||
} // namespace CodeGen
|
||||
} // namespace Luau
|
||||
|
@ -48,10 +48,9 @@ inline void gatherFunctions(std::vector<Proto*>& results, Proto* proto, unsigned
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder, typename IrLowering>
|
||||
inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, int bytecodeid, AssemblyOptions options)
|
||||
inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction& function, const std::vector<uint32_t>& sortedBlocks, int bytecodeid,
|
||||
AssemblyOptions options)
|
||||
{
|
||||
std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(function);
|
||||
|
||||
// For each IR instruction that begins a bytecode instruction, which bytecode instruction is it?
|
||||
std::vector<uint32_t> bcLocations(function.instructions.size() + 1, ~0u);
|
||||
|
||||
@ -202,22 +201,22 @@ inline bool lowerImpl(AssemblyBuilder& build, IrLowering& lowering, IrFunction&
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool lowerIr(
|
||||
X64::AssemblyBuilderX64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
|
||||
inline bool lowerIr(X64::AssemblyBuilderX64& build, IrBuilder& ir, const std::vector<uint32_t>& sortedBlocks, ModuleHelpers& helpers, Proto* proto,
|
||||
AssemblyOptions options, LoweringStats* stats)
|
||||
{
|
||||
optimizeMemoryOperandsX64(ir.function);
|
||||
|
||||
X64::IrLoweringX64 lowering(build, helpers, ir.function, stats);
|
||||
|
||||
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||
return lowerImpl(build, lowering, ir.function, sortedBlocks, proto->bytecodeid, options);
|
||||
}
|
||||
|
||||
inline bool lowerIr(
|
||||
A64::AssemblyBuilderA64& build, IrBuilder& ir, ModuleHelpers& helpers, Proto* proto, AssemblyOptions options, LoweringStats* stats)
|
||||
inline bool lowerIr(A64::AssemblyBuilderA64& build, IrBuilder& ir, const std::vector<uint32_t>& sortedBlocks, ModuleHelpers& helpers, Proto* proto,
|
||||
AssemblyOptions options, LoweringStats* stats)
|
||||
{
|
||||
A64::IrLoweringA64 lowering(build, helpers, ir.function, stats);
|
||||
|
||||
return lowerImpl(build, lowering, ir.function, proto->bytecodeid, options);
|
||||
return lowerImpl(build, lowering, ir.function, sortedBlocks, proto->bytecodeid, options);
|
||||
}
|
||||
|
||||
template<typename AssemblyBuilder>
|
||||
@ -237,7 +236,12 @@ inline bool lowerFunction(IrBuilder& ir, AssemblyBuilder& build, ModuleHelpers&
|
||||
createLinearBlocks(ir, useValueNumbering);
|
||||
}
|
||||
|
||||
return lowerIr(build, ir, helpers, proto, options, stats);
|
||||
std::vector<uint32_t> sortedBlocks = getSortedBlockOrder(ir.function);
|
||||
|
||||
// In order to allocate registers during lowering, we need to know where instruction results are last used
|
||||
updateLastUseLocations(ir.function, sortedBlocks);
|
||||
|
||||
return lowerIr(build, ir, sortedBlocks, helpers, proto, options, stats);
|
||||
}
|
||||
|
||||
} // namespace CodeGen
|
||||
|
@ -54,31 +54,47 @@ void updateUseCounts(IrFunction& function)
|
||||
}
|
||||
}
|
||||
|
||||
void updateLastUseLocations(IrFunction& function)
|
||||
void updateLastUseLocations(IrFunction& function, const std::vector<uint32_t>& sortedBlocks)
|
||||
{
|
||||
std::vector<IrInst>& instructions = function.instructions;
|
||||
|
||||
#if defined(LUAU_ASSERTENABLED)
|
||||
// Last use assignements should be called only once
|
||||
for (IrInst& inst : instructions)
|
||||
inst.lastUse = 0;
|
||||
LUAU_ASSERT(inst.lastUse == 0);
|
||||
#endif
|
||||
|
||||
for (size_t instIdx = 0; instIdx < instructions.size(); ++instIdx)
|
||||
for (size_t i = 0; i < sortedBlocks.size(); ++i)
|
||||
{
|
||||
IrInst& inst = instructions[instIdx];
|
||||
uint32_t blockIndex = sortedBlocks[i];
|
||||
IrBlock& block = function.blocks[blockIndex];
|
||||
|
||||
auto checkOp = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::Inst)
|
||||
instructions[op.index].lastUse = uint32_t(instIdx);
|
||||
};
|
||||
|
||||
if (isPseudo(inst.cmd))
|
||||
if (block.kind == IrBlockKind::Dead)
|
||||
continue;
|
||||
|
||||
checkOp(inst.a);
|
||||
checkOp(inst.b);
|
||||
checkOp(inst.c);
|
||||
checkOp(inst.d);
|
||||
checkOp(inst.e);
|
||||
checkOp(inst.f);
|
||||
LUAU_ASSERT(block.start != ~0u);
|
||||
LUAU_ASSERT(block.finish != ~0u);
|
||||
|
||||
for (uint32_t instIdx = block.start; instIdx <= block.finish; instIdx++)
|
||||
{
|
||||
LUAU_ASSERT(instIdx < function.instructions.size());
|
||||
IrInst& inst = instructions[instIdx];
|
||||
|
||||
auto checkOp = [&](IrOp op) {
|
||||
if (op.kind == IrOpKind::Inst)
|
||||
instructions[op.index].lastUse = uint32_t(instIdx);
|
||||
};
|
||||
|
||||
if (isPseudo(inst.cmd))
|
||||
continue;
|
||||
|
||||
checkOp(inst.a);
|
||||
checkOp(inst.b);
|
||||
checkOp(inst.c);
|
||||
checkOp(inst.d);
|
||||
checkOp(inst.e);
|
||||
checkOp(inst.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,9 +249,6 @@ IrLoweringA64::IrLoweringA64(AssemblyBuilderA64& build, ModuleHelpers& helpers,
|
||||
, valueTracker(function)
|
||||
, exitHandlerMap(~0u)
|
||||
{
|
||||
// In order to allocate registers during lowering, we need to know where instruction results are last used
|
||||
updateLastUseLocations(function);
|
||||
|
||||
valueTracker.setRestoreCallack(this, [](void* context, IrInst& inst) {
|
||||
IrLoweringA64* self = static_cast<IrLoweringA64*>(context);
|
||||
self->regs.restoreReg(self->build, inst);
|
||||
@ -2167,6 +2164,8 @@ AddressA64 IrLoweringA64::tempAddr(IrOp op, int offset)
|
||||
{
|
||||
// This is needed to tighten the bounds checks in the VmConst case below
|
||||
LUAU_ASSERT(offset % 4 == 0);
|
||||
// Full encoded range is wider depending on the load size, but this assertion helps establish a smaller guaranteed working range [0..4096)
|
||||
LUAU_ASSERT(offset >= 0 && unsigned(offset / 4) <= AssemblyBuilderA64::kMaxImmediate);
|
||||
|
||||
if (op.kind == IrOpKind::VmReg)
|
||||
return mem(rBase, vmRegOp(op) * sizeof(TValue) + offset);
|
||||
|
@ -31,9 +31,6 @@ IrLoweringX64::IrLoweringX64(AssemblyBuilderX64& build, ModuleHelpers& helpers,
|
||||
, valueTracker(function)
|
||||
, exitHandlerMap(~0u)
|
||||
{
|
||||
// In order to allocate registers during lowering, we need to know where instruction results are last used
|
||||
updateLastUseLocations(function);
|
||||
|
||||
valueTracker.setRestoreCallack(®s, [](void* context, IrInst& inst) {
|
||||
((IrRegAllocX64*)context)->restore(inst, false);
|
||||
});
|
||||
|
@ -886,7 +886,7 @@ std::vector<uint32_t> getSortedBlockOrder(IrFunction& function)
|
||||
return sortedBlocks;
|
||||
}
|
||||
|
||||
IrBlock& getNextBlock(IrFunction& function, std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i)
|
||||
IrBlock& getNextBlock(IrFunction& function, const std::vector<uint32_t>& sortedBlocks, IrBlock& dummy, size_t i)
|
||||
{
|
||||
for (size_t j = i + 1; j < sortedBlocks.size(); ++j)
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ LUAU_FASTFLAGVARIABLE(LuauCompileFenvNoBuiltinFold, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileTopCold, false)
|
||||
|
||||
LUAU_FASTFLAG(LuauFloorDivision)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFixContinueValidation, false)
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileFixContinueValidation2, false)
|
||||
|
||||
LUAU_FASTFLAGVARIABLE(LuauCompileContinueCloseUpvals, false)
|
||||
|
||||
@ -276,7 +276,7 @@ struct Compiler
|
||||
f.upvals = upvals;
|
||||
|
||||
// record information for inlining
|
||||
if (options.optimizationLevel >= 2 && !func->vararg && !getfenvUsed && !setfenvUsed)
|
||||
if (options.optimizationLevel >= 2 && !func->vararg && !func->self && !getfenvUsed && !setfenvUsed)
|
||||
{
|
||||
f.canInline = true;
|
||||
f.stackSize = stackSize;
|
||||
@ -665,14 +665,6 @@ struct Compiler
|
||||
else
|
||||
{
|
||||
locstants[arg.local] = arg.value;
|
||||
|
||||
if (FFlag::LuauCompileFixContinueValidation)
|
||||
{
|
||||
// Mark that optimization skipped allocation of this local
|
||||
Local& l = locals[arg.local];
|
||||
LUAU_ASSERT(!l.skipped);
|
||||
l.skipped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -721,23 +713,8 @@ struct Compiler
|
||||
{
|
||||
AstLocal* local = func->args.data[i];
|
||||
|
||||
if (FFlag::LuauCompileFixContinueValidation)
|
||||
{
|
||||
if (Constant* var = locstants.find(local); var && var->type != Constant::Type_Unknown)
|
||||
{
|
||||
var->type = Constant::Type_Unknown;
|
||||
|
||||
// Restore local allocation skip flag as well
|
||||
Local& l = locals[local];
|
||||
LUAU_ASSERT(l.skipped);
|
||||
l.skipped = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Constant* var = locstants.find(local))
|
||||
var->type = Constant::Type_Unknown;
|
||||
}
|
||||
if (Constant* var = locstants.find(local))
|
||||
var->type = Constant::Type_Unknown;
|
||||
}
|
||||
|
||||
foldConstants(constants, variables, locstants, builtinsFold, builtinsFoldMathK, func->body);
|
||||
@ -2510,12 +2487,18 @@ struct Compiler
|
||||
return;
|
||||
}
|
||||
|
||||
AstStat* continueStatement = extractStatContinue(stat->thenbody);
|
||||
AstStatContinue* continueStatement = extractStatContinue(stat->thenbody);
|
||||
|
||||
// Optimization: body is a "continue" statement with no "else" => we can directly continue in "then" case
|
||||
if (!stat->elsebody && continueStatement != nullptr && !areLocalsCaptured(loops.back().localOffsetContinue))
|
||||
{
|
||||
if (loops.back().untilCondition)
|
||||
if (FFlag::LuauCompileFixContinueValidation2)
|
||||
{
|
||||
// track continue statement for repeat..until validation (validateContinueUntil)
|
||||
if (!loops.back().continueUsed)
|
||||
loops.back().continueUsed = continueStatement;
|
||||
}
|
||||
else if (loops.back().untilCondition)
|
||||
validateContinueUntil(continueStatement, loops.back().untilCondition);
|
||||
|
||||
// fallthrough = proceed with the loop body as usual
|
||||
@ -2577,7 +2560,7 @@ struct Compiler
|
||||
size_t oldJumps = loopJumps.size();
|
||||
size_t oldLocals = localStack.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr, nullptr});
|
||||
hasLoops = true;
|
||||
|
||||
size_t loopLabel = bytecode.emitLabel();
|
||||
@ -2613,7 +2596,7 @@ struct Compiler
|
||||
size_t oldJumps = loopJumps.size();
|
||||
size_t oldLocals = localStack.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, stat->condition});
|
||||
loops.push_back({oldLocals, oldLocals, stat->condition, nullptr});
|
||||
hasLoops = true;
|
||||
|
||||
size_t loopLabel = bytecode.emitLabel();
|
||||
@ -2624,6 +2607,8 @@ struct Compiler
|
||||
|
||||
RegScope rs(this);
|
||||
|
||||
bool continueValidated = false;
|
||||
|
||||
for (size_t i = 0; i < body->body.size; ++i)
|
||||
{
|
||||
compileStat(body->body.data[i]);
|
||||
@ -2634,6 +2619,14 @@ struct Compiler
|
||||
// expression that continue will jump to.
|
||||
if (FFlag::LuauCompileContinueCloseUpvals)
|
||||
loops.back().localOffsetContinue = localStack.size();
|
||||
|
||||
// if continue was called from this statement, then any local defined after this in the loop body should not be accessed by until condition
|
||||
// it is sufficient to check this condition once, as if this holds for the first continue, it must hold for all subsequent continues.
|
||||
if (FFlag::LuauCompileFixContinueValidation2 && loops.back().continueUsed && !continueValidated)
|
||||
{
|
||||
validateContinueUntil(loops.back().continueUsed, stat->condition, body, i + 1);
|
||||
continueValidated = true;
|
||||
}
|
||||
}
|
||||
|
||||
size_t contLabel = bytecode.emitLabel();
|
||||
@ -2762,19 +2755,7 @@ struct Compiler
|
||||
{
|
||||
// Optimization: we don't need to allocate and assign const locals, since their uses will be constant-folded
|
||||
if (options.optimizationLevel >= 1 && options.debugLevel <= 1 && areLocalsRedundant(stat))
|
||||
{
|
||||
if (FFlag::LuauCompileFixContinueValidation)
|
||||
{
|
||||
// Mark that optimization skipped allocation of this local
|
||||
for (AstLocal* local : stat->vars)
|
||||
{
|
||||
Local& l = locals[local];
|
||||
l.skipped = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimization: for 1-1 local assignments, we can reuse the register *if* neither local is mutated
|
||||
if (options.optimizationLevel >= 1 && stat->vars.size == 1 && stat->values.size == 1)
|
||||
@ -2863,7 +2844,7 @@ struct Compiler
|
||||
size_t oldLocals = localStack.size();
|
||||
size_t oldJumps = loopJumps.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr, nullptr});
|
||||
|
||||
for (int iv = 0; iv < tripCount; ++iv)
|
||||
{
|
||||
@ -2914,7 +2895,7 @@ struct Compiler
|
||||
size_t oldLocals = localStack.size();
|
||||
size_t oldJumps = loopJumps.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr, nullptr});
|
||||
hasLoops = true;
|
||||
|
||||
// register layout: limit, step, index
|
||||
@ -2979,7 +2960,7 @@ struct Compiler
|
||||
size_t oldLocals = localStack.size();
|
||||
size_t oldJumps = loopJumps.size();
|
||||
|
||||
loops.push_back({oldLocals, oldLocals, nullptr});
|
||||
loops.push_back({oldLocals, oldLocals, nullptr, nullptr});
|
||||
hasLoops = true;
|
||||
|
||||
// register layout: generator, state, index, variables...
|
||||
@ -3391,7 +3372,13 @@ struct Compiler
|
||||
{
|
||||
LUAU_ASSERT(!loops.empty());
|
||||
|
||||
if (loops.back().untilCondition)
|
||||
if (FFlag::LuauCompileFixContinueValidation2)
|
||||
{
|
||||
// track continue statement for repeat..until validation (validateContinueUntil)
|
||||
if (!loops.back().continueUsed)
|
||||
loops.back().continueUsed = stat;
|
||||
}
|
||||
else if (loops.back().untilCondition)
|
||||
validateContinueUntil(stat, loops.back().untilCondition);
|
||||
|
||||
// before continuing, we need to close all local variables that were captured in closures since loop start
|
||||
@ -3477,6 +3464,7 @@ struct Compiler
|
||||
|
||||
void validateContinueUntil(AstStat* cont, AstExpr* condition)
|
||||
{
|
||||
LUAU_ASSERT(!FFlag::LuauCompileFixContinueValidation2);
|
||||
UndefinedLocalVisitor visitor(this);
|
||||
condition->visit(&visitor);
|
||||
|
||||
@ -3486,6 +3474,32 @@ struct Compiler
|
||||
visitor.undef->name.value, cont->location.begin.line + 1);
|
||||
}
|
||||
|
||||
void validateContinueUntil(AstStat* cont, AstExpr* condition, AstStatBlock* body, size_t start)
|
||||
{
|
||||
LUAU_ASSERT(FFlag::LuauCompileFixContinueValidation2);
|
||||
UndefinedLocalVisitor visitor(this);
|
||||
|
||||
for (size_t i = start; i < body->body.size; ++i)
|
||||
{
|
||||
if (AstStatLocal* stat = body->body.data[i]->as<AstStatLocal>())
|
||||
{
|
||||
for (AstLocal* local : stat->vars)
|
||||
visitor.locals.insert(local);
|
||||
}
|
||||
else if (AstStatLocalFunction* stat = body->body.data[i]->as<AstStatLocalFunction>())
|
||||
{
|
||||
visitor.locals.insert(stat->name);
|
||||
}
|
||||
}
|
||||
|
||||
condition->visit(&visitor);
|
||||
|
||||
if (visitor.undef)
|
||||
CompileError::raise(condition->location,
|
||||
"Local %s used in the repeat..until condition is undefined because continue statement on line %d jumps over it",
|
||||
visitor.undef->name.value, cont->location.begin.line + 1);
|
||||
}
|
||||
|
||||
void gatherConstUpvals(AstExprFunction* func)
|
||||
{
|
||||
ConstUpvalueVisitor visitor(this);
|
||||
@ -3702,18 +3716,24 @@ struct Compiler
|
||||
UndefinedLocalVisitor(Compiler* self)
|
||||
: self(self)
|
||||
, undef(nullptr)
|
||||
, locals(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void check(AstLocal* local)
|
||||
{
|
||||
Local& l = self->locals[local];
|
||||
if (FFlag::LuauCompileFixContinueValidation2)
|
||||
{
|
||||
if (!undef && locals.contains(local))
|
||||
undef = local;
|
||||
}
|
||||
else
|
||||
{
|
||||
Local& l = self->locals[local];
|
||||
|
||||
if (FFlag::LuauCompileFixContinueValidation && l.skipped)
|
||||
return;
|
||||
|
||||
if (!l.allocated && !undef)
|
||||
undef = local;
|
||||
if (!l.allocated && !undef)
|
||||
undef = local;
|
||||
}
|
||||
}
|
||||
|
||||
bool visit(AstExprLocal* node) override
|
||||
@ -3742,6 +3762,7 @@ struct Compiler
|
||||
|
||||
Compiler* self;
|
||||
AstLocal* undef;
|
||||
DenseHashSet<AstLocal*> locals;
|
||||
};
|
||||
|
||||
struct ConstUpvalueVisitor : AstVisitor
|
||||
@ -3837,7 +3858,6 @@ struct Compiler
|
||||
uint8_t reg = 0;
|
||||
bool allocated = false;
|
||||
bool captured = false;
|
||||
bool skipped = false;
|
||||
uint32_t debugpc = 0;
|
||||
};
|
||||
|
||||
@ -3858,7 +3878,10 @@ struct Compiler
|
||||
size_t localOffset;
|
||||
size_t localOffsetContinue;
|
||||
|
||||
// TODO: Remove with LuauCompileFixContinueValidation2
|
||||
AstExpr* untilCondition;
|
||||
|
||||
AstStatContinue* continueUsed;
|
||||
};
|
||||
|
||||
struct InlineArg
|
||||
|
@ -196,6 +196,7 @@ target_sources(Luau.Analysis PRIVATE
|
||||
Analysis/include/Luau/Transpiler.h
|
||||
Analysis/include/Luau/TxnLog.h
|
||||
Analysis/include/Luau/Type.h
|
||||
Analysis/include/Luau/TypePairHash.h
|
||||
Analysis/include/Luau/TypeArena.h
|
||||
Analysis/include/Luau/TypeAttach.h
|
||||
Analysis/include/Luau/TypeChecker2.h
|
||||
@ -441,6 +442,7 @@ if(TARGET Luau.UnitTest)
|
||||
tests/TypeInfer.test.cpp
|
||||
tests/TypeInfer.tryUnify.test.cpp
|
||||
tests/TypeInfer.typePacks.cpp
|
||||
tests/TypeInfer.typestates.test.cpp
|
||||
tests/TypeInfer.unionTypes.test.cpp
|
||||
tests/TypeInfer.unknownnever.test.cpp
|
||||
tests/TypePack.test.cpp
|
||||
|
@ -16,6 +16,8 @@ struct JsonEncoderFixture
|
||||
Allocator allocator;
|
||||
AstNameTable names{allocator};
|
||||
|
||||
ScopedFastFlag sff{"LuauClipExtraHasEndProps", true};
|
||||
|
||||
ParseResult parse(std::string_view src)
|
||||
{
|
||||
ParseOptions opts;
|
||||
@ -91,6 +93,8 @@ TEST_CASE("basic_escaping")
|
||||
|
||||
TEST_CASE("encode_AstStatBlock")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauClipExtraHasEndProps", true};
|
||||
|
||||
AstLocal astlocal{AstName{"a_local"}, Location(), nullptr, 0, 0, nullptr};
|
||||
AstLocal* astlocalarray[] = {&astlocal};
|
||||
|
||||
@ -103,9 +107,9 @@ TEST_CASE("encode_AstStatBlock")
|
||||
|
||||
AstStatBlock block{Location(), bodyArray};
|
||||
|
||||
CHECK_EQ(
|
||||
(R"({"type":"AstStatBlock","location":"0,0 - 0,0","body":[{"type":"AstStatLocal","location":"0,0 - 0,0","vars":[{"luauType":null,"name":"a_local","type":"AstLocal","location":"0,0 - 0,0"}],"values":[]}]})"),
|
||||
toJson(&block));
|
||||
CHECK(
|
||||
toJson(&block) ==
|
||||
(R"({"type":"AstStatBlock","location":"0,0 - 0,0","hasEnd":true,"body":[{"type":"AstStatLocal","location":"0,0 - 0,0","vars":[{"luauType":null,"name":"a_local","type":"AstLocal","location":"0,0 - 0,0"}],"values":[]}]})"));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_tables")
|
||||
@ -123,7 +127,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_tables")
|
||||
|
||||
CHECK(
|
||||
json ==
|
||||
R"({"type":"AstStatBlock","location":"0,0 - 6,4","body":[{"type":"AstStatLocal","location":"1,8 - 5,9","vars":[{"luauType":{"type":"AstTypeTable","location":"1,17 - 3,9","props":[{"name":"foo","type":"AstTableProp","location":"2,12 - 2,15","propType":{"type":"AstTypeReference","location":"2,17 - 2,23","name":"number","nameLocation":"2,17 - 2,23","parameters":[]}}],"indexer":null},"name":"x","type":"AstLocal","location":"1,14 - 1,15"}],"values":[{"type":"AstExprTable","location":"3,12 - 5,9","items":[{"type":"AstExprTableItem","kind":"record","key":{"type":"AstExprConstantString","location":"4,12 - 4,15","value":"foo"},"value":{"type":"AstExprConstantNumber","location":"4,18 - 4,21","value":123}}]}]}]})");
|
||||
R"({"type":"AstStatBlock","location":"0,0 - 6,4","hasEnd":true,"body":[{"type":"AstStatLocal","location":"1,8 - 5,9","vars":[{"luauType":{"type":"AstTypeTable","location":"1,17 - 3,9","props":[{"name":"foo","type":"AstTableProp","location":"2,12 - 2,15","propType":{"type":"AstTypeReference","location":"2,17 - 2,23","name":"number","nameLocation":"2,17 - 2,23","parameters":[]}}],"indexer":null},"name":"x","type":"AstLocal","location":"1,14 - 1,15"}],"values":[{"type":"AstExprTable","location":"3,12 - 5,9","items":[{"type":"AstExprTableItem","kind":"record","key":{"type":"AstExprConstantString","location":"4,12 - 4,15","value":"foo"},"value":{"type":"AstExprConstantNumber","location":"4,18 - 4,21","value":123}}]}]}]})");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_array")
|
||||
@ -135,7 +139,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_array")
|
||||
|
||||
CHECK(
|
||||
json ==
|
||||
R"({"type":"AstStatBlock","location":"0,0 - 0,17","body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})");
|
||||
R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_indexer")
|
||||
@ -147,7 +151,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_table_indexer")
|
||||
|
||||
CHECK(
|
||||
json ==
|
||||
R"({"type":"AstStatBlock","location":"0,0 - 0,17","body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})");
|
||||
R"({"type":"AstStatBlock","location":"0,0 - 0,17","hasEnd":true,"body":[{"type":"AstStatTypeAlias","location":"0,0 - 0,17","name":"X","generics":[],"genericPacks":[],"type":{"type":"AstTypeTable","location":"0,9 - 0,17","props":[],"indexer":{"location":"0,10 - 0,16","indexType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"number","nameLocation":"0,10 - 0,16","parameters":[]},"resultType":{"type":"AstTypeReference","location":"0,10 - 0,16","name":"string","nameLocation":"0,10 - 0,16","parameters":[]}}},"exported":false}]})");
|
||||
}
|
||||
|
||||
TEST_CASE("encode_AstExprGroup")
|
||||
@ -243,7 +247,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstExprFunction")
|
||||
AstExpr* expr = expectParseExpr("function (a) return a end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"luauType":null,"name":"a","type":"AstLocal","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":"","hasEnd":true})";
|
||||
R"({"type":"AstExprFunction","location":"0,4 - 0,29","generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,14 - 0,15"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,16 - 0,26","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,17 - 0,25","list":[{"type":"AstExprLocal","location":"0,24 - 0,25","local":{"luauType":null,"name":"a","type":"AstLocal","location":"0,14 - 0,15"}}]}]},"functionDepth":1,"debugname":""})";
|
||||
|
||||
CHECK(toJson(expr) == expected);
|
||||
}
|
||||
@ -311,7 +315,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatIf")
|
||||
AstStat* statement = expectParseStatement("if true then else end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatIf","location":"0,0 - 0,21","condition":{"type":"AstExprConstantBool","location":"0,3 - 0,7","value":true},"thenbody":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"elsebody":{"type":"AstStatBlock","location":"0,17 - 0,18","body":[]},"hasThen":true,"hasEnd":true})";
|
||||
R"({"type":"AstStatIf","location":"0,0 - 0,21","condition":{"type":"AstExprConstantBool","location":"0,3 - 0,7","value":true},"thenbody":{"type":"AstStatBlock","location":"0,12 - 0,13","hasEnd":true,"body":[]},"elsebody":{"type":"AstStatBlock","location":"0,17 - 0,18","hasEnd":true,"body":[]},"hasThen":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
@ -321,7 +325,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatWhile")
|
||||
AstStat* statement = expectParseStatement("while true do end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasDo":true,"hasEnd":true})";
|
||||
R"({"type":"AstStatWhile","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,14","hasEnd":true,"body":[]},"hasDo":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
@ -331,7 +335,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatRepeat")
|
||||
AstStat* statement = expectParseStatement("repeat until true");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatRepeat","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,13 - 0,17","value":true},"body":{"type":"AstStatBlock","location":"0,6 - 0,7","body":[]},"hasUntil":true})";
|
||||
R"({"type":"AstStatRepeat","location":"0,0 - 0,17","condition":{"type":"AstExprConstantBool","location":"0,13 - 0,17","value":true},"body":{"type":"AstStatBlock","location":"0,6 - 0,7","hasEnd":true,"body":[]}})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
@ -341,7 +345,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatBreak")
|
||||
AstStat* statement = expectParseStatement("while true do break end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true,"hasEnd":true})";
|
||||
R"({"type":"AstStatWhile","location":"0,0 - 0,23","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,20","hasEnd":true,"body":[{"type":"AstStatBreak","location":"0,14 - 0,19"}]},"hasDo":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
@ -351,7 +355,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatContinue")
|
||||
AstStat* statement = expectParseStatement("while true do continue end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true,"hasEnd":true})";
|
||||
R"({"type":"AstStatWhile","location":"0,0 - 0,26","condition":{"type":"AstExprConstantBool","location":"0,6 - 0,10","value":true},"body":{"type":"AstStatBlock","location":"0,13 - 0,23","hasEnd":true,"body":[{"type":"AstStatContinue","location":"0,14 - 0,22"}]},"hasDo":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
@ -361,7 +365,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatFor")
|
||||
AstStat* statement = expectParseStatement("for a=0,1 do end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"luauType":null,"name":"a","type":"AstLocal","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","body":[]},"hasDo":true,"hasEnd":true})";
|
||||
R"({"type":"AstStatFor","location":"0,0 - 0,16","var":{"luauType":null,"name":"a","type":"AstLocal","location":"0,4 - 0,5"},"from":{"type":"AstExprConstantNumber","location":"0,6 - 0,7","value":0},"to":{"type":"AstExprConstantNumber","location":"0,8 - 0,9","value":1},"body":{"type":"AstStatBlock","location":"0,12 - 0,13","hasEnd":true,"body":[]},"hasDo":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
@ -371,7 +375,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatForIn")
|
||||
AstStat* statement = expectParseStatement("for a in b do end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","body":[]},"hasIn":true,"hasDo":true,"hasEnd":true})";
|
||||
R"({"type":"AstStatForIn","location":"0,0 - 0,17","vars":[{"luauType":null,"name":"a","type":"AstLocal","location":"0,4 - 0,5"}],"values":[{"type":"AstExprGlobal","location":"0,9 - 0,10","global":"b"}],"body":{"type":"AstStatBlock","location":"0,13 - 0,14","hasEnd":true,"body":[]},"hasIn":true,"hasDo":true})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
@ -391,7 +395,7 @@ TEST_CASE_FIXTURE(JsonEncoderFixture, "encode_AstStatLocalFunction")
|
||||
AstStat* statement = expectParseStatement("local function a(b) return end");
|
||||
|
||||
std::string_view expected =
|
||||
R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"luauType":null,"name":"a","type":"AstLocal","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a","hasEnd":true}})";
|
||||
R"({"type":"AstStatLocalFunction","location":"0,0 - 0,30","name":{"luauType":null,"name":"a","type":"AstLocal","location":"0,15 - 0,16"},"func":{"type":"AstExprFunction","location":"0,0 - 0,30","generics":[],"genericPacks":[],"args":[{"luauType":null,"name":"b","type":"AstLocal","location":"0,17 - 0,18"}],"vararg":false,"varargLocation":"0,0 - 0,0","body":{"type":"AstStatBlock","location":"0,19 - 0,27","hasEnd":true,"body":[{"type":"AstStatReturn","location":"0,20 - 0,26","list":[]}]},"functionDepth":1,"debugname":"a"}})";
|
||||
|
||||
CHECK(toJson(statement) == expected);
|
||||
}
|
||||
|
@ -1911,7 +1911,7 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueIgnoresImplicitConstant")
|
||||
{
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true};
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
|
||||
|
||||
// this used to crash the compiler :(
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
@ -1928,7 +1928,7 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueIgnoresExplicitConstant")
|
||||
{
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true};
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
|
||||
|
||||
// Constants do not allocate locals and 'continue' validation should skip them if their lifetime already started
|
||||
CHECK_EQ("\n" + compileFunction0(R"(
|
||||
@ -1945,7 +1945,7 @@ RETURN R0 0
|
||||
|
||||
TEST_CASE("LoopContinueRespectsExplicitConstant")
|
||||
{
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true};
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
|
||||
|
||||
// If local lifetime hasn't started, even if it's a constant that will not receive an allocation, it cannot be jumped over
|
||||
try
|
||||
@ -1971,7 +1971,7 @@ until c
|
||||
|
||||
TEST_CASE("LoopContinueIgnoresImplicitConstantAfterInline")
|
||||
{
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation", true};
|
||||
ScopedFastFlag luauCompileFixContinueValidation{"LuauCompileFixContinueValidation2", true};
|
||||
|
||||
// Inlining might also replace some locals with constants instead of allocating them
|
||||
CHECK_EQ("\n" + compileFunction(R"(
|
||||
@ -1994,6 +1994,36 @@ RETURN R0 0
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_CASE("LoopContinueCorrectlyHandlesImplicitConstantAfterUnroll")
|
||||
{
|
||||
ScopedFastFlag sff{"LuauCompileFixContinueValidation2", true};
|
||||
ScopedFastInt sfi("LuauCompileLoopUnrollThreshold", 200);
|
||||
|
||||
// access to implicit constant that depends on the unrolled loop constant is still invalid even though we can constant-propagate it
|
||||
try
|
||||
{
|
||||
compileFunction(R"(
|
||||
for i = 1, 2 do
|
||||
s()
|
||||
repeat
|
||||
if i == 2 then
|
||||
continue
|
||||
end
|
||||
local x = i == 1 or a
|
||||
until f(x)
|
||||
end
|
||||
)", 0, 2);
|
||||
|
||||
CHECK(!"Expected CompileError");
|
||||
}
|
||||
catch (Luau::CompileError& e)
|
||||
{
|
||||
CHECK_EQ(e.getLocation().begin.line + 1, 9);
|
||||
CHECK_EQ(
|
||||
std::string(e.what()), "Local x used in the repeat..until condition is undefined because continue statement on line 6 jumps over it");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("LoopContinueUntilCapture")
|
||||
{
|
||||
ScopedFastFlag sff("LuauCompileContinueCloseUpvals", true);
|
||||
|
@ -757,8 +757,8 @@ TEST_CASE_FIXTURE(DifferFixture, "singleton_string")
|
||||
|
||||
TEST_CASE_FIXTURE(DifferFixtureWithBuiltins, "negation")
|
||||
{
|
||||
// Old solver does not correctly refine test types
|
||||
ScopedFastFlag sff{"DebugLuauDeferredConstraintResolution", true};
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local bar: { x: { y: unknown }}
|
||||
|
@ -174,7 +174,8 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
|
||||
{
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
ModulePtr module = Luau::check(*sourceModule, {}, builtinTypes, NotNull{&ice}, NotNull{&moduleResolver}, NotNull{&fileResolver},
|
||||
Mode mode = sourceModule->mode ? *sourceModule->mode : Mode::Strict;
|
||||
ModulePtr module = Luau::check(*sourceModule, mode, {}, builtinTypes, NotNull{&ice}, NotNull{&moduleResolver}, NotNull{&fileResolver},
|
||||
frontend.globals.globalScope, /*prepareModuleScope*/ nullptr, frontend.options, {});
|
||||
|
||||
Luau::lint(sourceModule->root, *sourceModule->names, frontend.globals.globalScope, module.get(), sourceModule->hotcomments, {});
|
||||
@ -194,7 +195,7 @@ AstStatBlock* Fixture::parse(const std::string& source, const ParseOptions& pars
|
||||
return result.root;
|
||||
}
|
||||
|
||||
CheckResult Fixture::check(Mode mode, std::string source)
|
||||
CheckResult Fixture::check(Mode mode, const std::string& source)
|
||||
{
|
||||
ModuleName mm = fromString(mainModuleName);
|
||||
configResolver.defaultConfig.mode = mode;
|
||||
|
@ -65,7 +65,7 @@ struct Fixture
|
||||
|
||||
// Throws Luau::ParseErrors if the parse fails.
|
||||
AstStatBlock* parse(const std::string& source, const ParseOptions& parseOptions = {});
|
||||
CheckResult check(Mode mode, std::string source);
|
||||
CheckResult check(Mode mode, const std::string& source);
|
||||
CheckResult check(const std::string& source);
|
||||
|
||||
LintResult lint(const std::string& source, const std::optional<LintOptions>& lintOptions = {});
|
||||
|
@ -1502,6 +1502,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiUntyped")
|
||||
}
|
||||
|
||||
LintResult result = lint(R"(
|
||||
-- TODO
|
||||
return function ()
|
||||
print(table.getn({}))
|
||||
table.foreach({}, function() end)
|
||||
@ -1514,6 +1515,35 @@ end
|
||||
CHECK_EQ(result.warnings[1].text, "Member 'table.foreach' is deprecated");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "DeprecatedApiFenv")
|
||||
{
|
||||
ScopedFastFlag sff("LuauLintDeprecatedFenv", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local f, g, h = ...
|
||||
|
||||
getfenv(1)
|
||||
getfenv(f :: () -> ())
|
||||
getfenv(g :: number)
|
||||
getfenv(h :: any)
|
||||
|
||||
setfenv(1, {})
|
||||
setfenv(f :: () -> (), {})
|
||||
setfenv(g :: number, {})
|
||||
setfenv(h :: any, {})
|
||||
)");
|
||||
|
||||
REQUIRE(4 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].text, "Function 'getfenv' is deprecated; consider using 'debug.info' instead");
|
||||
CHECK_EQ(result.warnings[0].location.begin.line + 1, 4);
|
||||
CHECK_EQ(result.warnings[1].text, "Function 'getfenv' is deprecated; consider using 'debug.info' instead");
|
||||
CHECK_EQ(result.warnings[1].location.begin.line + 1, 6);
|
||||
CHECK_EQ(result.warnings[2].text, "Function 'setfenv' is deprecated");
|
||||
CHECK_EQ(result.warnings[2].location.begin.line + 1, 9);
|
||||
CHECK_EQ(result.warnings[3].text, "Function 'setfenv' is deprecated");
|
||||
CHECK_EQ(result.warnings[3].location.begin.line + 1, 11);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperations")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
@ -1559,6 +1589,62 @@ table.create(42, {} :: {})
|
||||
result.warnings[9].text, "table.create with a table literal will reuse the same object for all elements; consider using a for loop instead");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "TableOperationsIndexer")
|
||||
{
|
||||
ScopedFastFlag sff("LuauLintTableIndexer", true);
|
||||
|
||||
LintResult result = lint(R"(
|
||||
local t1 = {} -- ok: empty
|
||||
local t2 = {1, 2} -- ok: array
|
||||
local t3 = { a = 1, b = 2 } -- not ok: dictionary
|
||||
local t4: {[number]: number} = {} -- ok: array
|
||||
local t5: {[string]: number} = {} -- not ok: dictionary
|
||||
local t6: typeof(setmetatable({1, 2}, {})) = {} -- ok: table with metatable
|
||||
local t7: string = "hello" -- ok: string
|
||||
local t8: {number} | {n: number} = {} -- ok: union
|
||||
|
||||
-- not ok
|
||||
print(#t3)
|
||||
print(#t5)
|
||||
ipairs(t5)
|
||||
|
||||
-- disabled
|
||||
-- ipairs(t3) adds indexer to t3, silencing error on #t3
|
||||
|
||||
-- ok
|
||||
print(#t1)
|
||||
print(#t2)
|
||||
print(#t4)
|
||||
print(#t6)
|
||||
print(#t7)
|
||||
print(#t8)
|
||||
|
||||
ipairs(t1)
|
||||
ipairs(t2)
|
||||
ipairs(t4)
|
||||
ipairs(t6)
|
||||
ipairs(t7)
|
||||
ipairs(t8)
|
||||
|
||||
-- ok, subtle: text is a string here implicitly, but the type annotation isn't available
|
||||
-- type checker assigns a type of generic table with the 'sub' member; we don't emit warnings on generic tables
|
||||
-- to avoid generating a false positive here
|
||||
function _impliedstring(element, text)
|
||||
for i = 1, #text do
|
||||
element:sendText(text:sub(i, i))
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
REQUIRE(3 == result.warnings.size());
|
||||
CHECK_EQ(result.warnings[0].location.begin.line + 1, 12);
|
||||
CHECK_EQ(result.warnings[0].text, "Using '#' on a table without an array part is likely a bug");
|
||||
CHECK_EQ(result.warnings[1].location.begin.line + 1, 13);
|
||||
CHECK_EQ(result.warnings[1].text, "Using '#' on a table with string keys is likely a bug");
|
||||
CHECK_EQ(result.warnings[2].location.begin.line + 1, 14);
|
||||
CHECK_EQ(result.warnings[2].text, "Using 'ipairs' on a table with string keys is likely a bug");
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "DuplicateConditions")
|
||||
{
|
||||
LintResult result = lint(R"(
|
||||
|
@ -15,102 +15,32 @@ using namespace Luau;
|
||||
struct NonStrictTypeCheckerFixture : Fixture
|
||||
{
|
||||
|
||||
ParseResult parse(std::string source)
|
||||
CheckResult checkNonStrict(const std::string& code)
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
return tryParse(source, opts);
|
||||
ScopedFastFlag flags[] = {
|
||||
{"LuauCheckedFunctionSyntax", true},
|
||||
};
|
||||
|
||||
return check(Mode::Nonstrict, code);
|
||||
}
|
||||
|
||||
std::string definitions = R"BUILTIN_SRC(
|
||||
declare function @checked abs(n: number): number
|
||||
abs("hi")
|
||||
)BUILTIN_SRC";
|
||||
};
|
||||
|
||||
|
||||
TEST_SUITE_BEGIN("NonStrictTypeCheckerTest");
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "basic")
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "simple_non_strict")
|
||||
{
|
||||
Luau::checkNonStrict(builtinTypes, nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_top_level_checked_fn")
|
||||
{
|
||||
std::string src = R"BUILTIN_SRC(
|
||||
auto res = checkNonStrict(R"BUILTIN_SRC(
|
||||
declare function @checked abs(n: number): number
|
||||
)BUILTIN_SRC";
|
||||
abs("hi")
|
||||
)BUILTIN_SRC");
|
||||
|
||||
ParseResult pr = parse(src);
|
||||
LUAU_ASSERT(pr.errors.size() == 0);
|
||||
|
||||
LUAU_ASSERT(pr.root->body.size == 1);
|
||||
AstStat* root = *(pr.root->body.data);
|
||||
auto func = root->as<AstStatDeclareFunction>();
|
||||
LUAU_ASSERT(func);
|
||||
LUAU_ASSERT(func->checkedFunction);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_declared_table_checked_member")
|
||||
{
|
||||
std::string src = R"BUILTIN_SRC(
|
||||
declare math : {
|
||||
abs : @checked (number) -> number
|
||||
}
|
||||
)BUILTIN_SRC";
|
||||
|
||||
ParseResult pr = parse(src);
|
||||
LUAU_ASSERT(pr.errors.size() == 0);
|
||||
|
||||
LUAU_ASSERT(pr.root->body.size == 1);
|
||||
AstStat* root = *(pr.root->body.data);
|
||||
auto glob = root->as<AstStatDeclareGlobal>();
|
||||
LUAU_ASSERT(glob);
|
||||
auto tbl = glob->type->as<AstTypeTable>();
|
||||
LUAU_ASSERT(tbl);
|
||||
LUAU_ASSERT(tbl->props.size == 1);
|
||||
auto prop = *tbl->props.data;
|
||||
auto func = prop.type->as<AstTypeFunction>();
|
||||
LUAU_ASSERT(func);
|
||||
LUAU_ASSERT(func->checkedFunction);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_outside_decl_fails")
|
||||
{
|
||||
auto src = R"(
|
||||
local @checked = 3
|
||||
)";
|
||||
|
||||
ParseResult pr = parse(src);
|
||||
LUAU_ASSERT(pr.errors.size() > 0);
|
||||
auto ts = pr.errors[1].getMessage();
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_in_and_out_of_decl_fails")
|
||||
{
|
||||
auto src = R"(
|
||||
local @checked = 3
|
||||
declare function @checked abs(n: number): number
|
||||
)";
|
||||
auto pr = parse(src);
|
||||
LUAU_ASSERT(pr.errors.size() == 2);
|
||||
LUAU_ASSERT(pr.errors[0].getLocation().begin.line == 1);
|
||||
LUAU_ASSERT(pr.errors[1].getLocation().begin.line == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "parse_checked_as_function_name_fails")
|
||||
{
|
||||
auto pr = parse(R"(
|
||||
function @checked(x: number) : number
|
||||
end
|
||||
)");
|
||||
LUAU_ASSERT(pr.errors.size() > 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(NonStrictTypeCheckerFixture, "cannot_use_@_as_variable_name")
|
||||
{
|
||||
auto pr = parse(R"(
|
||||
local @blah = 3
|
||||
)");
|
||||
|
||||
LUAU_ASSERT(pr.errors.size() > 0);
|
||||
// LUAU_REQUIRE_ERRORS(res);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -3006,4 +3006,110 @@ TEST_CASE_FIXTURE(Fixture, "parse_interpolated_string_with_lookahead_involved2")
|
||||
REQUIRE_MESSAGE(result.errors.empty(), result.errors[0].getMessage());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_top_level_checked_fn")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
|
||||
std::string src = R"BUILTIN_SRC(
|
||||
declare function @checked abs(n: number): number
|
||||
)BUILTIN_SRC";
|
||||
|
||||
ParseResult pr = tryParse(src, opts);
|
||||
LUAU_ASSERT(pr.errors.size() == 0);
|
||||
|
||||
LUAU_ASSERT(pr.root->body.size == 1);
|
||||
AstStat* root = *(pr.root->body.data);
|
||||
auto func = root->as<AstStatDeclareFunction>();
|
||||
LUAU_ASSERT(func);
|
||||
LUAU_ASSERT(func->checkedFunction);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_declared_table_checked_member")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
|
||||
const std::string src = R"BUILTIN_SRC(
|
||||
declare math : {
|
||||
abs : @checked (number) -> number
|
||||
}
|
||||
)BUILTIN_SRC";
|
||||
|
||||
ParseResult pr = tryParse(src, opts);
|
||||
LUAU_ASSERT(pr.errors.size() == 0);
|
||||
|
||||
LUAU_ASSERT(pr.root->body.size == 1);
|
||||
AstStat* root = *(pr.root->body.data);
|
||||
auto glob = root->as<AstStatDeclareGlobal>();
|
||||
LUAU_ASSERT(glob);
|
||||
auto tbl = glob->type->as<AstTypeTable>();
|
||||
LUAU_ASSERT(tbl);
|
||||
LUAU_ASSERT(tbl->props.size == 1);
|
||||
auto prop = *tbl->props.data;
|
||||
auto func = prop.type->as<AstTypeFunction>();
|
||||
LUAU_ASSERT(func);
|
||||
LUAU_ASSERT(func->checkedFunction);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_checked_outside_decl_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
|
||||
ParseResult pr = tryParse(R"(
|
||||
local @checked = 3
|
||||
)",
|
||||
opts);
|
||||
LUAU_ASSERT(pr.errors.size() > 0);
|
||||
auto ts = pr.errors[1].getMessage();
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_checked_in_and_out_of_decl_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
local @checked = 3
|
||||
declare function @checked abs(n: number): number
|
||||
)",
|
||||
opts);
|
||||
LUAU_ASSERT(pr.errors.size() == 2);
|
||||
LUAU_ASSERT(pr.errors[0].getLocation().begin.line == 1);
|
||||
LUAU_ASSERT(pr.errors[1].getLocation().begin.line == 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "parse_checked_as_function_name_fails")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
function @checked(x: number) : number
|
||||
end
|
||||
)",
|
||||
opts);
|
||||
LUAU_ASSERT(pr.errors.size() > 0);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cannot_use_@_as_variable_name")
|
||||
{
|
||||
ParseOptions opts;
|
||||
opts.allowDeclarationSyntax = true;
|
||||
ScopedFastFlag sff{"LuauCheckedFunctionSyntax", true};
|
||||
|
||||
auto pr = tryParse(R"(
|
||||
local @blah = 3
|
||||
)",
|
||||
opts);
|
||||
|
||||
LUAU_ASSERT(pr.errors.size() > 0);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1038,4 +1038,45 @@ TEST_IS_NOT_SUBTYPE(idx(builtinTypes->stringType, builtinTypes->numberType), tbl
|
||||
TEST_IS_SUBTYPE(tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}}), tbl({{"X", builtinTypes->numberType}}));
|
||||
TEST_IS_NOT_SUBTYPE(tbl({{"X", builtinTypes->numberType}}), tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}}));
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "interior_tests_are_cached")
|
||||
{
|
||||
TypeId tableA = tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}});
|
||||
TypeId tableB = tbl({{"X", builtinTypes->optionalNumberType}, {"Y", builtinTypes->optionalNumberType}});
|
||||
|
||||
CHECK_IS_NOT_SUBTYPE(tableA, tableB);
|
||||
|
||||
const SubtypingResult* cachedResult = subtyping.peekCache().find({builtinTypes->numberType, builtinTypes->optionalNumberType});
|
||||
REQUIRE(cachedResult);
|
||||
|
||||
CHECK(cachedResult->isSubtype);
|
||||
|
||||
cachedResult = subtyping.peekCache().find({tableA, tableB});
|
||||
REQUIRE(cachedResult);
|
||||
|
||||
CHECK(!cachedResult->isSubtype);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "results_that_are_contingent_on_generics_are_not_cached")
|
||||
{
|
||||
// <T>(T) -> T <: (number) -> number
|
||||
CHECK_IS_SUBTYPE(genericTToTType, numberToNumberType);
|
||||
|
||||
CHECK(subtyping.peekCache().empty());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SubtypeFixture, "dont_cache_tests_involving_cycles")
|
||||
{
|
||||
TypeId tableA = arena.addType(BlockedType{});
|
||||
TypeId tableA2 = tbl({{"self", tableA}});
|
||||
asMutable(tableA)->ty.emplace<BoundType>(tableA2);
|
||||
|
||||
TypeId tableB = arena.addType(BlockedType{});
|
||||
TypeId tableB2 = tbl({{"self", tableB}});
|
||||
asMutable(tableB)->ty.emplace<BoundType>(tableB2);
|
||||
|
||||
CHECK_IS_SUBTYPE(tableA, tableB);
|
||||
|
||||
CHECK(!subtyping.peekCache().find({tableA, tableB}));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -943,13 +943,13 @@ R"(Type
|
||||
could not be converted into
|
||||
'{ a: number, b: string, c: { d: number } }'
|
||||
caused by:
|
||||
Property 'c' is not compatible.
|
||||
Property 'c' is not compatible.
|
||||
Type
|
||||
'{| d: string |}'
|
||||
could not be converted into
|
||||
'{ d: number }'
|
||||
caused by:
|
||||
Property 'd' is not compatible.
|
||||
Property 'd' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)"
|
||||
:
|
||||
R"(Type
|
||||
@ -957,13 +957,13 @@ R"(Type
|
||||
could not be converted into
|
||||
'{| a: number, b: string, c: {| d: number |} |}'
|
||||
caused by:
|
||||
Property 'c' is not compatible.
|
||||
Property 'c' is not compatible.
|
||||
Type
|
||||
'{ d: string }'
|
||||
could not be converted into
|
||||
'{| d: number |}'
|
||||
caused by:
|
||||
Property 'd' is not compatible.
|
||||
Property 'd' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
//clang-format on
|
||||
//
|
||||
|
@ -149,13 +149,13 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "sort_with_bad_predicate")
|
||||
could not be converted into
|
||||
'((string, string) -> boolean)?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
None of the union options are compatible. For example:
|
||||
Type
|
||||
'(number, number) -> boolean'
|
||||
could not be converted into
|
||||
'(string, string) -> boolean'
|
||||
caused by:
|
||||
Argument #1 type is not compatible.
|
||||
Argument #1 type is not compatible.
|
||||
Type 'string' could not be converted into 'number')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
@ -383,7 +383,7 @@ b(a)
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'Vector2' could not be converted into '{- X: number, Y: string -}'
|
||||
caused by:
|
||||
Property 'Y' is not compatible.
|
||||
Property 'Y' is not compatible.
|
||||
Type 'number' could not be converted into 'string')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -466,7 +466,7 @@ local b: B = a
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'x' is not compatible.
|
||||
Property 'x' is not compatible.
|
||||
Type 'ChildClass' could not be converted into 'BaseClass' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
@ -1373,7 +1373,7 @@ local b: B = a
|
||||
could not be converted into
|
||||
'(number, string) -> string'
|
||||
caused by:
|
||||
Argument #2 type is not compatible.
|
||||
Argument #2 type is not compatible.
|
||||
Type 'string' could not be converted into 'number')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -1414,7 +1414,7 @@ local b: B = a
|
||||
could not be converted into
|
||||
'(number, number) -> number'
|
||||
caused by:
|
||||
Return type is not compatible.
|
||||
Return type is not compatible.
|
||||
Type 'string' could not be converted into 'number')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -1435,7 +1435,7 @@ local b: B = a
|
||||
could not be converted into
|
||||
'(number, number) -> (number, boolean)'
|
||||
caused by:
|
||||
Return #2 type is not compatible.
|
||||
Return #2 type is not compatible.
|
||||
Type 'string' could not be converted into 'boolean')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -1565,13 +1565,13 @@ end
|
||||
could not be converted into
|
||||
'((number) -> number)?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
None of the union options are compatible. For example:
|
||||
Type
|
||||
'(string) -> string'
|
||||
could not be converted into
|
||||
'(number) -> number'
|
||||
caused by:
|
||||
Argument #1 type is not compatible.
|
||||
Argument #1 type is not compatible.
|
||||
Type 'number' could not be converted into 'string')");
|
||||
CHECK_EQ(toString(result.errors[1]), R"(Type 'string' could not be converted into 'number')");
|
||||
}
|
||||
@ -2042,7 +2042,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "param_1_and_2_both_takes_the_same_generic_bu
|
||||
|
||||
const std::string expected = R"(Type '{ x: number }' could not be converted into 'vec2?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
None of the union options are compatible. For example:
|
||||
Table type '{ x: number }' not compatible with type 'vec2' because the former is missing field 'y')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
CHECK_EQ("Type 'vec2' could not be converted into 'number'", toString(result.errors[1]));
|
||||
|
@ -727,10 +727,10 @@ y.a.c = y
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
const std::string expected = R"(Type 'y' could not be converted into 'T<string>'
|
||||
caused by:
|
||||
Property 'a' is not compatible.
|
||||
Property 'a' is not compatible.
|
||||
Type '{ c: T<string>?, d: number }' could not be converted into 'U<string>'
|
||||
caused by:
|
||||
Property 'd' is not compatible.
|
||||
Property 'd' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
@ -397,7 +397,7 @@ local a: XYZ = 3
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'number' could not be converted into 'X & Y & Z'
|
||||
caused by:
|
||||
Not all intersection parts are compatible.
|
||||
Not all intersection parts are compatible.
|
||||
Type 'number' could not be converted into 'X')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
@ -412,7 +412,7 @@ local b: B.T = a
|
||||
CheckResult result = frontend.check("game/C");
|
||||
const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
|
||||
caused by:
|
||||
Property 'x' is not compatible.
|
||||
Property 'x' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
@ -447,7 +447,7 @@ local b: B.T = a
|
||||
CheckResult result = frontend.check("game/D");
|
||||
const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
|
||||
caused by:
|
||||
Property 'x' is not compatible.
|
||||
Property 'x' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
|
@ -263,13 +263,18 @@ TEST_CASE_FIXTURE(Fixture, "cannot_indirectly_compare_types_that_do_not_have_a_m
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
GenericError* gen = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(gen != nullptr);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(gen->message == "Types 'a' and 'b' cannot be compared with < because neither type has a metatable");
|
||||
{
|
||||
UninhabitedTypeFamily* utf = get<UninhabitedTypeFamily>(result.errors[0]);
|
||||
REQUIRE(utf);
|
||||
REQUIRE_EQ(toString(utf->ty), "lt<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
GenericError* gen = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(gen != nullptr);
|
||||
REQUIRE_EQ(gen->message, "Type a cannot be compared with < because it has no metatable");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_indirectly_compare_types_that_do_not_offer_overloaded_ordering_operators")
|
||||
@ -288,13 +293,18 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_indirectly_compare_types_that_do_not_
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
GenericError* gen = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(gen != nullptr);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK(gen->message == "Types 'M' and 'M' cannot be compared with < because neither type's metatable has a '__lt' metamethod");
|
||||
{
|
||||
UninhabitedTypeFamily* utf = get<UninhabitedTypeFamily>(result.errors[0]);
|
||||
REQUIRE(utf);
|
||||
REQUIRE_EQ(toString(utf->ty), "lt<M, M>");
|
||||
}
|
||||
else
|
||||
{
|
||||
GenericError* gen = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(gen != nullptr);
|
||||
REQUIRE_EQ(gen->message, "Table M does not offer metamethod __lt");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "cannot_compare_tables_that_do_not_have_the_same_metatable")
|
||||
@ -727,13 +737,18 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
GenericError* ge = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(ge);
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Types 'boolean' and 'boolean' cannot be compared with relational operator <", ge->message);
|
||||
{
|
||||
UninhabitedTypeFamily* utf = get<UninhabitedTypeFamily>(result.errors[0]);
|
||||
REQUIRE(utf);
|
||||
REQUIRE_EQ(toString(utf->ty), "lt<boolean, boolean>");
|
||||
}
|
||||
else
|
||||
{
|
||||
GenericError* ge = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(ge);
|
||||
CHECK_EQ("Type 'boolean' cannot be compared with relational operator <", ge->message);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operators2")
|
||||
@ -746,12 +761,18 @@ TEST_CASE_FIXTURE(Fixture, "error_on_invalid_operand_types_to_relational_operato
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
|
||||
GenericError* ge = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(ge);
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
CHECK_EQ("Types 'number | string' and 'number | string' cannot be compared with relational operator <", ge->message);
|
||||
{
|
||||
UninhabitedTypeFamily* utf = get<UninhabitedTypeFamily>(result.errors[0]);
|
||||
REQUIRE(utf);
|
||||
REQUIRE_EQ(toString(utf->ty), "lt<number | string, number | string>");
|
||||
}
|
||||
else
|
||||
{
|
||||
GenericError* ge = get<GenericError>(result.errors[0]);
|
||||
REQUIRE(ge);
|
||||
CHECK_EQ("Type 'number | string' cannot be compared with relational operator <", ge->message);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "cli_38355_recursive_union")
|
||||
@ -915,7 +936,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_any_in_all_modes_when_lhs_is_unknown")
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Add<a, b>");
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> add<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -947,7 +968,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_subtraction")
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Sub<a, b>");
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> sub<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -967,7 +988,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_multiplication")
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Mul<a, b>");
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> mul<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -987,7 +1008,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_division")
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Div<a, b>");
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> div<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1009,7 +1030,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_floor_division")
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> FloorDiv<a, b>");
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> idiv<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1029,7 +1050,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_exponentiation")
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Exp<a, b>");
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> pow<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1049,7 +1070,7 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_modulo")
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> Mod<a, b>");
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> mod<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1058,6 +1079,26 @@ TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_modulo")
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(Fixture, "infer_type_for_generic_concat")
|
||||
{
|
||||
CheckResult result = check(Mode::Strict, R"(
|
||||
local function f(x, y)
|
||||
return x .. y
|
||||
end
|
||||
)");
|
||||
|
||||
if (FFlag::DebugLuauDeferredConstraintResolution)
|
||||
{
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK(toString(requireType("f")) == "<a, b>(a, b) -> concat<a, b>");
|
||||
}
|
||||
else
|
||||
{
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(toString(result.errors[0]), "Unknown type used in .. operation; consider adding a type annotation to 'x'");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "equality_operations_succeed_if_any_union_branch_succeeds")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
|
@ -817,7 +817,7 @@ TEST_CASE_FIXTURE(Fixture, "assign_table_with_refined_property_with_a_similar_ty
|
||||
could not be converted into
|
||||
'{| x: number |}'
|
||||
caused by:
|
||||
Property 'x' is not compatible.
|
||||
Property 'x' is not compatible.
|
||||
Type 'number?' could not be converted into 'number' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -1099,4 +1099,40 @@ foo(1 :: any)
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "luau_roact_useState_minimization")
|
||||
{
|
||||
// We don't expect this test to work on the old solver, but it also does not yet work on the new solver.
|
||||
// So, we can't just put a scoped fast flag here, or it would block CI.
|
||||
if (!FFlag::DebugLuauDeferredConstraintResolution)
|
||||
return;
|
||||
|
||||
CheckResult result = check(R"(
|
||||
type BasicStateAction<S> = ((S) -> S) | S
|
||||
type Dispatch<A> = (A) -> ()
|
||||
|
||||
local function useState<S>(
|
||||
initialState: (() -> S) | S
|
||||
): (S, Dispatch<BasicStateAction<S>>)
|
||||
-- fake impl that obeys types
|
||||
local val = if type(initialState) == "function" then initialState() else initialState
|
||||
return val, function(value)
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
local test, setTest = useState(nil :: string?)
|
||||
|
||||
setTest(nil) -- this line causes the type to be narrowed in the old solver!!!
|
||||
|
||||
local function update(value: string)
|
||||
print(test)
|
||||
setTest(value)
|
||||
end
|
||||
|
||||
update("hello")
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
||||
|
@ -1908,22 +1908,23 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "refine_unknown_to_table")
|
||||
// this test is DCR-only as an instance of DCR fixing a bug in the old solver
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a : unknown = nil
|
||||
|
||||
local idx, val
|
||||
|
||||
if typeof(a) == "table" then
|
||||
for i, v in a do
|
||||
idx = i
|
||||
val = v
|
||||
local function f(a: unknown)
|
||||
if typeof(a) == "table" then
|
||||
for i, v in a do
|
||||
idx = i
|
||||
val = v
|
||||
end
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
|
||||
CHECK_EQ("unknown", toString(requireType("idx")));
|
||||
CHECK_EQ("unknown", toString(requireType("val")));
|
||||
// TODO: they should be `unknown`, not `nil`.
|
||||
CHECK_EQ("nil", toString(requireType("idx")));
|
||||
CHECK_EQ("nil", toString(requireType("val")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BuiltinsFixture, "conditional_refinement_should_stay_error_suppressing")
|
||||
|
@ -327,7 +327,7 @@ local a: Animal = { tag = 'cat', cafood = 'something' }
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'a' could not be converted into 'Cat | Dog'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
None of the union options are compatible. For example:
|
||||
Table type 'a' not compatible with type 'Cat' because the former is missing field 'catfood')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -345,7 +345,7 @@ local a: Result = { success = false, result = 'something' }
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'a' could not be converted into 'Bad | Good'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
None of the union options are compatible. For example:
|
||||
Table type 'a' not compatible with type 'Bad' because the former is missing field 'error')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
@ -2119,7 +2119,7 @@ local b: B = a
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'y' is not compatible.
|
||||
Property 'y' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -2140,10 +2140,10 @@ local b: B = a
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property 'b' is not compatible.
|
||||
Property 'b' is not compatible.
|
||||
Type 'AS' could not be converted into 'BS'
|
||||
caused by:
|
||||
Property 'y' is not compatible.
|
||||
Property 'y' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -2167,7 +2167,7 @@ caused by:
|
||||
could not be converted into
|
||||
'{ x: number, y: number }'
|
||||
caused by:
|
||||
Property 'y' is not compatible.
|
||||
Property 'y' is not compatible.
|
||||
Type 'string' could not be converted into 'number' in an invariant context)";
|
||||
const std::string expected2 = R"(Type 'b2' could not be converted into 'a2'
|
||||
caused by:
|
||||
@ -2176,7 +2176,7 @@ caused by:
|
||||
could not be converted into
|
||||
'{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible.
|
||||
Property '__call' is not compatible.
|
||||
Type
|
||||
'<a, b>(a, b) -> ()'
|
||||
could not be converted into
|
||||
@ -2188,7 +2188,7 @@ caused by:
|
||||
could not be converted into
|
||||
'{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible.
|
||||
Property '__call' is not compatible.
|
||||
Type
|
||||
'<a, b>(a, b) -> ()'
|
||||
could not be converted into
|
||||
@ -2209,7 +2209,7 @@ caused by:
|
||||
could not be converted into
|
||||
'{ __call: <a>(a) -> () }'
|
||||
caused by:
|
||||
Property '__call' is not compatible.
|
||||
Property '__call' is not compatible.
|
||||
Type
|
||||
'(a, b) -> ()'
|
||||
could not be converted into
|
||||
@ -2231,7 +2231,7 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_key")
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property '[indexer key]' is not compatible.
|
||||
Property '[indexer key]' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -2249,7 +2249,7 @@ TEST_CASE_FIXTURE(Fixture, "error_detailed_indexer_value")
|
||||
LUAU_REQUIRE_ERRORS(result);
|
||||
const std::string expected = R"(Type 'A' could not be converted into 'B'
|
||||
caused by:
|
||||
Property '[indexer value]' is not compatible.
|
||||
Property '[indexer value]' is not compatible.
|
||||
Type 'number' could not be converted into 'string' in an invariant context)";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -2287,7 +2287,7 @@ local y: number = tmp.p.y
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'tmp' could not be converted into 'HasSuper'
|
||||
caused by:
|
||||
Property 'p' is not compatible.
|
||||
Property 'p' is not compatible.
|
||||
Table type '{ x: number, y: number }' not compatible with type 'Super' because the former has extra field 'y')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -3399,7 +3399,7 @@ TEST_CASE_FIXTURE(Fixture, "scalar_is_not_a_subtype_of_a_compatible_polymorphic_
|
||||
const std::string expected1 =
|
||||
R"(Type 'string' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected1, toString(result.errors[0]));
|
||||
|
||||
@ -3407,7 +3407,7 @@ Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutel
|
||||
const std::string expected2 =
|
||||
R"(Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected2, toString(result.errors[1]));
|
||||
|
||||
@ -3416,10 +3416,10 @@ Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutel
|
||||
could not be converted into
|
||||
't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
Not all union options are compatible.
|
||||
Not all union options are compatible.
|
||||
Type '"bar"' could not be converted into 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {- absolutely_no_scalar_has_this_method: (t1) -> (a...) -}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
CHECK_EQ(expected3, toString(result.errors[2]));
|
||||
}
|
||||
@ -3450,7 +3450,7 @@ TEST_CASE_FIXTURE(Fixture, "a_free_shape_cannot_turn_into_a_scalar_if_it_is_not_
|
||||
const std::string expected =
|
||||
R"(Type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' could not be converted into 'string'
|
||||
caused by:
|
||||
The former's metatable does not satisfy the requirements.
|
||||
The former's metatable does not satisfy the requirements.
|
||||
Table type 'typeof(string)' not compatible with type 't1 where t1 = {+ absolutely_no_scalar_has_this_method: (t1) -> (a, b...) +}' because the former is missing field 'absolutely_no_scalar_has_this_method')";
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
|
@ -1045,19 +1045,19 @@ TEST_CASE_FIXTURE(Fixture, "cli_50041_committing_txnlog_in_apollo_client_error")
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'Policies' from 'MainModule' could not be converted into 'Policies' from 'MainModule'
|
||||
caused by:
|
||||
Property 'getStoreFieldName' is not compatible.
|
||||
Property 'getStoreFieldName' is not compatible.
|
||||
Type
|
||||
'(Policies, FieldSpecifier & {| from: number? |}) -> (a, b...)'
|
||||
could not be converted into
|
||||
'(Policies, FieldSpecifier) -> string'
|
||||
caused by:
|
||||
Argument #2 type is not compatible.
|
||||
Argument #2 type is not compatible.
|
||||
Type
|
||||
'FieldSpecifier'
|
||||
could not be converted into
|
||||
'FieldSpecifier & {| from: number? |}'
|
||||
caused by:
|
||||
Not all intersection parts are compatible.
|
||||
Not all intersection parts are compatible.
|
||||
Table type 'FieldSpecifier' not compatible with type '{| from: number? |}' because the former has extra field 'fieldName')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
|
122
tests/TypeInfer.typestates.test.cpp
Normal file
122
tests/TypeInfer.typestates.test.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
// 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(DebugLuauDeferredConstraintResolution)
|
||||
|
||||
using namespace Luau;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct TypeStateFixture : BuiltinsFixture
|
||||
{
|
||||
ScopedFastFlag dcr{"DebugLuauDeferredConstraintResolution", true};
|
||||
};
|
||||
}
|
||||
|
||||
TEST_SUITE_BEGIN("TypeStatesTest");
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "initialize_x_of_type_string_or_nil_with_nil")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x: string? = nil
|
||||
local a = x
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("nil" == toString(requireType("a")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "extraneous_lvalues_are_populated_with_nil")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(): (string, number)
|
||||
return "hello", 5
|
||||
end
|
||||
|
||||
local x, y, z = f()
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK("Function only returns 2 values, but 3 are required here" == toString(result.errors[0]));
|
||||
CHECK("string" == toString(requireType("x")));
|
||||
CHECK("number" == toString(requireType("y")));
|
||||
CHECK("nil" == toString(requireType("z")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "assign_different_values_to_x")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x: string? = nil
|
||||
local a = x
|
||||
x = "hello!"
|
||||
local b = x
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("nil" == toString(requireType("a")));
|
||||
CHECK("string" == toString(requireType("b")));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "parameter_x_was_constrained_by_two_types")
|
||||
{
|
||||
// Parameter `x` has a fresh type `'x` bounded by `never` and `unknown`.
|
||||
// The first use of `x` constrains `x`'s upper bound by `string | number`.
|
||||
// The second use of `x`, aliased by `y`, constrains `x`'s upper bound by `string?`.
|
||||
// This results in `'x <: (string | number) & (string?)`.
|
||||
// The principal type of the upper bound is `string`.
|
||||
CheckResult result = check(R"(
|
||||
local function f(x): string?
|
||||
local y: string | number = x
|
||||
return y
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
CHECK("(string) -> string?" == toString(requireType("f")));
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "local_that_will_be_assigned_later")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local x: string
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "refine_a_local_and_then_assign_it")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
if typeof(x) == "string" then
|
||||
x = nil
|
||||
end
|
||||
|
||||
local y: nil = x
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_NO_ERRORS(result);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE_FIXTURE(TypeStateFixture, "assign_a_local_and_then_refine_it")
|
||||
{
|
||||
CheckResult result = check(R"(
|
||||
local function f(x: string?)
|
||||
x = nil
|
||||
|
||||
if typeof(x) == "string" then
|
||||
local y: typeof(x) = "hello"
|
||||
end
|
||||
end
|
||||
)");
|
||||
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
CHECK("Type 'string' could not be converted into 'never'" == toString(result.errors[0]));
|
||||
}
|
||||
|
||||
TEST_SUITE_END();
|
@ -222,9 +222,9 @@ TEST_CASE_FIXTURE(Fixture, "union_equality_comparisons")
|
||||
type B = number | nil
|
||||
type C = number | boolean
|
||||
|
||||
local a: A = 1
|
||||
local b: B = nil
|
||||
local c: C = true
|
||||
local a = 1 :: A
|
||||
local b = nil :: B
|
||||
local c = true :: C
|
||||
local n = 1
|
||||
|
||||
local x = a == b
|
||||
@ -517,7 +517,7 @@ local a: X? = { w = 4 }
|
||||
LUAU_REQUIRE_ERROR_COUNT(1, result);
|
||||
const std::string expected = R"(Type 'a' could not be converted into 'X?'
|
||||
caused by:
|
||||
None of the union options are compatible. For example:
|
||||
None of the union options are compatible. For example:
|
||||
Table type 'a' not compatible with type 'X' because the former is missing field 'x')";
|
||||
CHECK_EQ(expected, toString(result.errors[0]));
|
||||
}
|
||||
@ -843,7 +843,7 @@ TEST_CASE_FIXTURE(Fixture, "suppress_errors_for_prop_lookup_of_a_union_that_incl
|
||||
registerHiddenTypes(&frontend);
|
||||
|
||||
CheckResult result = check(R"(
|
||||
local a : err | Not<nil>
|
||||
local a = 5 :: err | Not<nil>
|
||||
|
||||
local b = a.foo
|
||||
)");
|
||||
|
@ -1,16 +1,24 @@
|
||||
AnnotationTests.cloned_interface_maintains_pointers_between_definitions
|
||||
AnnotationTests.infer_type_of_value_a_via_typeof_with_assignment
|
||||
AnnotationTests.table_annotation
|
||||
AnnotationTests.two_type_params
|
||||
AnnotationTests.typeof_expr
|
||||
AnnotationTests.use_generic_type_alias
|
||||
AstQuery.last_argument_function_call_type
|
||||
AstQuery::getDocumentationSymbolAtPosition.class_method
|
||||
AstQuery::getDocumentationSymbolAtPosition.overloaded_class_method
|
||||
AstQuery::getDocumentationSymbolAtPosition.table_overloaded_function_prop
|
||||
AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
|
||||
AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
|
||||
AutocompleteTest.autocomplete_interpolated_string_as_singleton
|
||||
AutocompleteTest.autocomplete_oop_implicit_self
|
||||
AutocompleteTest.autocomplete_string_singleton_equality
|
||||
AutocompleteTest.autocomplete_string_singleton_escape
|
||||
AutocompleteTest.autocomplete_string_singletons
|
||||
AutocompleteTest.cyclic_table
|
||||
AutocompleteTest.do_wrong_compatible_nonself_calls
|
||||
AutocompleteTest.frontend_use_correct_global_scope
|
||||
AutocompleteTest.no_incompatible_self_calls_on_class
|
||||
AutocompleteTest.string_singleton_in_if_statement
|
||||
AutocompleteTest.suggest_external_module_type
|
||||
AutocompleteTest.type_correct_expected_argument_type_pack_suggestion
|
||||
AutocompleteTest.type_correct_expected_argument_type_suggestion
|
||||
@ -23,7 +31,6 @@ AutocompleteTest.type_correct_function_return_types
|
||||
AutocompleteTest.type_correct_keywords
|
||||
AutocompleteTest.type_correct_suggestion_for_overloads
|
||||
AutocompleteTest.type_correct_suggestion_in_argument
|
||||
AutocompleteTest.unsealed_table_2
|
||||
BuiltinTests.aliased_string_format
|
||||
BuiltinTests.assert_removes_falsy_types
|
||||
BuiltinTests.assert_removes_falsy_types2
|
||||
@ -32,6 +39,7 @@ BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_can
|
||||
BuiltinTests.bad_select_should_not_crash
|
||||
BuiltinTests.coroutine_resume_anything_goes
|
||||
BuiltinTests.debug_info_is_crazy
|
||||
BuiltinTests.dont_add_definitions_to_persistent_types
|
||||
BuiltinTests.global_singleton_types_are_sealed
|
||||
BuiltinTests.gmatch_capture_types
|
||||
BuiltinTests.gmatch_capture_types2
|
||||
@ -59,6 +67,7 @@ BuiltinTests.string_format_as_method
|
||||
BuiltinTests.string_format_correctly_ordered_types
|
||||
BuiltinTests.string_format_report_all_type_errors_at_correct_positions
|
||||
BuiltinTests.string_format_use_correct_argument2
|
||||
BuiltinTests.string_match
|
||||
BuiltinTests.table_concat_returns_string
|
||||
BuiltinTests.table_dot_remove_optionally_returns_generic
|
||||
BuiltinTests.table_freeze_is_generic
|
||||
@ -93,10 +102,12 @@ ControlFlowAnalysis.tagged_unions_breaking
|
||||
ControlFlowAnalysis.tagged_unions_continuing
|
||||
ControlFlowAnalysis.type_alias_does_not_leak_out_breaking
|
||||
ControlFlowAnalysis.type_alias_does_not_leak_out_continuing
|
||||
DefinitionTests.class_definition_function_prop
|
||||
DefinitionTests.class_definition_indexer
|
||||
DefinitionTests.class_definition_overload_metamethods
|
||||
DefinitionTests.class_definition_string_props
|
||||
DefinitionTests.declaring_generic_functions
|
||||
DefinitionTests.definition_file_class_function_args
|
||||
DefinitionTests.definition_file_classes
|
||||
Differ.equal_generictp_cyclic
|
||||
Differ.equal_table_A_B_C
|
||||
@ -119,9 +130,10 @@ Differ.table_left_circle_right_measuring_tape
|
||||
FrontendTest.accumulate_cached_errors_in_consistent_order
|
||||
FrontendTest.it_should_be_safe_to_stringify_errors_when_full_type_graph_is_discarded
|
||||
GenericsTests.apply_type_function_nested_generics1
|
||||
GenericsTests.apply_type_function_nested_generics3
|
||||
GenericsTests.apply_type_function_nested_generics2
|
||||
GenericsTests.better_mismatch_error_messages
|
||||
GenericsTests.bound_tables_do_not_clone_original_fields
|
||||
GenericsTests.calling_self_generic_methods
|
||||
GenericsTests.check_generic_function
|
||||
GenericsTests.check_generic_local_function
|
||||
GenericsTests.check_generic_typepack_function
|
||||
@ -169,11 +181,17 @@ GenericsTests.rank_N_types_via_typeof
|
||||
GenericsTests.self_recursive_instantiated_param
|
||||
GenericsTests.type_parameters_can_be_polytypes
|
||||
GenericsTests.typefuns_sharing_types
|
||||
IntersectionTypes.argument_is_intersection
|
||||
IntersectionTypes.error_detailed_intersection_all
|
||||
IntersectionTypes.error_detailed_intersection_part
|
||||
IntersectionTypes.fx_intersection_as_argument
|
||||
IntersectionTypes.index_on_an_intersection_type_with_mixed_types
|
||||
IntersectionTypes.index_on_an_intersection_type_with_one_part_missing_the_property
|
||||
IntersectionTypes.index_on_an_intersection_type_with_one_property_of_type_any
|
||||
IntersectionTypes.index_on_an_intersection_type_with_property_guaranteed_to_exist
|
||||
IntersectionTypes.index_on_an_intersection_type_works_at_arbitrary_depth
|
||||
IntersectionTypes.intersect_bool_and_false
|
||||
IntersectionTypes.intersect_false_and_bool_and_false
|
||||
IntersectionTypes.intersect_metatables
|
||||
IntersectionTypes.intersect_saturate_overloaded_functions
|
||||
IntersectionTypes.intersection_of_tables
|
||||
IntersectionTypes.intersection_of_tables_with_never_properties
|
||||
@ -192,12 +210,20 @@ IntersectionTypes.overloadeded_functions_with_weird_typepacks_1
|
||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_2
|
||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
|
||||
IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
|
||||
IntersectionTypes.propagates_name
|
||||
IntersectionTypes.select_correct_union_fn
|
||||
IntersectionTypes.should_still_pick_an_overload_whose_arguments_are_unions
|
||||
IntersectionTypes.table_extra_ok
|
||||
IntersectionTypes.table_intersection_setmetatable
|
||||
IntersectionTypes.table_intersection_write_sealed
|
||||
IntersectionTypes.table_intersection_write_sealed_indirect
|
||||
IntersectionTypes.table_write_sealed_indirect
|
||||
IntersectionTypes.union_saturate_overloaded_functions
|
||||
Linter.DeprecatedApiFenv
|
||||
Linter.FormatStringTyped
|
||||
Linter.TableOperationsIndexer
|
||||
Negations.cofinite_strings_can_be_compared_for_equality
|
||||
Negations.negated_string_is_a_subtype_of_string
|
||||
Normalize.higher_order_function_with_annotation
|
||||
Normalize.negations_of_tables
|
||||
Normalize.specific_functions_cannot_be_negated
|
||||
@ -212,10 +238,12 @@ ProvisionalTests.free_options_can_be_unified_together
|
||||
ProvisionalTests.free_options_cannot_be_unified_together
|
||||
ProvisionalTests.function_returns_many_things_but_first_of_it_is_forgotten
|
||||
ProvisionalTests.generic_type_leak_to_module_interface
|
||||
ProvisionalTests.generic_type_leak_to_module_interface_variadic
|
||||
ProvisionalTests.greedy_inference_with_shared_self_triggers_function_with_no_returns
|
||||
ProvisionalTests.it_should_be_agnostic_of_actual_size
|
||||
ProvisionalTests.luau-polyfill.Array.filter
|
||||
ProvisionalTests.luau-polyfill.Map.entries
|
||||
ProvisionalTests.luau_roact_useState_minimization
|
||||
ProvisionalTests.optional_class_instances_are_invariant
|
||||
ProvisionalTests.pcall_returns_at_least_two_value_but_function_returns_nothing
|
||||
ProvisionalTests.setmetatable_constrains_free_type_into_free_table
|
||||
@ -223,9 +251,11 @@ ProvisionalTests.specialization_binds_with_prototypes_too_early
|
||||
ProvisionalTests.table_insert_with_a_singleton_argument
|
||||
ProvisionalTests.table_unification_infinite_recursion
|
||||
ProvisionalTests.typeguard_inference_incomplete
|
||||
ProvisionalTests.weirditer_should_not_loop_forever
|
||||
ProvisionalTests.while_body_are_also_refined
|
||||
ProvisionalTests.xpcall_returns_what_f_returns
|
||||
RefinementTest.apply_refinements_on_astexprindexexpr_whose_subscript_expr_is_constant_string
|
||||
RefinementTest.assert_a_to_be_truthy_then_assert_a_to_be_number
|
||||
RefinementTest.correctly_lookup_property_whose_base_was_previously_refined
|
||||
RefinementTest.dataflow_analysis_can_tell_refinements_when_its_appropriate_to_refine_into_nil_or_never
|
||||
RefinementTest.discriminate_from_truthiness_of_x
|
||||
@ -241,6 +271,7 @@ RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
|
||||
RefinementTest.not_t_or_some_prop_of_t
|
||||
RefinementTest.refine_a_property_of_some_global
|
||||
RefinementTest.refine_unknown_to_table_then_clone_it
|
||||
RefinementTest.refinements_should_preserve_error_suppression
|
||||
RefinementTest.string_not_equal_to_string_or_nil
|
||||
RefinementTest.truthy_constraint_on_properties
|
||||
RefinementTest.type_annotations_arent_relevant_when_doing_dataflow_analysis
|
||||
@ -294,22 +325,27 @@ TableTests.explicitly_typed_table_with_indexer
|
||||
TableTests.extend_unsealed_table_with_metatable
|
||||
TableTests.generalize_table_argument
|
||||
TableTests.generic_table_instantiation_potential_regression
|
||||
TableTests.give_up_after_one_metatable_index_look_up
|
||||
TableTests.indexer_mismatch
|
||||
TableTests.indexer_on_sealed_table_must_unify_with_free_table
|
||||
TableTests.indexers_get_quantified_too
|
||||
TableTests.indexing_from_a_table_should_prefer_properties_when_possible
|
||||
TableTests.inequality_operators_imply_exactly_matching_types
|
||||
TableTests.infer_array_2
|
||||
TableTests.infer_indexer_for_left_unsealed_table_from_right_hand_table_with_indexer
|
||||
TableTests.infer_indexer_from_its_function_return_type
|
||||
TableTests.infer_indexer_from_its_variable_type_and_unifiable
|
||||
TableTests.infer_indexer_from_value_property_in_literal
|
||||
TableTests.infer_type_when_indexing_from_a_table_indexer
|
||||
TableTests.inferred_return_type_of_free_table
|
||||
TableTests.instantiate_table_cloning_3
|
||||
TableTests.instantiate_tables_at_scope_level
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_assignment_is_unsound
|
||||
TableTests.invariant_table_properties_means_instantiating_tables_in_call_is_unsound
|
||||
TableTests.leaking_bad_metatable_errors
|
||||
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.metatable_mismatch_should_fail
|
||||
TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
|
||||
TableTests.mixed_tables_with_implicit_numbered_keys
|
||||
@ -330,6 +366,8 @@ TableTests.quantify_even_that_table_was_never_exported_at_all
|
||||
TableTests.quantify_metatables_of_metatables_of_table
|
||||
TableTests.reasonable_error_when_adding_a_nonexistent_property_to_an_array_like_table
|
||||
TableTests.recursive_metatable_type_call
|
||||
TableTests.result_is_always_any_if_lhs_is_any
|
||||
TableTests.result_is_bool_for_equality_operators_if_lhs_is_any
|
||||
TableTests.right_table_missing_key2
|
||||
TableTests.scalar_is_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||
TableTests.scalar_is_not_a_subtype_of_a_compatible_polymorphic_shape_type
|
||||
@ -341,6 +379,7 @@ TableTests.shared_selfs_through_metatables
|
||||
TableTests.subproperties_can_also_be_covariantly_tested
|
||||
TableTests.table_call_metamethod_basic
|
||||
TableTests.table_call_metamethod_generic
|
||||
TableTests.table_call_metamethod_must_be_callable
|
||||
TableTests.table_function_check_use_after_free
|
||||
TableTests.table_insert_should_cope_with_optional_properties_in_strict
|
||||
TableTests.table_param_width_subtyping_1
|
||||
@ -354,6 +393,7 @@ TableTests.table_unification_4
|
||||
TableTests.table_unifies_into_map
|
||||
TableTests.top_table_type
|
||||
TableTests.type_mismatch_on_massive_table_is_cut_short
|
||||
TableTests.unification_of_unions_in_a_self_referential_type
|
||||
TableTests.unifying_tables_shouldnt_uaf1
|
||||
TableTests.used_colon_instead_of_dot
|
||||
TableTests.used_dot_instead_of_colon
|
||||
@ -362,11 +402,16 @@ ToDot.function
|
||||
ToString.exhaustive_toString_of_cyclic_table
|
||||
ToString.free_types
|
||||
ToString.named_metatable_toStringNamedFunction
|
||||
ToString.no_parentheses_around_cyclic_function_type_in_union
|
||||
ToString.pick_distinct_names_for_mixed_explicit_and_implicit_generics
|
||||
ToString.primitive
|
||||
ToString.tostring_error_mismatch
|
||||
ToString.tostring_unsee_ttv_if_array
|
||||
ToString.toStringDetailed2
|
||||
ToString.toStringErrorPack
|
||||
ToString.toStringNamedFunction_generic_pack
|
||||
ToString.toStringNamedFunction_map
|
||||
TranspilerTests.types_should_not_be_considered_cyclic_if_they_are_not_recursive
|
||||
TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
|
||||
TryUnifyTests.result_of_failed_typepack_unification_is_constrained
|
||||
TryUnifyTests.typepack_unification_should_trim_free_tails
|
||||
@ -374,6 +419,9 @@ TryUnifyTests.uninhabited_table_sub_anything
|
||||
TryUnifyTests.uninhabited_table_sub_never
|
||||
TryUnifyTests.variadics_should_use_reversed_properly
|
||||
TypeAliases.alias_expands_to_bare_reference_to_imported_type
|
||||
TypeAliases.corecursive_types_generic
|
||||
TypeAliases.cyclic_function_type_in_type_alias
|
||||
TypeAliases.cyclic_types_of_named_table_fields_do_not_expand_when_stringified
|
||||
TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
|
||||
TypeAliases.free_variables_from_typeof_in_aliases
|
||||
TypeAliases.generic_param_remap
|
||||
@ -385,6 +433,7 @@ TypeAliases.mutually_recursive_types_restriction_not_ok_2
|
||||
TypeAliases.mutually_recursive_types_swapsies_not_ok
|
||||
TypeAliases.recursive_types_restriction_not_ok
|
||||
TypeAliases.report_shadowed_aliases
|
||||
TypeAliases.saturate_to_first_type_pack
|
||||
TypeAliases.stringify_optional_parameterized_alias
|
||||
TypeAliases.type_alias_local_mutation
|
||||
TypeAliases.type_alias_local_rename
|
||||
@ -404,10 +453,10 @@ TypeInfer.check_expr_recursion_limit
|
||||
TypeInfer.check_type_infer_recursion_count
|
||||
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
|
||||
TypeInfer.dont_report_type_errors_within_an_AstStatError
|
||||
TypeInfer.follow_on_new_types_in_substitution
|
||||
TypeInfer.fuzz_free_table_type_change_during_index_check
|
||||
TypeInfer.globals_are_banned_in_strict_mode
|
||||
TypeInfer.if_statement
|
||||
TypeInfer.infer_locals_via_assignment_from_its_call_site
|
||||
@ -419,12 +468,17 @@ TypeInfer.no_stack_overflow_from_isoptional
|
||||
TypeInfer.promote_tail_type_packs
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter
|
||||
TypeInfer.recursive_function_that_invokes_itself_with_a_refinement_of_its_parameter_2
|
||||
TypeInfer.stringify_nested_unions_with_optionals
|
||||
TypeInfer.tc_after_error_recovery_no_replacement_name_in_error
|
||||
TypeInfer.tc_error
|
||||
TypeInfer.tc_error_2
|
||||
TypeInfer.tc_if_else_expressions_expected_type_3
|
||||
TypeInfer.type_infer_recursion_limit_no_ice
|
||||
TypeInfer.type_infer_recursion_limit_normalizer
|
||||
TypeInfer.unify_nearly_identical_recursive_types
|
||||
TypeInferAnyError.any_type_propagates
|
||||
TypeInferAnyError.assign_prop_to_table_by_calling_any_yields_any
|
||||
TypeInferAnyError.call_to_any_yields_any
|
||||
TypeInferAnyError.can_subscript_any
|
||||
TypeInferAnyError.for_in_loop_iterator_is_any
|
||||
TypeInferAnyError.for_in_loop_iterator_is_any2
|
||||
@ -433,19 +487,25 @@ TypeInferAnyError.for_in_loop_iterator_is_error2
|
||||
TypeInferAnyError.for_in_loop_iterator_returns_any
|
||||
TypeInferAnyError.for_in_loop_iterator_returns_any2
|
||||
TypeInferAnyError.intersection_of_any_can_have_props
|
||||
TypeInferAnyError.quantify_any_does_not_bind_to_itself
|
||||
TypeInferAnyError.replace_every_free_type_when_unifying_a_complex_function_with_any
|
||||
TypeInferAnyError.type_error_addition
|
||||
TypeInferClasses.callable_classes
|
||||
TypeInferClasses.can_read_prop_of_base_class_using_string
|
||||
TypeInferClasses.cannot_unify_class_instance_with_primitive
|
||||
TypeInferClasses.class_type_mismatch_with_name_conflict
|
||||
TypeInferClasses.class_unification_type_mismatch_is_correct_order
|
||||
TypeInferClasses.detailed_class_unification_error
|
||||
TypeInferClasses.index_instance_property
|
||||
TypeInferClasses.indexable_classes
|
||||
TypeInferClasses.optional_class_field_access_error
|
||||
TypeInferClasses.table_class_unification_reports_sane_errors_for_missing_properties
|
||||
TypeInferClasses.table_indexers_are_invariant
|
||||
TypeInferClasses.type_mismatch_invariance_required_for_error
|
||||
TypeInferClasses.we_can_report_when_someone_is_trying_to_use_a_table_rather_than_a_class
|
||||
TypeInferFunctions.another_higher_order_function
|
||||
TypeInferFunctions.another_other_higher_order_function
|
||||
TypeInferFunctions.another_recursive_local_function
|
||||
TypeInferFunctions.apply_of_lambda_with_inferred_and_explicit_types
|
||||
TypeInferFunctions.cannot_hoist_interior_defns_into_signature
|
||||
TypeInferFunctions.check_function_bodies
|
||||
@ -478,9 +538,10 @@ TypeInferFunctions.infer_generic_lib_function_function_argument
|
||||
TypeInferFunctions.infer_return_type_from_selected_overload
|
||||
TypeInferFunctions.infer_return_value_type
|
||||
TypeInferFunctions.infer_that_function_does_not_return_a_table
|
||||
TypeInferFunctions.inferred_higher_order_functions_are_quantified_at_the_right_time3
|
||||
TypeInferFunctions.instantiated_type_packs_must_have_a_non_null_scope
|
||||
TypeInferFunctions.it_is_ok_to_oversaturate_a_higher_order_function_argument
|
||||
TypeInferFunctions.list_all_overloads_if_no_overload_takes_given_argument_count
|
||||
TypeInferFunctions.list_only_alternative_overloads_that_match_argument_count
|
||||
TypeInferFunctions.luau_subtyping_is_np_hard
|
||||
TypeInferFunctions.no_lossy_function_type
|
||||
TypeInferFunctions.num_is_solved_after_num_or_str
|
||||
@ -490,6 +551,7 @@ 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.recursive_local_function
|
||||
TypeInferFunctions.report_exiting_without_return_strict
|
||||
TypeInferFunctions.return_type_by_overload
|
||||
TypeInferFunctions.too_few_arguments_variadic
|
||||
@ -501,6 +563,10 @@ TypeInferFunctions.too_many_return_values_no_function
|
||||
TypeInferFunctions.vararg_function_is_quantified
|
||||
TypeInferLoops.cli_68448_iterators_need_not_accept_nil
|
||||
TypeInferLoops.dcr_iteration_explore_raycast_minimization
|
||||
TypeInferLoops.dcr_iteration_fragmented_keys
|
||||
TypeInferLoops.dcr_iteration_minimized_fragmented_keys_1
|
||||
TypeInferLoops.dcr_iteration_minimized_fragmented_keys_2
|
||||
TypeInferLoops.dcr_iteration_minimized_fragmented_keys_3
|
||||
TypeInferLoops.dcr_iteration_on_never_gives_never
|
||||
TypeInferLoops.dcr_xpath_candidates
|
||||
TypeInferLoops.for_in_loop
|
||||
@ -511,6 +577,7 @@ TypeInferLoops.for_in_loop_on_non_function
|
||||
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
|
||||
@ -530,13 +597,17 @@ TypeInferLoops.unreachable_code_after_infinite_loop
|
||||
TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
|
||||
TypeInferLoops.while_loop
|
||||
TypeInferModules.bound_free_table_export_is_ok
|
||||
TypeInferModules.do_not_modify_imported_types
|
||||
TypeInferModules.do_not_modify_imported_types_4
|
||||
TypeInferModules.do_not_modify_imported_types_5
|
||||
TypeInferModules.general_require_call_expression
|
||||
TypeInferModules.module_type_conflict
|
||||
TypeInferModules.module_type_conflict_instantiated
|
||||
TypeInferModules.require
|
||||
TypeInferModules.require_failed_module
|
||||
TypeInferOOP.CheckMethodsOfSealed
|
||||
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.inferring_hundreds_of_self_calls_should_not_suffocate_memory
|
||||
@ -555,33 +626,37 @@ TypeInferOperators.compound_assign_result_must_be_compatible_with_var
|
||||
TypeInferOperators.concat_op_on_free_lhs_and_string_rhs
|
||||
TypeInferOperators.concat_op_on_string_lhs_and_free_rhs
|
||||
TypeInferOperators.disallow_string_and_types_without_metatables_from_arithmetic_binary_ops
|
||||
TypeInferOperators.luau-polyfill.Array.startswith
|
||||
TypeInferOperators.equality_operations_succeed_if_any_union_branch_succeeds
|
||||
TypeInferOperators.error_on_invalid_operand_types_to_relational_operators2
|
||||
TypeInferOperators.luau_polyfill_is_array
|
||||
TypeInferOperators.normalize_strings_comparison
|
||||
TypeInferOperators.operator_eq_completely_incompatible
|
||||
TypeInferOperators.mm_comparisons_must_return_a_boolean
|
||||
TypeInferOperators.operator_eq_verifies_types_do_intersect
|
||||
TypeInferOperators.reducing_and
|
||||
TypeInferOperators.refine_and_or
|
||||
TypeInferOperators.reworked_and
|
||||
TypeInferOperators.reworked_or
|
||||
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.unrelated_classes_cannot_be_compared
|
||||
TypeInferOperators.unrelated_primitives_cannot_be_compared
|
||||
TypeInferPrimitives.CheckMethodsOfNumber
|
||||
TypeInferPrimitives.string_function_indirect
|
||||
TypeInferPrimitives.string_index
|
||||
TypeInferUnknownNever.array_like_table_of_never_is_inhabitable
|
||||
TypeInferUnknownNever.assign_to_prop_which_is_never
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_never
|
||||
TypeInferUnknownNever.index_on_union_of_tables_for_properties_that_is_sorta_never
|
||||
TypeInferUnknownNever.length_of_never
|
||||
TypeInferUnknownNever.math_operators_and_never
|
||||
TypeInferUnknownNever.type_packs_containing_never_is_itself_uninhabitable
|
||||
TypePackTests.fuzz_typepack_iter_follow_2
|
||||
TypePackTests.pack_tail_unification_check
|
||||
TypePackTests.parenthesized_varargs_returns_any
|
||||
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
|
||||
TypePackTests.unify_variadic_tails_in_arguments_free
|
||||
@ -592,7 +667,7 @@ TypeSingletons.error_detailed_tagged_union_mismatch_string
|
||||
TypeSingletons.function_args_infer_singletons
|
||||
TypeSingletons.function_call_with_singletons
|
||||
TypeSingletons.function_call_with_singletons_mismatch
|
||||
TypeSingletons.overloaded_function_call_with_singletons
|
||||
TypeSingletons.overloaded_function_call_with_singletons_mismatch
|
||||
TypeSingletons.return_type_of_f_is_not_widened
|
||||
TypeSingletons.table_properties_singleton_strings
|
||||
TypeSingletons.table_properties_type_error_escapes
|
||||
@ -605,12 +680,24 @@ UnionTypes.error_detailed_union_all
|
||||
UnionTypes.error_detailed_union_part
|
||||
UnionTypes.generic_function_with_optional_arg
|
||||
UnionTypes.index_on_a_union_type_with_missing_property
|
||||
UnionTypes.index_on_a_union_type_with_mixed_types
|
||||
UnionTypes.index_on_a_union_type_with_one_optional_property
|
||||
UnionTypes.index_on_a_union_type_with_one_property_of_type_any
|
||||
UnionTypes.index_on_a_union_type_with_property_guaranteed_to_exist
|
||||
UnionTypes.index_on_a_union_type_works_at_arbitrary_depth
|
||||
UnionTypes.less_greedy_unification_with_union_types
|
||||
UnionTypes.optional_arguments_table2
|
||||
UnionTypes.optional_assignment_errors
|
||||
UnionTypes.optional_call_error
|
||||
UnionTypes.optional_field_access_error
|
||||
UnionTypes.optional_index_error
|
||||
UnionTypes.optional_length_error
|
||||
UnionTypes.optional_missing_key_error_details
|
||||
UnionTypes.optional_union_follow
|
||||
UnionTypes.optional_union_functions
|
||||
UnionTypes.optional_union_members
|
||||
UnionTypes.optional_union_methods
|
||||
UnionTypes.table_union_write_indirect
|
||||
UnionTypes.unify_sealed_table_union_check
|
||||
UnionTypes.union_of_functions
|
||||
UnionTypes.union_of_functions_mentioning_generic_typepacks
|
||||
UnionTypes.union_of_functions_mentioning_generics
|
||||
@ -620,3 +707,4 @@ UnionTypes.union_of_functions_with_mismatching_result_arities
|
||||
UnionTypes.union_of_functions_with_mismatching_result_variadics
|
||||
UnionTypes.union_of_functions_with_variadics
|
||||
UnionTypes.union_true_and_false
|
||||
VisitType.throw_when_limit_is_exceeded
|
||||
|
Loading…
Reference in New Issue
Block a user