Skip to content

Commit 0a2f1b1

Browse files
committed
[CSBindings] Delay key path type inference until literal capability is known
Since key path root is now transitively inferred. Key path type inference can be delayed until key path is resolved enough to infer its capability. This solves multiple problems: - Inference fully controls what key path type is bound to; - KeyPath constraint simplification doesn't have to double-check the capability and attempt to re-bind key path type; - Custom logic to resolve key path type is no longer necessary; - Diagnostics are improved because capability and root/value type mismatch are diagnosed when key path is matched against the contextual type.
1 parent 4f5123e commit 0a2f1b1

File tree

4 files changed

+108
-64
lines changed

4 files changed

+108
-64
lines changed

include/swift/Sema/CSBindings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ enum class LiteralBindingKind : uint8_t {
7171
/// along with information that can be used to construct related
7272
/// bindings, e.g., the supertypes of a given type.
7373
struct PotentialBinding {
74+
friend class BindingSet;
75+
7476
/// The type to which the type variable can be bound.
7577
Type BindingType;
7678

lib/Sema/CSBindings.cpp

Lines changed: 104 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ bool BindingSet::isDelayed() const {
9696

9797
// Delay key path literal type binding until there is at least
9898
// one contextual binding (or default is promoted into a binding).
99-
if (TypeVar->getImpl().isKeyPathType() && Bindings.empty())
99+
if (TypeVar->getImpl().isKeyPathType() && !Defaults.empty())
100100
return true;
101101

102102
if (isHole()) {
@@ -178,7 +178,7 @@ bool BindingSet::isPotentiallyIncomplete() const {
178178
// contextual type or key path is resolved enough to infer
179179
// capability and promote default into a binding.
180180
if (TypeVar->getImpl().isKeyPathType())
181-
return Bindings.empty();
181+
return !Defaults.empty();
182182

183183
// If current type variable is associated with a code completion token
184184
// it's possible that it doesn't have enough contextual information
@@ -531,6 +531,24 @@ void BindingSet::inferTransitiveBindings(
531531
}
532532
}
533533

534+
static BoundGenericType *getKeyPathType(ASTContext &ctx,
535+
KeyPathCapability capability,
536+
Type rootType, Type valueType) {
537+
switch (capability) {
538+
case KeyPathCapability::ReadOnly:
539+
return BoundGenericType::get(ctx.getKeyPathDecl(), /*parent=*/Type(),
540+
{rootType, valueType});
541+
542+
case KeyPathCapability::Writable:
543+
return BoundGenericType::get(ctx.getWritableKeyPathDecl(),
544+
/*parent=*/Type(), {rootType, valueType});
545+
546+
case KeyPathCapability::ReferenceWritable:
547+
return BoundGenericType::get(ctx.getReferenceWritableKeyPathDecl(),
548+
/*parent=*/Type(), {rootType, valueType});
549+
}
550+
}
551+
534552
void BindingSet::finalize(
535553
llvm::SmallDenseMap<TypeVariableType *, BindingSet> &inferredBindings) {
536554
inferTransitiveBindings(inferredBindings);
@@ -573,6 +591,90 @@ void BindingSet::finalize(
573591
}
574592
}
575593

594+
if (TypeVar->getImpl().isKeyPathType()) {
595+
auto &ctx = CS.getASTContext();
596+
597+
auto *keyPathLoc = TypeVar->getImpl().getLocator();
598+
auto *keyPath = castToExpr<KeyPathExpr>(keyPathLoc->getAnchor());
599+
600+
bool isValid;
601+
llvm::Optional<KeyPathCapability> capability;
602+
603+
std::tie(isValid, capability) = CS.inferKeyPathLiteralCapability(TypeVar);
604+
605+
if (!isValid) {
606+
// If key path is invalid we have to drop all the contextual
607+
// bindings, none of the could be used unless capability is
608+
// known.
609+
Bindings.clear();
610+
611+
// If one of the references in a key path is invalid let's add
612+
// a placeholder binding in diagnostic mode to indicate that
613+
// the key path cannot be properly resolved.
614+
if (CS.shouldAttemptFixes()) {
615+
auto rootTy = CS.getKeyPathRootType(keyPath);
616+
// If key path is structurally correct and has a resolved root
617+
// type, let's promote the fallback type into a binding because
618+
// root would have been inferred from explicit type already and
619+
// it's benefitial for diagnostics to assign a non-placeholder
620+
// type to key path literal to propagate root/value to the context.
621+
if (!keyPath->hasSingleInvalidComponent() &&
622+
(keyPath->getParsedRoot() ||
623+
!CS.getFixedType(rootTy)->isTypeVariableOrMember())) {
624+
auto fallback = llvm::find_if(Defaults, [](const auto &entry) {
625+
return entry.second->getKind() == ConstraintKind::FallbackType;
626+
});
627+
assert(fallback != Defaults.end());
628+
addBinding(
629+
{fallback->first, AllowedBindingKind::Exact, fallback->second});
630+
} else {
631+
addBinding(PotentialBinding::forHole(
632+
TypeVar, CS.getConstraintLocator(
633+
keyPath, ConstraintLocator::FallbackType)));
634+
}
635+
}
636+
637+
// No need for fallback if key path is invalid.
638+
Defaults.clear();
639+
return;
640+
}
641+
642+
// If the key path is sufficiently resolved we can add inferred binding
643+
// to the set.
644+
if (capability) {
645+
SmallSetVector<PotentialBinding, 4> updatedBindings;
646+
for (const auto &binding : Bindings) {
647+
auto bindingTy = binding.BindingType->lookThroughAllOptionalTypes();
648+
649+
assert(isKnownKeyPathType(bindingTy) ||
650+
bindingTy->is<FunctionType>());
651+
652+
// Functions don't have capability so we can simply add them.
653+
if (bindingTy->is<FunctionType>())
654+
updatedBindings.insert(binding);
655+
}
656+
657+
// Note that the binding is formed using root & value
658+
// type variables produced during constraint generation
659+
// because at this point root is already known (otherwise
660+
// inference wouldn't been able to determine key path's
661+
// capability) and we always want to infer value from
662+
// the key path and match it to a contextual type to produce
663+
// better diagnostics.
664+
auto keyPathTy =
665+
getKeyPathType(ctx, *capability, CS.getKeyPathRootType(keyPath),
666+
CS.getKeyPathValueType(keyPath));
667+
668+
updatedBindings.insert(
669+
{keyPathTy, AllowedBindingKind::Exact, keyPathLoc});
670+
671+
Bindings = std::move(updatedBindings);
672+
Defaults.clear();
673+
}
674+
675+
return;
676+
}
677+
576678
if (CS.shouldAttemptFixes() &&
577679
locator->isLastElement<LocatorPathElt::UnresolvedMemberChainResult>()) {
578680
// Let's see whether this chain is valid, if it isn't then to avoid
@@ -927,60 +1029,6 @@ void PotentialBindings::addDefault(Constraint *constraint) {
9271029

9281030
void BindingSet::addDefault(Constraint *constraint) {
9291031
auto defaultTy = constraint->getSecondType();
930-
931-
if (TypeVar->getImpl().isKeyPathType() && Bindings.empty()) {
932-
if (constraint->getKind() == ConstraintKind::FallbackType) {
933-
auto &ctx = CS.getASTContext();
934-
935-
bool isValid;
936-
llvm::Optional<KeyPathCapability> capability;
937-
938-
std::tie(isValid, capability) = CS.inferKeyPathLiteralCapability(TypeVar);
939-
940-
if (!isValid) {
941-
// If one of the references in a key path is invalid let's add
942-
// a placeholder binding in diagnostic mode to indicate that
943-
// the key path cannot be properly resolved.
944-
if (CS.shouldAttemptFixes()) {
945-
addBinding({PlaceholderType::get(ctx, TypeVar),
946-
AllowedBindingKind::Exact, constraint});
947-
}
948-
949-
// During normal solving the set has to stay empty.
950-
return;
951-
}
952-
953-
if (capability) {
954-
auto *keyPathType = defaultTy->castTo<BoundGenericType>();
955-
956-
auto root = keyPathType->getGenericArgs()[0];
957-
auto value = keyPathType->getGenericArgs()[1];
958-
959-
switch (*capability) {
960-
case KeyPathCapability::ReadOnly:
961-
break;
962-
963-
case KeyPathCapability::Writable:
964-
keyPathType = BoundGenericType::get(ctx.getWritableKeyPathDecl(),
965-
/*parent=*/Type(), {root, value});
966-
break;
967-
968-
case KeyPathCapability::ReferenceWritable:
969-
keyPathType =
970-
BoundGenericType::get(ctx.getReferenceWritableKeyPathDecl(),
971-
/*parent=*/Type(), {root, value});
972-
break;
973-
}
974-
975-
addBinding({keyPathType, AllowedBindingKind::Exact, constraint});
976-
}
977-
978-
// If key path is not yet sufficiently resolved, don't add any
979-
// bindings.
980-
return;
981-
}
982-
}
983-
9841032
Defaults.insert({defaultTy->getCanonicalType(), constraint});
9851033
}
9861034

lib/Sema/CSSimplify.cpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4471,13 +4471,6 @@ ConstraintSystem::matchTypesBindTypeVar(
44714471
: getTypeMatchFailure(locator);
44724472
}
44734473

4474-
if (typeVar->getImpl().isKeyPathType()) {
4475-
if (flags.contains(TMF_BindingTypeVariable))
4476-
return resolveKeyPath(typeVar, type, locator)
4477-
? getTypeMatchSuccess()
4478-
: getTypeMatchFailure(locator);
4479-
}
4480-
44814474
assignFixedType(typeVar, type, /*updateState=*/true,
44824475
/*notifyInference=*/!flags.contains(TMF_BindingTypeVariable));
44834476

test/expr/unary/keypath/keypath.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,8 @@ func testSubtypeKeypathClass(_ keyPath: ReferenceWritableKeyPath<Base, Int>) {
692692

693693
func testSubtypeKeypathProtocol(_ keyPath: ReferenceWritableKeyPath<PP, Int>) {
694694
testSubtypeKeypathProtocol(\Base.i)
695-
// expected-error@-1 {{key path with root type 'any PP' cannot be applied to a base of type 'Base'}}
695+
// expected-error@-1 {{cannot convert value of type 'ReferenceWritableKeyPath<Base, Int>' to expected argument type 'ReferenceWritableKeyPath<any PP, Int>'}}
696+
// expected-note@-2 {{arguments to generic parameter 'Root' ('Base' and 'any PP') are expected to be equal}}
696697
}
697698

698699
// rdar://problem/32057712

0 commit comments

Comments
 (0)