Skip to content

[ConstraintSystem] Infer some of the bindings attributes on demand #34737

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 60 additions & 34 deletions include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -4730,26 +4730,11 @@ class ConstraintSystem {
/// Whether the bindings of this type involve other type variables.
bool InvolvesTypeVariables = false;

/// Whether this type variable is considered a hole in the constraint system.
bool IsHole = false;

/// Whether the bindings represent (potentially) incomplete set,
/// there is no way to say with absolute certainty if that's the
/// case, but that could happen when certain constraints like
/// `bind param` are present in the system.
bool PotentiallyIncomplete = false;

ASTNode AssociatedCodeCompletionToken = ASTNode();

/// Whether this type variable has literal bindings.
LiteralBindingKind LiteralBinding = LiteralBindingKind::None;

/// Whether this type variable is only bound above by existential types.
bool SubtypeOfExistentialType = false;

/// The number of defaultable bindings.
unsigned NumDefaultableBindings = 0;

/// Tracks the position of the last known supertype in the group.
Optional<unsigned> lastSupertypeIndex;

Expand All @@ -4761,49 +4746,88 @@ class ConstraintSystem {
llvm::SmallMapVector<TypeVariableType *, Constraint *, 4> SupertypeOf;
llvm::SmallMapVector<TypeVariableType *, Constraint *, 4> EquivalentTo;

PotentialBindings(TypeVariableType *typeVar)
: TypeVar(typeVar), PotentiallyIncomplete(isGenericParameter()) {}
PotentialBindings(TypeVariableType *typeVar) : TypeVar(typeVar) {}

/// Determine whether the set of bindings is non-empty.
explicit operator bool() const { return !Bindings.empty(); }

/// Whether there are any non-defaultable bindings.
bool hasNonDefaultableBindings() const {
return Bindings.size() > NumDefaultableBindings;
/// Whether the bindings represent (potentially) incomplete set,
/// there is no way to say with absolute certainty if that's the
/// case, but that could happen when certain constraints like
/// `bind param` are present in the system.
bool isPotentiallyIncomplete() const;

/// If there is only one binding and it's to a hole type, consider
/// this type variable to be a hole in a constraint system regardless
/// of where hole type originated.
bool isHole() const {
if (Bindings.size() != 1)
return false;

auto &binding = Bindings.front();
return binding.BindingType->is<HoleType>();
}

/// Determine if the bindings only constrain the type variable from above
/// with an existential type; such a binding is not very helpful because
/// it's impossible to enumerate the existential type's subtypes.
bool isSubtypeOfExistentialType() const {
if (Bindings.empty())
return false;

return llvm::all_of(Bindings, [](const PotentialBinding &binding) {
return binding.BindingType->isExistentialType() &&
binding.Kind == AllowedBindingKind::Subtypes;
});
}

unsigned getNumDefaultableBindings() const {
return llvm::count_if(Bindings, [](const PotentialBinding &binding) {
return binding.isDefaultableBinding();
});
}

static BindingScore formBindingScore(const PotentialBindings &b) {
return std::make_tuple(b.IsHole,
!b.hasNonDefaultableBindings(),
auto numDefaults = b.getNumDefaultableBindings();
auto hasNoDefaultableBindings = b.Bindings.size() > numDefaults;

return std::make_tuple(b.isHole(),
!hasNoDefaultableBindings,
b.FullyBound,
b.SubtypeOfExistentialType,
b.isSubtypeOfExistentialType(),
b.InvolvesTypeVariables,
static_cast<unsigned char>(b.LiteralBinding),
-(b.Bindings.size() - b.NumDefaultableBindings));
-(b.Bindings.size() - numDefaults));
}

/// Compare two sets of bindings, where \c x < y indicates that
/// \c x is a better set of bindings that \c y.
friend bool operator<(const PotentialBindings &x,
const PotentialBindings &y) {
if (formBindingScore(x) < formBindingScore(y))
auto xScore = formBindingScore(x);
auto yScore = formBindingScore(y);

if (xScore < yScore)
return true;

if (formBindingScore(y) < formBindingScore(x))
if (yScore < xScore)
return false;

auto xDefaults = x.Bindings.size() + std::get<6>(xScore);
auto yDefaults = y.Bindings.size() + std::get<6>(yScore);

// If there is a difference in number of default types,
// prioritize bindings with fewer of them.
if (x.NumDefaultableBindings != y.NumDefaultableBindings)
return x.NumDefaultableBindings < y.NumDefaultableBindings;
if (xDefaults != yDefaults)
return xDefaults < yDefaults;

// If neither type variable is a "hole" let's check whether
// there is a subtype relationship between them and prefer
// type variable which represents superclass first in order
// for "subtype" type variable to attempt more bindings later.
// This is required because algorithm can't currently infer
// bindings for subtype transitively through superclass ones.
if (!(x.IsHole && y.IsHole)) {
if (!(std::get<0>(xScore) && std::get<0>(yScore))) {
if (x.isSubtypeOf(y.TypeVar))
return false;

Expand All @@ -4813,7 +4837,7 @@ class ConstraintSystem {

// As a last resort, let's check if the bindings are
// potentially incomplete, and if so, let's de-prioritize them.
return x.PotentiallyIncomplete < y.PotentiallyIncomplete;
return x.isPotentiallyIncomplete() < y.isPotentiallyIncomplete();
}

void foundLiteralBinding(ProtocolDecl *proto) {
Expand Down Expand Up @@ -4914,18 +4938,20 @@ class ConstraintSystem {
void dump(llvm::raw_ostream &out,
unsigned indent = 0) const LLVM_ATTRIBUTE_USED {
out.indent(indent);
if (PotentiallyIncomplete)
if (isPotentiallyIncomplete())
out << "potentially_incomplete ";
if (FullyBound)
out << "fully_bound ";
if (SubtypeOfExistentialType)
if (isSubtypeOfExistentialType())
out << "subtype_of_existential ";
if (LiteralBinding != LiteralBindingKind::None)
out << "literal=" << static_cast<int>(LiteralBinding) << " ";
if (InvolvesTypeVariables)
out << "involves_type_vars ";
if (NumDefaultableBindings > 0)
out << "#defaultable_bindings=" << NumDefaultableBindings << " ";

auto numDefaultable = getNumDefaultableBindings();
if (numDefaultable > 0)
out << "#defaultable_bindings=" << numDefaultable << " ";

PrintOptions PO;
PO.PrintTypesForDebugging = true;
Expand Down
121 changes: 62 additions & 59 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,64 @@
using namespace swift;
using namespace constraints;

bool ConstraintSystem::PotentialBindings::isPotentiallyIncomplete() const {
// Generic parameters are always potentially incomplete.
if (isGenericParameter())
return true;

// If current type variable is associated with a code completion token
// it's possible that it doesn't have enough contextual information
// to be resolved to anything so let's delay considering it until everything
// else is resolved.
if (AssociatedCodeCompletionToken)
return true;

auto *locator = TypeVar->getImpl().getLocator();
if (!locator)
return false;

if (locator->isLastElement<LocatorPathElt::UnresolvedMemberChainResult>()) {
// If subtyping is allowed and this is a result of an implicit member chain,
// let's delay binding it to an optional until its object type resolved too or
// it has been determined that there is no possibility to resolve it. Otherwise
// we might end up missing solutions since it's allowed to implicitly unwrap
// base type of the chain but it can't be done early - type variable
// representing chain's result type has a different l-valueness comparing
// to generic parameter of the optional.
if (llvm::any_of(Bindings, [&](const PotentialBinding &binding) {
if (binding.Kind != AllowedBindingKind::Subtypes)
return false;

auto objectType = binding.BindingType->getOptionalObjectType();
return objectType && objectType->isTypeVariableOrMember();
}))
return true;
}

if (isHole()) {
// If the base of the unresolved member reference like `.foo`
// couldn't be resolved we'd want to bind it to a hole at the
// very last moment possible, just like generic parameters.
if (locator->isLastElement<LocatorPathElt::MemberRefBase>())
return true;

// Delay resolution of the code completion expression until
// the very end to give it a chance to be bound to some
// contextual type even if it's a hole.
if (locator->directlyAt<CodeCompletionExpr>())
return true;

// Delay resolution of the `nil` literal to a hole until
// the very end to give it a change to be bound to some
// other type, just like code completion expression which
// relies solely on contextual information.
if (locator->directlyAt<NilLiteralExpr>())
return true;
}

return false;
}

void ConstraintSystem::PotentialBindings::inferTransitiveProtocolRequirements(
const ConstraintSystem &cs,
llvm::SmallDenseMap<TypeVariableType *, ConstraintSystem::PotentialBindings>
Expand Down Expand Up @@ -462,30 +520,23 @@ void ConstraintSystem::PotentialBindings::finalize(
// If there are no bindings, typeVar may be a hole.
if (cs.shouldAttemptFixes() && Bindings.empty() &&
TypeVar->getImpl().canBindToHole()) {
IsHole = true;
// If the base of the unresolved member reference like `.foo`
// couldn't be resolved we'd want to bind it to a hole at the
// very last moment possible, just like generic parameters.
auto *locator = TypeVar->getImpl().getLocator();
if (locator->isLastElement<LocatorPathElt::MemberRefBase>())
PotentiallyIncomplete = true;

// Delay resolution of the code completion expression until
// the very end to give it a chance to be bound to some
// contextual type even if it's a hole.
if (locator->directlyAt<CodeCompletionExpr>()) {
if (locator->directlyAt<CodeCompletionExpr>())
FullyBound = true;
PotentiallyIncomplete = true;
}

// Delay resolution of the `nil` literal to a hole until
// the very end to give it a change to be bound to some
// other type, just like code completion expression which
// relies solely on contextual information.
if (locator->directlyAt<NilLiteralExpr>()) {
if (locator->directlyAt<NilLiteralExpr>())
FullyBound = true;
PotentiallyIncomplete = true;
}

// If this type variable is associated with a code completion token
// and it failed to infer any bindings let's adjust hole's locator
Expand Down Expand Up @@ -513,17 +564,6 @@ void ConstraintSystem::PotentialBindings::finalize(
std::rotate(AnyTypePos, AnyTypePos + 1, Bindings.end());
}
}

// Determine if the bindings only constrain the type variable from above with
// an existential type; such a binding is not very helpful because it's
// impossible to enumerate the existential type's subtypes.
if (!Bindings.empty()) {
SubtypeOfExistentialType =
llvm::all_of(Bindings, [](const PotentialBinding &binding) {
return binding.BindingType->isExistentialType() &&
binding.Kind == AllowedBindingKind::Subtypes;
});
}
}

Optional<ConstraintSystem::PotentialBindings>
Expand Down Expand Up @@ -678,9 +718,6 @@ void ConstraintSystem::PotentialBindings::addPotentialBinding(
if (!isViable(binding))
return;

if (binding.isDefaultableBinding())
++NumDefaultableBindings;

Bindings.push_back(std::move(binding));
}

Expand Down Expand Up @@ -709,7 +746,7 @@ bool ConstraintSystem::PotentialBindings::isViable(

bool ConstraintSystem::PotentialBindings::favoredOverDisjunction(
Constraint *disjunction) const {
if (IsHole || FullyBound)
if (isHole() || FullyBound)
return false;

// If this bindings are for a closure and there are no holes,
Expand Down Expand Up @@ -889,10 +926,8 @@ ConstraintSystem::getPotentialBindingForRelationalConstraint(
// bindings and use it when forming a hole if there are no other bindings
// available.
if (auto *locator = bindingTypeVar->getImpl().getLocator()) {
if (locator->directlyAt<CodeCompletionExpr>()) {
if (locator->directlyAt<CodeCompletionExpr>())
result.AssociatedCodeCompletionToken = locator->getAnchor();
result.PotentiallyIncomplete = true;
}
}

switch (constraint->getKind()) {
Expand Down Expand Up @@ -931,24 +966,6 @@ ConstraintSystem::getPotentialBindingForRelationalConstraint(
return None;
}

// If subtyping is allowed and this is a result of an implicit member chain,
// let's delay binding it to an optional until its object type resolved too or
// it has been determined that there is no possibility to resolve it. Otherwise
// we might end up missing solutions since it's allowed to implicitly unwrap
// base type of the chain but it can't be done early - type variable
// representing chain's result type has a different l-valueness comparing
// to generic parameter of the optional.
if (kind == AllowedBindingKind::Subtypes) {
auto *locator = typeVar->getImpl().getLocator();
if (locator &&
locator->isLastElement<LocatorPathElt::UnresolvedMemberChainResult>()) {
auto objectType = type->getOptionalObjectType();
if (objectType && objectType->isTypeVariableOrMember()) {
result.PotentiallyIncomplete = true;
}
}
}

if (type->is<InOutType>() && !typeVar->getImpl().canBindToInOut())
type = LValueType::get(type->getInOutObjectType());
if (type->is<LValueType>() && !typeVar->getImpl().canBindToLValue())
Expand Down Expand Up @@ -987,20 +1004,6 @@ bool ConstraintSystem::PotentialBindings::infer(
case ConstraintKind::ArgumentConversion:
case ConstraintKind::OperatorArgumentConversion:
case ConstraintKind::OptionalObject: {
// If there is a `bind param` constraint associated with
// current type variable, result should be aware of that
// fact. Binding set might be incomplete until
// this constraint is resolved, because we currently don't
// look-through constraints expect to `subtype` to try and
// find related bindings.
// This only affects type variable that appears one the
// right-hand side of the `bind param` constraint and
// represents result type of the closure body, because
// left-hand side gets types from overload choices.
if (constraint->getKind() == ConstraintKind::BindParam &&
constraint->getSecondType()->isEqual(TypeVar))
PotentiallyIncomplete = true;

auto binding =
cs.getPotentialBindingForRelationalConstraint(*this, constraint);
if (!binding)
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/ConstraintGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1113,7 +1113,7 @@ bool ConstraintGraph::contractEdges() {
bool isNotContractable = true;
if (auto bindings = CS.inferBindingsFor(tyvar1)) {
// Holes can't be contracted.
if (bindings.IsHole)
if (bindings.isHole())
continue;

for (auto &binding : bindings.Bindings) {
Expand Down