mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
324 lines
11 KiB
C++
324 lines
11 KiB
C++
//===--- CSPropagate.cpp - Constraint Propagation -------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file implements the constraint propagation algorithm in the solver.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#include "ConstraintGraph.h"
|
|
#include "ConstraintSystem.h"
|
|
#include "llvm/ADT/SetVector.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
using namespace swift;
|
|
using namespace constraints;
|
|
|
|
bool isBindOverloadDisjunction(Constraint *disjunction) {
|
|
assert(disjunction->getKind() == ConstraintKind::Disjunction &&
|
|
"Expected disjunction constraint!");
|
|
|
|
assert(!disjunction->getNestedConstraints().empty() &&
|
|
"Unexpected empty disjunction!");
|
|
|
|
auto *nested = disjunction->getNestedConstraints().front();
|
|
return nested->getKind() == ConstraintKind::BindOverload;
|
|
}
|
|
|
|
// Find the disjunction of bind overload constraints related to this
|
|
// applicable function constraint, if it exists.
|
|
Constraint *
|
|
getBindOverloadDisjunction(ConstraintSystem &CS, Constraint *applicableFn) {
|
|
assert(applicableFn->getKind() == ConstraintKind::ApplicableFunction
|
|
&& "Expected ApplicableFunction disjunction!");
|
|
auto *tyvar = applicableFn->getSecondType()->getAs<TypeVariableType>();
|
|
assert(tyvar && "Expected type variable!");
|
|
|
|
Constraint *found = nullptr;
|
|
for (auto *constraint : CS.getConstraintGraph()[tyvar].getConstraints()) {
|
|
if (constraint->getKind() == ConstraintKind::Disjunction) {
|
|
found = constraint;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
return nullptr;
|
|
|
|
#if !defined(NDEBUG)
|
|
for (auto *constraint : CS.getConstraintGraph()[tyvar].getConstraints()) {
|
|
if (constraint == found)
|
|
continue;
|
|
|
|
assert(constraint->getKind() != ConstraintKind::Disjunction
|
|
&& "Type variable is involved in more than one disjunction!");
|
|
}
|
|
#endif
|
|
|
|
// Verify the disjunction consists of BindOverload constraints.
|
|
assert(isBindOverloadDisjunction(found));
|
|
|
|
return found;
|
|
}
|
|
|
|
void ConstraintSystem::collectNeighboringBindOverloadDisjunctions(
|
|
llvm::SetVector<Constraint *> &neighbors) {
|
|
|
|
while (!ActiveConstraints.empty()) {
|
|
auto *constraint = &ActiveConstraints.front();
|
|
ActiveConstraints.pop_front();
|
|
|
|
assert(constraint->isActive() && "Expected constraints to be active?");
|
|
assert(!constraint->isDisabled() && "Unexpected disabled constraint!");
|
|
|
|
if (constraint->getKind() == ConstraintKind::Disjunction) {
|
|
if (isBindOverloadDisjunction(constraint)) {
|
|
neighbors.insert(constraint);
|
|
}
|
|
} else if (constraint->getKind() == ConstraintKind::ApplicableFunction) {
|
|
if (auto *bindDisjunction =
|
|
getBindOverloadDisjunction(*this, constraint)) {
|
|
neighbors.insert(bindDisjunction);
|
|
}
|
|
}
|
|
|
|
solverState->retireConstraint(constraint);
|
|
CG.removeConstraint(constraint);
|
|
constraint->setActive(false);
|
|
}
|
|
}
|
|
|
|
// Simplify any active constraints, returning true on success, false
|
|
// on failure.
|
|
bool ConstraintSystem::simplifyForConstraintPropagation() {
|
|
while (!ActiveConstraints.empty()) {
|
|
auto *constraint = &ActiveConstraints.front();
|
|
ActiveConstraints.pop_front();
|
|
|
|
assert(constraint->isActive()
|
|
&& "Expected constraints to be active?");
|
|
assert(!constraint->isDisabled() && "Unexpected disabled constraint!");
|
|
|
|
bool failed = false;
|
|
|
|
// Simplify this constraint.
|
|
switch (simplifyConstraint(*constraint)) {
|
|
case SolutionKind::Error:
|
|
failed = true;
|
|
LLVM_FALLTHROUGH;
|
|
|
|
case SolutionKind::Solved:
|
|
solverState->retireConstraint(constraint);
|
|
CG.removeConstraint(constraint);
|
|
break;
|
|
|
|
case SolutionKind::Unsolved:
|
|
InactiveConstraints.push_back(constraint);
|
|
break;
|
|
}
|
|
|
|
constraint->setActive(false);
|
|
|
|
if (failed)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ConstraintSystem::areBindPairConsistent(Constraint *first,
|
|
Constraint *second) {
|
|
// Set up a scope that will be torn down when we're done testing
|
|
// this constraint.
|
|
ConstraintSystem::SolverScope scope(*this);
|
|
|
|
if (TC.getLangOpts().DebugConstraintSolver) {
|
|
auto &log = getASTContext().TypeCheckerDebug->getStream();
|
|
log << "Testing constraints for consistency: ";
|
|
first->print(log, &TC.Context.SourceMgr);
|
|
log << "\nversus: ";
|
|
second->print(log, &TC.Context.SourceMgr);
|
|
}
|
|
|
|
auto result = simplifyConstraint(*first);
|
|
assert(result == ConstraintSystem::SolutionKind::Solved &&
|
|
"Expected the first bind constraint to work!");
|
|
|
|
solverState->retireConstraint(first);
|
|
solverState->addGeneratedConstraint(first);
|
|
|
|
result = simplifyConstraint(*second);
|
|
assert(result == ConstraintSystem::SolutionKind::Solved &&
|
|
"Expected the second bind constraint to work!");
|
|
|
|
solverState->retireConstraint(second);
|
|
solverState->addGeneratedConstraint(second);
|
|
|
|
auto success = simplifyForConstraintPropagation();
|
|
if (TC.getLangOpts().DebugConstraintSolver) {
|
|
auto &log = getASTContext().TypeCheckerDebug->getStream();
|
|
if (success)
|
|
log << "Consistent!\n";
|
|
else
|
|
log << "Not consistent!\n";
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
// Test a bind overload constraint to see if it is consistent with the
|
|
// rest of the constraint system.
|
|
bool ConstraintSystem::isBindOverloadConsistent(
|
|
Constraint *bindConstraint, llvm::SetVector<Constraint *> &workList) {
|
|
|
|
llvm::SetVector<Constraint *> otherDisjunctions;
|
|
|
|
{
|
|
// Set up a scope that will be torn down when we're done testing
|
|
// this constraint.
|
|
ConstraintSystem::SolverScope scope(*this);
|
|
|
|
assert(bindConstraint->getKind() == ConstraintKind::BindOverload &&
|
|
"Expected a BindOverload constraint!");
|
|
|
|
// Test this bind overload constraint, activating neighboring
|
|
// constraints.
|
|
auto result = simplifyConstraint(*bindConstraint);
|
|
assert(result == ConstraintSystem::SolutionKind::Solved &&
|
|
"Expected the bind constraint to work!");
|
|
(void)result;
|
|
|
|
solverState->retireConstraint(bindConstraint);
|
|
solverState->addGeneratedConstraint(bindConstraint);
|
|
|
|
collectNeighboringBindOverloadDisjunctions(otherDisjunctions);
|
|
}
|
|
|
|
// Test the our primary constraint against all of the members of
|
|
// neighboring disjunctions. If this constraint fails with all
|
|
// members of a neighboring disjunction, we'll disable it and queue
|
|
// up these neighbors for further processing. If this constraint
|
|
// works with any member of each of the disjunctions, we do not need
|
|
// to test the remaining members.
|
|
for (auto *disjunction : otherDisjunctions) {
|
|
auto insertPt = InactiveConstraints.erase(disjunction);
|
|
CG.removeConstraint(disjunction);
|
|
|
|
bool foundConsistent = false;
|
|
for (auto *nested : disjunction->getNestedConstraints()) {
|
|
assert(nested->getKind() == ConstraintKind::BindOverload &&
|
|
"Expected a BindOverload constraint");
|
|
|
|
if (nested->isDisabled())
|
|
continue;
|
|
|
|
if (areBindPairConsistent(bindConstraint, nested)) {
|
|
foundConsistent = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
CG.addConstraint(disjunction);
|
|
InactiveConstraints.insert(insertPt, disjunction);
|
|
|
|
// We failed to find a working pair between the bind overload
|
|
// constraint we started with and the members of this
|
|
// disjunction. We'll mark this bind overload as disabled and
|
|
// queue up the neighboring disjunctions for re-processing.
|
|
if (!foundConsistent) {
|
|
if (TC.getLangOpts().DebugConstraintSolver) {
|
|
auto &log = getASTContext().TypeCheckerDebug->getStream();
|
|
log << "Disabling bind constraint: ";
|
|
bindConstraint->print(log, &TC.Context.SourceMgr);
|
|
log << "\n";
|
|
}
|
|
|
|
bindConstraint->setDisabled();
|
|
for (auto *disjunction : otherDisjunctions)
|
|
workList.insert(disjunction);
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ConstraintSystem::reviseBindOverloadDisjunction(
|
|
Constraint *disjunction, llvm::SetVector<Constraint *> &workList,
|
|
bool *foundConsistent) {
|
|
assert(disjunction->getKind() == ConstraintKind::Disjunction &&
|
|
"Expected disjunction constraint on work list!");
|
|
|
|
// Set up a scope that will be torn down when we're done testing
|
|
// this constraint.
|
|
ConstraintSystem::SolverScope scope(*this);
|
|
|
|
// Temporarily remove the disjunction from the constraint system and
|
|
// constraint graph.
|
|
auto insertPt = InactiveConstraints.erase(disjunction);
|
|
CG.removeConstraint(disjunction);
|
|
|
|
*foundConsistent = false;
|
|
for (auto *bindConstraint : disjunction->getNestedConstraints()) {
|
|
assert(bindConstraint->getKind() == ConstraintKind::BindOverload
|
|
&& "Expected a BindOverload constraint!");
|
|
|
|
if (bindConstraint->isDisabled())
|
|
continue;
|
|
|
|
if (isBindOverloadConsistent(bindConstraint, workList))
|
|
*foundConsistent = true;
|
|
}
|
|
|
|
CG.addConstraint(disjunction);
|
|
InactiveConstraints.insert(insertPt, disjunction);
|
|
}
|
|
|
|
// Do a form of constraint propagation consisting of examining
|
|
// applicable function constraints and their associated disjunction of
|
|
// bind overload constraints. Disable bind overload constraints in the
|
|
// disjunction if they are inconsistent with the rest of the
|
|
// constraint system. By doing this we can eliminate a lot of the work
|
|
// that we'll perform in the constraint solver.
|
|
bool ConstraintSystem::propagateConstraints() {
|
|
assert(!failedConstraint && "Unexpected failed constraint!");
|
|
assert(getActiveConstraints().empty() && "Expected no active constraints!");
|
|
|
|
if (TC.getLangOpts().DebugConstraintSolver) {
|
|
auto &log = getASTContext().TypeCheckerDebug->getStream();
|
|
log << "---Propagating constraints---\n";
|
|
}
|
|
|
|
// Queue an initial list of bind overload disjunction constraints to
|
|
// process.
|
|
llvm::SetVector<Constraint *> workList;
|
|
for (auto &constraint : getConstraints())
|
|
if (constraint.getKind() == ConstraintKind::Disjunction)
|
|
if (isBindOverloadDisjunction(&constraint))
|
|
workList.insert(&constraint);
|
|
|
|
// Process each disjunction in the work list. If we modify the
|
|
// active constraints in the disjunction as a result of processing
|
|
// it, we'll add it's neighbors back to the worklist for
|
|
// reprocessing.
|
|
while (!workList.empty()) {
|
|
auto *disjunction = workList.pop_back_val();
|
|
|
|
bool foundConsistent;
|
|
reviseBindOverloadDisjunction(disjunction, workList, &foundConsistent);
|
|
if (!foundConsistent)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|