Skip to content

Commit 731810e

Browse files
committed
[CSBindings] Check literal coverage as new bindings/requirements are discovered
One more step towards incrementality of binding inference. Instead of trying to determine literal protocol coverage during finalization of the bindings, let's do that as soon as new bindings and/or literal protocol requirements are discovered.
1 parent 41623ab commit 731810e

File tree

3 files changed

+166
-113
lines changed

3 files changed

+166
-113
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4789,6 +4789,10 @@ class ConstraintSystem {
47894789
return !Bindings.empty() || !Defaults.empty() || isDirectHole();
47904790
}
47914791

4792+
/// Determines whether this type variable could be `nil`,
4793+
/// which means that all of its bindings should be optional.
4794+
bool canBeNil() const;
4795+
47924796
/// Determine whether attempting this type variable should be
47934797
/// delayed until the rest of the constraint system is considered
47944798
/// "fully bound" meaning constraints, which affect completeness
@@ -4975,6 +4979,28 @@ class ConstraintSystem {
49754979

49764980
void addLiteral(Constraint *constraint);
49774981

4982+
/// Determines whether the given literal protocol is "covered"
4983+
/// by the given binding - type of the binding could either be
4984+
/// equal (in canonical sense) to the protocol's default type,
4985+
/// or conform to a protocol.
4986+
///
4987+
/// \param literal The literal protocol requirement to check.
4988+
///
4989+
/// \param binding The binding to check for coverage.
4990+
///
4991+
/// \param canBeNil The flag that determines whether given type
4992+
/// variable requires all of its bindings to be optional.
4993+
///
4994+
/// \param isDirectRequirement The flag that determines whether
4995+
/// this literal conformance requirement is associated with the
4996+
/// current type variable or it's inferred.
4997+
///
4998+
/// \returns true if binding covers given literal protocol.
4999+
bool isLiteralCoveredBy(ProtocolDecl *literal,
5000+
PotentialBinding &binding,
5001+
bool canBeNil,
5002+
bool isDirectRequirement) const;
5003+
49785004
/// Add a potential binding to the list of bindings,
49795005
/// coalescing supertype bounds when we are able to compute the meet.
49805006
void addPotentialBinding(PotentialBinding binding,

lib/Sema/CSBindings.cpp

Lines changed: 139 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
using namespace swift;
2323
using namespace constraints;
2424

25+
bool ConstraintSystem::PotentialBindings::canBeNil() const {
26+
auto &ctx = CS.getASTContext();
27+
return Literals.count(
28+
ctx.getProtocol(KnownProtocolKind::ExpressibleByNilLiteral));
29+
}
30+
2531
bool ConstraintSystem::PotentialBinding::isViableForJoin() const {
2632
return Kind == AllowedBindingKind::Supertypes &&
2733
!BindingType->hasLValueType() &&
@@ -349,110 +355,8 @@ void ConstraintSystem::PotentialBindings::inferTransitiveBindings(
349355
}
350356
}
351357

352-
static bool
353-
isUnviableDefaultType(Type defaultType,
354-
llvm::SmallPtrSetImpl<CanType> &existingTypes) {
355-
auto canType = defaultType->getCanonicalType();
356-
357-
if (!defaultType->hasUnboundGenericType())
358-
return !existingTypes.insert(canType).second;
359-
360-
// For generic literal types, check whether we already have a
361-
// specialization of this generic within our list.
362-
// FIXME: This assumes that, e.g., the default literal
363-
// int/float/char/string types are never generic.
364-
auto nominal = defaultType->getAnyNominal();
365-
if (!nominal)
366-
return true;
367-
368-
if (llvm::any_of(existingTypes, [&nominal](CanType existingType) {
369-
// FIXME: Check parents?
370-
return nominal == existingType->getAnyNominal();
371-
}))
372-
return true;
373-
374-
existingTypes.insert(canType);
375-
return false;
376-
}
377-
378358
void ConstraintSystem::PotentialBindings::inferDefaultTypes(
379359
ConstraintSystem &cs, llvm::SmallPtrSetImpl<CanType> &existingTypes) {
380-
bool canBeNil = llvm::any_of(
381-
Literals, [](const std::pair<ProtocolDecl *, LiteralInfo> &literal) {
382-
return literal.first->isSpecificProtocol(
383-
KnownProtocolKind::ExpressibleByNilLiteral);
384-
});
385-
386-
for (auto &binding : Bindings) {
387-
Type type;
388-
389-
switch (binding.Kind) {
390-
case AllowedBindingKind::Exact:
391-
type = binding.BindingType;
392-
break;
393-
394-
case AllowedBindingKind::Subtypes:
395-
case AllowedBindingKind::Supertypes:
396-
type = binding.BindingType->getRValueType();
397-
break;
398-
}
399-
400-
if (type->isTypeVariableOrMember() || type->isHole())
401-
continue;
402-
403-
bool requiresUnwrap = false;
404-
for (auto &literal : Literals) {
405-
auto *protocol = literal.first;
406-
bool isDirectRequirement = std::get<1>(literal.second);
407-
Constraint *&coveredBy = std::get<2>(literal.second);
408-
409-
if (coveredBy)
410-
continue;
411-
412-
// Ignore `ExpressibleByNilLiteral` since it can't produce
413-
// a default type.
414-
if (protocol->isSpecificProtocol(
415-
KnownProtocolKind::ExpressibleByNilLiteral))
416-
continue;
417-
418-
do {
419-
// If the type conforms to this protocol, we're covered.
420-
if (TypeChecker::conformsToProtocol(type, protocol, cs.DC)) {
421-
coveredBy = binding.getSource();
422-
break;
423-
}
424-
425-
// Can't unwrap optionals if there is `ExpressibleByNilLiteral`
426-
// conformance requirement placed on the type variable.
427-
if (canBeNil)
428-
break;
429-
430-
// If this literal protocol is not a direct requirement it
431-
// would not be possible to change optionality while inferring
432-
// bindings for a supertype, so this hack doesn't apply.
433-
if (!isDirectRequirement)
434-
break;
435-
436-
// If we're allowed to bind to subtypes, look through optionals.
437-
// FIXME: This is really crappy special case of computing a reasonable
438-
// result based on the given constraints.
439-
if (binding.Kind == AllowedBindingKind::Subtypes) {
440-
if (auto objTy = type->getOptionalObjectType()) {
441-
requiresUnwrap = true;
442-
type = objTy;
443-
continue;
444-
}
445-
}
446-
447-
requiresUnwrap = false;
448-
break;
449-
} while (true);
450-
}
451-
452-
if (requiresUnwrap)
453-
binding.BindingType = type;
454-
}
455-
456360
for (const auto &literal : Literals) {
457361
Constraint *constraint = nullptr;
458362
bool isDirectRequirement = false;
@@ -470,9 +374,6 @@ void ConstraintSystem::PotentialBindings::inferDefaultTypes(
470374
if (!defaultType)
471375
continue;
472376

473-
if (isUnviableDefaultType(defaultType, existingTypes))
474-
continue;
475-
476377
// We need to figure out whether this is a direct conformance
477378
// requirement or inferred transitive one to identify binding
478379
// kind correctly.
@@ -611,6 +512,86 @@ void ConstraintSystem::PotentialBindings::addDefault(Constraint *constraint) {
611512
Defaults.insert({defaultTy->getCanonicalType(), constraint});
612513
}
613514

515+
static bool isCoveredBy(ProtocolDecl *protocol, Type type, DeclContext *useDC) {
516+
auto coversDefaultType = [](Type type, Type defaultType) -> bool {
517+
if (!defaultType->hasUnboundGenericType())
518+
return type->isEqual(defaultType);
519+
520+
// For generic literal types, check whether we already have a
521+
// specialization of this generic within our list.
522+
// FIXME: This assumes that, e.g., the default literal
523+
// int/float/char/string types are never generic.
524+
auto nominal = defaultType->getAnyNominal();
525+
if (!nominal)
526+
return false;
527+
528+
// FIXME: Check parents?
529+
return nominal == type->getAnyNominal();
530+
};
531+
532+
if (auto defaultType = TypeChecker::getDefaultType(protocol, useDC)) {
533+
if (coversDefaultType(type, defaultType))
534+
return true;
535+
}
536+
537+
return bool(TypeChecker::conformsToProtocol(type, protocol, useDC));
538+
}
539+
540+
bool ConstraintSystem::PotentialBindings::isLiteralCoveredBy(
541+
ProtocolDecl *literal, PotentialBinding &binding, bool canBeNil,
542+
bool isDirectRequirement) const {
543+
auto type = binding.BindingType;
544+
switch (binding.Kind) {
545+
case AllowedBindingKind::Exact:
546+
type = binding.BindingType;
547+
break;
548+
549+
case AllowedBindingKind::Subtypes:
550+
case AllowedBindingKind::Supertypes:
551+
type = binding.BindingType->getRValueType();
552+
break;
553+
}
554+
555+
if (type->isTypeVariableOrMember() || type->isHole())
556+
return false;
557+
558+
bool requiresUnwrap = false;
559+
do {
560+
if (isCoveredBy(literal, type, CS.DC)) {
561+
// FIXME: Side-effect like this is not great (to say the least),
562+
// but this is an artifact of the binding collection which could
563+
// be fixed separately.
564+
if (requiresUnwrap)
565+
binding.BindingType = type;
566+
return true;
567+
}
568+
569+
// Can't unwrap optionals if there is `ExpressibleByNilLiteral`
570+
// conformance requirement placed on the type variable.
571+
if (canBeNil)
572+
return false;
573+
574+
// If this literal protocol is not a direct requirement it
575+
// would not be possible to change optionality while inferring
576+
// bindings for a supertype, so this hack doesn't apply.
577+
if (!isDirectRequirement)
578+
return false;
579+
580+
// If we're allowed to bind to subtypes, look through optionals.
581+
// FIXME: This is really crappy special case of computing a reasonable
582+
// result based on the given constraints.
583+
if (binding.Kind == AllowedBindingKind::Subtypes) {
584+
if (auto objTy = type->getOptionalObjectType()) {
585+
requiresUnwrap = true;
586+
type = objTy;
587+
continue;
588+
}
589+
}
590+
591+
return false;
592+
} while (true);
593+
}
594+
614595
void ConstraintSystem::PotentialBindings::addPotentialBinding(
615596
PotentialBinding binding, bool allowJoinMeet) {
616597
assert(!binding.BindingType->is<ErrorType>());
@@ -654,6 +635,29 @@ void ConstraintSystem::PotentialBindings::addPotentialBinding(
654635
if (!isViable(binding))
655636
return;
656637

638+
// Check whether the given binding covers any of the literal protocols
639+
// associated with this type variable.
640+
{
641+
bool allowsNil = canBeNil();
642+
643+
for (auto &literal : Literals) {
644+
auto *protocol = literal.first;
645+
646+
// Skip conformance to `nil` protocol since it doesn't
647+
// have a default type and can't affect binding set.
648+
if (protocol->isSpecificProtocol(
649+
KnownProtocolKind::ExpressibleByNilLiteral))
650+
continue;
651+
652+
auto isDirectRequirement = std::get<1>(literal.second);
653+
auto *&coveredBy = std::get<2>(literal.second);
654+
655+
if (!coveredBy &&
656+
isLiteralCoveredBy(protocol, binding, allowsNil, isDirectRequirement))
657+
coveredBy = binding.getSource();
658+
}
659+
}
660+
657661
Bindings.push_back(std::move(binding));
658662
}
659663

@@ -689,9 +693,36 @@ void ConstraintSystem::PotentialBindings::addLiteral(Constraint *constraint) {
689693
}
690694
}
691695

692-
Literals.insert(
693-
{protocol, std::make_tuple(constraint, isDirectRequirement(constraint),
694-
/*coveredBy=*/nullptr)});
696+
if (Literals.count(protocol) > 0)
697+
return;
698+
699+
bool isDirect = isDirectRequirement(constraint);
700+
Constraint *coveredBy = nullptr;
701+
702+
// Coverage is not applicable to `ExpressibleByNilLiteral` since it
703+
// doesn't have a default type.
704+
if (protocol->isSpecificProtocol(
705+
KnownProtocolKind::ExpressibleByNilLiteral)) {
706+
Literals.insert(
707+
{protocol, std::make_tuple(constraint, isDirect, coveredBy)});
708+
return;
709+
}
710+
711+
// Check whether any of the existing bindings covers this literal
712+
// protocol.
713+
{
714+
bool allowsNil = canBeNil();
715+
716+
for (auto &binding : Bindings) {
717+
if (!coveredBy &&
718+
isLiteralCoveredBy(protocol, binding, allowsNil, isDirect)) {
719+
coveredBy = binding.getSource();
720+
break;
721+
}
722+
}
723+
}
724+
725+
Literals.insert({protocol, std::make_tuple(constraint, isDirect, coveredBy)});
695726
}
696727

697728
bool ConstraintSystem::PotentialBindings::isViable(

lib/Sema/ConstraintSystem.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5330,11 +5330,7 @@ bool ConstraintSystem::isReadOnlyKeyPathComponent(
53305330
TypeVarBindingProducer::TypeVarBindingProducer(
53315331
ConstraintSystem::PotentialBindings &bindings)
53325332
: BindingProducer(bindings.CS, bindings.TypeVar->getImpl().getLocator()),
5333-
TypeVar(bindings.TypeVar),
5334-
CanBeNil(llvm::any_of(bindings.Literals, [](const auto &literal) {
5335-
return literal.first->isSpecificProtocol(
5336-
KnownProtocolKind::ExpressibleByNilLiteral);
5337-
})) {
5333+
TypeVar(bindings.TypeVar), CanBeNil(bindings.canBeNil()) {
53385334
if (bindings.isDirectHole()) {
53395335
auto *locator = getLocator();
53405336
// If this type variable is associated with a code completion token

0 commit comments

Comments
 (0)