Skip to content

Commit 07c5ab8

Browse files
committed
Implement \ syntax for Swift key paths.
This introduces a few unfortunate things because the syntax is awkward. In particular, the period and following token in \.[a], \.? and \.! are token sequences that don't appear anywhere else in Swift, and so need special handling. This is somewhat compounded by \foo.bar.baz possibly being \(foo).bar.baz or \(foo.bar).baz (parens around the type), and, furthermore, needing to distinguish \Foo?.bar from \Foo.?bar. rdar://problem/31724243
1 parent db1643f commit 07c5ab8

19 files changed

+450
-286
lines changed

include/swift/AST/DiagnosticsParse.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,6 +1176,8 @@ ERROR(expr_keypath_expected_property_or_type,PointsToFirstBadToken,
11761176
"expected property or type name within '#keyPath(...)'", ())
11771177
ERROR(expr_keypath_expected_rparen,PointsToFirstBadToken,
11781178
"expected ')' to complete '#keyPath' expression", ())
1179+
ERROR(expr_keypath_expected_expr,none,
1180+
"expected expression path in Swift key path",())
11791181

11801182
// Selector expressions.
11811183
ERROR(expr_selector_expected_lparen,PointsToFirstBadToken,

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ ERROR(expr_unsupported_objc_key_path_compound_name,none,
441441
"compound name", ())
442442
ERROR(expr_keypath_no_keypath_type,none,
443443
"broken standard library: no 'KeyPath' type found", ())
444+
ERROR(expr_swift_keypath_invalid_component,none,
445+
"invalid component of Swift key path", ())
446+
ERROR(expr_swift_keypath_not_starting_with_type,none,
447+
"a Swift key path must begin with a type", ())
444448

445449
// Selector expressions.
446450
ERROR(expr_selector_no_objc_runtime,none,

include/swift/AST/Expr.h

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4558,12 +4558,21 @@ class ObjCSelectorExpr : public Expr {
45584558
/// #keyPath(Person.friends.firstName)
45594559
/// \endcode
45604560
class KeyPathExpr : public Expr {
4561-
SourceLoc KeywordLoc;
4561+
SourceLoc StartLoc;
45624562
SourceLoc LParenLoc;
4563-
SourceLoc RParenLoc;
4564-
TypeRepr *RootType;
4563+
SourceLoc EndLoc;
45654564
Expr *ObjCStringLiteralExpr = nullptr;
45664565

4566+
// The parsed root of a Swift keypath (the section before an unusual dot, like
4567+
// Foo.Bar in \Foo.Bar.?.baz).
4568+
Expr *ParsedRoot = nullptr;
4569+
// The parsed path of a Swift keypath (the section after an unusual dot, like
4570+
// ?.baz in \Foo.Bar.?.baz).
4571+
Expr *ParsedPath = nullptr;
4572+
4573+
// The processed/resolved type, like Foo.Bar in \Foo.Bar.?.baz.
4574+
TypeRepr *RootType = nullptr;
4575+
45674576
public:
45684577
/// A single stored component, which will be one of:
45694578
/// - an unresolved DeclName, which has to be type-checked
@@ -4772,17 +4781,23 @@ class KeyPathExpr : public Expr {
47724781
/// Create a new #keyPath expression.
47734782
KeyPathExpr(ASTContext &C,
47744783
SourceLoc keywordLoc, SourceLoc lParenLoc,
4775-
TypeRepr *root,
47764784
ArrayRef<Component> components,
47774785
SourceLoc rParenLoc,
4778-
bool isObjC,
47794786
bool isImplicit = false);
47804787

4781-
SourceLoc getLoc() const { return KeywordLoc; }
4782-
SourceRange getSourceRange() const {
4783-
return SourceRange(KeywordLoc, RParenLoc);
4788+
KeyPathExpr(SourceLoc backslashLoc, Expr *parsedRoot, Expr *parsedPath,
4789+
bool isImplicit = false)
4790+
: Expr(ExprKind::KeyPath, isImplicit), StartLoc(backslashLoc),
4791+
EndLoc(parsedPath ? parsedPath->getEndLoc() : parsedRoot->getEndLoc()),
4792+
ParsedRoot(parsedRoot), ParsedPath(parsedPath) {
4793+
assert((parsedRoot || parsedPath) &&
4794+
"keypath must have either root or path");
4795+
KeyPathExprBits.IsObjC = false;
47844796
}
47854797

4798+
SourceLoc getLoc() const { return StartLoc; }
4799+
SourceRange getSourceRange() const { return SourceRange(StartLoc, EndLoc); }
4800+
47864801
/// Get the components array.
47874802
ArrayRef<Component> getComponents() const {
47884803
return Components;
@@ -4807,11 +4822,34 @@ class KeyPathExpr : public Expr {
48074822
void setObjCStringLiteralExpr(Expr *expr) {
48084823
ObjCStringLiteralExpr = expr;
48094824
}
4810-
4825+
4826+
Expr *getParsedRoot() const {
4827+
assert(!isObjC() && "cannot get parsed root of ObjC keypath");
4828+
return ParsedRoot;
4829+
}
4830+
void setParsedRoot(Expr *root) {
4831+
assert(!isObjC() && "cannot get parsed root of ObjC keypath");
4832+
ParsedRoot = root;
4833+
}
4834+
4835+
Expr *getParsedPath() const {
4836+
assert(!isObjC() && "cannot get parsed path of ObjC keypath");
4837+
return ParsedPath;
4838+
}
4839+
void setParsedPath(Expr *path) {
4840+
assert(!isObjC() && "cannot set parsed path of ObjC keypath");
4841+
ParsedPath = path;
4842+
}
4843+
48114844
TypeRepr *getRootType() const {
4845+
assert(!isObjC() && "cannot get root type of ObjC keypath");
48124846
return RootType;
48134847
}
4814-
4848+
void setRootType(TypeRepr *rootType) {
4849+
assert(!isObjC() && "cannot set root type of ObjC keypath");
4850+
RootType = rootType;
4851+
}
4852+
48154853
/// True if this is an ObjC key path expression.
48164854
bool isObjC() const { return KeyPathExprBits.IsObjC; }
48174855

@@ -4820,6 +4858,22 @@ class KeyPathExpr : public Expr {
48204858
}
48214859
};
48224860

4861+
/// Represents the unusual behaviour of a . in a \ keypath expression, such as
4862+
/// \.[0] and \Foo.?.
4863+
class KeyPathDotExpr : public Expr {
4864+
SourceLoc DotLoc;
4865+
4866+
public:
4867+
KeyPathDotExpr(SourceLoc dotLoc)
4868+
: Expr(ExprKind::KeyPathDot, /*isImplicit=*/true), DotLoc(dotLoc) {}
4869+
4870+
SourceLoc getLoc() const { return DotLoc; }
4871+
SourceRange getSourceRange() const { return SourceRange(DotLoc, DotLoc); }
4872+
4873+
static bool classof(const Expr *E) {
4874+
return E->getKind() == ExprKind::KeyPathDot;
4875+
}
4876+
};
48234877

48244878
inline bool Expr::isInfixOperator() const {
48254879
return isa<BinaryExpr>(this) || isa<IfExpr>(this) ||

include/swift/AST/ExprNodes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ UNCHECKED_EXPR(UnresolvedPattern, Expr)
168168
EXPR(EditorPlaceholder, Expr)
169169
EXPR(ObjCSelector, Expr)
170170
EXPR(KeyPath, Expr)
171+
UNCHECKED_EXPR(KeyPathDot, Expr)
171172

172173
#undef EXPR_RANGE
173174
#undef LITERAL_EXPR

include/swift/Parse/Parser.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ class Parser {
130130

131131
bool InPoundLineEnvironment = false;
132132
bool InPoundIfEnvironment = false;
133+
bool InSwiftKeyPath = false;
133134

134135
LocalContext *CurLocalContext = nullptr;
135136

@@ -1143,6 +1144,7 @@ class Parser {
11431144
bool isExprBasic);
11441145
ParserResult<Expr> parseExprPostfixSuffix(ParserResult<Expr> inner,
11451146
bool isExprBasic,
1147+
bool periodHasKeyPathBehaviour,
11461148
bool &hasBindOptional);
11471149
ParserResult<Expr> parseExprPostfix(Diag<> ID, bool isExprBasic);
11481150
ParserResult<Expr> parseExprUnary(Diag<> ID, bool isExprBasic);

include/swift/Syntax/TokenKinds.def

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ PUNCTUATOR(arrow, "->")
174174

175175
PUNCTUATOR(backtick, "`")
176176

177+
PUNCTUATOR(backslash, "\\")
178+
177179
PUNCTUATOR(exclaim_postfix, "!") // if left-bound
178180

179181
PUNCTUATOR(question_postfix, "?") // if left-bound
@@ -193,7 +195,6 @@ POUND_KEYWORD(else)
193195
POUND_KEYWORD(elseif)
194196
POUND_KEYWORD(endif)
195197
POUND_KEYWORD(keyPath)
196-
POUND_KEYWORD(keyPath2) // TODO
197198
POUND_KEYWORD(line)
198199
POUND_KEYWORD(sourceLocation)
199200
POUND_KEYWORD(selector)

lib/AST/ASTDumper.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2467,6 +2467,25 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
24672467
OS << '\n';
24682468
printRec(stringLiteral);
24692469
}
2470+
if (!E->isObjC()) {
2471+
OS << "\n";
2472+
if (auto root = E->getParsedRoot()) {
2473+
printRec(root);
2474+
} else {
2475+
OS.indent(Indent + 2) << "<<null>>";
2476+
}
2477+
OS << "\n";
2478+
if (auto path = E->getParsedPath()) {
2479+
printRec(path);
2480+
} else {
2481+
OS.indent(Indent + 2) << "<<null>>";
2482+
}
2483+
}
2484+
OS << ")";
2485+
}
2486+
2487+
void visitKeyPathDotExpr(KeyPathDotExpr *E) {
2488+
printCommon(E, "key_path_dot_expr");
24702489
OS << ")";
24712490
}
24722491
};

lib/AST/ASTWalker.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -915,10 +915,35 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
915915
E->setObjCStringLiteralExpr(sub);
916916
return E;
917917
}
918-
918+
919+
auto components = E->getComponents();
920+
if (components.empty()) {
921+
// No components means a parsed-only/pre-resolution Swift key path.
922+
assert(!E->isObjC());
923+
if (auto parsedRoot = E->getParsedRoot()) {
924+
Expr *newRoot = doIt(parsedRoot);
925+
if (!newRoot)
926+
return nullptr;
927+
E->setParsedRoot(newRoot);
928+
}
929+
if (auto parsedPath = E->getParsedPath()) {
930+
Expr *newPath = doIt(parsedPath);
931+
if (!newPath)
932+
return nullptr;
933+
E->setParsedPath(newPath);
934+
}
935+
return E;
936+
}
937+
938+
if (!E->isObjC()) {
939+
auto rootType = E->getRootType();
940+
if (rootType && doIt(rootType))
941+
return nullptr;
942+
}
943+
919944
SmallVector<KeyPathExpr::Component, 4> updatedComponents;
920945
bool didChangeComponents = false;
921-
for (auto &origComponent : E->getComponents()) {
946+
for (auto &origComponent : components) {
922947
auto component = origComponent;
923948
switch (auto kind = component.getKind()) {
924949
case KeyPathExpr::Component::Kind::Subscript:
@@ -955,10 +980,12 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
955980
E->resolveComponents(E->getType()->getASTContext(),
956981
updatedComponents);
957982
}
958-
983+
959984
return E;
960985
}
961986

987+
Expr *visitKeyPathDotExpr(KeyPathDotExpr *E) { return E; }
988+
962989
//===--------------------------------------------------------------------===//
963990
// Everything Else
964991
//===--------------------------------------------------------------------===//

lib/AST/Expr.cpp

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ ConcreteDeclRef Expr::getReferencedDecl() const {
510510
NO_REFERENCE(EditorPlaceholder);
511511
NO_REFERENCE(ObjCSelector);
512512
NO_REFERENCE(KeyPath);
513+
NO_REFERENCE(KeyPathDot);
513514

514515
#undef SIMPLE_REFERENCE
515516
#undef NO_REFERENCE
@@ -796,6 +797,7 @@ bool Expr::canAppendCallParentheses() const {
796797
case ExprKind::Assign:
797798
case ExprKind::UnresolvedPattern:
798799
case ExprKind::EditorPlaceholder:
800+
case ExprKind::KeyPathDot:
799801
return false;
800802
}
801803

@@ -2003,23 +2005,17 @@ ArchetypeType *OpenExistentialExpr::getOpenedArchetype() const {
20032005
return type->castTo<ArchetypeType>();
20042006
}
20052007

2006-
KeyPathExpr::KeyPathExpr(ASTContext &C,
2007-
SourceLoc keywordLoc, SourceLoc lParenLoc,
2008-
TypeRepr *root,
2009-
ArrayRef<Component> components,
2010-
SourceLoc rParenLoc,
2011-
bool isObjC,
2012-
bool isImplicit)
2013-
: Expr(ExprKind::KeyPath, isImplicit),
2014-
KeywordLoc(keywordLoc), LParenLoc(lParenLoc), RParenLoc(rParenLoc),
2015-
RootType(root),
2016-
Components(C.AllocateUninitialized<Component>(components.size()))
2017-
{
2008+
KeyPathExpr::KeyPathExpr(ASTContext &C, SourceLoc keywordLoc,
2009+
SourceLoc lParenLoc, ArrayRef<Component> components,
2010+
SourceLoc rParenLoc, bool isImplicit)
2011+
: Expr(ExprKind::KeyPath, isImplicit), StartLoc(keywordLoc),
2012+
LParenLoc(lParenLoc), EndLoc(rParenLoc),
2013+
Components(C.AllocateUninitialized<Component>(components.size())) {
20182014
// Copy components into the AST context.
20192015
std::uninitialized_copy(components.begin(), components.end(),
20202016
Components.begin());
2021-
2022-
KeyPathExprBits.IsObjC = isObjC;
2017+
2018+
KeyPathExprBits.IsObjC = true;
20232019
}
20242020

20252021
void

lib/Parse/Lexer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,8 @@ void Lexer::lexImpl() {
19361936
case ',': return formToken(tok::comma, TokStart);
19371937
case ';': return formToken(tok::semi, TokStart);
19381938
case ':': return formToken(tok::colon, TokStart);
1939+
case '\\':
1940+
return formToken(tok::backslash, TokStart);
19391941

19401942
case '#':
19411943
return lexHash();

0 commit comments

Comments
 (0)