// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/Type.h" #include "Luau/BuiltinDefinitions.h" #include "Luau/Common.h" #include "Luau/ConstraintSolver.h" #include "Luau/DenseHash.h" #include "Luau/Error.h" #include "Luau/RecursionCounter.h" #include "Luau/StringUtils.h" #include "Luau/ToString.h" #include "Luau/TypeFunction.h" #include "Luau/TypeInfer.h" #include "Luau/TypePack.h" #include "Luau/VecDeque.h" #include "Luau/VisitType.h" #include #include #include #include #include LUAU_FASTFLAG(DebugLuauFreezeArena) LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500) LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0) LUAU_FASTINT(LuauTypeInferRecursionLimit) LUAU_FASTFLAG(LuauInstantiateInSubtyping) namespace Luau { // LUAU_NOINLINE prevents unwrapLazy from being inlined into advance below; advance is important to keep inlineable static LUAU_NOINLINE TypeId unwrapLazy(LazyType* ltv) { TypeId unwrapped = ltv->unwrapped.load(); if (unwrapped) return unwrapped; ltv->unwrap(*ltv); unwrapped = ltv->unwrapped.load(); if (!unwrapped) throw InternalCompilerError("Lazy Type didn't fill in unwrapped type field"); if (get(unwrapped)) throw InternalCompilerError("Lazy Type cannot resolve to another Lazy Type"); return unwrapped; } TypeId follow(TypeId t) { return follow(t, FollowOption::Normal); } TypeId follow(TypeId t, FollowOption followOption) { return follow( t, followOption, nullptr, [](const void*, TypeId t) -> TypeId { return t; } ); } TypeId follow(TypeId t, const void* context, TypeId (*mapper)(const void*, TypeId)) { return follow(t, FollowOption::Normal, context, mapper); } TypeId follow(TypeId t, FollowOption followOption, const void* context, TypeId (*mapper)(const void*, TypeId)) { auto advance = [followOption, context, mapper](TypeId ty) -> std::optional { TypeId mapped = mapper(context, ty); if (auto btv = get>(mapped)) return btv->boundTo; if (auto ttv = get(mapped)) return ttv->boundTo; if (auto ltv = getMutable(mapped); ltv && followOption != FollowOption::DisableLazyTypeThunks) return unwrapLazy(ltv); return std::nullopt; }; TypeId cycleTester = t; // Null once we've determined that there is no cycle if (auto a = advance(cycleTester)) cycleTester = *a; else return t; if (!advance(cycleTester)) // Short circuit traversal for the rather common case when advance(advance(t)) == null return cycleTester; while (true) { auto a1 = advance(t); if (a1) t = *a1; else return t; if (nullptr != cycleTester) { auto a2 = advance(cycleTester); if (a2) { auto a3 = advance(*a2); if (a3) cycleTester = *a3; else cycleTester = nullptr; } else cycleTester = nullptr; if (t == cycleTester) throw InternalCompilerError("Luau::follow detected a Type cycle!!"); } } } std::vector flattenIntersection(TypeId ty) { if (!get(follow(ty))) return {ty}; std::unordered_set seen; VecDeque queue{ty}; std::vector result; while (!queue.empty()) { TypeId current = follow(queue.front()); queue.pop_front(); if (seen.find(current) != seen.end()) continue; seen.insert(current); if (auto itv = get(current)) { for (TypeId ty : itv->parts) queue.push_back(ty); } else result.push_back(current); } return result; } bool isPrim(TypeId ty, PrimitiveType::Type primType) { auto p = get(follow(ty)); return p && p->type == primType; } bool isNil(TypeId ty) { return isPrim(ty, PrimitiveType::NilType); } bool isBoolean(TypeId ty) { if (isPrim(ty, PrimitiveType::Boolean) || get(get(follow(ty)))) return true; if (auto utv = get(follow(ty))) return std::all_of(begin(utv), end(utv), isBoolean); return false; } bool isNumber(TypeId ty) { return isPrim(ty, PrimitiveType::Number); } // Returns true when ty is a subtype of string bool isString(TypeId ty) { ty = follow(ty); if (isPrim(ty, PrimitiveType::String) || get(get(ty))) return true; if (auto utv = get(ty)) return std::all_of(begin(utv), end(utv), isString); return false; } // Returns true when ty is a supertype of string bool maybeString(TypeId ty) { ty = follow(ty); if (isPrim(ty, PrimitiveType::String) || get(ty)) return true; if (auto utv = get(ty)) return std::any_of(begin(utv), end(utv), maybeString); return false; } bool isThread(TypeId ty) { return isPrim(ty, PrimitiveType::Thread); } bool isBuffer(TypeId ty) { return isPrim(ty, PrimitiveType::Buffer); } bool isOptional(TypeId ty) { if (isNil(ty)) return true; ty = follow(ty); if (get(ty) || get(ty)) return true; auto utv = get(ty); if (!utv) return false; return std::any_of(begin(utv), end(utv), isOptional); } bool isTableIntersection(TypeId ty) { if (!get(follow(ty))) return false; std::vector parts = flattenIntersection(ty); return std::all_of(parts.begin(), parts.end(), getTableType); } bool isTableUnion(TypeId ty) { const UnionType* ut = get(follow(ty)); if (!ut) return false; return std::all_of(begin(ut), end(ut), getTableType); } bool isOverloadedFunction(TypeId ty) { if (!get(follow(ty))) return false; auto isFunction = [](TypeId part) -> bool { return get(part); }; std::vector parts = flattenIntersection(ty); return std::all_of(parts.begin(), parts.end(), isFunction); } std::optional getMetatable(TypeId type, NotNull builtinTypes) { type = follow(type); if (const MetatableType* mtType = get(type)) return mtType->metatable; else if (const ClassType* classType = get(type)) return classType->metatable; else if (isString(type)) { auto ptv = get(builtinTypes->stringType); LUAU_ASSERT(ptv && ptv->metatable); return ptv->metatable; } return std::nullopt; } const TableType* getTableType(TypeId type) { type = follow(type); if (const TableType* ttv = get(type)) return ttv; else if (const MetatableType* mtv = get(type)) return get(follow(mtv->table)); else return nullptr; } TableType* getMutableTableType(TypeId type) { return const_cast(getTableType(type)); } const std::string* getName(TypeId type) { type = follow(type); if (auto mtv = get(type)) { if (mtv->syntheticName) return &*mtv->syntheticName; type = follow(mtv->table); } if (auto ttv = get(type)) { if (ttv->name) return &*ttv->name; if (ttv->syntheticName) return &*ttv->syntheticName; } return nullptr; } std::optional getDefinitionModuleName(TypeId type) { type = follow(type); if (auto ttv = get(type)) { if (!ttv->definitionModuleName.empty()) return ttv->definitionModuleName; } else if (auto ftv = get(type)) { if (ftv->definition) return ftv->definition->definitionModuleName; } else if (auto ctv = get(type)) { if (!ctv->definitionModuleName.empty()) return ctv->definitionModuleName; } return std::nullopt; } bool isSubset(const UnionType& super, const UnionType& sub) { std::unordered_set superTypes; for (TypeId id : super.options) superTypes.insert(id); for (TypeId id : sub.options) { if (superTypes.find(id) == superTypes.end()) return false; } return true; } bool hasPrimitiveTypeInIntersection(TypeId ty, PrimitiveType::Type primTy) { TypeId tf = follow(ty); if (isPrim(tf, primTy)) return true; for (auto t : flattenIntersection(tf)) return isPrim(follow(t), primTy); return false; } // When typechecking an assignment `x = e`, we typecheck `x:T` and `e:U`, // then instantiate U if `isGeneric(U)` is true, and `maybeGeneric(T)` is false. bool isGeneric(TypeId ty) { LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping); ty = follow(ty); if (auto ftv = get(ty)) return ftv->generics.size() > 0 || ftv->genericPacks.size() > 0; else // TODO: recurse on type synonyms CLI-39914 // TODO: recurse on table types CLI-39914 return false; } bool maybeGeneric(TypeId ty) { LUAU_ASSERT(!FFlag::LuauInstantiateInSubtyping); ty = follow(ty); if (get(ty)) return true; if (auto ttv = get(ty)) { // TODO: recurse on table types CLI-39914 (void)ttv; return true; } if (auto itv = get(ty)) { return std::any_of(begin(itv), end(itv), maybeGeneric); } return isGeneric(ty); } bool maybeSingleton(TypeId ty) { ty = follow(ty); if (get(ty)) return true; if (const UnionType* utv = get(ty)) for (TypeId option : utv) if (get(follow(option))) return true; if (const IntersectionType* itv = get(ty)) for (TypeId part : itv) if (maybeSingleton(part)) // will i regret this? return true; if (const TypeFunctionInstanceType* tfit = get(ty)) if (tfit->function->name == "keyof" || tfit->function->name == "rawkeyof") return true; return false; } bool hasLength(TypeId ty, DenseHashSet& seen, int* recursionCount) { RecursionLimiter _rl(recursionCount, FInt::LuauTypeInferRecursionLimit); ty = follow(ty); if (seen.contains(ty)) return true; if (isString(ty) || isPrim(ty, PrimitiveType::Table) || get(ty) || get(ty) || get(ty)) return true; if (auto uty = get(ty)) { seen.insert(ty); for (TypeId part : uty->options) { if (!hasLength(part, seen, recursionCount)) return false; } return true; } if (auto ity = get(ty)) { seen.insert(ty); for (TypeId part : ity->parts) { if (hasLength(part, seen, recursionCount)) return true; } return false; } return false; } FreeType::FreeType(TypeLevel level) : index(Unifiable::freshIndex()) , level(level) , scope(nullptr) { } FreeType::FreeType(Scope* scope) : index(Unifiable::freshIndex()) , level{} , scope(scope) { } FreeType::FreeType(Scope* scope, TypeLevel level) : index(Unifiable::freshIndex()) , level(level) , scope(scope) { } FreeType::FreeType(Scope* scope, TypeId lowerBound, TypeId upperBound) : index(Unifiable::freshIndex()) , scope(scope) , lowerBound(lowerBound) , upperBound(upperBound) { } GenericType::GenericType() : index(Unifiable::freshIndex()) , name("g" + std::to_string(index)) { } GenericType::GenericType(TypeLevel level) : index(Unifiable::freshIndex()) , level(level) , name("g" + std::to_string(index)) { } GenericType::GenericType(const Name& name) : index(Unifiable::freshIndex()) , name(name) , explicitName(true) { } GenericType::GenericType(Scope* scope) : index(Unifiable::freshIndex()) , scope(scope) { } GenericType::GenericType(TypeLevel level, const Name& name) : index(Unifiable::freshIndex()) , level(level) , name(name) , explicitName(true) { } GenericType::GenericType(Scope* scope, const Name& name) : index(Unifiable::freshIndex()) , scope(scope) , name(name) , explicitName(true) { } BlockedType::BlockedType() : index(Unifiable::freshIndex()) { } Constraint* BlockedType::getOwner() const { return owner; } void BlockedType::setOwner(Constraint* newOwner) { LUAU_ASSERT(owner == nullptr); if (owner != nullptr) return; owner = newOwner; } void BlockedType::replaceOwner(Constraint* newOwner) { owner = newOwner; } PendingExpansionType::PendingExpansionType( std::optional prefix, AstName name, std::vector typeArguments, std::vector packArguments ) : prefix(prefix) , name(name) , typeArguments(typeArguments) , packArguments(packArguments) , index(++nextIndex) { } size_t PendingExpansionType::nextIndex = 0; FunctionType::FunctionType(TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf) : definition(std::move(defn)) , argTypes(argTypes) , retTypes(retTypes) , hasSelf(hasSelf) { } FunctionType::FunctionType(TypeLevel level, TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf) : definition(std::move(defn)) , level(level) , argTypes(argTypes) , retTypes(retTypes) , hasSelf(hasSelf) { } FunctionType::FunctionType( TypeLevel level, Scope* scope, TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf ) : definition(std::move(defn)) , level(level) , scope(scope) , argTypes(argTypes) , retTypes(retTypes) , hasSelf(hasSelf) { } FunctionType::FunctionType( std::vector generics, std::vector genericPacks, TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf ) : definition(std::move(defn)) , generics(generics) , genericPacks(genericPacks) , argTypes(argTypes) , retTypes(retTypes) , hasSelf(hasSelf) { } FunctionType::FunctionType( TypeLevel level, std::vector generics, std::vector genericPacks, TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf ) : definition(std::move(defn)) , generics(generics) , genericPacks(genericPacks) , level(level) , argTypes(argTypes) , retTypes(retTypes) , hasSelf(hasSelf) { } FunctionType::FunctionType( TypeLevel level, Scope* scope, std::vector generics, std::vector genericPacks, TypePackId argTypes, TypePackId retTypes, std::optional defn, bool hasSelf ) : definition(std::move(defn)) , generics(generics) , genericPacks(genericPacks) , level(level) , scope(scope) , argTypes(argTypes) , retTypes(retTypes) , hasSelf(hasSelf) { } Property::Property() {} Property::Property( TypeId readTy, bool deprecated, const std::string& deprecatedSuggestion, std::optional location, const Tags& tags, const std::optional& documentationSymbol, std::optional typeLocation ) : deprecated(deprecated) , deprecatedSuggestion(deprecatedSuggestion) , location(location) , typeLocation(typeLocation) , tags(tags) , documentationSymbol(documentationSymbol) , readTy(readTy) , writeTy(readTy) { } Property Property::readonly(TypeId ty) { Property p; p.readTy = ty; return p; } Property Property::writeonly(TypeId ty) { Property p; p.writeTy = ty; return p; } Property Property::rw(TypeId ty) { return Property::rw(ty, ty); } Property Property::rw(TypeId read, TypeId write) { Property p; p.readTy = read; p.writeTy = write; return p; } Property Property::create(std::optional read, std::optional write) { if (read && !write) return Property::readonly(*read); else if (!read && write) return Property::writeonly(*write); else { LUAU_ASSERT(read && write); return Property::rw(*read, *write); } } TypeId Property::type() const { LUAU_ASSERT(readTy); return *readTy; } void Property::setType(TypeId ty) { readTy = ty; if (FFlag::LuauSolverV2) writeTy = ty; } void Property::makeShared() { if (writeTy) writeTy = readTy; } bool Property::isShared() const { return readTy && writeTy && readTy == writeTy; } bool Property::isReadOnly() const { return readTy && !writeTy; } bool Property::isWriteOnly() const { return !readTy && writeTy; } bool Property::isReadWrite() const { return readTy && writeTy; } TableType::TableType(TableState state, TypeLevel level, Scope* scope) : state(state) , level(level) , scope(scope) { } TableType::TableType(const Props& props, const std::optional& indexer, TypeLevel level, TableState state) : props(props) , indexer(indexer) , state(state) , level(level) { } TableType::TableType(const Props& props, const std::optional& indexer, TypeLevel level, Scope* scope, TableState state) : props(props) , indexer(indexer) , state(state) , level(level) , scope(scope) { } // Test Types for equivalence // More complex than we'd like because Types can self-reference. bool areSeen(SeenSet& seen, const void* lhs, const void* rhs) { if (lhs == rhs) return true; auto p = std::make_pair(const_cast(lhs), const_cast(rhs)); if (seen.find(p) != seen.end()) return true; seen.insert(p); return false; } bool areEqual(SeenSet& seen, const FunctionType& lhs, const FunctionType& rhs) { if (areSeen(seen, &lhs, &rhs)) return true; // TODO: check generics CLI-39915 if (!areEqual(seen, *lhs.argTypes, *rhs.argTypes)) return false; if (!areEqual(seen, *lhs.retTypes, *rhs.retTypes)) return false; return true; } bool areEqual(SeenSet& seen, const TableType& lhs, const TableType& rhs) { if (areSeen(seen, &lhs, &rhs)) return true; if (lhs.state != rhs.state) return false; if (lhs.props.size() != rhs.props.size()) return false; if (bool(lhs.indexer) != bool(rhs.indexer)) return false; if (lhs.indexer && rhs.indexer) { if (!areEqual(seen, *lhs.indexer->indexType, *rhs.indexer->indexType)) return false; if (!areEqual(seen, *lhs.indexer->indexResultType, *rhs.indexer->indexResultType)) return false; } auto l = lhs.props.begin(); auto r = rhs.props.begin(); while (l != lhs.props.end()) { if (l->first != r->first) return false; if (!areEqual(seen, *l->second.type(), *r->second.type())) return false; ++l; ++r; } return true; } static bool areEqual(SeenSet& seen, const MetatableType& lhs, const MetatableType& rhs) { if (areSeen(seen, &lhs, &rhs)) return true; return areEqual(seen, *lhs.table, *rhs.table) && areEqual(seen, *lhs.metatable, *rhs.metatable); } bool areEqual(SeenSet& seen, const Type& lhs, const Type& rhs) { if (auto bound = get_if(&lhs.ty)) return areEqual(seen, *bound->boundTo, rhs); if (auto bound = get_if(&rhs.ty)) return areEqual(seen, lhs, *bound->boundTo); if (lhs.ty.index() != rhs.ty.index()) return false; { const FreeType* lf = get_if(&lhs.ty); const FreeType* rf = get_if(&rhs.ty); if (lf && rf) return lf->index == rf->index; } { const GenericType* lg = get_if(&lhs.ty); const GenericType* rg = get_if(&rhs.ty); if (lg && rg) return lg->index == rg->index; } { const PrimitiveType* lp = get_if(&lhs.ty); const PrimitiveType* rp = get_if(&rhs.ty); if (lp && rp) return lp->type == rp->type; } { const GenericType* lg = get_if(&lhs.ty); const GenericType* rg = get_if(&rhs.ty); if (lg && rg) return lg->index == rg->index; } { const ErrorType* le = get_if(&lhs.ty); const ErrorType* re = get_if(&rhs.ty); if (le && re) return le->index == re->index; } { const FunctionType* lf = get_if(&lhs.ty); const FunctionType* rf = get_if(&rhs.ty); if (lf && rf) return areEqual(seen, *lf, *rf); } { const TableType* lt = get_if(&lhs.ty); const TableType* rt = get_if(&rhs.ty); if (lt && rt) return areEqual(seen, *lt, *rt); } { const MetatableType* lmt = get_if(&lhs.ty); const MetatableType* rmt = get_if(&rhs.ty); if (lmt && rmt) return areEqual(seen, *lmt, *rmt); } if (get_if(&lhs.ty) && get_if(&rhs.ty)) return true; return false; } Type* asMutable(TypeId ty) { return const_cast(ty); } bool Type::operator==(const Type& rhs) const { SeenSet seen; return areEqual(seen, *this, rhs); } bool Type::operator!=(const Type& rhs) const { SeenSet seen; return !areEqual(seen, *this, rhs); } Type& Type::operator=(const TypeVariant& rhs) { ty = rhs; return *this; } Type& Type::operator=(TypeVariant&& rhs) { ty = std::move(rhs); return *this; } Type& Type::operator=(const Type& rhs) { LUAU_ASSERT(owningArena == rhs.owningArena); LUAU_ASSERT(!rhs.persistent); reassign(rhs); return *this; } TypeId makeFunction( TypeArena& arena, std::optional selfType, std::initializer_list generics, std::initializer_list genericPacks, std::initializer_list paramTypes, std::initializer_list paramNames, std::initializer_list retTypes ); TypeId makeStringMetatable(NotNull builtinTypes); // BuiltinDefinitions.cpp BuiltinTypes::BuiltinTypes() : arena(new TypeArena) , debugFreezeArena(FFlag::DebugLuauFreezeArena) , nilType(arena->addType(Type{PrimitiveType{PrimitiveType::NilType}, /*persistent*/ true})) , numberType(arena->addType(Type{PrimitiveType{PrimitiveType::Number}, /*persistent*/ true})) , stringType(arena->addType(Type{PrimitiveType{PrimitiveType::String}, /*persistent*/ true})) , booleanType(arena->addType(Type{PrimitiveType{PrimitiveType::Boolean}, /*persistent*/ true})) , threadType(arena->addType(Type{PrimitiveType{PrimitiveType::Thread}, /*persistent*/ true})) , bufferType(arena->addType(Type{PrimitiveType{PrimitiveType::Buffer}, /*persistent*/ true})) , functionType(arena->addType(Type{PrimitiveType{PrimitiveType::Function}, /*persistent*/ true})) , classType(arena->addType(Type{ClassType{"class", {}, std::nullopt, std::nullopt, {}, {}, {}, {}}, /*persistent*/ true})) , tableType(arena->addType(Type{PrimitiveType{PrimitiveType::Table}, /*persistent*/ true})) , emptyTableType(arena->addType(Type{TableType{TableState::Sealed, TypeLevel{}, nullptr}, /*persistent*/ true})) , trueType(arena->addType(Type{SingletonType{BooleanSingleton{true}}, /*persistent*/ true})) , falseType(arena->addType(Type{SingletonType{BooleanSingleton{false}}, /*persistent*/ true})) , anyType(arena->addType(Type{AnyType{}, /*persistent*/ true})) , unknownType(arena->addType(Type{UnknownType{}, /*persistent*/ true})) , neverType(arena->addType(Type{NeverType{}, /*persistent*/ true})) , errorType(arena->addType(Type{ErrorType{}, /*persistent*/ true})) , falsyType(arena->addType(Type{UnionType{{falseType, nilType}}, /*persistent*/ true})) , truthyType(arena->addType(Type{NegationType{falsyType}, /*persistent*/ true})) , optionalNumberType(arena->addType(Type{UnionType{{numberType, nilType}}, /*persistent*/ true})) , optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true})) , emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true})) , anyTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, /*persistent*/ true})) , unknownTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{unknownType}, /*persistent*/ true})) , neverTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{neverType}, /*persistent*/ true})) , uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true})) , errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true})) { freeze(*arena); } BuiltinTypes::~BuiltinTypes() { // Destroy the arena with the same memory management flags it was created with bool prevFlag = FFlag::DebugLuauFreezeArena; FFlag::DebugLuauFreezeArena.value = debugFreezeArena; unfreeze(*arena); arena.reset(nullptr); FFlag::DebugLuauFreezeArena.value = prevFlag; } TypeId BuiltinTypes::errorRecoveryType() const { return errorType; } TypePackId BuiltinTypes::errorRecoveryTypePack() const { return errorTypePack; } TypeId BuiltinTypes::errorRecoveryType(TypeId guess) const { return guess; } TypePackId BuiltinTypes::errorRecoveryTypePack(TypePackId guess) const { return guess; } void persist(TypeId ty) { VecDeque queue{ty}; while (!queue.empty()) { TypeId t = queue.front(); queue.pop_front(); if (t->persistent) continue; asMutable(t)->persistent = true; if (auto btv = get(t)) queue.push_back(btv->boundTo); else if (auto ftv = get(t)) { persist(ftv->argTypes); persist(ftv->retTypes); } else if (auto ttv = get(t)) { LUAU_ASSERT(ttv->state != TableState::Free && ttv->state != TableState::Unsealed); for (const auto& [_name, prop] : ttv->props) queue.push_back(prop.type()); if (ttv->indexer) { queue.push_back(ttv->indexer->indexType); queue.push_back(ttv->indexer->indexResultType); } } else if (auto ctv = get(t)) { for (const auto& [_name, prop] : ctv->props) queue.push_back(prop.type()); } else if (auto utv = get(t)) { for (TypeId opt : utv->options) queue.push_back(opt); } else if (auto itv = get(t)) { for (TypeId opt : itv->parts) queue.push_back(opt); } else if (auto mtv = get(t)) { queue.push_back(mtv->table); queue.push_back(mtv->metatable); } else if (get(t) || get(t) || get(t) || get(t) || get(t) || get(t)) { } else if (auto tfit = get(t)) { for (auto ty : tfit->typeArguments) queue.push_back(ty); for (auto tp : tfit->packArguments) persist(tp); } else { LUAU_ASSERT(!"TypeId is not supported in a persist call"); } } } void persist(TypePackId tp) { if (tp->persistent) return; asMutable(tp)->persistent = true; if (auto p = get(tp)) { for (TypeId ty : p->head) persist(ty); if (p->tail) persist(*p->tail); } else if (auto vtp = get(tp)) { persist(vtp->ty); } else if (get(tp)) { } else if (auto tfitp = get(tp)) { for (auto ty : tfitp->typeArguments) persist(ty); for (auto tp : tfitp->packArguments) persist(tp); } else { LUAU_ASSERT(!"TypePackId is not supported in a persist call"); } } const TypeLevel* getLevel(TypeId ty) { ty = follow(ty); if (auto ftv = get(ty)) return &ftv->level; else if (auto ttv = get(ty)) return &ttv->level; else if (auto ftv = get(ty)) return &ftv->level; else return nullptr; } TypeLevel* getMutableLevel(TypeId ty) { return const_cast(getLevel(ty)); } std::optional getLevel(TypePackId tp) { tp = follow(tp); if (auto ftv = get(tp)) return ftv->level; else return std::nullopt; } const Property* lookupClassProp(const ClassType* cls, const Name& name) { while (cls) { auto it = cls->props.find(name); if (it != cls->props.end()) return &it->second; if (cls->parent) cls = get(*cls->parent); else return nullptr; LUAU_ASSERT(cls); } return nullptr; } bool isSubclass(const ClassType* cls, const ClassType* parent) { while (cls) { if (cls == parent) return true; else if (!cls->parent) return false; cls = get(*cls->parent); LUAU_ASSERT(cls); } return false; } const std::vector& getTypes(const UnionType* utv) { return utv->options; } const std::vector& getTypes(const IntersectionType* itv) { return itv->parts; } UnionTypeIterator begin(const UnionType* utv) { return UnionTypeIterator{utv}; } UnionTypeIterator end(const UnionType* utv) { return UnionTypeIterator{}; } IntersectionTypeIterator begin(const IntersectionType* itv) { return IntersectionTypeIterator{itv}; } IntersectionTypeIterator end(const IntersectionType* itv) { return IntersectionTypeIterator{}; } TypeId freshType(NotNull arena, NotNull builtinTypes, Scope* scope) { return arena->addType(FreeType{scope, builtinTypes->neverType, builtinTypes->unknownType}); } std::vector filterMap(TypeId type, TypeIdPredicate predicate) { type = follow(type); if (auto utv = get(type)) { std::set options; for (TypeId option : utv) if (auto out = predicate(follow(option))) options.insert(*out); return std::vector(options.begin(), options.end()); } else if (auto out = predicate(type)) return {*out}; return {}; } static Tags* getTags(TypeId ty) { ty = follow(ty); if (auto ftv = getMutable(ty)) return &ftv->tags; else if (auto ttv = getMutable(ty)) return &ttv->tags; else if (auto ctv = getMutable(ty)) return &ctv->tags; return nullptr; } void attachTag(TypeId ty, const std::string& tagName) { if (auto tags = getTags(ty)) tags->push_back(tagName); else LUAU_ASSERT(!"This TypeId does not support tags"); } void attachTag(Property& prop, const std::string& tagName) { prop.tags.push_back(tagName); } // We would ideally not expose this because it could cause a footgun. // If the Base class has a tag and you ask if Derived has that tag, it would return false. // Unfortunately, there's already use cases that's hard to disentangle. For now, we expose it. bool hasTag(const Tags& tags, const std::string& tagName) { return std::find(tags.begin(), tags.end(), tagName) != tags.end(); } bool hasTag(TypeId ty, const std::string& tagName) { ty = follow(ty); // We special case classes because getTags only returns a pointer to one vector of tags. // But classes has multiple vector of tags, represented throughout the hierarchy. if (auto ctv = get(ty)) { while (ctv) { if (hasTag(ctv->tags, tagName)) return true; else if (!ctv->parent) return false; ctv = get(*ctv->parent); LUAU_ASSERT(ctv); } } else if (auto tags = getTags(ty)) return hasTag(*tags, tagName); return false; } bool hasTag(const Property& prop, const std::string& tagName) { return hasTag(prop.tags, tagName); } bool TypeFun::operator==(const TypeFun& rhs) const { return type == rhs.type && typeParams == rhs.typeParams && typePackParams == rhs.typePackParams; } bool GenericTypeDefinition::operator==(const GenericTypeDefinition& rhs) const { return ty == rhs.ty && defaultValue == rhs.defaultValue; } bool GenericTypePackDefinition::operator==(const GenericTypePackDefinition& rhs) const { return tp == rhs.tp && defaultValue == rhs.defaultValue; } template<> LUAU_NOINLINE Unifiable::Bound* emplaceType(Type* ty, TypeId& tyArg) { LUAU_ASSERT(ty != follow(tyArg)); return &ty->ty.emplace(tyArg); } } // namespace Luau