Skip to content

Commit afc9e20

Browse files
authored
Merge pull request #24208 from xedin/diagnose-method-ref-in-keypath
[ConstraintSystem] Detect and fix use of method references in key path
2 parents 015e8f7 + 6fdb548 commit afc9e20

File tree

7 files changed

+148
-51
lines changed

7 files changed

+148
-51
lines changed

lib/Sema/CSApply.cpp

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4573,20 +4573,13 @@ namespace {
45734573
ConstraintLocator *locator) {
45744574
if (overload.choice.isDecl()) {
45754575
auto property = overload.choice.getDecl();
4576-
45774576
// Key paths can only refer to properties currently.
4578-
if (!isa<VarDecl>(property)) {
4579-
cs.TC.diagnose(componentLoc, diag::expr_keypath_not_property,
4580-
property->getDescriptiveKind(),
4581-
property->getFullName());
4582-
} else {
4583-
// Key paths don't work with mutating-get properties.
4584-
auto varDecl = cast<VarDecl>(property);
4585-
assert(!varDecl->isGetterMutating());
4586-
// Key paths don't currently support static members.
4587-
// There is a fix which diagnoses such situation already.
4588-
assert(!varDecl->isStatic());
4589-
}
4577+
auto varDecl = cast<VarDecl>(property);
4578+
// Key paths don't work with mutating-get properties.
4579+
assert(!varDecl->isGetterMutating());
4580+
// Key paths don't currently support static members.
4581+
// There is a fix which diagnoses such situation already.
4582+
assert(!varDecl->isStatic());
45904583

45914584
cs.TC.requestMemberLayout(property);
45924585

lib/Sema/CSDiagnostics.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1516,6 +1516,13 @@ bool MissingCallFailure::diagnoseAsError() {
15161516
if (auto *FVE = dyn_cast<ForceValueExpr>(baseExpr))
15171517
baseExpr = FVE->getSubExpr();
15181518

1519+
// Calls are not yet supported by key path, but it
1520+
// is useful to record this fix to diagnose chaining
1521+
// where one of the key path components is a method
1522+
// reference.
1523+
if (isa<KeyPathExpr>(baseExpr))
1524+
return false;
1525+
15191526
if (auto *DRE = dyn_cast<DeclRefExpr>(baseExpr)) {
15201527
emitDiagnostic(baseExpr->getLoc(), diag::did_not_call_function,
15211528
DRE->getDecl()->getBaseName().getIdentifier())
@@ -2479,3 +2486,9 @@ bool InvalidMemberWithMutatingGetterInKeyPath::diagnoseAsError() {
24792486
emitDiagnostic(getLoc(), diag::expr_keypath_mutating_getter, getName());
24802487
return true;
24812488
}
2489+
2490+
bool InvalidMethodRefInKeyPath::diagnoseAsError() {
2491+
emitDiagnostic(getLoc(), diag::expr_keypath_not_property, getKind(),
2492+
getName());
2493+
return true;
2494+
}

lib/Sema/CSDiagnostics.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,8 @@ class InvalidMemberRefInKeyPath : public FailureDiagnostic {
10421042
assert(locator->isForKeyPathComponent());
10431043
}
10441044

1045+
DescriptiveDeclKind getKind() const { return Member->getDescriptiveKind(); }
1046+
10451047
DeclName getName() const { return Member->getFullName(); }
10461048

10471049
bool diagnoseAsError() override = 0;
@@ -1098,6 +1100,29 @@ class InvalidMemberWithMutatingGetterInKeyPath final
10981100
bool diagnoseAsError() override;
10991101
};
11001102

1103+
/// Diagnose an attempt to reference a method as a key path component
1104+
/// e.g.
1105+
///
1106+
/// ```swift
1107+
/// struct S {
1108+
/// func foo() -> Int { return 42 }
1109+
/// static func bar() -> Int { return 0 }
1110+
/// }
1111+
///
1112+
/// _ = \S.foo
1113+
/// _ = \S.Type.bar
1114+
/// ```
1115+
class InvalidMethodRefInKeyPath final : public InvalidMemberRefInKeyPath {
1116+
public:
1117+
InvalidMethodRefInKeyPath(Expr *root, ConstraintSystem &cs, ValueDecl *method,
1118+
ConstraintLocator *locator)
1119+
: InvalidMemberRefInKeyPath(root, cs, method, locator) {
1120+
assert(isa<FuncDecl>(method));
1121+
}
1122+
1123+
bool diagnoseAsError() override;
1124+
};
1125+
11011126
} // end namespace constraints
11021127
} // end namespace swift
11031128

lib/Sema/CSFix.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,38 @@ bool AllowInvalidRefInKeyPath::diagnose(Expr *root, bool asNote) const {
436436
root, getConstraintSystem(), Member, getLocator());
437437
return failure.diagnose(asNote);
438438
}
439+
440+
case RefKind::Method: {
441+
InvalidMethodRefInKeyPath failure(root, getConstraintSystem(), Member,
442+
getLocator());
443+
return failure.diagnose(asNote);
444+
}
445+
}
446+
}
447+
448+
AllowInvalidRefInKeyPath *
449+
AllowInvalidRefInKeyPath::forRef(ConstraintSystem &cs, ValueDecl *member,
450+
ConstraintLocator *locator) {
451+
// Referencing (instance or static) methods in key path is
452+
// not currently allowed.
453+
if (isa<FuncDecl>(member))
454+
return AllowInvalidRefInKeyPath::create(cs, RefKind::Method, member,
455+
locator);
456+
457+
// Referencing static members in key path is not currently allowed.
458+
if (member->isStatic())
459+
return AllowInvalidRefInKeyPath::create(cs, RefKind::StaticMember, member,
460+
locator);
461+
462+
if (auto *storage = dyn_cast<AbstractStorageDecl>(member)) {
463+
// Referencing members with mutating getters in key path is not
464+
// currently allowed.
465+
if (storage->isGetterMutating())
466+
return AllowInvalidRefInKeyPath::create(cs, RefKind::MutatingGetter,
467+
member, locator);
439468
}
469+
470+
return nullptr;
440471
}
441472

442473
AllowInvalidRefInKeyPath *

lib/Sema/CSFix.h

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,9 @@ class AllowInvalidRefInKeyPath final : public ConstraintFix {
791791
// Allow a reference to a declaration with mutating getter as
792792
// a key path component.
793793
MutatingGetter,
794+
// Allow a reference to a method (instance or static) as
795+
// a key path component.
796+
Method,
794797
} Kind;
795798

796799
ValueDecl *Member;
@@ -808,22 +811,16 @@ class AllowInvalidRefInKeyPath final : public ConstraintFix {
808811
case RefKind::MutatingGetter:
809812
return "allow reference to a member with mutating getter as a key "
810813
"path component";
814+
case RefKind::Method:
815+
return "allow reference to a method as a key path component";
811816
}
812817
}
813818

814819
bool diagnose(Expr *root, bool asNote = false) const override;
815820

816-
static AllowInvalidRefInKeyPath *forStaticMember(ConstraintSystem &cs,
817-
ValueDecl *member,
818-
ConstraintLocator *locator) {
819-
return create(cs, RefKind::StaticMember, member, locator);
820-
}
821-
821+
/// Determine whether give reference requires a fix and produce one.
822822
static AllowInvalidRefInKeyPath *
823-
forMutatingGetter(ConstraintSystem &cs, ValueDecl *member,
824-
ConstraintLocator *locator) {
825-
return create(cs, RefKind::MutatingGetter, member, locator);
826-
}
823+
forRef(ConstraintSystem &cs, ValueDecl *member, ConstraintLocator *locator);
827824

828825
private:
829826
static AllowInvalidRefInKeyPath *create(ConstraintSystem &cs, RefKind kind,

lib/Sema/CSSimplify.cpp

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,15 +1975,29 @@ static ConstraintFix *fixRequirementFailure(ConstraintSystem &cs, Type type1,
19751975
}
19761976
}
19771977

1978-
static void
1978+
/// Attempt to repair typing failures and record fixes if needed.
1979+
/// \return true if at least some of the failures has been repaired
1980+
/// successfully, which allows type matcher to continue.
1981+
static bool
19791982
repairFailures(ConstraintSystem &cs, Type lhs, Type rhs,
19801983
SmallVectorImpl<RestrictionOrFix> &conversionsOrFixes,
19811984
ConstraintLocatorBuilder locator) {
19821985
SmallVector<LocatorPathElt, 4> path;
19831986
auto *anchor = locator.getLocatorParts(path);
19841987

1985-
if (path.empty())
1986-
return;
1988+
if (path.empty()) {
1989+
// If method reference forms a value type of the key path,
1990+
// there is going to be a constraint to match result of the
1991+
// member lookup to the generic parameter `V` of *KeyPath<R, V>
1992+
// type associated with key path expression, which we need to
1993+
// fix-up here.
1994+
if (anchor && isa<KeyPathExpr>(anchor)) {
1995+
auto *fnType = lhs->getAs<FunctionType>();
1996+
if (fnType && fnType->getResult()->isEqual(rhs))
1997+
return true;
1998+
}
1999+
return false;
2000+
}
19872001

19882002
auto &elt = path.back();
19892003
switch (elt.getKind()) {
@@ -2007,7 +2021,7 @@ repairFailures(ConstraintSystem &cs, Type lhs, Type rhs,
20072021
if (path.empty() ||
20082022
!(path.back().getKind() == ConstraintLocator::ApplyArgToParam ||
20092023
path.back().getKind() == ConstraintLocator::ContextualType))
2010-
return;
2024+
return false;
20112025

20122026
auto arg = llvm::find_if(cs.getTypeVariables(),
20132027
[&argLoc](const TypeVariableType *typeVar) {
@@ -2058,13 +2072,14 @@ repairFailures(ConstraintSystem &cs, Type lhs, Type rhs,
20582072
cs.getConstraintLocator(locator));
20592073
conversionsOrFixes.push_back(fix);
20602074
}
2061-
20622075
break;
20632076
}
20642077

20652078
default:
2066-
return;
2079+
break;
20672080
}
2081+
2082+
return !conversionsOrFixes.empty();
20682083
}
20692084

20702085
ConstraintSystem::TypeMatchResult
@@ -2873,8 +2888,12 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
28732888
}
28742889

28752890
// Attempt to repair any failures identifiable at this point.
2876-
if (attemptFixes)
2877-
repairFailures(*this, type1, type2, conversionsOrFixes, locator);
2891+
if (attemptFixes) {
2892+
if (repairFailures(*this, type1, type2, conversionsOrFixes, locator)) {
2893+
if (conversionsOrFixes.empty())
2894+
return getTypeMatchSuccess();
2895+
}
2896+
}
28782897

28792898
if (conversionsOrFixes.empty())
28802899
return getTypeMatchFailure(locator);
@@ -4368,7 +4387,7 @@ fixMemberRef(ConstraintSystem &cs, Type baseTy,
43684387
break;
43694388
case MemberLookupResult::UR_KeyPathWithAnyObjectRootType:
43704389
return AllowAnyObjectKeyPathRoot::create(cs, locator);
4371-
}
4390+
}
43724391
}
43734392

43744393
return nullptr;
@@ -5124,31 +5143,25 @@ ConstraintSystem::simplifyKeyPathConstraint(Type keyPathTy,
51245143
}
51255144

51265145
auto storage = dyn_cast<AbstractStorageDecl>(choices[i].getDecl());
5127-
if (!storage) {
5128-
return SolutionKind::Error;
5129-
}
51305146

5131-
// Referencing static members in key path is not currently allowed.
5132-
if (storage->isStatic() || storage->isGetterMutating()) {
5133-
if (!shouldAttemptFixes())
5134-
return SolutionKind::Error;
5147+
auto *componentLoc = getConstraintLocator(
5148+
locator.withPathElement(LocatorPathElt::getKeyPathComponent(i)));
51355149

5136-
auto *componentLoc = getConstraintLocator(
5137-
locator.withPathElement(LocatorPathElt::getKeyPathComponent(i)));
5150+
if (auto *fix = AllowInvalidRefInKeyPath::forRef(
5151+
*this, choices[i].getDecl(), componentLoc)) {
5152+
if (!shouldAttemptFixes() || recordFix(fix))
5153+
return SolutionKind::Error;
51385154

5139-
ConstraintFix *fix = nullptr;
5140-
if (storage->isStatic()) {
5141-
fix = AllowInvalidRefInKeyPath::forStaticMember(
5142-
*this, choices[i].getDecl(), componentLoc);
5143-
} else {
5144-
fix = AllowInvalidRefInKeyPath::forMutatingGetter(
5145-
*this, choices[i].getDecl(), componentLoc);
5155+
// If this was a method reference let's mark it as read-only.
5156+
if (!storage) {
5157+
capability = ReadOnly;
5158+
continue;
51465159
}
5147-
5148-
if (recordFix(fix))
5149-
return SolutionKind::Error;
51505160
}
51515161

5162+
if (!storage)
5163+
return SolutionKind::Error;
5164+
51525165
if (isReadOnlyKeyPathComponent(storage)) {
51535166
capability = ReadOnly;
51545167
continue;

test/expr/unary/keypath/keypath.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,31 @@ func test_keypath_with_mutating_getter() {
776776
}
777777
}
778778

779+
func test_keypath_with_method_refs() {
780+
struct S {
781+
func foo() -> Int { return 42 }
782+
static func bar() -> Int { return 0 }
783+
}
784+
785+
let _: KeyPath<S, Int> = \.foo // expected-error {{key path cannot refer to instance method 'foo()'}}
786+
let _: KeyPath<S, Int> = \.bar // expected-error {{key path cannot refer to static member 'bar()'}}
787+
let _ = \S.Type.bar // expected-error {{key path cannot refer to static method 'bar()'}}
788+
789+
struct A {
790+
func foo() -> B { return B() }
791+
static func faz() -> B { return B() }
792+
}
793+
794+
struct B {
795+
var bar: Int = 42
796+
}
797+
798+
let _: KeyPath<A, Int> = \.foo.bar // expected-error {{key path cannot refer to instance method 'foo()'}}
799+
let _: KeyPath<A, Int> = \.faz.bar // expected-error {{key path cannot refer to static member 'faz()'}}
800+
let _ = \A.foo.bar // expected-error {{key path cannot refer to instance method 'foo()'}}
801+
let _ = \A.Type.faz.bar // expected-error {{key path cannot refer to static method 'faz()'}}
802+
}
803+
779804
func testSyntaxErrors() { // expected-note{{}}
780805
_ = \. ; // expected-error{{expected member name following '.'}}
781806
_ = \.a ;

0 commit comments

Comments
 (0)