mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
In cases where matched concrete overload used a bridging, CF*, or `AnyHashable` conversion, let's attempt generic overload choices as well because one of them could produce a better solution e.g. `RawRepresentable` for `==` where underlying type conforms to `Equatable` has a better generic match than `(AnyHashable, AnyHashable) -> Bool`. Resolves: rdar://95992916
1041 lines
35 KiB
C++
1041 lines
35 KiB
C++
//===--- CSStep.h - Constraint Solver Steps -------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 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 \c SolverStep class and its related types,
|
|
// which is used by constraint solver to do iterative solving.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef SWIFT_SEMA_CSSTEP_H
|
|
#define SWIFT_SEMA_CSSTEP_H
|
|
|
|
#include "swift/AST/Types.h"
|
|
#include "swift/Sema/Constraint.h"
|
|
#include "swift/Sema/ConstraintGraph.h"
|
|
#include "swift/Sema/ConstraintSystem.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/ADT/Optional.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/Support/SaveAndRestore.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
#include <memory>
|
|
|
|
using namespace llvm;
|
|
|
|
namespace swift {
|
|
namespace constraints {
|
|
|
|
class SolverStep;
|
|
class ComponentStep;
|
|
|
|
/// Represents available states which every
|
|
/// given step could be in during it's lifetime.
|
|
enum class StepState { Setup, Ready, Running, Suspended, Done };
|
|
|
|
/// Represents result of the step execution,
|
|
/// and can only be constructed by `SolverStep`.
|
|
struct StepResult {
|
|
using Kind = ConstraintSystem::SolutionKind;
|
|
|
|
friend class SolverStep;
|
|
|
|
private:
|
|
Kind ResultKind;
|
|
SmallVector<std::unique_ptr<SolverStep>, 4> NextSteps;
|
|
|
|
StepResult(Kind kind) : ResultKind(kind) {}
|
|
|
|
StepResult(Kind kind, std::unique_ptr<SolverStep> step) : ResultKind(kind) {
|
|
NextSteps.push_back(std::move(step));
|
|
}
|
|
|
|
StepResult(Kind kind, SmallVectorImpl<std::unique_ptr<SolverStep>> &followup)
|
|
: ResultKind(kind), NextSteps(std::move(followup)) {}
|
|
|
|
public:
|
|
StepResult() = delete;
|
|
|
|
Kind getKind() const { return ResultKind; }
|
|
|
|
void transfer(SmallVectorImpl<std::unique_ptr<SolverStep>> &workList) {
|
|
workList.reserve(NextSteps.size());
|
|
for (auto &step : NextSteps)
|
|
workList.push_back(std::move(step));
|
|
}
|
|
|
|
private:
|
|
static StepResult success() { return StepResult(Kind::Solved); }
|
|
static StepResult failure() { return StepResult(Kind::Error); }
|
|
|
|
static StepResult unsolved(std::unique_ptr<SolverStep> singleStep) {
|
|
return StepResult(Kind::Unsolved, std::move(singleStep));
|
|
}
|
|
|
|
static StepResult
|
|
unsolved(SmallVectorImpl<std::unique_ptr<SolverStep>> &followup) {
|
|
return StepResult(Kind::Unsolved, followup);
|
|
}
|
|
};
|
|
|
|
/// Represents a single independently solvable part of
|
|
/// the constraint system. And is a base class for all
|
|
/// different types of steps there are.
|
|
class SolverStep {
|
|
friend class ConstraintSystem;
|
|
|
|
protected:
|
|
ConstraintSystem &CS;
|
|
|
|
StepState State = StepState::Setup;
|
|
|
|
/// Once step is complete this is a container to hold finalized solutions.
|
|
SmallVectorImpl<Solution> &Solutions;
|
|
|
|
public:
|
|
explicit SolverStep(ConstraintSystem &cs,
|
|
SmallVectorImpl<Solution> &solutions)
|
|
: CS(cs), Solutions(solutions) {}
|
|
|
|
virtual ~SolverStep() {}
|
|
|
|
/// \returns The current state of this step.
|
|
StepState getState() const { return State; }
|
|
|
|
/// Run preliminary setup (if needed) right
|
|
/// before taking this step for the first time.
|
|
virtual void setup() {}
|
|
|
|
/// Try to move solver forward by simplifying constraints if possible.
|
|
/// Such simplification might lead to either producing a solution, or
|
|
/// creating a set of "follow-up" more granular steps to execute.
|
|
///
|
|
/// \param prevFailed Indicate whether previous step
|
|
/// has failed (returned StepResult::Kind = Error),
|
|
/// this is useful to propagate failures when
|
|
/// unsolved steps are re-taken.
|
|
///
|
|
/// \returns status and any follow-up steps to take before considering
|
|
/// this step solved or failed.
|
|
virtual StepResult take(bool prevFailed) = 0;
|
|
|
|
/// Try to resume previously suspended step.
|
|
///
|
|
/// This happens after "follow-up" steps are done
|
|
/// and all of the required information should be
|
|
/// available to re-take this step.
|
|
///
|
|
/// \param prevFailed Indicate whether previous step
|
|
/// has failed (returned StepResult::Kind = Error),
|
|
/// this is useful to propagate failures when
|
|
/// unsolved steps are re-taken.
|
|
///
|
|
/// \returns status and any follow-up steps to take before considering
|
|
/// this step solved or failed.
|
|
virtual StepResult resume(bool prevFailed) = 0;
|
|
|
|
virtual void print(llvm::raw_ostream &Out) = 0;
|
|
|
|
protected:
|
|
/// Transition this step into one of the available states.
|
|
///
|
|
/// This is primarily driven by execution of the step itself and
|
|
/// the solver, while it executes the work list.
|
|
///
|
|
/// \param newState The new state this step should be in.
|
|
void transitionTo(StepState newState) {
|
|
#ifndef NDEBUG
|
|
// Make sure that ordering of the state transitions is correct,
|
|
// because `setup -> ready -> running [-> suspended]* -> done`
|
|
// is the only reasonable state transition path.
|
|
switch (State) {
|
|
case StepState::Setup:
|
|
assert(newState == StepState::Ready);
|
|
break;
|
|
|
|
case StepState::Ready:
|
|
assert(newState == StepState::Running);
|
|
break;
|
|
|
|
case StepState::Running:
|
|
assert(newState == StepState::Suspended || newState == StepState::Done);
|
|
break;
|
|
|
|
case StepState::Suspended:
|
|
assert(newState == StepState::Running);
|
|
break;
|
|
|
|
case StepState::Done:
|
|
llvm_unreachable("step is already done.");
|
|
}
|
|
#endif
|
|
|
|
State = newState;
|
|
}
|
|
|
|
StepResult done(bool isSuccess) {
|
|
transitionTo(StepState::Done);
|
|
return isSuccess ? StepResult::success() : StepResult::failure();
|
|
}
|
|
|
|
StepResult replaceWith(std::unique_ptr<SolverStep> replacement) {
|
|
transitionTo(StepState::Done);
|
|
return StepResult(StepResult::Kind::Solved, std::move(replacement));
|
|
}
|
|
|
|
StepResult suspend(std::unique_ptr<SolverStep> followup) {
|
|
transitionTo(StepState::Suspended);
|
|
return StepResult::unsolved(std::move(followup));
|
|
}
|
|
|
|
StepResult suspend(SmallVectorImpl<std::unique_ptr<SolverStep>> &followup) {
|
|
transitionTo(StepState::Suspended);
|
|
return StepResult::unsolved(followup);
|
|
}
|
|
|
|
/// Erase constraint from the constraint system (include constraint graph)
|
|
/// and return the constraint which follows it.
|
|
ConstraintList::iterator erase(Constraint *constraint) {
|
|
CS.CG.removeConstraint(constraint);
|
|
return CS.InactiveConstraints.erase(constraint);
|
|
}
|
|
|
|
void restore(ConstraintList::iterator &iterator, Constraint *constraint) {
|
|
CS.InactiveConstraints.insert(iterator, constraint);
|
|
CS.CG.addConstraint(constraint);
|
|
}
|
|
|
|
void recordDisjunctionChoice(ConstraintLocator *disjunctionLocator,
|
|
unsigned index) const {
|
|
CS.recordDisjunctionChoice(disjunctionLocator, index);
|
|
}
|
|
|
|
Score getCurrentScore() const { return CS.CurrentScore; }
|
|
|
|
Optional<Score> getBestScore() const { return CS.solverState->BestScore; }
|
|
|
|
void filterSolutions(SmallVectorImpl<Solution> &solutions, bool minimize) {
|
|
CS.filterSolutions(solutions, minimize);
|
|
}
|
|
|
|
llvm::raw_ostream &getDebugLogger(bool indent = true) const {
|
|
auto &log = llvm::errs();
|
|
return indent ? log.indent(CS.solverState->depth * 2) : log;
|
|
}
|
|
};
|
|
|
|
/// `SplitterStep` is responsible for running connected components
|
|
/// algorithm to determine how many independent sub-systems there are.
|
|
/// Once that's done it would create one `ComponentStep` per such
|
|
/// sub-system, and move to try to solve each and then merge partial
|
|
/// solutions produced by components into complete solution(s).
|
|
class SplitterStep final : public SolverStep {
|
|
// Set of constraints associated with each component, after
|
|
// component steps are complete, all of the constraints are
|
|
// returned back to the work-list in their original order.
|
|
SmallVector<ConstraintList, 4> Components;
|
|
// Partial solutions associated with given step, each element
|
|
// of the array presents a disjoint component (or follow-up step)
|
|
// that current step has been split into.
|
|
std::unique_ptr<SmallVector<Solution, 4>[]> PartialSolutions = nullptr;
|
|
|
|
SmallVector<Constraint *, 4> OrphanedConstraints;
|
|
|
|
/// Whether to include the partial results of this component in the final
|
|
/// merged results.
|
|
SmallVector<bool, 4> IncludeInMergedResults;
|
|
|
|
public:
|
|
SplitterStep(ConstraintSystem &cs, SmallVectorImpl<Solution> &solutions)
|
|
: SolverStep(cs, solutions) {}
|
|
|
|
StepResult take(bool prevFailed) override;
|
|
StepResult resume(bool prevFailed) override;
|
|
|
|
void print(llvm::raw_ostream &Out) override {
|
|
Out << "SplitterStep with #" << Components.size() << " components\n";
|
|
}
|
|
|
|
private:
|
|
/// If current step needs follow-up steps to get completely solved,
|
|
/// let's compute them using connected components algorithm.
|
|
void computeFollowupSteps(
|
|
SmallVectorImpl<std::unique_ptr<SolverStep>> &steps);
|
|
|
|
/// Once all of the follow-up steps are complete, let's try
|
|
/// to merge resulting solutions together, to form final solution(s)
|
|
/// for this step.
|
|
///
|
|
/// \returns true if there are any solutions, false otherwise.
|
|
bool mergePartialSolutions() const;
|
|
};
|
|
|
|
/// `DependentComponentSplitterStep` is responsible for composing the partial
|
|
/// solutions from other components (on which this component depends) into
|
|
/// the inputs based on which we can solve a particular component.
|
|
class DependentComponentSplitterStep final : public SolverStep {
|
|
/// Constraints "in scope" of this step.
|
|
ConstraintList *Constraints;
|
|
|
|
/// Index into the parent splitter step.
|
|
unsigned Index;
|
|
|
|
/// The component that has dependencies.
|
|
ConstraintGraph::Component Component;
|
|
|
|
/// Array containing all of the partial solutions for the parent split.
|
|
MutableArrayRef<SmallVector<Solution, 4>> AllPartialSolutions;
|
|
|
|
/// The solutions computed the \c ComponentSteps created for each partial
|
|
/// solution combinations. Will be merged into the final \c Solutions vector
|
|
/// in \c resume.
|
|
std::vector<std::unique_ptr<SmallVector<Solution, 2>>> ContextualSolutions;
|
|
|
|
/// Take all of the constraints in this component and put them into
|
|
/// \c Constraints.
|
|
void injectConstraints() {
|
|
for (auto constraint : Component.getConstraints()) {
|
|
Constraints->erase(constraint);
|
|
Constraints->push_back(constraint);
|
|
}
|
|
}
|
|
|
|
public:
|
|
DependentComponentSplitterStep(
|
|
ConstraintSystem &cs,
|
|
ConstraintList *constraints,
|
|
unsigned index,
|
|
ConstraintGraph::Component &&component,
|
|
MutableArrayRef<SmallVector<Solution, 4>> allPartialSolutions)
|
|
: SolverStep(cs, allPartialSolutions[index]), Constraints(constraints),
|
|
Index(index), Component(std::move(component)),
|
|
AllPartialSolutions(allPartialSolutions) {
|
|
assert(!Component.getDependencies().empty() && "Should use ComponentStep");
|
|
injectConstraints();
|
|
}
|
|
|
|
StepResult take(bool prevFailed) override;
|
|
StepResult resume(bool prevFailed) override;
|
|
|
|
void print(llvm::raw_ostream &Out) override;
|
|
};
|
|
|
|
|
|
/// `ComponentStep` represents a set of type variables and related
|
|
/// constraints which could be solved independently. It's further
|
|
/// simplified into "binding" steps which attempt type variable and
|
|
/// disjunction choices.
|
|
class ComponentStep final : public SolverStep {
|
|
class Scope {
|
|
ConstraintSystem &CS;
|
|
ConstraintSystem::SolverScope *SolverScope;
|
|
|
|
SetVector<TypeVariableType *> TypeVars;
|
|
ConstraintSystem::SolverScope *PrevPartialScope = nullptr;
|
|
|
|
// The component this scope is associated with.
|
|
ComponentStep &Component;
|
|
|
|
public:
|
|
Scope(ComponentStep &component);
|
|
|
|
~Scope() {
|
|
delete SolverScope; // rewind back all of the changes.
|
|
CS.solverState->PartialSolutionScope = PrevPartialScope;
|
|
|
|
// return all of the saved type variables back to the system.
|
|
CS.TypeVariables = std::move(TypeVars);
|
|
// return all of the saved constraints back to the component.
|
|
auto &constraints = *Component.Constraints;
|
|
constraints.splice(constraints.end(), CS.InactiveConstraints);
|
|
}
|
|
};
|
|
|
|
/// The position of the component in the set of
|
|
/// components produced by "split" step.
|
|
unsigned Index;
|
|
|
|
/// Indicates whether this is only component produced
|
|
/// by "split" step. This information opens optimization
|
|
/// opportunity, because if there are no other components,
|
|
/// constraint system doesn't have to pruned from
|
|
/// unrelated type variables and their constraints.
|
|
bool IsSingle;
|
|
|
|
/// The score associated with constraint system before
|
|
/// the component step is taken.
|
|
Score OriginalScore;
|
|
|
|
/// The original best score computed before any of the
|
|
/// component steps belonging to the same "split" are taken.
|
|
Optional<Score> OriginalBestScore;
|
|
|
|
/// If this step depends on other smaller steps to be solved first
|
|
/// we need to keep active scope until all of the work is done.
|
|
std::unique_ptr<Scope> ComponentScope = nullptr;
|
|
|
|
/// Type variables and constraints "in scope" of this step.
|
|
TinyPtrVector<TypeVariableType *> TypeVars;
|
|
/// Constraints "in scope" of this step.
|
|
ConstraintList *Constraints;
|
|
|
|
/// The set of partial solutions that should be composed before evaluating
|
|
/// this component.
|
|
SmallVector<const Solution *, 2> DependsOnPartialSolutions;
|
|
|
|
/// Constraint which doesn't have any free type variables associated
|
|
/// with it, which makes it disconnected in the graph.
|
|
Constraint *OrphanedConstraint = nullptr;
|
|
|
|
public:
|
|
/// Create a single component step.
|
|
ComponentStep(ConstraintSystem &cs, unsigned index,
|
|
ConstraintList *constraints,
|
|
SmallVectorImpl<Solution> &solutions)
|
|
: SolverStep(cs, solutions), Index(index), IsSingle(true),
|
|
OriginalScore(getCurrentScore()), OriginalBestScore(getBestScore()),
|
|
Constraints(constraints) {}
|
|
|
|
/// Create a component step from a constraint graph component.
|
|
ComponentStep(ConstraintSystem &cs, unsigned index,
|
|
ConstraintList *constraints,
|
|
ConstraintGraph::Component &&component,
|
|
SmallVectorImpl<Solution> &solutions)
|
|
: SolverStep(cs, solutions), Index(index), IsSingle(false),
|
|
OriginalScore(getCurrentScore()), OriginalBestScore(getBestScore()),
|
|
Constraints(constraints) {
|
|
if (component.isOrphaned()) {
|
|
assert(component.getConstraints().size() == 1);
|
|
OrphanedConstraint = component.getConstraints().front();
|
|
} else {
|
|
assert(component.typeVars.size() > 0);
|
|
}
|
|
|
|
TypeVars = std::move(component.typeVars);
|
|
|
|
for (auto constraint : component.getConstraints()) {
|
|
constraints->erase(constraint);
|
|
Constraints->push_back(constraint);
|
|
}
|
|
|
|
assert(component.getDependencies().empty());
|
|
}
|
|
|
|
/// Create a component step that composes existing partial solutions before
|
|
/// solving constraints.
|
|
ComponentStep(
|
|
ConstraintSystem &cs, unsigned index,
|
|
ConstraintList *constraints,
|
|
const ConstraintGraph::Component &component,
|
|
llvm::SmallVectorImpl<const Solution *> &&dependsOnPartialSolutions,
|
|
SmallVectorImpl<Solution> &solutions)
|
|
: SolverStep(cs, solutions), Index(index), IsSingle(false),
|
|
OriginalScore(getCurrentScore()), OriginalBestScore(getBestScore()),
|
|
Constraints(constraints),
|
|
DependsOnPartialSolutions(std::move(dependsOnPartialSolutions)) {
|
|
TypeVars = component.typeVars;
|
|
assert(DependsOnPartialSolutions.size() ==
|
|
component.getDependencies().size());
|
|
|
|
for (auto constraint : component.getConstraints()) {
|
|
constraints->erase(constraint);
|
|
Constraints->push_back(constraint);
|
|
}
|
|
}
|
|
|
|
StepResult take(bool prevFailed) override;
|
|
|
|
StepResult resume(bool prevFailed) override { return finalize(!prevFailed); }
|
|
|
|
void print(llvm::raw_ostream &Out) override {
|
|
Out << "ComponentStep with at #" << Index << '\n';
|
|
}
|
|
|
|
private:
|
|
void setupScope() {
|
|
// If this is a single component, there is no need
|
|
// to preliminary modify constraint system or log anything.
|
|
if (IsSingle)
|
|
return;
|
|
|
|
if (CS.isDebugMode())
|
|
getDebugLogger() << "(solving component #" << Index << '\n';
|
|
|
|
ComponentScope = std::make_unique<Scope>(*this);
|
|
|
|
// If this component has orphaned constraint attached,
|
|
// let's return it to the graph.
|
|
CS.CG.setOrphanedConstraint(OrphanedConstraint);
|
|
}
|
|
|
|
/// Finalize current component by either cleanup if sub-tasks
|
|
/// have failed, or solution generation and minimization.
|
|
StepResult finalize(bool isSuccess);
|
|
};
|
|
|
|
template <typename P> class BindingStep : public SolverStep {
|
|
protected:
|
|
using Scope = ConstraintSystem::SolverScope;
|
|
|
|
P Producer;
|
|
|
|
/// Indicates whether any of the attempted bindings
|
|
/// produced a solution.
|
|
bool AnySolved = false;
|
|
|
|
/// Active binding (scope + choice) which is currently
|
|
/// being attempted, helps to rewind state of the
|
|
/// constraint system back to original before attempting
|
|
/// next binding, if any.
|
|
Optional<std::pair<std::unique_ptr<Scope>, typename P::Element>> ActiveChoice;
|
|
|
|
BindingStep(ConstraintSystem &cs, P producer,
|
|
SmallVectorImpl<Solution> &solutions)
|
|
: SolverStep(cs, solutions), Producer(std::move(producer)) {}
|
|
|
|
public:
|
|
StepResult take(bool prevFailed) override {
|
|
// Before attempting the next choice, let's check whether the constraint
|
|
// system is too complex already.
|
|
if (CS.isTooComplex(Solutions))
|
|
return done(/*isSuccess=*/false);
|
|
|
|
while (auto choice = Producer()) {
|
|
if (shouldSkip(*choice))
|
|
continue;
|
|
|
|
if (shouldStopAt(*choice))
|
|
break;
|
|
|
|
if (CS.isDebugMode()) {
|
|
auto &log = getDebugLogger();
|
|
log << "(attempting ";
|
|
choice->print(log, &CS.getASTContext().SourceMgr);
|
|
log << '\n';
|
|
}
|
|
|
|
{
|
|
auto scope = std::make_unique<Scope>(CS);
|
|
if (attempt(*choice)) {
|
|
ActiveChoice.emplace(std::move(scope), *choice);
|
|
return suspend(std::make_unique<SplitterStep>(CS, Solutions));
|
|
}
|
|
}
|
|
|
|
if (CS.isDebugMode())
|
|
getDebugLogger() << ")\n";
|
|
|
|
// If this binding didn't match, let's check if we've attempted
|
|
// enough bindings to stop, because some producers might need
|
|
// to compute next step of bindings to try, which we'd want to avoid.
|
|
if (shouldStopAfter(*choice))
|
|
break;
|
|
}
|
|
|
|
return done(/*isSuccess=*/AnySolved);
|
|
}
|
|
|
|
protected:
|
|
/// Attempt to apply given binding choice to constraint system.
|
|
/// This action is going to establish "active choice" of this step
|
|
/// to point to a given choice.
|
|
///
|
|
/// \param choice The choice to attempt.
|
|
///
|
|
/// \return true if the choice has been accepted and system can be
|
|
/// simplified further, false otherwise.
|
|
virtual bool attempt(const typename P::Element &choice) = 0;
|
|
|
|
/// Check whether attempting this choice could be avoided,
|
|
/// which could speed-up solving.
|
|
virtual bool shouldSkip(const typename P::Element &choice) const = 0;
|
|
|
|
/// Check whether attempting binding choices should be stopped,
|
|
/// because optimal solution has already been found.
|
|
virtual bool shouldStopAt(const typename P::Element &choice) const = 0;
|
|
|
|
/// Check whether attempting binding choices should be stopped,
|
|
/// after current choice has been attempted, because optimal
|
|
/// solution has already been found,
|
|
virtual bool shouldStopAfter(const typename P::Element &choice) const {
|
|
return false;
|
|
}
|
|
|
|
bool needsToComputeNext() const { return Producer.needsToComputeNext(); }
|
|
|
|
ConstraintLocator *getLocator() const { return Producer.getLocator(); }
|
|
};
|
|
|
|
class TypeVariableStep final : public BindingStep<TypeVarBindingProducer> {
|
|
using BindingContainer = inference::BindingSet;
|
|
using Binding = inference::PotentialBinding;
|
|
|
|
TypeVariableType *TypeVar;
|
|
|
|
/// Indicates whether source of one of the previously
|
|
/// attempted bindings was a literal constraint. This
|
|
/// is useful for a performance optimization to stop
|
|
/// attempting other bindings in certain conditions.
|
|
bool SawFirstLiteralConstraint = false;
|
|
|
|
public:
|
|
TypeVariableStep(BindingContainer &bindings,
|
|
SmallVectorImpl<Solution> &solutions)
|
|
: BindingStep(bindings.getConstraintSystem(), {bindings}, solutions),
|
|
TypeVar(bindings.getTypeVariable()) {}
|
|
|
|
void setup() override;
|
|
|
|
StepResult resume(bool prevFailed) override;
|
|
|
|
void print(llvm::raw_ostream &Out) override {
|
|
PrintOptions PO;
|
|
PO.PrintTypesForDebugging = true;
|
|
Out << "TypeVariableStep for " << TypeVar->getString(PO) << '\n';
|
|
}
|
|
|
|
protected:
|
|
bool attempt(const TypeVariableBinding &choice) override;
|
|
|
|
bool shouldSkip(const TypeVariableBinding &choice) const override {
|
|
// If this is a defaultable binding and we have found solutions,
|
|
// don't explore the default binding.
|
|
return AnySolved && choice.isDefaultable();
|
|
}
|
|
|
|
/// Check whether attempting type variable binding choices should
|
|
/// be stopped, because optimal solution has already been found.
|
|
bool shouldStopAt(const TypeVariableBinding &choice) const override {
|
|
// Let's always attempt default types inferred from literals in diagnostic
|
|
// mode because that could lead to better diagnostics if the problem is
|
|
// contextual like argument/parameter conversion or collection element
|
|
// mismatch.
|
|
if (CS.shouldAttemptFixes())
|
|
return false;
|
|
|
|
// If we were able to solve this without considering
|
|
// default literals, don't bother looking at default literals.
|
|
return AnySolved && choice.hasDefaultedProtocol() &&
|
|
!SawFirstLiteralConstraint;
|
|
}
|
|
|
|
bool shouldStopAfter(const TypeVariableBinding &choice) const override {
|
|
// Let's always attempt additional bindings in diagnostic mode, as that
|
|
// could lead to better diagnostic for e.g trying the unwrapped type.
|
|
if (CS.shouldAttemptFixes())
|
|
return false;
|
|
|
|
// If there has been at least one solution so far
|
|
// at a current batch of bindings is done it's a
|
|
// success because each new batch would be less
|
|
// and less precise.
|
|
return AnySolved && needsToComputeNext();
|
|
}
|
|
};
|
|
|
|
class DisjunctionStep final : public BindingStep<DisjunctionChoiceProducer> {
|
|
Constraint *Disjunction;
|
|
SmallVector<Constraint *, 4> DisabledChoices;
|
|
ConstraintList::iterator AfterDisjunction;
|
|
|
|
Optional<Score> BestNonGenericScore;
|
|
Optional<std::pair<Constraint *, Score>> LastSolvedChoice;
|
|
|
|
public:
|
|
DisjunctionStep(ConstraintSystem &cs, Constraint *disjunction,
|
|
SmallVectorImpl<Solution> &solutions)
|
|
: BindingStep(cs, {cs, disjunction}, solutions), Disjunction(disjunction),
|
|
AfterDisjunction(erase(disjunction)) {
|
|
assert(Disjunction->getKind() == ConstraintKind::Disjunction);
|
|
pruneOverloadSet(Disjunction);
|
|
++cs.solverState->NumDisjunctions;
|
|
}
|
|
|
|
~DisjunctionStep() override {
|
|
// Rewind back any changes left after attempting last choice.
|
|
ActiveChoice.reset();
|
|
// Return disjunction constraint back to the system.
|
|
restore(AfterDisjunction, Disjunction);
|
|
// Re-enable previously disabled overload choices.
|
|
for (auto *choice : DisabledChoices)
|
|
choice->setEnabled();
|
|
}
|
|
|
|
StepResult resume(bool prevFailed) override;
|
|
|
|
void print(llvm::raw_ostream &Out) override {
|
|
Out << "DisjunctionStep for ";
|
|
Disjunction->print(Out, &CS.getASTContext().SourceMgr);
|
|
Out << '\n';
|
|
}
|
|
|
|
private:
|
|
bool shouldSkip(const DisjunctionChoice &choice) const override;
|
|
|
|
/// Whether we should short-circuit a disjunction that already has a
|
|
/// solution when we encounter the given choice.
|
|
///
|
|
/// FIXME: This is performance hack, which should go away.
|
|
///
|
|
/// \params choice The disjunction choice we are about to attempt.
|
|
///
|
|
/// \returns true if disjunction step should be considered complete,
|
|
/// false otherwise.
|
|
bool shouldStopAt(const DisjunctionChoice &choice) const override;
|
|
bool shortCircuitDisjunctionAt(Constraint *currentChoice,
|
|
Constraint *lastSuccessfulChoice) const;
|
|
|
|
bool shouldSkipGenericOperators() const {
|
|
if (!BestNonGenericScore)
|
|
return false;
|
|
|
|
// Let's skip generic overload choices only in case if
|
|
// non-generic score indicates that there were no forced
|
|
// unwrappings of optional(s), no unavailable overload
|
|
// choices present in the solution, no fixes required,
|
|
// and there are no non-trivial user or function conversions.
|
|
auto &score = BestNonGenericScore->Data;
|
|
return (score[SK_ForceUnchecked] == 0 && score[SK_Unavailable] == 0 &&
|
|
score[SK_Fix] == 0 && score[SK_UserConversion] == 0 &&
|
|
score[SK_FunctionConversion] == 0);
|
|
}
|
|
|
|
/// Attempt to apply given disjunction choice to constraint system.
|
|
/// This action is going to establish "active choice" of this disjunction
|
|
/// to point to a given choice.
|
|
///
|
|
/// \param choice The choice to attempt.
|
|
///
|
|
/// \return true if the choice has been accepted and system can be
|
|
/// simplified further, false otherwise.
|
|
bool attempt(const DisjunctionChoice &choice) override;
|
|
|
|
// Check if selected disjunction has a representative
|
|
// this might happen when there are multiple binary operators
|
|
// chained together. If so, disable choices which differ
|
|
// from currently selected representative.
|
|
void pruneOverloadSet(Constraint *disjunction) {
|
|
auto *choice = disjunction->getNestedConstraints().front();
|
|
if (choice->getKind() != ConstraintKind::BindOverload)
|
|
return;
|
|
|
|
auto *typeVar = choice->getFirstType()->getAs<TypeVariableType>();
|
|
if (!typeVar)
|
|
return;
|
|
|
|
auto *repr = typeVar->getImpl().getRepresentative(nullptr);
|
|
if (!repr || repr == typeVar)
|
|
return;
|
|
|
|
for (auto overload : CS.getResolvedOverloads()) {
|
|
auto resolved = overload.second;
|
|
if (!resolved.boundType->isEqual(repr))
|
|
continue;
|
|
|
|
auto &representative = resolved.choice;
|
|
if (!representative.isDecl())
|
|
return;
|
|
|
|
// Disable all of the overload choices which are different from
|
|
// the one which is currently picked for representative.
|
|
for (auto *constraint : disjunction->getNestedConstraints()) {
|
|
auto choice = constraint->getOverloadChoice();
|
|
if (!choice.isDecl() || choice.getDecl() == representative.getDecl())
|
|
continue;
|
|
|
|
constraint->setDisabled();
|
|
DisabledChoices.push_back(constraint);
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
|
|
// Figure out which of the solutions has the smallest score.
|
|
static Optional<Score> getBestScore(SmallVectorImpl<Solution> &solutions) {
|
|
if (solutions.empty())
|
|
return None;
|
|
|
|
Score bestScore = solutions.front().getFixedScore();
|
|
if (solutions.size() == 1)
|
|
return bestScore;
|
|
|
|
for (unsigned i = 1, n = solutions.size(); i != n; ++i) {
|
|
auto &score = solutions[i].getFixedScore();
|
|
if (score < bestScore)
|
|
bestScore = score;
|
|
}
|
|
return bestScore;
|
|
}
|
|
};
|
|
|
|
class ConjunctionStep : public BindingStep<ConjunctionElementProducer> {
|
|
/// Snapshot of the constraint system before conjunction.
|
|
class SolverSnapshot {
|
|
ConstraintSystem &CS;
|
|
|
|
/// The conjunction this snapshot belongs to.
|
|
Constraint *Conjunction;
|
|
|
|
Optional<llvm::SaveAndRestore<DeclContext *>> DC = None;
|
|
|
|
llvm::SetVector<TypeVariableType *> TypeVars;
|
|
ConstraintList Constraints;
|
|
|
|
/// If this conjunction has to be solved in isolation,
|
|
/// this scope would be initialized once all of the
|
|
/// elements are successfully solved to continue solving
|
|
/// along the current path as-if there was no conjunction.
|
|
std::unique_ptr<Scope> IsolationScope = nullptr;
|
|
|
|
public:
|
|
SolverSnapshot(ConstraintSystem &cs, Constraint *conjunction)
|
|
: CS(cs), Conjunction(conjunction),
|
|
TypeVars(std::move(cs.TypeVariables)) {
|
|
auto *locator = Conjunction->getLocator();
|
|
// If this conjunction represents a closure, we need to
|
|
// switch declaration context over to it.
|
|
if (locator->directlyAt<ClosureExpr>()) {
|
|
DC.emplace(CS.DC, castToExpr<ClosureExpr>(locator->getAnchor()));
|
|
}
|
|
|
|
auto &CG = CS.getConstraintGraph();
|
|
// Remove all of the current inactive constraints.
|
|
Constraints.splice(Constraints.end(), CS.InactiveConstraints);
|
|
// Clear constraint graph.
|
|
for (auto &constraint : Constraints)
|
|
CG.removeConstraint(&constraint);
|
|
}
|
|
|
|
void setupOuterContext(Solution solution) {
|
|
// Re-add type variables and constraints back
|
|
// to the constraint system.
|
|
restore();
|
|
|
|
// Establish isolation scope so that conjunction solution
|
|
// and follow-up steps could be rolled back.
|
|
IsolationScope = std::make_unique<Scope>(CS);
|
|
|
|
// Apply solution inferred for the conjunction.
|
|
applySolution(solution);
|
|
|
|
// Add constraints to the graph after solution
|
|
// has been applied to make sure that all type
|
|
// information is available to incremental inference.
|
|
for (auto &constraint : CS.InactiveConstraints)
|
|
CS.CG.addConstraint(&constraint);
|
|
}
|
|
|
|
bool isScoped() const { return bool(IsolationScope); }
|
|
|
|
~SolverSnapshot() {
|
|
if (!IsolationScope)
|
|
restore();
|
|
|
|
IsolationScope.reset();
|
|
// Re-add all of the constraint to the constraint
|
|
// graph after scope has been rolled back, to make
|
|
// make sure the original (before conjunction)
|
|
// state is completely restored.
|
|
updateConstraintGraph();
|
|
}
|
|
|
|
private:
|
|
void restore() {
|
|
DC.reset();
|
|
CS.TypeVariables = std::move(TypeVars);
|
|
CS.InactiveConstraints.splice(CS.InactiveConstraints.end(), Constraints);
|
|
}
|
|
|
|
void updateConstraintGraph() {
|
|
auto &CG = CS.getConstraintGraph();
|
|
for (auto &constraint : CS.InactiveConstraints)
|
|
CG.addConstraint(&constraint);
|
|
}
|
|
|
|
void applySolution(const Solution &solution) {
|
|
CS.applySolution(solution);
|
|
|
|
if (!CS.shouldAttemptFixes())
|
|
return;
|
|
|
|
// If inference succeeded, we are done.
|
|
auto score = solution.getFixedScore();
|
|
if (score.Data[SK_Fix] == 0)
|
|
return;
|
|
|
|
// If this conjunction represents a closure and inference
|
|
// has failed, let's bind all of unresolved type variables
|
|
// in its interface type to holes to avoid extraneous
|
|
// fixes produced by outer context.
|
|
|
|
auto locator = Conjunction->getLocator();
|
|
if (locator->directlyAt<ClosureExpr>()) {
|
|
auto closureTy =
|
|
CS.getClosureType(castToExpr<ClosureExpr>(locator->getAnchor()));
|
|
|
|
CS.simplifyType(closureTy).visit([&](Type componentTy) {
|
|
if (auto *typeVar = componentTy->getAs<TypeVariableType>()) {
|
|
CS.assignFixedType(
|
|
typeVar, PlaceholderType::get(CS.getASTContext(), typeVar));
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Best solution solver reached so far.
|
|
Optional<Score> BestScore;
|
|
/// The score established before conjunction is attempted.
|
|
Score CurrentScore;
|
|
|
|
/// The number of constraint solver scopes already explored
|
|
/// before accepting this conjunction.
|
|
llvm::SaveAndRestore<unsigned> OuterScopeCount;
|
|
|
|
/// The number of milliseconds until outer constraint system
|
|
/// is considered "too complex" if timer is enabled.
|
|
Optional<std::pair<ExpressionTimer::AnchorType, unsigned>>
|
|
OuterTimeRemaining = None;
|
|
|
|
/// Conjunction constraint associated with this step.
|
|
Constraint *Conjunction;
|
|
/// Position of the conjunction in the inactive constraints
|
|
/// list which is required to re-instate it to the system
|
|
/// after this step is done.
|
|
ConstraintList::iterator AfterConjunction;
|
|
|
|
/// Indicates that one of the elements failed inference.
|
|
bool HadFailure = false;
|
|
|
|
/// If conjunction has to be solved in isolation, this
|
|
/// variable would capture the snapshot of the constraint
|
|
/// system step before conjunction step.
|
|
Optional<SolverSnapshot> Snapshot;
|
|
|
|
/// A set of previously deduced solutions. This is used upon
|
|
/// successful solution of an isolated conjunction to introduce
|
|
/// all of the inferred information back into the outer context.
|
|
SmallVectorImpl<Solution> &OuterSolutions;
|
|
|
|
/// Solutions produced while attempting elements of an isolated conjunction.
|
|
///
|
|
/// Note that this is what `BindingStep` is initialized with
|
|
/// in isolated mode.
|
|
SmallVector<Solution, 4> IsolatedSolutions;
|
|
|
|
public:
|
|
ConjunctionStep(ConstraintSystem &cs, Constraint *conjunction,
|
|
SmallVectorImpl<Solution> &solutions)
|
|
: BindingStep(cs, {cs, conjunction},
|
|
conjunction->isIsolated() ? IsolatedSolutions : solutions),
|
|
BestScore(getBestScore()), CurrentScore(getCurrentScore()),
|
|
OuterScopeCount(cs.CountScopes, 0), Conjunction(conjunction),
|
|
AfterConjunction(erase(conjunction)), OuterSolutions(solutions) {
|
|
assert(conjunction->getKind() == ConstraintKind::Conjunction);
|
|
|
|
// Make a snapshot of the constraint system state before conjunction.
|
|
if (conjunction->isIsolated())
|
|
Snapshot.emplace(cs, conjunction);
|
|
|
|
if (cs.Timer) {
|
|
auto remainingTime = cs.Timer->getRemainingProcessTimeInMillis();
|
|
OuterTimeRemaining.emplace(cs.Timer->getAnchor(), remainingTime);
|
|
}
|
|
}
|
|
|
|
~ConjunctionStep() override {
|
|
assert(!bool(ActiveChoice));
|
|
|
|
// Return all of the type variables and constraints back.
|
|
Snapshot.reset();
|
|
|
|
// Restore conjunction constraint.
|
|
restore(AfterConjunction, Conjunction);
|
|
|
|
// Restore best score only if conjunction fails because
|
|
// successful outcome should keep a score set by `restoreOuterState`.
|
|
if (HadFailure) {
|
|
auto solutionScore = Score();
|
|
restoreBestScore();
|
|
restoreCurrentScore(solutionScore);
|
|
}
|
|
|
|
if (OuterTimeRemaining) {
|
|
auto anchor = OuterTimeRemaining->first;
|
|
auto remainingTime = OuterTimeRemaining->second;
|
|
CS.Timer.emplace(anchor, CS, remainingTime);
|
|
}
|
|
}
|
|
|
|
StepResult resume(bool prevFailed) override;
|
|
|
|
void print(llvm::raw_ostream &Out) override {
|
|
Out << "ConjunctionStep for ";
|
|
Conjunction->print(Out, &CS.getASTContext().SourceMgr);
|
|
Out << '\n';
|
|
}
|
|
|
|
protected:
|
|
bool attempt(const ConjunctionElement &element) override;
|
|
|
|
/// Conjunction can't skip elements.
|
|
bool shouldSkip(const ConjunctionElement &element) const override {
|
|
return false;
|
|
}
|
|
|
|
/// Conjunction can't reject attempting any of its elements.
|
|
bool shouldStopAt(const ConjunctionElement &element) const override {
|
|
return false;
|
|
}
|
|
|
|
/// Conjunctions only stop after first failure.
|
|
///
|
|
/// TODO: In diagnostic mode conjunction evaluation should stop
|
|
/// after first element failure and consider the rest to
|
|
/// be solved, in order to produce good diagnostics.
|
|
bool shouldStopAfter(const ConjunctionElement &element) const override {
|
|
return HadFailure;
|
|
}
|
|
|
|
void markAsFailed() {
|
|
HadFailure = true;
|
|
// During performance mode, failure to infer a type for one
|
|
// of the elements automatically fails whole conjunction.
|
|
//
|
|
// TODO: In diagnostic mode, let's consider this conjunction
|
|
// a success if at least one of its elements was solved
|
|
// successfully by use of fixes, and ignore the rest.
|
|
AnySolved = false;
|
|
}
|
|
|
|
private:
|
|
/// Restore best and current scores as they were before conjunction.
|
|
void restoreCurrentScore(const Score &solutionScore) const {
|
|
CS.CurrentScore = CurrentScore;
|
|
CS.increaseScore(SK_Fix, solutionScore.Data[SK_Fix]);
|
|
CS.increaseScore(SK_Hole, solutionScore.Data[SK_Hole]);
|
|
}
|
|
|
|
void restoreBestScore() const { CS.solverState->BestScore = BestScore; }
|
|
|
|
// Restore constraint system state before conjunction.
|
|
//
|
|
// Note that this doesn't include conjunction constraint
|
|
// itself because we don't want to re-solve it.
|
|
void restoreOuterState(const Score &solutionScore) const;
|
|
};
|
|
|
|
} // end namespace constraints
|
|
} // end namespace swift
|
|
|
|
#endif // SWIFT_SEMA_CSSTEP_H
|