// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details #include "Luau/TypeUtils.h" #include "Luau/Normalize.h" #include "Luau/Scope.h" #include "Luau/ToString.h" #include "Luau/TypeInfer.h" LUAU_FASTFLAG(LuauFunctionArgMismatchDetails) namespace Luau { std::optional findMetatableEntry( NotNull singletonTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location) { type = follow(type); std::optional metatable = getMetatable(type, singletonTypes); if (!metatable) return std::nullopt; TypeId unwrapped = follow(*metatable); if (get(unwrapped)) return singletonTypes->anyType; const TableTypeVar* mtt = getTableType(unwrapped); if (!mtt) { errors.push_back(TypeError{location, GenericError{"Metatable was not a table"}}); return std::nullopt; } auto it = mtt->props.find(entry); if (it != mtt->props.end()) return it->second.type; else return std::nullopt; } std::optional findTablePropertyRespectingMeta( NotNull singletonTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location) { if (get(ty)) return ty; if (const TableTypeVar* tableType = getTableType(ty)) { const auto& it = tableType->props.find(name); if (it != tableType->props.end()) return it->second.type; } std::optional mtIndex = findMetatableEntry(singletonTypes, errors, ty, "__index", location); int count = 0; while (mtIndex) { TypeId index = follow(*mtIndex); if (count >= 100) return std::nullopt; ++count; if (const auto& itt = getTableType(index)) { const auto& fit = itt->props.find(name); if (fit != itt->props.end()) return fit->second.type; } else if (const auto& itf = get(index)) { std::optional r = first(follow(itf->retTypes)); if (!r) return singletonTypes->nilType; else return *r; } else if (get(index)) return singletonTypes->anyType; else errors.push_back(TypeError{location, GenericError{"__index should either be a function or table. Got " + toString(index)}}); mtIndex = findMetatableEntry(singletonTypes, errors, *mtIndex, "__index", location); } return std::nullopt; } std::optional getIndexTypeFromType(const ScopePtr& scope, ErrorVec& errors, TypeArena* arena, NotNull singletonTypes, TypeId type, const std::string& prop, const Location& location, bool addErrors, InternalErrorReporter& handle) { type = follow(type); if (get(type) || get(type) || get(type)) return type; if (auto f = get(type)) *asMutable(type) = TableTypeVar{TableState::Free, f->level}; if (isString(type)) { std::optional mtIndex = Luau::findMetatableEntry(singletonTypes, errors, singletonTypes->stringType, "__index", location); LUAU_ASSERT(mtIndex); type = *mtIndex; } if (getTableType(type)) { return findTablePropertyRespectingMeta(singletonTypes, errors, type, prop, location); } else if (const ClassTypeVar* cls = get(type)) { if (const Property* p = lookupClassProp(cls, prop)) return p->type; } else if (const UnionTypeVar* utv = get(type)) { std::vector goodOptions; std::vector badOptions; for (TypeId t : utv) { // TODO: we should probably limit recursion here? // RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit); // Not needed when we normalize types. if (get(follow(t))) return t; if (std::optional ty = getIndexTypeFromType(scope, errors, arena, singletonTypes, t, prop, location, /* addErrors= */ false, handle)) goodOptions.push_back(*ty); else badOptions.push_back(t); } if (!badOptions.empty()) { if (addErrors) { if (goodOptions.empty()) errors.push_back(TypeError{location, UnknownProperty{type, prop}}); else errors.push_back(TypeError{location, MissingUnionProperty{type, badOptions, prop}}); } return std::nullopt; } if (goodOptions.empty()) return singletonTypes->neverType; if (goodOptions.size() == 1) return goodOptions[0]; // TODO: inefficient. TypeId result = arena->addType(UnionTypeVar{std::move(goodOptions)}); auto [ty, ok] = normalize(result, NotNull{scope.get()}, *arena, singletonTypes, handle); if (!ok && addErrors) errors.push_back(TypeError{location, NormalizationTooComplex{}}); return ok ? ty : singletonTypes->anyType; } else if (const IntersectionTypeVar* itv = get(type)) { std::vector parts; for (TypeId t : itv->parts) { // TODO: we should probably limit recursion here? // RecursionLimiter _rl(&recursionCount, FInt::LuauTypeInferRecursionLimit); if (std::optional ty = getIndexTypeFromType(scope, errors, arena, singletonTypes, t, prop, location, /* addErrors= */ false, handle)) parts.push_back(*ty); } // If no parts of the intersection had the property we looked up for, it never existed at all. if (parts.empty()) { if (addErrors) errors.push_back(TypeError{location, UnknownProperty{type, prop}}); return std::nullopt; } if (parts.size() == 1) return parts[0]; return arena->addType(IntersectionTypeVar{std::move(parts)}); // Not at all correct. } if (addErrors) errors.push_back(TypeError{location, UnknownProperty{type, prop}}); return std::nullopt; } std::pair> getParameterExtents(const TxnLog* log, TypePackId tp, bool includeHiddenVariadics) { size_t minCount = 0; size_t optionalCount = 0; auto it = begin(tp, log); auto endIter = end(tp); while (it != endIter) { TypeId ty = *it; if (isOptional(ty)) ++optionalCount; else { minCount += optionalCount; optionalCount = 0; minCount++; } ++it; } if (it.tail() && (!FFlag::LuauFunctionArgMismatchDetails || isVariadicTail(*it.tail(), *log, includeHiddenVariadics))) return {minCount, std::nullopt}; else return {minCount, minCount + optionalCount}; } std::vector flatten(TypeArena& arena, NotNull singletonTypes, TypePackId pack, size_t length) { std::vector result; auto it = begin(pack); auto endIt = end(pack); while (it != endIt) { result.push_back(*it); if (result.size() >= length) return result; ++it; } if (!it.tail()) return result; TypePackId tail = *it.tail(); if (get(tail)) LUAU_ASSERT(0); else if (auto vtp = get(tail)) { while (result.size() < length) result.push_back(vtp->ty); } else if (get(tail) || get(tail)) { while (result.size() < length) result.push_back(arena.addType(FreeTypeVar{nullptr})); } else if (auto etp = get(tail)) { while (result.size() < length) result.push_back(singletonTypes->errorRecoveryType()); } return result; } } // namespace Luau