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:
parent
d458d240cd
commit
76bea81a7b
|
@ -83,7 +83,7 @@ struct IterableConstraint
|
|||
TypePackId variables;
|
||||
|
||||
const AstNode* nextAstFragment;
|
||||
DenseHashMap<const AstNode*, TypeId>* astOverloadResolvedTypes;
|
||||
DenseHashMap<const AstNode*, TypeId>* astForInNextTypes;
|
||||
};
|
||||
|
||||
// name(namedType) = name
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
LUAU_FASTFLAG(LuauClassTypeVarsInSubstitution)
|
||||
|
||||
namespace Luau
|
||||
{
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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);
|
||||