Skip to content

Commit fdcff3a

Browse files
authored
Merge pull request #86134 from xedin/rdar-157249275
[CSDiagnostics] Produce a tailored diagnostic for key path mutability mismatches
2 parents a1d585d + b1ccdc3 commit fdcff3a

File tree

5 files changed

+87
-13
lines changed

5 files changed

+87
-13
lines changed

lib/Sema/CSDiagnostics.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "CSDiagnostics.h"
1818
#include "MiscDiagnostics.h"
19+
#include "TypeCheckAvailability.h"
1920
#include "TypeCheckConcurrency.h"
2021
#include "TypeCheckProtocol.h"
2122
#include "TypeCheckType.h"
@@ -47,6 +48,7 @@
4748
#include "llvm/ADT/ArrayRef.h"
4849
#include "llvm/ADT/PointerUnion.h"
4950
#include "llvm/ADT/SmallString.h"
51+
#include "llvm/Support/ErrorHandling.h"
5052
#include <string>
5153

5254
using namespace swift;
@@ -2628,6 +2630,9 @@ bool ContextualFailure::diagnoseAsError() {
26282630
}
26292631
}
26302632

2633+
if (diagnoseKeyPathLiteralMutabilityMismatch())
2634+
return true;
2635+
26312636
if (diagnoseConversionToNil())
26322637
return true;
26332638

@@ -2994,6 +2999,48 @@ getContextualNilDiagnostic(ContextualTypePurpose CTP) {
29942999
llvm_unreachable("Unhandled ContextualTypePurpose in switch");
29953000
}
29963001

3002+
bool ContextualFailure::diagnoseKeyPathLiteralMutabilityMismatch() const {
3003+
auto fromType = getFromType();
3004+
auto toType = getToType();
3005+
3006+
if (!(fromType->isKeyPath() &&
3007+
(toType->isWritableKeyPath() || toType->isReferenceWritableKeyPath())))
3008+
return false;
3009+
3010+
auto keyPathLiteral = getAsExpr<KeyPathExpr>(getAnchor());
3011+
if (!keyPathLiteral)
3012+
return false;
3013+
3014+
auto &S = getSolution();
3015+
for (unsigned i : indices(keyPathLiteral->getComponents())) {
3016+
auto &component = keyPathLiteral->getComponents()[i];
3017+
3018+
auto *componentLoc = getConstraintLocator(
3019+
keyPathLiteral, LocatorPathElt::KeyPathComponent(i));
3020+
3021+
auto overload = S.getCalleeOverloadChoiceIfAvailable(componentLoc);
3022+
if (!overload)
3023+
continue;
3024+
3025+
auto *storageDecl =
3026+
dyn_cast_or_null<AbstractStorageDecl>(overload->choice.getDeclOrNull());
3027+
if (!storageDecl)
3028+
continue;
3029+
3030+
if (auto *setter = storageDecl->getOpaqueAccessor(AccessorKind::Set)) {
3031+
if (getUnsatisfiedAvailabilityConstraint(setter, S.getDC(),
3032+
component.getLoc())) {
3033+
auto where =
3034+
ExportContext::forFunctionBody(S.getDC(), component.getLoc());
3035+
return diagnoseDeclAvailability(setter, component.getLoc(),
3036+
/*call=*/nullptr, where);
3037+
}
3038+
}
3039+
}
3040+
3041+
return false;
3042+
}
3043+
29973044
bool ContextualFailure::diagnoseConversionToNil() const {
29983045
auto anchor = getAnchor();
29993046

@@ -7462,6 +7509,9 @@ bool ArgumentMismatchFailure::diagnoseAsError() {
74627509
return false;
74637510
}
74647511

7512+
if (diagnoseKeyPathLiteralMutabilityMismatch())
7513+
return true;
7514+
74657515
if (diagnoseMisplacedMissingArgument())
74667516
return true;
74677517

lib/Sema/CSDiagnostics.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,12 @@ class ContextualFailure : public FailureDiagnostic {
690690

691691
bool diagnoseAsNote() override;
692692

693+
/// If the type of a key path literal is read-only due to setter
694+
/// availability constraints but the context requires a writable
695+
/// key path, let's produce a tailed availability diagnostic that
696+
/// points to the offending setter.
697+
bool diagnoseKeyPathLiteralMutabilityMismatch() const;
698+
693699
/// If we're trying to convert something to `nil`.
694700
bool diagnoseConversionToNil() const;
695701

lib/Sema/CSSimplify.cpp

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14920,7 +14920,31 @@ ConstraintSystem::simplifyRestrictedConstraintImpl(
1492014920
}
1492114921

1492214922
assert(fix);
14923-
return !recordFix(fix, impact);
14923+
14924+
if (recordFix(fix, impact))
14925+
return false;
14926+
14927+
// If we are trying to check whether "from" key path is a subclass of
14928+
// of a "to" key path, the problem here is either mutablity or erasure
14929+
// and we should still try to match their root and value types top help
14930+
// inference.
14931+
if (restriction == ConversionRestrictionKind::Superclass) {
14932+
if (fromType->isKnownKeyPathType() && toType->isKnownKeyPathType() &&
14933+
!fromType->isAnyKeyPath()) {
14934+
auto fromKeyPath = fromType->castTo<BoundGenericType>();
14935+
auto toKeyPath = toType->castTo<BoundGenericType>();
14936+
14937+
auto flags = subflags;
14938+
flags |= TMF_ApplyingFix;
14939+
flags |= TMF_MatchingGenericArguments;
14940+
14941+
(void)matchDeepTypeArguments(*this, flags,
14942+
fromKeyPath->getGenericArgs(),
14943+
toKeyPath->getGenericArgs(), locator);
14944+
}
14945+
}
14946+
14947+
return true;
1492414948
}
1492514949

1492614950
return false;

test/Availability/availability_accessors.swift

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ struct BaseStruct<T> {
6363
var unavailableSetter: T {
6464
get { fatalError() }
6565
@available(*, unavailable)
66-
set { fatalError() } // expected-note 33 {{setter for 'unavailableSetter' has been explicitly marked unavailable here}}
66+
set { fatalError() } // expected-note 34 {{setter for 'unavailableSetter' has been explicitly marked unavailable here}}
6767
}
6868

6969
var unavailableGetterAndSetter: T {
7070
@available(*, unavailable)
7171
get { fatalError() } // expected-note 71 {{getter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
7272
@available(*, unavailable)
73-
set { fatalError() } // expected-note 33 {{setter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
73+
set { fatalError() } // expected-note 34 {{setter for 'unavailableGetterAndSetter' has been explicitly marked unavailable here}}
7474
}
7575

7676
var deprecatedGetter: T {
@@ -579,17 +579,11 @@ func testKeyPathArguments_Struct() {
579579
takesSendableKeyPath(x, \.deprecatedSetter)
580580

581581
func takesWritableKeyPath<T, U>(_ t: inout T, _ keyPath: WritableKeyPath<T, U>) -> () { }
582-
// expected-note@-1 2 {{in call to function 'takesWritableKeyPath'}}
583582

584583
takesWritableKeyPath(&x, \.available)
585584
takesWritableKeyPath(&x, \.unavailableGetter) // expected-error {{getter for 'unavailableGetter' is unavailable}}
586-
// FIXME: Ideally we would diagnose the unavailability of the setter instead
587-
// of simply indicating that a conversion to WritableKeyPath is not possible
588-
// (rdar://157249275)
589-
takesWritableKeyPath(&x, \.unavailableSetter) // expected-error {{cannot convert value of type 'KeyPath<BaseStruct<StructValue>, StructValue>' to expected argument type 'WritableKeyPath<BaseStruct<StructValue>, U>'}}
590-
// expected-error@-1 {{generic parameter 'U' could not be inferred}}
591-
takesWritableKeyPath(&x, \.unavailableGetterAndSetter) // expected-error {{cannot convert value of type 'KeyPath<BaseStruct<StructValue>, StructValue>' to expected argument type 'WritableKeyPath<BaseStruct<StructValue>, U>'}}
592-
// expected-error@-1 {{generic parameter 'U' could not be inferred}}
585+
takesWritableKeyPath(&x, \.unavailableSetter) // expected-error {{setter for 'unavailableSetter' is unavailable}}
586+
takesWritableKeyPath(&x, \.unavailableGetterAndSetter) // expected-error {{setter for 'unavailableGetterAndSetter' is unavailable}}
593587
takesWritableKeyPath(&x, \.deprecatedGetter) // expected-warning {{getter for 'deprecatedGetter' is deprecated: reading not recommended}}
594588
takesWritableKeyPath(&x, \.deprecatedSetter) // expected-warning {{setter for 'deprecatedSetter' is deprecated: writing not recommended}}
595589

test/stdlib/KeyPathAppending.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func mismatchedAppends<T, U, V>(readOnlyLeft: KeyPath<T, U>,
6161
// expected-error@-1 {{no exact matches in call to instance method 'appending'}}
6262

6363
_ = writableRight.appending(path: readOnlyLeft)
64-
// expected-error@-1 {{instance method 'appending(path:)' requires that 'KeyPath<U, V>' inherit from 'KeyPath<U, T>'}}
64+
// expected-error@-1 {{no exact matches in call to instance method 'appending'}}
6565

6666
_ = writableRight.appending(path: writableLeft)
6767
// expected-error@-1 {{cannot convert value of type 'WritableKeyPath<T, U>' to expected argument type 'WritableKeyPath<V, U>'}}
@@ -71,7 +71,7 @@ func mismatchedAppends<T, U, V>(readOnlyLeft: KeyPath<T, U>,
7171
// expected-error@-1 {{no exact matches in call to instance method 'appending'}}
7272

7373
_ = referenceRight.appending(path: readOnlyLeft)
74-
// expected-error@-1 {{instance method 'appending(path:)' requires that 'KeyPath<U, V>' inherit from 'KeyPath<U, T>'}}
74+
// expected-error@-1 {{no exact matches in call to instance method 'appending'}}
7575

7676
_ = referenceRight.appending(path: writableLeft)
7777
// expected-error@-1 {{cannot convert value of type 'WritableKeyPath<T, U>' to expected argument type 'WritableKeyPath<V, U>'}}

0 commit comments

Comments
 (0)