Merge pull request #85902 from slavapestov/csbindings-cleanups

Sema: A few more simple CSBindings cleanups
This commit is contained in:
Slava Pestov
2025-12-09 18:29:53 -05:00
committed by GitHub
4 changed files with 219 additions and 162 deletions

View File

@@ -147,6 +147,8 @@ struct PotentialBinding {
return {placeholderTy, AllowedBindingKind::Exact,
PointerUnion<Constraint *, ConstraintLocator *>()};
}
void print(llvm::raw_ostream &out, const PrintOptions &PO) const;
};
struct LiteralRequirement {

View File

@@ -38,15 +38,6 @@ void ConstraintGraphNode::initBindingSet() {
Set.emplace(CG.getConstraintSystem(), TypeVar, Potential);
}
/// Check whether there exists a type that could be implicitly converted
/// to a given type i.e. is the given type is Double or Optional<..> this
/// function is going to return true because CGFloat could be converted
/// to a Double and non-optional value could be injected into an optional.
static bool hasConversions(Type);
static std::optional<Type> checkTypeOfBinding(TypeVariableType *typeVar,
Type type);
BindingSet::BindingSet(ConstraintSystem &CS, TypeVariableType *TypeVar,
const PotentialBindings &info)
: CS(CS), TypeVar(TypeVar), Info(info) {
@@ -121,6 +112,73 @@ bool PotentialBinding::isViableForJoin() const {
!isDefaultableBinding();
}
namespace {
struct PrintableBinding {
private:
enum class BindingKind { Exact, Subtypes, Supertypes, Literal };
BindingKind Kind;
Type BindingType;
bool Viable;
PrintableBinding(BindingKind kind, Type bindingType, bool viable)
: Kind(kind), BindingType(bindingType), Viable(viable) {}
public:
static PrintableBinding supertypesOf(Type binding) {
return PrintableBinding{BindingKind::Supertypes, binding, true};
}
static PrintableBinding subtypesOf(Type binding) {
return PrintableBinding{BindingKind::Subtypes, binding, true};
}
static PrintableBinding exact(Type binding) {
return PrintableBinding{BindingKind::Exact, binding, true};
}
static PrintableBinding literalDefaultType(Type binding, bool viable) {
return PrintableBinding{BindingKind::Literal, binding, viable};
}
void print(llvm::raw_ostream &out, const PrintOptions &PO,
unsigned indent = 0) const {
switch (Kind) {
case BindingKind::Exact:
break;
case BindingKind::Subtypes:
out << "(subtypes of) ";
break;
case BindingKind::Supertypes:
out << "(supertypes of) ";
break;
case BindingKind::Literal:
out << "(default type of literal) ";
break;
}
if (BindingType)
BindingType.print(out, PO);
if (!Viable)
out << " [literal not viable]";
}
};
}
void PotentialBinding::print(llvm::raw_ostream &out,
const PrintOptions &PO) const {
switch (Kind) {
case AllowedBindingKind::Exact:
PrintableBinding::exact(BindingType).print(out, PO);
break;
case AllowedBindingKind::Supertypes:
PrintableBinding::supertypesOf(BindingType).print(out, PO);
break;
case AllowedBindingKind::Subtypes:
PrintableBinding::subtypesOf(BindingType).print(out, PO);
break;
}
}
bool BindingSet::isDelayed() const {
if (auto *locator = TypeVar->getImpl().getLocator()) {
if (locator->isLastElement<LocatorPathElt::MemberRefBase>()) {
@@ -228,10 +286,11 @@ bool BindingSet::involvesTypeVariables() const {
TypeVar->getImpl().canBindToPack())
return true;
// This is effectively O(1) right now since bindings are re-computed
// on each step of the solver, but once bindings are computed
// incrementally it becomes more important to double-check that
// any adjacent type variables found previously are still unresolved.
// This is effectively a no-op right now since bindings are re-computed
// on each step of the solver and fixed types won't appear in AdjancentVars,
// but once bindings are computed incrementally it becomes important
// to double-check that any adjacent type variables found previously are
// still unresolved.
return llvm::any_of(AdjacentVars, [](TypeVariableType *typeVar) {
return !typeVar->getImpl().getFixedType(/*record=*/nullptr);
});
@@ -967,15 +1026,6 @@ void BindingSet::determineLiteralCoverage() {
}
void BindingSet::addLiteralRequirement(Constraint *constraint) {
auto isDirectRequirement = [&](Constraint *constraint) -> bool {
if (auto *typeVar = constraint->getFirstType()->getAs<TypeVariableType>()) {
auto *repr = CS.getRepresentative(typeVar);
return repr == TypeVar;
}
return false;
};
auto *protocol = constraint->getProtocol();
// Let's try to coalesce integer and floating point literal protocols
@@ -1001,23 +1051,25 @@ void BindingSet::addLiteralRequirement(Constraint *constraint) {
if (Literals.count(protocol) > 0)
return;
auto isDirectRequirement = [&](Constraint *constraint) -> bool {
if (auto *typeVar = constraint->getFirstType()->getAs<TypeVariableType>()) {
auto *repr = CS.getRepresentative(typeVar);
return repr == TypeVar;
}
return false;
};
bool isDirect = isDirectRequirement(constraint);
// Coverage is not applicable to `ExpressibleByNilLiteral` since it
// doesn't have a default type.
if (protocol->isSpecificProtocol(
Type defaultType;
// `ExpressibleByNilLiteral` doesn't have a default type.
if (!protocol->isSpecificProtocol(
KnownProtocolKind::ExpressibleByNilLiteral)) {
Literals.insert(
{protocol, LiteralRequirement(constraint,
/*DefaultType=*/Type(), isDirect)});
return;
defaultType = TypeChecker::getDefaultType(protocol, CS.DC);
}
// Check whether any of the existing bindings covers this literal
// protocol.
LiteralRequirement literal(
constraint, TypeChecker::getDefaultType(protocol, CS.DC), isDirect);
LiteralRequirement literal(constraint, defaultType, isDirect);
Literals.insert({protocol, std::move(literal)});
}
@@ -1316,9 +1368,89 @@ void PotentialBindings::addPotentialBinding(TypeVariableType *TypeVar,
binding = binding.withType(binding.BindingType->getRValueType());
}
LLVM_DEBUG(
PrintOptions PO = PrintOptions::forDebugging();
llvm::dbgs() << "Recording ";
TypeVar->print(llvm::dbgs(), PO);
llvm::dbgs() << " ";
binding.print(llvm::dbgs(), PO);
llvm::dbgs() << " from ";
if (auto *constraint = dyn_cast<Constraint *>(binding.BindingSource)) {
constraint->print(llvm::dbgs(), &TypeVar->getASTContext().SourceMgr, 0);
} else {
auto *locator = cast<ConstraintLocator *>(binding.BindingSource);
locator->dump(&TypeVar->getASTContext().SourceMgr, llvm::dbgs());
}
llvm::dbgs() << "\n");
Bindings.push_back(std::move(binding));
}
/// Check whether the given type can be used as a binding for the given
/// type variable.
///
/// \returns true if the binding is okay.
static bool checkTypeOfBinding(TypeVariableType *typeVar, Type type) {
// If the type references the type variable, don't permit the binding.
if (type->hasTypeVariable()) {
SmallPtrSet<TypeVariableType *, 4> referencedTypeVars;
type->getTypeVariables(referencedTypeVars);
if (referencedTypeVars.count(typeVar))
return false;
}
{
auto objType = type->getWithoutSpecifierType();
// If the type is a type variable itself, don't permit the binding.
if (objType->is<TypeVariableType>())
return false;
// Don't bind to a dependent member type, even if it's currently
// wrapped in any number of optionals, because binding producer
// might unwrap and try to attempt it directly later.
if (objType->lookThroughAllOptionalTypes()->is<DependentMemberType>())
return false;
}
// Okay, allow the binding.
return true;
}
/// Check whether there exists a type that could be implicitly converted
/// to a given type i.e. is the given type is Double or Optional<..> this
/// function is going to return true because CGFloat could be converted
/// to a Double and non-optional value could be injected into an optional.
static bool hasConversions(Type type) {
if (type->isAnyHashable() || type->isDouble() || type->isCGFloat())
return true;
if (type->getAnyPointerElementType())
return true;
if (auto *structTy = type->getAs<BoundGenericStructType>()) {
if (auto eltTy = structTy->getArrayElementType()) {
return hasConversions(eltTy);
} else if (auto pair = ConstraintSystem::isDictionaryType(structTy)) {
return hasConversions(pair->second);
} else if (auto eltTy = ConstraintSystem::isSetType(structTy)) {
return hasConversions(*eltTy);
}
return false;
}
if (auto *enumTy = type->getAs<BoundGenericEnumType>()) {
if (enumTy->getOptionalObjectType())
return true;
return false;
}
return !(type->is<StructType>() || type->is<EnumType>() ||
type->is<BuiltinType>() || type->is<ArchetypeType>());
}
bool BindingSet::isViable(PotentialBinding &binding, bool isTransitive) {
// Prevent against checking against the same opened nominal type
// over and over again. Doing so means redundant work in the best
@@ -1395,36 +1527,6 @@ bool BindingSet::isViable(PotentialBinding &binding, bool isTransitive) {
return true;
}
static bool hasConversions(Type type) {
if (type->isAnyHashable() || type->isDouble() || type->isCGFloat())
return true;
if (type->getAnyPointerElementType())
return true;
if (auto *structTy = type->getAs<BoundGenericStructType>()) {
if (auto eltTy = structTy->getArrayElementType()) {
return hasConversions(eltTy);
} else if (auto pair = ConstraintSystem::isDictionaryType(structTy)) {
return hasConversions(pair->second);
} else if (auto eltTy = ConstraintSystem::isSetType(structTy)) {
return hasConversions(*eltTy);
}
return false;
}
if (auto *enumTy = type->getAs<BoundGenericEnumType>()) {
if (enumTy->getOptionalObjectType())
return true;
return false;
}
return !(type->is<StructType>() || type->is<EnumType>() ||
type->is<BuiltinType>() || type->is<ArchetypeType>());
}
bool BindingSet::favoredOverDisjunction(Constraint *disjunction) const {
if (isHole())
return false;
@@ -1576,38 +1678,6 @@ BindingSet ConstraintSystem::getBindingsFor(TypeVariableType *typeVar) {
return bindings;
}
/// Check whether the given type can be used as a binding for the given
/// type variable.
///
/// \returns the type to bind to, if the binding is okay.
static std::optional<Type> checkTypeOfBinding(TypeVariableType *typeVar,
Type type) {
// If the type references the type variable, don't permit the binding.
if (type->hasTypeVariable()) {
SmallPtrSet<TypeVariableType *, 4> referencedTypeVars;
type->getTypeVariables(referencedTypeVars);
if (referencedTypeVars.count(typeVar))
return std::nullopt;
}
{
auto objType = type->getWithoutSpecifierType();
// If the type is a type variable itself, don't permit the binding.
if (objType->is<TypeVariableType>())
return std::nullopt;
// Don't bind to a dependent member type, even if it's currently
// wrapped in any number of optionals, because binding producer
// might unwrap and try to attempt it directly later.
if (objType->lookThroughAllOptionalTypes()->is<DependentMemberType>())
return std::nullopt;
}
// Okay, allow the binding (with the simplified type).
return type;
}
std::optional<PotentialBinding>
PotentialBindings::inferFromRelational(ConstraintSystem &CS,
TypeVariableType *TypeVar,
@@ -1616,11 +1686,29 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
ConstraintClassification::Relational &&
"only relational constraints handled here");
LLVM_DEBUG(
llvm::dbgs() << "inferFromRelational(";
TypeVar->print(llvm::dbgs(), PrintOptions::forDebugging());
llvm::dbgs() << ", ";
constraint->print(llvm::dbgs(), &CS.getASTContext().SourceMgr, 0);
llvm::dbgs() << ")\n");
auto first = CS.simplifyType(constraint->getFirstType());
auto second = CS.simplifyType(constraint->getSecondType());
if (first->is<TypeVariableType>() && first->isEqual(second))
#define DEBUG_BAILOUT(msg) \
LLVM_DEBUG( \
PrintOptions PO = PrintOptions::forDebugging(); \
llvm::dbgs() << msg << " "; \
TypeVar->print(llvm::dbgs(), PO); \
llvm::dbgs() << " from "; \
constraint->print(llvm::dbgs(), &CS.getASTContext().SourceMgr); \
llvm::dbgs() << "\n");
if (first->is<TypeVariableType>() && first->isEqual(second)) {
DEBUG_BAILOUT("First is second");
return std::nullopt;
}
Type type;
AllowedBindingKind kind;
@@ -1641,6 +1729,7 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
// of bindings for them until closure's body is opened.
if (auto *typeVar = first->getAs<TypeVariableType>()) {
if (typeVar->getImpl().isClosureType()) {
DEBUG_BAILOUT("Delayed (1)");
DelayedBy.push_back(constraint);
return std::nullopt;
}
@@ -1674,8 +1763,10 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
}
// Do not attempt to bind to ErrorType.
if (type->hasError())
if (type->hasError()) {
DEBUG_BAILOUT("Has error");
return std::nullopt;
}
if (TypeVar->getImpl().isKeyPathType()) {
auto objectTy = type->lookThroughAllOptionalTypes();
@@ -1694,8 +1785,10 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
}
}
if (!(objectTy->isKnownKeyPathType() || objectTy->is<AnyFunctionType>()))
if (!(objectTy->isKnownKeyPathType() || objectTy->is<AnyFunctionType>())) {
DEBUG_BAILOUT("Bad key path type (1)");
return std::nullopt;
}
}
if (TypeVar->getImpl().isKeyPathSubscriptIndex()) {
@@ -1713,6 +1806,7 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
if (auto superclass = layout.explicitSuperclass) {
type = superclass;
} else if (!CS.shouldAttemptFixes()) {
DEBUG_BAILOUT("Bad key path type (2)");
return std::nullopt;
}
}
@@ -1736,8 +1830,10 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
// type of a chain, Result should always be a concrete type which conforms
// to the protocol inferred for the base.
if (constraint->getKind() == ConstraintKind::UnresolvedMemberChainBase &&
kind == AllowedBindingKind::Subtypes && type->is<ProtocolType>())
kind == AllowedBindingKind::Subtypes && type->is<ProtocolType>()) {
DEBUG_BAILOUT("Unresolved member chain base");
return std::nullopt;
}
}
if (constraint->getKind() == ConstraintKind::LValueObject) {
@@ -1745,14 +1841,17 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
// not the other way around, that would be handled by constraint
// simplification.
if (kind == AllowedBindingKind::Subtypes) {
if (type->isTypeVariableOrMember())
if (type->isTypeVariableOrMember()) {
DEBUG_BAILOUT("Disallowed l-value inference");
return std::nullopt;
}
type = LValueType::get(type);
} else {
// Right-hand side of the l-value object constraint can only
// be bound via constraint simplification when l-value type
// is inferred or contextually from other constraints.
DEBUG_BAILOUT("Delayed (2)");
DelayedBy.push_back(constraint);
return std::nullopt;
}
@@ -1793,6 +1892,7 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
if (!containsSelf)
DelayedBy.push_back(constraint);
DEBUG_BAILOUT("Dependent member");
return std::nullopt;
}
@@ -1814,13 +1914,13 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
}
// Check whether we can perform this binding.
if (auto boundType = checkTypeOfBinding(TypeVar, type)) {
type = *boundType;
} else {
if (!checkTypeOfBinding(TypeVar, type)) {
auto *bindingTypeVar = type->getRValueType()->getAs<TypeVariableType>();
if (!bindingTypeVar)
if (!bindingTypeVar) {
DEBUG_BAILOUT("Not a type variable");
return std::nullopt;
}
// If current type variable is associated with a code completion token
// it's possible that it doesn't have enough contextual information
@@ -1887,8 +1987,10 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
// lvalue-binding rules.
if (auto otherTypeVar = type->getAs<TypeVariableType>()) {
if (TypeVar->getImpl().canBindToLValue() !=
otherTypeVar->getImpl().canBindToLValue())
otherTypeVar->getImpl().canBindToLValue()) {
DEBUG_BAILOUT("LValue mismatch");
return std::nullopt;
}
}
if (type->is<InOutType>() && !TypeVar->getImpl().canBindToInOut())
@@ -1913,6 +2015,8 @@ PotentialBindings::inferFromRelational(ConstraintSystem &CS,
return PotentialBinding{type, kind, constraint};
}
#undef DEBUG_BAILOUT
/// Retrieve the set of potential type bindings for the given
/// representative type variable, along with flags indicating whether
/// those types should be opened.
@@ -2362,54 +2466,6 @@ void BindingSet::dump(llvm::raw_ostream &out, unsigned indent) const {
if (numDefaultable > 0)
out << "[defaultable bindings: " << numDefaultable << "] ";
struct PrintableBinding {
private:
enum class BindingKind { Exact, Subtypes, Supertypes, Literal };
BindingKind Kind;
Type BindingType;
bool Viable;
PrintableBinding(BindingKind kind, Type bindingType, bool viable)
: Kind(kind), BindingType(bindingType), Viable(viable) {}
public:
static PrintableBinding supertypesOf(Type binding) {
return PrintableBinding{BindingKind::Supertypes, binding, true};
}
static PrintableBinding subtypesOf(Type binding) {
return PrintableBinding{BindingKind::Subtypes, binding, true};
}
static PrintableBinding exact(Type binding) {
return PrintableBinding{BindingKind::Exact, binding, true};
}
static PrintableBinding literalDefaultType(Type binding, bool viable) {
return PrintableBinding{BindingKind::Literal, binding, viable};
}
void print(llvm::raw_ostream &out, const PrintOptions &PO,
unsigned indent = 0) const {
switch (Kind) {
case BindingKind::Exact:
break;
case BindingKind::Subtypes:
out << "(subtypes of) ";
break;
case BindingKind::Supertypes:
out << "(supertypes of) ";
break;
case BindingKind::Literal:
out << "(default type of literal) ";
break;
}
if (BindingType)
BindingType.print(out, PO);
if (!Viable)
out << " [literal not viable]";
}
};
out << "[potential bindings: ";
SmallVector<PrintableBinding, 2> potentialBindings;
for (const auto &binding : Bindings) {
@@ -2677,8 +2733,7 @@ bool TypeVarBindingProducer::computeNext() {
for (auto supertype : enumerateDirectSupertypes(type)) {
// If we're not allowed to try this binding, skip it.
if (auto simplifiedSuper = checkTypeOfBinding(TypeVar, supertype)) {
auto supertype = *simplifiedSuper;
if (checkTypeOfBinding(TypeVar, supertype)) {
// A key path type cannot be bound to type-erased key path variants.
if (TypeVar->getImpl().isKeyPathType() &&
isTypeErasedKeyPathType(supertype))

View File

@@ -910,6 +910,9 @@ bool ConstraintGraph::contractEdges() {
if (!(tyvar1 && tyvar2))
continue;
auto rep1 = CS.getRepresentative(tyvar1);
auto rep2 = CS.getRepresentative(tyvar2);
// If the argument is allowed to bind to `inout`, in general,
// it's invalid to contract the edge between argument and parameter,
// but if we can prove that there are no possible bindings
@@ -919,9 +922,9 @@ bool ConstraintGraph::contractEdges() {
// Such action is valid because argument type variable can
// only get its bindings from related overload, which gives
// us enough information to decided on l-valueness.
if (tyvar1->getImpl().canBindToInOut()) {
if (rep1->getImpl().canBindToInOut()) {
bool isNotContractable = true;
auto bindings = CS.getBindingsFor(tyvar1);
auto bindings = CS.getBindingsFor(rep1);
if (bindings.isViable()) {
// Holes can't be contracted.
if (bindings.isHole())
@@ -949,9 +952,6 @@ bool ConstraintGraph::contractEdges() {
continue;
}
auto rep1 = CS.getRepresentative(tyvar1);
auto rep2 = CS.getRepresentative(tyvar2);
if (CS.isDebugMode()) {
auto indent = CS.solverState ? CS.solverState->getCurrentIndent() : 0;
auto &log = llvm::errs().indent(indent);

View File

@@ -1,5 +1,5 @@
// {"kind":"typecheck","original":"5af4a4fb","signature":"swift::constraints::ConstraintSystem::getBindingsFor(swift::TypeVariableType*)","signatureAssert":"Assertion failed: (typeVar->getImpl().getRepresentative(nullptr) == typeVar && \"not a representative\"), function getBindingsFor"}
// RUN: not --crash %target-swift-frontend -typecheck %s
// RUN: not %target-swift-frontend -typecheck %s
struct a {
func b(arr: [a]) {
arr.compactMap {