Skip to content

Commit 5126775

Browse files
authored
Merge pull request #30867 from xedin/rdar-59874355
[TypeChecker] Diagnose key paths with contextual type but no leading dot
2 parents c4e7bee + 8f44a0b commit 5126775

File tree

7 files changed

+57
-13
lines changed

7 files changed

+57
-13
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,9 @@ ERROR(expr_swift_keypath_invalid_component,none,
585585
"invalid component of Swift key path", ())
586586
ERROR(expr_swift_keypath_not_starting_with_type,none,
587587
"a Swift key path must begin with a type", ())
588+
ERROR(expr_swift_keypath_not_starting_with_dot,none,
589+
"a Swift key path with contextual root must begin with a leading dot",
590+
())
588591
ERROR(expr_smart_keypath_value_covert_to_contextual_type,none,
589592
"key path value type %0 cannot be converted to contextual type %1",
590593
(Type, Type))

include/swift/AST/Expr.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5035,6 +5035,10 @@ class KeyPathExpr : public Expr {
50355035
// The processed/resolved type, like Foo.Bar in \Foo.Bar.?.baz.
50365036
TypeRepr *RootType = nullptr;
50375037

5038+
/// Determines whether a key path starts with '.' which denotes necessity for
5039+
/// a contextual root type.
5040+
bool HasLeadingDot = false;
5041+
50385042
public:
50395043
/// A single stored component, which will be one of:
50405044
/// - an unresolved DeclNameRef, which has to be type-checked
@@ -5396,10 +5400,11 @@ class KeyPathExpr : public Expr {
53965400
bool isImplicit = false);
53975401

53985402
KeyPathExpr(SourceLoc backslashLoc, Expr *parsedRoot, Expr *parsedPath,
5399-
bool isImplicit = false)
5403+
bool hasLeadingDot, bool isImplicit = false)
54005404
: Expr(ExprKind::KeyPath, isImplicit), StartLoc(backslashLoc),
54015405
EndLoc(parsedPath ? parsedPath->getEndLoc() : parsedRoot->getEndLoc()),
5402-
ParsedRoot(parsedRoot), ParsedPath(parsedPath) {
5406+
ParsedRoot(parsedRoot), ParsedPath(parsedPath),
5407+
HasLeadingDot(hasLeadingDot) {
54035408
assert((parsedRoot || parsedPath) &&
54045409
"keypath must have either root or path");
54055410
Bits.KeyPathExpr.IsObjC = false;
@@ -5463,6 +5468,9 @@ class KeyPathExpr : public Expr {
54635468
/// True if this is an ObjC key path expression.
54645469
bool isObjC() const { return Bits.KeyPathExpr.IsObjC; }
54655470

5471+
/// True if this key path expression has a leading dot.
5472+
bool expectsContextualRoot() const { return HasLeadingDot; }
5473+
54665474
static bool classof(const Expr *E) {
54675475
return E->getKind() == ExprKind::KeyPath;
54685476
}

lib/Parse/ParseExpr.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,8 @@ ParserResult<Expr> Parser::parseExprKeyPath() {
570570
return rootResult;
571571
}
572572

573-
if (startsWithSymbol(Tok, '.')) {
573+
bool hasLeadingDot = startsWithSymbol(Tok, '.');
574+
if (hasLeadingDot) {
574575
SyntaxParsingContext ExprContext(SyntaxContext, SyntaxContextKind::Expr);
575576

576577
auto dotLoc = Tok.getLoc();
@@ -600,8 +601,9 @@ ParserResult<Expr> Parser::parseExprKeyPath() {
600601
if (rootResult.isNull() && pathResult.isNull())
601602
return nullptr;
602603

603-
auto keypath = new (Context) KeyPathExpr(
604-
backslashLoc, rootResult.getPtrOrNull(), pathResult.getPtrOrNull());
604+
auto keypath =
605+
new (Context) KeyPathExpr(backslashLoc, rootResult.getPtrOrNull(),
606+
pathResult.getPtrOrNull(), hasLeadingDot);
605607

606608
// Handle code completion.
607609
if ((Tok.is(tok::code_complete) && !Tok.isAtStartOfLine()) ||

lib/Sema/CSApply.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,7 @@ namespace {
19641964
auto *keyPath = new (ctx) KeyPathExpr(/*backslashLoc=*/dotLoc,
19651965
/*parsedRoot=*/nullptr,
19661966
/*parsedPath=*/anchor,
1967+
/*hasLeadingDot=*/false,
19671968
/*isImplicit=*/true);
19681969
// Type of the keypath expression we are forming is known
19691970
// in advance, so let's set it right away.

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,16 +1774,31 @@ void PreCheckExpression::resolveKeyPathExpr(KeyPathExpr *KPE) {
17741774
auto traversePath = [&](Expr *expr, bool isInParsedPath,
17751775
bool emitErrors = true) {
17761776
Expr *outermostExpr = expr;
1777+
// We can end up in scenarios where the key path has contextual type,
1778+
// but is missing a leading dot. This can happen when we have an
1779+
// implicit TypeExpr or an implicit DeclRefExpr.
1780+
auto diagnoseMissingDot = [&]() {
1781+
DE.diagnose(expr->getLoc(),
1782+
diag::expr_swift_keypath_not_starting_with_dot)
1783+
.fixItInsert(expr->getStartLoc(), ".");
1784+
};
17771785
while (1) {
17781786
// Base cases: we've reached the top.
17791787
if (auto TE = dyn_cast<TypeExpr>(expr)) {
17801788
assert(!isInParsedPath);
17811789
rootType = TE->getTypeRepr();
1790+
if (TE->isImplicit() && !KPE->expectsContextualRoot())
1791+
diagnoseMissingDot();
17821792
return;
17831793
} else if (isa<KeyPathDotExpr>(expr)) {
17841794
assert(isInParsedPath);
17851795
// Nothing here: the type is either the root, or is inferred.
17861796
return;
1797+
} else if (!KPE->expectsContextualRoot() && expr->isImplicit() &&
1798+
isa<DeclRefExpr>(expr)) {
1799+
assert(!isInParsedPath);
1800+
diagnoseMissingDot();
1801+
return;
17871802
}
17881803

17891804
// Recurring cases:

lib/Sema/TypeCheckStorage.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -817,19 +817,15 @@ static Expr *buildStorageReference(AccessorDecl *accessor,
817817
propertyKeyPath = UnresolvedDotExpr::createImplicit(ctx, propertyKeyPath,
818818
enclosingSelfAccess->accessedProperty->getFullName());
819819
propertyKeyPath = new (ctx) KeyPathExpr(
820-
SourceLoc(), nullptr, propertyKeyPath);
820+
SourceLoc(), nullptr, propertyKeyPath, /*hasLeadingDot=*/true);
821821

822822
// Key path referring to the backing storage property.
823823
Expr *storageKeyPath = new (ctx) KeyPathDotExpr(SourceLoc());
824824
storageKeyPath = UnresolvedDotExpr::createImplicit(ctx, storageKeyPath,
825825
storage->getFullName());
826-
storageKeyPath = new (ctx) KeyPathExpr(
827-
SourceLoc(), nullptr, storageKeyPath);
828-
Expr *args[3] = {
829-
selfDRE,
830-
propertyKeyPath,
831-
storageKeyPath
832-
};
826+
storageKeyPath = new (ctx) KeyPathExpr(SourceLoc(), nullptr, storageKeyPath,
827+
/*hasLeadingDot=*/true);
828+
Expr *args[3] = {selfDRE, propertyKeyPath, storageKeyPath};
833829

834830
SubscriptDecl *subscriptDecl = enclosingSelfAccess->subscript;
835831
lookupExpr = SubscriptExpr::create(

test/expr/unary/keypath/keypath.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,25 @@ func sr11562() {
874874
// expected-error@-1 {{subscript index of type '(Int, Int)' in a key path must be Hashable}}
875875
}
876876

877+
// SR-12290: Ban keypaths with contextual root and without a leading dot
878+
struct SR_12290 {
879+
let property: [Int] = []
880+
let kp1: KeyPath<SR_12290, Int> = \property.count // expected-error {{a Swift key path with contextual root must begin with a leading dot}}{{38-38=.}}
881+
let kp2: KeyPath<SR_12290, Int> = \.property.count // Ok
882+
let kp3: KeyPath<SR_12290, Int> = \SR_12290.property.count // Ok
883+
884+
func foo1(_: KeyPath<SR_12290, Int> = \property.count) {} // expected-error {{a Swift key path with contextual root must begin with a leading dot}}{{42-42=.}}
885+
func foo2(_: KeyPath<SR_12290, Int> = \.property.count) {} // Ok
886+
func foo3(_: KeyPath<SR_12290, Int> = \SR_12290.property.count) {} // Ok
887+
888+
func foo4<T>(_: KeyPath<SR_12290, T>) {}
889+
func useFoo4() {
890+
foo4(\property.count) // expected-error {{a Swift key path with contextual root must begin with a leading dot}}{{11-11=.}}
891+
foo4(\.property.count) // Ok
892+
foo4(\SR_12290.property.count) // Ok
893+
}
894+
}
895+
877896
func testSyntaxErrors() { // expected-note{{}}
878897
_ = \. ; // expected-error{{expected member name following '.'}}
879898
_ = \.a ;

0 commit comments

Comments
 (0)