// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once #include #include "Luau/DenseHash.h" #include "Luau/RecursionCounter.h" #include "Luau/TypePack.h" #include "Luau/TypeVar.h" LUAU_FASTFLAG(LuauUseVisitRecursionLimit) LUAU_FASTINT(LuauVisitRecursionLimit) namespace Luau { namespace visit_detail { /** * Apply f(tid, t, seen) if doing so would pass type checking, else apply f(tid, t) * * We do this to permit (but not require) TypeVar visitors to accept the seen set as an argument. */ template auto apply(A tid, const B& t, C& c, F& f) -> decltype(f(tid, t, c)) { return f(tid, t, c); } template auto apply(A tid, const B& t, C&, F& f) -> decltype(f(tid, t)) { return f(tid, t); } inline bool hasSeen(std::unordered_set& seen, const void* tv) { void* ttv = const_cast(tv); return !seen.insert(ttv).second; } inline bool hasSeen(DenseHashSet& seen, const void* tv) { void* ttv = const_cast(tv); if (seen.contains(ttv)) return true; seen.insert(ttv); return false; } inline void unsee(std::unordered_set& seen, const void* tv) { void* ttv = const_cast(tv); seen.erase(ttv); } inline void unsee(DenseHashSet& seen, const void* tv) { // When DenseHashSet is used for 'visitTypeVarOnce', where don't forget visited elements } template void visit(TypePackId tp, F& f, Set& seen); template void visit(TypeId ty, F& f, Set& seen) { if (visit_detail::hasSeen(seen, ty)) { f.cycle(ty); return; } if (auto btv = get(ty)) { if (apply(ty, *btv, seen, f)) visit(btv->boundTo, f, seen); } else if (auto ftv = get(ty)) apply(ty, *ftv, seen, f); else if (auto gtv = get(ty)) apply(ty, *gtv, seen, f); else if (auto etv = get(ty)) apply(ty, *etv, seen, f); else if (auto ctv = get(ty)) { if (apply(ty, *ctv, seen, f)) { for (TypeId part : ctv->parts) visit(part, f, seen); } } else if (auto ptv = get(ty)) apply(ty, *ptv, seen, f); else if (auto ftv = get(ty)) { if (apply(ty, *ftv, seen, f)) { visit(ftv->argTypes, f, seen); visit(ftv->retType, f, seen); } } else if (auto ttv = get(ty)) { // Some visitors want to see bound tables, that's why we visit the original type if (apply(ty, *ttv, seen, f)) { if (ttv->boundTo) { visit(*ttv->boundTo, f, seen); } else { for (auto& [_name, prop] : ttv->props) visit(prop.type, f, seen); if (ttv->indexer) { visit(ttv->indexer->indexType, f, seen); visit(ttv->indexer->indexResultType, f, seen); } } } } else if (auto mtv = get(ty)) { if (apply(ty, *mtv, seen, f)) { visit(mtv->table, f, seen); visit(mtv->metatable, f, seen); } } else if (auto ctv = get(ty)) { if (apply(ty, *ctv, seen, f)) { for (const auto& [name, prop] : ctv->props) visit(prop.type, f, seen); if (ctv->parent) visit(*ctv->parent, f, seen); if (ctv->metatable) visit(*ctv->metatable, f, seen); } } else if (auto atv = get(ty)) apply(ty, *atv, seen, f); else if (auto utv = get(ty)) { if (apply(ty, *utv, seen, f)) { for (TypeId optTy : utv->options) visit(optTy, f, seen); } } else if (auto itv = get(ty)) { if (apply(ty, *itv, seen, f)) { for (TypeId partTy : itv->parts) visit(partTy, f, seen); } } visit_detail::unsee(seen, ty); } template void visit(TypePackId tp, F& f, Set& seen) { if (visit_detail::hasSeen(seen, tp)) { f.cycle(tp); return; } if (auto btv = get(tp)) { if (apply(tp, *btv, seen, f)) visit(btv->boundTo, f, seen); } else if (auto ftv = get(tp)) apply(tp, *ftv, seen, f); else if (auto gtv = get(tp)) apply(tp, *gtv, seen, f); else if (auto etv = get(tp)) apply(tp, *etv, seen, f); else if (auto pack = get(tp)) { apply(tp, *pack, seen, f); for (TypeId ty : pack->head) visit(ty, f, seen); if (pack->tail) visit(*pack->tail, f, seen); } else if (auto pack = get(tp)) { apply(tp, *pack, seen, f); visit(pack->ty, f, seen); } visit_detail::unsee(seen, tp); } } // namespace visit_detail template struct GenericTypeVarVisitor { using Set = S; Set seen; int recursionCounter = 0; GenericTypeVarVisitor() = default; explicit GenericTypeVarVisitor(Set seen) : seen(std::move(seen)) { } virtual void cycle(TypeId) {} virtual void cycle(TypePackId) {} virtual bool visit(TypeId ty) { return true; } virtual bool visit(TypeId ty, const BoundTypeVar& btv) { return visit(ty); } virtual bool visit(TypeId ty, const FreeTypeVar& ftv) { return visit(ty); } virtual bool visit(TypeId ty, const GenericTypeVar& gtv) { return visit(ty); } virtual bool visit(TypeId ty, const ErrorTypeVar& etv) { return visit(ty); } virtual bool visit(TypeId ty, const ConstrainedTypeVar& ctv) { return visit(ty); } virtual bool visit(TypeId ty, const PrimitiveTypeVar& ptv) { return visit(ty); } virtual bool visit(TypeId ty, const FunctionTypeVar& ftv) { return visit(ty); } virtual bool visit(TypeId ty, const TableTypeVar& ttv) { return visit(ty); } virtual bool visit(TypeId ty, const MetatableTypeVar& mtv) { return visit(ty); } virtual bool visit(TypeId ty, const ClassTypeVar& ctv) { return visit(ty); } virtual bool visit(TypeId ty, const AnyTypeVar& atv) { return visit(ty); } virtual bool visit(TypeId ty, const UnionTypeVar& utv) { return visit(ty); } virtual bool visit(TypeId ty, const IntersectionTypeVar& itv) { return visit(ty); } virtual bool visit(TypePackId tp) { return true; } virtual bool visit(TypePackId tp, const BoundTypePack& btp) { return visit(tp); } virtual bool visit(TypePackId tp, const FreeTypePack& ftp) { return visit(tp); } virtual bool visit(TypePackId tp, const GenericTypePack& gtp) { return visit(tp); } virtual bool visit(TypePackId tp, const Unifiable::Error& etp) { return visit(tp); } virtual bool visit(TypePackId tp, const TypePack& pack) { return visit(tp); } virtual bool visit(TypePackId tp, const VariadicTypePack& vtp) { return visit(tp); } void traverse(TypeId ty) { RecursionLimiter limiter{&recursionCounter, FInt::LuauVisitRecursionLimit, "TypeVarVisitor"}; if (visit_detail::hasSeen(seen, ty)) { cycle(ty); return; } if (auto btv = get(ty)) { if (visit(ty, *btv)) traverse(btv->boundTo); } else if (auto ftv = get(ty)) visit(ty, *ftv); else if (auto gtv = get(ty)) visit(ty, *gtv); else if (auto etv = get(ty)) visit(ty, *etv); else if (auto ctv = get(ty)) { if (visit(ty, *ctv)) { for (TypeId part : ctv->parts) traverse(part); } } else if (auto ptv = get(ty)) visit(ty, *ptv); else if (auto ftv = get(ty)) { if (visit(ty, *ftv)) { traverse(ftv->argTypes); traverse(ftv->retType); } } else if (auto ttv = get(ty)) { // Some visitors want to see bound tables, that's why we traverse the original type if (visit(ty, *ttv)) { if (ttv->boundTo) { traverse(*ttv->boundTo); } else { for (auto& [_name, prop] : ttv->props) traverse(prop.type); if (ttv->indexer) { traverse(ttv->indexer->indexType); traverse(ttv->indexer->indexResultType); } } } } else if (auto mtv = get(ty)) { if (visit(ty, *mtv)) { traverse(mtv->table); traverse(mtv->metatable); } } else if (auto ctv = get(ty)) { if (visit(ty, *ctv)) { for (const auto& [name, prop] : ctv->props) traverse(prop.type); if (ctv->parent) traverse(*ctv->parent); if (ctv->metatable) traverse(*ctv->metatable); } } else if (auto atv = get(ty)) visit(ty, *atv); else if (auto utv = get(ty)) { if (visit(ty, *utv)) { for (TypeId optTy : utv->options) traverse(optTy); } } else if (auto itv = get(ty)) { if (visit(ty, *itv)) { for (TypeId partTy : itv->parts) traverse(partTy); } } visit_detail::unsee(seen, ty); } void traverse(TypePackId tp) { if (visit_detail::hasSeen(seen, tp)) { cycle(tp); return; } if (auto btv = get(tp)) { if (visit(tp, *btv)) traverse(btv->boundTo); } else if (auto ftv = get(tp)) visit(tp, *ftv); else if (auto gtv = get(tp)) visit(tp, *gtv); else if (auto etv = get(tp)) visit(tp, *etv); else if (auto pack = get(tp)) { visit(tp, *pack); for (TypeId ty : pack->head) traverse(ty); if (pack->tail) traverse(*pack->tail); } else if (auto pack = get(tp)) { visit(tp, *pack); traverse(pack->ty); } else LUAU_ASSERT(!"GenericTypeVarVisitor::traverse(TypePackId) is not exhaustive!"); visit_detail::unsee(seen, tp); } }; /** Visit each type under a given type. Skips over cycles and keeps recursion depth under control. * * The same type may be visited multiple times if there are multiple distinct paths to it. If this is undesirable, use * TypeVarOnceVisitor. */ struct TypeVarVisitor : GenericTypeVarVisitor> { }; /// Visit each type under a given type. Each type will only be checked once even if there are multiple paths to it. struct TypeVarOnceVisitor : GenericTypeVarVisitor> { TypeVarOnceVisitor() : GenericTypeVarVisitor{DenseHashSet{nullptr}} { } }; // Clip with FFlagLuauUseVisitRecursionLimit template void DEPRECATED_visitTypeVar(TID ty, F& f, std::unordered_set& seen) { visit_detail::visit(ty, f, seen); } // Delete and inline when clipping FFlagLuauUseVisitRecursionLimit template void DEPRECATED_visitTypeVar(TID ty, F& f) { if (FFlag::LuauUseVisitRecursionLimit) f.traverse(ty); else { std::unordered_set seen; visit_detail::visit(ty, f, seen); } } // Delete and inline when clipping FFlagLuauUseVisitRecursionLimit template void DEPRECATED_visitTypeVarOnce(TID ty, F& f, DenseHashSet& seen) { if (FFlag::LuauUseVisitRecursionLimit) f.traverse(ty); else { seen.clear(); visit_detail::visit(ty, f, seen); } } } // namespace Luau