// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #pragma once #include "Luau/DenseHash.h" #include "Luau/TypeVar.h" #include "Luau/TypePack.h" LUAU_FASTFLAG(LuauCacheUnifyTableResults) 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 'visitOnce', 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 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 (FFlag::LuauCacheUnifyTableResults && 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 void visitTypeVar(TID ty, F& f, std::unordered_set& seen) { visit_detail::visit(ty, f, seen); } template void visitTypeVar(TID ty, F& f) { std::unordered_set seen; visit_detail::visit(ty, f, seen); } template void visitTypeVarOnce(TID ty, F& f, DenseHashSet& seen) { seen.clear(); visit_detail::visit(ty, f, seen); } } // namespace Luau