diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index 9dc01dab101db..3ea2e334a36a1 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -1371,6 +1371,14 @@ bool ContextualFailure::diagnoseAsError() { break; } + case ConstraintLocator::ContextualType: { + if (isKnownKeyPathType(FromType) && isKnownKeyPathType(ToType)) { + diagnostic = diag::cannot_convert_initializer_value; + break; + } + + LLVM_FALLTHROUGH; + } default: return false; } diff --git a/lib/Sema/CSFix.cpp b/lib/Sema/CSFix.cpp index 3a5b984533c7a..537e8336c71fc 100644 --- a/lib/Sema/CSFix.cpp +++ b/lib/Sema/CSFix.cpp @@ -477,3 +477,10 @@ AllowInvalidRefInKeyPath::create(ConstraintSystem &cs, RefKind kind, return new (cs.getAllocator()) AllowInvalidRefInKeyPath(cs, kind, member, locator); } + +KeyPathContextualMismatch * +KeyPathContextualMismatch::create(ConstraintSystem &cs, Type lhs, Type rhs, + ConstraintLocator *locator) { + return new (cs.getAllocator()) + KeyPathContextualMismatch(cs, lhs, rhs, locator); +} diff --git a/lib/Sema/CSFix.h b/lib/Sema/CSFix.h index c5fce558381eb..e5a564ffd7307 100644 --- a/lib/Sema/CSFix.h +++ b/lib/Sema/CSFix.h @@ -458,6 +458,31 @@ class ContextualMismatch : public ConstraintFix { ConstraintLocator *locator); }; +/// Detect situations where key path doesn't have capability required +/// by the context e.g. read-only vs. writable, or either root or value +/// types are incorrect e.g. +/// +/// ```swift +/// struct S { let foo: Int } +/// let _: WritableKeyPath = \.foo +/// ``` +/// +/// Here context requires a writable key path but `foo` property is +/// read-only. +class KeyPathContextualMismatch final : public ContextualMismatch { + KeyPathContextualMismatch(ConstraintSystem &cs, Type lhs, Type rhs, + ConstraintLocator *locator) + : ContextualMismatch(cs, lhs, rhs, locator) {} + +public: + std::string getName() const override { + return "fix key path contextual mismatch"; + } + + static KeyPathContextualMismatch * + create(ConstraintSystem &cs, Type lhs, Type rhs, ConstraintLocator *locator); +}; + /// Detect situations when argument of the @autoclosure parameter is itself /// marked as @autoclosure and is not applied. Form a fix which suggests a /// proper way to forward such arguments, e.g.: diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index cbae5dca4d77b..8b6cde294c775 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2066,6 +2066,14 @@ repairFailures(ConstraintSystem &cs, Type lhs, Type rhs, } case ConstraintLocator::ContextualType: { + // If both types are key path, the only differences + // between them are mutability and/or root, value type mismatch. + if (isKnownKeyPathType(lhs) && isKnownKeyPathType(rhs)) { + auto *fix = KeyPathContextualMismatch::create( + cs, lhs, rhs, cs.getConstraintLocator(locator)); + conversionsOrFixes.push_back(fix); + } + if (lhs->is() && !rhs->is() && isa(anchor)) { auto *fix = ContextualMismatch::create(cs, lhs, rhs, @@ -5230,8 +5238,10 @@ ConstraintSystem::simplifyKeyPathConstraint(Type keyPathTy, auto resolvedKPTy = BoundGenericType::get(kpDecl, nullptr, {rootTy, valueTy}); - return matchTypes(resolvedKPTy, keyPathTy, ConstraintKind::Bind, - subflags, locator); + // Let's check whether deduced key path type would match + // expected contextual one. + return matchTypes(resolvedKPTy, keyPathTy, ConstraintKind::Bind, subflags, + locator.withPathElement(ConstraintLocator::ContextualType)); } ConstraintSystem::SolutionKind diff --git a/lib/Sema/ConstraintSystem.cpp b/lib/Sema/ConstraintSystem.cpp index e38be5385e1ba..fd18309d442d9 100644 --- a/lib/Sema/ConstraintSystem.cpp +++ b/lib/Sema/ConstraintSystem.cpp @@ -2709,6 +2709,12 @@ void ConstraintSystem::generateConstraints( } } +bool constraints::isKnownKeyPathType(Type type) { + if (auto *BGT = type->getAs()) + return isKnownKeyPathDecl(type->getASTContext(), BGT->getDecl()); + return false; +} + bool constraints::isKnownKeyPathDecl(ASTContext &ctx, ValueDecl *decl) { return decl == ctx.getKeyPathDecl() || decl == ctx.getWritableKeyPathDecl() || decl == ctx.getReferenceWritableKeyPathDecl() || diff --git a/lib/Sema/ConstraintSystem.h b/lib/Sema/ConstraintSystem.h index 8c65b8e0af97a..c501f3ee9e625 100644 --- a/lib/Sema/ConstraintSystem.h +++ b/lib/Sema/ConstraintSystem.h @@ -4011,6 +4011,10 @@ class DisjunctionChoiceProducer : public BindingProducer { } }; +/// Determine whether given type is a known one +/// for a key path `{Writable, ReferenceWritable}KeyPath`. +bool isKnownKeyPathType(Type type); + /// Determine whether given declaration is one for a key path /// `{Writable, ReferenceWritable}KeyPath`. bool isKnownKeyPathDecl(ASTContext &ctx, ValueDecl *decl); diff --git a/test/Constraints/keypath_swift_5.swift b/test/Constraints/keypath_swift_5.swift index 1bd5fca885bbf..6fe4e855df543 100644 --- a/test/Constraints/keypath_swift_5.swift +++ b/test/Constraints/keypath_swift_5.swift @@ -4,7 +4,7 @@ struct S { let i: Int init() { - let _: WritableKeyPath = \.i // expected-error {{type of expression is ambiguous without more context}} + let _: WritableKeyPath = \.i // expected-error {{cannot convert value of type 'KeyPath' to specified type 'WritableKeyPath'}} S()[keyPath: \.i] = 1 // expected-error@-1 {{cannot assign through subscript: immutable key path}} @@ -12,7 +12,7 @@ struct S { } func test() { - let _: WritableKeyPath = \.i // expected-error {{type of expression is ambiguous without more context}} + let _: WritableKeyPath = \.i // expected-error {{cannot convert value of type 'KeyPath' to specified type 'WritableKeyPath'}} C()[keyPath: \.i] = 1 // expected-error@-1 {{cannot assign through subscript: immutable key path}} diff --git a/test/expr/unary/keypath/keypath.swift b/test/expr/unary/keypath/keypath.swift index 615c542a73d64..2ed73acbbcafe 100644 --- a/test/expr/unary/keypath/keypath.swift +++ b/test/expr/unary/keypath/keypath.swift @@ -125,16 +125,16 @@ func testKeyPath(sub: Sub, optSub: OptSub, let _: PartialKeyPath = \.property let _: KeyPath = \.property let _: WritableKeyPath = \.property - // expected-error@+1{{ambiguous}} (need to improve diagnostic) let _: ReferenceWritableKeyPath = \.property + //expected-error@-1 {{cannot convert value of type 'WritableKeyPath' to specified type 'ReferenceWritableKeyPath'}} // FIXME: shouldn't be ambiguous // expected-error@+1{{ambiguous}} let _: PartialKeyPath = \.[sub] let _: KeyPath = \.[sub] let _: WritableKeyPath = \.[sub] - // expected-error@+1{{ambiguous}} (need to improve diagnostic) let _: ReferenceWritableKeyPath = \.[sub] + // expected-error@-1 {{cannot convert value of type 'WritableKeyPath' to specified type 'ReferenceWritableKeyPath}} let _: PartialKeyPath = \.optProperty? let _: KeyPath = \.optProperty? @@ -158,8 +158,8 @@ func testKeyPath(sub: Sub, optSub: OptSub, let _: PartialKeyPath> = \.value let _: KeyPath, A> = \.value let _: WritableKeyPath, A> = \.value - // expected-error@+1{{ambiguous}} (need to improve diagnostic) let _: ReferenceWritableKeyPath, A> = \.value + // expected-error@-1 {{cannot convert value of type 'WritableKeyPath, A>' to specified type 'ReferenceWritableKeyPath, A>'}} let _: PartialKeyPath> = \C.value let _: KeyPath, A> = \C.value @@ -684,7 +684,8 @@ func testSubtypeKeypathClass(_ keyPath: ReferenceWritableKeyPath) { } func testSubtypeKeypathProtocol(_ keyPath: ReferenceWritableKeyPath) { - testSubtypeKeypathProtocol(\Base.i) // expected-error {{type 'PP' has no member 'i'}} + testSubtypeKeypathProtocol(\Base.i) + // expected-error@-1 {{cannot convert value of type 'ReferenceWritableKeyPath' to specified type 'ReferenceWritableKeyPath'}} } // rdar://problem/32057712