diff --git a/Analysis/include/Luau/ConstraintSolver.h b/Analysis/include/Luau/ConstraintSolver.h index c6b4a8288..308b983b0 100644 --- a/Analysis/include/Luau/ConstraintSolver.h +++ b/Analysis/include/Luau/ConstraintSolver.h @@ -98,6 +98,9 @@ struct ConstraintSolver DenseHashSet generalizedTypes_{nullptr}; const NotNull> generalizedTypes{&generalizedTypes_}; + // The current Constraint that is being processed, can be nullptr. + const Constraint* currentConstraintRef; + // Recorded errors that take place within the solver. ErrorVec errors; diff --git a/Analysis/src/ConstraintSolver.cpp b/Analysis/src/ConstraintSolver.cpp index ae02c60ae..d978ea1b3 100644 --- a/Analysis/src/ConstraintSolver.cpp +++ b/Analysis/src/ConstraintSolver.cpp @@ -284,6 +284,252 @@ void dump(ConstraintSolver* cs, ToStringOptions& opts) } } +struct TypeFinder : TypeOnceVisitor +{ + TypeId tyToFind; + bool hasFoundType = false; + + // SOLID Principle for telling that we found the type. + void makeFound(TypeId ty) + { + hasFoundType = true; + } + + // Reset the boolean + // so we don't tell that we found a type we didn't actually find. + void reset() + { + hasFoundType = false; + } + + bool visit(TypeId ty) override + { + ty = follow(ty); + + if (ty == tyToFind) + makeFound(ty); // If we found the type + + return true; + } + + // Override for IntersectionTypes + bool visit(TypeId ty, const IntersectionType& iTy) override + { + for (TypeId part : iTy.parts) + { + part = follow(part); + + if (part == tyToFind) + makeFound(part); // If we found the type + } + + return visit(ty); + } + + bool isMatch(TypeId ty) + { + reset(); + + if (ty == tyToFind) + makeFound(ty); + else + traverse(ty); + + if (hasFoundType == true) + return true; + else + return false; + } +}; + +// A way to find a type, such as a dependency through Constraints +struct ConstraintTypeFinder +{ + // TODO: Standardize Constraint Visitors? + + TypeId tyToFind; + TypeFinder typeFinder{}; + + explicit ConstraintTypeFinder(TypeId tyToFind) + : tyToFind(tyToFind) + { + typeFinder.tyToFind = tyToFind; + } + + bool visit(AssignIndexConstraint c) + { + if (typeFinder.isMatch(c.indexType)) + return true; + if (typeFinder.isMatch(c.lhsType)) + return true; + if (typeFinder.isMatch(c.propType)) + return true; + if (typeFinder.isMatch(c.rhsType)) + return true; + + return false; + } + bool visit(AssignPropConstraint c) + { + if (typeFinder.isMatch(c.lhsType)) + return true; + if (typeFinder.isMatch(c.propType)) + return true; + if (typeFinder.isMatch(c.rhsType)) + return true; + + return false; + } + bool visit(EqualityConstraint c) + { + if (typeFinder.isMatch(c.assignmentType)) + return true; + if (typeFinder.isMatch(c.resultType)) + return true; + + return false; + } + bool visit(FunctionCallConstraint c) + { + if (typeFinder.isMatch(c.fn)) + return true; + // argPacks not included + + return false; + } + bool visit(FunctionCheckConstraint c) + { + if (typeFinder.isMatch(c.fn)) + return true; + // argsPacks not included + + return false; + } + bool visit(GeneralizationConstraint c) + { + if (typeFinder.isMatch(c.generalizedType)) + return true; + if (typeFinder.isMatch(c.sourceType)) + return true; + for (auto ty : c.interiorTypes) + { + if (typeFinder.isMatch(ty)) + return true; + } + + return false; + } + bool visit(IterableConstraint c) + { + for (auto ty : c.variables) + { + if (typeFinder.isMatch(ty)) + return true; + } + + return false; + } + bool visit(NameConstraint c) + { + if (typeFinder.isMatch(c.namedType)) + return true; + + for (auto ty : c.typeParameters) + { + if (typeFinder.isMatch(ty)) + return true; + } + + return false; + } + bool visit(PackSubtypeConstraint c) + { + // ? + return false; + } + bool visit(PrimitiveTypeConstraint c) + { + if (typeFinder.isMatch(c.freeType)) + return true; + if (typeFinder.isMatch(c.primitiveType)) + return true; + // expectedType needed? + + return false; + } + bool visit(ReduceConstraint c) + { + if (typeFinder.isMatch(c.ty)) + return true; + + return false; + } + bool visit(ReducePackConstraint c) + { + // ? + return false; + } + bool visit(SubtypeConstraint c) + { + if (typeFinder.isMatch(c.subType)) + return true; + if (typeFinder.isMatch(c.superType)) + return true; + + return false; + } + bool visit(TypeAliasExpansionConstraint c) + { + if (typeFinder.isMatch(c.target)) + return true; + + return false; + } + bool visit(UnpackConstraint c) + { + // ? + return false; + } + + + // Returns true if it found the type. + bool traverseAndFind(ConstraintV cV) + { + if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + else if (auto c = get_if(&cV)) + return visit(*c); + + return false; + } +}; + struct InstantiationQueuer : TypeOnceVisitor { ConstraintSolver* solver; @@ -305,7 +551,27 @@ struct InstantiationQueuer : TypeOnceVisitor bool visit(TypeId ty, const TypeFunctionInstanceType&) override { - solver->pushConstraint(scope, location, ReduceConstraint{ty}); + auto newConstraint = solver->pushConstraint(scope, location, ReduceConstraint{ty}); + + if (solver->currentConstraintRef) + { + // Block the things that depend on the Pending Expansion that is being expanded + // On the resolution of the new Reduction Constraint + if (auto currentC = get_if(&solver->currentConstraintRef->c)) + { + auto cFinder = ConstraintTypeFinder(currentC->target); + + for (auto constraint : solver->constraints) + { + if (solver->currentConstraintRef == constraint || newConstraint == constraint) + continue; + + if (cFinder.traverseAndFind(constraint.get()->c)) + solver->block(static_cast>(newConstraint), constraint); + } + } + } + return true; } @@ -426,6 +692,9 @@ void ConstraintSolver::run() snapshot = logger->prepareStepSnapshot(rootScope, c, force, unsolvedConstraints); } + // Set current Constraint + currentConstraintRef = c.get(); + bool success = tryDispatch(c, force); progress |= success; diff --git a/tests/TypeFunction.test.cpp b/tests/TypeFunction.test.cpp index 2b7b40a3c..1398a0eb1 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; + + // Missing Dependency Fix check + + 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)