Skip to content

Commit 75d0f78

Browse files
authored
Merge pull request #62069 from grynspan/jgrynspan/kvo-keypath-semantics
Augment maybeDiagnoseCallToKeyValueObserveMethod() and add support for a @_semantics attribute to trigger it rather than just hard-coding the name of the Foundation method.
2 parents 70072a0 + f3bcef9 commit 75d0f78

File tree

3 files changed

+39
-20
lines changed

3 files changed

+39
-20
lines changed

include/swift/AST/SemanticAttrs.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ SEMANTICS_ATTR(PROGRAMTERMINATION_POINT, "programtermination_point")
9797
SEMANTICS_ATTR(CONVERT_TO_OBJECTIVE_C, "convertToObjectiveC")
9898

9999
SEMANTICS_ATTR(KEYPATH_KVC_KEY_PATH_STRING, "keypath.kvcKeyPathString")
100+
SEMANTICS_ATTR(KEYPATH_MUST_BE_VALID_FOR_KVO, "keypath.mustBeValidForKVO")
100101

101102
/// The prefix used to force opt-remarks to be emitted in a specific function.
102103
///

lib/Sema/MiscDiagnostics.cpp

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/NameLookup.h"
2626
#include "swift/AST/NameLookupRequests.h"
2727
#include "swift/AST/Pattern.h"
28+
#include "swift/AST/SemanticAttrs.h"
2829
#include "swift/AST/SourceFile.h"
2930
#include "swift/AST/Stmt.h"
3031
#include "swift/AST/TypeCheckRequests.h"
@@ -5053,31 +5054,40 @@ static void maybeDiagnoseCallToKeyValueObserveMethod(const Expr *E,
50535054
auto fn = expr->getCalledValue();
50545055
if (!fn)
50555056
return;
5056-
if (fn->getModuleContext()->getName() != C.Id_Foundation)
5057-
return;
5058-
if (!fn->getName().isCompoundName("observe",
5059-
{"", "options", "changeHandler"}))
5060-
return;
5057+
SmallVector<KeyPathExpr *, 1> keyPathArgs;
50615058
auto *args = expr->getArgs();
5062-
auto firstArg = dyn_cast<KeyPathExpr>(args->getExpr(0));
5063-
if (!firstArg)
5064-
return;
5065-
auto lastComponent = firstArg->getComponents().back();
5066-
if (lastComponent.getKind() != KeyPathExpr::Component::Kind::Property)
5067-
return;
5068-
auto property = lastComponent.getDeclRef().getDecl();
5069-
if (!property)
5070-
return;
5071-
auto propertyVar = cast<VarDecl>(property);
5072-
if (propertyVar->shouldUseObjCDispatch() ||
5073-
(propertyVar->isObjC() &&
5074-
propertyVar->getParsedAccessor(AccessorKind::Set)))
5075-
return;
5076-
C.Diags
5059+
if (fn->getModuleContext()->getName() == C.Id_Foundation &&
5060+
fn->getName().isCompoundName("observe",
5061+
{"", "options", "changeHandler"})) {
5062+
if (auto keyPathArg = dyn_cast<KeyPathExpr>(args->getExpr(0))) {
5063+
keyPathArgs.push_back(keyPathArg);
5064+
}
5065+
} else if (fn->getAttrs()
5066+
.hasSemanticsAttr(semantics::KEYPATH_MUST_BE_VALID_FOR_KVO)) {
5067+
for (const auto& arg: *args) {
5068+
if (auto keyPathArg = dyn_cast<KeyPathExpr>(arg.getExpr())) {
5069+
keyPathArgs.push_back(keyPathArg);
5070+
}
5071+
}
5072+
}
5073+
for (auto *keyPathArg : keyPathArgs) {
5074+
auto lastComponent = keyPathArg->getComponents().back();
5075+
if (lastComponent.getKind() != KeyPathExpr::Component::Kind::Property)
5076+
continue;
5077+
auto property = lastComponent.getDeclRef().getDecl();
5078+
if (!property)
5079+
continue;
5080+
auto propertyVar = cast<VarDecl>(property);
5081+
if (propertyVar->shouldUseObjCDispatch() ||
5082+
(propertyVar->isObjC() &&
5083+
propertyVar->getParsedAccessor(AccessorKind::Set)))
5084+
continue;
5085+
C.Diags
50775086
.diagnose(expr->getLoc(),
50785087
diag::observe_keypath_property_not_objc_dynamic,
50795088
property->getName(), fn->getName())
50805089
.highlight(lastComponent.getLoc());
5090+
}
50815091
}
50825092

50835093
PreWalkResult<Expr *> walkToExprPre(Expr *E) override {

test/expr/primary/keypath/keypath-observe-objc.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,11 @@ class Bar: NSObject {
4545
}
4646
}
4747
}
48+
49+
@_semantics("keypath.mustBeValidForKVO")
50+
func quux<T, V, U>(_ object: T, at keyPath: KeyPath<T, V>, _ keyPath2: KeyPath<T, U>) { }
51+
52+
// The presence of a valid keypath should not prevent detection of invalid ones
53+
// later in the argument list, so start with a valid one here.
54+
quux(Foo(), at: \.number4, \.number1)
55+
// expected-warning@-1 {{passing reference to non-'@objc dynamic' property 'number1' to KVO method 'quux(_:at:_:)' may lead to unexpected behavior or runtime trap}}

0 commit comments

Comments
 (0)