From 9c2146288d78e8beacefe692ae1149fec066440e Mon Sep 17 00:00:00 2001
From: vegorov-rbx <75688451+vegorov-rbx@users.noreply.github.com>
Date: Fri, 12 Apr 2024 10:18:49 -0700
Subject: [PATCH] Sync to upstream/release/621 (#1229)
# What's changed?
* Support for new 'require by string' RFC with relative paths and
aliases in now enabled in Luau REPL application
### New Type Solver
* Fixed assertion failure on generic table keys (`[expr] = value`)
* Fixed an issue with type substitution traversing into the substituted
parts during type instantiation
* Fixed crash in union simplification when that union contained
uninhabited unions and other types inside
* Union types in binary type families like `add` are expanded
into `add | add` to handle
* Added handling for type family solving creating new type families
* Fixed a bug with normalization operation caching types with unsolved
parts
* Tables with uninhabited properties are now simplified to `never`
* Fixed failures found by fuzzer
### Native Code Generation
* Added support for shared code generation between multiple Luau VM
instances
* Fixed issue in load-store propagation and new tagged LOAD_TVALUE
instructions
* Fixed issues with partial register dead store elimination causing
failures in GC assists
---
### Internal Contributors
Co-authored-by: Aaron Weiss
Co-authored-by: Alexander McCord
Co-authored-by: Andy Friesen
Co-authored-by: James McNellis
Co-authored-by: Vighnesh Vijay
Co-authored-by: Vyacheslav Egorov
---
Analysis/include/Luau/Clone.h | 2 -
Analysis/include/Luau/ConstraintGenerator.h | 8 +-
Analysis/include/Luau/ConstraintSolver.h | 5 +-
Analysis/include/Luau/Error.h | 22 +-
Analysis/include/Luau/Instantiation2.h | 15 +-
Analysis/include/Luau/Normalize.h | 10 +-
Analysis/include/Luau/Substitution.h | 25 +-
Analysis/include/Luau/Subtyping.h | 2 +-
Analysis/include/Luau/TableLiteralInference.h | 14 +-
Analysis/include/Luau/TypeFamily.h | 19 +-
.../include/Luau/TypeFamilyReductionGuesser.h | 2 +-
Analysis/include/Luau/TypeUtils.h | 2 +
Analysis/src/Clone.cpp | 576 +-----------------
Analysis/src/ConstraintGenerator.cpp | 148 ++---
Analysis/src/ConstraintSolver.cpp | 111 ++--
Analysis/src/DataFlowGraph.cpp | 4 +-
Analysis/src/Error.cpp | 40 ++
Analysis/src/Frontend.cpp | 3 -
Analysis/src/Instantiation2.cpp | 30 +-
Analysis/src/IostreamHelpers.cpp | 28 +
Analysis/src/Normalize.cpp | 237 +++++--
Analysis/src/Simplify.cpp | 10 +-
Analysis/src/Substitution.cpp | 44 +-
Analysis/src/Subtyping.cpp | 6 +-
Analysis/src/TableLiteralInference.cpp | 48 +-
Analysis/src/ToDot.cpp | 9 +-
Analysis/src/Type.cpp | 4 +
Analysis/src/TypeChecker2.cpp | 167 +++--
Analysis/src/TypeFamily.cpp | 197 ++++--
Analysis/src/TypeFamilyReductionGuesser.cpp | 6 +-
Analysis/src/TypeInfer.cpp | 36 +-
Analysis/src/TypeUtils.cpp | 22 +-
Analysis/src/Unifier.cpp | 222 ++++---
CLI/Repl.cpp | 148 ++---
CodeGen/include/Luau/CodeGen.h | 30 +
CodeGen/include/Luau/IrVisitUseDef.h | 6 +-
CodeGen/include/Luau/SharedCodeAllocator.h | 20 +-
CodeGen/src/AssemblyBuilderA64.cpp | 40 +-
CodeGen/src/CodeGen.cpp | 234 +++----
CodeGen/src/CodeGenContext.cpp | 159 ++++-
CodeGen/src/CodeGenContext.h | 43 +-
CodeGen/src/CodeGenLower.h | 4 +-
CodeGen/src/EmitBuiltinsX64.cpp | 12 +-
CodeGen/src/IrLoweringA64.cpp | 14 +-
CodeGen/src/IrTranslateBuiltins.cpp | 6 +-
CodeGen/src/IrValueLocationTracking.cpp | 2 +-
CodeGen/src/OptimizeConstProp.cpp | 12 +-
CodeGen/src/OptimizeDeadStore.cpp | 239 +++++++-
CodeGen/src/SharedCodeAllocator.cpp | 70 ++-
Common/include/Luau/ExperimentalFlags.h | 1 -
Config/include/Luau/LinterConfig.h | 1 -
Sources.cmake | 3 +-
VM/src/ldblib.cpp | 7 +-
VM/src/lgcdebug.cpp | 13 +-
tests/AssemblyBuilderA64.test.cpp | 4 -
tests/Conformance.test.cpp | 3 -
tests/DataFlowGraph.test.cpp | 20 +
tests/Error.test.cpp | 4 +-
tests/Instantiation2.test.cpp | 53 ++
tests/IrBuilder.test.cpp | 505 ++++++++++++++-
tests/IrLowering.test.cpp | 20 +-
tests/Module.test.cpp | 44 +-
tests/Normalize.test.cpp | 113 ++--
tests/RequireByString.test.cpp | 22 -
tests/SharedCodeAllocator.test.cpp | 116 ++++
tests/Simplify.test.cpp | 11 +
tests/TypeFamily.test.cpp | 58 +-
tests/TypeInfer.aliases.test.cpp | 53 ++
tests/TypeInfer.cfa.test.cpp | 55 +-
tests/TypeInfer.functions.test.cpp | 59 ++
tests/TypeInfer.oop.test.cpp | 4 -
tests/TypeInfer.provisional.test.cpp | 32 +-
tests/TypeInfer.singletons.test.cpp | 12 +
tests/TypeInfer.tables.test.cpp | 37 ++
tests/TypeInfer.test.cpp | 39 +-
tests/TypeInfer.tryUnify.test.cpp | 25 -
tests/TypeInfer.unionTypes.test.cpp | 5 -
tools/faillist.txt | 11 +-
78 files changed, 2775 insertions(+), 1668 deletions(-)
create mode 100644 tests/Instantiation2.test.cpp
diff --git a/Analysis/include/Luau/Clone.h b/Analysis/include/Luau/Clone.h
index 28ab99315..103b5bbd5 100644
--- a/Analysis/include/Luau/Clone.h
+++ b/Analysis/include/Luau/Clone.h
@@ -20,8 +20,6 @@ struct CloneState
SeenTypes seenTypes;
SeenTypePacks seenTypePacks;
-
- int recursionCount = 0;
};
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState);
diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h
index 48a1b77a8..4f217142e 100644
--- a/Analysis/include/Luau/ConstraintGenerator.h
+++ b/Analysis/include/Luau/ConstraintGenerator.h
@@ -95,10 +95,6 @@ struct ConstraintGenerator
// will enqueue them during solving.
std::vector unqueuedConstraints;
- // Type family instances created by the generator. This is used to ensure
- // that these instances are reduced fully by the solver.
- std::vector familyInstances;
-
// The private scope of type aliases for which the type parameters belong to.
DenseHashMap astTypeAliasDefiningScopes{nullptr};
@@ -264,8 +260,8 @@ struct ConstraintGenerator
std::optional assignedTy;
};
- LValueBounds checkLValue(const ScopePtr& scope, AstExpr* expr, bool transform);
- LValueBounds checkLValue(const ScopePtr& scope, AstExprLocal* local, bool transform);
+ LValueBounds checkLValue(const ScopePtr& scope, AstExpr* expr);
+ LValueBounds checkLValue(const ScopePtr& scope, AstExprLocal* local);
LValueBounds checkLValue(const ScopePtr& scope, AstExprGlobal* global);
LValueBounds checkLValue(const ScopePtr& scope, AstExprIndexName* indexName);
LValueBounds checkLValue(const ScopePtr& scope, AstExprIndexExpr* indexExpr);
diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h
index 3d00ec065..9ad885a7f 100644
--- a/Analysis/include/Luau/ConstraintSolver.h
+++ b/Analysis/include/Luau/ConstraintSolver.h
@@ -75,9 +75,6 @@ struct ConstraintSolver
// A constraint can be both blocked and unsolved, for instance.
std::vector> unsolvedConstraints;
- // This is a set of type families that need to be reduced after all constraints have been dispatched.
- DenseHashSet familyInstances{nullptr};
-
// A mapping of constraint pointer to how many things the constraint is
// blocked on. Can be empty or 0 for constraints that are not blocked on
// anything.
@@ -137,7 +134,7 @@ struct ConstraintSolver
bool tryDispatch(const HasPropConstraint& c, NotNull constraint);
bool tryDispatch(const SetPropConstraint& c, NotNull constraint);
- bool tryDispatchHasIndexer(int& recursionDepth, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType);
+ bool tryDispatchHasIndexer(int& recursionDepth, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set& seen);
bool tryDispatch(const HasIndexerConstraint& c, NotNull constraint);
/// (dispatched, found) where
diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h
index 4fbb40894..2ba284a15 100644
--- a/Analysis/include/Luau/Error.h
+++ b/Analysis/include/Luau/Error.h
@@ -409,6 +409,26 @@ struct CheckedFunctionIncorrectArgs
bool operator==(const CheckedFunctionIncorrectArgs& rhs) const;
};
+struct CannotAssignToNever
+{
+ // type of the rvalue being assigned
+ TypeId rhsType;
+
+ // Originating type.
+ std::vector cause;
+
+ enum class Reason
+ {
+ // when assigning to a property in a union of tables, the properties type
+ // is narrowed to the intersection of its type in each variant.
+ PropertyNarrowed,
+ };
+
+ Reason reason;
+
+ bool operator==(const CannotAssignToNever& rhs) const;
+};
+
struct UnexpectedTypeInSubtyping
{
TypeId ty;
@@ -427,7 +447,7 @@ using TypeErrorData =
Variant instantiate2(
+ TypeArena* arena, DenseHashMap genericSubstitutions, DenseHashMap genericPackSubstitutions, TypeId ty);
+std::optional instantiate2(TypeArena* arena, DenseHashMap genericSubstitutions,
+ DenseHashMap genericPackSubstitutions, TypePackId tp);
+
} // namespace Luau
diff --git a/Analysis/include/Luau/Normalize.h b/Analysis/include/Luau/Normalize.h
index 9b992a5e1..35e0c7a1d 100644
--- a/Analysis/include/Luau/Normalize.h
+++ b/Analysis/include/Luau/Normalize.h
@@ -283,6 +283,11 @@ struct NormalizedType
// The generic/free part of the type.
NormalizedTyvars tyvars;
+ // Free types, blocked types, and certain other types change shape as type
+ // inference is done. If we were to cache the normalization of these types,
+ // we'd be reusing bad, stale data.
+ bool isCacheable = true;
+
NormalizedType(NotNull builtinTypes);
NormalizedType() = delete;
@@ -330,7 +335,7 @@ struct NormalizedType
class Normalizer
{
- std::unordered_map> cachedNormals;
+ std::unordered_map> cachedNormals;
std::unordered_map cachedIntersections;
std::unordered_map cachedUnions;
std::unordered_map> cachedTypeIds;
@@ -355,7 +360,8 @@ class Normalizer
Normalizer& operator=(Normalizer&) = delete;
// If this returns null, the typechecker should emit a "too complex" error
- const NormalizedType* normalize(TypeId ty);
+ const NormalizedType* DEPRECATED_normalize(TypeId ty);
+ std::shared_ptr normalize(TypeId ty);
void clearNormal(NormalizedType& norm);
// ------- Cached TypeIds
diff --git a/Analysis/include/Luau/Substitution.h b/Analysis/include/Luau/Substitution.h
index 8e3bdcd5c..16e36e093 100644
--- a/Analysis/include/Luau/Substitution.h
+++ b/Analysis/include/Luau/Substitution.h
@@ -183,13 +183,21 @@ struct Tarjan
struct Substitution : Tarjan
{
protected:
- Substitution(const TxnLog* log_, TypeArena* arena)
- : arena(arena)
- {
- log = log_;
- LUAU_ASSERT(log);
- LUAU_ASSERT(arena);
- }
+ Substitution(const TxnLog* log_, TypeArena* arena);
+
+ /*
+ * By default, Substitution assumes that the types produced by clean() are
+ * freshly allocated types that are safe to mutate.
+ *
+ * If your clean() implementation produces a type that is not safe to
+ * mutate, you must call dontTraverseInto on this type (or type pack) to
+ * prevent Substitution from attempting to perform substitutions within the
+ * cleaned type.
+ *
+ * See the test weird_cyclic_instantiation for an example.
+ */
+ void dontTraverseInto(TypeId ty);
+ void dontTraverseInto(TypePackId tp);
public:
TypeArena* arena;
@@ -198,6 +206,9 @@ struct Substitution : Tarjan
DenseHashSet replacedTypes{nullptr};
DenseHashSet replacedTypePacks{nullptr};
+ DenseHashSet noTraverseTypes{nullptr};
+ DenseHashSet noTraverseTypePacks{nullptr};
+
std::optional substitute(TypeId ty);
std::optional substitute(TypePackId tp);
diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h
index 0a2c3c7f1..649b76b52 100644
--- a/Analysis/include/Luau/Subtyping.h
+++ b/Analysis/include/Luau/Subtyping.h
@@ -208,7 +208,7 @@ struct Subtyping
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const TableIndexer& subIndexer, const TableIndexer& superIndexer);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const Property& subProperty, const Property& superProperty, const std::string& name);
- SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm);
+ SubtypingResult isCovariantWith(SubtypingEnvironment& env, const std::shared_ptr& subNorm, const std::shared_ptr& superNorm);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const NormalizedClassType& superClass);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedClassType& subClass, const TypeIds& superTables);
SubtypingResult isCovariantWith(SubtypingEnvironment& env, const NormalizedStringType& subString, const NormalizedStringType& superString);
diff --git a/Analysis/include/Luau/TableLiteralInference.h b/Analysis/include/Luau/TableLiteralInference.h
index 1a6d51ea6..6f541a001 100644
--- a/Analysis/include/Luau/TableLiteralInference.h
+++ b/Analysis/include/Luau/TableLiteralInference.h
@@ -14,15 +14,7 @@ struct BuiltinTypes;
struct Unifier2;
class AstExpr;
-TypeId matchLiteralType(
- NotNull> astTypes,
- NotNull> astExpectedTypes,
- NotNull builtinTypes,
- NotNull arena,
- NotNull unifier,
- TypeId expectedType,
- TypeId exprType,
- const AstExpr* expr
-);
-
+TypeId matchLiteralType(NotNull> astTypes, NotNull> astExpectedTypes,
+ NotNull builtinTypes, NotNull arena, NotNull unifier, TypeId expectedType, TypeId exprType,
+ const AstExpr* expr, std::vector& toBlock);
}
diff --git a/Analysis/include/Luau/TypeFamily.h b/Analysis/include/Luau/TypeFamily.h
index 99f4f4460..eef26e784 100644
--- a/Analysis/include/Luau/TypeFamily.h
+++ b/Analysis/include/Luau/TypeFamily.h
@@ -19,6 +19,22 @@ struct TypeArena;
struct TxnLog;
class Normalizer;
+struct TypeFamilyQueue
+{
+ NotNull> queuedTys;
+ NotNull> queuedTps;
+
+ void add(TypeId instanceTy);
+ void add(TypePackId instanceTp);
+
+ template
+ void add(const std::vector& ts)
+ {
+ for (const T& t : ts)
+ enqueue(t);
+ }
+};
+
struct TypeFamilyContext
{
NotNull arena;
@@ -60,6 +76,7 @@ struct TypeFamilyContext
NotNull pushConstraint(ConstraintV&& c);
};
+
/// Represents a reduction result, which may have successfully reduced the type,
/// may have concretely failed to reduce the type, or may simply be stuck
/// without more information.
@@ -83,7 +100,7 @@ struct TypeFamilyReductionResult
template
using ReducerFunction =
- std::function(T, const std::vector&, const std::vector&, NotNull)>;
+ std::function(T, NotNull, const std::vector&, const std::vector&, NotNull)>;
/// Represents a type function that may be applied to map a series of types and
/// type packs to a single output type.
diff --git a/Analysis/include/Luau/TypeFamilyReductionGuesser.h b/Analysis/include/Luau/TypeFamilyReductionGuesser.h
index 9903e3813..29114f728 100644
--- a/Analysis/include/Luau/TypeFamilyReductionGuesser.h
+++ b/Analysis/include/Luau/TypeFamilyReductionGuesser.h
@@ -68,7 +68,7 @@ struct TypeFamilyReductionGuesser
bool operandIsAssignable(TypeId ty);
std::optional tryAssignOperandType(TypeId ty);
- const NormalizedType* normalize(TypeId ty);
+ std::shared_ptr normalize(TypeId ty);
void step();
void infer();
bool done();
diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h
index b4726b5c7..b02319ceb 100644
--- a/Analysis/include/Luau/TypeUtils.h
+++ b/Analysis/include/Luau/TypeUtils.h
@@ -62,6 +62,8 @@ std::optional findTablePropertyRespectingMeta(
std::optional findTablePropertyRespectingMeta(
NotNull builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, ValueContext context, Location location);
+bool occursCheck(TypeId needle, TypeId haystack);
+
// Returns the minimum and maximum number of types the argument list can accept.
std::pair> getParameterExtents(const TxnLog* log, TypePackId tp, bool includeHiddenVariadics = false);
diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp
index bf02f743b..a96e58668 100644
--- a/Analysis/src/Clone.cpp
+++ b/Analysis/src/Clone.cpp
@@ -2,16 +2,13 @@
#include "Luau/Clone.h"
#include "Luau/NotNull.h"
-#include "Luau/RecursionCounter.h"
-#include "Luau/TxnLog.h"
#include "Luau/Type.h"
#include "Luau/TypePack.h"
#include "Luau/Unifiable.h"
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
-LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
-LUAU_FASTFLAGVARIABLE(LuauStacklessTypeClone3, false)
+// For each `Luau::clone` call, we will clone only up to N amount of types _and_ packs, as controlled by this limit.
LUAU_FASTINTVARIABLE(LuauTypeCloneIterationLimit, 100'000)
namespace Luau
@@ -28,7 +25,7 @@ const T* get(const Kind& kind)
return get_if(&kind);
}
-class TypeCloner2
+class TypeCloner
{
NotNull arena;
NotNull builtinTypes;
@@ -44,7 +41,7 @@ class TypeCloner2
int steps = 0;
public:
- TypeCloner2(NotNull arena, NotNull builtinTypes, NotNull types, NotNull packs)
+ TypeCloner(NotNull arena, NotNull builtinTypes, NotNull types, NotNull packs)
: arena(arena)
, builtinTypes(builtinTypes)
, types(types)
@@ -204,15 +201,14 @@ class TypeCloner2
if (auto ty = p.writeTy)
cloneWriteTy = shallowClone(*ty);
- std::optional cloned = Property::create(cloneReadTy, cloneWriteTy);
- LUAU_ASSERT(cloned);
- cloned->deprecated = p.deprecated;
- cloned->deprecatedSuggestion = p.deprecatedSuggestion;
- cloned->location = p.location;
- cloned->tags = p.tags;
- cloned->documentationSymbol = p.documentationSymbol;
- cloned->typeLocation = p.typeLocation;
- return *cloned;
+ Property cloned = Property::create(cloneReadTy, cloneWriteTy);
+ cloned.deprecated = p.deprecated;
+ cloned.deprecatedSuggestion = p.deprecatedSuggestion;
+ cloned.location = p.location;
+ cloned.tags = p.tags;
+ cloned.documentationSymbol = p.documentationSymbol;
+ cloned.typeLocation = p.typeLocation;
+ return cloned;
}
else
{
@@ -453,469 +449,13 @@ class TypeCloner2
} // namespace
-namespace
-{
-
-Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState)
-{
- if (FFlag::DebugLuauDeferredConstraintResolution)
- {
- std::optional cloneReadTy;
- if (auto ty = prop.readTy)
- cloneReadTy = clone(*ty, dest, cloneState);
-
- std::optional cloneWriteTy;
- if (auto ty = prop.writeTy)
- cloneWriteTy = clone(*ty, dest, cloneState);
-
- std::optional cloned = Property::create(cloneReadTy, cloneWriteTy);
- LUAU_ASSERT(cloned);
- cloned->deprecated = prop.deprecated;
- cloned->deprecatedSuggestion = prop.deprecatedSuggestion;
- cloned->location = prop.location;
- cloned->tags = prop.tags;
- cloned->documentationSymbol = prop.documentationSymbol;
- cloned->typeLocation = prop.typeLocation;
- return *cloned;
- }
- else
- {
- return Property{
- clone(prop.type(), dest, cloneState),
- prop.deprecated,
- prop.deprecatedSuggestion,
- prop.location,
- prop.tags,
- prop.documentationSymbol,
- prop.typeLocation,
- };
- }
-}
-
-static TableIndexer clone(const TableIndexer& indexer, TypeArena& dest, CloneState& cloneState)
-{
- return TableIndexer{clone(indexer.indexType, dest, cloneState), clone(indexer.indexResultType, dest, cloneState)};
-}
-
-struct TypePackCloner;
-
-/*
- * Both TypeCloner and TypePackCloner work by depositing the requested type variable into the appropriate 'seen' set.
- * They do not return anything because their sole consumer (the deepClone function) already has a pointer into this storage.
- */
-
-struct TypeCloner
-{
- TypeCloner(TypeArena& dest, TypeId typeId, CloneState& cloneState)
- : dest(dest)
- , typeId(typeId)
- , seenTypes(cloneState.seenTypes)
- , seenTypePacks(cloneState.seenTypePacks)
- , cloneState(cloneState)
- {
- }
-
- TypeArena& dest;
- TypeId typeId;
- SeenTypes& seenTypes;
- SeenTypePacks& seenTypePacks;
- CloneState& cloneState;
-
- template
- void defaultClone(const T& t);
-
- void operator()(const FreeType& t);
- void operator()(const LocalType& t);
- void operator()(const GenericType& t);
- void operator()(const BoundType& t);
- void operator()(const ErrorType& t);
- void operator()(const BlockedType& t);
- void operator()(const PendingExpansionType& t);
- void operator()(const PrimitiveType& t);
- void operator()(const SingletonType& t);
- void operator()(const FunctionType& t);
- void operator()(const TableType& t);
- void operator()(const MetatableType& t);
- void operator()(const ClassType& t);
- void operator()(const AnyType& t);
- void operator()(const UnionType& t);
- void operator()(const IntersectionType& t);
- void operator()(const LazyType& t);
- void operator()(const UnknownType& t);
- void operator()(const NeverType& t);
- void operator()(const NegationType& t);
- void operator()(const TypeFamilyInstanceType& t);
-};
-
-struct TypePackCloner
-{
- TypeArena& dest;
- TypePackId typePackId;
- SeenTypes& seenTypes;
- SeenTypePacks& seenTypePacks;
- CloneState& cloneState;
-
- TypePackCloner(TypeArena& dest, TypePackId typePackId, CloneState& cloneState)
- : dest(dest)
- , typePackId(typePackId)
- , seenTypes(cloneState.seenTypes)
- , seenTypePacks(cloneState.seenTypePacks)
- , cloneState(cloneState)
- {
- }
-
- template
- void defaultClone(const T& t)
- {
- TypePackId cloned = dest.addTypePack(TypePackVar{t});
- seenTypePacks[typePackId] = cloned;
- }
-
- void operator()(const FreeTypePack& t)
- {
- defaultClone(t);
- }
-
- void operator()(const GenericTypePack& t)
- {
- defaultClone(t);
- }
-
- void operator()(const ErrorTypePack& t)
- {
- defaultClone(t);
- }
-
- void operator()(const BlockedTypePack& t)
- {
- defaultClone(t);
- }
-
- // While we are a-cloning, we can flatten out bound Types and make things a bit tighter.
- // We just need to be sure that we rewrite pointers both to the binder and the bindee to the same pointer.
- void operator()(const Unifiable::Bound& t)
- {
- TypePackId cloned = clone(t.boundTo, dest, cloneState);
- seenTypePacks[typePackId] = cloned;
- }
-
- void operator()(const VariadicTypePack& t)
- {
- TypePackId cloned = dest.addTypePack(TypePackVar{VariadicTypePack{clone(t.ty, dest, cloneState), /*hidden*/ t.hidden}});
- seenTypePacks[typePackId] = cloned;
- }
-
- void operator()(const TypePack& t)
- {
- TypePackId cloned = dest.addTypePack(TypePack{});
- TypePack* destTp = getMutable(cloned);
- LUAU_ASSERT(destTp != nullptr);
- seenTypePacks[typePackId] = cloned;
-
- for (TypeId ty : t.head)
- destTp->head.push_back(clone(ty, dest, cloneState));
-
- if (t.tail)
- destTp->tail = clone(*t.tail, dest, cloneState);
- }
-
- void operator()(const TypeFamilyInstanceTypePack& t)
- {
- TypePackId cloned = dest.addTypePack(TypeFamilyInstanceTypePack{t.family, {}, {}});
- TypeFamilyInstanceTypePack* destTp = getMutable(cloned);
- LUAU_ASSERT(destTp);
- seenTypePacks[typePackId] = cloned;
-
- destTp->typeArguments.reserve(t.typeArguments.size());
- for (TypeId ty : t.typeArguments)
- destTp->typeArguments.push_back(clone(ty, dest, cloneState));
-
- destTp->packArguments.reserve(t.packArguments.size());
- for (TypePackId tp : t.packArguments)
- destTp->packArguments.push_back(clone(tp, dest, cloneState));
- }
-};
-
-template
-void TypeCloner::defaultClone(const T& t)
-{
- TypeId cloned = dest.addType(t);
- seenTypes[typeId] = cloned;
-}
-
-void TypeCloner::operator()(const FreeType& t)
-{
- if (FFlag::DebugLuauDeferredConstraintResolution)
- {
- FreeType ft{nullptr, clone(t.lowerBound, dest, cloneState), clone(t.upperBound, dest, cloneState)};
- TypeId res = dest.addType(ft);
- seenTypes[typeId] = res;
- }
- else
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const LocalType& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const GenericType& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const Unifiable::Bound& t)
-{
- TypeId boundTo = clone(t.boundTo, dest, cloneState);
- seenTypes[typeId] = boundTo;
-}
-
-void TypeCloner::operator()(const Unifiable::Error& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const BlockedType& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const PendingExpansionType& t)
-{
- TypeId res = dest.addType(PendingExpansionType{t.prefix, t.name, t.typeArguments, t.packArguments});
- PendingExpansionType* petv = getMutable(res);
- LUAU_ASSERT(petv);
-
- seenTypes[typeId] = res;
-
- std::vector typeArguments;
- for (TypeId arg : t.typeArguments)
- typeArguments.push_back(clone(arg, dest, cloneState));
-
- std::vector packArguments;
- for (TypePackId arg : t.packArguments)
- packArguments.push_back(clone(arg, dest, cloneState));
-
- petv->typeArguments = std::move(typeArguments);
- petv->packArguments = std::move(packArguments);
-}
-
-void TypeCloner::operator()(const PrimitiveType& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const SingletonType& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const FunctionType& t)
-{
- // FISHY: We always erase the scope when we clone things. clone() was
- // originally written so that we could copy a module's type surface into an
- // export arena. This probably dates to that.
- TypeId result = dest.addType(FunctionType{TypeLevel{0, 0}, {}, {}, nullptr, nullptr, t.definition, t.hasSelf});
- FunctionType* ftv = getMutable(result);
- LUAU_ASSERT(ftv != nullptr);
-
- seenTypes[typeId] = result;
-
- for (TypeId generic : t.generics)
- ftv->generics.push_back(clone(generic, dest, cloneState));
-
- for (TypePackId genericPack : t.genericPacks)
- ftv->genericPacks.push_back(clone(genericPack, dest, cloneState));
-
- ftv->tags = t.tags;
- ftv->argTypes = clone(t.argTypes, dest, cloneState);
- ftv->argNames = t.argNames;
- ftv->retTypes = clone(t.retTypes, dest, cloneState);
- ftv->hasNoFreeOrGenericTypes = t.hasNoFreeOrGenericTypes;
- ftv->isCheckedFunction = t.isCheckedFunction;
-}
-
-void TypeCloner::operator()(const TableType& t)
-{
- // If table is now bound to another one, we ignore the content of the original
- if (t.boundTo)
- {
- TypeId boundTo = clone(*t.boundTo, dest, cloneState);
- seenTypes[typeId] = boundTo;
- return;
- }
-
- TypeId result = dest.addType(TableType{});
- TableType* ttv = getMutable(result);
- LUAU_ASSERT(ttv != nullptr);
-
- *ttv = t;
-
- seenTypes[typeId] = result;
-
- ttv->level = TypeLevel{0, 0};
-
- for (const auto& [name, prop] : t.props)
- ttv->props[name] = clone(prop, dest, cloneState);
-
- if (t.indexer)
- ttv->indexer = clone(*t.indexer, dest, cloneState);
-
- for (TypeId& arg : ttv->instantiatedTypeParams)
- arg = clone(arg, dest, cloneState);
-
- for (TypePackId& arg : ttv->instantiatedTypePackParams)
- arg = clone(arg, dest, cloneState);
-
- ttv->definitionModuleName = t.definitionModuleName;
- ttv->definitionLocation = t.definitionLocation;
- ttv->tags = t.tags;
-}
-
-void TypeCloner::operator()(const MetatableType& t)
-{
- TypeId result = dest.addType(MetatableType{});
- MetatableType* mtv = getMutable(result);
- seenTypes[typeId] = result;
-
- mtv->table = clone(t.table, dest, cloneState);
- mtv->metatable = clone(t.metatable, dest, cloneState);
-}
-
-void TypeCloner::operator()(const ClassType& t)
-{
- TypeId result = dest.addType(ClassType{t.name, {}, std::nullopt, std::nullopt, t.tags, t.userData, t.definitionModuleName});
- ClassType* ctv = getMutable(result);
-
- seenTypes[typeId] = result;
-
- for (const auto& [name, prop] : t.props)
- ctv->props[name] = clone(prop, dest, cloneState);
-
- if (t.parent)
- ctv->parent = clone(*t.parent, dest, cloneState);
-
- if (t.metatable)
- ctv->metatable = clone(*t.metatable, dest, cloneState);
-
- if (t.indexer)
- ctv->indexer = clone(*t.indexer, dest, cloneState);
-}
-
-void TypeCloner::operator()(const AnyType& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const UnionType& t)
-{
- // We're just using this FreeType as a placeholder until we've finished
- // cloning the parts of this union so it is okay that its bounds are
- // nullptr. We'll never indirect them.
- TypeId result = dest.addType(FreeType{nullptr, /*lowerBound*/ nullptr, /*upperBound*/ nullptr});
- seenTypes[typeId] = result;
-
- std::vector options;
- options.reserve(t.options.size());
-
- for (TypeId ty : t.options)
- options.push_back(clone(ty, dest, cloneState));
-
- asMutable(result)->ty.emplace(std::move(options));
-}
-
-void TypeCloner::operator()(const IntersectionType& t)
-{
- TypeId result = dest.addType(IntersectionType{});
- seenTypes[typeId] = result;
-
- IntersectionType* option = getMutable(result);
- LUAU_ASSERT(option != nullptr);
-
- for (TypeId ty : t.parts)
- option->parts.push_back(clone(ty, dest, cloneState));
-}
-
-void TypeCloner::operator()(const LazyType& t)
-{
- if (TypeId unwrapped = t.unwrapped.load())
- {
- seenTypes[typeId] = clone(unwrapped, dest, cloneState);
- }
- else
- {
- defaultClone(t);
- }
-}
-
-void TypeCloner::operator()(const UnknownType& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const NeverType& t)
-{
- defaultClone(t);
-}
-
-void TypeCloner::operator()(const NegationType& t)
-{
- TypeId result = dest.addType(AnyType{});
- seenTypes[typeId] = result;
-
- TypeId ty = clone(t.ty, dest, cloneState);
- asMutable(result)->ty = NegationType{ty};
-}
-
-void TypeCloner::operator()(const TypeFamilyInstanceType& t)
-{
- TypeId result = dest.addType(TypeFamilyInstanceType{
- t.family,
- {},
- {},
- });
-
- seenTypes[typeId] = result;
-
- TypeFamilyInstanceType* tfit = getMutable(result);
- LUAU_ASSERT(tfit != nullptr);
-
- tfit->typeArguments.reserve(t.typeArguments.size());
- for (TypeId p : t.typeArguments)
- tfit->typeArguments.push_back(clone(p, dest, cloneState));
-
- tfit->packArguments.reserve(t.packArguments.size());
- for (TypePackId p : t.packArguments)
- tfit->packArguments.push_back(clone(p, dest, cloneState));
-}
-
-} // anonymous namespace
-
TypePackId clone(TypePackId tp, TypeArena& dest, CloneState& cloneState)
{
if (tp->persistent)
return tp;
- if (FFlag::LuauStacklessTypeClone3)
- {
- TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
- return cloner.clone(tp);
- }
- else
- {
- RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
-
- TypePackId& res = cloneState.seenTypePacks[tp];
-
- if (res == nullptr)
- {
- TypePackCloner cloner{dest, tp, cloneState};
- Luau::visit(cloner, tp->ty); // Mutates the storage that 'res' points into.
- }
-
- return res;
- }
+ TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
+ return cloner.clone(tp);
}
TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
@@ -923,91 +463,35 @@ TypeId clone(TypeId typeId, TypeArena& dest, CloneState& cloneState)
if (typeId->persistent)
return typeId;
- if (FFlag::LuauStacklessTypeClone3)
- {
- TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
- return cloner.clone(typeId);
- }
- else
- {
- RecursionLimiter _ra(&cloneState.recursionCount, FInt::LuauTypeCloneRecursionLimit);
-
- TypeId& res = cloneState.seenTypes[typeId];
-
- if (res == nullptr)
- {
- TypeCloner cloner{dest, typeId, cloneState};
- Luau::visit(cloner, typeId->ty); // Mutates the storage that 'res' points into.
-
- // Persistent types are not being cloned and we get the original type back which might be read-only
- if (!res->persistent)
- {
- asMutable(res)->documentationSymbol = typeId->documentationSymbol;
- }
- }
-
- return res;
- }
+ TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
+ return cloner.clone(typeId);
}
TypeFun clone(const TypeFun& typeFun, TypeArena& dest, CloneState& cloneState)
{
- if (FFlag::LuauStacklessTypeClone3)
- {
- TypeCloner2 cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
-
- TypeFun copy = typeFun;
-
- for (auto& param : copy.typeParams)
- {
- param.ty = cloner.clone(param.ty);
-
- if (param.defaultValue)
- param.defaultValue = cloner.clone(*param.defaultValue);
- }
-
- for (auto& param : copy.typePackParams)
- {
- param.tp = cloner.clone(param.tp);
-
- if (param.defaultValue)
- param.defaultValue = cloner.clone(*param.defaultValue);
- }
+ TypeCloner cloner{NotNull{&dest}, cloneState.builtinTypes, NotNull{&cloneState.seenTypes}, NotNull{&cloneState.seenTypePacks}};
- copy.type = cloner.clone(copy.type);
+ TypeFun copy = typeFun;
- return copy;
- }
- else
+ for (auto& param : copy.typeParams)
{
- TypeFun result;
-
- for (auto param : typeFun.typeParams)
- {
- TypeId ty = clone(param.ty, dest, cloneState);
- std::optional defaultValue;
-
- if (param.defaultValue)
- defaultValue = clone(*param.defaultValue, dest, cloneState);
-
- result.typeParams.push_back({ty, defaultValue});
- }
+ param.ty = cloner.clone(param.ty);
- for (auto param : typeFun.typePackParams)
- {
- TypePackId tp = clone(param.tp, dest, cloneState);
- std::optional defaultValue;
+ if (param.defaultValue)
+ param.defaultValue = cloner.clone(*param.defaultValue);
+ }
- if (param.defaultValue)
- defaultValue = clone(*param.defaultValue, dest, cloneState);
+ for (auto& param : copy.typePackParams)
+ {
+ param.tp = cloner.clone(param.tp);
- result.typePackParams.push_back({tp, defaultValue});
- }
+ if (param.defaultValue)
+ param.defaultValue = cloner.clone(*param.defaultValue);
+ }
- result.type = clone(typeFun.type, dest, cloneState);
+ copy.type = cloner.clone(copy.type);
- return result;
- }
+ return copy;
}
} // namespace Luau
diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp
index 4a4c8c3ce..91be2a3a1 100644
--- a/Analysis/src/ConstraintGenerator.cpp
+++ b/Analysis/src/ConstraintGenerator.cpp
@@ -8,15 +8,16 @@
#include "Luau/ControlFlow.h"
#include "Luau/DcrLogger.h"
#include "Luau/DenseHash.h"
-#include "Luau/InsertionOrderedMap.h"
#include "Luau/ModuleResolver.h"
#include "Luau/RecursionCounter.h"
#include "Luau/Refinement.h"
#include "Luau/Scope.h"
#include "Luau/Simplify.h"
+#include "Luau/StringUtils.h"
#include "Luau/TableLiteralInference.h"
#include "Luau/Type.h"
#include "Luau/TypeFamily.h"
+#include "Luau/TypePack.h"
#include "Luau/TypeUtils.h"
#include "Luau/Unifier2.h"
#include "Luau/VisitType.h"
@@ -27,7 +28,6 @@
LUAU_FASTINT(LuauCheckRecursionLimit);
LUAU_FASTFLAG(DebugLuauLogSolverToJson);
LUAU_FASTFLAG(DebugLuauMagicTypes);
-LUAU_FASTFLAG(LuauLoopControlFlowAnalysis);
namespace Luau
{
@@ -641,9 +641,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStat* stat)
else if (auto s = stat->as())
return visit(scope, s);
else if (stat->is())
- return FFlag::LuauLoopControlFlowAnalysis ? ControlFlow::Breaks : ControlFlow::None;
+ return ControlFlow::Breaks;
else if (stat->is())
- return FFlag::LuauLoopControlFlowAnalysis ? ControlFlow::Continues : ControlFlow::None;
+ return ControlFlow::Continues;
else if (auto r = stat->as())
return visit(scope, r);
else if (auto e = stat->as())
@@ -989,9 +989,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
else
scope->bindings[localName->local] = Binding{generalizedType, localName->location};
- sig.bodyScope->bindings[localName->local] = Binding{sig.signature, localName->location};
- sig.bodyScope->lvalueTypes[def] = sig.signature;
- sig.bodyScope->rvalueRefinements[def] = sig.signature;
+ scope->bindings[localName->local] = Binding{sig.signature, localName->location};
+ scope->lvalueTypes[def] = sig.signature;
+ scope->rvalueRefinements[def] = sig.signature;
}
else if (AstExprGlobal* globalName = function->name->as())
{
@@ -1001,9 +1001,9 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatFunction* f
if (!sigFullyDefined)
generalizedType = *existingFunctionTy;
- sig.bodyScope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
- sig.bodyScope->lvalueTypes[def] = sig.signature;
- sig.bodyScope->rvalueRefinements[def] = sig.signature;
+ scope->bindings[globalName->name] = Binding{sig.signature, globalName->location};
+ scope->lvalueTypes[def] = sig.signature;
+ scope->rvalueRefinements[def] = sig.signature;
}
else if (AstExprIndexName* indexName = function->name->as())
{
@@ -1121,53 +1121,11 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatAssign* ass
Checkpoint lvalueBeginCheckpoint = checkpoint(this);
- size_t i = 0;
for (AstExpr* lvalue : assign->vars)
{
- // This is a really weird thing to do, but it's critically important for some kinds of
- // assignments with the current type state behavior. Consider this code:
- // local function f(l, r)
- // local i = l
- // for _ = l, r do
- // i = i + 1
- // end
- // end
- //
- // With type states now, we will not create a new state for `i` within the loop. This means
- // that, in the absence of the analysis below, we would infer a too-broad bound for i: the
- // cyclic type t1 where t1 = add. In order to stop this, we say that
- // assignments to a definition with a self-referential binary expression do not transform
- // the type of the definition. This will only apply for loops, where the definition is
- // shared in more places; for non-loops, there will be a separate DefId for the lvalue in
- // the assignment, so we will deem the expression to be transformative.
- //
- // Deeming the addition in the code sample above as non-transformative means that i is known
- // to be exactly number further on, ensuring the type family reduces down to number, as is
- // expected for this code snippet.
- //
- // There is a potential for spurious errors here if the expression is more complex than a
- // simple binary expression, e.g. i = (i + 1) * 2. At the time of writing, this case hasn't
- // materialized.
- bool transform = true;
-
- if (assign->values.size > i)
- {
- AstExpr* value = assign->values.data[i];
- if (auto bexp = value->as())
- {
- DefId lvalueDef = dfg->getDef(lvalue);
- DefId lDef = dfg->getDef(bexp->left);
- DefId rDef = dfg->getDef(bexp->right);
-
- if (lvalueDef == lDef || lvalueDef == rDef)
- transform = false;
- }
- }
-
- auto [upperBound, typeState] = checkLValue(scope, lvalue, transform);
+ auto [upperBound, typeState] = checkLValue(scope, lvalue);
upperBounds.push_back(upperBound.value_or(builtinTypes->unknownType));
typeStates.push_back(typeState.value_or(builtinTypes->unknownType));
- ++i;
}
Checkpoint lvalueEndCheckpoint = checkpoint(this);
@@ -1196,7 +1154,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatCompoundAss
AstExprBinary binop = AstExprBinary{assign->location, assign->op, assign->var, assign->value};
TypeId resultTy = check(scope, &binop).ty;
- auto [upperBound, typeState] = checkLValue(scope, assign->var, true);
+ auto [upperBound, typeState] = checkLValue(scope, assign->var);
Constraint* sc = nullptr;
if (upperBound)
@@ -1246,7 +1204,7 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifState
if (elsecf == ControlFlow::None)
scope->inheritAssignments(elseScope);
- if (FFlag::LuauLoopControlFlowAnalysis && thencf == elsecf)
+ if (thencf == elsecf)
return thencf;
else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
return ControlFlow::Returns;
@@ -1254,25 +1212,6 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatIf* ifState
return ControlFlow::None;
}
-static bool occursCheck(TypeId needle, TypeId haystack)
-{
- LUAU_ASSERT(get(needle));
- haystack = follow(haystack);
-
- auto checkHaystack = [needle](TypeId haystack) {
- return occursCheck(needle, haystack);
- };
-
- if (needle == haystack)
- return true;
- else if (auto ut = get(haystack))
- return std::any_of(begin(ut), end(ut), checkHaystack);
- else if (auto it = get(haystack))
- return std::any_of(begin(it), end(it), checkHaystack);
-
- return false;
-}
-
ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias* alias)
{
if (alias->name == kParseNameError)
@@ -1298,11 +1237,10 @@ ControlFlow ConstraintGenerator::visit(const ScopePtr& scope, AstStatTypeAlias*
if (bindingIt == typeBindings->end() || defnScope == nullptr)
return ControlFlow::None;
- TypeId ty = resolveType(*defnScope, alias->type, /* inTypeArguments */ false);
+ TypeId ty = resolveType(*defnScope, alias->type, /* inTypeArguments */ false, /* replaceErrorWithFresh */ false);
TypeId aliasTy = bindingIt->second.type;
LUAU_ASSERT(get(aliasTy));
-
if (occursCheck(aliasTy, ty))
{
asMutable(aliasTy)->ty.emplace(builtinTypes->anyType);
@@ -2377,10 +2315,10 @@ std::tuple ConstraintGenerator::checkBinary(
}
}
-ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr, bool transform)
+ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExpr* expr)
{
if (auto local = expr->as())
- return checkLValue(scope, local, transform);
+ return checkLValue(scope, local);
else if (auto global = expr->as())
return checkLValue(scope, global);
else if (auto indexName = expr->as())
@@ -2396,7 +2334,7 @@ ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePt
ice->ice("checkLValue is inexhaustive");
}
-ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local, bool transform)
+ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePtr& scope, AstExprLocal* local)
{
std::optional annotatedTy = scope->lookup(local->local);
LUAU_ASSERT(annotatedTy);
@@ -2406,16 +2344,13 @@ ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePt
if (ty)
{
- if (transform)
+ if (auto lt = getMutable(*ty))
+ ++lt->blockCount;
+ else if (auto ut = getMutable(*ty))
{
- if (auto lt = getMutable(*ty))
- ++lt->blockCount;
- else if (auto ut = getMutable(*ty))
- {
- for (TypeId optTy : ut->options)
- if (auto lt = getMutable(optTy))
- ++lt->blockCount;
- }
+ for (TypeId optTy : ut->options)
+ if (auto lt = getMutable(optTy))
+ ++lt->blockCount;
}
}
else
@@ -2441,27 +2376,22 @@ ConstraintGenerator::LValueBounds ConstraintGenerator::checkLValue(const ScopePt
}
// TODO: Need to clip this, but this requires more code to be reworked first before we can clip this.
- std::optional assignedTy;
+ std::optional assignedTy = arena->addType(BlockedType{});
- if (transform)
- {
- assignedTy = arena->addType(BlockedType{});
-
- auto unpackC = addConstraint(scope, local->location,
- UnpackConstraint{arena->addTypePack({*ty}), arena->addTypePack({*assignedTy}),
- /*resultIsLValue*/ true});
+ auto unpackC = addConstraint(scope, local->location,
+ UnpackConstraint{arena->addTypePack({*ty}), arena->addTypePack({*assignedTy}),
+ /*resultIsLValue*/ true});
- if (auto blocked = get(*ty))
- {
- if (blocked->getOwner())
- unpackC->dependencies.push_back(NotNull{blocked->getOwner()});
- else if (auto blocked = getMutable(*ty))
- blocked->setOwner(unpackC);
- }
-
- recordInferredBinding(local->local, *ty);
+ if (auto blocked = get(*ty))
+ {
+ if (blocked->getOwner())
+ unpackC->dependencies.push_back(NotNull{blocked->getOwner()});
+ else if (auto blocked = getMutable(*ty))
+ blocked->setOwner(unpackC);
}
+ recordInferredBinding(local->local, *ty);
+
return {annotatedTy, assignedTy};
}
@@ -2518,7 +2448,8 @@ ConstraintGenerator::LValueBounds ConstraintGenerator::updateProperty(const Scop
TypeId subjectType = check(scope, indexExpr->expr).ty;
TypeId indexType = check(scope, indexExpr->index).ty;
TypeId assignedTy = arena->addType(BlockedType{});
- addConstraint(scope, expr->location, SetIndexerConstraint{subjectType, indexType, assignedTy});
+ auto sic = addConstraint(scope, expr->location, SetIndexerConstraint{subjectType, indexType, assignedTy});
+ getMutable(assignedTy)->setOwner(sic);
module->astTypes[expr] = assignedTy;
@@ -2696,7 +2627,9 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
if (expectedType)
{
Unifier2 unifier{arena, builtinTypes, NotNull{scope.get()}, ice};
- matchLiteralType(NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}, builtinTypes, arena, NotNull{&unifier}, *expectedType, ty, expr);
+ std::vector toBlock;
+ matchLiteralType(
+ NotNull{&module->astTypes}, NotNull{&module->astExpectedTypes}, builtinTypes, arena, NotNull{&unifier}, *expectedType, ty, expr, toBlock);
}
return Inference{ty};
@@ -3472,7 +3405,6 @@ TypeId ConstraintGenerator::createFamilyInstance(TypeFamilyInstanceType instance
{
TypeId result = arena->addType(std::move(instance));
addConstraint(scope, location, ReduceConstraint{result});
- familyInstances.push_back(result);
return result;
}
diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp
index 7d337c19b..948722c6c 100644
--- a/Analysis/src/ConstraintSolver.cpp
+++ b/Analysis/src/ConstraintSolver.cpp
@@ -276,7 +276,6 @@ struct InstantiationQueuer : TypeOnceVisitor
bool visit(TypeId ty, const TypeFamilyInstanceType&) override
{
solver->pushConstraint(scope, location, ReduceConstraint{ty});
- solver->familyInstances.insert(ty);
return true;
}
@@ -455,16 +454,6 @@ void ConstraintSolver::run()
progress |= runSolverPass(true);
} while (progress);
- for (TypeId instance : familyInstances)
- {
- if (FFlag::DebugLuauLogSolver)
- printf("Post-solve family reduction of %s\n", toString(instance).c_str());
-
- TypeCheckLimits limits{};
- FamilyGraphReductionResult result =
- reduceFamilies(instance, Location{}, TypeFamilyContext{arena, builtinTypes, rootScope, normalizer, NotNull{&iceReporter}, NotNull{&limits}}, false);
- }
-
if (FFlag::DebugLuauLogSolver || FFlag::DebugLuauLogBindings)
dumpBindings(rootScope, opts);
@@ -843,6 +832,18 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul
return true;
}
+ // Due to how pending expansion types and TypeFun's are created
+ // If this check passes, we have created a cyclic / corecursive type alias
+ // of size 0
+ TypeId lhs = c.target;
+ TypeId rhs = tf->type;
+ if (occursCheck(lhs, rhs))
+ {
+ reportError(OccursCheckFailed{}, constraint->location);
+ bindResult(errorRecoveryType());
+ return true;
+ }
+
auto [typeArguments, packArguments] = saturateArguments(arena, builtinTypes, *tf, petv->typeArguments, petv->packArguments);
bool sameTypes = std::equal(typeArguments.begin(), typeArguments.end(), tf->typeParams.begin(), tf->typeParams.end(), [](auto&& itp, auto&& p) {
@@ -1106,8 +1107,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull subst = instantiation.substitute(result);
+ std::optional subst = instantiate2(arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions), result);
if (!subst)
{
reportError(CodeTooComplex{}, constraint->location);
@@ -1183,6 +1183,14 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull res = replacer.substitute(fn);
if (res)
{
+ if (*res != fn)
+ {
+ FunctionType* ftvMut = getMutable(*res);
+ LUAU_ASSERT(ftvMut);
+ ftvMut->generics.clear();
+ ftvMut->genericPacks.clear();
+ }
+
fn = *res;
ftv = get(*res);
LUAU_ASSERT(ftv);
@@ -1233,7 +1241,12 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNullis())
{
Unifier2 u2{arena, builtinTypes, constraint->scope, NotNull{&iceReporter}};
- (void) matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, expectedArgTy, actualArgTy, expr);
+ std::vector toBlock;
+ (void)matchLiteralType(c.astTypes, c.astExpectedTypes, builtinTypes, arena, NotNull{&u2}, expectedArgTy, actualArgTy, expr, toBlock);
+ for (auto t : toBlock)
+ block(t, constraint);
+ if (!toBlock.empty())
+ return false;
}
}
@@ -1449,13 +1462,17 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType)
+bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull constraint, TypeId subjectType, TypeId indexType, TypeId resultType, Set& seen)
{
RecursionLimiter _rl{&recursionDepth, FInt::LuauSolverRecursionLimit};
subjectType = follow(subjectType);
indexType = follow(indexType);
+ if (seen.contains(subjectType))
+ return false;
+ seen.insert(subjectType);
+
LUAU_ASSERT(get(resultType));
LUAU_ASSERT(canMutate(resultType, constraint));
@@ -1496,7 +1513,7 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNull(subjectType))
- return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType);
+ return tryDispatchHasIndexer(recursionDepth, constraint, mt->table, indexType, resultType, seen);
else if (auto ct = get(subjectType))
{
if (auto indexer = ct->indexer)
@@ -1531,10 +1548,10 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNulladdType(BlockedType{});
getMutable(r)->setOwner(const_cast(constraint.get()));
- bool ok = tryDispatchHasIndexer(recursionDepth, constraint, part, indexType, r);
- // FIXME: It's too late to stop and block now I think? We should
- // scan for blocked types before we actually do anything.
- LUAU_ASSERT(ok);
+ bool ok = tryDispatchHasIndexer(recursionDepth, constraint, part, indexType, r, seen);
+ // If we've cut a recursive loop short, skip it.
+ if (!ok)
+ continue;
r = follow(r);
if (!get(r))
@@ -1563,9 +1580,10 @@ bool ConstraintSolver::tryDispatchHasIndexer(int& recursionDepth, NotNulladdType(BlockedType{});
getMutable(r)->setOwner(const_cast(constraint.get()));
- bool ok = tryDispatchHasIndexer(recursionDepth, constraint, part, indexType, r);
- // We should have found all the blocked types ahead of time (see BlockedTypeFinder below)
- LUAU_ASSERT(ok);
+ bool ok = tryDispatchHasIndexer(recursionDepth, constraint, part, indexType, r, seen);
+ // If we've cut a recursive loop short, skip it.
+ if (!ok)
+ continue;
r = follow(r);
if (!get(r))
@@ -1628,7 +1646,9 @@ bool ConstraintSolver::tryDispatch(const HasIndexerConstraint& c, NotNull seen{nullptr};
+
+ return tryDispatchHasIndexer(recursionDepth, constraint, subjectType, indexType, c.resultType, seen);
}
std::pair ConstraintSolver::tryDispatchSetIndexer(NotNull constraint, TypeId subjectType, TypeId indexType, TypeId propType, bool expandFreeTypeBounds)
@@ -1641,24 +1661,16 @@ std::pair ConstraintSolver::tryDispatchSetIndexer(NotNullindexer)
{
unify(constraint, indexType, tt->indexer->indexType);
-
- // We have a `BoundType` check here because we must mutate only our owning `BlockedType`, not some other constraint's `BlockedType`.
- // TODO: We should rather have a `bool mutateProp` parameter that is set to false if we're traversing a union or intersection type.
- // The union or intersection type themselves should be the one to mutate the `propType`, not each or first `TableType` in a union/intersection type.
- //
- // Fixing this requires fixing other ones first.
- if (!get(propType) && get(propType))
- emplaceType(asMutable(propType), tt->indexer->indexResultType);
+ bindBlockedType(propType, tt->indexer->indexResultType, subjectType, constraint);
return {true, true};
}
else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
{
+ bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint);
tt->indexer = TableIndexer{indexType, propType};
return {true, true};
}
- else
- return {true, false};
}
else if (auto ft = getMutable(subjectType); ft && expandFreeTypeBounds)
{
@@ -1669,6 +1681,10 @@ std::pair ConstraintSolver::tryDispatchSetIndexer(NotNullupperBound, indexType, propType, /*expandFreeTypeBounds=*/ false);
if (dispatched && !found)
{
+ // Despite that we haven't found a table type, adding a table type causes us to have one that we can /now/ find.
+ found = true;
+ bindBlockedType(propType, freshType(arena, builtinTypes, constraint->scope.get()), subjectType, constraint);
+
TypeId tableTy = arena->addType(TableType{TableState::Sealed, TypeLevel{}, constraint->scope.get()});
TableType* tt2 = getMutable(tableTy);
tt2->indexer = TableIndexer{indexType, propType};
@@ -1690,6 +1706,11 @@ std::pair ConstraintSolver::tryDispatchSetIndexer(NotNull(subjectType) && expandFreeTypeBounds)
+ {
+ bindBlockedType(propType, subjectType, subjectType, constraint);
+ return {true, true};
+ }
return {true, false};
}
@@ -1701,8 +1722,14 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNullerrorRecoveryType(), subjectType, constraint);
+
unblock(c.propType, constraint->location);
+ }
+
return dispatched;
}
@@ -1833,10 +1860,10 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNullblockCount)
asMutable(resultTy)->ty.emplace(lt->domain);
}
- else if (get(*resultIter) || get(*resultIter))
+ else if (get(resultTy) || get(resultTy))
{
- asMutable(*resultIter)->ty.emplace(builtinTypes->nilType);
- unblock(*resultIter, constraint->location);
+ asMutable(resultTy)->ty.emplace(builtinTypes->nilType);
+ unblock(resultTy, constraint->location);
}
++resultIter;
@@ -2321,8 +2348,16 @@ std::pair, std::optional> ConstraintSolver::lookupTa
{
TypeId one = *begin(options);
TypeId two = *(++begin(options));
+
+ // if we're in an lvalue context, we need the _common_ type here.
+ if (context == ValueContext::LValue)
+ return {{}, simplifyIntersection(builtinTypes, arena, one, two).result};
+
return {{}, simplifyUnion(builtinTypes, arena, one, two).result};
}
+ // if we're in an lvalue context, we need the _common_ type here.
+ else if (context == ValueContext::LValue)
+ return {{}, arena->addType(IntersectionType{std::vector(begin(options), end(options))})};
else
return {{}, arena->addType(UnionType{std::vector(begin(options), end(options))})};
}
diff --git a/Analysis/src/DataFlowGraph.cpp b/Analysis/src/DataFlowGraph.cpp
index b67614c49..33b416986 100644
--- a/Analysis/src/DataFlowGraph.cpp
+++ b/Analysis/src/DataFlowGraph.cpp
@@ -6,12 +6,10 @@
#include "Luau/Common.h"
#include "Luau/Error.h"
-#include
#include
LUAU_FASTFLAG(DebugLuauFreezeArena)
LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
-LUAU_FASTFLAG(LuauLoopControlFlowAnalysis)
namespace Luau
{
@@ -403,7 +401,7 @@ ControlFlow DataFlowGraphBuilder::visit(DfgScope* scope, AstStatIf* i)
else if ((thencf | elsecf) == ControlFlow::None)
join(scope, thenScope, elseScope);
- if (FFlag::LuauLoopControlFlowAnalysis && thencf == elsecf)
+ if (thencf == elsecf)
return thencf;
else if (matches(thencf, ControlFlow::Returns | ControlFlow::Throws) && matches(elsecf, ControlFlow::Returns | ControlFlow::Throws))
return ControlFlow::Returns;
diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp
index 68e732e62..dcd591b40 100644
--- a/Analysis/src/Error.cpp
+++ b/Analysis/src/Error.cpp
@@ -591,6 +591,25 @@ struct ErrorConverter
{
return "Encountered an unexpected type pack in subtyping: " + toString(e.tp);
}
+
+ std::string operator()(const CannotAssignToNever& e) const
+ {
+ std::string result = "Cannot assign a value of type " + toString(e.rhsType) + " to a field of type never";
+
+ switch (e.reason)
+ {
+ case CannotAssignToNever::Reason::PropertyNarrowed:
+ if (!e.cause.empty())
+ {
+ result += "\ncaused by the property being given the following incompatible types:\n";
+ for (auto ty : e.cause)
+ result += " " + toString(ty) + "\n";
+ result += "There are no values that could safely satisfy all of these types at once.";
+ }
+ }
+
+ return result;
+ }
};
struct InvalidNameChecker
@@ -950,6 +969,20 @@ bool UnexpectedTypePackInSubtyping::operator==(const UnexpectedTypePackInSubtypi
return tp == rhs.tp;
}
+bool CannotAssignToNever::operator==(const CannotAssignToNever& rhs) const
+{
+ if (cause.size() != rhs.cause.size())
+ return false;
+
+ for (size_t i = 0; i < cause.size(); ++i)
+ {
+ if (*cause[i] != *rhs.cause[i])
+ return false;
+ }
+
+ return *rhsType == *rhs.rhsType && reason == rhs.reason;
+}
+
std::string toString(const TypeError& error)
{
return toString(error, TypeErrorToStringOptions{});
@@ -1140,6 +1173,13 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
e.ty = clone(e.ty);
else if constexpr (std::is_same_v)
e.tp = clone(e.tp);
+ else if constexpr (std::is_same_v)
+ {
+ e.rhsType = clone(e.rhsType);
+
+ for (auto& ty : e.cause)
+ ty = clone(ty);
+ }
else
static_assert(always_false_v, "Non-exhaustive type switch");
}
diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp
index 1ac76835c..55cff7f64 100644
--- a/Analysis/src/Frontend.cpp
+++ b/Analysis/src/Frontend.cpp
@@ -1268,9 +1268,6 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector(ft->lowerBound))
- return ft->upperBound;
-
// we default to the lower bound which represents the most specific type for the free type.
- return ft->lowerBound;
+ TypeId res = get(ft->lowerBound)
+ ? ft->upperBound
+ : ft->lowerBound;
+
+ // Instantiation should not traverse into the type that we are substituting for.
+ dontTraverseInto(res);
+
+ return res;
}
TypePackId Instantiation2::clean(TypePackId tp)
{
- return genericPackSubstitutions[tp];
+ TypePackId res = genericPackSubstitutions[tp];
+ dontTraverseInto(res);
+ return res;
+}
+
+std::optional instantiate2(
+ TypeArena* arena, DenseHashMap genericSubstitutions, DenseHashMap genericPackSubstitutions, TypeId ty)
+{
+ Instantiation2 instantiation{arena, std::move(genericSubstitutions), std::move(genericPackSubstitutions)};
+ return instantiation.substitute(ty);
+}
+
+std::optional instantiate2(
+ TypeArena* arena, DenseHashMap genericSubstitutions, DenseHashMap genericPackSubstitutions, TypePackId tp)
+{
+ Instantiation2 instantiation{arena, std::move(genericSubstitutions), std::move(genericPackSubstitutions)};
+ return instantiation.substitute(tp);
}
} // namespace Luau
diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp
index dd392faad..59aa577e7 100644
--- a/Analysis/src/IostreamHelpers.cpp
+++ b/Analysis/src/IostreamHelpers.cpp
@@ -225,10 +225,38 @@ static void errorToString(std::ostream& stream, const T& err)
stream << "UnexpectedTypeInSubtyping { ty = '" + toString(err.ty) + "' }";
else if constexpr (std::is_same_v)
stream << "UnexpectedTypePackInSubtyping { tp = '" + toString(err.tp) + "' }";
+ else if constexpr (std::is_same_v)
+ {
+ stream << "CannotAssignToNever { rvalueType = '" << toString(err.rhsType) << "', reason = '" << err.reason << "', cause = { ";
+
+ bool first = true;
+ for (TypeId ty : err.cause)
+ {
+ if (first)
+ first = false;
+ else
+ stream << ", ";
+
+ stream << "'" << toString(ty) << "'";
+ }
+
+ stream << " } } ";
+ }
else
static_assert(always_false_v, "Non-exhaustive type switch");
}
+std::ostream& operator<<(std::ostream& stream, const CannotAssignToNever::Reason& reason)
+{
+ switch (reason)
+ {
+ case CannotAssignToNever::Reason::PropertyNarrowed:
+ return stream << "PropertyNarrowed";
+ default:
+ return stream << "UnknownReason";
+ }
+}
+
std::ostream& operator<<(std::ostream& stream, const TypeErrorData& data)
{
auto cb = [&](const auto& e) {
diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp
index 4a3d8b6bd..ce95e6356 100644
--- a/Analysis/src/Normalize.cpp
+++ b/Analysis/src/Normalize.cpp
@@ -16,16 +16,28 @@
#include "Luau/Unifier.h"
LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
+LUAU_FASTFLAGVARIABLE(LuauNormalizeAwayUninhabitableTables, false)
+LUAU_FASTFLAGVARIABLE(LuauFixNormalizeCaching, false);
// This could theoretically be 2000 on amd64, but x86 requires this.
LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
-LUAU_FASTFLAG(LuauTransitiveSubtyping)
-LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
+
+static bool fixNormalizeCaching()
+{
+ return FFlag::LuauFixNormalizeCaching || FFlag::DebugLuauDeferredConstraintResolution;
+}
namespace Luau
{
+// helper to make `FFlag::LuauNormalizeAwayUninhabitableTables` not explicitly required when DCR is enabled.
+static bool normalizeAwayUninhabitableTables()
+{
+ return FFlag::LuauNormalizeAwayUninhabitableTables || FFlag::DebugLuauDeferredConstraintResolution;
+}
+
TypeIds::TypeIds(std::initializer_list tys)
{
for (TypeId ty : tys)
@@ -528,8 +540,16 @@ NormalizationResult Normalizer::isInhabited(TypeId ty, Set& seen)
return isInhabited(mtv->metatable, seen);
}
- const NormalizedType* norm = normalize(ty);
- return isInhabited(norm, seen);
+ if (fixNormalizeCaching())
+ {
+ std::shared_ptr norm = normalize(ty);
+ return isInhabited(norm.get(), seen);
+ }
+ else
+ {
+ const NormalizedType* norm = DEPRECATED_normalize(ty);
+ return isInhabited(norm, seen);
+ }
}
NormalizationResult Normalizer::isIntersectionInhabited(TypeId left, TypeId right)
@@ -829,7 +849,7 @@ Normalizer::Normalizer(TypeArena* arena, NotNull builtinTypes, Not
{
}
-const NormalizedType* Normalizer::normalize(TypeId ty)
+const NormalizedType* Normalizer::DEPRECATED_normalize(TypeId ty)
{
if (!arena)
sharedState->iceHandler->ice("Normalizing types outside a module");
@@ -848,12 +868,102 @@ const NormalizedType* Normalizer::normalize(TypeId ty)
clearNormal(norm);
norm.tops = builtinTypes->unknownType;
}
- std::unique_ptr uniq = std::make_unique(std::move(norm));
- const NormalizedType* result = uniq.get();
- cachedNormals[ty] = std::move(uniq);
+ std::shared_ptr shared = std::make_shared(std::move(norm));
+ const NormalizedType* result = shared.get();
+ cachedNormals[ty] = std::move(shared);
return result;
}
+static bool isCacheable(TypeId ty, Set& seen);
+
+static bool isCacheable(TypePackId tp, Set& seen)
+{
+ tp = follow(tp);
+
+ auto it = begin(tp);
+ auto endIt = end(tp);
+ for (; it != endIt; ++it)
+ {
+ if (!isCacheable(*it, seen))
+ return false;
+ }
+
+ if (auto tail = it.tail())
+ {
+ if (get(*tail) || get(*tail) || get(*tail))
+ return false;
+ }
+
+ return true;
+}
+
+static bool isCacheable(TypeId ty, Set& seen)
+{
+ if (seen.contains(ty))
+ return true;
+ seen.insert(ty);
+
+ ty = follow(ty);
+
+ if (get(ty) || get(ty) || get(ty))
+ return false;
+
+ if (auto tfi = get(ty))
+ {
+ for (TypeId t: tfi->typeArguments)
+ {
+ if (!isCacheable(t, seen))
+ return false;
+ }
+
+ for (TypePackId tp: tfi->packArguments)
+ {
+ if (!isCacheable(tp, seen))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool isCacheable(TypeId ty)
+{
+ if (!fixNormalizeCaching())
+ return true;
+
+ Set seen{nullptr};
+ return isCacheable(ty, seen);
+}
+
+std::shared_ptr Normalizer::normalize(TypeId ty)
+{
+ if (!arena)
+ sharedState->iceHandler->ice("Normalizing types outside a module");
+
+ auto found = cachedNormals.find(ty);
+ if (found != cachedNormals.end())
+ return found->second;
+
+ NormalizedType norm{builtinTypes};
+ Set seenSetTypes{nullptr};
+ NormalizationResult res = unionNormalWithTy(norm, ty, seenSetTypes);
+ if (res != NormalizationResult::True)
+ return nullptr;
+
+ if (norm.isUnknown())
+ {
+ clearNormal(norm);
+ norm.tops = builtinTypes->unknownType;
+ }
+
+ std::shared_ptr shared = std::make_shared(std::move(norm));
+
+ if (shared->isCacheable)
+ cachedNormals[ty] = shared;
+
+ return shared;
+}
+
NormalizationResult Normalizer::normalizeIntersections(const std::vector& intersections, NormalizedType& outType)
{
if (!arena)
@@ -1498,6 +1608,11 @@ void Normalizer::unionFunctionsWithFunction(NormalizedFunctionType& heres, TypeI
void Normalizer::unionTablesWithTable(TypeIds& heres, TypeId there)
{
// TODO: remove unions of tables where possible
+
+ // we can always skip `never`
+ if (normalizeAwayUninhabitableTables() && get(there))
+ return;
+
heres.insert(there);
}
@@ -1539,8 +1654,10 @@ void Normalizer::unionTables(TypeIds& heres, const TypeIds& theres)
// That's what you get for having a type system with generics, intersection and union types.
NormalizationResult Normalizer::unionNormals(NormalizedType& here, const NormalizedType& there, int ignoreSmallerTyvars)
{
+ here.isCacheable &= there.isCacheable;
+
TypeId tops = unionOfTops(here.tops, there.tops);
- if (FFlag::LuauTransitiveSubtyping && get(tops) && (get(here.errors) || get(there.errors)))
+ if (get(tops) && (get(here.errors) || get(there.errors)))
tops = builtinTypes->anyType;
if (!get(tops))
{
@@ -1617,17 +1734,15 @@ NormalizationResult Normalizer::unionNormalWithTy(NormalizedType& here, TypeId t
if (get(there) || get(there))
{
TypeId tops = unionOfTops(here.tops, there);
- if (FFlag::LuauTransitiveSubtyping && get(tops) && get(here.errors))
+ if (get(tops) && get(here.errors))
tops = builtinTypes->anyType;
clearNormal(here);
here.tops = tops;
return NormalizationResult::True;
}
- else if (!FFlag::LuauTransitiveSubtyping && (get(there) || !get(here.tops)))
+ else if (get(there) || get(here.tops))
return NormalizationResult::True;
- else if (FFlag::LuauTransitiveSubtyping && (get(there) || get(here.tops)))
- return NormalizationResult::True;
- else if (FFlag::LuauTransitiveSubtyping && get(there) && get(here.tops))
+ else if (get