diff --git a/Analysis/include/Luau/Constraint.h b/Analysis/include/Luau/Constraint.h
index 38506f0a..5f9c4572 100644
--- a/Analysis/include/Luau/Constraint.h
+++ b/Analysis/include/Luau/Constraint.h
@@ -14,8 +14,16 @@
 namespace Luau
 {
 
+enum class ValueContext;
 struct Scope;
 
+// if resultType is a freeType, assignmentType <: freeType <: resultType bounds
+struct EqualityConstraint
+{
+    TypeId resultType;
+    TypeId assignmentType;
+};
+
 // subType <: superType
 struct SubtypeConstraint
 {
@@ -40,6 +48,8 @@ struct GeneralizationConstraint
 {
     TypeId generalizedType;
     TypeId sourceType;
+
+    std::vector<TypeId> interiorTypes;
 };
 
 // subType ~ inst superType
@@ -145,6 +155,7 @@ struct HasPropConstraint
     TypeId resultType;
     TypeId subjectType;
     std::string prop;
+    ValueContext context;
 
     // HACK: We presently need types like true|false or string|"hello" when
     // deciding whether a particular literal expression should have a singleton
@@ -256,7 +267,8 @@ struct ReducePackConstraint
 
 using ConstraintV = Variant<SubtypeConstraint, PackSubtypeConstraint, GeneralizationConstraint, InstantiationConstraint, IterableConstraint,
     NameConstraint, TypeAliasExpansionConstraint, FunctionCallConstraint, FunctionCheckConstraint, PrimitiveTypeConstraint, HasPropConstraint,
-    SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint>;
+    SetPropConstraint, SetIndexerConstraint, SingletonOrTopTypeConstraint, UnpackConstraint, SetOpConstraint, ReduceConstraint, ReducePackConstraint,
+    EqualityConstraint>;
 
 struct Constraint
 {
diff --git a/Analysis/include/Luau/ConstraintGenerator.h b/Analysis/include/Luau/ConstraintGenerator.h
index 65318a31..3d3dcb4d 100644
--- a/Analysis/include/Luau/ConstraintGenerator.h
+++ b/Analysis/include/Luau/ConstraintGenerator.h
@@ -130,6 +130,8 @@ struct ConstraintGenerator
     void visitModuleRoot(AstStatBlock* block);
 
 private:
+    std::vector<std::vector<TypeId>> interiorTypes;
+
     /**
      * Fabricates a new free type belonging to a given scope.
      * @param scope the scope the free type belongs to.
diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h
index 0f4be214..54d229c3 100644
--- a/Analysis/include/Luau/ConstraintSolver.h
+++ b/Analysis/include/Luau/ConstraintSolver.h
@@ -8,6 +8,7 @@
 #include "Luau/Location.h"
 #include "Luau/Module.h"
 #include "Luau/Normalize.h"
+#include "Luau/Substitution.h"
 #include "Luau/ToString.h"
 #include "Luau/Type.h"
 #include "Luau/TypeCheckLimits.h"
@@ -20,6 +21,8 @@
 namespace Luau
 {
 
+enum class ValueContext;
+
 struct DcrLogger;
 
 // TypeId, TypePackId, or Constraint*. It is impossible to know which, but we
@@ -110,8 +113,6 @@ struct ConstraintSolver
 
     bool isDone();
 
-    void finalizeModule();
-
     /** Attempt to dispatch a constraint.  Returns true if it was successful. If
      * tryDispatch() returns false, the constraint remains in the unsolved set
      * and will be retried later.
@@ -136,6 +137,7 @@ struct ConstraintSolver
     bool tryDispatch(const SetOpConstraint& c, NotNull<const Constraint> constraint, bool force);
     bool tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force);
     bool tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force);
+    bool tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force);
 
     // for a, ... in some_table do
     // also handles __iter metamethod
@@ -146,9 +148,9 @@ struct ConstraintSolver
         TypeId nextTy, TypeId tableTy, TypeId firstIndexTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force);
 
     std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
-        TypeId subjectType, const std::string& propName, bool suppressSimplification = false);
+        TypeId subjectType, const std::string& propName, ValueContext context, bool suppressSimplification = false);
     std::pair<std::vector<TypeId>, std::optional<TypeId>> lookupTableProp(
-        TypeId subjectType, const std::string& propName, bool suppressSimplification, DenseHashSet<TypeId>& seen);
+        TypeId subjectType, const std::string& propName, ValueContext context, bool suppressSimplification, DenseHashSet<TypeId>& seen);
 
     void block(NotNull<const Constraint> target, NotNull<const Constraint> constraint);
     /**
@@ -208,23 +210,6 @@ struct ConstraintSolver
      */
     bool isBlocked(NotNull<const Constraint> constraint);
 
-    /**
-     * Creates a new Unifier and performs a single unification operation. Commits
-     * the result.
-     * @param subType the sub-type to unify.
-     * @param superType the super-type to unify.
-     * @returns optionally a unification too complex error if unification failed
-     */
-    std::optional<TypeError> unify(NotNull<Scope> scope, Location location, TypeId subType, TypeId superType);
-
-    /**
-     * Creates a new Unifier and performs a single unification operation. Commits
-     * the result.
-     * @param subPack the sub-type pack to unify.
-     * @param superPack the super-type pack to unify.
-     */
-    ErrorVec unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack);
-
     /** Pushes a new solver constraint to the solver.
      * @param cv the body of the constraint.
      **/
@@ -253,20 +238,33 @@ struct ConstraintSolver
      */
     bool hasUnresolvedConstraints(TypeId ty);
 
-private:
-    /** Helper used by tryDispatch(SubtypeConstraint) and
-     * tryDispatch(PackSubtypeConstraint)
+    /**
+     * Creates a new Unifier and performs a single unification operation.
      *
-     * Attempts to unify subTy with superTy.  If doing so would require unifying
+     * @param subType the sub-type to unify.
+     * @param superType the super-type to unify.
+     * @returns true if the unification succeeded.  False if the unification was
+     * too complex.
+     */
+    template <typename TID>
+    bool unify(NotNull<Scope> scope, Location location, TID subType, TID superType);
+
+    /** Attempts to unify subTy with superTy.  If doing so would require unifying
      * BlockedTypes, fail and block the constraint on those BlockedTypes.
      *
+     * Note: TID can only be TypeId or TypePackId.
+     *
      * If unification fails, replace all free types with errorType.
      *
      * If unification succeeds, unblock every type changed by the unification.
+     *
+     * @returns true if the unification succeeded.  False if the unification was
+     * too complex.
      */
     template<typename TID>
-    bool tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
+    bool unify(NotNull<const Constraint> constraint, TID subTy, TID superTy);
 
+private:
     /**
      * 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
@@ -295,6 +293,13 @@ private:
      **/
     void unblock_(BlockedConstraintId progressed);
 
+    /**
+     * Reproduces any constraints necessary for new types that are copied when applying a substitution.
+     * At the time of writing, this pertains only to type families.
+     * @param subst the substitution that was applied
+     **/
+    void reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst);
+
     TypeId errorRecoveryType() const;
     TypePackId errorRecoveryTypePack() const;
 
diff --git a/Analysis/include/Luau/Error.h b/Analysis/include/Luau/Error.h
index 5a2caf65..d9b5f1ba 100644
--- a/Analysis/include/Luau/Error.h
+++ b/Analysis/include/Luau/Error.h
@@ -380,6 +380,20 @@ struct NonStrictFunctionDefinitionError
     bool operator==(const NonStrictFunctionDefinitionError& rhs) const;
 };
 
+struct PropertyAccessViolation
+{
+    TypeId table;
+    Name key;
+
+    enum
+    {
+        CannotRead,
+        CannotWrite
+    } context;
+
+    bool operator==(const PropertyAccessViolation& rhs) const;
+};
+
 struct CheckedFunctionIncorrectArgs
 {
     std::string functionName;
@@ -388,14 +402,28 @@ struct CheckedFunctionIncorrectArgs
     bool operator==(const CheckedFunctionIncorrectArgs& rhs) const;
 };
 
-using TypeErrorData =
-    Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods, DuplicateTypeDefinition,
-        CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire, IncorrectGenericParameterCount, SyntaxError,
-        CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError, CannotCallNonFunction, ExtraInformation,
-        DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning, DuplicateGenericParameter,
-        CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty, TypesAreUnrelated,
-        NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily, UninhabitedTypePackFamily,
-        WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError, CheckedFunctionIncorrectArgs>;
+struct UnexpectedTypeInSubtyping
+{
+    TypeId ty;
+
+    bool operator==(const UnexpectedTypeInSubtyping& rhs) const;
+};
+
+struct UnexpectedTypePackInSubtyping
+{
+    TypePackId tp;
+
+    bool operator==(const UnexpectedTypePackInSubtyping& rhs) const;
+};
+
+using TypeErrorData = Variant<TypeMismatch, UnknownSymbol, UnknownProperty, NotATable, CannotExtendTable, OnlyTablesCanHaveMethods,
+    DuplicateTypeDefinition, CountMismatch, FunctionDoesNotTakeSelf, FunctionRequiresSelf, OccursCheckFailed, UnknownRequire,
+    IncorrectGenericParameterCount, SyntaxError, CodeTooComplex, UnificationTooComplex, UnknownPropButFoundLikeProp, GenericError, InternalError,
+    CannotCallNonFunction, ExtraInformation, DeprecatedApiUsed, ModuleHasCyclicDependency, IllegalRequire, FunctionExitsWithoutReturning,
+    DuplicateGenericParameter, CannotInferBinaryOperation, MissingProperties, SwappedGenericTypeParameter, OptionalValueAccess, MissingUnionProperty,
+    TypesAreUnrelated, NormalizationTooComplex, TypePackMismatch, DynamicPropertyLookupOnClassesUnsafe, UninhabitedTypeFamily,
+    UninhabitedTypePackFamily, WhereClauseNeeded, PackWhereClauseNeeded, CheckedFunctionCallError, NonStrictFunctionDefinitionError,
+    PropertyAccessViolation, CheckedFunctionIncorrectArgs, UnexpectedTypeInSubtyping, UnexpectedTypePackInSubtyping>;
 
 struct TypeErrorSummary
 {
diff --git a/Analysis/include/Luau/Instantiation2.h b/Analysis/include/Luau/Instantiation2.h
new file mode 100644
index 00000000..9dfbb613
--- /dev/null
+++ b/Analysis/include/Luau/Instantiation2.h
@@ -0,0 +1,70 @@
+// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
+#pragma once
+
+#include "Luau/NotNull.h"
+#include "Luau/Substitution.h"
+#include "Luau/TxnLog.h"
+#include "Luau/TypeFwd.h"
+#include "Luau/Unifiable.h"
+
+namespace Luau
+{
+
+struct TypeArena;
+struct TypeCheckLimits;
+
+struct Replacer : Substitution
+{
+    DenseHashMap<TypeId, TypeId> replacements;
+    DenseHashMap<TypePackId, TypePackId> replacementPacks;
+
+    Replacer(NotNull<TypeArena> arena, DenseHashMap<TypeId, TypeId> replacements, DenseHashMap<TypePackId, TypePackId> replacementPacks)
+        : Substitution(TxnLog::empty(), arena)
+        , replacements(std::move(replacements))
+        , replacementPacks(std::move(replacementPacks))
+    {
+    }
+
+    bool isDirty(TypeId ty) override
+    {
+        return replacements.find(ty) != nullptr;
+    }
+
+    bool isDirty(TypePackId tp) override
+    {
+        return replacementPacks.find(tp) != nullptr;
+    }
+
+    TypeId clean(TypeId ty) override
+    {
+        return replacements[ty];
+    }
+
+    TypePackId clean(TypePackId tp) override
+    {
+        return replacementPacks[tp];
+    }
+};
+
+// A substitution which replaces generic functions by monomorphic functions
+struct Instantiation2 : Substitution
+{
+    // Mapping from generic types to free types to be used in instantiation.
+    DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
+    // Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
+    DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
+
+    Instantiation2(TypeArena* arena, DenseHashMap<TypeId, TypeId> genericSubstitutions, DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions)
+        : Substitution(TxnLog::empty(), arena)
+        , genericSubstitutions(std::move(genericSubstitutions))
+        , genericPackSubstitutions(std::move(genericPackSubstitutions))
+    {
+    }
+
+    bool isDirty(TypeId ty) override;
+    bool isDirty(TypePackId tp) override;
+    TypeId clean(TypeId ty) override;
+    TypePackId clean(TypePackId tp) override;
+};
+
+} // namespace Luau
diff --git a/Analysis/include/Luau/Subtyping.h b/Analysis/include/Luau/Subtyping.h
index 0ca2380e..8fa4b8b0 100644
--- a/Analysis/include/Luau/Subtyping.h
+++ b/Analysis/include/Luau/Subtyping.h
@@ -21,14 +21,15 @@ struct InternalErrorReporter;
 
 class TypeIds;
 class Normalizer;
-struct NormalizedType;
 struct NormalizedClassType;
-struct NormalizedStringType;
 struct NormalizedFunctionType;
-struct TypeArena;
-struct TypeCheckLimits;
+struct NormalizedStringType;
+struct NormalizedType;
+struct Property;
 struct Scope;
 struct TableIndexer;
+struct TypeArena;
+struct TypeCheckLimits;
 
 enum class SubtypingVariance
 {
@@ -79,6 +80,7 @@ struct SubtypingResult
     SubtypingResult& withSubPath(TypePath::Path path);
     SubtypingResult& withSuperPath(TypePath::Path path);
     SubtypingResult& withErrors(ErrorVec& err);
+    SubtypingResult& withError(TypeError err);
 
     // Only negates the `isSubtype`.
     static SubtypingResult negate(const SubtypingResult& result);
@@ -102,6 +104,10 @@ struct SubtypingEnvironment
     DenseHashMap<TypePackId, TypePackId> mappedGenericPacks{nullptr};
 
     DenseHashMap<std::pair<TypeId, TypeId>, SubtypingResult, TypePairHash> ephemeralCache{{}};
+
+    /// Applies `mappedGenerics` to the given type.
+    /// This is used specifically to substitute for generics in type family instances.
+    std::optional<TypeId> applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty);
 };
 
 struct Subtyping
@@ -192,6 +198,7 @@ private:
     SubtypingResult isCovariantWith(SubtypingEnvironment& env, const SingletonType* subSingleton, const TableType* superTable);
 
     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 NormalizedClassType& subClass, const NormalizedClassType& superClass);
diff --git a/Analysis/include/Luau/Type.h b/Analysis/include/Luau/Type.h
index c72864ce..a0c6b9cc 100644
--- a/Analysis/include/Luau/Type.h
+++ b/Analysis/include/Luau/Type.h
@@ -414,16 +414,11 @@ struct Property
     TypeId type() const;
     void setType(TypeId ty);
 
-    // Should only be called in RWP!
-    // We do not assert that `readTy` nor `writeTy` are nullopt or not.
-    // The invariant is that at least one of them mustn't be nullopt, which we do assert here.
-    // TODO: Kill this in favor of exposing `readTy`/`writeTy` directly? If we do, we'll lose the asserts which will be useful while debugging.
-    std::optional<TypeId> readType() const;
-    std::optional<TypeId> writeType() const;
-
     bool isShared() const;
+    bool isReadOnly() const;
+    bool isWriteOnly() const;
+    bool isReadWrite() const;
 
-private:
     std::optional<TypeId> readTy;
     std::optional<TypeId> writeTy;
 };
@@ -844,6 +839,7 @@ public:
 
     const TypePackId emptyTypePack;
     const TypePackId anyTypePack;
+    const TypePackId unknownTypePack;
     const TypePackId neverTypePack;
     const TypePackId uninhabitableTypePack;
     const TypePackId errorTypePack;
diff --git a/Analysis/include/Luau/TypeFamily.h b/Analysis/include/Luau/TypeFamily.h
index 79dc435c..389a2e00 100644
--- a/Analysis/include/Luau/TypeFamily.h
+++ b/Analysis/include/Luau/TypeFamily.h
@@ -30,8 +30,10 @@ struct TypeFamilyContext
 
     // nullptr if the type family is being reduced outside of the constraint solver.
     ConstraintSolver* solver;
+    // The constraint being reduced in this run of the reduction
+    const Constraint* constraint;
 
-    TypeFamilyContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope)
+    TypeFamilyContext(NotNull<ConstraintSolver> cs, NotNull<Scope> scope, NotNull<const Constraint> constraint)
         : arena(cs->arena)
         , builtins(cs->builtinTypes)
         , scope(scope)
@@ -39,6 +41,7 @@ struct TypeFamilyContext
         , ice(NotNull{&cs->iceReporter})
         , limits(NotNull{&cs->limits})
         , solver(cs.get())
+        , constraint(constraint.get())
     {
     }
 
@@ -51,10 +54,10 @@ struct TypeFamilyContext
         , ice(ice)
         , limits(limits)
         , solver(nullptr)
+        , constraint(nullptr)
     {
     }
 };
-
 /// 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.
diff --git a/Analysis/include/Luau/TypeUtils.h b/Analysis/include/Luau/TypeUtils.h
index 6ac4e7c6..2fcc8fef 100644
--- a/Analysis/include/Luau/TypeUtils.h
+++ b/Analysis/include/Luau/TypeUtils.h
@@ -28,6 +28,8 @@ std::optional<TypeId> findMetatableEntry(
     NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId type, const std::string& entry, Location location);
 std::optional<TypeId> findTablePropertyRespectingMeta(
     NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location);
+std::optional<TypeId> findTablePropertyRespectingMeta(
+    NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, ValueContext context, Location location);
 
 // Returns the minimum and maximum number of types the argument list can accept.
 std::pair<size_t, std::optional<size_t>> getParameterExtents(const TxnLog* log, TypePackId tp, bool includeHiddenVariadics = false);
diff --git a/Analysis/include/Luau/Unifier2.h b/Analysis/include/Luau/Unifier2.h
index f9a3fdc9..a0553a2a 100644
--- a/Analysis/include/Luau/Unifier2.h
+++ b/Analysis/include/Luau/Unifier2.h
@@ -38,6 +38,11 @@ struct Unifier2
 
     DenseHashMap<TypeId, std::vector<TypeId>> expandedFreeTypes{nullptr};
 
+    // Mapping from generic types to free types to be used in instantiation.
+    DenseHashMap<TypeId, TypeId> genericSubstitutions{nullptr};
+    // Mapping from generic type packs to `TypePack`s of free types to be used in instantiation.
+    DenseHashMap<TypePackId, TypePackId> genericPackSubstitutions{nullptr};
+
     int recursionCount = 0;
     int recursionLimit = 0;
 
diff --git a/Analysis/include/Luau/VisitType.h b/Analysis/include/Luau/VisitType.h
index 6e1fea6a..94510e32 100644
--- a/Analysis/include/Luau/VisitType.h
+++ b/Analysis/include/Luau/VisitType.h
@@ -11,7 +11,6 @@
 LUAU_FASTINT(LuauVisitRecursionLimit)
 LUAU_FASTFLAG(LuauBoundLazyTypes2)
 LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
-LUAU_FASTFLAG(DebugLuauReadWriteProperties)
 
 namespace Luau
 {
@@ -281,12 +280,16 @@ struct GenericTypeVisitor
                 {
                     for (auto& [_name, prop] : ttv->props)
                     {
-                        if (FFlag::DebugLuauReadWriteProperties)
+                        if (FFlag::DebugLuauDeferredConstraintResolution)
                         {
-                            if (auto ty = prop.readType())
+                            if (auto ty = prop.readTy)
                                 traverse(*ty);
 
-                            if (auto ty = prop.writeType())
+                            // In the case that the readType and the writeType
+                            // are the same pointer, just traverse once.
+                            // Traversing each property twice has pretty
+                            // significant performance consequences.
+                            if (auto ty = prop.writeTy; ty && !prop.isShared())
                                 traverse(*ty);
                         }
                         else
@@ -315,12 +318,16 @@ struct GenericTypeVisitor
             {
                 for (const auto& [name, prop] : ctv->props)
                 {
-                    if (FFlag::DebugLuauReadWriteProperties)
+                    if (FFlag::DebugLuauDeferredConstraintResolution)
                     {
-                        if (auto ty = prop.readType())
+                        if (auto ty = prop.readTy)
                             traverse(*ty);
 
-                        if (auto ty = prop.writeType())
+                        // In the case that the readType and the writeType are
+                        // the same pointer, just traverse once. Traversing each
+                        // property twice would have pretty significant
+                        // performance consequences.
+                        if (auto ty = prop.writeTy; ty && !prop.isShared())
                             traverse(*ty);
                     }
                     else
diff --git a/Analysis/src/AstQuery.cpp b/Analysis/src/AstQuery.cpp
index cea81080..cebb226a 100644
--- a/Analysis/src/AstQuery.cpp
+++ b/Analysis/src/AstQuery.cpp
@@ -11,7 +11,7 @@
 
 #include <algorithm>
 
-LUAU_FASTFLAG(DebugLuauReadWriteProperties)
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
 
 namespace Luau
 {
@@ -524,9 +524,9 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
                 {
                     if (auto propIt = ttv->props.find(indexName->index.value); propIt != ttv->props.end())
                     {
-                        if (FFlag::DebugLuauReadWriteProperties)
+                        if (FFlag::DebugLuauDeferredConstraintResolution)
                         {
-                            if (auto ty = propIt->second.readType())
+                            if (auto ty = propIt->second.readTy)
                                 return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
                         }
                         else
@@ -537,9 +537,9 @@ std::optional<DocumentationSymbol> getDocumentationSymbolAtPosition(const Source
                 {
                     if (auto propIt = ctv->props.find(indexName->index.value); propIt != ctv->props.end())
                     {
-                        if (FFlag::DebugLuauReadWriteProperties)
+                        if (FFlag::DebugLuauDeferredConstraintResolution)
                         {
-                            if (auto ty = propIt->second.readType())
+                            if (auto ty = propIt->second.readTy)
                                 return checkOverloadedDocumentationSymbol(module, *ty, parentExpr, propIt->second.documentationSymbol);
                         }
                         else
diff --git a/Analysis/src/Autocomplete.cpp b/Analysis/src/Autocomplete.cpp
index 1c8620b8..d5c947da 100644
--- a/Analysis/src/Autocomplete.cpp
+++ b/Analysis/src/Autocomplete.cpp
@@ -14,7 +14,6 @@
 #include <utility>
 
 LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
-LUAU_FASTFLAG(DebugLuauReadWriteProperties);
 LUAU_FASTFLAGVARIABLE(LuauAutocompleteStringLiteralBounds, false);
 LUAU_FASTFLAGVARIABLE(LuauAutocompleteTableKeysNoInitialCharacter, false);
 
@@ -277,9 +276,9 @@ static void autocompleteProps(const Module& module, TypeArena* typeArena, NotNul
             {
                 Luau::TypeId type;
 
-                if (FFlag::DebugLuauReadWriteProperties)
+                if (FFlag::DebugLuauDeferredConstraintResolution)
                 {
-                    if (auto ty = prop.readType())
+                    if (auto ty = prop.readTy)
                         type = follow(*ty);
                     else
                         continue;
diff --git a/Analysis/src/BuiltinDefinitions.cpp b/Analysis/src/BuiltinDefinitions.cpp
index c0d88d9a..a8b17c55 100644
--- a/Analysis/src/BuiltinDefinitions.cpp
+++ b/Analysis/src/BuiltinDefinitions.cpp
@@ -283,6 +283,26 @@ void registerBuiltinGlobals(Frontend& frontend, GlobalTypes& globals, bool typeC
     }
 
     attachMagicFunction(getGlobalBinding(globals, "assert"), magicFunctionAssert);
+
+    if (FFlag::DebugLuauDeferredConstraintResolution)
+    {
+        // declare function assert<T>(value: T, errorMessage: string?): intersect<T, ~(false?)>
+        TypeId genericT = arena.addType(GenericType{"T"});
+        TypeId refinedTy = arena.addType(TypeFamilyInstanceType{
+            NotNull{&kBuiltinTypeFamilies.intersectFamily},
+            {genericT, arena.addType(NegationType{builtinTypes->falsyType})},
+            {}
+        });
+
+        TypeId assertTy = arena.addType(FunctionType{
+            {genericT},
+            {},
+            arena.addTypePack(TypePack{{genericT, builtinTypes->optionalStringType}}),
+            arena.addTypePack(TypePack{{refinedTy}})
+        });
+        addGlobalBinding(globals, "assert", assertTy, "@luau");
+    }
+
     attachMagicFunction(getGlobalBinding(globals, "setmetatable"), magicFunctionSetMetaTable);
     attachMagicFunction(getGlobalBinding(globals, "select"), magicFunctionSelect);
     attachDcrMagicFunction(getGlobalBinding(globals, "select"), dcrMagicFunctionSelect);
diff --git a/Analysis/src/Clone.cpp b/Analysis/src/Clone.cpp
index 6d2d04b6..bf02f743 100644
--- a/Analysis/src/Clone.cpp
+++ b/Analysis/src/Clone.cpp
@@ -8,8 +8,6 @@
 #include "Luau/TypePack.h"
 #include "Luau/Unifiable.h"
 
-LUAU_FASTFLAG(DebugLuauReadWriteProperties)
-
 LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
 LUAU_FASTINTVARIABLE(LuauTypeCloneRecursionLimit, 300)
 
@@ -196,14 +194,14 @@ private:
 
     Property shallowClone(const Property& p)
     {
-        if (FFlag::DebugLuauReadWriteProperties)
+        if (FFlag::DebugLuauDeferredConstraintResolution)
         {
             std::optional<TypeId> cloneReadTy;
-            if (auto ty = p.readType())
+            if (auto ty = p.readTy)
                 cloneReadTy = shallowClone(*ty);
 
             std::optional<TypeId> cloneWriteTy;
-            if (auto ty = p.writeType())
+            if (auto ty = p.writeTy)
                 cloneWriteTy = shallowClone(*ty);
 
             std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
@@ -460,14 +458,14 @@ namespace
 
 Property clone(const Property& prop, TypeArena& dest, CloneState& cloneState)
 {
-    if (FFlag::DebugLuauReadWriteProperties)
+    if (FFlag::DebugLuauDeferredConstraintResolution)
     {
         std::optional<TypeId> cloneReadTy;
-        if (auto ty = prop.readType())
+        if (auto ty = prop.readTy)
             cloneReadTy = clone(*ty, dest, cloneState);
 
         std::optional<TypeId> cloneWriteTy;
-        if (auto ty = prop.writeType())
+        if (auto ty = prop.writeTy)
             cloneWriteTy = clone(*ty, dest, cloneState);
 
         std::optional<Property> cloned = Property::create(cloneReadTy, cloneWriteTy);
diff --git a/Analysis/src/ConstraintGenerator.cpp b/Analysis/src/ConstraintGenerator.cpp
index 6f744951..17bffe87 100644
--- a/Analysis/src/ConstraintGenerator.cpp
+++ b/Analysis/src/ConstraintGenerator.cpp
@@ -217,9 +217,25 @@ void ConstraintGenerator::visitModuleRoot(AstStatBlock* block)
 
     rootScope->returnType = freshTypePack(scope);
 
+    TypeId moduleFnTy = arena->addType(FunctionType{TypeLevel{}, rootScope, builtinTypes->anyTypePack, rootScope->returnType});
+    interiorTypes.emplace_back();
+
     prepopulateGlobalScope(scope, block);
 
-    visitBlockWithoutChildScope(scope, block);
+    Checkpoint start = checkpoint(this);
+
+    ControlFlow cf = visitBlockWithoutChildScope(scope, block);
+    if (cf == ControlFlow::None)
+        addConstraint(scope, block->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, rootScope->returnType});
+
+    Checkpoint end = checkpoint(this);
+
+    NotNull<Constraint> genConstraint = addConstraint(scope, block->location, GeneralizationConstraint{arena->addType(BlockedType{}), moduleFnTy, std::move(interiorTypes.back())});
+    forEachConstraint(start, end, this, [genConstraint](const ConstraintPtr& c) {
+        genConstraint->dependencies.push_back(NotNull{c.get()});
+    });
+
+    interiorTypes.pop_back();
 
     fillInInferredBindings(scope, block);
 
@@ -406,7 +422,7 @@ void ConstraintGenerator::computeRefinement(const ScopePtr& scope, Location loca
 
             TypeId nextDiscriminantTy = arena->addType(TableType{});
             NotNull<TableType> table{getMutable<TableType>(nextDiscriminantTy)};
-            table->props[*key->propName] = {discriminantTy};
+            table->props[*key->propName] = Property::readonly(discriminantTy);
             table->scope = scope.get();
             table->state = TableState::Sealed;
 
@@ -1894,7 +1910,7 @@ Inference ConstraintGenerator::checkIndexName(const ScopePtr& scope, const Refin
         scope->rvalueRefinements[key->def] = result;
     }
 
-    addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index)});
+    addConstraint(scope, indexee->location, HasPropConstraint{result, obj, std::move(index), ValueContext::RValue});
 
     if (key)
         return Inference{result, refinementArena.proposition(key, builtinTypes->truthyType)};
@@ -1945,27 +1961,30 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprFunction* fun
 {
     Checkpoint startCheckpoint = checkpoint(this);
     FunctionSignature sig = checkFunctionSignature(scope, func, expectedType);
+
+    interiorTypes.push_back(std::vector<TypeId>{});
     checkFunctionBody(sig.bodyScope, func);
     Checkpoint endCheckpoint = checkpoint(this);
 
+    TypeId generalizedTy = arena->addType(BlockedType{});
+    NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature, std::move(interiorTypes.back())});
+    interiorTypes.pop_back();
+
+    Constraint* previous = nullptr;
+    forEachConstraint(startCheckpoint, endCheckpoint, this, [gc, &previous](const ConstraintPtr& constraint) {
+        gc->dependencies.emplace_back(constraint.get());
+
+        if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
+        {
+            if (previous)
+                constraint->dependencies.push_back(NotNull{previous});
+
+            previous = constraint.get();
+        }
+    });
+
     if (generalize && hasFreeType(sig.signature))
     {
-        TypeId generalizedTy = arena->addType(BlockedType{});
-        NotNull<Constraint> gc = addConstraint(sig.signatureScope, func->location, GeneralizationConstraint{generalizedTy, sig.signature});
-
-        Constraint* previous = nullptr;
-        forEachConstraint(startCheckpoint, endCheckpoint, this, [gc, &previous](const ConstraintPtr& constraint) {
-            gc->dependencies.emplace_back(constraint.get());
-
-            if (auto psc = get<PackSubtypeConstraint>(*constraint); psc && psc->returns)
-            {
-                if (previous)
-                    constraint->dependencies.push_back(NotNull{previous});
-
-                previous = constraint.get();
-            }
-        });
-
         return Inference{generalizedTy};
     }
     else
@@ -2529,7 +2548,8 @@ TypeId ConstraintGenerator::updateProperty(const ScopePtr& scope, AstExpr* expr,
     {
         TypeId segmentTy = arena->addType(BlockedType{});
         module->astTypes[exprs[i]] = segmentTy;
-        addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i]});
+        ValueContext ctx = i == segments.size() - 1 ? ValueContext::LValue : ValueContext::RValue;
+        addConstraint(scope, expr->location, HasPropConstraint{segmentTy, prevSegmentTy, segments[i], ctx});
         prevSegmentTy = segmentTy;
     }
 
@@ -2563,6 +2583,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
     ttv->state = TableState::Unsealed;
     ttv->scope = scope.get();
 
+    interiorTypes.back().push_back(ty);
+
     auto createIndexer = [this, scope, ttv](const Location& location, TypeId currentIndexType, TypeId currentResultType) {
         if (!ttv->indexer)
         {
@@ -2613,7 +2635,8 @@ Inference ConstraintGenerator::check(const ScopePtr& scope, AstExprTable* expr,
                 {
                     expectedValueType = arena->addType(BlockedType{});
                     addConstraint(scope, item.value->location,
-                        HasPropConstraint{*expectedValueType, *expectedType, stringKey->value.data, /*suppressSimplification*/ true});
+                        HasPropConstraint{
+                            *expectedValueType, *expectedType, stringKey->value.data, ValueContext::RValue, /*suppressSimplification*/ true});
                 }
             }
         }
@@ -2876,15 +2899,10 @@ ConstraintGenerator::FunctionSignature ConstraintGenerator::checkFunctionSignatu
 
 void ConstraintGenerator::checkFunctionBody(const ScopePtr& scope, AstExprFunction* fn)
 {
-    visitBlockWithoutChildScope(scope, fn->body);
-
     // If it is possible for execution to reach the end of the function, the return type must be compatible with ()
-
-    if (nullptr != getFallthrough(fn->body))
-    {
-        TypePackId empty = arena->addTypePack({}); // TODO we could have CG retain one of these forever
-        addConstraint(scope, fn->location, PackSubtypeConstraint{scope->returnType, empty});
-    }
+    ControlFlow cf = visitBlockWithoutChildScope(scope, fn->body);
+    if (cf == ControlFlow::None)
+        addConstraint(scope, fn->location, PackSubtypeConstraint{builtinTypes->emptyTypePack, scope->returnType});
 }
 
 TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool inTypeArguments, bool replaceErrorWithFresh)
@@ -2977,20 +2995,30 @@ TypeId ConstraintGenerator::resolveType(const ScopePtr& scope, AstType* ty, bool
 
         for (const AstTableProp& prop : tab->props)
         {
-            if (prop.access == AstTableAccess::Read)
-                reportError(prop.accessLocation.value_or(Location{}), GenericError{"read keyword is illegal here"});
-            else if (prop.access == AstTableAccess::Write)
-                reportError(prop.accessLocation.value_or(Location{}), GenericError{"write keyword is illegal here"});
-            else if (prop.access == AstTableAccess::ReadWrite)
+            // TODO: Recursion limit.
+            TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
+
+            Property& p = props[prop.name.value];
+            p.typeLocation = prop.location;
+
+            switch (prop.access)
             {
-                std::string name = prop.name.value;
-                // TODO: Recursion limit.
-                TypeId propTy = resolveType(scope, prop.type, inTypeArguments);
-                props[name] = {propTy};
-                props[name].typeLocation = prop.location;
-            }
-            else
+            case AstTableAccess::ReadWrite:
+                p.readTy = propTy;
+                p.writeTy = propTy;
+                break;
+            case AstTableAccess::Read:
+                p.readTy = propTy;
+                break;
+            case AstTableAccess::Write:
+                reportError(*prop.accessLocation, GenericError{"write keyword is illegal here"});
+                p.readTy = propTy;
+                p.writeTy = propTy;
+                break;
+            default:
                 ice->ice("Unexpected property access " + std::to_string(int(prop.access)));
+                break;
+            }
         }
 
         if (AstTableIndexer* astIndexer = tab->indexer)
diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp
index 109fb2fb..996d638b 100644
--- a/Analysis/src/ConstraintSolver.cpp
+++ b/Analysis/src/ConstraintSolver.cpp
@@ -6,6 +6,7 @@
 #include "Luau/ConstraintSolver.h"
 #include "Luau/DcrLogger.h"
 #include "Luau/Instantiation.h"
+#include "Luau/Instantiation2.h"
 #include "Luau/Location.h"
 #include "Luau/ModuleResolver.h"
 #include "Luau/OverloadResolution.h"
@@ -430,8 +431,6 @@ void ConstraintSolver::run()
             progress |= runSolverPass(true);
     } while (progress);
 
-    finalizeModule();
-
     if (FFlag::DebugLuauLogSolver)
     {
         dumpBindings(rootScope, opts);
@@ -477,48 +476,6 @@ struct FreeTypeSearcher : TypeOnceVisitor
 
 } // namespace
 
-void ConstraintSolver::finalizeModule()
-{
-    Anyification a{arena, rootScope, builtinTypes, &iceReporter, builtinTypes->anyType, builtinTypes->anyTypePack};
-    std::optional<TypePackId> returnType = a.substitute(rootScope->returnType);
-    if (!returnType)
-    {
-        reportError(CodeTooComplex{}, Location{});
-        rootScope->returnType = builtinTypes->errorTypePack;
-    }
-    else
-    {
-        rootScope->returnType = anyifyModuleReturnTypePackGenerics(*returnType);
-    }
-
-    Unifier2 u2{NotNull{arena}, builtinTypes, rootScope, NotNull{&iceReporter}};
-
-    VecDeque<TypeAndLocation> queue;
-    for (auto& [name, binding] : rootScope->bindings)
-        queue.push_back({binding.typeId, binding.location});
-
-    DenseHashSet<TypeId> seen{nullptr};
-
-    while (!queue.empty())
-    {
-        TypeAndLocation binding = queue.front();
-        queue.pop_front();
-
-        TypeId ty = follow(binding.typeId);
-
-        if (seen.find(ty))
-            continue;
-        seen.insert(ty);
-
-        FreeTypeSearcher fts{&queue, binding.location};
-        fts.traverse(ty);
-
-        auto result = u2.generalize(ty);
-        if (!result)
-            reportError(CodeTooComplex{}, binding.location);
-    }
-}
-
 bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool force)
 {
     if (!force && isBlocked(constraint))
@@ -562,6 +519,8 @@ bool ConstraintSolver::tryDispatch(NotNull<const Constraint> constraint, bool fo
         success = tryDispatch(*rc, constraint, force);
     else if (auto rpc = get<ReducePackConstraint>(*constraint))
         success = tryDispatch(*rpc, constraint, force);
+    else if (auto eqc = get<EqualityConstraint>(*constraint))
+        success = tryDispatch(*eqc, constraint, force);
     else
         LUAU_ASSERT(false);
 
@@ -578,7 +537,9 @@ bool ConstraintSolver::tryDispatch(const SubtypeConstraint& c, NotNull<const Con
     else if (isBlocked(c.superType))
         return block(c.superType, constraint);
 
-    return tryUnify(constraint, c.subType, c.superType);
+    unify(constraint, c.subType, c.superType);
+
+    return true;
 }
 
 bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const Constraint> constraint, bool force)
@@ -588,7 +549,9 @@ bool ConstraintSolver::tryDispatch(const PackSubtypeConstraint& c, NotNull<const
     else if (isBlocked(c.superPack))
         return block(c.superPack, constraint);
 
-    return tryUnify(constraint, c.subPack, c.superPack);
+    unify(constraint, c.subPack, c.superPack);
+
+    return true;
 }
 
 bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<const Constraint> constraint, bool force)
@@ -615,13 +578,13 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
         if (get<BlockedType>(generalizedType))
             asMutable(generalizedType)->ty.emplace<BoundType>(generalized->result);
         else
-            unify(constraint->scope, constraint->location, generalizedType, generalized->result);
+            unify(constraint, generalizedType, generalized->result);
 
         for (auto [free, gen] : generalized->insertedGenerics.pairings)
-            unify(constraint->scope, constraint->location, free, gen);
+            unify(constraint, free, gen);
 
         for (auto [free, gen] : generalized->insertedGenericPacks.pairings)
-            unify(constraint->scope, constraint->location, free, gen);
+            unify(constraint, free, gen);
     }
     else
     {
@@ -632,6 +595,9 @@ bool ConstraintSolver::tryDispatch(const GeneralizationConstraint& c, NotNull<co
     unblock(c.generalizedType, constraint->location);
     unblock(c.sourceType, constraint->location);
 
+    for (TypeId ty : c.interiorTypes)
+        u2.generalize(ty);
+
     return true;
 }
 
@@ -730,7 +696,7 @@ bool ConstraintSolver::tryDispatch(const IterableConstraint& c, NotNull<const Co
         Anyification anyify{arena, constraint->scope, builtinTypes, &iceReporter, errorRecoveryType(), errorRecoveryTypePack()};
         std::optional<TypePackId> anyified = anyify.substitute(c.variables);
         LUAU_ASSERT(anyified);
-        unify(constraint->scope, constraint->location, *anyified, c.variables);
+        unify(constraint, *anyified, c.variables);
 
         return true;
     }
@@ -1112,6 +1078,26 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
 
     const bool occursCheckPassed = u2.unify(overloadToUse, inferredTy);
 
+    if (!u2.genericSubstitutions.empty() || !u2.genericPackSubstitutions.empty())
+    {
+        Instantiation2 instantiation{arena, std::move(u2.genericSubstitutions), std::move(u2.genericPackSubstitutions)};
+
+        std::optional<TypePackId> subst = instantiation.substitute(result);
+
+        if (!subst)
+        {
+            reportError(CodeTooComplex{}, constraint->location);
+            result = builtinTypes->errorTypePack;
+        }
+        else
+        {
+            result = *subst;
+        }
+
+        if (c.result != result)
+            asMutable(c.result)->ty.emplace<BoundTypePack>(result);
+    }
+
     for (const auto& [expanded, additions] : u2.expandedFreeTypes)
     {
         for (TypeId addition : additions)
@@ -1132,7 +1118,7 @@ bool ConstraintSolver::tryDispatch(const FunctionCallConstraint& c, NotNull<cons
 
 bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<const Constraint> constraint)
 {
-    const TypeId fn = follow(c.fn);
+    TypeId fn = follow(c.fn);
     const TypePackId argsPack = follow(c.argsPack);
 
     if (isBlocked(fn))
@@ -1156,6 +1142,35 @@ bool ConstraintSolver::tryDispatch(const FunctionCheckConstraint& c, NotNull<con
     if (!ftv)
         return true;
 
+    DenseHashMap<TypeId, TypeId> replacements{nullptr};
+    DenseHashMap<TypePackId, TypePackId> replacementPacks{nullptr};
+
+    for (auto generic : ftv->generics)
+        replacements[generic] = builtinTypes->unknownType;
+
+    for (auto genericPack : ftv->genericPacks)
+        replacementPacks[genericPack] = builtinTypes->unknownTypePack;
+
+    // If the type of the function has generics, we don't actually want to push any of the generics themselves
+    // into the argument types as expected types because this creates an unnecessary loop. Instead, we want to
+    // replace these types with `unknown` (and `...unknown`) to keep any structure but not create the cycle.
+    if (!replacements.empty() || !replacementPacks.empty())
+    {
+        Replacer replacer{arena, std::move(replacements), std::move(replacementPacks)};
+
+        std::optional<TypeId> res = replacer.substitute(fn);
+        if (res)
+        {
+            fn = *res;
+            ftv = get<FunctionType>(*res);
+            LUAU_ASSERT(ftv);
+
+            // we've potentially copied type families here, so we need to reproduce their reduce constraint.
+            reproduceConstraints(constraint->scope, constraint->location, replacer);
+        }
+    }
+
+
     const std::vector<TypeId> expectedArgs = flatten(ftv->argTypes).first;
     const std::vector<TypeId> argPackHead = flatten(argsPack).first;
 
@@ -1237,7 +1252,7 @@ bool ConstraintSolver::tryDispatch(const HasPropConstraint& c, NotNull<const Con
     if (isBlocked(subjectType) || get<PendingExpansionType>(subjectType) || get<TypeFamilyInstanceType>(subjectType))
         return block(subjectType, constraint);
 
-    auto [blocked, result] = lookupTableProp(subjectType, c.prop, c.suppressSimplification);
+    auto [blocked, result] = lookupTableProp(subjectType, c.prop, c.context, c.suppressSimplification);
     if (!blocked.empty())
     {
         for (TypeId blocked : blocked)
@@ -1304,7 +1319,7 @@ static void updateTheTableType(
     for (size_t i = 0; i < path.size() - 1; ++i)
     {
         t = follow(t);
-        auto propTy = findTablePropertyRespectingMeta(builtinTypes, dummy, t, path[i], Location{});
+        auto propTy = findTablePropertyRespectingMeta(builtinTypes, dummy, t, path[i], ValueContext::LValue, Location{});
         dummy.clear();
 
         if (!propTy)
@@ -1329,17 +1344,26 @@ static void updateTheTableType(
 bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Constraint> constraint, bool force)
 {
     TypeId subjectType = follow(c.subjectType);
+    const TypeId propType = follow(c.propType);
 
     if (isBlocked(subjectType))
         return block(subjectType, constraint);
 
     std::optional<TypeId> existingPropType = subjectType;
-    for (const std::string& segment : c.path)
+
+    LUAU_ASSERT(!c.path.empty());
+    if (c.path.empty())
+        return false;
+
+    for (size_t i = 0; i < c.path.size(); ++i)
     {
+        const std::string& segment = c.path[i];
         if (!existingPropType)
             break;
 
-        auto [blocked, result] = lookupTableProp(*existingPropType, segment);
+        ValueContext ctx = i == c.path.size() - 1 ? ValueContext::LValue : ValueContext::RValue;
+
+        auto [blocked, result] = lookupTableProp(*existingPropType, segment, ctx);
         if (!blocked.empty())
         {
             for (TypeId blocked : blocked)
@@ -1356,8 +1380,8 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
 
     if (existingPropType)
     {
-        if (!isBlocked(c.propType))
-            unify(constraint->scope, constraint->location, c.propType, *existingPropType);
+        unify(constraint->scope, constraint->location, propType, *existingPropType);
+        unify(constraint->scope, constraint->location, *existingPropType, propType);
         bind(c.resultType, c.subjectType);
         unblock(c.resultType, constraint->location);
         return true;
@@ -1382,7 +1406,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
         {
             LUAU_ASSERT(!subjectType->persistent);
 
-            ttv->props[c.path[0]] = Property{c.propType};
+            ttv->props[c.path[0]] = Property{propType};
             bind(c.resultType, subjectType);
             unblock(c.resultType, constraint->location);
             return true;
@@ -1391,7 +1415,7 @@ bool ConstraintSolver::tryDispatch(const SetPropConstraint& c, NotNull<const Con
         {
             LUAU_ASSERT(!subjectType->persistent);
 
-            updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, c.propType);
+            updateTheTableType(builtinTypes, NotNull{arena}, subjectType, c.path, propType);
         }
     }
 
@@ -1425,7 +1449,7 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
         if (tt->indexer)
         {
             // TODO This probably has to be invariant.
-            unify(constraint->scope, constraint->location, c.indexType, tt->indexer->indexType);
+            unify(constraint, c.indexType, tt->indexer->indexType);
             asMutable(c.propType)->ty.emplace<BoundType>(tt->indexer->indexResultType);
             asMutable(c.resultType)->ty.emplace<BoundType>(subjectType);
             unblock(c.propType, constraint->location);
@@ -1435,7 +1459,7 @@ bool ConstraintSolver::tryDispatch(const SetIndexerConstraint& c, NotNull<const
         else if (tt->state == TableState::Free || tt->state == TableState::Unsealed)
         {
             TypeId promotedIndexTy = freshType(arena, builtinTypes, tt->scope);
-            unify(constraint->scope, constraint->location, c.indexType, promotedIndexTy);
+            unify(constraint, c.indexType, promotedIndexTy);
 
             auto mtt = getMutable<TableType>(subjectType);
             mtt->indexer = TableIndexer{promotedIndexTy, c.propType};
@@ -1486,6 +1510,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
 
     if (isBlocked(resultPack))
     {
+        LUAU_ASSERT(resultPack != sourcePack);
         asMutable(resultPack)->ty.emplace<BoundTypePack>(sourcePack);
         unblock(resultPack, constraint->location);
         return true;
@@ -1525,7 +1550,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
         else
         {
             LUAU_ASSERT(c.resultIsLValue);
-            unify(constraint->scope, constraint->location, resultTy, srcTy);
+            unify(constraint, resultTy, srcTy);
         }
 
         unblock(resultTy, constraint->location);
@@ -1553,7 +1578,7 @@ bool ConstraintSolver::tryDispatch(const UnpackConstraint& c, NotNull<const Cons
                 apply(resultTy, srcTy);
         }
         else
-            unify(constraint->scope, constraint->location, resultTy, srcTy);
+            unify(constraint, resultTy, srcTy);
 
         ++resultIter;
         ++i;
@@ -1616,7 +1641,9 @@ bool ConstraintSolver::tryDispatch(const SetOpConstraint& c, NotNull<const Const
 bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Constraint> constraint, bool force)
 {
     TypeId ty = follow(c.ty);
-    FamilyGraphReductionResult result = reduceFamilies(ty, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope}, force);
+    FamilyGraphReductionResult result =
+        reduceFamilies(ty, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope, constraint}, force);
+
 
     for (TypeId r : result.reducedTypes)
         unblock(r, constraint->location);
@@ -1639,7 +1666,8 @@ bool ConstraintSolver::tryDispatch(const ReduceConstraint& c, NotNull<const Cons
 bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const Constraint> constraint, bool force)
 {
     TypePackId tp = follow(c.tp);
-    FamilyGraphReductionResult result = reduceFamilies(tp, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope}, force);
+    FamilyGraphReductionResult result =
+        reduceFamilies(tp, constraint->location, TypeFamilyContext{NotNull{this}, constraint->scope, constraint}, force);
 
     for (TypeId r : result.reducedTypes)
         unblock(r, constraint->location);
@@ -1659,6 +1687,13 @@ bool ConstraintSolver::tryDispatch(const ReducePackConstraint& c, NotNull<const
     return result.blockedTypes.empty() && result.blockedPacks.empty();
 }
 
+bool ConstraintSolver::tryDispatch(const EqualityConstraint& c, NotNull<const Constraint> constraint, bool force)
+{
+    unify(constraint->scope, constraint->location, c.resultType, c.assignmentType);
+    unify(constraint->scope, constraint->location, c.assignmentType, c.resultType);
+    return true;
+}
+
 bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const IterableConstraint& c, NotNull<const Constraint> constraint, bool force)
 {
     auto block_ = [&](auto&& t) {
@@ -1721,7 +1756,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
         if (iteratorTable->indexer)
         {
             TypePackId expectedVariablePack = arena->addTypePack({iteratorTable->indexer->indexType, iteratorTable->indexer->indexResultType});
-            unify(constraint->scope, constraint->location, c.variables, expectedVariablePack);
+            unify(constraint, c.variables, expectedVariablePack);
 
             auto [variableTys, variablesTail] = flatten(c.variables);
 
@@ -1755,7 +1790,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
             if (auto iterFtv = get<FunctionType>(*instantiatedIterFn))
             {
                 TypePackId expectedIterArgs = arena->addTypePack({iteratorTy});
-                unify(constraint->scope, constraint->location, iterFtv->argTypes, expectedIterArgs);
+                unify(constraint, iterFtv->argTypes, expectedIterArgs);
 
                 TypePack iterRets = extendTypePack(*arena, builtinTypes, iterFtv->retTypes, 2);
 
@@ -1779,7 +1814,7 @@ bool ConstraintSolver::tryDispatchIterableTable(TypeId iteratorTy, const Iterabl
                     const TypePackId nextRetPack = arena->addTypePack(TypePack{{firstIndex}, valueTailTy});
 
                     const TypeId expectedNextTy = arena->addType(FunctionType{nextArgPack, nextRetPack});
-                    unify(constraint->scope, constraint->location, *instantiatedNextFn, expectedNextTy);
+                    unify(constraint, *instantiatedNextFn, expectedNextTy);
 
                     pushConstraint(constraint->scope, constraint->location, UnpackConstraint{c.variables, nextRetPack});
                 }
@@ -1849,13 +1884,11 @@ bool ConstraintSolver::tryDispatchIterableFunction(
     const TypePackId nextRetPack = arena->addTypePack(TypePack{{retIndex}, valueTailTy});
 
     const TypeId expectedNextTy = arena->addType(FunctionType{TypeLevel{}, constraint->scope, nextArgPack, nextRetPack});
-    std::optional<TypeError> error = unify(constraint->scope, constraint->location, nextTy, expectedNextTy);
+    bool ok = unify(constraint, nextTy, expectedNextTy);
 
     // if there are no errors from unifying the two, we can pass forward the expected type as our selected resolution.
-    if (!error)
+    if (ok)
         (*c.astForInNextTypes)[c.nextAstFragment] = expectedNextTy;
-    else
-        reportError(*error);
 
     auto it = begin(nextRetPack);
     std::vector<TypeId> modifiedNextRetHead;
@@ -1882,14 +1915,14 @@ bool ConstraintSolver::tryDispatchIterableFunction(
 }
 
 std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(
-    TypeId subjectType, const std::string& propName, bool suppressSimplification)
+    TypeId subjectType, const std::string& propName, ValueContext context, bool suppressSimplification)
 {
     DenseHashSet<TypeId> seen{nullptr};
-    return lookupTableProp(subjectType, propName, suppressSimplification, seen);
+    return lookupTableProp(subjectType, propName, context, suppressSimplification, seen);
 }
 
 std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTableProp(
-    TypeId subjectType, const std::string& propName, bool suppressSimplification, DenseHashSet<TypeId>& seen)
+    TypeId subjectType, const std::string& propName, ValueContext context, bool suppressSimplification, DenseHashSet<TypeId>& seen)
 {
     if (seen.contains(subjectType))
         return {};
@@ -1906,19 +1939,58 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
     else if (auto ttv = getMutable<TableType>(subjectType))
     {
         if (auto prop = ttv->props.find(propName); prop != ttv->props.end())
-            return {{}, FFlag::DebugLuauReadWriteProperties ? prop->second.readType() : prop->second.type()};
-        else if (ttv->indexer && maybeString(ttv->indexer->indexType))
+        {
+            switch (context)
+            {
+            case ValueContext::RValue:
+                if (auto rt = prop->second.readTy)
+                    return {{}, rt};
+                break;
+            case ValueContext::LValue:
+                if (auto wt = prop->second.writeTy)
+                    return {{}, wt};
+                break;
+            }
+        }
+
+        if (ttv->indexer && maybeString(ttv->indexer->indexType))
             return {{}, ttv->indexer->indexResultType};
-        else if (ttv->state == TableState::Free)
+
+        if (ttv->state == TableState::Free)
         {
             TypeId result = freshType(arena, builtinTypes, ttv->scope);
-            ttv->props[propName] = Property{result};
+            switch (context)
+            {
+            case ValueContext::RValue:
+                ttv->props[propName].readTy = result;
+                break;
+            case ValueContext::LValue:
+                if (auto it = ttv->props.find(propName); it != ttv->props.end() && it->second.isReadOnly())
+                {
+                    // We do infer read-only properties, but we do not infer
+                    // separate read and write types.
+                    //
+                    // If we encounter a case where a free table has a read-only
+                    // property that we subsequently sense a write to, we make
+                    // the judgement that the property is read-write and that
+                    // both the read and write types are the same.
+
+                    Property& prop = it->second;
+
+                    prop.writeTy = prop.readTy;
+                    return {{}, *prop.readTy};
+                }
+                else
+                    ttv->props[propName] = Property::rw(result);
+
+                break;
+            }
             return {{}, result};
         }
     }
     else if (auto mt = get<MetatableType>(subjectType))
     {
-        auto [blocked, result] = lookupTableProp(mt->table, propName, suppressSimplification, seen);
+        auto [blocked, result] = lookupTableProp(mt->table, propName, context, suppressSimplification, seen);
         if (!blocked.empty() || result)
             return {blocked, result};
 
@@ -1949,13 +2021,13 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
                 }
             }
             else
-                return lookupTableProp(indexType, propName, suppressSimplification, seen);
+                return lookupTableProp(indexType, propName, context, suppressSimplification, seen);
         }
     }
     else if (auto ct = get<ClassType>(subjectType))
     {
         if (auto p = lookupClassProp(ct, propName))
-            return {{}, p->type()};
+            return {{}, context == ValueContext::RValue ? p->readTy : p->writeTy};
         if (ct->indexer)
         {
             return {{}, ct->indexer->indexResultType};
@@ -1970,14 +2042,14 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
         if (indexProp == metatable->props.end())
             return {{}, std::nullopt};
 
-        return lookupTableProp(indexProp->second.type(), propName, suppressSimplification, seen);
+        return lookupTableProp(indexProp->second.type(), propName, context, suppressSimplification, seen);
     }
     else if (auto ft = get<FreeType>(subjectType))
     {
         const TypeId upperBound = follow(ft->upperBound);
 
         if (get<TableType>(upperBound) || get<PrimitiveType>(upperBound))
-            return lookupTableProp(upperBound, propName, suppressSimplification, seen);
+            return lookupTableProp(upperBound, propName, context, suppressSimplification, seen);
 
         // TODO: The upper bound could be an intersection that contains suitable tables or classes.
 
@@ -1987,7 +2059,16 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
         TableType* tt = getMutable<TableType>(newUpperBound);
         LUAU_ASSERT(tt);
         TypeId propType = freshType(arena, builtinTypes, scope);
-        tt->props[propName] = Property{propType};
+
+        switch (context)
+        {
+        case ValueContext::RValue:
+            tt->props[propName] = Property::readonly(propType);
+            break;
+        case ValueContext::LValue:
+            tt->props[propName] = Property::rw(propType);
+            break;
+        }
 
         unify(scope, Location{}, subjectType, newUpperBound);
 
@@ -2000,7 +2081,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
 
         for (TypeId ty : utv)
         {
-            auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, suppressSimplification, seen);
+            auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, context, suppressSimplification, seen);
             blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
             if (innerResult)
                 options.insert(*innerResult);
@@ -2029,7 +2110,7 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
 
         for (TypeId ty : itv)
         {
-            auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, suppressSimplification, seen);
+            auto [innerBlocked, innerResult] = lookupTableProp(ty, propName, context, suppressSimplification, seen);
             blocked.insert(blocked.end(), innerBlocked.begin(), innerBlocked.end());
             if (innerResult)
                 options.insert(*innerResult);
@@ -2055,34 +2136,39 @@ std::pair<std::vector<TypeId>, std::optional<TypeId>> ConstraintSolver::lookupTa
     return {{}, std::nullopt};
 }
 
-template<typename TID>
-bool ConstraintSolver::tryUnify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
+template <typename TID>
+bool ConstraintSolver::unify(NotNull<Scope> scope, Location location, TID subType, TID superType)
 {
-    Unifier2 u2{NotNull{arena}, builtinTypes, constraint->scope, NotNull{&iceReporter}};
+    Unifier2 u2{NotNull{arena}, builtinTypes, scope, NotNull{&iceReporter}};
 
-    bool success = u2.unify(subTy, superTy);
+    const bool ok = u2.unify(subType, superType);
 
-    if (success)
+    if (ok)
     {
         for (const auto& [expanded, additions] : u2.expandedFreeTypes)
         {
             for (TypeId addition : additions)
-                upperBoundContributors[expanded].push_back(std::make_pair(constraint->location, addition));
+                upperBoundContributors[expanded].push_back(std::make_pair(location, addition));
         }
     }
     else
     {
-        // Unification only fails when doing so would fail the occurs check.
-        // ie create a self-bound type or a cyclic type pack
-        reportError(OccursCheckFailed{}, constraint->location);
+        reportError(OccursCheckFailed{}, location);
+        return false;
     }
 
-    unblock(subTy, constraint->location);
-    unblock(superTy, constraint->location);
+    unblock(subType, location);
+    unblock(superType, location);
 
     return true;
 }
 
+template<typename TID>
+bool ConstraintSolver::unify(NotNull<const Constraint> constraint, TID subTy, TID superTy)
+{
+    return unify(constraint->scope, constraint->location, subTy, superTy);
+}
+
 void ConstraintSolver::bindBlockedType(TypeId blockedTy, TypeId resultTy, TypeId rootTy, Location location)
 {
     resultTy = follow(resultTy);
@@ -2297,6 +2383,21 @@ void ConstraintSolver::unblock(const std::vector<TypePackId>& packs, Location lo
         unblock(t, location);
 }
 
+void ConstraintSolver::reproduceConstraints(NotNull<Scope> scope, const Location& location, const Substitution& subst)
+{
+    for (auto [_, newTy] : subst.newTypes)
+    {
+        if (get<TypeFamilyInstanceType>(newTy))
+            pushConstraint(scope, location, ReduceConstraint{newTy});
+    }
+
+    for (auto [_, newPack] : subst.newPacks)
+    {
+        if (get<TypeFamilyInstanceTypePack>(newPack))
+            pushConstraint(scope, location, ReducePackConstraint{newPack});
+    }
+}
+
 bool ConstraintSolver::isBlocked(TypeId ty)
 {
     ty = follow(ty);
@@ -2318,39 +2419,6 @@ bool ConstraintSolver::isBlocked(NotNull<const Constraint> constraint)
     return blockedIt != blockedConstraints.end() && blockedIt->second > 0;
 }
 
-std::optional<TypeError> ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypeId subType, TypeId superType)
-{
-    Unifier2 u2{NotNull{arena}, builtinTypes, scope, NotNull{&iceReporter}};
-
-    const bool ok = u2.unify(subType, superType);
-
-    if (!ok)
-        return {{location, UnificationTooComplex{}}};
-
-    unblock(subType, Location{});
-    unblock(superType, Location{});
-
-    return {};
-}
-
-ErrorVec ConstraintSolver::unify(NotNull<Scope> scope, Location location, TypePackId subPack, TypePackId superPack)
-{
-    Unifier2 u{arena, builtinTypes, scope, NotNull{&iceReporter}};
-
-    u.unify(subPack, superPack);
-
-    for (const auto& [expanded, additions] : u.expandedFreeTypes)
-    {
-        for (TypeId addition : additions)
-            upperBoundContributors[expanded].push_back(std::make_pair(location, addition));
-    }
-
-    unblock(subPack, Location{});
-    unblock(superPack, Location{});
-
-    return {};
-}
-
 NotNull<Constraint> ConstraintSolver::pushConstraint(NotNull<Scope> scope, const Location& location, ConstraintV cv)
 {
     std::unique_ptr<Constraint> c = std::make_unique<Constraint>(scope, location, std::move(cv));
diff --git a/Analysis/src/Error.cpp b/Analysis/src/Error.cpp
index ba4e3a35..d1516b09 100644
--- a/Analysis/src/Error.cpp
+++ b/Analysis/src/Error.cpp
@@ -541,11 +541,36 @@ struct ErrorConverter
                "' is used in a way that will run time error";
     }
 
+    std::string operator()(const PropertyAccessViolation& e) const
+    {
+        const std::string stringKey = isIdentifier(e.key) ? e.key : "\"" + e.key + "\"";
+        switch (e.context)
+        {
+        case PropertyAccessViolation::CannotRead:
+            return "Property " + stringKey + " of table '" + toString(e.table) + "' is write-only";
+        case PropertyAccessViolation::CannotWrite:
+            return "Property " + stringKey + " of table '" + toString(e.table) + "' is read-only";
+        }
+
+        LUAU_UNREACHABLE();
+        return "<Invalid PropertyAccessViolation>";
+    }
+
     std::string operator()(const CheckedFunctionIncorrectArgs& e) const
     {
         return "Checked Function " + e.functionName + " expects " + std::to_string(e.expected) + " arguments, but received " +
                std::to_string(e.actual);
     }
+
+    std::string operator()(const UnexpectedTypeInSubtyping& e) const
+    {
+        return "Encountered an unexpected type in subtyping: " + toString(e.ty);
+    }
+
+    std::string operator()(const UnexpectedTypePackInSubtyping& e) const
+    {
+        return "Encountered an unexpected type pack in subtyping: " + toString(e.tp);
+    }
 };
 
 struct InvalidNameChecker
@@ -638,6 +663,11 @@ bool UnknownProperty::operator==(const UnknownProperty& rhs) const
     return *table == *rhs.table && key == rhs.key;
 }
 
+bool PropertyAccessViolation::operator==(const PropertyAccessViolation& rhs) const
+{
+    return *table == *rhs.table && key == rhs.key && context == rhs.context;
+}
+
 bool NotATable::operator==(const NotATable& rhs) const
 {
     return ty == rhs.ty;
@@ -884,6 +914,16 @@ bool CheckedFunctionIncorrectArgs::operator==(const CheckedFunctionIncorrectArgs
     return functionName == rhs.functionName && expected == rhs.expected && actual == rhs.actual;
 }
 
+bool UnexpectedTypeInSubtyping::operator==(const UnexpectedTypeInSubtyping& rhs) const
+{
+    return ty == rhs.ty;
+}
+
+bool UnexpectedTypePackInSubtyping::operator==(const UnexpectedTypePackInSubtyping& rhs) const
+{
+    return tp == rhs.tp;
+}
+
 std::string toString(const TypeError& error)
 {
     return toString(error, TypeErrorToStringOptions{});
@@ -1059,9 +1099,15 @@ void copyError(T& e, TypeArena& destArena, CloneState& cloneState)
     {
         e.argumentType = clone(e.argumentType);
     }
+    else if constexpr (std::is_same_v<T, PropertyAccessViolation>)
+        e.table = clone(e.table);
     else if constexpr (std::is_same_v<T, CheckedFunctionIncorrectArgs>)
     {
     }
+    else if constexpr (std::is_same_v<T, UnexpectedTypeInSubtyping>)
+        e.ty = clone(e.ty);
+    else if constexpr (std::is_same_v<T, UnexpectedTypePackInSubtyping>)
+        e.tp = clone(e.tp);
     else
         static_assert(always_false_v<T>, "Non-exhaustive type switch");
 }
diff --git a/Analysis/src/Frontend.cpp b/Analysis/src/Frontend.cpp
index b0dadd0b..82b78149 100644
--- a/Analysis/src/Frontend.cpp
+++ b/Analysis/src/Frontend.cpp
@@ -32,9 +32,8 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
 LUAU_FASTINT(LuauTarjanChildLimit)
 LUAU_FASTFLAG(LuauInferInNoCheckMode)
 LUAU_FASTFLAGVARIABLE(LuauKnowsTheDataModel3, false)
-LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
 LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJson, false)
-LUAU_FASTFLAGVARIABLE(DebugLuauReadWriteProperties, false)
 LUAU_FASTFLAGVARIABLE(DebugLuauLogSolverToJsonFile, false)
 
 namespace Luau
@@ -1219,6 +1218,15 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
         result->cancelled = true;
     }
 
+    if (recordJsonLog)
+    {
+        std::string output = logger->compileOutput();
+        if (FFlag::DebugLuauLogSolverToJsonFile && writeJsonLog)
+            writeJsonLog(sourceModule.name, std::move(output));
+        else
+            printf("%s\n", output.c_str());
+    }
+
     for (TypeError& e : cs.errors)
         result->errors.emplace_back(std::move(e));
 
@@ -1263,15 +1271,6 @@ ModulePtr check(const SourceModule& sourceModule, Mode mode, const std::vector<R
     freeze(result->internalTypes);
     freeze(result->interfaceTypes);
 
-    if (recordJsonLog)
-    {
-        std::string output = logger->compileOutput();
-        if (FFlag::DebugLuauLogSolverToJsonFile && writeJsonLog)
-            writeJsonLog(sourceModule.name, std::move(output));
-        else
-            printf("%s\n", output.c_str());
-    }
-
     return result;
 }
 
diff --git a/Analysis/src/Instantiation.cpp b/Analysis/src/Instantiation.cpp
index 235786a8..525319c6 100644
--- a/Analysis/src/Instantiation.cpp
+++ b/Analysis/src/Instantiation.cpp
@@ -2,6 +2,7 @@
 #include "Luau/Instantiation.h"
 
 #include "Luau/Common.h"
+#include "Luau/Instantiation2.h" // including for `Replacer` which was stolen since it will be kept in the new solver
 #include "Luau/ToString.h"
 #include "Luau/TxnLog.h"
 #include "Luau/TypeArena.h"
@@ -143,39 +144,6 @@ TypePackId ReplaceGenerics::clean(TypePackId tp)
     return addTypePack(TypePackVar(FreeTypePack{scope, level}));
 }
 
-struct Replacer : Substitution
-{
-    DenseHashMap<TypeId, TypeId> replacements;
-    DenseHashMap<TypePackId, TypePackId> replacementPacks;
-
-    Replacer(NotNull<TypeArena> arena, DenseHashMap<TypeId, TypeId> replacements, DenseHashMap<TypePackId, TypePackId> replacementPacks)
-        : Substitution(TxnLog::empty(), arena)
-        , replacements(std::move(replacements))
-        , replacementPacks(std::move(replacementPacks))
-    {
-    }
-
-    bool isDirty(TypeId ty) override
-    {
-        return replacements.find(ty) != nullptr;
-    }
-
-    bool isDirty(TypePackId tp) override
-    {
-        return replacementPacks.find(tp) != nullptr;
-    }
-
-    TypeId clean(TypeId ty) override
-    {
-        return replacements[ty];
-    }
-
-    TypePackId clean(TypePackId tp) override
-    {
-        return replacementPacks[tp];
-    }
-};
-
 std::optional<TypeId> instantiate(
     NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, NotNull<TypeCheckLimits> limits, NotNull<Scope> scope, TypeId ty)
 {
diff --git a/Analysis/src/Instantiation2.cpp b/Analysis/src/Instantiation2.cpp
new file mode 100644
index 00000000..31f27f8e
--- /dev/null
+++ b/Analysis/src/Instantiation2.cpp
@@ -0,0 +1,38 @@
+// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
+#include "Luau/Instantiation2.h"
+
+namespace Luau
+{
+
+bool Instantiation2::isDirty(TypeId ty)
+{
+    return get<GenericType>(ty) && genericSubstitutions.contains(ty);
+}
+
+bool Instantiation2::isDirty(TypePackId tp)
+{
+    return get<GenericTypePack>(tp) && genericPackSubstitutions.contains(tp);
+}
+
+TypeId Instantiation2::clean(TypeId ty)
+{
+    TypeId substTy = genericSubstitutions[ty];
+    const FreeType* ft = get<FreeType>(substTy);
+
+    // violation of the substitution invariant if this is not a free type.
+    LUAU_ASSERT(ft);
+
+    // if we didn't learn anything about the lower bound, we pick the upper bound instead.
+    if (get<NeverType>(ft->lowerBound))
+        return ft->upperBound;
+
+    // we default to the lower bound which represents the most specific type for the free type.
+    return ft->lowerBound;
+}
+
+TypePackId Instantiation2::clean(TypePackId tp)
+{
+    return genericPackSubstitutions[tp];
+}
+
+} // namespace Luau
diff --git a/Analysis/src/IostreamHelpers.cpp b/Analysis/src/IostreamHelpers.cpp
index 72090848..54afd5d6 100644
--- a/Analysis/src/IostreamHelpers.cpp
+++ b/Analysis/src/IostreamHelpers.cpp
@@ -207,9 +207,15 @@ static void errorToString(std::ostream& stream, const T& err)
     else if constexpr (std::is_same_v<T, NonStrictFunctionDefinitionError>)
         stream << "NonStrictFunctionDefinitionError { functionName = '" + err.functionName + "', argument = '" + err.argument +
                       "', argumentType = '" + toString(err.argumentType) + "' }";
+    else if constexpr (std::is_same_v<T, PropertyAccessViolation>)
+        stream << "PropertyAccessViolation { table = " << toString(err.table) << ", prop = '" << err.key << "', context = " << err.context << " }";
     else if constexpr (std::is_same_v<T, CheckedFunctionIncorrectArgs>)
         stream << "CheckedFunction {  functionName = '" + err.functionName + ", expected = " + std::to_string(err.expected) +
                       ", actual = " + std::to_string(err.actual) + "}";
+    else if constexpr (std::is_same_v<T, UnexpectedTypeInSubtyping>)
+        stream << "UnexpectedTypeInSubtyping {  ty = '" + toString(err.ty) + "' }";
+    else if constexpr (std::is_same_v<T, UnexpectedTypePackInSubtyping>)
+        stream << "UnexpectedTypePackInSubtyping {  tp = '" + toString(err.tp) + "' }";
     else
         static_assert(always_false_v<T>, "Non-exhaustive type switch");
 }
diff --git a/Analysis/src/Linter.cpp b/Analysis/src/Linter.cpp
index 79a7ef56..d79361c0 100644
--- a/Analysis/src/Linter.cpp
+++ b/Analysis/src/Linter.cpp
@@ -14,6 +14,8 @@
 
 LUAU_FASTINTVARIABLE(LuauSuggestionDistance, 4)
 
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
+
 namespace Luau
 {
 
@@ -1848,6 +1850,49 @@ private:
 
     bool visit(AstTypeTable* node) override
     {
+        if (FFlag::DebugLuauDeferredConstraintResolution)
+        {
+            struct Rec
+            {
+                AstTableAccess access;
+                Location location;
+            };
+            DenseHashMap<AstName, Rec> names(AstName{});
+
+            for (const AstTableProp& item : node->props)
+            {
+                Rec* rec = names.find(item.name);
+                if (!rec)
+                {
+                    names[item.name] = Rec{item.access, item.location};
+                    continue;
+                }
+
+                if (int(rec->access) & int(item.access))
+                {
+                    if (rec->access == item.access)
+                        emitWarning(*context, LintWarning::Code_TableLiteral, item.location,
+                            "Table type field '%s' is a duplicate; previously defined at line %d", item.name.value, rec->location.begin.line + 1);
+                    else if (rec->access == AstTableAccess::ReadWrite)
+                        emitWarning(*context, LintWarning::Code_TableLiteral, item.location,
+                            "Table type field '%s' is already read-write; previously defined at line %d", item.name.value,
+                            rec->location.begin.line + 1);
+                    else if (rec->access == AstTableAccess::Read)
+                        emitWarning(*context, LintWarning::Code_TableLiteral, rec->location,
+                            "Table type field '%s' already has a read type defined at line %d", item.name.value, rec->location.begin.line + 1);
+                    else if (rec->access == AstTableAccess::Write)
+                        emitWarning(*context, LintWarning::Code_TableLiteral, rec->location,
+                            "Table type field '%s' already has a write type defined at line %d", item.name.value, rec->location.begin.line + 1);
+                    else
+                        LUAU_ASSERT(!"Unreachable");
+                }
+                else
+                    rec->access = AstTableAccess(int(rec->access) | int(item.access));
+            }
+
+            return true;
+        }
+
         DenseHashMap<AstName, int> names(AstName{});
 
         for (const AstTableProp& item : node->props)
diff --git a/Analysis/src/Normalize.cpp b/Analysis/src/Normalize.cpp
index ee80cc99..62aeb145 100644
--- a/Analysis/src/Normalize.cpp
+++ b/Analysis/src/Normalize.cpp
@@ -9,6 +9,7 @@
 #include "Luau/Common.h"
 #include "Luau/RecursionCounter.h"
 #include "Luau/Set.h"
+#include "Luau/Simplify.h"
 #include "Luau/Subtyping.h"
 #include "Luau/Type.h"
 #include "Luau/TypeFwd.h"
@@ -20,7 +21,6 @@ LUAU_FASTFLAGVARIABLE(DebugLuauCheckNormalizeInvariant, false)
 LUAU_FASTINTVARIABLE(LuauNormalizeIterationLimit, 1200);
 LUAU_FASTINTVARIABLE(LuauNormalizeCacheLimit, 100000);
 LUAU_FASTFLAG(LuauTransitiveSubtyping)
-LUAU_FASTFLAG(DebugLuauReadWriteProperties)
 LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
 
 namespace Luau
@@ -472,11 +472,11 @@ bool Normalizer::isInhabited(TypeId ty, Set<TypeId>& seen)
     {
         for (const auto& [_, prop] : ttv->props)
         {
-            if (FFlag::DebugLuauReadWriteProperties)
+            if (FFlag::DebugLuauDeferredConstraintResolution)
             {
                 // A table enclosing a read property whose type is uninhabitable is also itself uninhabitable,
                 // but not its write property. That just means the write property doesn't exist, and so is readonly.
-                if (auto ty = prop.readType(); ty && !isInhabited(*ty, seen))
+                if (auto ty = prop.readTy; ty && !isInhabited(*ty, seen))
                     return false;
             }
             else
@@ -2349,12 +2349,61 @@ std::optional<TypeId> Normalizer::intersectionOfTables(TypeId here, TypeId there
         {
             const auto& [_name, tprop] = *tfound;
             // TODO: variance issues here, which can't be fixed until we have read/write property types
-            prop.setType(intersectionType(hprop.type(), tprop.type()));
-            hereSubThere &= (prop.type() == hprop.type());
-            thereSubHere &= (prop.type() == tprop.type());
+            if (FFlag::DebugLuauDeferredConstraintResolution)
+            {
+                if (hprop.readTy.has_value())
+                {
+                    if (tprop.readTy.has_value())
+                    {
+                        TypeId ty = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.readTy, *tprop.readTy).result;
+                        prop.readTy = ty;
+                        hereSubThere &= (ty == hprop.readTy);
+                        thereSubHere &= (ty == tprop.readTy);
+                    }
+                    else
+                    {
+                        prop.readTy = *hprop.readTy;
+                        thereSubHere = false;
+                    }
+                }
+                else if (tprop.readTy.has_value())
+                {
+                    prop.readTy = *tprop.readTy;
+                    hereSubThere = false;
+                }
+
+                if (hprop.writeTy.has_value())
+                {
+                    if (tprop.writeTy.has_value())
+                    {
+                        prop.writeTy = simplifyIntersection(builtinTypes, NotNull{arena}, *hprop.writeTy, *tprop.writeTy).result;
+                        hereSubThere &= (prop.writeTy == hprop.writeTy);
+                        thereSubHere &= (prop.writeTy == tprop.writeTy);
+                    }
+                    else
+                    {
+                        prop.writeTy = *hprop.writeTy;
+                        thereSubHere = false;
+                    }
+                }
+                else if (tprop.writeTy.has_value())
+                {
+                    prop.writeTy = *tprop.writeTy;
+                    hereSubThere = false;
+                }
+            }
+            else
+            {
+                prop.setType(intersectionType(hprop.type(), tprop.type()));
+                hereSubThere &= (prop.type() == hprop.type());
+                thereSubHere &= (prop.type() == tprop.type());
+            }
         }
+
         // TODO: string indexers
-        result.props[name] = prop;
+
+        if (prop.readTy || prop.writeTy)
+            result.props[name] = prop;
     }
 
     for (const auto& [name, tprop] : tttv->props)
@@ -2431,8 +2480,10 @@ void Normalizer::intersectTablesWithTable(TypeIds& heres, TypeId there)
 {
     TypeIds tmp;
     for (TypeId here : heres)
+    {
         if (std::optional<TypeId> inter = intersectionOfTables(here, there))
             tmp.insert(*inter);
+    }
     heres.retain(tmp);
     heres.insert(tmp.begin(), tmp.end());
 }
@@ -2441,9 +2492,14 @@ void Normalizer::intersectTables(TypeIds& heres, const TypeIds& theres)
 {
     TypeIds tmp;
     for (TypeId here : heres)
+    {
         for (TypeId there : theres)
+        {
             if (std::optional<TypeId> inter = intersectionOfTables(here, there))
                 tmp.insert(*inter);
+        }
+    }
+
     heres.retain(tmp);
     heres.insert(tmp.begin(), tmp.end());
 }
diff --git a/Analysis/src/Simplify.cpp b/Analysis/src/Simplify.cpp
index d04aeb82..dcfc1965 100644
--- a/Analysis/src/Simplify.cpp
+++ b/Analysis/src/Simplify.cpp
@@ -3,10 +3,10 @@
 #include "Luau/Simplify.h"
 
 #include "Luau/DenseHash.h"
-#include "Luau/Normalize.h" // TypeIds
 #include "Luau/RecursionCounter.h"
-#include "Luau/ToString.h"
+#include "Luau/Set.h"
 #include "Luau/TypeArena.h"
+#include "Luau/TypePairHash.h"
 #include "Luau/TypeUtils.h"
 
 #include <algorithm>
@@ -17,6 +17,8 @@ LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution)
 namespace Luau
 {
 
+using SimplifierSeenSet = Set<std::pair<TypeId, TypeId>, TypePairHash>;
+
 struct TypeSimplifier
 {
     NotNull<BuiltinTypes> builtinTypes;
@@ -226,23 +228,27 @@ static bool isTypeVariable(TypeId ty)
     return get<FreeType>(ty) || get<GenericType>(ty) || get<BlockedType>(ty) || get<PendingExpansionType>(ty);
 }
 
-Relation relate(TypeId left, TypeId right);
+Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen);
 
-Relation relateTables(TypeId left, TypeId right)
+Relation relateTables(TypeId left, TypeId right, SimplifierSeenSet& seen)
 {
     NotNull<const TableType> leftTable{get<TableType>(left)};
     NotNull<const TableType> rightTable{get<TableType>(right)};
     LUAU_ASSERT(1 == rightTable->props.size());
     // Disjoint props have nothing in common
     // t1 with props p1's cannot appear in t2 and t2 with props p2's cannot appear in t1
-    bool foundPropFromLeftInRight = std::any_of(begin(leftTable->props), end(leftTable->props), [&](auto prop) {
-        return rightTable->props.find(prop.first) != end(rightTable->props);
-    });
-    bool foundPropFromRightInLeft = std::any_of(begin(rightTable->props), end(rightTable->props), [&](auto prop) {
-        return leftTable->props.find(prop.first) != end(leftTable->props);
-    });
+    bool foundPropFromLeftInRight = std::any_of(begin(leftTable->props), end(leftTable->props),
+        [&](auto prop)
+        {
+            return rightTable->props.count(prop.first) > 0;
+        });
+    bool foundPropFromRightInLeft = std::any_of(begin(rightTable->props), end(rightTable->props),
+        [&](auto prop)
+        {
+            return leftTable->props.count(prop.first) > 0;
+        });
 
-    if (!(foundPropFromLeftInRight || foundPropFromRightInLeft) && leftTable->props.size() >= 1 && rightTable->props.size() >= 1)
+    if (!foundPropFromLeftInRight && !foundPropFromRightInLeft && leftTable->props.size() >= 1 && rightTable->props.size() >= 1)
         return Relation::Disjoint;
 
     const auto [propName, rightProp] = *begin(rightTable->props);
@@ -257,7 +263,10 @@ Relation relateTables(TypeId left, TypeId right)
 
     const Property leftProp = it->second;
 
-    Relation r = relate(leftProp.type(), rightProp.type());
+    if (!leftProp.isShared() || !rightProp.isShared())
+        return Relation::Intersects;
+
+    Relation r = relate(leftProp.type(), rightProp.type(), seen);
     if (r == Relation::Coincident && 1 != leftTable->props.size())
     {
         // eg {tag: "cat", prop: string} & {tag: "cat"}
@@ -268,7 +277,7 @@ Relation relateTables(TypeId left, TypeId right)
 }
 
 // A cheap and approximate subtype test
-Relation relate(TypeId left, TypeId right)
+Relation relate(TypeId left, TypeId right, SimplifierSeenSet& seen)
 {
     // TODO nice to have: Relate functions of equal argument and return arity
 
@@ -278,6 +287,14 @@ Relation relate(TypeId left, TypeId right)
     if (left == right)
         return Relation::Coincident;
 
+    std::pair<TypeId, TypeId> typePair{left, right};
+    if (!seen.insert(typePair))
+    {
+        // TODO: is this right at all?
+        // The thinking here is that this is a cycle if we get here, and therefore its coincident.
+        return Relation::Coincident;
+    }
+
     if (get<UnknownType>(left))
     {
         if (get<AnyType>(right))
@@ -291,7 +308,7 @@ Relation relate(TypeId left, TypeId right)
     }
 
     if (get<UnknownType>(right))
-        return flip(relate(right, left));
+        return flip(relate(right, left, seen));
 
     if (get<AnyType>(left))
     {
@@ -302,7 +319,7 @@ Relation relate(TypeId left, TypeId right)
     }
 
     if (get<AnyType>(right))
-        return flip(relate(right, left));
+        return flip(relate(right, left, seen));
 
     // Type variables
     // * FreeType
@@ -340,7 +357,7 @@ Relation relate(TypeId left, TypeId right)
             return Relation::Disjoint;
     }
     if (get<ErrorType>(right))
-        return flip(relate(right, left));
+        return flip(relate(right, left, seen));
 
     if (get<NeverType>(left))
     {
@@ -350,7 +367,7 @@ Relation relate(TypeId left, TypeId right)
             return Relation::Subset;
     }
     if (get<NeverType>(right))
-        return flip(relate(right, left));
+        return flip(relate(right, left, seen));
 
     if (auto ut = get<IntersectionType>(left))
         return Relation::Intersects;
@@ -363,14 +380,14 @@ Relation relate(TypeId left, TypeId right)
     {
         std::vector<Relation> opts;
         for (TypeId part : ut)
-            if (relate(left, part) == Relation::Subset)
+            if (relate(left, part, seen) == Relation::Subset)
                 return Relation::Subset;
         return Relation::Intersects;
     }
 
     if (auto rnt = get<NegationType>(right))
     {
-        Relation a = relate(left, rnt->ty);
+        Relation a = relate(left, rnt->ty, seen);
         switch (a)
         {
         case Relation::Coincident:
@@ -401,7 +418,7 @@ Relation relate(TypeId left, TypeId right)
         }
     }
     else if (get<NegationType>(left))
-        return flip(relate(right, left));
+        return flip(relate(right, left, seen));
 
     if (auto lp = get<PrimitiveType>(left))
     {
@@ -448,7 +465,7 @@ Relation relate(TypeId left, TypeId right)
             return Relation::Disjoint;
 
         if (get<PrimitiveType>(right))
-            return flip(relate(right, left));
+            return flip(relate(right, left, seen));
         if (auto rs = get<SingletonType>(right))
         {
             if (ls->variant == rs->variant)
@@ -485,7 +502,7 @@ Relation relate(TypeId left, TypeId right)
             // TODO PROBABLY indexers and metatables.
             if (1 == rt->props.size())
             {
-                Relation r = relateTables(left, right);
+                Relation r = relateTables(left, right, seen);
                 /*
                  * A reduction of these intersections is certainly possible, but
                  * it would require minting new table types. Also, I don't think
@@ -504,7 +521,7 @@ Relation relate(TypeId left, TypeId right)
                     return r;
             }
             else if (1 == lt->props.size())
-                return flip(relate(right, left));
+                return flip(relate(right, left, seen));
             else
                 return Relation::Intersects;
         }
@@ -531,6 +548,13 @@ Relation relate(TypeId left, TypeId right)
     return Relation::Intersects;
 }
 
+// A cheap and approximate subtype test
+Relation relate(TypeId left, TypeId right)
+{
+    SimplifierSeenSet seen{{}};
+    return relate(left, right, seen);
+}
+
 TypeId TypeSimplifier::mkNegation(TypeId ty)
 {
     TypeId result = nullptr;
@@ -1056,7 +1080,7 @@ std::optional<TypeId> TypeSimplifier::basicIntersect(TypeId left, TypeId right)
             const auto [propName, leftProp] = *begin(lt->props);
 
             auto it = rt->props.find(propName);
-            if (it != rt->props.end())
+            if (it != rt->props.end() && leftProp.isShared() && it->second.isShared())
             {
                 Relation r = relate(leftProp.type(), it->second.type());
 
@@ -1266,9 +1290,12 @@ TypeId TypeSimplifier::simplify(TypeId ty, DenseHashSet<TypeId>& seen)
     {
         if (1 == tt->props.size())
         {
-            TypeId propTy = simplify(begin(tt->props)->second.type(), seen);
-            if (get<NeverType>(propTy))
-                return builtinTypes->neverType;
+            if (std::optional<TypeId> readTy = begin(tt->props)->second.readTy)
+            {
+                TypeId propTy = simplify(*readTy, seen);
+                if (get<NeverType>(propTy))
+                    return builtinTypes->neverType;
+            }
         }
     }
 
diff --git a/Analysis/src/Substitution.cpp b/Analysis/src/Substitution.cpp
index 7da9aee8..0deba9bd 100644
--- a/Analysis/src/Substitution.cpp
+++ b/Analysis/src/Substitution.cpp
@@ -9,7 +9,7 @@
 #include <stdexcept>
 
 LUAU_FASTINTVARIABLE(LuauTarjanChildLimit, 10000)
-LUAU_FASTFLAG(DebugLuauReadWriteProperties)
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
 LUAU_FASTFLAGVARIABLE(LuauPreallocateTarjanVectors, false);
 LUAU_FASTINTVARIABLE(LuauTarjanPreallocationSize, 256);
 
@@ -185,10 +185,10 @@ void Tarjan::visitChildren(TypeId ty, int index)
         LUAU_ASSERT(!ttv->boundTo);
         for (const auto& [name, prop] : ttv->props)
         {
-            if (FFlag::DebugLuauReadWriteProperties)
+            if (FFlag::DebugLuauDeferredConstraintResolution)
             {
-                visitChild(prop.readType());
-                visitChild(prop.writeType());
+                visitChild(prop.readTy);
+                visitChild(prop.writeTy);
             }
             else
                 visitChild(prop.type());
@@ -700,8 +700,8 @@ void Substitution::replaceChildren(TypeId ty)
         LUAU_ASSERT(!ttv->boundTo);
         for (auto& [name, prop] : ttv->props)
         {
-            if (FFlag::DebugLuauReadWriteProperties)
-                prop = Property::create(replace(prop.readType()), replace(prop.writeType()));
+            if (FFlag::DebugLuauDeferredConstraintResolution)
+                prop = Property::create(replace(prop.readTy), replace(prop.writeTy));
             else
                 prop.setType(replace(prop.type()));
         }
diff --git a/Analysis/src/Subtyping.cpp b/Analysis/src/Subtyping.cpp
index 3c7c32f1..6abf9a3b 100644
--- a/Analysis/src/Subtyping.cpp
+++ b/Analysis/src/Subtyping.cpp
@@ -7,7 +7,9 @@
 #include "Luau/Normalize.h"
 #include "Luau/Scope.h"
 #include "Luau/StringUtils.h"
+#include "Luau/Substitution.h"
 #include "Luau/ToString.h"
+#include "Luau/TxnLog.h"
 #include "Luau/Type.h"
 #include "Luau/TypeArena.h"
 #include "Luau/TypeCheckLimits.h"
@@ -217,7 +219,14 @@ SubtypingResult& SubtypingResult::withSuperPath(TypePath::Path path)
 
 SubtypingResult& SubtypingResult::withErrors(ErrorVec& err)
 {
-    errors = std::move(err);
+    for (TypeError& e : err)
+        errors.emplace_back(e);
+    return *this;
+}
+
+SubtypingResult& SubtypingResult::withError(TypeError err)
+{
+    errors.push_back(std::move(err));
     return *this;
 }
 
@@ -245,6 +254,74 @@ SubtypingResult SubtypingResult::any(const std::vector<SubtypingResult>& results
     return acc;
 }
 
+struct ApplyMappedGenerics : Substitution
+{
+    using MappedGenerics = DenseHashMap<TypeId, SubtypingEnvironment::GenericBounds>;
+    using MappedGenericPacks = DenseHashMap<TypePackId, TypePackId>;
+
+    NotNull<BuiltinTypes> builtinTypes;
+    NotNull<TypeArena> arena;
+
+    MappedGenerics& mappedGenerics;
+    MappedGenericPacks& mappedGenericPacks;
+
+
+    ApplyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, MappedGenerics& mappedGenerics, MappedGenericPacks& mappedGenericPacks)
+        : Substitution(TxnLog::empty(), arena)
+        , builtinTypes(builtinTypes)
+        , arena(arena)
+        , mappedGenerics(mappedGenerics)
+        , mappedGenericPacks(mappedGenericPacks)
+    {
+    }
+
+    bool isDirty(TypeId ty) override
+    {
+        return mappedGenerics.contains(ty);
+    }
+
+    bool isDirty(TypePackId tp) override
+    {
+        return mappedGenericPacks.contains(tp);
+    }
+
+    TypeId clean(TypeId ty) override
+    {
+        const auto& bounds = mappedGenerics[ty];
+
+        if (bounds.upperBound.empty())
+            return builtinTypes->unknownType;
+
+        if (bounds.upperBound.size() == 1)
+            return *begin(bounds.upperBound);
+
+        return arena->addType(IntersectionType{std::vector<TypeId>(begin(bounds.upperBound), end(bounds.upperBound))});
+    }
+
+    TypePackId clean(TypePackId tp) override
+    {
+        return mappedGenericPacks[tp];
+    }
+
+    bool ignoreChildren(TypeId ty) override
+    {
+        if (get<ClassType>(ty))
+            return true;
+
+        return ty->persistent;
+    }
+    bool ignoreChildren(TypePackId ty) override
+    {
+        return ty->persistent;
+    }
+};
+
+std::optional<TypeId> SubtypingEnvironment::applyMappedGenerics(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> arena, TypeId ty)
+{
+    ApplyMappedGenerics amg{builtinTypes, arena, mappedGenerics, mappedGenericPacks};
+    return amg.substitute(ty);
+}
+
 Subtyping::Subtyping(NotNull<BuiltinTypes> builtinTypes, NotNull<TypeArena> typeArena, NotNull<Normalizer> normalizer,
     NotNull<InternalErrorReporter> iceReporter, NotNull<Scope> scope)
     : builtinTypes(builtinTypes)
@@ -493,9 +570,19 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypeId sub
         }
     }
     else if (auto subTypeFamilyInstance = get<TypeFamilyInstanceType>(subTy))
+    {
+        if (auto substSubTy = env.applyMappedGenerics(builtinTypes, arena, subTy))
+            subTypeFamilyInstance = get<TypeFamilyInstanceType>(*substSubTy);
+
         result = isCovariantWith(env, subTypeFamilyInstance, superTy);
+    }
     else if (auto superTypeFamilyInstance = get<TypeFamilyInstanceType>(superTy))
+    {
+        if (auto substSuperTy = env.applyMappedGenerics(builtinTypes, arena, superTy))
+            superTypeFamilyInstance = get<TypeFamilyInstanceType>(*substSuperTy);
+
         result = isCovariantWith(env, subTy, superTypeFamilyInstance);
+    }
     else if (auto subGeneric = get<GenericType>(subTy); subGeneric && variance == Variance::Covariant)
     {
         bool ok = bindGeneric(env, subTy, superTy);
@@ -604,7 +691,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
             else if (get<ErrorTypePack>(*subTail))
                 return SubtypingResult{true}.withSubComponent(TypePath::PackField::Tail);
             else
-                unexpected(*subTail);
+                return SubtypingResult{false}
+                    .withSubComponent(TypePath::PackField::Tail)
+                    .withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
         }
         else
         {
@@ -656,7 +745,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
             else if (get<ErrorTypePack>(*superTail))
                 return SubtypingResult{true}.withSuperComponent(TypePath::PackField::Tail);
             else
-                unexpected(*superTail);
+                return SubtypingResult{false}
+                    .withSuperComponent(TypePath::PackField::Tail)
+                    .withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
         }
         else
             return {false};
@@ -717,8 +808,10 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
             // error type is fine on either side
             results.push_back(SubtypingResult{true}.withBothComponent(TypePath::PackField::Tail));
         else
-            iceReporter->ice(
-                format("Subtyping::isSubtype got unexpected type packs %s and %s", toString(*subTail).c_str(), toString(*superTail).c_str()));
+            return SubtypingResult{false}
+                .withBothComponent(TypePath::PackField::Tail)
+                .withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}})
+                .withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
     }
     else if (subTail)
     {
@@ -732,7 +825,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
             return SubtypingResult{ok}.withSubComponent(TypePath::PackField::Tail);
         }
         else
-            unexpected(*subTail);
+            return SubtypingResult{false}
+                .withSubComponent(TypePath::PackField::Tail)
+                .withError({scope->location, UnexpectedTypePackInSubtyping{*subTail}});
     }
     else if (superTail)
     {
@@ -759,7 +854,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, TypePackId
                 results.push_back(SubtypingResult{false}.withSuperComponent(TypePath::PackField::Tail));
         }
         else
-            iceReporter->ice("Subtyping test encountered the unexpected type pack: " + toString(*superTail));
+            return SubtypingResult{false}
+                .withSuperComponent(TypePath::PackField::Tail)
+                .withError({scope->location, UnexpectedTypePackInSubtyping{*superTail}});
     }
 
     SubtypingResult result = SubtypingResult::all(results);
@@ -1126,18 +1223,32 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
     if (subTable->props.empty() && !subTable->indexer && superTable->indexer)
         return {false};
 
-    for (const auto& [name, prop] : superTable->props)
+    for (const auto& [name, superProp] : superTable->props)
     {
         std::vector<SubtypingResult> results;
-        if (auto it = subTable->props.find(name); it != subTable->props.end())
-            results.push_back(isInvariantWith(env, it->second.type(), prop.type()).withBothComponent(TypePath::Property(name)));
+        if (auto subIter = subTable->props.find(name); subIter != subTable->props.end())
+            results.push_back(isCovariantWith(env, subIter->second, superProp, name));
 
         if (subTable->indexer)
         {
-            if (isInvariantWith(env, subTable->indexer->indexType, builtinTypes->stringType).isSubtype)
-                results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, prop.type())
-                                      .withSubComponent(TypePath::TypeField::IndexResult)
-                                      .withSuperComponent(TypePath::Property(name)));
+            if (isCovariantWith(env, builtinTypes->stringType, subTable->indexer->indexType).isSubtype)
+            {
+                if (superProp.isShared())
+                    results.push_back(isInvariantWith(env, subTable->indexer->indexResultType, superProp.type())
+                        .withSubComponent(TypePath::TypeField::IndexResult)
+                        .withSuperComponent(TypePath::Property::read(name)));
+                else
+                {
+                    if (superProp.readTy)
+                        results.push_back(isCovariantWith(env, subTable->indexer->indexResultType, *superProp.readTy)
+                            .withSubComponent(TypePath::TypeField::IndexResult)
+                            .withSuperComponent(TypePath::Property::read(name)));
+                    if (superProp.writeTy)
+                        results.push_back(isContravariantWith(env, subTable->indexer->indexResultType, *superProp.writeTy)
+                            .withSubComponent(TypePath::TypeField::IndexResult)
+                            .withSuperComponent(TypePath::Property::write(name)));
+                }
+            }
         }
 
         if (results.empty())
@@ -1197,7 +1308,9 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Clas
     for (const auto& [name, prop] : superTable->props)
     {
         if (auto classProp = lookupClassProp(subClass, name))
-            result.andAlso(isInvariantWith(env, prop.type(), classProp->type()).withBothComponent(TypePath::Property(name)));
+        {
+            result.andAlso(isCovariantWith(env, *classProp, prop, name));
+        }
         else
             return SubtypingResult{false};
     }
@@ -1230,7 +1343,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Prim
                 {
                     if (auto stringTable = get<TableType>(it->second.type()))
                         result.orElse(
-                            isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().prop("__index").build()));
+                            isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
                 }
             }
         }
@@ -1252,7 +1365,7 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Sing
                 {
                     if (auto stringTable = get<TableType>(it->second.type()))
                         result.orElse(
-                            isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().prop("__index").build()));
+                            isCovariantWith(env, stringTable, superTable).withSubPath(TypePath::PathBuilder().mt().readProp("__index").build()));
                 }
             }
         }
@@ -1267,6 +1380,31 @@ SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Tabl
         .andAlso(isInvariantWith(env, subIndexer.indexResultType, superIndexer.indexResultType).withBothComponent(TypePath::TypeField::IndexResult));
 }
 
+SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const Property& subProp, const Property& superProp, const std::string& name)
+{
+    SubtypingResult res{true};
+
+    if (superProp.isShared() && subProp.isShared())
+        res.andAlso(isInvariantWith(env, subProp.type(), superProp.type()).withBothComponent(TypePath::Property::read(name)));
+    else
+    {
+        if (superProp.readTy.has_value() && subProp.readTy.has_value())
+            res.andAlso(isCovariantWith(env, *subProp.readTy, *superProp.readTy).withBothComponent(TypePath::Property::read(name)));
+        if (superProp.writeTy.has_value() && subProp.writeTy.has_value())
+            res.andAlso(isContravariantWith(env, *subProp.writeTy, *superProp.writeTy).withBothComponent(TypePath::Property::write(name)));
+
+        if (superProp.isReadWrite())
+        {
+            if (subProp.isReadOnly())
+                res.andAlso(SubtypingResult{false}.withBothComponent(TypePath::Property::read(name)));
+            else if (subProp.isWriteOnly())
+                res.andAlso(SubtypingResult{false}.withBothComponent(TypePath::Property::write(name)));
+        }
+    }
+
+    return res;
+}
+
 SubtypingResult Subtyping::isCovariantWith(SubtypingEnvironment& env, const NormalizedType* subNorm, const NormalizedType* superNorm)
 {
     if (!subNorm || !superNorm)
@@ -1473,14 +1611,4 @@ TypeId Subtyping::makeAggregateType(const Container& container, TypeId orElse)
         return arena->addType(T{std::vector<TypeId>(begin(container), end(container))});
 }
 
-void Subtyping::unexpected(TypeId ty)
-{
-    iceReporter->ice(format("Unexpected type %s", toString(ty).c_str()));
-}
-
-void Subtyping::unexpected(TypePackId tp)
-{
-    iceReporter->ice(format("Unexpected type pack %s", toString(tp).c_str()));
-}
-
 } // namespace Luau
diff --git a/Analysis/src/ToString.cpp b/Analysis/src/ToString.cpp
index 2e204926..e26ed138 100644
--- a/Analysis/src/ToString.cpp
+++ b/Analysis/src/ToString.cpp
@@ -374,7 +374,7 @@ struct TypeStringifier
             tv->ty);
     }
 
-    void stringify(const std::string& name, const Property& prop)
+    void emitKey(const std::string& name)
     {
         if (isIdentifier(name))
             state.emit(name);
@@ -385,31 +385,46 @@ struct TypeStringifier
             state.emit("\"]");
         }
         state.emit(": ");
+    }
 
-        if (FFlag::DebugLuauReadWriteProperties)
+    void _newStringify(const std::string& name, const Property& prop)
+    {
+        bool comma = false;
+        if (prop.isShared())
         {
-            // We special case the stringification if the property's read and write types are shared.
-            if (prop.isShared())
-                return stringify(*prop.readType());
-
-            // Otherwise emit them separately.
-            if (auto ty = prop.readType())
-            {
-                state.emit("read ");
-                stringify(*ty);
-            }
-
-            if (prop.readType() && prop.writeType())
-                state.emit(" + ");
-
-            if (auto ty = prop.writeType())
-            {
-                state.emit("write ");
-                stringify(*ty);
-            }
-        }
-        else
+            emitKey(name);
             stringify(prop.type());
+            return;
+        }
+
+        if (prop.readTy)
+        {
+            state.emit("read ");
+            emitKey(name);
+            stringify(*prop.readTy);
+            comma = true;
+        }
+        if (prop.writeTy)
+        {
+            if (comma)
+            {
+                state.emit(",");
+                state.newline();
+            }
+
+            state.emit("write ");
+            emitKey(name);
+            stringify(*prop.writeTy);
+        }
+    }
+
+    void stringify(const std::string& name, const Property& prop)
+    {
+        if (FFlag::DebugLuauDeferredConstraintResolution)
+            return _newStringify(name, prop);
+
+        emitKey(name);
+        stringify(prop.type());
     }
 
     void stringify(TypePackId tp);
@@ -1755,7 +1770,7 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
         }
         else if constexpr (std::is_same_v<T, HasPropConstraint>)
         {
-            return tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\"";
+            return tos(c.resultType) + " ~ hasProp " + tos(c.subjectType) + ", \"" + c.prop + "\" ctx=" + std::to_string(int(c.context));
         }
         else if constexpr (std::is_same_v<T, SetPropConstraint>)
         {
@@ -1801,6 +1816,8 @@ std::string toString(const Constraint& constraint, ToStringOptions& opts)
         {
             return "reduce " + tos(c.tp);
         }
+        else if constexpr (std::is_same_v<T, EqualityConstraint>)
+            return "equality: " + tos(c.resultType) + " ~ " + tos(c.assignmentType);
         else
             static_assert(always_false_v<T>, "Non-exhaustive constraint switch");
     };
diff --git a/Analysis/src/Type.cpp b/Analysis/src/Type.cpp
index 71a07d73..d8044c16 100644
--- a/Analysis/src/Type.cpp
+++ b/Analysis/src/Type.cpp
@@ -26,7 +26,6 @@ LUAU_FASTINTVARIABLE(LuauTypeMaximumStringifierLength, 500)
 LUAU_FASTINTVARIABLE(LuauTableTypeMaximumStringifierLength, 0)
 LUAU_FASTINT(LuauTypeInferRecursionLimit)
 LUAU_FASTFLAG(LuauInstantiateInSubtyping)
-LUAU_FASTFLAG(DebugLuauReadWriteProperties)
 
 namespace Luau
 {
@@ -632,13 +631,10 @@ Property::Property(TypeId readTy, bool deprecated, const std::string& deprecated
     , readTy(readTy)
     , writeTy(readTy)
 {
-    LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
 }
 
 Property Property::readonly(TypeId ty)
 {
-    LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
-
     Property p;
     p.readTy = ty;
     return p;
@@ -646,8 +642,6 @@ Property Property::readonly(TypeId ty)
 
 Property Property::writeonly(TypeId ty)
 {
-    LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
-
     Property p;
     p.writeTy = ty;
     return p;
@@ -660,8 +654,6 @@ Property Property::rw(TypeId ty)
 
 Property Property::rw(TypeId read, TypeId write)
 {
-    LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
-
     Property p;
     p.readTy = read;
     p.writeTy = write;
@@ -683,29 +675,15 @@ Property Property::create(std::optional<TypeId> read, std::optional<TypeId> writ
 
 TypeId Property::type() const
 {
-    LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
     LUAU_ASSERT(readTy);
     return *readTy;
 }
 
 void Property::setType(TypeId ty)
 {
-    LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
     readTy = ty;
-}
-
-std::optional<TypeId> Property::readType() const
-{
-    LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
-    LUAU_ASSERT(!(bool(readTy) && bool(writeTy)));
-    return readTy;
-}
-
-std::optional<TypeId> Property::writeType() const
-{
-    LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
-    LUAU_ASSERT(!(bool(readTy) && bool(writeTy)));
-    return writeTy;
+    if (FFlag::DebugLuauDeferredConstraintResolution)
+        writeTy = ty;
 }
 
 bool Property::isShared() const
@@ -713,6 +691,21 @@ bool Property::isShared() const
     return readTy && writeTy && readTy == writeTy;
 }
 
+bool Property::isReadOnly() const
+{
+    return readTy && !writeTy;
+}
+
+bool Property::isWriteOnly() const
+{
+    return !readTy && writeTy;
+}
+
+bool Property::isReadWrite() const
+{
+    return readTy && writeTy;
+}
+
 TableType::TableType(TableState state, TypeLevel level, Scope* scope)
     : state(state)
     , level(level)
@@ -961,6 +954,7 @@ BuiltinTypes::BuiltinTypes()
     , optionalStringType(arena->addType(Type{UnionType{{stringType, nilType}}, /*persistent*/ true}))
     , emptyTypePack(arena->addTypePack(TypePackVar{TypePack{{}}, /*persistent*/ true}))
     , anyTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{anyType}, /*persistent*/ true}))
+    , unknownTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{unknownType}, /*persistent*/ true}))
     , neverTypePack(arena->addTypePack(TypePackVar{VariadicTypePack{neverType}, /*persistent*/ true}))
     , uninhabitableTypePack(arena->addTypePack(TypePackVar{TypePack{{neverType}, neverTypePack}, /*persistent*/ true}))
     , errorTypePack(arena->addTypePack(TypePackVar{Unifiable::Error{}, /*persistent*/ true}))
diff --git a/Analysis/src/TypeChecker2.cpp b/Analysis/src/TypeChecker2.cpp
index 5411bf24..853e46ff 100644
--- a/Analysis/src/TypeChecker2.cpp
+++ b/Analysis/src/TypeChecker2.cpp
@@ -1460,7 +1460,7 @@ struct TypeChecker2
     {
         visit(expr, ValueContext::RValue);
         TypeId leftType = stripFromNilAndReport(lookupType(expr), location);
-        checkIndexTypeFromType(leftType, propName, location, context, astIndexExprTy);
+        checkIndexTypeFromType(leftType, propName, context, location, astIndexExprTy);
     }
 
     void visit(AstExprIndexName* indexName, ValueContext context)
@@ -1709,8 +1709,8 @@ struct TypeChecker2
 
     TypeId visit(AstExprBinary* expr, AstNode* overrideKey = nullptr)
     {
-        visit(expr->left, ValueContext::LValue);
-        visit(expr->right, ValueContext::LValue);
+        visit(expr->left, ValueContext::RValue);
+        visit(expr->right, ValueContext::RValue);
 
         NotNull<Scope> scope = stack.back();
 
@@ -2534,20 +2534,16 @@ struct TypeChecker2
             reportError(std::move(e));
     }
 
-    // If the provided type does not have the named property, report an error.
-    void checkIndexTypeFromType(TypeId tableTy, const std::string& prop, const Location& location, ValueContext context, TypeId astIndexExprType)
+    /* A helper for checkIndexTypeFromType.
+     *
+     * Returns a pair:
+     * * A boolean indicating that at least one of the constituent types
+     *     contains the prop, and
+     * * A vector of types that do not contain the prop.
+     */
+    std::pair<bool, std::vector<TypeId>> lookupProp(const NormalizedType* norm, const std::string& prop, ValueContext context,
+        const Location& location, TypeId astIndexExprType, std::vector<TypeError>& errors)
     {
-        const NormalizedType* norm = normalizer.normalize(tableTy);
-        if (!norm)
-        {
-            reportError(NormalizationTooComplex{}, location);
-            return;
-        }
-
-        // if the type is error suppressing, we don't actually have any work left to do.
-        if (norm->shouldSuppressErrors())
-            return;
-
         bool foundOneProp = false;
         std::vector<TypeId> typesMissingTheProp;
 
@@ -2556,7 +2552,7 @@ struct TypeChecker2
                 return;
 
             DenseHashSet<TypeId> seen{nullptr};
-            bool found = hasIndexTypeFromType(ty, prop, location, seen, astIndexExprType);
+            bool found = hasIndexTypeFromType(ty, prop, context, location, seen, astIndexExprType, errors);
             foundOneProp |= found;
             if (!found)
                 typesMissingTheProp.push_back(ty);
@@ -2601,6 +2597,26 @@ struct TypeChecker2
                 fetch(tyvar);
         }
 
+        return {foundOneProp, typesMissingTheProp};
+    }
+
+    // If the provided type does not have the named property, report an error.
+    void checkIndexTypeFromType(TypeId tableTy, const std::string& prop, ValueContext context, const Location& location, TypeId astIndexExprType)
+    {
+        const NormalizedType* norm = normalizer.normalize(tableTy);
+        if (!norm)
+        {
+            reportError(NormalizationTooComplex{}, location);
+            return;
+        }
+
+        // if the type is error suppressing, we don't actually have any work left to do.
+        if (norm->shouldSuppressErrors())
+            return;
+
+        std::vector<TypeError> dummy;
+        const auto [foundOneProp, typesMissingTheProp] = lookupProp(norm, prop, context, location, astIndexExprType, module->errors);
+
         if (!typesMissingTheProp.empty())
         {
             if (foundOneProp)
@@ -2611,17 +2627,29 @@ struct TypeChecker2
             // the `else` branch.
             else if (context == ValueContext::LValue && !get<ClassType>(tableTy))
             {
-                if (get<PrimitiveType>(tableTy) || get<FunctionType>(tableTy))
+                const auto [lvFoundOneProp, lvTypesMissingTheProp] = lookupProp(norm, prop, ValueContext::RValue, location, astIndexExprType, dummy);
+                if (lvFoundOneProp && lvTypesMissingTheProp.empty())
+                    reportError(PropertyAccessViolation{tableTy, prop, PropertyAccessViolation::CannotWrite}, location);
+                else if (get<PrimitiveType>(tableTy) || get<FunctionType>(tableTy))
                     reportError(NotATable{tableTy}, location);
                 else
                     reportError(CannotExtendTable{tableTy, CannotExtendTable::Property, prop}, location);
             }
+            else if (context == ValueContext::RValue && !get<ClassType>(tableTy))
+            {
+                const auto [rvFoundOneProp, rvTypesMissingTheProp] = lookupProp(norm, prop, ValueContext::LValue, location, astIndexExprType, dummy);
+                if (rvFoundOneProp && rvTypesMissingTheProp.empty())
+                    reportError(PropertyAccessViolation{tableTy, prop, PropertyAccessViolation::CannotRead}, location);
+                else
+                    reportError(UnknownProperty{tableTy, prop}, location);
+            }
             else
                 reportError(UnknownProperty{tableTy, prop}, location);
         }
     }
 
-    bool hasIndexTypeFromType(TypeId ty, const std::string& prop, const Location& location, DenseHashSet<TypeId>& seen, TypeId astIndexExprType)
+    bool hasIndexTypeFromType(TypeId ty, const std::string& prop, ValueContext context, const Location& location, DenseHashSet<TypeId>& seen,
+        TypeId astIndexExprType, std::vector<TypeError>& errors)
     {
         // If we have already encountered this type, we must assume that some
         // other codepath will do the right thing and signal false if the
@@ -2635,14 +2663,14 @@ struct TypeChecker2
 
         if (isString(ty))
         {
-            std::optional<TypeId> mtIndex = Luau::findMetatableEntry(builtinTypes, module->errors, builtinTypes->stringType, "__index", location);
+            std::optional<TypeId> mtIndex = Luau::findMetatableEntry(builtinTypes, errors, builtinTypes->stringType, "__index", location);
             LUAU_ASSERT(mtIndex);
             ty = *mtIndex;
         }
 
         if (auto tt = getTableType(ty))
         {
-            if (findTablePropertyRespectingMeta(builtinTypes, module->errors, ty, prop, location))
+            if (findTablePropertyRespectingMeta(builtinTypes, errors, ty, prop, context, location))
                 return true;
 
             if (tt->indexer)
@@ -2674,11 +2702,11 @@ struct TypeChecker2
         }
         else if (const UnionType* utv = get<UnionType>(ty))
             return std::all_of(begin(utv), end(utv), [&](TypeId part) {
-                return hasIndexTypeFromType(part, prop, location, seen, astIndexExprType);
+                return hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors);
             });
         else if (const IntersectionType* itv = get<IntersectionType>(ty))
             return std::any_of(begin(itv), end(itv), [&](TypeId part) {
-                return hasIndexTypeFromType(part, prop, location, seen, astIndexExprType);
+                return hasIndexTypeFromType(part, prop, context, location, seen, astIndexExprType, errors);
             });
         else
             return false;
diff --git a/Analysis/src/TypeFamily.cpp b/Analysis/src/TypeFamily.cpp
index 9d28e7da..5456c423 100644
--- a/Analysis/src/TypeFamily.cpp
+++ b/Analysis/src/TypeFamily.cpp
@@ -7,6 +7,7 @@
 #include "Luau/DenseHash.h"
 #include "Luau/Instantiation.h"
 #include "Luau/Normalize.h"
+#include "Luau/NotNull.h"
 #include "Luau/Simplify.h"
 #include "Luau/Substitution.h"
 #include "Luau/Subtyping.h"
@@ -853,6 +854,27 @@ static TypeFamilyReductionResult<TypeId> comparisonFamilyFn(TypeId instance, con
     TypeId lhsTy = follow(typeParams.at(0));
     TypeId rhsTy = follow(typeParams.at(1));
 
+    // Algebra Reduction Rules for comparison family functions
+    // Note that comparing to never tells you nothing about the other operand
+    // lt< 'a , never> -> continue
+    // lt< never, 'a>  -> continue
+    // lt< 'a, t>      -> 'a is t - we'll solve the constraint, return and solve lt<t, t> -> bool
+    // lt< t, 'a>      -> same as above
+    bool canSubmitConstraint = ctx->solver && ctx->constraint;
+    if (canSubmitConstraint)
+    {
+        if (get<FreeType>(lhsTy) && get<NeverType>(rhsTy) == nullptr)
+        {
+            auto c1 = ctx->solver->pushConstraint(ctx->scope, {}, EqualityConstraint{lhsTy, rhsTy});
+            const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
+        }
+        else if (get<FreeType>(rhsTy) && get<NeverType>(lhsTy) == nullptr)
+        {
+            auto c1 = ctx->solver->pushConstraint(ctx->scope, {}, EqualityConstraint{rhsTy, lhsTy});
+            const_cast<Constraint*>(ctx->constraint)->dependencies.emplace_back(c1);
+        }
+    }
+
     // check to see if both operand types are resolved enough, and wait to reduce if not
     if (isPending(lhsTy, ctx->solver))
         return {std::nullopt, false, {lhsTy}, {}};
@@ -1248,8 +1270,8 @@ TypeFamilyReductionResult<TypeId> keyofFamilyImpl(
     if (!normTy)
         return {std::nullopt, false, {}, {}};
 
-    // if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes as
-    // well)
+    // if we don't have either just tables or just classes, we've got nothing to get keys of (at least until a future version perhaps adds classes
+    // as well)
     if (normTy->hasTables() == normTy->hasClasses())
         return {std::nullopt, true, {}, {}};
 
diff --git a/Analysis/src/TypePath.cpp b/Analysis/src/TypePath.cpp
index fb4d68cb..50507263 100644
--- a/Analysis/src/TypePath.cpp
+++ b/Analysis/src/TypePath.cpp
@@ -13,7 +13,7 @@
 #include <sstream>
 #include <type_traits>
 
-LUAU_FASTFLAG(DebugLuauReadWriteProperties);
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
 
 // Maximum number of steps to follow when traversing a path. May not always
 // equate to the number of components in a path, depending on the traversal
@@ -29,7 +29,7 @@ namespace TypePath
 Property::Property(std::string name)
     : name(std::move(name))
 {
-    LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
+    LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
 }
 
 Property Property::read(std::string name)
@@ -146,21 +146,21 @@ Path PathBuilder::build()
 
 PathBuilder& PathBuilder::readProp(std::string name)
 {
-    LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
+    LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
     components.push_back(Property{std::move(name), true});
     return *this;
 }
 
 PathBuilder& PathBuilder::writeProp(std::string name)
 {
-    LUAU_ASSERT(FFlag::DebugLuauReadWriteProperties);
+    LUAU_ASSERT(FFlag::DebugLuauDeferredConstraintResolution);
     components.push_back(Property{std::move(name), false});
     return *this;
 }
 
 PathBuilder& PathBuilder::prop(std::string name)
 {
-    LUAU_ASSERT(!FFlag::DebugLuauReadWriteProperties);
+    LUAU_ASSERT(!FFlag::DebugLuauDeferredConstraintResolution);
     components.push_back(Property{std::move(name)});
     return *this;
 }
@@ -323,7 +323,7 @@ struct TraversalState
                 // logic there.
                 updateCurrent(*m);
 
-                if (!traverse(TypePath::Property{"__index"}))
+                if (!traverse(TypePath::Property::read("__index")))
                     return false;
 
                 return traverse(property);
@@ -333,8 +333,8 @@ struct TraversalState
         if (prop)
         {
             std::optional<TypeId> maybeType;
-            if (FFlag::DebugLuauReadWriteProperties)
-                maybeType = property.isRead ? prop->readType() : prop->writeType();
+            if (FFlag::DebugLuauDeferredConstraintResolution)
+                maybeType = property.isRead ? prop->readTy : prop->writeTy;
             else
                 maybeType = prop->type();
 
@@ -514,7 +514,7 @@ std::string toString(const TypePath::Path& path, bool prefixDot)
         if constexpr (std::is_same_v<T, TypePath::Property>)
         {
             result << '[';
-            if (FFlag::DebugLuauReadWriteProperties)
+            if (FFlag::DebugLuauDeferredConstraintResolution)
             {
                 if (c.isRead)
                     result << "read ";
diff --git a/Analysis/src/TypeUtils.cpp b/Analysis/src/TypeUtils.cpp
index f746f620..795324f4 100644
--- a/Analysis/src/TypeUtils.cpp
+++ b/Analysis/src/TypeUtils.cpp
@@ -44,6 +44,12 @@ std::optional<TypeId> findMetatableEntry(
 
 std::optional<TypeId> findTablePropertyRespectingMeta(
     NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, Location location)
+{
+    return findTablePropertyRespectingMeta(builtinTypes, errors, ty, name, ValueContext::RValue, location);
+}
+
+std::optional<TypeId> findTablePropertyRespectingMeta(
+    NotNull<BuiltinTypes> builtinTypes, ErrorVec& errors, TypeId ty, const std::string& name, ValueContext context, Location location)
 {
     if (get<AnyType>(ty))
         return ty;
@@ -52,7 +58,20 @@ std::optional<TypeId> findTablePropertyRespectingMeta(
     {
         const auto& it = tableType->props.find(name);
         if (it != tableType->props.end())
-            return it->second.type();
+        {
+            if (FFlag::DebugLuauDeferredConstraintResolution)
+            {
+                switch (context)
+                {
+                case ValueContext::RValue:
+                    return it->second.readTy;
+                case ValueContext::LValue:
+                    return it->second.writeTy;
+                }
+            }
+            else
+                return it->second.type();
+        }
     }
 
     std::optional<TypeId> mtIndex = findMetatableEntry(builtinTypes, errors, ty, "__index", location);
diff --git a/Analysis/src/Unifier2.cpp b/Analysis/src/Unifier2.cpp
index 5d3dc864..02e2bf67 100644
--- a/Analysis/src/Unifier2.cpp
+++ b/Analysis/src/Unifier2.cpp
@@ -8,6 +8,7 @@
 #include "Luau/Type.h"
 #include "Luau/TypeArena.h"
 #include "Luau/TypeCheckLimits.h"
+#include "Luau/TypeFamily.h"
 #include "Luau/TypeUtils.h"
 #include "Luau/VisitType.h"
 
@@ -19,6 +20,59 @@ LUAU_FASTINT(LuauTypeInferRecursionLimit)
 namespace Luau
 {
 
+static bool areCompatible(TypeId left, TypeId right)
+{
+    auto p = get2<TableType, TableType>(follow(left), follow(right));
+    if (!p)
+        return true;
+
+    const TableType* leftTable = p.first;
+    LUAU_ASSERT(leftTable);
+    const TableType* rightTable = p.second;
+    LUAU_ASSERT(rightTable);
+
+    const auto missingPropIsCompatible = [](const Property& leftProp, const TableType* rightTable) {
+        // Two tables may be compatible even if their shapes aren't exactly the
+        // same if the extra property is optional, free (and therefore
+        // potentially optional), or if the right table has an indexer.  Or if
+        // the right table is free (and therefore potentially has an indexer or
+        // a compatible property)
+
+        LUAU_ASSERT(leftProp.isReadOnly() || leftProp.isShared());
+
+        const TypeId leftType = follow(
+            leftProp.isReadOnly() ? *leftProp.readTy : leftProp.type()
+        );
+
+        if (isOptional(leftType) || get<FreeType>(leftType) || rightTable->state == TableState::Free || rightTable->indexer.has_value())
+            return true;
+
+        return false;
+    };
+
+    for (const auto& [name, leftProp]: leftTable->props)
+    {
+        auto it = rightTable->props.find(name);
+        if (it == rightTable->props.end())
+        {
+            if (!missingPropIsCompatible(leftProp, rightTable))
+                return false;
+        }
+    }
+
+    for (const auto& [name, rightProp]: rightTable->props)
+    {
+        auto it = leftTable->props.find(name);
+        if (it == leftTable->props.end())
+        {
+            if (!missingPropIsCompatible(rightProp, leftTable))
+                return false;
+        }
+    }
+
+    return true;
+}
+
 Unifier2::Unifier2(NotNull<TypeArena> arena, NotNull<BuiltinTypes> builtinTypes, NotNull<Scope> scope, NotNull<InternalErrorReporter> ice)
     : arena(arena)
     , builtinTypes(builtinTypes)
@@ -34,6 +88,12 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
     subTy = follow(subTy);
     superTy = follow(superTy);
 
+    if (auto subGen = genericSubstitutions.find(subTy))
+        return unify(*subGen, superTy);
+
+    if (auto superGen = genericSubstitutions.find(superTy))
+        return unify(subTy, *superGen);
+
     if (seenTypePairings.contains({subTy, superTy}))
         return true;
     seenTypePairings.insert({subTy, superTy});
@@ -44,14 +104,21 @@ bool Unifier2::unify(TypeId subTy, TypeId superTy)
     FreeType* subFree = getMutable<FreeType>(subTy);
     FreeType* superFree = getMutable<FreeType>(superTy);
 
-    if (subFree)
+    if (subFree && superFree)
+    {
+        superFree->lowerBound = mkUnion(subFree->lowerBound, superFree->lowerBound);
+        superFree->upperBound = mkIntersection(subFree->upperBound, superFree->upperBound);
+        asMutable(subTy)->ty.emplace<BoundType>(superTy);
+    }
+    else if (subFree)
     {
         subFree->upperBound = mkIntersection(subFree->upperBound, superTy);
         expandedFreeTypes[subTy].push_back(superTy);
     }
-
-    if (superFree)
+    else if (superFree)
+    {
         superFree->lowerBound = mkUnion(superFree->lowerBound, subTy);
+    }
 
     if (subFree || superFree)
         return true;
@@ -159,13 +226,11 @@ bool Unifier2::unify(TypeId subTy, const FunctionType* superFn)
 
     if (shouldInstantiate)
     {
-        std::optional<TypeId> instantiated = instantiate(builtinTypes, arena, NotNull{&limits}, scope, subTy);
-        if (!instantiated)
-            return false;
+        for (auto generic : subFn->generics)
+            genericSubstitutions[generic] = freshType(arena, builtinTypes, scope);
 
-        subFn = get<FunctionType>(*instantiated);
-
-        LUAU_ASSERT(subFn); // instantiation should not make a function type _not_ a function type.
+        for (auto genericPack : subFn->genericPacks)
+            genericPackSubstitutions[genericPack] = arena->freshTypePack(scope);
     }
 
     bool argResult = unify(superFn->argTypes, subFn->argTypes);
@@ -179,7 +244,10 @@ bool Unifier2::unify(const UnionType* subUnion, TypeId superTy)
 
     // if the occurs check fails for any option, it fails overall
     for (auto subOption : subUnion->options)
-        result &= unify(subOption, superTy);
+    {
+        if (areCompatible(subOption, superTy))
+            result &= unify(subOption, superTy);
+    }
 
     return result;
 }
@@ -190,7 +258,10 @@ bool Unifier2::unify(TypeId subTy, const UnionType* superUnion)
 
     // if the occurs check fails for any option, it fails overall
     for (auto superOption : superUnion->options)
-        result &= unify(subTy, superOption);
+    {
+        if (areCompatible(subTy, superOption))
+            result &= unify(subTy, superOption);
+    }
 
     return result;
 }
@@ -228,7 +299,21 @@ bool Unifier2::unify(TableType* subTable, const TableType* superTable)
         auto superPropOpt = superTable->props.find(propName);
 
         if (superPropOpt != superTable->props.end())
-            result &= unify(subProp.type(), superPropOpt->second.type());
+        {
+            const Property& superProp = superPropOpt->second;
+
+            if (subProp.isReadOnly() && superProp.isReadOnly())
+                result &= unify(*subProp.readTy, *superPropOpt->second.readTy);
+            else if (subProp.isReadOnly())
+                result &= unify(*subProp.readTy, superProp.type());
+            else if (superProp.isReadOnly())
+                result &= unify(subProp.type(), *superProp.readTy);
+            else
+            {
+                result &= unify(subProp.type(), superProp.type());
+                result &= unify(superProp.type(), subProp.type());
+            }
+        }
     }
 
     auto subTypeParamsIter = subTable->instantiatedTypeParams.begin();
@@ -293,10 +378,19 @@ bool Unifier2::unify(TypePackId subTp, TypePackId superTp)
     subTp = follow(subTp);
     superTp = follow(superTp);
 
+    if (auto subGen = genericPackSubstitutions.find(subTp))
+        return unify(*subGen, superTp);
+
+    if (auto superGen = genericPackSubstitutions.find(superTp))
+        return unify(subTp, *superGen);
+
     if (seenTypePackPairings.contains({subTp, superTp}))
         return true;
     seenTypePackPairings.insert({subTp, superTp});
 
+    if (subTp == superTp)
+        return true;
+
     const FreeTypePack* subFree = get<FreeTypePack>(subTp);
     const FreeTypePack* superFree = get<FreeTypePack>(superTp);
 
@@ -378,11 +472,14 @@ struct FreeTypeSearcher : TypeVisitor
     {
     }
 
-    enum
+    enum Polarity
     {
         Positive,
-        Negative
-    } polarity = Positive;
+        Negative,
+        Both,
+    };
+
+    Polarity polarity = Positive;
 
     void flip()
     {
@@ -394,6 +491,8 @@ struct FreeTypeSearcher : TypeVisitor
         case Negative:
             polarity = Positive;
             break;
+        case Both:
+            break;
         }
     }
 
@@ -419,6 +518,10 @@ struct FreeTypeSearcher : TypeVisitor
         case Negative:
             negativeTypes[ty]++;
             break;
+        case Both:
+            positiveTypes[ty]++;
+            negativeTypes[ty]++;
+            break;
         }
 
         return true;
@@ -436,10 +539,35 @@ struct FreeTypeSearcher : TypeVisitor
             case Negative:
                 negativeTypes[ty]++;
                 break;
+            case Both:
+                positiveTypes[ty]++;
+                negativeTypes[ty]++;
+                break;
             }
         }
 
-        return true;
+        for (const auto& [_name, prop] : tt.props)
+        {
+            if (prop.isReadOnly())
+                traverse(*prop.readTy);
+            else
+            {
+                LUAU_ASSERT(prop.isShared());
+
+                Polarity p = polarity;
+                polarity = Both;
+                traverse(prop.type());
+                polarity = p;
+            }
+        }
+
+        if (tt.indexer)
+        {
+            traverse(tt.indexer->indexType);
+            traverse(tt.indexer->indexResultType);
+        }
+
+        return false;
     }
 
     bool visit(TypeId ty, const FunctionType& ft) override
@@ -538,8 +666,8 @@ struct MutatingGeneralizer : TypeOnceVisitor
         if (!ft)
             return false;
 
-        const bool positiveCount = getCount(positiveTypes, ty);
-        const bool negativeCount = getCount(negativeTypes, ty);
+        const size_t positiveCount = getCount(positiveTypes, ty);
+        const size_t negativeCount = getCount(negativeTypes, ty);
 
         if (!positiveCount && !negativeCount)
             return false;
diff --git a/Ast/src/Parser.cpp b/Ast/src/Parser.cpp
index fa1ab61e..a7363552 100644
--- a/Ast/src/Parser.cpp
+++ b/Ast/src/Parser.cpp
@@ -18,6 +18,7 @@ LUAU_FASTINTVARIABLE(LuauParseErrorLimit, 100)
 // See docs/SyntaxChanges.md for an explanation.
 LUAU_FASTFLAG(LuauCheckedFunctionSyntax)
 LUAU_FASTFLAGVARIABLE(LuauReadWritePropertySyntax, false)
+LUAU_FASTFLAGVARIABLE(DebugLuauDeferredConstraintResolution, false)
 
 namespace Luau
 {
@@ -1339,7 +1340,7 @@ AstType* Parser::parseTableType(bool inDeclarationContext)
         AstTableAccess access = AstTableAccess::ReadWrite;
         std::optional<Location> accessLocation;
 
-        if (FFlag::LuauReadWritePropertySyntax)
+        if (FFlag::LuauReadWritePropertySyntax || FFlag::DebugLuauDeferredConstraintResolution)
         {
             if (lexer.current().type == Lexeme::Name && lexer.lookahead().type != ':')
             {
diff --git a/CLI/FileUtils.cpp b/CLI/FileUtils.cpp
index 548d09b9..2e17bcb5 100644
--- a/CLI/FileUtils.cpp
+++ b/CLI/FileUtils.cpp
@@ -431,6 +431,10 @@ std::vector<std::string> getSourceFiles(int argc, char** argv)
 
     for (int i = 1; i < argc; ++i)
     {
+        // Early out once we reach --program-args,-a since the remaining args are passed to lua
+        if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
+            return files;
+
         // Treat '-' as a special file whose source is read from stdin
         // All other arguments that start with '-' are skipped
         if (argv[i][0] == '-' && argv[i][1] != '\0')
diff --git a/CLI/Reduce.cpp b/CLI/Reduce.cpp
index 721bb51c..4b41fc76 100644
--- a/CLI/Reduce.cpp
+++ b/CLI/Reduce.cpp
@@ -152,7 +152,7 @@ struct Reducer
         }
 
 #if VERBOSE >= 1
-        printf("running %s\n", command.c_str());
+        printf("running %s\n", cmd.c_str());
 #endif
 
         TestResult result = TestResult::NoBug;
@@ -160,7 +160,7 @@ struct Reducer
         ++step;
         printf("Step %4d...\n", step);
 
-        FILE* p = popen(command.c_str(), "r");
+        FILE* p = popen(cmd.c_str(), "r");
 
         while (!feof(p))
         {
diff --git a/CLI/Repl.cpp b/CLI/Repl.cpp
index 2e5cb065..84e4a654 100644
--- a/CLI/Repl.cpp
+++ b/CLI/Repl.cpp
@@ -46,6 +46,8 @@ LUAU_FASTFLAGVARIABLE(LuauUpdatedRequireByStringSemantics, false)
 constexpr int MaxTraversalLimit = 50;
 
 static bool codegen = false;
+static int program_argc = 0;
+char** program_argv = nullptr;
 
 // Ctrl-C handling
 static void sigintCallback(lua_State* L, int gc)
@@ -318,6 +320,12 @@ void setupState(lua_State* L)
     luaL_sandbox(L);
 }
 
+void setupArguments(lua_State* L, int argc, char** argv)
+{
+    for (int i = 0; i < argc; ++i)
+        lua_pushstring(L, argv[i]);
+}
+
 std::string runCode(lua_State* L, const std::string& source)
 {
     std::string bytecode = Luau::compile(source, copts());
@@ -668,7 +676,8 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
         if (coverageActive())
             coverageTrack(L, -1);
 
-        status = lua_resume(L, NULL, 0);
+        setupArguments(L, program_argc, program_argv);
+        status = lua_resume(L, NULL, program_argc);
     }
     else
     {
@@ -704,7 +713,7 @@ static bool runFile(const char* name, lua_State* GL, bool repl)
 
 static void displayHelp(const char* argv0)
 {
-    printf("Usage: %s [options] [file list]\n", argv0);
+    printf("Usage: %s [options] [file list] [-a] [arg list]\n", argv0);
     printf("\n");
     printf("When file list is omitted, an interactive REPL is started instead.\n");
     printf("\n");
@@ -717,6 +726,7 @@ static void displayHelp(const char* argv0)
     printf("  --profile[=N]: profile the code using N Hz sampling (default 10000) and output results to profile.out\n");
     printf("  --timetrace: record compiler time tracing information into trace.json\n");
     printf("  --codegen: execute code using native code generation\n");
+    printf("  --program-args,-a: declare start of arguments to be passed to the Luau program");
 }
 
 static int assertionHandler(const char* expr, const char* file, int line, const char* function)
@@ -739,6 +749,7 @@ int replMain(int argc, char** argv)
     bool coverage = false;
     bool interactive = false;
     bool codegenPerf = false;
+    int program_args = argc;
 
     for (int i = 1; i < argc; i++)
     {
@@ -800,6 +811,11 @@ int replMain(int argc, char** argv)
         {
             setLuauFlags(argv[i] + 9);
         }
+        else if (strcmp(argv[i], "--program-args") == 0 || strcmp(argv[i], "-a") == 0)
+        {
+            program_args = i + 1;
+            break;
+        }
         else if (argv[i][0] == '-')
         {
             fprintf(stderr, "Error: Unrecognized option '%s'.\n\n", argv[i]);
@@ -808,6 +824,10 @@ int replMain(int argc, char** argv)
         }
     }
 
+    program_argc = argc - program_args;
+    program_argv = &argv[program_args];
+
+
 #if !defined(LUAU_ENABLE_TIME_TRACE)
     if (FFlag::DebugLuauTimeTracing)
     {
diff --git a/CodeGen/include/Luau/AssemblyBuilderA64.h b/CodeGen/include/Luau/AssemblyBuilderA64.h
index a86403d4..bea70fd0 100644
--- a/CodeGen/include/Luau/AssemblyBuilderA64.h
+++ b/CodeGen/include/Luau/AssemblyBuilderA64.h
@@ -211,7 +211,6 @@ private:
     void placeSR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift = 0, int N = 0);
     void placeSR2(const char* name, RegisterA64 dst, RegisterA64 src, uint8_t op, uint8_t op2 = 0);
     void placeR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, uint8_t op2);
-    void placeR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t sizes, uint8_t op, uint8_t op2);
     void placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op);
     void placeI12(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op);
     void placeI16(const char* name, RegisterA64 dst, int src, uint8_t op, int shift = 0);
@@ -230,6 +229,7 @@ private:
     void placeBM(const char* name, RegisterA64 dst, RegisterA64 src1, uint32_t src2, uint8_t op);
     void placeBFM(const char* name, RegisterA64 dst, RegisterA64 src1, int src2, uint8_t op, int immr, int imms);
     void placeER(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t op, int shift);
+    void placeVR(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint16_t op, uint8_t op2);
 
     void place(uint32_t word);
 
diff --git a/CodeGen/src/AssemblyBuilderA64.cpp b/CodeGen/src/AssemblyBuilderA64.cpp
index 96d17192..ffb0a774 100644
--- a/CodeGen/src/AssemblyBuilderA64.cpp
+++ b/CodeGen/src/AssemblyBuilderA64.cpp
@@ -63,13 +63,22 @@ AssemblyBuilderA64::~AssemblyBuilderA64()
 
 void AssemblyBuilderA64::mov(RegisterA64 dst, RegisterA64 src)
 {
-    CODEGEN_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst == sp);
-    CODEGEN_ASSERT(dst.kind == src.kind || (dst.kind == KindA64::x && src == sp) || (dst == sp && src.kind == KindA64::x));
+    if (dst.kind != KindA64::q)
+    {
+        CODEGEN_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst == sp);
+        CODEGEN_ASSERT(dst.kind == src.kind || (dst.kind == KindA64::x && src == sp) || (dst == sp && src.kind == KindA64::x));
 
-    if (dst == sp || src == sp)
-        placeR1("mov", dst, src, 0b00'100010'0'000000000000);
+        if (dst == sp || src == sp)
+            placeR1("mov", dst, src, 0b00'100010'0'000000000000);
+        else
+            placeSR2("mov", dst, src, 0b01'01010);
+    }
     else
-        placeSR2("mov", dst, src, 0b01'01010);
+    {
+        CODEGEN_ASSERT(dst.kind == src.kind);
+
+        placeR1("mov", dst, src, 0b10'01110'10'1'00000'00011'1 | (src.index << 6));
+    }
 }
 
 void AssemblyBuilderA64::mov(RegisterA64 dst, int src)
@@ -575,12 +584,18 @@ void AssemblyBuilderA64::fadd(RegisterA64 dst, RegisterA64 src1, RegisterA64 src
 
         placeR3("fadd", dst, src1, src2, 0b11110'01'1, 0b0010'10);
     }
-    else
+    else if (dst.kind == KindA64::s)
     {
-        CODEGEN_ASSERT(dst.kind == KindA64::s && src1.kind == KindA64::s && src2.kind == KindA64::s);
+        CODEGEN_ASSERT(src1.kind == KindA64::s && src2.kind == KindA64::s);
 
         placeR3("fadd", dst, src1, src2, 0b11110'00'1, 0b0010'10);
     }
+    else
+    {
+        CODEGEN_ASSERT(dst.kind == KindA64::q && src1.kind == KindA64::q && src2.kind == KindA64::q);
+
+        placeVR("fadd", dst, src1, src2, 0b0'01110'0'0'1, 0b11010'1);
+    }
 }
 
 void AssemblyBuilderA64::fdiv(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
@@ -591,12 +606,18 @@ void AssemblyBuilderA64::fdiv(RegisterA64 dst, RegisterA64 src1, RegisterA64 src
 
         placeR3("fdiv", dst, src1, src2, 0b11110'01'1, 0b0001'10);
     }
-    else
+    else if (dst.kind == KindA64::s)
     {
-        CODEGEN_ASSERT(dst.kind == KindA64::s && src1.kind == KindA64::s && src2.kind == KindA64::s);
+        CODEGEN_ASSERT(src1.kind == KindA64::s && src2.kind == KindA64::s);
 
         placeR3("fdiv", dst, src1, src2, 0b11110'00'1, 0b0001'10);
     }
+    else
+    {
+        CODEGEN_ASSERT(dst.kind == KindA64::q && src1.kind == KindA64::q && src2.kind == KindA64::q);
+
+        placeVR("fdiv", dst, src1, src2, 0b1'01110'00'1, 0b11111'1);
+    }
 }
 
 void AssemblyBuilderA64::fmul(RegisterA64 dst, RegisterA64 src1, RegisterA64 src2)
@@ -607,12 +628,18 @@ void AssemblyBuilderA64::fmul(RegisterA64 dst, RegisterA64 src1, RegisterA64 src
 
         placeR3("fmul", dst, src1, src2, 0b11110'01'1, 0b0000'10);
     }
-    else
+    else if (dst.kind == KindA64::s)
     {
-        CODEGEN_ASSERT(dst.kind == KindA64::s && src1.kind == KindA64::s && src2.kind == KindA64::s);
+        CODEGEN_ASSERT(src1.kind == KindA64::s && src2.kind == KindA64::s);
 
         placeR3("fmul", dst, src1, src2, 0b11110'00'1, 0b0000'10);
     }
+    else
+    {
+        CODEGEN_ASSERT(dst.kind == KindA64::q && src1.kind == KindA64::q && src2.kind == KindA64::q);
+
+        placeVR("fmul", dst, src1, src2, 0b1'01110'00'1, 0b11011'1);
+    }
 }
 
 void AssemblyBuilderA64::fneg(RegisterA64 dst, RegisterA64 src)
@@ -623,12 +650,18 @@ void AssemblyBuilderA64::fneg(RegisterA64 dst, RegisterA64 src)
 
         placeR1("fneg", dst, src, 0b000'11110'01'1'0000'10'10000);
     }
-    else
+    else if (dst.kind == KindA64::s)
     {
-        CODEGEN_ASSERT(dst.kind == KindA64::s && src.kind == KindA64::s);
+        CODEGEN_ASSERT(src.kind == KindA64::s);
 
         placeR1("fneg", dst, src, 0b000'11110'00'1'0000'10'10000);
     }
+    else
+    {
+        CODEGEN_ASSERT(dst.kind == KindA64::q && src.kind == KindA64::q);
+
+        placeR1("fneg", dst, src, 0b011'01110'1'0'10000'01111'10);
+    }
 }
 
 void AssemblyBuilderA64::fsqrt(RegisterA64 dst, RegisterA64 src)
@@ -646,12 +679,18 @@ void AssemblyBuilderA64::fsub(RegisterA64 dst, RegisterA64 src1, RegisterA64 src
 
         placeR3("fsub", dst, src1, src2, 0b11110'01'1, 0b0011'10);
     }
-    else
+    else if (dst.kind == KindA64::s)
     {
-        CODEGEN_ASSERT(dst.kind == KindA64::s && src1.kind == KindA64::s && src2.kind == KindA64::s);
+        CODEGEN_ASSERT(src1.kind == KindA64::s && src2.kind == KindA64::s);
 
         placeR3("fsub", dst, src1, src2, 0b11110'00'1, 0b0011'10);
     }
+    else
+    {
+        CODEGEN_ASSERT(dst.kind == KindA64::q && src1.kind == KindA64::q && src2.kind == KindA64::q);
+
+        placeVR("fsub", dst, src1, src2, 0b0'01110'10'1, 0b11010'1);
+    }
 }
 
 void AssemblyBuilderA64::ins_4s(RegisterA64 dst, RegisterA64 src, uint8_t index)
@@ -952,18 +991,6 @@ void AssemblyBuilderA64::placeR3(const char* name, RegisterA64 dst, RegisterA64
     commit();
 }
 
-void AssemblyBuilderA64::placeR3(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint8_t sizes, uint8_t op, uint8_t op2)
-{
-    if (logText)
-        log(name, dst, src1, src2);
-
-    CODEGEN_ASSERT(dst.kind == KindA64::w || dst.kind == KindA64::x || dst.kind == KindA64::d || dst.kind == KindA64::q);
-    CODEGEN_ASSERT(dst.kind == src1.kind && dst.kind == src2.kind);
-
-    place(dst.index | (src1.index << 5) | (op2 << 10) | (src2.index << 16) | (op << 21) | (sizes << 29));
-    commit();
-}
-
 void AssemblyBuilderA64::placeR1(const char* name, RegisterA64 dst, RegisterA64 src, uint32_t op)
 {
     if (logText)
@@ -1226,6 +1253,17 @@ void AssemblyBuilderA64::placeER(const char* name, RegisterA64 dst, RegisterA64
     commit();
 }
 
+void AssemblyBuilderA64::placeVR(const char* name, RegisterA64 dst, RegisterA64 src1, RegisterA64 src2, uint16_t op, uint8_t op2)
+{
+    if (logText)
+        logAppend(" %-12sv%d.4s,v%d.4s,v%d.4s\n", name, dst.index, src1.index, src2.index);
+
+    CODEGEN_ASSERT(dst.kind == KindA64::q && dst.kind == src1.kind && dst.kind == src2.kind);
+
+    place(dst.index | (src1.index << 5) | (op2 << 10) | (src2.index << 16) | (op << 21) | (1 << 30));
+    commit();
+}
+
 void AssemblyBuilderA64::place(uint32_t word)
 {
     CODEGEN_ASSERT(codePos < codeEnd);
diff --git a/CodeGen/src/CodeGen.cpp b/CodeGen/src/CodeGen.cpp
index bada61cd..bf500d69 100644
--- a/CodeGen/src/CodeGen.cpp
+++ b/CodeGen/src/CodeGen.cpp
@@ -57,8 +57,6 @@ LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockLimit, 32'768) // 32 K
 // Current value is based on some member variables being limited to 16 bits
 LUAU_FASTINTVARIABLE(CodegenHeuristicsBlockInstructionLimit, 65'536) // 64 K
 
-LUAU_FASTFLAGVARIABLE(DisableNativeCodegenIfBreakpointIsSet, false)
-
 namespace Luau
 {
 namespace CodeGen
@@ -302,7 +300,7 @@ void create(lua_State* L, AllocationCallback* allocationCallback, void* allocati
     ecb->close = onCloseState;
     ecb->destroy = onDestroyFunction;
     ecb->enter = onEnter;
-    ecb->disable = FFlag::DisableNativeCodegenIfBreakpointIsSet ? onDisable : nullptr;
+    ecb->disable = onDisable;
 }
 
 void create(lua_State* L)
diff --git a/CodeGen/src/IrLoweringA64.cpp b/CodeGen/src/IrLoweringA64.cpp
index 681c56ec..9d9df188 100644
--- a/CodeGen/src/IrLoweringA64.cpp
+++ b/CodeGen/src/IrLoweringA64.cpp
@@ -12,6 +12,7 @@
 #include "lgc.h"
 
 LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodeGenFixBufferLenCheckA64, false)
+LUAU_FASTFLAGVARIABLE(LuauCodeGenVectorA64, false)
 
 namespace Luau
 {
@@ -673,15 +674,26 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
     {
         inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b});
 
-        RegisterA64 tempa = regs.allocTemp(KindA64::s);
-        RegisterA64 tempb = regs.allocTemp(KindA64::s);
-
-        for (uint8_t i = 0; i < 3; i++)
+        if (FFlag::LuauCodeGenVectorA64)
         {
-            build.dup_4s(tempa, regOp(inst.a), i);
-            build.dup_4s(tempb, regOp(inst.b), i);
-            build.fadd(tempa, tempa, tempb);
-            build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            build.fadd(inst.regA64, regOp(inst.a), regOp(inst.b));
+
+            RegisterA64 tempw = regs.allocTemp(KindA64::w);
+            build.mov(tempw, LUA_TVECTOR);
+            build.ins_4s(inst.regA64, tempw, 3);
+        }
+        else
+        {
+            RegisterA64 tempa = regs.allocTemp(KindA64::s);
+            RegisterA64 tempb = regs.allocTemp(KindA64::s);
+
+            for (uint8_t i = 0; i < 3; i++)
+            {
+                build.dup_4s(tempa, regOp(inst.a), i);
+                build.dup_4s(tempb, regOp(inst.b), i);
+                build.fadd(tempa, tempa, tempb);
+                build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            }
         }
         break;
     }
@@ -689,15 +701,26 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
     {
         inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b});
 
-        RegisterA64 tempa = regs.allocTemp(KindA64::s);
-        RegisterA64 tempb = regs.allocTemp(KindA64::s);
-
-        for (uint8_t i = 0; i < 3; i++)
+        if (FFlag::LuauCodeGenVectorA64)
         {
-            build.dup_4s(tempa, regOp(inst.a), i);
-            build.dup_4s(tempb, regOp(inst.b), i);
-            build.fsub(tempa, tempa, tempb);
-            build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            build.fsub(inst.regA64, regOp(inst.a), regOp(inst.b));
+
+            RegisterA64 tempw = regs.allocTemp(KindA64::w);
+            build.mov(tempw, LUA_TVECTOR);
+            build.ins_4s(inst.regA64, tempw, 3);
+        }
+        else
+        {
+            RegisterA64 tempa = regs.allocTemp(KindA64::s);
+            RegisterA64 tempb = regs.allocTemp(KindA64::s);
+
+            for (uint8_t i = 0; i < 3; i++)
+            {
+                build.dup_4s(tempa, regOp(inst.a), i);
+                build.dup_4s(tempb, regOp(inst.b), i);
+                build.fsub(tempa, tempa, tempb);
+                build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            }
         }
         break;
     }
@@ -705,15 +728,26 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
     {
         inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b});
 
-        RegisterA64 tempa = regs.allocTemp(KindA64::s);
-        RegisterA64 tempb = regs.allocTemp(KindA64::s);
-
-        for (uint8_t i = 0; i < 3; i++)
+        if (FFlag::LuauCodeGenVectorA64)
         {
-            build.dup_4s(tempa, regOp(inst.a), i);
-            build.dup_4s(tempb, regOp(inst.b), i);
-            build.fmul(tempa, tempa, tempb);
-            build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            build.fmul(inst.regA64, regOp(inst.a), regOp(inst.b));
+
+            RegisterA64 tempw = regs.allocTemp(KindA64::w);
+            build.mov(tempw, LUA_TVECTOR);
+            build.ins_4s(inst.regA64, tempw, 3);
+        }
+        else
+        {
+            RegisterA64 tempa = regs.allocTemp(KindA64::s);
+            RegisterA64 tempb = regs.allocTemp(KindA64::s);
+
+            for (uint8_t i = 0; i < 3; i++)
+            {
+                build.dup_4s(tempa, regOp(inst.a), i);
+                build.dup_4s(tempb, regOp(inst.b), i);
+                build.fmul(tempa, tempa, tempb);
+                build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            }
         }
         break;
     }
@@ -721,15 +755,26 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
     {
         inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a, inst.b});
 
-        RegisterA64 tempa = regs.allocTemp(KindA64::s);
-        RegisterA64 tempb = regs.allocTemp(KindA64::s);
-
-        for (uint8_t i = 0; i < 3; i++)
+        if (FFlag::LuauCodeGenVectorA64)
         {
-            build.dup_4s(tempa, regOp(inst.a), i);
-            build.dup_4s(tempb, regOp(inst.b), i);
-            build.fdiv(tempa, tempa, tempb);
-            build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            build.fdiv(inst.regA64, regOp(inst.a), regOp(inst.b));
+
+            RegisterA64 tempw = regs.allocTemp(KindA64::w);
+            build.mov(tempw, LUA_TVECTOR);
+            build.ins_4s(inst.regA64, tempw, 3);
+        }
+        else
+        {
+            RegisterA64 tempa = regs.allocTemp(KindA64::s);
+            RegisterA64 tempb = regs.allocTemp(KindA64::s);
+
+            for (uint8_t i = 0; i < 3; i++)
+            {
+                build.dup_4s(tempa, regOp(inst.a), i);
+                build.dup_4s(tempb, regOp(inst.b), i);
+                build.fdiv(tempa, tempa, tempb);
+                build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            }
         }
         break;
     }
@@ -737,13 +782,24 @@ void IrLoweringA64::lowerInst(IrInst& inst, uint32_t index, const IrBlock& next)
     {
         inst.regA64 = regs.allocReuse(KindA64::q, index, {inst.a});
 
-        RegisterA64 tempa = regs.allocTemp(KindA64::s);
-
-        for (uint8_t i = 0; i < 3; i++)
+        if (FFlag::LuauCodeGenVectorA64)
         {
-            build.dup_4s(tempa, regOp(inst.a), i);
-            build.fneg(tempa, tempa);
-            build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            build.fneg(inst.regA64, regOp(inst.a));
+
+            RegisterA64 tempw = regs.allocTemp(KindA64::w);
+            build.mov(tempw, LUA_TVECTOR);
+            build.ins_4s(inst.regA64, tempw, 3);
+        }
+        else
+        {
+            RegisterA64 tempa = regs.allocTemp(KindA64::s);
+
+            for (uint8_t i = 0; i < 3; i++)
+            {
+                build.dup_4s(tempa, regOp(inst.a), i);
+                build.fneg(tempa, tempa);
+                build.ins_4s(inst.regA64, i, castReg(KindA64::q, tempa), 0);
+            }
         }
         break;
     }
diff --git a/CodeGen/src/IrValueLocationTracking.cpp b/CodeGen/src/IrValueLocationTracking.cpp
index 70fd7fc4..182dd736 100644
--- a/CodeGen/src/IrValueLocationTracking.cpp
+++ b/CodeGen/src/IrValueLocationTracking.cpp
@@ -3,6 +3,8 @@
 
 #include "Luau/IrUtils.h"
 
+LUAU_DYNAMIC_FASTFLAGVARIABLE(LuauCodegenTrackingMultilocationFix, false)
+
 namespace Luau
 {
 namespace CodeGen
@@ -159,6 +161,9 @@ void IrValueLocationTracking::afterInstLowering(IrInst& inst, uint32_t instIdx)
     case IrCmd::LOAD_DOUBLE:
     case IrCmd::LOAD_INT:
     case IrCmd::LOAD_TVALUE:
+        if (DFFlag::LuauCodegenTrackingMultilocationFix && inst.a.kind == IrOpKind::VmReg)
+            invalidateRestoreOp(inst.a, /*skipValueInvalidation*/ false);
+
         recordRestoreOp(instIdx, inst.a);
         break;
     case IrCmd::STORE_POINTER:
diff --git a/Sources.cmake b/Sources.cmake
index 1f9aa5a7..5745f6dd 100644
--- a/Sources.cmake
+++ b/Sources.cmake
@@ -176,6 +176,7 @@ target_sources(Luau.Analysis PRIVATE
     Analysis/include/Luau/GlobalTypes.h
     Analysis/include/Luau/InsertionOrderedMap.h
     Analysis/include/Luau/Instantiation.h
+    Analysis/include/Luau/Instantiation2.h
     Analysis/include/Luau/IostreamHelpers.h
     Analysis/include/Luau/JsonEmitter.h
     Analysis/include/Luau/Linter.h
@@ -242,6 +243,7 @@ target_sources(Luau.Analysis PRIVATE
     Analysis/src/Frontend.cpp
     Analysis/src/GlobalTypes.cpp
     Analysis/src/Instantiation.cpp
+    Analysis/src/Instantiation2.cpp
     Analysis/src/IostreamHelpers.cpp
     Analysis/src/JsonEmitter.cpp
     Analysis/src/Linter.cpp
@@ -457,7 +459,6 @@ if(TARGET Luau.UnitTest)
         tests/TypeInfer.primitives.test.cpp
         tests/TypeInfer.provisional.test.cpp
         tests/TypeInfer.refinements.test.cpp
-        tests/TypeInfer.rwprops.test.cpp
         tests/TypeInfer.singletons.test.cpp
         tests/TypeInfer.tables.test.cpp
         tests/TypeInfer.test.cpp
diff --git a/tests/AssemblyBuilderA64.test.cpp b/tests/AssemblyBuilderA64.test.cpp
index 6657d889..320a7a6a 100644
--- a/tests/AssemblyBuilderA64.test.cpp
+++ b/tests/AssemblyBuilderA64.test.cpp
@@ -218,6 +218,7 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "Moves")
 {
     SINGLE_COMPARE(mov(x0, x1), 0xAA0103E0);
     SINGLE_COMPARE(mov(w0, w1), 0x2A0103E0);
+    SINGLE_COMPARE(mov(q0, q1), 0x4EA11C20);
 
     SINGLE_COMPARE(movz(x0, 42), 0xD2800540);
     SINGLE_COMPARE(movz(w0, 42), 0x52800540);
@@ -501,6 +502,15 @@ TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "PrePostIndexing")
     SINGLE_COMPARE(str(q0, mem(x1, 1, AddressKindA64::post)), 0x3C801420);
 }
 
+TEST_CASE_FIXTURE(AssemblyBuilderA64Fixture, "SIMDMath")
+{
+    SINGLE_COMPARE(fadd(q0, q1, q2), 0x4E22D420);
+    SINGLE_COMPARE(fsub(q0, q1, q2), 0x4EA2D420);
+    SINGLE_COMPARE(fmul(q0, q1, q2), 0x6E22DC20);
+    SINGLE_COMPARE(fdiv(q0, q1, q2), 0x6E22FC20);
+    SINGLE_COMPARE(fneg(q0, q1), 0x6EA0F820);
+}
+
 TEST_CASE("LogTest")
 {
     AssemblyBuilderA64 build(/* logText= */ true);
@@ -552,6 +562,7 @@ TEST_CASE("LogTest")
     build.ins_4s(q31, 1, q29, 2);
     build.dup_4s(s29, q31, 2);
     build.dup_4s(q29, q30, 0);
+    build.fmul(q0, q1, q2);
 
     build.setLabel(l);
     build.ret();
@@ -594,6 +605,7 @@ TEST_CASE("LogTest")
  ins         v31.s[1],v29.s[2]
  dup         s29,v31.s[2]
  dup         v29.4s,v30.s[0]
+ fmul        v0.4s,v1.4s,v2.4s
 .L1:
  ret
 )";
diff --git a/tests/Conformance.test.cpp b/tests/Conformance.test.cpp
index 3293b246..4ce13658 100644
--- a/tests/Conformance.test.cpp
+++ b/tests/Conformance.test.cpp
@@ -31,6 +31,7 @@ LUAU_FASTFLAG(LuauSciNumberSkipTrailDot)
 LUAU_DYNAMIC_FASTFLAG(LuauInterruptablePatternMatch)
 LUAU_FASTINT(CodegenHeuristicsInstructionLimit)
 LUAU_DYNAMIC_FASTFLAG(LuauCodeGenFixBufferLenCheckA64)
+LUAU_DYNAMIC_FASTFLAG(LuauCodegenTrackingMultilocationFix)
 
 static lua_CompileOptions defaultOptions()
 {
@@ -2040,6 +2041,7 @@ TEST_CASE("SafeEnv")
 TEST_CASE("Native")
 {
     ScopedFastFlag luauCodeGenFixBufferLenCheckA64{DFFlag::LuauCodeGenFixBufferLenCheckA64, true};
+    ScopedFastFlag luauCodegenTrackingMultilocationFix{DFFlag::LuauCodegenTrackingMultilocationFix, true};
 
     // This tests requires code to run natively, otherwise all 'is_native' checks will fail
     if (!codegen || !luau_codegen_supported())
diff --git a/tests/ConstraintSolver.test.cpp b/tests/ConstraintSolver.test.cpp
index 204d4d14..170510bd 100644
--- a/tests/ConstraintSolver.test.cpp
+++ b/tests/ConstraintSolver.test.cpp
@@ -17,7 +17,7 @@ static TypeId requireBinding(Scope* scope, const char* name)
 
 TEST_SUITE_BEGIN("ConstraintSolver");
 
-TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "hello")
+TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "constraint_basics")
 {
     solve(R"(
         local a = 55
@@ -58,12 +58,7 @@ TEST_CASE_FIXTURE(ConstraintGeneratorFixture, "proper_let_generalization")
 
     TypeId idType = requireBinding(rootScope, "b");
 
-    ToStringOptions opts;
-
-    if (FFlag::DebugLuauDeferredConstraintResolution)
-        CHECK("(unknown) -> number" == toString(idType, opts));
-    else
-        CHECK("<a>(a) -> number" == toString(idType, opts));
+    CHECK("(unknown) -> number" == toString(idType));
 }
 
 TEST_SUITE_END();
diff --git a/tests/Differ.test.cpp b/tests/Differ.test.cpp
index 9cf8c3aa..b009cec0 100644
--- a/tests/Differ.test.cpp
+++ b/tests/Differ.test.cpp
@@ -204,6 +204,8 @@ TEST_CASE_FIXTURE(DifferFixture, "right_cyclic_table_left_table_property_wrong")
 
 TEST_CASE_FIXTURE(DifferFixture, "equal_table_two_cyclic_tables_are_not_different")
 {
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
+
     CheckResult result = check(R"(
     local function id<a>(x: a): a
       return x
diff --git a/tests/Linter.test.cpp b/tests/Linter.test.cpp
index b0e01dce..9f7aa77a 100644
--- a/tests/Linter.test.cpp
+++ b/tests/Linter.test.cpp
@@ -7,6 +7,8 @@
 
 #include "doctest.h"
 
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
+
 using namespace Luau;
 
 TEST_SUITE_BEGIN("Linter");
@@ -1246,6 +1248,30 @@ _ = {
     CHECK_EQ(result.warnings[5].text, "Table index 1 is a duplicate; previously defined at line 36");
 }
 
+TEST_CASE_FIXTURE(Fixture, "read_write_table_props")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    LintResult result = lint(R"(-- line 1
+        type A = {x: number}
+        type B = {read x: number, write x: number}
+        type C = {x: number, read x: number} -- line 4
+        type D = {x: number, write x: number}
+        type E = {read x: number, x: boolean}
+        type F = {read x: number, read x: number}
+        type G = {write x: number, x: boolean}
+        type H = {write x: number, write x: boolean}
+    )");
+
+    REQUIRE(6 == result.warnings.size());
+    CHECK(result.warnings[0].text == "Table type field 'x' is already read-write; previously defined at line 4");
+    CHECK(result.warnings[1].text == "Table type field 'x' is already read-write; previously defined at line 5");
+    CHECK(result.warnings[2].text == "Table type field 'x' already has a read type defined at line 6");
+    CHECK(result.warnings[3].text == "Table type field 'x' is a duplicate; previously defined at line 7");
+    CHECK(result.warnings[4].text == "Table type field 'x' already has a write type defined at line 8");
+    CHECK(result.warnings[5].text == "Table type field 'x' is a duplicate; previously defined at line 9");
+}
+
 TEST_CASE_FIXTURE(Fixture, "ImportOnlyUsedInTypeAnnotation")
 {
     LintResult result = lint(R"(
diff --git a/tests/Normalize.test.cpp b/tests/Normalize.test.cpp
index 2b09603e..81cb7cee 100644
--- a/tests/Normalize.test.cpp
+++ b/tests/Normalize.test.cpp
@@ -940,4 +940,26 @@ TEST_CASE_FIXTURE(NormalizeFixture, "normalize_unknown")
     CHECK(toString(normalizer.typeFromNormal(*nt)) == "unknown");
 }
 
+TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CHECK("{ x: string }" == toString(normal("{ read x: string } & { x: string }"), {true}));
+    CHECK("{ x: string }" == toString(normal("{ x: string } & { read x: string }"), {true}));
+}
+
+TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props_2")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CHECK(R"({ x: never })" == toString(normal(R"({ x: "hello" } & { x: "world" })"), {true}));
+}
+
+TEST_CASE_FIXTURE(NormalizeFixture, "read_only_props_3")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CHECK("{ read x: never }" == toString(normal(R"({ read x: "hello" } & { read x: "world" })"), {true}));
+}
+
 TEST_SUITE_END();
diff --git a/tests/Subtyping.test.cpp b/tests/Subtyping.test.cpp
index 80a4c058..d42856c2 100644
--- a/tests/Subtyping.test.cpp
+++ b/tests/Subtyping.test.cpp
@@ -15,6 +15,8 @@
 
 #include <initializer_list>
 
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
+
 using namespace Luau;
 
 namespace Luau
@@ -65,6 +67,8 @@ struct SubtypeFixture : Fixture
     UnifierSharedState sharedState{&ice};
     Normalizer normalizer{&arena, builtinTypes, NotNull{&sharedState}};
 
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
     ScopePtr rootScope{new Scope(builtinTypes->emptyTypePack)};
     ScopePtr moduleScope{new Scope(rootScope)};
 
@@ -220,6 +224,11 @@ struct SubtypeFixture : Fixture
                                        {"Y", builtinTypes->numberType},
                                    });
 
+    TypeId readOnlyVec2Class = cls("ReadOnlyVec2", {
+        {"X", Property::readonly(builtinTypes->numberType)},
+        {"Y", Property::readonly(builtinTypes->numberType)},
+    });
+
     // "hello" | "hello"
     TypeId helloOrHelloType = arena.addType(UnionType{{helloType, helloType}});
 
@@ -787,6 +796,34 @@ TEST_CASE_FIXTURE(SubtypeFixture, "{x: <T>(T) -> ()} <: {x: <U>(U) -> ()}")
     CHECK_IS_SUBTYPE(tbl({{"x", genericTToNothingType}}), tbl({{"x", genericUToNothingType}}));
 }
 
+TEST_CASE_FIXTURE(SubtypeFixture, "{ x: number } <: { read x: number }")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({{"x", Property::readonly(builtinTypes->numberType)}}));
+}
+
+TEST_CASE_FIXTURE(SubtypeFixture, "{ x: number } <: { write x: number }")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->numberType}}), tbl({{"x", Property::writeonly(builtinTypes->numberType)}}));
+}
+
+TEST_CASE_FIXTURE(SubtypeFixture, "{ x: \"hello\" } <: { read x: string }")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CHECK_IS_SUBTYPE(tbl({{"x", helloType}}), tbl({{"x", Property::readonly(builtinTypes->stringType)}}));
+}
+
+TEST_CASE_FIXTURE(SubtypeFixture, "{ x: string } <: { write x: string }")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CHECK_IS_SUBTYPE(tbl({{"x", builtinTypes->stringType}}), tbl({{"x", Property::writeonly(builtinTypes->stringType)}}));
+}
+
 TEST_CASE_FIXTURE(SubtypeFixture, "{ @metatable { x: number } } <: { @metatable {} }")
 {
     CHECK_IS_SUBTYPE(meta({{"x", builtinTypes->numberType}}), meta({}));
@@ -1027,6 +1064,28 @@ TEST_CASE_FIXTURE(SubtypeFixture, "Vec2 <!: table & { X: number, Y: number }")
     CHECK_IS_NOT_SUBTYPE(vec2Class, meet(builtinTypes->tableType, xy));
 }
 
+TEST_CASE_FIXTURE(SubtypeFixture, "ReadOnlyVec2 <!: { X: number, Y: number}")
+{
+    CHECK_IS_NOT_SUBTYPE(readOnlyVec2Class, tbl({{"X", builtinTypes->numberType}, {"Y", builtinTypes->numberType}}));
+}
+
+TEST_CASE_FIXTURE(SubtypeFixture, "ReadOnlyVec2 <: { read X: number, read Y: number}")
+{
+    CHECK_IS_SUBTYPE(
+        readOnlyVec2Class, tbl({{"X", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}}));
+}
+
+TEST_IS_SUBTYPE(vec2Class, tbl({{"X", Property::readonly(builtinTypes->numberType)}, {"Y", Property::readonly(builtinTypes->numberType)}}));
+
+TEST_IS_NOT_SUBTYPE(tbl({{"P", grandchildOneClass}}), tbl({{"P", Property::rw(rootClass)}}));
+TEST_IS_SUBTYPE(tbl({{"P", grandchildOneClass}}), tbl({{"P", Property::readonly(rootClass)}}));
+TEST_IS_SUBTYPE(tbl({{"P", rootClass}}), tbl({{"P", Property::writeonly(grandchildOneClass)}}));
+
+TEST_IS_NOT_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", rootClass}}));
+TEST_IS_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", Property::readonly(rootClass)}}));
+TEST_IS_NOT_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", grandchildOneClass}}));
+TEST_IS_SUBTYPE(cls("HasChild", {{"P", childClass}}), tbl({{"P", Property::writeonly(grandchildOneClass)}}));
+
 TEST_CASE_FIXTURE(SubtypeFixture, "\"hello\" <: { lower : (string) -> string }")
 {
     CHECK_IS_SUBTYPE(helloType, tableWithLower);
@@ -1217,8 +1276,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "table_property")
 
     SubtypingResult result = isSubtype(subTy, superTy);
     CHECK(!result.isSubtype);
-    CHECK(result.reasoning == std::vector{SubtypingReasoning{/* subPath */ Path(TypePath::Property("X")),
-                                  /* superPath */ Path(TypePath::Property("X")),
+    CHECK(result.reasoning == std::vector{SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")),
+                                  /* superPath */ Path(TypePath::Property::read("X")),
                                   /* variance */ SubtypingVariance::Invariant}});
 }
 
@@ -1317,8 +1376,8 @@ TEST_CASE_FIXTURE(SubtypeFixture, "nested_table_properties")
     SubtypingResult result = isSubtype(subTy, superTy);
     CHECK(!result.isSubtype);
     CHECK(result.reasoning == std::vector{SubtypingReasoning{
-                                  /* subPath */ TypePath::PathBuilder().prop("X").prop("Y").prop("Z").build(),
-                                  /* superPath */ TypePath::PathBuilder().prop("X").prop("Y").prop("Z").build(),
+                                  /* subPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(),
+                                  /* superPath */ TypePath::PathBuilder().readProp("X").readProp("Y").readProp("Z").build(),
                                   /* variance */ SubtypingVariance::Invariant,
                               }});
 }
@@ -1335,7 +1394,7 @@ TEST_CASE_FIXTURE(SubtypeFixture, "string_table_mt")
     // metatable is empty, and abort there, without looking at the metatable
     // properties (because there aren't any).
     CHECK(result.reasoning == std::vector{SubtypingReasoning{
-                                  /* subPath */ TypePath::PathBuilder().mt().prop("__index").build(),
+                                  /* subPath */ TypePath::PathBuilder().mt().readProp("__index").build(),
                                   /* superPath */ TypePath::kEmpty,
                               }});
 }
@@ -1360,12 +1419,13 @@ TEST_CASE_FIXTURE(SubtypeFixture, "multiple_reasonings")
 
     SubtypingResult result = isSubtype(subTy, superTy);
     CHECK(!result.isSubtype);
-    CHECK(result.reasoning == std::vector{
-                                  SubtypingReasoning{/* subPath */ Path(TypePath::Property("X")), /* superPath */ Path(TypePath::Property("X")),
-                                      /* variance */ SubtypingVariance::Invariant},
-                                  SubtypingReasoning{/* subPath */ Path(TypePath::Property("Y")), /* superPath */ Path(TypePath::Property("Y")),
-                                      /* variance */ SubtypingVariance::Invariant},
-                              });
+    CHECK(result.reasoning ==
+          std::vector{
+              SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("X")), /* superPath */ Path(TypePath::Property::read("X")),
+                  /* variance */ SubtypingVariance::Invariant},
+              SubtypingReasoning{/* subPath */ Path(TypePath::Property::read("Y")), /* superPath */ Path(TypePath::Property::read("Y")),
+                  /* variance */ SubtypingVariance::Invariant},
+          });
 }
 
 TEST_SUITE_END();
diff --git a/tests/ToString.test.cpp b/tests/ToString.test.cpp
index c2e4aadd..b9c0381f 100644
--- a/tests/ToString.test.cpp
+++ b/tests/ToString.test.cpp
@@ -931,18 +931,17 @@ TEST_CASE_FIXTURE(Fixture, "tostring_unsee_ttv_if_array")
 TEST_CASE_FIXTURE(Fixture, "tostring_error_mismatch")
 {
     CheckResult result = check(R"(
---!strict
-   function f1() : {a : number, b : string, c : { d : number}}
-     return { a = 1, b = "a", c = {d = "a"}}
-   end
+        --!strict
+        function f1() : {a : number, b : string, c : { d : number}}
+            return { a = 1, b = "a", c = {d = "a"}}
+        end
+    )");
 
-)");
-    //clang-format off
-    std::string expected =
-        (FFlag::DebugLuauDeferredConstraintResolution)
-            ? R"(Type pack '{| a: number, b: string, c: {| d: string |} |}' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0]["c"]["d"], string is not exactly number)"
-            :
-            R"(Type
+    std::string expected;
+    if (FFlag::DebugLuauDeferredConstraintResolution)
+        expected = R"(Type pack '{ a: number, b: string, c: { d: string } }' could not be converted into '{ a: number, b: string, c: { d: number } }'; at [0][read "c"][read "d"], string is not exactly number)";
+    else
+        expected = R"(Type
     '{ a: number, b: string, c: { d: string } }'
 could not be converted into
     '{| a: number, b: string, c: {| d: number |} |}'
@@ -955,7 +954,6 @@ could not be converted into
 caused by:
   Property 'd' is not compatible.
 Type 'string' could not be converted into 'number' in an invariant context)";
-    //clang-format on
 
     LUAU_REQUIRE_ERROR_COUNT(1, result);
 
@@ -984,4 +982,20 @@ local f = abs
     TypeId fn = requireType("f");
     CHECK("@checked (number) -> number" == toString(fn));
 }
+
+TEST_CASE_FIXTURE(Fixture, "read_only_properties")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CheckResult result = check(R"(
+        type A = {x: string}
+        type B = {read x: string}
+    )");
+
+    LUAU_REQUIRE_NO_ERRORS(result);
+
+    CHECK("{ x: string }" == toString(requireTypeAlias("A"), {true}));
+    CHECK("{ read x: string }" == toString(requireTypeAlias("B"), {true}));
+}
+
 TEST_SUITE_END();
diff --git a/tests/TypeInfer.aliases.test.cpp b/tests/TypeInfer.aliases.test.cpp
index a9f358eb..52930087 100644
--- a/tests/TypeInfer.aliases.test.cpp
+++ b/tests/TypeInfer.aliases.test.cpp
@@ -202,7 +202,7 @@ TEST_CASE_FIXTURE(Fixture, "generic_aliases")
     )");
 
     LUAU_REQUIRE_ERROR_COUNT(1, result);
-    const std::string expected = R"(Type 'bad' could not be converted into 'T<number>'; at ["v"], string is not exactly number)";
+    const std::string expected = R"(Type 'bad' could not be converted into 'T<number>'; at [read "v"], string is not exactly number)";
     CHECK(result.errors[0].location == Location{{4, 31}, {4, 44}});
     CHECK_EQ(expected, toString(result.errors[0]));
 }
@@ -221,7 +221,7 @@ TEST_CASE_FIXTURE(Fixture, "dependent_generic_aliases")
     )");
 
     LUAU_REQUIRE_ERROR_COUNT(1, result);
-    const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'; at ["t"]["v"], string is not exactly number)";
+    const std::string expected = R"(Type 'bad' could not be converted into 'U<number>'; at [read "t"][read "v"], string is not exactly number)";
 
     CHECK(result.errors[0].location == Location{{4, 31}, {4, 52}});
     CHECK_EQ(expected, toString(result.errors[0]));
diff --git a/tests/TypeInfer.builtins.test.cpp b/tests/TypeInfer.builtins.test.cpp
index 45f5d01c..11bb5eda 100644
--- a/tests/TypeInfer.builtins.test.cpp
+++ b/tests/TypeInfer.builtins.test.cpp
@@ -386,10 +386,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "setmetatable_on_union_of_tables")
 
     LUAU_REQUIRE_NO_ERRORS(result);
 
-    if (FFlag::DebugLuauDeferredConstraintResolution)
-        CHECK("{ @metatable {|  |}, A } | { @metatable {|  |}, B }" == toString(requireTypeAlias("X")));
-    else
-        CHECK("{ @metatable {  }, A } | { @metatable {  }, B }" == toString(requireTypeAlias("X")));
+    CHECK("{ @metatable {  }, A } | { @metatable {  }, B }" == toString(requireTypeAlias("X")));
 }
 
 TEST_CASE_FIXTURE(BuiltinsFixture, "table_insert_correctly_infers_type_of_array_2_args_overload")
@@ -1012,6 +1009,22 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types2")
     CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
 }
 
+TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types3")
+{
+    CheckResult result = check(R"(
+        local function f(x: (number | boolean)?)
+            assert(x)
+            return x
+        end
+    )");
+
+    LUAU_REQUIRE_NO_ERRORS(result);
+    if (FFlag::DebugLuauDeferredConstraintResolution)
+        CHECK_EQ("((boolean | number)?) -> number | true", toString(requireType("f")));
+    else // without the annotation, the old solver doesn't infer the best return type here
+        CHECK_EQ("((boolean | number)?) -> boolean | number", toString(requireType("f")));
+}
+
 TEST_CASE_FIXTURE(BuiltinsFixture, "assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_the_first_type")
 {
     CheckResult result = check(R"(
diff --git a/tests/TypeInfer.classes.test.cpp b/tests/TypeInfer.classes.test.cpp
index 00023b42..0fdea695 100644
--- a/tests/TypeInfer.classes.test.cpp
+++ b/tests/TypeInfer.classes.test.cpp
@@ -463,7 +463,7 @@ local b: B = a
     LUAU_REQUIRE_ERRORS(result);
 
     if (FFlag::DebugLuauDeferredConstraintResolution)
-        CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'B'; at [\"x\"], ChildClass is not exactly BaseClass");
+        CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'B'; at [read \"x\"], ChildClass is not exactly BaseClass");
     else
     {
         const std::string expected = R"(Type 'A' could not be converted into 'B'
@@ -639,4 +639,58 @@ TEST_CASE_FIXTURE(ClassFixture, "indexable_classes")
     }
 }
 
+TEST_CASE_FIXTURE(Fixture, "read_write_class_properties")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    TypeArena& arena = frontend.globals.globalTypes;
+
+    unfreeze(arena);
+
+    TypeId instanceType = arena.addType(ClassType{"Instance", {}, nullopt, nullopt, {}, {}, "Test"});
+    getMutable<ClassType>(instanceType)->props = {
+        {"Parent", Property::rw(instanceType)}
+    };
+
+    //
+
+    TypeId workspaceType = arena.addType(ClassType{"Workspace", {}, nullopt, nullopt, {}, {}, "Test"});
+
+    TypeId scriptType = arena.addType(ClassType{
+        "Script", {
+            {"Parent", Property::rw(workspaceType, instanceType)}
+        },
+        instanceType, nullopt, {}, {}, "Test"
+    });
+
+    TypeId partType = arena.addType(ClassType{
+        "Part", {
+            {"BrickColor", Property::rw(builtinTypes->stringType)},
+            {"Parent", Property::rw(workspaceType, instanceType)}
+        },
+        instanceType, nullopt, {}, {}, "Test"});
+
+    getMutable<ClassType>(workspaceType)->props = {
+        {"Script", Property::readonly(scriptType)},
+        {"Part", Property::readonly(partType)}
+    };
+
+    frontend.globals.globalScope->bindings[frontend.globals.globalNames.names->getOrAdd("script")] = Binding{scriptType};
+
+    freeze(arena);
+
+    CheckResult result = check(R"(
+        script.Parent.Part.BrickColor = 0xFFFFFF
+        script.Parent.Part.Parent = script
+    )");
+
+    LUAU_REQUIRE_ERROR_COUNT(1, result);
+
+    CHECK(Location{{1, 40}, {1, 48}} == result.errors[0].location);
+    TypeMismatch* tm = get<TypeMismatch>(result.errors[0]);
+    REQUIRE(tm);
+    CHECK(builtinTypes->stringType == tm->wantedType);
+    CHECK(builtinTypes->numberType == tm->givenType);
+}
+
 TEST_SUITE_END();
diff --git a/tests/TypeInfer.generics.test.cpp b/tests/TypeInfer.generics.test.cpp
index 8b647f99..a5f17f77 100644
--- a/tests/TypeInfer.generics.test.cpp
+++ b/tests/TypeInfer.generics.test.cpp
@@ -27,6 +27,8 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_function")
         local y: number = id(37)
     )");
     LUAU_REQUIRE_NO_ERRORS(result);
+    CHECK_EQ(builtinTypes->stringType, requireType("x"));
+    CHECK_EQ(builtinTypes->numberType, requireType("y"));
 }
 
 TEST_CASE_FIXTURE(Fixture, "check_generic_local_function")
@@ -39,6 +41,40 @@ TEST_CASE_FIXTURE(Fixture, "check_generic_local_function")
         local y: number = id(37)
     )");
     LUAU_REQUIRE_NO_ERRORS(result);
+    CHECK_EQ(builtinTypes->stringType, requireType("x"));
+    CHECK_EQ(builtinTypes->numberType, requireType("y"));
+}
+
+TEST_CASE_FIXTURE(Fixture, "check_generic_local_function2")
+{
+    CheckResult result = check(R"(
+        local function id<a>(x:a): a
+            return x
+        end
+        local x = id("hi")
+        local y = id(37)
+    )");
+    LUAU_REQUIRE_NO_ERRORS(result);
+    CHECK_EQ(builtinTypes->stringType, requireType("x"));
+    CHECK_EQ(builtinTypes->numberType, requireType("y"));
+}
+
+TEST_CASE_FIXTURE(BuiltinsFixture, "unions_and_generics")
+{
+    CheckResult result = check(R"(
+        type foo = <T>(T | {T}) -> T
+        local foo = (nil :: any) :: foo
+
+        type Test = number | {number}
+        local res = foo(1 :: Test)
+    )");
+
+    LUAU_REQUIRE_NO_ERRORS(result);
+
+    if (FFlag::DebugLuauDeferredConstraintResolution)
+        CHECK_EQ("number | {number}", toString(requireType("res")));
+    else // in the old solver, this just totally falls apart
+        CHECK_EQ("a", toString(requireType("res")));
 }
 
 TEST_CASE_FIXTURE(Fixture, "check_generic_typepack_function")
@@ -370,7 +406,7 @@ TEST_CASE_FIXTURE(Fixture, "calling_self_generic_methods")
     {
         LUAU_REQUIRE_NO_ERRORS(result);
 
-        CHECK_EQ("{ f: (t1) -> (), id: <a>(unknown, a) -> a } where t1 = { id: ((t1, number) -> number) & ((t1, string) -> string) }",
+        CHECK_EQ("{ f: (t1) -> (), id: <a>(unknown, a) -> a } where t1 = { read id: ((t1, number) -> number) & ((t1, string) -> string) }",
             toString(requireType("x"), {true}));
     }
     else
@@ -437,6 +473,7 @@ TEST_CASE_FIXTURE(Fixture, "dont_leak_generic_types")
         -- so this assignment should fail
         local b: boolean = f(true)
     )");
+
     LUAU_REQUIRE_ERRORS(result);
 }
 
@@ -797,8 +834,9 @@ y.a.c = y
     LUAU_REQUIRE_ERRORS(result);
 
     if (FFlag::DebugLuauDeferredConstraintResolution)
-        CHECK(toString(result.errors.at(0)) ==
-              R"(Type 'x' could not be converted into 'T<number>'; type x["a"]["c"] (nil) is not exactly T<number>["a"]["c"][0] (T<number>))");
+        CHECK(
+            toString(result.errors.at(0)) ==
+            R"(Type 'x' could not be converted into 'T<number>'; type x[read "a"][read "c"] (nil) is not exactly T<number>[read "a"][read "c"][0] (T<number>))");
     else
     {
         const std::string expected = R"(Type 'y' could not be converted into 'T<string>'
@@ -1369,6 +1407,19 @@ TEST_CASE_FIXTURE(Fixture, "bidirectional_checking_and_generalization_play_nice"
     CHECK("string" == toString(requireType("b")));
 }
 
+TEST_CASE_FIXTURE(BuiltinsFixture, "generalization_no_cyclic_intersections")
+{
+    CheckResult result = check(R"(
+        local f, t, n = pairs({"foo"})
+        local k, v = f(t)
+    )");
+
+    CHECK("({string}, number?) -> (number?, string)" == toString(requireType("f")));
+    CHECK("{string}" == toString(requireType("t")));
+    CHECK("number?" == toString(requireType("k")));
+    CHECK("string" == toString(requireType("v")));
+}
+
 TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
 {
     CheckResult result = check(R"(
@@ -1381,4 +1432,20 @@ TEST_CASE_FIXTURE(Fixture, "missing_generic_type_parameter")
     REQUIRE(get<UnknownSymbol>(result.errors[1]));
 }
 
+TEST_CASE_FIXTURE(BuiltinsFixture, "generic_type_families_work_in_subtyping")
+{
+    if (!FFlag::DebugLuauDeferredConstraintResolution)
+        return;
+
+    CheckResult result = check(R"(
+        local function addOne<T>(x: T): add<T, number> return x + 1 end
+
+        local function six(): number
+            return addOne(5)
+        end
+    )");
+
+    LUAU_REQUIRE_NO_ERRORS(result);
+}
+
 TEST_SUITE_END();
diff --git a/tests/TypeInfer.modules.test.cpp b/tests/TypeInfer.modules.test.cpp
index 13031560..97efef16 100644
--- a/tests/TypeInfer.modules.test.cpp
+++ b/tests/TypeInfer.modules.test.cpp
@@ -414,7 +414,7 @@ local b: B.T = a
     LUAU_REQUIRE_ERROR_COUNT(1, result);
 
     if (FFlag::DebugLuauDeferredConstraintResolution)
-        CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'T'; at [\"x\"], number is not exactly string");
+        CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'T'; at [read \"x\"], number is not exactly string");
     else
     {
         const std::string expected = R"(Type 'T' from 'game/A' could not be converted into 'T' from 'game/B'
@@ -455,7 +455,7 @@ local b: B.T = a
     LUAU_REQUIRE_ERROR_COUNT(1, result);
 
     if (FFlag::DebugLuauDeferredConstraintResolution)
-        CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'T'; at [\"x\"], number is not exactly string");
+        CHECK(toString(result.errors.at(0)) == "Type 'a' could not be converted into 'T'; at [read \"x\"], number is not exactly string");
     else
     {
         const std::string expected = R"(Type 'T' from 'game/B' could not be converted into 'T' from 'game/C'
diff --git a/tests/TypeInfer.rwprops.test.cpp b/tests/TypeInfer.rwprops.test.cpp
deleted file mode 100644
index b3ed7947..00000000
--- a/tests/TypeInfer.rwprops.test.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-// This file is part of the Luau programming language and is licensed under MIT License; see LICENSE.txt for details
-#include "Fixture.h"
-
-#include "doctest.h"
-
-LUAU_FASTFLAG(DebugLuauReadWriteProperties)
-LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
-
-using namespace Luau;
-
-namespace
-{
-
-struct ReadWriteFixture : Fixture
-{
-    ScopedFastFlag dcr{FFlag::DebugLuauDeferredConstraintResolution, true};
-
-    ReadWriteFixture()
-        : Fixture()
-    {
-        if (!FFlag::DebugLuauReadWriteProperties)
-            return;
-
-        TypeArena* arena = &frontend.globals.globalTypes;
-        NotNull<Scope> globalScope{frontend.globals.globalScope.get()};
-
-        unfreeze(*arena);
-
-        TypeId genericT = arena->addType(GenericType{"T"});
-
-        TypeId readonlyX = arena->addType(TableType{TableState::Sealed, TypeLevel{}, globalScope});
-        getMutable<TableType>(readonlyX)->props["x"] = Property::readonly(genericT);
-        globalScope->addBuiltinTypeBinding("ReadonlyX", TypeFun{{{genericT}}, readonlyX});
-
-        freeze(*arena);
-    }
-};
-
-} // namespace
-
-TEST_SUITE_BEGIN("ReadWriteProperties");
-
-TEST_CASE_FIXTURE(ReadWriteFixture, "read_from_a_readonly_prop")
-{
-    if (!FFlag::DebugLuauReadWriteProperties)
-        return;
-
-    CheckResult result = check(R"(
-        function f(rx: ReadonlyX<string>)
-            local x = rx.x
-        end
-    )");
-
-    LUAU_REQUIRE_NO_ERRORS(result);
-}
-
-TEST_CASE_FIXTURE(ReadWriteFixture, "write_to_a_readonly_prop")
-{
-    if (!FFlag::DebugLuauReadWriteProperties)
-        return;
-
-    CheckResult result = check(R"(
-        function f(rx: ReadonlyX<string>)
-            rx.x = "hello!" -- error
-        end
-    )");
-
-    LUAU_REQUIRE_ERROR_COUNT(1, result);
-}
-
-TEST_SUITE_END();
diff --git a/tests/TypeInfer.singletons.test.cpp b/tests/TypeInfer.singletons.test.cpp
index 34fd902b..81cfabd9 100644
--- a/tests/TypeInfer.singletons.test.cpp
+++ b/tests/TypeInfer.singletons.test.cpp
@@ -360,7 +360,7 @@ TEST_CASE_FIXTURE(Fixture, "parametric_tagged_union_alias")
         type Result<O, E> = Ok<O> | Err<E>
 
         local a : Result<string, number> = {success = false, result = "hotdogs"}
-        local b : Result<string, number> = {success = true, result = "hotdogs"}
+        -- local b : Result<string, number> = {success = true, result = "hotdogs"}
     )");
 
     LUAU_REQUIRE_ERROR_COUNT(1, result);
diff --git a/tests/TypeInfer.tables.test.cpp b/tests/TypeInfer.tables.test.cpp
index 05f6cb7e..a9ae8fc6 100644
--- a/tests/TypeInfer.tables.test.cpp
+++ b/tests/TypeInfer.tables.test.cpp
@@ -357,7 +357,7 @@ TEST_CASE_FIXTURE(Fixture, "open_table_unification_2")
     LUAU_REQUIRE_ERROR_COUNT(1, result);
     TypeError& err = result.errors[0];
     MissingProperties* error = get<MissingProperties>(err);
-    REQUIRE(error != nullptr);
+    REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(err));
     REQUIRE(error->properties.size() == 1);
 
     CHECK_EQ("y", error->properties[0]);
@@ -426,7 +426,7 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_2")
     LUAU_REQUIRE_ERROR_COUNT(1, result);
 
     MissingProperties* error = get<MissingProperties>(result.errors[0]);
-    REQUIRE(error != nullptr);
+    REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(result.errors[0]));
     REQUIRE(error->properties.size() == 1);
 
     CHECK_EQ("baz", error->properties[0]);
@@ -446,7 +446,7 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_3")
     LUAU_REQUIRE_ERROR_COUNT(1, result);
     TypeError& err = result.errors[0];
     MissingProperties* error = get<MissingProperties>(err);
-    REQUIRE(error != nullptr);
+    REQUIRE_MESSAGE(error != nullptr, "Expected MissingProperties but got " << toString(err));
     REQUIRE(error->properties.size() == 1);
 
     CHECK_EQ("baz", error->properties[0]);
@@ -461,52 +461,6 @@ TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_3")
     CHECK_EQ(err.location, (Location{Position{6, 8}, Position{6, 9}}));
 }
 
-#if 0
-TEST_CASE_FIXTURE(Fixture, "table_param_width_subtyping_2")
-{
-    CheckResult result = check(R"(
-        function id(x)
-            return x
-        end
-
-        function foo(o)
-            id(o.x)
-            id(o.y)
-            return o
-        end
-
-        local a = {x=55, y=nil, w=3.14159}
-        local b = {}
-        b.x = 1
-        b.y = 'hello'
-        b.z = 'something extra!'
-
-        local q = foo(a) -- line 17
-        local w = foo(b) -- line 18
-    )");
-
-    LUAU_REQUIRE_NO_ERRORS(result);
-
-    TypeId qType = requireType("q");
-    const TableType* qTable = get<TableType>(qType);
-    REQUIRE(qType != nullptr);
-
-    CHECK(qTable->props.find("x") != qTable->props.end());
-    CHECK(qTable->props.find("y") != qTable->props.end());
-    CHECK(qTable->props.find("z") == qTable->props.end());
-    CHECK(qTable->props.find("w") != qTable->props.end());
-
-    TypeId wType = requireType("w");
-    const TableType* wTable = get<TableType>(wType);
-    REQUIRE(wTable != nullptr);
-
-    CHECK(wTable->props.find("x") != wTable->props.end());
-    CHECK(wTable->props.find("y") != wTable->props.end());
-    CHECK(wTable->props.find("z") != wTable->props.end());
-    CHECK(wTable->props.find("w") == wTable->props.end());
-}
-#endif
-
 TEST_CASE_FIXTURE(Fixture, "table_unification_4")
 {
     CheckResult result = check(R"(
@@ -680,7 +634,8 @@ TEST_CASE_FIXTURE(Fixture, "indexers_get_quantified_too")
 
     REQUIRE("number" == toString(indexer.indexType));
 
-    REQUIRE(nullptr != get<GenericType>(follow(indexer.indexResultType)));
+    TypeId indexResultType = follow(indexer.indexResultType);
+    REQUIRE_MESSAGE(get<GenericType>(indexResultType), "Expected generic but got " << toString(indexResultType));
 }
 
 TEST_CASE_FIXTURE(Fixture, "indexers_quantification_2")
@@ -1077,6 +1032,8 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_inferred")
 
 TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways")
 {
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, false};
+
     CheckResult result = check(R"(
         type VectorMt = { __add: (Vector, number) -> Vector }
         local vectorMt: VectorMt
@@ -1093,6 +1050,30 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways")
     CHECK_EQ(*requireType("a"), *requireType("c"));
 }
 
+TEST_CASE_FIXTURE(BuiltinsFixture, "meta_add_both_ways_lti")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CheckResult result = check(R"(
+        local vectorMt = {}
+
+        function vectorMt.__add(self: Vector, other: number)
+            return self
+        end
+
+        type Vector = typeof(setmetatable({}, vectorMt))
+        local a: Vector = setmetatable({}, vectorMt)
+
+        local b = a + 2
+        local c = 2 + a
+    )");
+    LUAU_REQUIRE_NO_ERRORS(result);
+
+    CHECK_EQ("Vector", toString(requireType("a")));
+    CHECK_EQ(*requireType("a"), *requireType("b"));
+    CHECK_EQ(*requireType("a"), *requireType("c"));
+}
+
 // This test exposed a bug where we let go of the "seen" stack while unifying table types
 // As a result, type inference crashed with a stack overflow.
 TEST_CASE_FIXTURE(BuiltinsFixture, "unification_of_unions_in_a_self_referential_type")
@@ -1570,7 +1551,7 @@ TEST_CASE_FIXTURE(Fixture, "right_table_missing_key2")
     LUAU_REQUIRE_ERROR_COUNT(1, result);
 
     MissingProperties* mp = get<MissingProperties>(result.errors[0]);
-    REQUIRE(mp);
+    REQUIRE_MESSAGE(mp, "Expected MissingProperties but got " << toString(result.errors[0]));
     CHECK_EQ(mp->context, MissingProperties::Missing);
     REQUIRE_EQ(1, mp->properties.size());
     CHECK_EQ(mp->properties[0], "a");
@@ -1664,7 +1645,7 @@ TEST_CASE_FIXTURE(Fixture, "casting_tables_with_props_into_table_with_indexer4")
     if (FFlag::DebugLuauDeferredConstraintResolution)
     {
         LUAU_REQUIRE_ERROR_COUNT(1, result);
-        CHECK(toString(result.errors[0]) == "Type 'string' could not be converted into 'number'");
+        CHECK(toString(result.errors[0]) == "Type 'number' could not be converted into 'string' in an invariant context");
     }
     else
     {
@@ -1685,7 +1666,7 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_with_missing_props_dont_report_multi
 
     if (FFlag::DebugLuauDeferredConstraintResolution)
     {
-        CHECK_EQ("Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }'"
+        CHECK_EQ("Type pack '{ x: number }' could not be converted into '{ x: number, y: number, z: number }';"
                  " at [0], { x: number } is not a subtype of { x: number, y: number, z: number }",
             toString(result.errors[0]));
     }
@@ -2176,7 +2157,7 @@ local b: B = a
     LUAU_REQUIRE_ERRORS(result);
 
     if (FFlag::DebugLuauDeferredConstraintResolution)
-        CHECK(toString(result.errors.at(0)) == R"(Type 'a' could not be converted into 'B'; at ["y"], number is not exactly string)");
+        CHECK(toString(result.errors.at(0)) == R"(Type 'a' could not be converted into 'B'; at [read "y"], number is not exactly string)");
     else
     {
         const std::string expected = R"(Type 'A' could not be converted into 'B'
@@ -2203,7 +2184,7 @@ local b: B = a
     LUAU_REQUIRE_ERRORS(result);
 
     if (FFlag::DebugLuauDeferredConstraintResolution)
-        CHECK(toString(result.errors.at(0)) == R"(Type 'a' could not be converted into 'B'; at ["b"]["y"], number is not exactly string)");
+        CHECK(toString(result.errors.at(0)) == R"(Type 'a' could not be converted into 'B'; at [read "b"][read "y"], number is not exactly string)");
     else
     {
         const std::string expected = R"(Type 'A' could not be converted into 'B'
@@ -3979,15 +3960,18 @@ TEST_CASE_FIXTURE(Fixture, "identify_all_problematic_table_fields")
 
     LUAU_REQUIRE_ERROR_COUNT(1, result);
 
-    std::string expected = "Type 'a' could not be converted into 'T'; at [\"a\"], string is not exactly number"
-                           "\n\tat [\"b\"], boolean is not exactly string"
-                           "\n\tat [\"c\"], number is not exactly boolean";
+    std::string expected = "Type 'a' could not be converted into 'T'; at [read \"a\"], string is not exactly number"
+                           "\n\tat [read \"b\"], boolean is not exactly string"
+                           "\n\tat [read \"c\"], number is not exactly boolean";
     CHECK(toString(result.errors[0]) == expected);
 }
 
 TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
 {
-    ScopedFastFlag sff{FFlag::LuauReadWritePropertySyntax, true};
+    ScopedFastFlag sff[] = {
+        {FFlag::LuauReadWritePropertySyntax, true},
+        {FFlag::DebugLuauDeferredConstraintResolution, false},
+    };
 
     CheckResult result = check(R"(
         type W = {read x: number}
@@ -4026,6 +4010,22 @@ TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
     CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
 }
 
+TEST_CASE_FIXTURE(Fixture, "infer_write_property")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CheckResult result = check(R"(
+        function f(t)
+            t.y = 1
+        end
+    )");
+
+    LUAU_REQUIRE_NO_ERRORS(result);
+
+    // CHECK("({ y: number }) -> ()" == toString(requireType("f")));
+    CHECK("({ y: number & unknown }) -> ()" == toString(requireType("f")));
+}
+
 TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
 {
     CheckResult result = check(R"(
@@ -4057,4 +4057,132 @@ TEST_CASE_FIXTURE(Fixture, "table_subtyping_error_suppression")
     }
 }
 
+TEST_CASE_FIXTURE(Fixture, "write_to_read_only_property")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CheckResult result = check(R"(
+        function f(t: {read x: number})
+            t.x = 5
+        end
+    )");
+
+    LUAU_REQUIRE_ERROR_COUNT(1, result);
+
+    CHECK("Property x of table '{ read x: number }' is read-only" == toString(result.errors[0]));
+
+    PropertyAccessViolation* pav = get<PropertyAccessViolation>(result.errors[0]);
+    REQUIRE(pav);
+
+    CHECK("{ read x: number }" == toString(pav->table, {true}));
+    CHECK("x" == pav->key);
+    CHECK(PropertyAccessViolation::CannotWrite == pav->context);
+}
+
+TEST_CASE_FIXTURE(Fixture, "write_to_unusually_named_read_only_property")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CheckResult result = check(R"(
+        function f(t: {read ["hello world"]: number})
+            t["hello world"] = 5
+        end
+    )");
+
+    LUAU_REQUIRE_ERROR_COUNT(1, result);
+
+    CHECK("Property \"hello world\" of table '{ read [\"hello world\"]: number }' is read-only" == toString(result.errors[0]));
+}
+
+TEST_CASE_FIXTURE(Fixture, "write_annotations_are_unsupported_even_with_the_new_solver")
+{
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CheckResult result = check(R"(
+        function f(t: {write foo: number})
+        end
+    )");
+
+    LUAU_REQUIRE_ERROR_COUNT(1, result);
+
+    CHECK("write keyword is illegal here" == toString(result.errors[0]));
+    CHECK(Location{{1, 23}, {1, 28}} == result.errors[0].location);
+}
+
+TEST_CASE_FIXTURE(Fixture, "read_and_write_only_table_properties_are_unsupported")
+{
+    ScopedFastFlag sff[] = {
+        {FFlag::LuauReadWritePropertySyntax, true},
+        {FFlag::DebugLuauDeferredConstraintResolution, false}
+    };
+
+    CheckResult result = check(R"(
+        type W = {read x: number}
+        type X = {write x: boolean}
+
+        type Y = {read ["prop"]: boolean}
+        type Z = {write ["prop"]: string}
+    )");
+
+    LUAU_REQUIRE_ERROR_COUNT(4, result);
+
+    CHECK("read keyword is illegal here" == toString(result.errors[0]));
+    CHECK(Location{{1, 18}, {1, 22}} == result.errors[0].location);
+    CHECK("write keyword is illegal here" == toString(result.errors[1]));
+    CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
+    CHECK("read keyword is illegal here" == toString(result.errors[2]));
+    CHECK(Location{{4, 18}, {4, 22}} == result.errors[2].location);
+    CHECK("write keyword is illegal here" == toString(result.errors[3]));
+    CHECK(Location{{5, 18}, {5, 23}} == result.errors[3].location);
+}
+
+TEST_CASE_FIXTURE(Fixture, "read_ond_write_only_indexers_are_unsupported")
+{
+    ScopedFastFlag sff[] = {
+        {FFlag::LuauReadWritePropertySyntax, true},
+        {FFlag::DebugLuauDeferredConstraintResolution, false}
+    };
+
+    CheckResult result = check(R"(
+        type T = {read [string]: number}
+        type U = {write [string]: boolean}
+    )");
+
+    LUAU_REQUIRE_ERROR_COUNT(2, result);
+
+    CHECK("read keyword is illegal here" == toString(result.errors[0]));
+    CHECK(Location{{1, 18}, {1, 22}} == result.errors[0].location);
+    CHECK("write keyword is illegal here" == toString(result.errors[1]));
+    CHECK(Location{{2, 18}, {2, 23}} == result.errors[1].location);
+}
+
+TEST_CASE_FIXTURE(Fixture, "table_writes_introduce_write_properties")
+{
+    ScopedFastFlag sff[] = {
+        {FFlag::LuauReadWritePropertySyntax, true},
+        {FFlag::DebugLuauDeferredConstraintResolution, true}
+    };
+
+    CheckResult result = check(R"(
+        function oc(player, speaker)
+            local head = speaker.Character:FindFirstChild('Head')
+            speaker.Character = player[1].Character
+        end
+    )");
+
+    LUAU_REQUIRE_NO_ERRORS(result);
+
+    CHECK("<a, b, c...>({{ read Character: a }}, { Character: t1 }) -> () "
+        "where "
+        "t1 = a & { read FindFirstChild: (t1, string) -> (b, c...) }" == toString(requireType("oc")));
+
+// We currently get
+// <a, b, c...>({{ read Character: a }}, { Character: t1 }) -> () where t1 = { read FindFirstChild: (t1, string) -> (b, c...) }
+
+// But we'd like to see
+// <a, b...>({{ read Character: t1 }}, { Character: t1 }) -> () where t1 = { read FindFirstChild: (t1, string) -> (a, b...) }
+
+// The type of speaker.Character should be the same as player[1].Character
+}
+
 TEST_SUITE_END();
diff --git a/tests/TypePath.test.cpp b/tests/TypePath.test.cpp
index 697c2540..c5a7a2bb 100644
--- a/tests/TypePath.test.cpp
+++ b/tests/TypePath.test.cpp
@@ -15,8 +15,19 @@
 using namespace Luau;
 using namespace Luau::TypePath;
 
+LUAU_FASTFLAG(DebugLuauDeferredConstraintResolution);
 LUAU_DYNAMIC_FASTINT(LuauTypePathMaximumTraverseSteps);
 
+struct TypePathFixture : Fixture
+{
+    ScopedFastFlag sff1{FFlag::DebugLuauDeferredConstraintResolution, true};
+};
+
+struct TypePathBuiltinsFixture : BuiltinsFixture
+{
+    ScopedFastFlag sff1{FFlag::DebugLuauDeferredConstraintResolution, true};
+};
+
 TEST_SUITE_BEGIN("TypePathManipulation");
 
 TEST_CASE("append")
@@ -95,12 +106,12 @@ TEST_SUITE_BEGIN("TypePathTraversal");
         LUAU_REQUIRE_NO_ERRORS(result); \
     } while (false);
 
-TEST_CASE_FIXTURE(Fixture, "empty_traversal")
+TEST_CASE_FIXTURE(TypePathFixture, "empty_traversal")
 {
     CHECK(traverseForType(builtinTypes->numberType, kEmpty, builtinTypes) == builtinTypes->numberType);
 }
 
-TEST_CASE_FIXTURE(Fixture, "table_property")
+TEST_CASE_FIXTURE(TypePathFixture, "table_property")
 {
     TYPESOLVE_CODE(R"(
         local x = { y = 123 }
@@ -114,7 +125,7 @@ TEST_CASE_FIXTURE(ClassFixture, "class_property")
     CHECK(traverseForType(vector2InstanceType, Path(TypePath::Property{"X", true}), builtinTypes) == builtinTypes->numberType);
 }
 
-TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_property")
+TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "metatable_property")
 {
     SUBCASE("meta_does_not_contribute")
     {
@@ -138,10 +149,10 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "metatable_property")
         )");
     }
 
-    CHECK(traverseForType(requireType("x"), Path(TypePath::Property("x")), builtinTypes) == builtinTypes->numberType);
+    CHECK(traverseForType(requireType("x"), Path(TypePath::Property::read("x")), builtinTypes) == builtinTypes->numberType);
 }
 
-TEST_CASE_FIXTURE(Fixture, "index")
+TEST_CASE_FIXTURE(TypePathFixture, "index")
 {
     SUBCASE("unions")
     {
@@ -242,7 +253,7 @@ TEST_CASE_FIXTURE(ClassFixture, "metatables")
     }
 }
 
-TEST_CASE_FIXTURE(Fixture, "bounds")
+TEST_CASE_FIXTURE(TypePathFixture, "bounds")
 {
     SUBCASE("free_type")
     {
@@ -274,7 +285,7 @@ TEST_CASE_FIXTURE(Fixture, "bounds")
     }
 }
 
-TEST_CASE_FIXTURE(Fixture, "indexers")
+TEST_CASE_FIXTURE(TypePathFixture, "indexers")
 {
     SUBCASE("table")
     {
@@ -308,7 +319,7 @@ TEST_CASE_FIXTURE(Fixture, "indexers")
     // TODO: Class types
 }
 
-TEST_CASE_FIXTURE(Fixture, "negated")
+TEST_CASE_FIXTURE(TypePathFixture, "negated")
 {
     SUBCASE("valid")
     {
@@ -327,7 +338,7 @@ TEST_CASE_FIXTURE(Fixture, "negated")
     }
 }
 
-TEST_CASE_FIXTURE(Fixture, "variadic")
+TEST_CASE_FIXTURE(TypePathFixture, "variadic")
 {
     SUBCASE("valid")
     {
@@ -346,7 +357,7 @@ TEST_CASE_FIXTURE(Fixture, "variadic")
     }
 }
 
-TEST_CASE_FIXTURE(Fixture, "arguments")
+TEST_CASE_FIXTURE(TypePathFixture, "arguments")
 {
     SUBCASE("function")
     {
@@ -368,7 +379,7 @@ TEST_CASE_FIXTURE(Fixture, "arguments")
     }
 }
 
-TEST_CASE_FIXTURE(Fixture, "returns")
+TEST_CASE_FIXTURE(TypePathFixture, "returns")
 {
     SUBCASE("function")
     {
@@ -391,7 +402,7 @@ TEST_CASE_FIXTURE(Fixture, "returns")
     }
 }
 
-TEST_CASE_FIXTURE(Fixture, "tail")
+TEST_CASE_FIXTURE(TypePathFixture, "tail")
 {
     SUBCASE("has_tail")
     {
@@ -422,7 +433,7 @@ TEST_CASE_FIXTURE(Fixture, "tail")
     }
 }
 
-TEST_CASE_FIXTURE(Fixture, "cycles" * doctest::timeout(0.5))
+TEST_CASE_FIXTURE(TypePathFixture, "cycles" * doctest::timeout(0.5))
 {
     // This will fail an occurs check, but it's a quick example of a cyclic type
     // where there _is_ no traversal.
@@ -451,7 +462,7 @@ TEST_CASE_FIXTURE(Fixture, "cycles" * doctest::timeout(0.5))
     }
 }
 
-TEST_CASE_FIXTURE(Fixture, "step_limit")
+TEST_CASE_FIXTURE(TypePathFixture, "step_limit")
 {
     ScopedFastInt sfi(DFInt::LuauTypePathMaximumTraverseSteps, 2);
 
@@ -466,12 +477,12 @@ TEST_CASE_FIXTURE(Fixture, "step_limit")
     )");
 
     TypeId root = requireTypeAlias("T");
-    Path path = PathBuilder().prop("x").prop("y").prop("z").build();
+    Path path = PathBuilder().readProp("x").readProp("y").readProp("z").build();
     auto result = traverseForType(root, path, builtinTypes);
     CHECK(!result);
 }
 
-TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
+TEST_CASE_FIXTURE(TypePathBuiltinsFixture, "complex_chains")
 {
     SUBCASE("add_metamethod_return_type")
     {
@@ -484,7 +495,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
         )");
 
         TypeId root = requireTypeAlias("Tab");
-        Path path = PathBuilder().mt().prop("__add").rets().index(0).build();
+        Path path = PathBuilder().mt().readProp("__add").rets().index(0).build();
         auto result = traverseForType(root, path, builtinTypes);
         CHECK(result == builtinTypes->numberType);
     }
@@ -498,7 +509,7 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "complex_chains")
         )");
 
         TypeId root = requireTypeAlias("Obj");
-        Path path = PathBuilder().prop("method").index(0).args().index(1).build();
+        Path path = PathBuilder().readProp("method").index(0).args().index(1).build();
         auto result = traverseForType(root, path, builtinTypes);
         CHECK(*result == builtinTypes->falseType);
     }
@@ -510,6 +521,10 @@ TEST_SUITE_BEGIN("TypePathToString");
 
 TEST_CASE("field")
 {
+    ScopedFastFlag sff[] = {
+        {FFlag::DebugLuauDeferredConstraintResolution, false},
+    };
+
     CHECK(toString(PathBuilder().prop("foo").build()) == R"(["foo"])");
 }
 
@@ -535,10 +550,26 @@ TEST_CASE("empty_path")
 
 TEST_CASE("prop")
 {
+    ScopedFastFlag sff[] = {
+        {FFlag::DebugLuauDeferredConstraintResolution, false},
+    };
+
     Path p = PathBuilder().prop("foo").build();
     CHECK(p == Path(TypePath::Property{"foo"}));
 }
 
+TEST_CASE_FIXTURE(TypePathFixture, "readProp")
+{
+    Path p = PathBuilder().readProp("foo").build();
+    CHECK(p == Path(TypePath::Property::read("foo")));
+}
+
+TEST_CASE_FIXTURE(TypePathFixture, "writeProp")
+{
+    Path p = PathBuilder().writeProp("foo").build();
+    CHECK(p == Path(TypePath::Property::write("foo")));
+}
+
 TEST_CASE("index")
 {
     Path p = PathBuilder().index(0).build();
@@ -561,8 +592,10 @@ TEST_CASE("fields")
 
 TEST_CASE("chained")
 {
-    CHECK(PathBuilder().index(0).prop("foo").mt().prop("bar").args().index(1).build() ==
-          Path({Index{0}, TypePath::Property{"foo"}, TypeField::Metatable, TypePath::Property{"bar"}, PackField::Arguments, Index{1}}));
+    ScopedFastFlag sff{FFlag::DebugLuauDeferredConstraintResolution, true};
+
+    CHECK(PathBuilder().index(0).readProp("foo").mt().readProp("bar").args().index(1).build() ==
+          Path({Index{0}, TypePath::Property::read("foo"), TypeField::Metatable, TypePath::Property::read("bar"), PackField::Arguments, Index{1}}));
 }
 
 TEST_SUITE_END(); // TypePathBuilder
diff --git a/tests/Unifier2.test.cpp b/tests/Unifier2.test.cpp
index 56edb4a7..842f9e06 100644
--- a/tests/Unifier2.test.cpp
+++ b/tests/Unifier2.test.cpp
@@ -76,13 +76,13 @@ TEST_CASE_FIXTURE(Unifier2Fixture, "T <: U")
 
     CHECK(u2.unify(left, right));
 
-    CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(left));
-    CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(right));
+    CHECK("'a" == toString(left));
+    CHECK("'a" == toString(right));
 
     CHECK("never" == toString(freeLeft->lowerBound));
-    CHECK("t1 where t1 = (('a <: t1) <: 'b)" == toString(freeLeft->upperBound));
+    CHECK("unknown" == toString(freeLeft->upperBound));
 
-    CHECK("t1 where t1 = ('a <: (t1 <: 'b))" == toString(freeRight->lowerBound));
+    CHECK("never" == toString(freeRight->lowerBound));
     CHECK("unknown" == toString(freeRight->upperBound));
 }
 
diff --git a/tests/conformance/native.lua b/tests/conformance/native.lua
index 9df7a3bb..6ebe85d6 100644
--- a/tests/conformance/native.lua
+++ b/tests/conformance/native.lua
@@ -293,6 +293,24 @@ end
 
 assert(loopIteratorProtocol(0, table.create(100, 5)) == 5058)
 
+function valueTrackingIssue1()
+  local b = buffer.create(1)
+  buffer.writeu8(b, 0, 0)
+  local v1
+
+  local function closure()
+    assert(type(b) == "buffer") -- b is the first upvalue
+    v1 = nil -- v1 is the second upvalue
+
+    -- prevent inlining
+    for i = 1, 100 do print(`{b} is {b}`) end
+  end
+
+  closure()
+end
+
+valueTrackingIssue1()
+
 local function vec3compsum(a: vector)
   return a.X + a.Y + a.Z
 end
diff --git a/tools/faillist.txt b/tools/faillist.txt
index dd51634b..89675715 100644
--- a/tools/faillist.txt
+++ b/tools/faillist.txt
@@ -1,4 +1,3 @@
-AnnotationTests.typeof_expr
 AstQuery.last_argument_function_call_type
 AutocompleteTest.anonymous_autofilled_generic_on_argument_type_pack_vararg
 AutocompleteTest.anonymous_autofilled_generic_type_pack_vararg
@@ -13,7 +12,6 @@ BuiltinTests.assert_removes_falsy_types_even_from_type_pack_tail_but_only_for_th
 BuiltinTests.assert_returns_false_and_string_iff_it_knows_the_first_argument_cannot_be_truthy
 BuiltinTests.bad_select_should_not_crash
 BuiltinTests.coroutine_resume_anything_goes
-BuiltinTests.debug_info_is_crazy
 BuiltinTests.global_singleton_types_are_sealed
 BuiltinTests.gmatch_capture_types
 BuiltinTests.gmatch_capture_types2
@@ -67,24 +65,12 @@ DefinitionTests.class_definition_string_props
 DefinitionTests.declaring_generic_functions
 DefinitionTests.definition_file_classes
 Differ.equal_generictp_cyclic
-Differ.equal_table_A_B_C
-Differ.equal_table_cyclic_diamonds_unraveled
-Differ.equal_table_kind_A
-Differ.equal_table_kind_B
-Differ.equal_table_kind_C
-Differ.equal_table_kind_D
-Differ.equal_table_measuring_tapes
-Differ.equal_table_two_shifted_circles_are_not_different
 Differ.generictp_normal
 Differ.generictp_normal_2
-Differ.left_cyclic_table_right_table_missing_property
-Differ.left_cyclic_table_right_table_property_wrong
 Differ.metatable_metamissing_left
 Differ.metatable_metamissing_right
 Differ.metatable_metanormal
 Differ.negation
-Differ.right_cyclic_table_left_table_property_wrong
-Differ.table_left_circle_right_measuring_tape
 FrontendTest.accumulate_cached_errors_in_consistent_order
 FrontendTest.environments
 FrontendTest.imported_table_modification_2
@@ -94,47 +80,37 @@ FrontendTest.trace_requires_in_nonstrict_mode
 GenericsTests.apply_type_function_nested_generics1
 GenericsTests.better_mismatch_error_messages
 GenericsTests.bound_tables_do_not_clone_original_fields
-GenericsTests.check_generic_function
-GenericsTests.check_generic_local_function
-GenericsTests.check_mutual_generic_functions
-GenericsTests.check_mutual_generic_functions_errors
-GenericsTests.check_mutual_generic_functions_unannotated
-GenericsTests.check_nested_generic_function
-GenericsTests.check_recursive_generic_function
 GenericsTests.correctly_instantiate_polymorphic_member_functions
 GenericsTests.do_not_always_instantiate_generic_intersection_types
+GenericsTests.do_not_infer_generic_functions
+GenericsTests.dont_leak_generic_types
+GenericsTests.dont_leak_inferred_generic_types
 GenericsTests.dont_substitute_bound_types
 GenericsTests.error_detailed_function_mismatch_generic_pack
 GenericsTests.error_detailed_function_mismatch_generic_types
 GenericsTests.factories_of_generics
-GenericsTests.function_arguments_can_be_polytypes
 GenericsTests.generic_argument_count_too_few
 GenericsTests.generic_argument_count_too_many
 GenericsTests.generic_factories
 GenericsTests.generic_functions_dont_cache_type_parameters
 GenericsTests.generic_functions_in_types
+GenericsTests.generic_type_families_work_in_subtyping
 GenericsTests.generic_type_pack_parentheses
 GenericsTests.generic_type_pack_unification1
 GenericsTests.generic_type_pack_unification2
 GenericsTests.generic_type_pack_unification3
 GenericsTests.higher_rank_polymorphism_should_not_accept_instantiated_arguments
 GenericsTests.hof_subtype_instantiation_regression
-GenericsTests.infer_generic_function
+GenericsTests.infer_generic_function_function_argument
 GenericsTests.infer_generic_function_function_argument_2
 GenericsTests.infer_generic_function_function_argument_3
 GenericsTests.infer_generic_function_function_argument_overloaded
 GenericsTests.infer_generic_lib_function_function_argument
-GenericsTests.infer_generic_local_function
-GenericsTests.infer_generic_property
-GenericsTests.infer_nested_generic_function
-GenericsTests.inferred_local_vars_can_be_polytypes
 GenericsTests.instantiated_function_argument_names
-GenericsTests.local_vars_can_be_polytypes
+GenericsTests.mutable_state_polymorphism
 GenericsTests.no_stack_overflow_from_quantifying
 GenericsTests.properties_can_be_instantiated_polytypes
-GenericsTests.properties_can_be_polytypes
 GenericsTests.quantify_functions_even_if_they_have_an_explicit_generic
-GenericsTests.rank_N_types_via_typeof
 GenericsTests.self_recursive_instantiated_param
 GenericsTests.type_parameters_can_be_polytypes
 GenericsTests.typefuns_sharing_types
@@ -164,7 +140,6 @@ IntersectionTypes.overloadeded_functions_with_weird_typepacks_3
 IntersectionTypes.overloadeded_functions_with_weird_typepacks_4
 IntersectionTypes.table_write_sealed_indirect
 IntersectionTypes.union_saturate_overloaded_functions
-Linter.DeprecatedApiFenv
 Linter.FormatStringTyped
 Linter.TableOperationsIndexer
 ModuleTests.clone_self_property
@@ -210,16 +185,12 @@ RefinementTest.discriminate_from_isa_of_x
 RefinementTest.discriminate_from_truthiness_of_x
 RefinementTest.discriminate_tag
 RefinementTest.discriminate_tag_with_implicit_else
-RefinementTest.else_with_no_explicit_expression_should_also_refine_the_tagged_union
 RefinementTest.fail_to_refine_a_property_of_subscript_expression
 RefinementTest.falsiness_of_TruthyPredicate_narrows_into_nil
-RefinementTest.function_call_with_colon_after_refining_not_to_be_nil
 RefinementTest.globals_can_be_narrowed_too
 RefinementTest.impossible_type_narrow_is_not_an_error
 RefinementTest.index_on_a_refined_property
 RefinementTest.isa_type_refinement_must_be_known_ahead_of_time
-RefinementTest.luau_polyfill_isindexkey_refine_conjunction
-RefinementTest.luau_polyfill_isindexkey_refine_conjunction_variant
 RefinementTest.narrow_property_of_a_bounded_variable
 RefinementTest.nonoptional_type_can_narrow_to_nil_if_sense_is_true
 RefinementTest.not_t_or_some_prop_of_t
@@ -245,7 +216,6 @@ TableTests.any_when_indexing_into_an_unsealed_table_with_no_indexer_in_nonstrict
 TableTests.array_factory_function
 TableTests.casting_tables_with_props_into_table_with_indexer2
 TableTests.casting_tables_with_props_into_table_with_indexer3
-TableTests.casting_tables_with_props_into_table_with_indexer4
 TableTests.casting_unsealed_tables_with_props_into_table_with_indexer
 TableTests.checked_prop_too_early
 TableTests.cli_84607_missing_prop_in_array_or_dict
@@ -258,12 +228,10 @@ TableTests.common_table_element_union_in_call
 TableTests.common_table_element_union_in_call_tail
 TableTests.common_table_element_union_in_prop
 TableTests.confusing_indexing
-TableTests.cyclic_shifted_tables
 TableTests.disallow_indexing_into_an_unsealed_table_with_no_indexer_in_strict_mode
 TableTests.dont_crash_when_setmetatable_does_not_produce_a_metatabletypevar
 TableTests.dont_extend_unsealed_tables_in_rvalue_position
 TableTests.dont_leak_free_table_props
-TableTests.dont_quantify_table_that_belongs_to_outer_scope
 TableTests.dont_suggest_exact_match_keys
 TableTests.error_detailed_indexer_key
 TableTests.error_detailed_indexer_value
@@ -275,8 +243,8 @@ TableTests.generalize_table_argument
 TableTests.generic_table_instantiation_potential_regression
 TableTests.indexer_mismatch
 TableTests.indexers_get_quantified_too
+TableTests.indexing_from_a_table_should_prefer_properties_when_possible
 TableTests.inequality_operators_imply_exactly_matching_types
-TableTests.infer_indexer_from_array_like_table
 TableTests.infer_indexer_from_its_variable_type_and_unifiable
 TableTests.inferred_return_type_of_free_table
 TableTests.instantiate_table_cloning_3
@@ -286,7 +254,7 @@ TableTests.length_operator_intersection
 TableTests.length_operator_non_table_union
 TableTests.length_operator_union
 TableTests.less_exponential_blowup_please
-TableTests.meta_add_both_ways
+TableTests.meta_add
 TableTests.meta_add_inferred
 TableTests.metatable_mismatch_should_fail
 TableTests.missing_metatable_for_sealed_tables_do_not_get_inferred
@@ -300,8 +268,6 @@ TableTests.oop_polymorphic
 TableTests.open_table_unification_2
 TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table
 TableTests.pass_a_union_of_tables_to_a_function_that_requires_a_table_2
-TableTests.pass_incompatible_union_to_a_generic_table_without_crashing
-TableTests.passing_compatible_unions_to_a_generic_table_without_crashing
 TableTests.persistent_sealed_table_is_immutable
 TableTests.prop_access_on_key_whose_types_mismatches
 TableTests.prop_access_on_unions_of_indexers_where_key_whose_types_mismatches
@@ -320,12 +286,10 @@ TableTests.shared_selfs_from_free_param
 TableTests.shared_selfs_through_metatables
 TableTests.table_call_metamethod_basic
 TableTests.table_call_metamethod_must_be_callable
-TableTests.table_function_check_use_after_free
 TableTests.table_param_width_subtyping_2
 TableTests.table_param_width_subtyping_3
 TableTests.table_simple_call
 TableTests.table_subtyping_with_extra_props_dont_report_multiple_errors
-TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors
 TableTests.table_subtyping_with_missing_props_dont_report_multiple_errors2
 TableTests.table_unification_4
 TableTests.table_unifies_into_map
@@ -351,7 +315,6 @@ TryUnifyTests.members_of_failed_typepack_unification_are_unified_with_errorType
 TryUnifyTests.result_of_failed_typepack_unification_is_constrained
 TryUnifyTests.uninhabited_table_sub_anything
 TryUnifyTests.uninhabited_table_sub_never
-TryUnifyTests.variadics_should_use_reversed_properly
 TypeAliases.dont_lose_track_of_PendingExpansionTypes_after_substitution
 TypeAliases.generic_param_remap
 TypeAliases.mismatched_generic_type_param
@@ -376,10 +339,10 @@ TypeFamilyTests.internal_families_raise_errors
 TypeFamilyTests.table_internal_families
 TypeFamilyTests.type_families_inhabited_with_normalization
 TypeFamilyTests.unsolvable_family
+TypeInfer.be_sure_to_use_active_txnlog_when_evaluating_a_variadic_overload
 TypeInfer.bidirectional_checking_of_callback_property
 TypeInfer.check_type_infer_recursion_count
 TypeInfer.checking_should_not_ice
-TypeInfer.cli_39932_use_unifier_in_ensure_methods
 TypeInfer.cli_50041_committing_txnlog_in_apollo_client_error
 TypeInfer.dont_ice_when_failing_the_occurs_check
 TypeInfer.dont_report_type_errors_within_an_AstExprError
@@ -447,7 +410,6 @@ TypeInferFunctions.function_exprs_are_generalized_at_signature_scope_not_enclosi
 TypeInferFunctions.function_is_supertype_of_concrete_functions
 TypeInferFunctions.function_statement_sealed_table_assignment_through_indexer
 TypeInferFunctions.generic_packs_are_not_variadic
-TypeInferFunctions.higher_order_function_2
 TypeInferFunctions.higher_order_function_4
 TypeInferFunctions.improved_function_arg_mismatch_error_nonstrict
 TypeInferFunctions.improved_function_arg_mismatch_errors
@@ -469,7 +431,6 @@ TypeInferFunctions.other_things_are_not_related_to_function
 TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible
 TypeInferFunctions.param_1_and_2_both_takes_the_same_generic_but_their_arguments_are_incompatible_2
 TypeInferFunctions.record_matching_overload
-TypeInferFunctions.regex_benchmark_string_format_minimization
 TypeInferFunctions.report_exiting_without_return_nonstrict
 TypeInferFunctions.return_type_by_overload
 TypeInferFunctions.too_few_arguments_variadic
@@ -492,8 +453,6 @@ TypeInferLoops.for_in_loop_with_custom_iterator
 TypeInferLoops.for_in_loop_with_incompatible_args_to_iterator
 TypeInferLoops.for_in_loop_with_next
 TypeInferLoops.for_in_with_an_iterator_of_type_any
-TypeInferLoops.for_in_with_generic_next
-TypeInferLoops.for_in_with_just_one_iterator_is_ok
 TypeInferLoops.for_loop
 TypeInferLoops.ipairs_produces_integral_indices
 TypeInferLoops.iterate_over_free_table
@@ -511,7 +470,6 @@ TypeInferLoops.properly_infer_iteratee_is_a_free_table
 TypeInferLoops.repeat_loop
 TypeInferLoops.varlist_declared_by_for_in_loop_should_be_free
 TypeInferLoops.while_loop
-TypeInferModules.bound_free_table_export_is_ok
 TypeInferModules.custom_require_global
 TypeInferModules.do_not_modify_imported_types
 TypeInferModules.do_not_modify_imported_types_5
@@ -521,7 +479,6 @@ TypeInferOOP.cycle_between_object_constructor_and_alias
 TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_another_overload_works
 TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_it_wont_help_2
 TypeInferOOP.dont_suggest_using_colon_rather_than_dot_if_not_defined_with_colon
-TypeInferOOP.inferred_methods_of_free_tables_have_the_same_level_as_the_enclosing_table
 TypeInferOOP.inferring_hundreds_of_self_calls_should_not_suffocate_memory
 TypeInferOOP.methods_are_topologically_sorted
 TypeInferOOP.object_constructor_can_refer_to_method_of_self
@@ -547,7 +504,6 @@ TypeInferOperators.strict_binary_op_where_lhs_unknown
 TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection
 TypeInferOperators.typecheck_overloaded_multiply_that_is_an_intersection_on_rhs
 TypeInferOperators.typecheck_unary_len_error
-TypeInferOperators.typecheck_unary_minus
 TypeInferOperators.typecheck_unary_minus_error
 TypeInferOperators.UnknownGlobalCompoundAssign
 TypeInferPrimitives.CheckMethodsOfNumber
@@ -564,7 +520,6 @@ TypePackTests.pack_tail_unification_check
 TypePackTests.type_alias_backwards_compatible
 TypePackTests.type_alias_default_type_errors
 TypePackTests.type_alias_type_packs_import
-TypePackTests.type_packs_with_tails_in_vararg_adjustment
 TypePackTests.unify_variadic_tails_in_arguments
 TypeSingletons.enums_using_singletons_mismatch
 TypeSingletons.error_detailed_tagged_union_mismatch_bool
diff --git a/tools/lldb_formatters.py b/tools/lldb_formatters.py
index 30654af3..8238cc90 100644
--- a/tools/lldb_formatters.py
+++ b/tools/lldb_formatters.py
@@ -385,7 +385,7 @@ def luau_typepath_property_summary(valobj, internal_dict, options):
     read_write = False
     try:
         fflag_valobj = valobj.GetFrame().GetValueForVariablePath(
-            "FFlag::DebugLuauReadWriteProperties::value")
+            "FFlag::DebugLuauDeferredConstraintResolution::value")
 
         read_write = fflag_valobj.GetValue() == "true"
     except Exception as e:
diff --git a/tools/test_dcr.py b/tools/test_dcr.py
index 3598b02c..0a23587a 100644
--- a/tools/test_dcr.py
+++ b/tools/test_dcr.py
@@ -107,12 +107,6 @@ def main():
         action="store_true",
         help="Write a new faillist.txt after running tests.",
     )
-    parser.add_argument(
-        "--rwp",
-        dest="rwp",
-        action="store_true",
-        help="Run the tests with read-write properties enabled.",
-    )
     parser.add_argument(
         "--ts",
         dest="suite",
@@ -135,8 +129,6 @@ def main():
     failList = loadFailList()
 
     flags = ["true", "DebugLuauDeferredConstraintResolution"]
-    if args.rwp:
-        flags.append("DebugLuauReadWriteProperties")
 
     commandLine = [args.path, "--reporters=xml", "--fflags=" + ",".join(flags)]