// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once #include "Luau/Set.h" #include "Luau/TypeFwd.h" #include "Luau/TypePairHash.h" #include "Luau/TypePath.h" #include "Luau/TypeFunction.h" #include "Luau/TypeCheckLimits.h" #include "Luau/DenseHash.h" #include #include namespace Luau { template struct TryPair; struct InternalErrorReporter; class TypeIds; class Normalizer; struct NormalizedClassType; struct NormalizedFunctionType; struct NormalizedStringType; struct NormalizedType; struct Property; struct Scope; struct TableIndexer; struct TypeArena; struct TypeCheckLimits; enum class SubtypingVariance { // Used for an empty key. Should never appear in actual code. Invalid, Covariant, // This is used to identify cases where we have a covariant + a // contravariant reason and we need to merge them. Contravariant, Invariant, }; struct SubtypingReasoning { // The path, relative to the _root subtype_, where subtyping failed. Path subPath; // The path, relative to the _root supertype_, where subtyping failed. Path superPath; SubtypingVariance variance = SubtypingVariance::Covariant; bool operator==(const SubtypingReasoning& other) const; }; struct SubtypingReasoningHash { size_t operator()(const SubtypingReasoning& r) const; }; using SubtypingReasonings = DenseHashSet; static const SubtypingReasoning kEmptyReasoning = SubtypingReasoning{TypePath::kEmpty, TypePath::kEmpty, SubtypingVariance::Invalid}; struct SubtypingResult { bool isSubtype = false; bool normalizationTooComplex = false; bool isCacheable = true; ErrorVec errors; /// The reason for isSubtype to be false. May not be present even if /// isSubtype is false, depending on the input types. SubtypingReasonings reasoning{kEmptyReasoning}; SubtypingResult& andAlso(const SubtypingResult& other); SubtypingResult& orElse(const SubtypingResult& other); SubtypingResult& withBothComponent(TypePath::Component component); SubtypingResult& withSuperComponent(TypePath::Component component); SubtypingResult& withSubComponent(TypePath::Component component); SubtypingResult& withBothPath(TypePath::Path path); SubtypingResult& withSubPath(TypePath::Path path); SubtypingResult& withSuperPath(TypePath::Path path); SubtypingResult& withErrors(ErrorVec& err); SubtypingResult& withError(TypeError err); // Only negates the `isSubtype`. static SubtypingResult negate(const SubtypingResult& result); static SubtypingResult all(const std::vector& results); static SubtypingResult any(const std::vector& results); }; struct SubtypingEnvironment { struct GenericBounds { DenseHashSet lowerBound{nullptr}; DenseHashSet upperBound{nullptr}; }; /* For nested subtyping relationship tests of mapped generic bounds, we keep the outer environment immutable */ SubtypingEnvironment* parent = nullptr; /// Applies `mappedGenerics` to the given type. /// This is used specifically to substitute for generics in type function instances. std::optional applyMappedGenerics(NotNull builtinTypes, NotNull arena, TypeId ty); const TypeId* tryFindSubstitution(TypeId ty) const; const SubtypingResult* tryFindSubtypingResult(std::pair subAndSuper) const; bool containsMappedType(TypeId ty) const; bool containsMappedPack(TypePackId tp) const; GenericBounds& getMappedTypeBounds(TypeId ty); TypePackId* getMappedPackBounds(TypePackId tp); /* * 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 mappedGenerics{nullptr}; DenseHashMap mappedGenericPacks{nullptr}; /* * See the test cyclic_tables_are_assumed_to_be_compatible_with_classes for * details. * * An empty value is equivalent to a nonexistent key. */ DenseHashMap substitutions{nullptr}; DenseHashMap, SubtypingResult, TypePairHash> ephemeralCache{{}}; }; struct Subtyping { NotNull builtinTypes; NotNull arena; NotNull normalizer; NotNull iceReporter; TypeCheckLimits limits; enum class Variance { Covariant, Contravariant }; Variance variance = Variance::Covariant; using SeenSet = Set, TypePairHash>; SeenSet seenTypes{{}}; Subtyping( NotNull builtinTypes, NotNull typeArena, NotNull normalizer, NotNull iceReporter ); 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, SubtypingResult, TypePairHash>& peekCache() const { return resultCache; } // TODO cache // TODO cyclic types // TODO recursion limits SubtypingResult isSubtype(TypeId subTy, TypeId superTy, NotNull scope); SubtypingResult isSubtype(TypePackId subTy, TypePackId superTy, NotNull scope); private: DenseHashMap, SubtypingResult, TypePairHash> resultCache{{}}; SubtypingResult cache(SubtypingEnvironment& env, SubtypingResult res, TypeId subTy, TypeId superTy); SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, TypeId superTy, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp, NotNull scope); template SubtypingResult isContravariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope); template SubtypingResult isInvariantWith(SubtypingEnvironment& env, SubTy&& subTy, SuperTy&& superTy, NotNull scope); template SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull scope); template SubtypingResult isContravariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull); template SubtypingResult isInvariantWith(SubtypingEnvironment& env, const TryPair& pair, NotNull); SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const UnionType* superUnion, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const UnionType* subUnion, TypeId superTy, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const IntersectionType* superIntersection, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const IntersectionType* subIntersection, TypeId superTy, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NegationType* subNegation, TypeId superTy, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeId subTy, const NegationType* superNegation, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const PrimitiveType* superPrim, NotNull scope); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const SingletonType* subSingleton, const PrimitiveType* superPrim, NotNull scope ); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const SingletonType* subSingleton, const SingletonType* superSingleton, NotNull scope ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const TableType* superTable, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const MetatableType* superMt, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const MetatableType* subMt, const TableType* superTable, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const ClassType* subClass, const ClassType* superClass, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, TypeId subTy, const ClassType* subClass, TypeId superTy, const TableType* superTable, NotNull); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const FunctionType* subFunction, const FunctionType* superFunction, NotNull scope ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableType* subTable, const PrimitiveType* superPrim, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const PrimitiveType* subPrim, const TableType* superTable, NotNull scope); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable, NotNull scope); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer, NotNull scope ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name, NotNull); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const std::shared_ptr& subNorm, const std::shared_ptr& superNorm, NotNull scope ); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass, NotNull scope ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables, NotNull scope); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString, NotNull scope ); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const NormalizedStringType& subString, const TypeIds& superTables, NotNull scope ); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedFunctionType& subFunction, const NormalizedFunctionType& superFunction, NotNull); SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TypeIds& subTypes, const TypeIds& superTypes, NotNull scope); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const VariadicTypePack* subVariadic, const VariadicTypePack* superVariadic, NotNull scope ); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const TypeFunctionInstanceType* subFunctionInstance, const TypeId superTy, NotNull scope ); SubtypingResult isCovariantWith( SubtypingEnvironment& env, const TypeId subTy, const TypeFunctionInstanceType* superFunctionInstance, NotNull scope ); bool bindGeneric(SubtypingEnvironment& env, TypeId subTp, TypeId superTp); bool bindGeneric(SubtypingEnvironment& env, TypePackId subTp, TypePackId superTp); template TypeId makeAggregateType(const Container& container, TypeId orElse); std::pair handleTypeFunctionReductionResult(const TypeFunctionInstanceType* functionInstance, NotNull scope); [[noreturn]] void unexpected(TypeId ty); [[noreturn]] void unexpected(TypePackId tp); }; } // namespace Luau