diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index c6b4a8288..dfb0429d7 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -98,6 +98,13 @@ struct ConstraintSolver DenseHashSet generalizedTypes_{nullptr}; const NotNull> generalizedTypes{&generalizedTypes_}; + // The current Constraint that is being processed, can be nullptr. + const Constraint* currentConstraintRef; + + // Offset of current pushed constraints + // Used to ensure things are pushes in an order within a vector. + int curUnsolvedConstraintPushOffset; + // Recorded errors that take place within the solver. ErrorVec errors; @@ -296,6 +303,21 @@ struct ConstraintSolver **/ NotNull pushConstraint(NotNull scope, const Location& location, ConstraintV cv); + /** Push a Constraint right at the position after a specific constraint. + * @param cv the body of the constraint. + * @param afterConstraint The constraint to find in unsolvedConstraints to insert the new constraint after at. + * @param b_isFromRecursive + Whether this function is being called from the current dispatch through a recursive context + to apply insert offset. + **/ + NotNull pushConstraintAfter( + NotNull scope, + const Location& location, + ConstraintV cv, + const Constraint& afterConstraint, + bool b_isFromRecursive = false + ); + /** * Attempts to resolve a module from its module information. Returns the * module-level return type of the module, or the error type if one cannot diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index ae02c60ae..5fb0811fc 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -305,7 +305,7 @@ struct InstantiationQueuer : TypeOnceVisitor bool visit(TypeId ty, const TypeFunctionInstanceType&) override { - solver->pushConstraint(scope, location, ReduceConstraint{ty}); + solver->pushConstraintAfter(scope, location, ReduceConstraint{ty}, *solver->currentConstraintRef, true); return true; } @@ -426,6 +426,11 @@ void ConstraintSolver::run() snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints); } + // Set current constraint + // This is used for pushing new Constraints with "pushConstraintAfter" + currentConstraintRef = c.get(); + curUnsolvedConstraintPushOffset = 0; // Reset + bool success = tryDispatch(c, force); progress |= success; @@ -926,7 +931,9 @@ bool ConstraintSolver::tryDispatch(const TypeAliasExpansionConstraint& c, NotNul // Adding ReduceConstraint on type function for the constraint solver if (auto typeFn = get(follow(tf->type))) - pushConstraint(NotNull(constraint->scope.get()), constraint->location, ReduceConstraint{tf->type}); + pushConstraintAfter( + NotNull(constraint->scope.get()), constraint->location, ReduceConstraint{tf->type}, *constraint.get() + ); // If there are no parameters to the type function we can just use the type // directly. @@ -2852,6 +2859,47 @@ NotNull ConstraintSolver::pushConstraint(NotNull scope, const return borrow; } +NotNull ConstraintSolver::pushConstraintAfter( + NotNull scope, + const Location& location, + ConstraintV cv, + const Constraint& afterConstraint, + bool b_isFromRecursive // optional +) +{ + std::unique_ptr c = std::make_unique(scope, location, std::move(cv)); + NotNull borrow = NotNull(c.get()); + + // Get the location of the constraint from the unsolvedConstraints. + auto it = std::find(unsolvedConstraints.begin(), unsolvedConstraints.end(), NotNull(&afterConstraint)); + // If not at the end + if (it != unsolvedConstraints.end()) + { + // Increment index by 1, to insert after found constraint. + it += 1; + + if (b_isFromRecursive) + { + // Increment based on offset + // Because they get inserted like so C, B, A + // And we want A, B, C + // for some reason I have to +1 this as well to work + it += curUnsolvedConstraintPushOffset + 1; + + // Increase offset + // This resets every dispatch. + curUnsolvedConstraintPushOffset += 1; + } + } + else + LUAU_ASSERT("The provided \"afterConstraint\" was not found in \"unsolvedConstraints\"."); + + solverConstraints.push_back(std::move(c)); + unsolvedConstraints.insert(it, borrow); + + return borrow; +} + TypeId ConstraintSolver::resolveModule(const ModuleInfo& info, const Location& location) { if (info.name.empty()) diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 2b7b40a3c..087e38c1a 100644 --- a/tests/TypeFunction.test.cpp +++ b/tests/TypeFunction.test.cpp @@ -953,6 +953,36 @@ TEST_CASE_FIXTURE(BuiltinsFixture, "index_wait_for_pending_no_crash") // Should not crash! } +TEST_CASE_FIXTURE(BuiltinsFixture, "keyof_andGeneralTypeFunction_dependency_issue1") +{ + if (!FFlag::LuauSolverV2) + return; + + // Explanation https://devforum.roblox.com/t/new-type-solver-beta/3155804/97 + + CheckResult result = check(R"( + --!strict + + local PlayerData = { + Coins = 0, + Level = 1, + Exp = 0, + MaxExp = 100 + } + + type Keys = keyof + + -- This function makes it think that there's going to be a pending expansion + local function UpdateData(key: Keys, value) + PlayerData[key] = value + end + + UpdateData("Coins", 2) + )"); + + LUAU_REQUIRE_NO_ERRORS(result); +} + TEST_CASE_FIXTURE(BuiltinsFixture, "index_type_function_works_w_array") { if (!FFlag::LuauSolverV2)