Sync to upstream/release/582 (#960)

* Optimized operations like instantiation and module export for very
large types

In our new typechecker:
* Typechecking of function calls was rewritten to handle more cases
correctly
* Fixed a crash that can happen after self-referential type is exported
from a module
* Fixed a false positive error in string comparison
* Added handling of `for...in` variable type annotations and fixed
issues with the iterator call inside
* Self-referential 'hasProp' and 'setProp' constraints are now handled
correctly
 
In our native code generation (jit):
* Added '--target' argument to luau-compile to test multiple
architectures different from host architecture
* GC barrier tag check is skipped if type is already known to be
GC-collectable
* Added GET_TYPE/GET_TYPEOF instructions for type/typeof fast-calls
* Improved code size of interrupt handlers on X64
This commit is contained in:
vegorov-rbx 2023-06-23 23:19:39 -07:00 committed by GitHub
parent d458d240cd
commit 76bea81a7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 1487 additions and 1028 deletions

View File

@ -83,7 +83,7 @@ struct IterableConstraint
TypePackId variables;
const AstNode* nextAstFragment;
DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes;
DenseHashMap<const AstNode*, TypeId>* astForInNextTypes;
};
// name(namedType) = name

View File

@ -245,6 +245,17 @@ private:
template <typename TID>
bool tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
/**
* Bind a BlockedType to another type while taking care not to bind it to
* itself in the case that resultTy == blockedTy. This can happen if we
* have a tautological constraint. When it does, we must instead bind
* blockedTy to a fresh type belonging to an appropriate scope.
*
* To determine which scope is appropriate, we also accept rootTy, which is
* to be the type that contains blockedTy.
*/
void bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, Location location);
/**
* Marks a constraint as being blocked on a type or type pack. The constraint
* solver will not attempt to dispatch blocked constraints until their

View File

@ -81,13 +81,29 @@ struct Module
DenseHashMap<const AstExpr*, TypePackId> astTypePacks{nullptr};
DenseHashMap<const AstExpr*, TypeId> astExpectedTypes{nullptr};
// For AST nodes that are function calls, this map provides the
// unspecialized type of the function that was called. If a function call
// resolves to a __call metamethod application, this map will point at that
// metamethod.
//
// This is useful for type checking and Signature Help.
DenseHashMap<const AstNode*, TypeId> astOriginalCallTypes{nullptr};
// The specialization of a function that was selected. If the function is
// generic, those generic type parameters will be replaced with the actual
// types that were passed. If the function is an overload, this map will
// point at the specific overloads that were selected.
DenseHashMap<const AstNode*, TypeId> astOverloadResolvedTypes{nullptr};
// Only used with for...in loops. The computed type of the next() function
// is kept here for type checking.
DenseHashMap<const AstNode*, TypeId> astForInNextTypes{nullptr};
DenseHashMap<const AstType*, TypeId> astResolvedTypes{nullptr};
DenseHashMap<const AstTypePack*, TypePackId> astResolvedTypePacks{nullptr};
// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because we need a sentinel value for the map.
// Map AST nodes to the scope they create. Cannot be NotNull<Scope> because
// we need a sentinel value for the map.
DenseHashMap<const AstNode*, Scope*> astScopes{nullptr};
std::unordered_map<Name, TypeId> declaredGlobals;
@ -103,8 +119,9 @@ struct Module
bool hasModuleScope() const;
ScopePtr getModuleScope() const;
// Once a module has been typechecked, we clone its public interface into a separate arena.
// This helps us to force Type ownership into a DAG rather than a DCG.
// Once a module has been typechecked, we clone its public interface into a
// separate arena. This helps us to force Type ownership into a DAG rather
// than a DCG.
void clonePublicInterface(NotNull<BuiltinTypes> builtinTypes, InternalErrorReporter& ice);
};

View File

@ -207,8 +207,6 @@ struct NormalizedFunctionType
struct NormalizedType;
using NormalizedTyvars = std::unordered_map<TypeId, std::unique_ptr<NormalizedType>>;
bool isInhabited_DEPRECATED(const NormalizedType& norm);
// A normalized type is either any, unknown, or one of the form P | T | F | G where
// * P is a union of primitive types (including singletons, classes and the error type)
// * T is a union of table types

View File

@ -69,6 +69,19 @@ struct TarjanWorklistVertex
int lastEdge;
};
struct TarjanNode
{
TypeId ty;
TypePackId tp;
bool onStack;
bool dirty;
// Tarjan calculates the lowlink for each vertex,
// which is the lowest ancestor index reachable from the vertex.
int lowlink;
};
// Tarjan's algorithm for finding the SCCs in a cyclic structure.
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
struct Tarjan
@ -76,17 +89,12 @@ struct Tarjan
// Vertices (types and type packs) are indexed, using pre-order traversal.
DenseHashMap<TypeId, int> typeToIndex{nullptr};
DenseHashMap<TypePackId, int> packToIndex{nullptr};
std::vector<TypeId> indexToType;
std::vector<TypePackId> indexToPack;
std::vector<TarjanNode> nodes;
// Tarjan keeps a stack of vertices where we're still in the process
// of finding their SCC.
std::vector<int> stack;
std::vector<bool> onStack;
// Tarjan calculates the lowlink for each vertex,
// which is the lowest ancestor index reachable from the vertex.
std::vector<int> lowlink;
int childCount = 0;
int childLimit = 0;
@ -98,6 +106,7 @@ struct Tarjan
std::vector<TypeId> edgesTy;
std::vector<TypePackId> edgesTp;
std::vector<TarjanWorklistVertex> worklist;
// This is hot code, so we optimize recursion to a stack.
TarjanResult loop();
@ -124,10 +133,22 @@ struct Tarjan
TarjanResult visitRoot(TypeId ty);
TarjanResult visitRoot(TypePackId ty);
// Each subclass gets called back once for each edge,
// and once for each SCC.
virtual void visitEdge(int index, int parentIndex) {}
virtual void visitSCC(int index) {}
void clearTarjan();
// Get/set the dirty bit for an index (grows the vector if needed)
bool getDirty(int index);
void setDirty(int index, bool d);
// Find all the dirty vertices reachable from `t`.
TarjanResult findDirty(TypeId t);
TarjanResult findDirty(TypePackId t);
// We find dirty vertices using Tarjan
void visitEdge(int index, int parentIndex);
void visitSCC(int index);
TarjanResult loop_DEPRECATED();
void visitSCC_DEPRECATED(int index);
// Each subclass can decide to ignore some nodes.
virtual bool ignoreChildren(TypeId ty)
@ -150,27 +171,6 @@ struct Tarjan
{
return ignoreChildren(ty);
}
};
// We use Tarjan to calculate dirty bits. We set `dirty[i]` true
// if the vertex with index `i` can reach a dirty vertex.
struct FindDirty : Tarjan
{
std::vector<bool> dirty;
void clearTarjan();
// Get/set the dirty bit for an index (grows the vector if needed)
bool getDirty(int index);
void setDirty(int index, bool d);
// Find all the dirty vertices reachable from `t`.
TarjanResult findDirty(TypeId t);
TarjanResult findDirty(TypePackId t);
// We find dirty vertices using Tarjan
void visitEdge(int index, int parentIndex) override;
void visitSCC(int index) override;
// Subclasses should say which vertices are dirty,
// and what to do with dirty vertices.
@ -178,11 +178,18 @@ struct FindDirty : Tarjan
virtual bool isDirty(TypePackId tp) = 0;
virtual void foundDirty(TypeId ty) = 0;
virtual void foundDirty(TypePackId tp) = 0;
// TODO: remove with FFlagLuauTarjanSingleArr
std::vector<TypeId> indexToType;
std::vector<TypePackId> indexToPack;
std::vector<bool> onStack;
std::vector<int> lowlink;
std::vector<bool> dirty;
};
// And finally substitution, which finds all the reachable dirty vertices
// and replaces them with clean ones.
struct Substitution : FindDirty
struct Substitution : Tarjan
{
protected:
Substitution(const TxnLog* log_, TypeArena* arena)

View File

@ -19,8 +19,6 @@
#include <unordered_map>
#include <unordered_set>
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
namespace Luau
{

View File

@ -64,4 +64,13 @@ const T* get(std::optional<Ty> ty)
return nullptr;
}
template<typename Ty>
std::optional<Ty> follow(std::optional<Ty> ty)
{
if (ty)
return follow(*ty);
else
return std::nullopt;
}
} // namespace Luau

View File

@ -6,8 +6,6 @@
#include "Luau/Normalize.h"
#include "Luau/TxnLog.h"
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
namespace Luau
{
@ -78,7 +76,7 @@ TypePackId Anyification::clean(TypePackId tp)
bool Anyification::ignoreChildren(TypeId ty)
{
if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
if (get<ClassType>(ty))
return true;
return ty->persistent;

View File

@ -2,8 +2,6 @@
#include "Luau/ApplyTypeFunction.h"
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
namespace Luau
{
@ -33,7 +31,7 @@ bool ApplyTypeFunction::ignoreChildren(TypeId ty)
{
if (get<GenericType>(ty))
return true;
else if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
else if (get<ClassType>(ty))
return true;
else
return false;

View File

@ -751,7 +751,12 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
variableTypes.reserve(forIn->vars.size);
for (AstLocal* var : forIn->vars)
{
TypeId ty = freshType(loopScope);
TypeId ty = nullptr;
if (var->annotation)
ty = resolveType(loopScope, var->annotation, /*inTypeArguments*/ false);
else
ty = freshType(loopScope);
loopScope->bindings[var] = Binding{ty, var->location};
variableTypes.push_back(ty);
@ -763,7 +768,7 @@ ControlFlow ConstraintGraphBuilder::visit(const ScopePtr& scope, AstStatForIn* f
TypePackId variablePack = arena->addTypePack(std::move(variableTypes), arena->addTypePack(FreeTypePack{loopScope.get()}));
addConstraint(
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack, forIn->values.data[0], &module->astOverloadResolvedTypes});
loopScope, getLocation(forIn->values), IterableConstraint{iterator, variablePack, forIn->values.data[0], &module->astForInNextTypes});
visit(loopScope, forIn->body);
return ControlFlow::None;

View File

@ -1453,7 +1453,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
return false;
}
asMutable(c.resultType)->ty.emplace<BoundType>(result.value_or(builtinTypes->anyType));
bindBlockedType(c.resultType, result.value_or(builtinTypes->anyType), c.subjectType, constraint->location);
unblock(c.resultType, constraint->location);
return true;
}
@ -1559,8 +1559,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
existingPropType = result;
}
auto bind = [](TypeId a, TypeId b) {
asMutable(a)->ty.emplace<BoundType>(b);
auto bind = [&](TypeId a, TypeId b) {
bindBlockedType(a, b, c.subjectType, constraint->location);
};
if (existingPropType)
@ -2143,7 +2143,9 @@ bool ConstraintSolver::tryDispatchIterableFunction(
// if there are no errors from unifying the two, we can pass forward the expected type as our selected resolution.
if (errors.empty())
(*c.astOverloadResolvedTypes)[c.nextAstFragment] = expectedNextTy;
{
(*c.astForInNextTypes)[c.nextAstFragment] = expectedNextTy;
}
auto it = begin(nextRetPack);
std::vector<TypeId> modifiedNextRetHead;
@ -2380,6 +2382,31 @@ bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy,
return true;
}
void ConstraintSolver::bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, Location location)
{
resultTy = follow(resultTy);
LUAU_ASSERT(get<BlockedType>(blockedTy));
if (blockedTy == resultTy)
{
rootTy = follow(rootTy);
Scope* freeScope = nullptr;
if (auto ft = get<FreeType>(rootTy))
freeScope = ft->scope;
else if (auto tt = get<TableType>(rootTy); tt && tt->state == TableState::Free)
freeScope = tt->scope;
else
iceReporter.ice("bindBlockedType couldn't find an appropriate scope for a fresh type!", location);
LUAU_ASSERT(freeScope);
asMutable(blockedTy)->ty.emplace<BoundType>(arena->freshType(freeScope));
}
else
asMutable(blockedTy)->ty.emplace<BoundType>(resultTy);
}
void ConstraintSolver::block_(BlockedConstraintId target, NotNull<const Constraint> constraint)
{
blocked[target].push_back(constraint);

View File

@ -10,7 +10,6 @@
#include <stdexcept>
#include <type_traits>
LUAU_FASTFLAGVARIABLE(LuauTypeMismatchInvarianceInError, false)
static std::string wrongNumberOfArgsString(
size_t expectedCount, std::optional<size_t> maximumCount, size_t actualCount, const char* argPrefix = nullptr, bool isVariadic = false)
@ -106,7 +105,7 @@ struct ErrorConverter
{
result += "; " + tm.reason;
}
else if (FFlag::LuauTypeMismatchInvarianceInError && tm.context == TypeMismatch::InvariantContext)
else if (tm.context == TypeMismatch::InvariantContext)
{
result += " in an invariant context";
}

View File

@ -950,6 +950,7 @@ void Frontend::checkBuildQueueItem(BuildQueueItem& item)
module->astExpectedTypes.clear();
module->astOriginalCallTypes.clear();
module->astOverloadResolvedTypes.clear();
module->astForInNextTypes.clear();
module->astResolvedTypes.clear();
module->astResolvedTypePacks.clear();
module->astScopes.clear();

View File

@ -4,8 +4,6 @@
#include "Luau/TxnLog.h"
#include "Luau/TypeArena.h"
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
namespace Luau
{
@ -33,7 +31,7 @@ bool Instantiation::ignoreChildren(TypeId ty)
{
if (log->getMutable<FunctionType>(ty))
return true;
else if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
else if (get<ClassType>(ty))
return true;
else
return false;
@ -84,7 +82,7 @@ bool ReplaceGenerics::ignoreChildren(TypeId ty)
// whenever we quantify, so the vectors overlap if and only if they are equal.
return (!generics.empty() || !genericPacks.empty()) && (ftv->generics == generics) && (ftv->genericPacks == genericPacks);
}
else if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
else if (get<ClassType>(ty))
return true;
else
{

View File

@ -16,9 +16,6 @@
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAGVARIABLE(LuauClonePublicInterfaceLess2, false);
LUAU_FASTFLAG(LuauSubstitutionReentrant);
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution);
LUAU_FASTFLAG(LuauSubstitutionFixMissingFields);
LUAU_FASTFLAGVARIABLE(LuauCloneSkipNonInternalVisit, false);
namespace Luau
@ -134,8 +131,6 @@ struct ClonePublicInterface : Substitution
TypeId cloneType(TypeId ty)
{
LUAU_ASSERT(FFlag::LuauSubstitutionReentrant && FFlag::LuauSubstitutionFixMissingFields);
std::optional<TypeId> result = substitute(ty);
if (result)
{
@ -150,8 +145,6 @@ struct ClonePublicInterface : Substitution
TypePackId cloneTypePack(TypePackId tp)
{
LUAU_ASSERT(FFlag::LuauSubstitutionReentrant && FFlag::LuauSubstitutionFixMissingFields);
std::optional<TypePackId> result = substitute(tp);
if (result)
{
@ -166,8 +159,6 @@ struct ClonePublicInterface : Substitution
TypeFun cloneTypeFun(const TypeFun& tf)
{
LUAU_ASSERT(FFlag::LuauSubstitutionReentrant && FFlag::LuauSubstitutionFixMissingFields);
std::vector<GenericTypeDefinition> typeParams;
std::vector<GenericTypePackDefinition> typePackParams;

View File

@ -19,7 +19,6 @@ LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
LUAU_FASTFLAGVARIABLE(LuauNormalizeBlockedTypes, false);
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
LUAU_FASTFLAG(LuauUninhabitedSubAnything2)
LUAU_FASTFLAG(LuauTransitiveSubtyping)
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
@ -312,12 +311,6 @@ static bool isShallowInhabited(const NormalizedType& norm)
!norm.functions.isNever() || !norm.tables.empty() || !norm.tyvars.empty();
}
bool isInhabited_DEPRECATED(const NormalizedType& norm)
{
LUAU_ASSERT(!FFlag::LuauUninhabitedSubAnything2);
return isShallowInhabited(norm);
}
bool Normalizer::isInhabited(const NormalizedType* norm, std::unordered_set<TypeId> seen)
{
// If normalization failed, the type is complex, and so is more likely than not to be inhabited.

View File

@ -10,7 +10,6 @@
LUAU_FASTFLAG(DebugLuauSharedSelf)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
namespace Luau
{
@ -244,7 +243,7 @@ struct PureQuantifier : Substitution
bool ignoreChildren(TypeId ty) override
{
if (FFlag::LuauClassTypeVarsInSubstitution && get<ClassType>(ty))
if (get<ClassType>(ty))
return true;
return ty->persistent;

View File

@ -8,13 +8,11 @@
#include <algorithm>
#include <stdexcept>
LUAU_FASTFLAGVARIABLE(LuauSubstitutionFixMissingFields, false)
LUAU_FASTFLAG(LuauClonePublicInterfaceLess2)
LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
LUAU_FASTFLAGVARIABLE(LuauClassTypeVarsInSubstitution, false)
LUAU_FASTFLAGVARIABLE(LuauSubstitutionReentrant, false)
LUAU_FASTFLAG(DebugLuauReadWriteProperties)
LUAU_FASTFLAG(LuauCloneSkipNonInternalVisit)
LUAU_FASTFLAGVARIABLE(LuauTarjanSingleArr, false)
namespace Luau
{
@ -113,20 +111,35 @@ static TypeId shallowClone(TypeId ty, TypeArena& dest, const TxnLog* log, bool a
else if constexpr (std::is_same_v<T, BlockedType>)
return dest.addType(a);
else if constexpr (std::is_same_v<T, PrimitiveType>)
{
LUAU_ASSERT(ty->persistent);
return ty;
}
else if constexpr (std::is_same_v<T, PendingExpansionType>)
{
PendingExpansionType clone = PendingExpansionType{a.prefix, a.name, a.typeArguments, a.packArguments};
return dest.addType(std::move(clone));
}
else if constexpr (std::is_same_v<T, AnyType>)
{
LUAU_ASSERT(ty->persistent);
return ty;
}
else if constexpr (std::is_same_v<T, ErrorType>)
{
LUAU_ASSERT(ty->persistent);
return ty;
}
else if constexpr (std::is_same_v<T, UnknownType>)
{
LUAU_ASSERT(ty->persistent);
return ty;
}
else if constexpr (std::is_same_v<T, NeverType>)
{
LUAU_ASSERT(ty->persistent);
return ty;
}
else if constexpr (std::is_same_v<T, LazyType>)
return ty;
else if constexpr (std::is_same_v<T, SingletonType>)
@ -227,13 +240,10 @@ void Tarjan::visitChildren(TypeId ty, int index)
if (const FunctionType* ftv = get<FunctionType>(ty))
{
if (FFlag::LuauSubstitutionFixMissingFields)
{
for (TypeId generic : ftv->generics)
visitChild(generic);
for (TypePackId genericPack : ftv->genericPacks)
visitChild(genericPack);
}
for (TypeId generic : ftv->generics)
visitChild(generic);
for (TypePackId genericPack : ftv->genericPacks)
visitChild(genericPack);
visitChild(ftv->argTypes);
visitChild(ftv->retTypes);
@ -295,7 +305,7 @@ void Tarjan::visitChildren(TypeId ty, int index)
for (TypePackId a : tfit->packArguments)
visitChild(a);
}
else if (const ClassType* ctv = get<ClassType>(ty); FFlag::LuauClassTypeVarsInSubstitution && ctv)
else if (const ClassType* ctv = get<ClassType>(ty))
{
for (const auto& [name, prop] : ctv->props)
visitChild(prop.type());
@ -348,36 +358,67 @@ std::pair<int, bool> Tarjan::indexify(TypeId ty)
{
ty = log->follow(ty);
bool fresh = !typeToIndex.contains(ty);
int& index = typeToIndex[ty];
if (fresh)
if (FFlag::LuauTarjanSingleArr)
{
index = int(indexToType.size());
indexToType.push_back(ty);
indexToPack.push_back(nullptr);
onStack.push_back(false);
lowlink.push_back(index);
auto [index, fresh] = typeToIndex.try_insert(ty, false);
if (fresh)
{
index = int(nodes.size());
nodes.push_back({ty, nullptr, false, false, index});
}
return {index, fresh};
}
else
{
bool fresh = !typeToIndex.contains(ty);
int& index = typeToIndex[ty];
if (fresh)
{
index = int(indexToType.size());
indexToType.push_back(ty);
indexToPack.push_back(nullptr);
onStack.push_back(false);
lowlink.push_back(index);
}
return {index, fresh};
}
return {index, fresh};
}
std::pair<int, bool> Tarjan::indexify(TypePackId tp)
{
tp = log->follow(tp);
bool fresh = !packToIndex.contains(tp);
int& index = packToIndex[tp];
if (fresh)
if (FFlag::LuauTarjanSingleArr)
{
index = int(indexToPack.size());
indexToType.push_back(nullptr);
indexToPack.push_back(tp);
onStack.push_back(false);
lowlink.push_back(index);
auto [index, fresh] = packToIndex.try_insert(tp, false);
if (fresh)
{
index = int(nodes.size());
nodes.push_back({nullptr, tp, false, false, index});
}
return {index, fresh};
}
else
{
bool fresh = !packToIndex.contains(tp);
int& index = packToIndex[tp];
if (fresh)
{
index = int(indexToPack.size());
indexToType.push_back(nullptr);
indexToPack.push_back(tp);
onStack.push_back(false);
lowlink.push_back(index);
}
return {index, fresh};
}
return {index, fresh};
}
void Tarjan::visitChild(TypeId ty)
@ -397,6 +438,246 @@ void Tarjan::visitChild(TypePackId tp)
}
TarjanResult Tarjan::loop()
{
if (!FFlag::LuauTarjanSingleArr)
return loop_DEPRECATED();
// Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing
while (!worklist.empty())
{
auto [index, currEdge, lastEdge] = worklist.back();
// First visit
if (currEdge == -1)
{
++childCount;
if (childLimit > 0 && childLimit <= childCount)
return TarjanResult::TooManyChildren;
stack.push_back(index);
nodes[index].onStack = true;
currEdge = int(edgesTy.size());
// Fill in edge list of this vertex
if (TypeId ty = nodes[index].ty)
visitChildren(ty, index);
else if (TypePackId tp = nodes[index].tp)
visitChildren(tp, index);
lastEdge = int(edgesTy.size());
}
// Visit children
bool foundFresh = false;
for (; currEdge < lastEdge; currEdge++)
{
int childIndex = -1;
bool fresh = false;
if (auto ty = edgesTy[currEdge])
std::tie(childIndex, fresh) = indexify(ty);
else if (auto tp = edgesTp[currEdge])
std::tie(childIndex, fresh) = indexify(tp);
else
LUAU_ASSERT(false);
if (fresh)
{
// Original recursion point, update the parent continuation point and start the new element
worklist.back() = {index, currEdge + 1, lastEdge};
worklist.push_back({childIndex, -1, -1});
// We need to continue the top-level loop from the start with the new worklist element
foundFresh = true;
break;
}
else if (nodes[childIndex].onStack)
{
nodes[index].lowlink = std::min(nodes[index].lowlink, childIndex);
}
visitEdge(childIndex, index);
}
if (foundFresh)
continue;
if (nodes[index].lowlink == index)
{
visitSCC(index);
while (!stack.empty())
{
int popped = stack.back();
stack.pop_back();
nodes[popped].onStack = false;
if (popped == index)
break;
}
}
worklist.pop_back();
// Original return from recursion into a child
if (!worklist.empty())
{
auto [parentIndex, _, parentEndEdge] = worklist.back();
// No need to keep child edges around
edgesTy.resize(parentEndEdge);
edgesTp.resize(parentEndEdge);
nodes[parentIndex].lowlink = std::min(nodes[parentIndex].lowlink, nodes[index].lowlink);
visitEdge(index, parentIndex);
}
}
return TarjanResult::Ok;
}
TarjanResult Tarjan::visitRoot(TypeId ty)
{
childCount = 0;
if (childLimit == 0)
childLimit = FInt::LuauTarjanChildLimit;
ty = log->follow(ty);
auto [index, fresh] = indexify(ty);
worklist.push_back({index, -1, -1});
return loop();
}
TarjanResult Tarjan::visitRoot(TypePackId tp)
{
childCount = 0;
if (childLimit == 0)
childLimit = FInt::LuauTarjanChildLimit;
tp = log->follow(tp);
auto [index, fresh] = indexify(tp);
worklist.push_back({index, -1, -1});
return loop();
}
void Tarjan::clearTarjan()
{
if (FFlag::LuauTarjanSingleArr)
{
typeToIndex.clear();
packToIndex.clear();
nodes.clear();
stack.clear();
}
else
{
dirty.clear();
typeToIndex.clear();
packToIndex.clear();
indexToType.clear();
indexToPack.clear();
stack.clear();
onStack.clear();
lowlink.clear();
}
edgesTy.clear();
edgesTp.clear();
worklist.clear();
}
bool Tarjan::getDirty(int index)
{
if (FFlag::LuauTarjanSingleArr)
{
LUAU_ASSERT(size_t(index) < nodes.size());
return nodes[index].dirty;
}
else
{
if (dirty.size() <= size_t(index))
dirty.resize(index + 1, false);
return dirty[index];
}
}
void Tarjan::setDirty(int index, bool d)
{
if (FFlag::LuauTarjanSingleArr)
{
LUAU_ASSERT(size_t(index) < nodes.size());
nodes[index].dirty = d;
}
else
{
if (dirty.size() <= size_t(index))
dirty.resize(index + 1, false);
dirty[index] = d;
}
}
void Tarjan::visitEdge(int index, int parentIndex)
{
if (getDirty(index))
setDirty(parentIndex, true);
}
void Tarjan::visitSCC(int index)
{
if (!FFlag::LuauTarjanSingleArr)
return visitSCC_DEPRECATED(index);
bool d = getDirty(index);
for (auto it = stack.rbegin(); !d && it != stack.rend(); it++)
{
TarjanNode& node = nodes[*it];
if (TypeId ty = node.ty)
d = isDirty(ty);
else if (TypePackId tp = node.tp)
d = isDirty(tp);
if (*it == index)
break;
}
if (!d)
return;
for (auto it = stack.rbegin(); it != stack.rend(); it++)
{
setDirty(*it, true);
TarjanNode& node = nodes[*it];
if (TypeId ty = node.ty)
foundDirty(ty);
else if (TypePackId tp = node.tp)
foundDirty(tp);
if (*it == index)
return;
}
}
TarjanResult Tarjan::findDirty(TypeId ty)
{
return visitRoot(ty);
}
TarjanResult Tarjan::findDirty(TypePackId tp)
{
return visitRoot(tp);
}
TarjanResult Tarjan::loop_DEPRECATED()
{
// Normally Tarjan is presented recursively, but this is a hot loop, so worth optimizing
while (!worklist.empty())
@ -492,71 +773,8 @@ TarjanResult Tarjan::loop()
return TarjanResult::Ok;
}
TarjanResult Tarjan::visitRoot(TypeId ty)
{
childCount = 0;
if (childLimit == 0)
childLimit = FInt::LuauTarjanChildLimit;
ty = log->follow(ty);
auto [index, fresh] = indexify(ty);
worklist.push_back({index, -1, -1});
return loop();
}
TarjanResult Tarjan::visitRoot(TypePackId tp)
{
childCount = 0;
if (childLimit == 0)
childLimit = FInt::LuauTarjanChildLimit;
tp = log->follow(tp);
auto [index, fresh] = indexify(tp);
worklist.push_back({index, -1, -1});
return loop();
}
void FindDirty::clearTarjan()
{
dirty.clear();
typeToIndex.clear();
packToIndex.clear();
indexToType.clear();
indexToPack.clear();
stack.clear();
onStack.clear();
lowlink.clear();
edgesTy.clear();
edgesTp.clear();
worklist.clear();
}
bool FindDirty::getDirty(int index)
{
if (dirty.size() <= size_t(index))
dirty.resize(index + 1, false);
return dirty[index];
}
void FindDirty::setDirty(int index, bool d)
{
if (dirty.size() <= size_t(index))
dirty.resize(index + 1, false);
dirty[index] = d;
}
void FindDirty::visitEdge(int index, int parentIndex)
{
if (getDirty(index))
setDirty(parentIndex, true);
}
void FindDirty::visitSCC(int index)
void Tarjan::visitSCC_DEPRECATED(int index)
{
bool d = getDirty(index);
@ -585,23 +803,12 @@ void FindDirty::visitSCC(int index)
}
}
TarjanResult FindDirty::findDirty(TypeId ty)
{
return visitRoot(ty);
}
TarjanResult FindDirty::findDirty(TypePackId tp)
{
return visitRoot(tp);
}
std::optional<TypeId> Substitution::substitute(TypeId ty)
{
ty = log->follow(ty);
// clear algorithm state for reentrancy
if (FFlag::LuauSubstitutionReentrant)
clearTarjan();
clearTarjan();
auto result = findDirty(ty);
if (result != TarjanResult::Ok)
@ -609,34 +816,18 @@ std::optional<TypeId> Substitution::substitute(TypeId ty)
for (auto [oldTy, newTy] : newTypes)
{
if (FFlag::LuauSubstitutionReentrant)
if (!ignoreChildren(oldTy) && !replacedTypes.contains(newTy))
{
if (!ignoreChildren(oldTy) && !replacedTypes.contains(newTy))
{
replaceChildren(newTy);
replacedTypes.insert(newTy);
}
}
else
{
if (!ignoreChildren(oldTy))
replaceChildren(newTy);
replaceChildren(newTy);
replacedTypes.insert(newTy);
}
}
for (auto [oldTp, newTp] : newPacks)
{
if (FFlag::LuauSubstitutionReentrant)
if (!ignoreChildren(oldTp) && !replacedTypePacks.contains(newTp))
{
if (!ignoreChildren(oldTp) && !replacedTypePacks.contains(newTp))
{
replaceChildren(newTp);
replacedTypePacks.insert(newTp);
}
}
else
{
if (!ignoreChildren(oldTp))
replaceChildren(newTp);
replaceChildren(newTp);
replacedTypePacks.insert(newTp);
}
}
TypeId newTy = replace(ty);
@ -648,8 +839,7 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
tp = log->follow(tp);
// clear algorithm state for reentrancy
if (FFlag::LuauSubstitutionReentrant)
clearTarjan();
clearTarjan();
auto result = findDirty(tp);
if (result != TarjanResult::Ok)
@ -657,34 +847,18 @@ std::optional<TypePackId> Substitution::substitute(TypePackId tp)
for (auto [oldTy, newTy] : newTypes)
{
if (FFlag::LuauSubstitutionReentrant)
if (!ignoreChildren(oldTy) && !replacedTypes.contains(newTy))
{
if (!ignoreChildren(oldTy) && !replacedTypes.contains(newTy))
{
replaceChildren(newTy);
replacedTypes.insert(newTy);
}
}
else
{
if (!ignoreChildren(oldTy))
replaceChildren(newTy);
replaceChildren(newTy);
replacedTypes.insert(newTy);
}
}
for (auto [oldTp, newTp] : newPacks)
{
if (FFlag::LuauSubstitutionReentrant)
if (!ignoreChildren(oldTp) && !replacedTypePacks.contains(newTp))
{
if (!ignoreChildren(oldTp) && !replacedTypePacks.contains(newTp))
{
replaceChildren(newTp);
replacedTypePacks.insert(newTp);
}
}
else
{
if (!ignoreChildren(oldTp))
replaceChildren(newTp);
replaceChildren(newTp);
replacedTypePacks.insert(newTp);
}
}
TypePackId newTp = replace(tp);
@ -714,8 +888,7 @@ TypePackId Substitution::clone(TypePackId tp)
{
VariadicTypePack clone;
clone.ty = vtp->ty;
if (FFlag::LuauSubstitutionFixMissingFields)
clone.hidden = vtp->hidden;
clone.hidden = vtp->hidden;
return addTypePack(std::move(clone));
}
else if (const TypeFamilyInstanceTypePack* tfitp = get<TypeFamilyInstanceTypePack>(tp))
@ -738,7 +911,7 @@ void Substitution::foundDirty(TypeId ty)
{
ty = log->follow(ty);
if (FFlag::LuauSubstitutionReentrant && newTypes.contains(ty))
if (newTypes.contains(ty))
return;
if (isDirty(ty))
@ -751,7 +924,7 @@ void Substitution::foundDirty(TypePackId tp)
{
tp = log->follow(tp);
if (FFlag::LuauSubstitutionReentrant && newPacks.contains(tp))
if (newPacks.contains(tp))
return;
if (isDirty(tp))
@ -792,13 +965,10 @@ void Substitution::replaceChildren(TypeId ty)
if (FunctionType* ftv = getMutable<FunctionType>(ty))
{
if (FFlag::LuauSubstitutionFixMissingFields)
{
for (TypeId& generic : ftv->generics)
generic = replace(generic);
for (TypePackId& genericPack : ftv->genericPacks)
genericPack = replace(genericPack);
}
for (TypeId& generic : ftv->generics)
generic = replace(generic);
for (TypePackId& genericPack : ftv->genericPacks)
genericPack = replace(genericPack);
ftv->argTypes = replace(ftv->argTypes);
ftv->retTypes = replace(ftv->retTypes);
@ -857,7 +1027,7 @@ void Substitution::replaceChildren(TypeId ty)
for (TypePackId& a : tfit->packArguments)
a = replace(a);
}
else if (ClassType* ctv = getMutable<ClassType>(ty); FFlag::LuauClassTypeVarsInSubstitution && ctv)
else if (ClassType* ctv = getMutable<ClassType>(ty))
{
for (auto& [name, prop] : ctv->props)
prop.setType(replace(prop.type()));

View File

@ -7,6 +7,7 @@
#include "Luau/Common.h"
#include "Luau/DcrLogger.h"
#include "Luau/Error.h"
#include "Luau/InsertionOrderedMap.h"
#include "Luau/Instantiation.h"
#include "Luau/Metamethods.h"
#include "Luau/Normalize.h"
@ -656,7 +657,7 @@ struct TypeChecker2
// if the initial and expected types from the iterator unified during constraint solving,
// we'll have a resolved type to use here, but we'll only use it if either the iterator is
// directly present in the for-in statement or if we have an iterator state constraining us
TypeId* resolvedTy = module->astOverloadResolvedTypes.find(firstValue);
TypeId* resolvedTy = module->astForInNextTypes.find(firstValue);
if (resolvedTy && (!retPack || valueTypes.size() > 1))
valueTypes[0] = *resolvedTy;
@ -1062,83 +1063,21 @@ struct TypeChecker2
// Note: this is intentionally separated from `visit(AstExprCall*)` for stack allocation purposes.
void visitCall(AstExprCall* call)
{
TypePackId expectedRetType = lookupExpectedPack(call, testArena);
TypePack args;
std::vector<Location> argLocs;
argLocs.reserve(call->args.size + 1);
auto maybeOriginalCallTy = module->astOriginalCallTypes.find(call);
if (!maybeOriginalCallTy)
TypeId* originalCallTy = module->astOriginalCallTypes.find(call);
TypeId* selectedOverloadTy = module->astOverloadResolvedTypes.find(call);
if (!originalCallTy && !selectedOverloadTy)
return;
TypeId originalCallTy = follow(*maybeOriginalCallTy);
std::vector<TypeId> overloads = flattenIntersection(originalCallTy);
if (get<AnyType>(originalCallTy) || get<ErrorType>(originalCallTy) || get<NeverType>(originalCallTy))
TypeId fnTy = follow(selectedOverloadTy ? *selectedOverloadTy : *originalCallTy);
if (get<AnyType>(fnTy) || get<ErrorType>(fnTy) || get<NeverType>(fnTy))
return;
else if (std::optional<TypeId> callMm = findMetatableEntry(builtinTypes, module->errors, originalCallTy, "__call", call->func->location))
else if (isOptional(fnTy))
{
if (get<FunctionType>(follow(*callMm)))
{
args.head.push_back(originalCallTy);
argLocs.push_back(call->func->location);
}
else
{
// TODO: This doesn't flag the __call metamethod as the problem
// very clearly.
reportError(CannotCallNonFunction{*callMm}, call->func->location);
return;
}
}
else if (get<FunctionType>(originalCallTy))
{
// ok.
}
else if (get<IntersectionType>(originalCallTy))
{
auto norm = normalizer.normalize(originalCallTy);
if (!norm)
return reportError(CodeTooComplex{}, call->location);
// NormalizedType::hasFunction returns true if its' tops component is `unknown`, but for soundness we want the reverse.
if (get<UnknownType>(norm->tops) || !norm->hasFunctions())
return reportError(CannotCallNonFunction{originalCallTy}, call->func->location);
}
else if (auto utv = get<UnionType>(originalCallTy))
{
// Sometimes it's okay to call a union of functions, but only if all of the functions are the same.
// Another scenario we might run into it is if the union has a nil member. In this case, we want to throw an error
if (isOptional(originalCallTy))
{
reportError(OptionalValueAccess{originalCallTy}, call->location);
return;
}
std::optional<TypeId> fst;
for (TypeId ty : utv)
{
if (!fst)
fst = follow(ty);
else if (fst != follow(ty))
{
reportError(CannotCallNonFunction{originalCallTy}, call->func->location);
return;
}
}
if (!fst)
ice->ice("UnionType had no elements, so fst is nullopt?");
originalCallTy = follow(*fst);
if (!get<FunctionType>(originalCallTy))
{
reportError(CannotCallNonFunction{originalCallTy}, call->func->location);
return;
}
}
else
{
reportError(CannotCallNonFunction{originalCallTy}, call->func->location);
reportError(OptionalValueAccess{fnTy}, call->func->location);
return;
}
@ -1161,9 +1100,12 @@ struct TypeChecker2
args.head.push_back(*argTy);
else if (i == call->args.size - 1)
{
TypePackId* argTail = module->astTypePacks.find(arg);
if (argTail)
args.tail = *argTail;
if (auto argTail = module->astTypePacks.find(arg))
{
auto [head, tail] = flatten(*argTail);
args.head.insert(args.head.end(), head.begin(), head.end());
args.tail = tail;
}
else
args.tail = builtinTypes->anyTypePack;
}
@ -1171,142 +1113,318 @@ struct TypeChecker2
args.head.push_back(builtinTypes->anyType);
}
TypePackId expectedArgTypes = testArena.addTypePack(args);