Skip to content

Commit 9f0cec4

Browse files
committed
SE-0062: Implement #keyPath expression.
Implement the Objective-C #keyPath expression, which maps a sequence of @objc property accesses to a key-path suitable for use with Cocoa[Touch]. The implementation handles @objc properties of types that are either @objc or can be bridged to Objective-C, including the collections that work with key-value coding (Array/NSArray, Dictionary/NSDictionary, Set/NSSet). Still to come: code completion support and Fix-Its to migrate string literal keypaths to #keyPath. Implements the bulk of SR-1237 / rdar://problem/25710611.
1 parent 79c83c7 commit 9f0cec4

20 files changed

+880
-5
lines changed

Diff for: include/swift/AST/DiagnosticsParse.def

+13-1
Original file line numberDiff line numberDiff line change
@@ -1081,11 +1081,23 @@ ERROR(expected_type_after_as,none,
10811081
ERROR(string_interpolation_extra,none,
10821082
"extra tokens after interpolated string expression", ())
10831083

1084+
// Keypath expressions.
1085+
ERROR(expr_keypath_expected_lparen,PointsToFirstBadToken,
1086+
"expected '(' following '#keyPath'", ())
1087+
ERROR(expr_keypath_expected_property_or_type,PointsToFirstBadToken,
1088+
"expected property or type name within '#keyPath(...)'", ())
1089+
ERROR(expr_keypath_expected_rparen,PointsToFirstBadToken,
1090+
"expected ')' to complete '#keyPath' expression", ())
1091+
ERROR(expr_keypath_compound_name,none,
1092+
"cannot use compound name %0 in '#keyPath' expression", (DeclName))
1093+
10841094
// Selector expressions.
10851095
ERROR(expr_selector_expected_lparen,PointsToFirstBadToken,
10861096
"expected '(' following '#selector'", ())
1087-
ERROR(expr_selector_expected_expr,PointsToFirstBadToken,
1097+
ERROR(expr_selector_expected_method_expr,PointsToFirstBadToken,
10881098
"expected expression naming a method within '#selector(...)'", ())
1099+
ERROR(expr_selector_expected_property_expr,PointsToFirstBadToken,
1100+
"expected expression naming a property within '#selector(...)'", ())
10891101
ERROR(expr_selector_expected_rparen,PointsToFirstBadToken,
10901102
"expected ')' to complete '#selector' expression", ())
10911103

Diff for: include/swift/AST/DiagnosticsSema.def

+21-1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,26 @@ ERROR(noescape_functiontype_mismatch,none,
368368
"invalid conversion from non-escaping function of type %0 to "
369369
"potentially escaping function type %1", (Type, Type))
370370

371+
// Key-path expressions.
372+
ERROR(expr_keypath_no_objc_runtime,none,
373+
"'#keyPath' can only be used with the Objective-C runtime", ())
374+
ERROR(expression_unused_keypath_result,none,
375+
"result of '#keyPath' is unused", ())
376+
ERROR(expr_keypath_non_objc_property,none,
377+
"argument of '#keyPath' refers to non-'@objc' property %0",
378+
(DeclName))
379+
ERROR(stdlib_anyobject_not_found,none,
380+
"broken standard library: cannot find 'AnyObject' protocol", ())
381+
ERROR(expr_keypath_type_of_property,none,
382+
"cannot refer to type member %0 within instance of type %1",
383+
(DeclName, Type))
384+
ERROR(expr_keypath_generic_type,none,
385+
"'#keyPath' cannot refer to generic type %0", (DeclName))
386+
ERROR(expr_keypath_not_property,none,
387+
"'#keyPath' cannot refer to %0 %1", (DescriptiveDeclKind, DeclName))
388+
ERROR(expr_keypath_empty,none,
389+
"empty '#keyPath' does not refer to a property", ())
390+
371391
// Selector expressions.
372392
ERROR(expr_selector_no_objc_runtime,none,
373393
"'#selector' can only be used with the Objective-C runtime", ())
@@ -401,7 +421,7 @@ ERROR(expr_selector_not_objc,none,
401421
"argument of '#selector' refers to %0 %1 that is not exposed to "
402422
"Objective-C",
403423
(DescriptiveDeclKind, DeclName))
404-
NOTE(expr_selector_make_objc,none,
424+
NOTE(make_decl_objc,none,
405425
"add '@objc' to expose this %0 to Objective-C",
406426
(DescriptiveDeclKind))
407427

Diff for: include/swift/AST/Expr.h

+119
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,19 @@ class alignas(8) Expr {
311311
enum { NumObjCSelectorExprBits = NumExprBits + 2 };
312312
static_assert(NumObjCSelectorExprBits <= 32, "fits in an unsigned");
313313

314+
class ObjCKeyPathExprBitfields {
315+
friend class ObjCKeyPathExpr;
316+
unsigned : NumExprBits;
317+
318+
/// The number of components in the selector path.
319+
unsigned NumComponents : 8;
320+
321+
/// Whether the names have corresponding source locations.
322+
unsigned HaveSourceLocations : 1;
323+
};
324+
enum { NumObjCKeyPathExprBits = NumExprBits + 17 };
325+
static_assert(NumObjCKeyPathExprBits <= 32, "fits in an unsigned");
326+
314327
protected:
315328
union {
316329
ExprBitfields ExprBits;
@@ -333,6 +346,7 @@ class alignas(8) Expr {
333346
CollectionUpcastConversionExprBitfields CollectionUpcastConversionExprBits;
334347
TupleShuffleExprBitfields TupleShuffleExprBits;
335348
ObjCSelectorExprBitfields ObjCSelectorExprBits;
349+
ObjCKeyPathExprBitfields ObjCKeyPathExprBits;
336350
};
337351

338352
private:
@@ -3860,6 +3874,111 @@ class ObjCSelectorExpr : public Expr {
38603874
}
38613875
};
38623876

3877+
/// Produces a keypath string for the given referenced property.
3878+
///
3879+
/// \code
3880+
/// #keyPath(Person.friends.firstName)
3881+
/// \endcode
3882+
class ObjCKeyPathExpr : public Expr {
3883+
SourceLoc KeywordLoc;
3884+
SourceLoc LParenLoc;
3885+
SourceLoc RParenLoc;
3886+
Expr *SemanticExpr = nullptr;
3887+
3888+
/// A single stored component, which will be either an identifier or
3889+
/// a resolved declaration.
3890+
typedef llvm::PointerUnion<Identifier, ValueDecl *> StoredComponent;
3891+
3892+
ObjCKeyPathExpr(SourceLoc keywordLoc, SourceLoc lParenLoc,
3893+
ArrayRef<Identifier> names,
3894+
ArrayRef<SourceLoc> nameLocs,
3895+
SourceLoc rParenLoc);
3896+
3897+
/// Retrieve a mutable version of the "components" array, for
3898+
/// initialization purposes.
3899+
MutableArrayRef<StoredComponent> getComponentsMutable() {
3900+
return { reinterpret_cast<StoredComponent *>(this + 1), getNumComponents() };
3901+
}
3902+
3903+
/// Retrieve the "components" storage.
3904+
ArrayRef<StoredComponent> getComponents() const {
3905+
return { reinterpret_cast<StoredComponent const *>(this + 1),
3906+
getNumComponents() };
3907+
}
3908+
3909+
/// Retrieve a mutable version of the name locations array, for
3910+
/// initialization purposes.
3911+
MutableArrayRef<SourceLoc> getNameLocsMutable() {
3912+
if (!ObjCKeyPathExprBits.HaveSourceLocations) return { };
3913+
3914+
auto mutableComponents = getComponentsMutable();
3915+
return { reinterpret_cast<SourceLoc *>(mutableComponents.end()),
3916+
mutableComponents.size() };
3917+
}
3918+
3919+
public:
3920+
/// Create a new #keyPath expression.
3921+
///
3922+
/// \param nameLocs The locations of the names in the key-path,
3923+
/// which must either have the same number of entries as \p names or
3924+
/// must be empty.
3925+
static ObjCKeyPathExpr *create(ASTContext &ctx,
3926+
SourceLoc keywordLoc, SourceLoc lParenLoc,
3927+
ArrayRef<Identifier> names,
3928+
ArrayRef<SourceLoc> nameLocs,
3929+
SourceLoc rParenLoc);
3930+
3931+
SourceLoc getLoc() const { return KeywordLoc; }
3932+
SourceRange getSourceRange() const {
3933+
return SourceRange(KeywordLoc, RParenLoc);
3934+
}
3935+
3936+
/// Retrieve the number of components in the key-path.
3937+
unsigned getNumComponents() const {
3938+
return ObjCKeyPathExprBits.NumComponents;
3939+
}
3940+
3941+
/// Retrieve's the name for the (i)th component;
3942+
Identifier getComponentName(unsigned i) const;
3943+
3944+
/// Retrieve's the declaration corresponding to the (i)th component,
3945+
/// or null if this component has not yet been resolved.
3946+
ValueDecl *getComponentDecl(unsigned i) const {
3947+
return getComponents()[i].dyn_cast<ValueDecl *>();
3948+
}
3949+
3950+
/// Retrieve the location corresponding to the (i)th name.
3951+
///
3952+
/// If no location information is available, returns an empty
3953+
/// \c DeclNameLoc.
3954+
SourceLoc getComponentNameLoc(unsigned i) const {
3955+
if (!ObjCKeyPathExprBits.HaveSourceLocations) return { };
3956+
3957+
auto components = getComponents();
3958+
ArrayRef<SourceLoc> nameLocs(
3959+
reinterpret_cast<SourceLoc const *>(components.end()),
3960+
components.size());
3961+
3962+
return nameLocs[i];
3963+
}
3964+
3965+
/// Retrieve the semantic expression, which will be \c NULL prior to
3966+
/// type checking and a string literal after type checking.
3967+
Expr *getSemanticExpr() const { return SemanticExpr; }
3968+
3969+
/// Set the semantic expression.
3970+
void setSemanticExpr(Expr *expr) { SemanticExpr = expr; }
3971+
3972+
/// Resolve the given component to the given declaration.
3973+
void resolveComponent(unsigned idx, ValueDecl *decl) {
3974+
getComponentsMutable()[idx] = decl;
3975+
}
3976+
3977+
static bool classof(const Expr *E) {
3978+
return E->getKind() == ExprKind::ObjCKeyPath;
3979+
}
3980+
};
3981+
38633982
#undef SWIFT_FORWARD_SOURCE_LOCS_TO
38643983

38653984
} // end namespace swift

Diff for: include/swift/AST/ExprNodes.def

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ EXPR(CodeCompletion, Expr)
158158
UNCHECKED_EXPR(UnresolvedPattern, Expr)
159159
EXPR(EditorPlaceholder, Expr)
160160
EXPR(ObjCSelector, Expr)
161+
EXPR(ObjCKeyPath, Expr)
161162

162163
#undef EXPR_RANGE
163164
#undef UNCHECKED_EXPR

Diff for: include/swift/Parse/Parser.h

+1
Original file line numberDiff line numberDiff line change
@@ -1109,6 +1109,7 @@ class Parser {
11091109
bool isExprBasic);
11101110
ParserResult<Expr> parseExprPostfix(Diag<> ID, bool isExprBasic);
11111111
ParserResult<Expr> parseExprUnary(Diag<> ID, bool isExprBasic);
1112+
ParserResult<Expr> parseExprKeyPath();
11121113
ParserResult<Expr> parseExprSelector();
11131114
ParserResult<Expr> parseExprSuper();
11141115
ParserResult<Expr> parseExprConfiguration();

Diff for: include/swift/Parse/Tokens.def

+1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ POUND_KEYWORD(if)
195195
POUND_KEYWORD(else)
196196
POUND_KEYWORD(elseif)
197197
POUND_KEYWORD(endif)
198+
POUND_KEYWORD(keyPath)
198199
POUND_KEYWORD(line)
199200
POUND_KEYWORD(setline)
200201
POUND_KEYWORD(sourceLocation)

Diff for: lib/AST/ASTDumper.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -2226,6 +2226,24 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
22262226
printRec(E->getSubExpr());
22272227
OS << ')';
22282228
}
2229+
2230+
void visitObjCKeyPathExpr(ObjCKeyPathExpr *E) {
2231+
printCommon(E, "keypath_expr");
2232+
for (unsigned i = 0, n = E->getNumComponents(); i != n; ++i) {
2233+
OS << "\n";
2234+
OS.indent(Indent + 2);
2235+
OS << "component=";
2236+
if (auto decl = E->getComponentDecl(i))
2237+
decl->dumpRef(OS);
2238+
else
2239+
OS << E->getComponentName(i);
2240+
}
2241+
if (auto semanticE = E->getSemanticExpr()) {
2242+
OS << '\n';
2243+
printRec(semanticE);
2244+
}
2245+
OS << ")";
2246+
}
22292247
};
22302248

22312249
} // end anonymous namespace.

Diff for: lib/AST/ASTWalker.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,11 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
833833
return E;
834834
}
835835

836+
Expr *visitObjCKeyPathExpr(ObjCKeyPathExpr *E) {
837+
HANDLE_SEMANTIC_EXPR(E);
838+
return E;
839+
}
840+
836841
//===--------------------------------------------------------------------===//
837842
// Everything Else
838843
//===--------------------------------------------------------------------===//

Diff for: lib/AST/Expr.cpp

+42
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ void Expr::propagateLValueAccessKind(AccessKind accessKind,
315315
NON_LVALUE_EXPR(DefaultValue)
316316
NON_LVALUE_EXPR(CodeCompletion)
317317
NON_LVALUE_EXPR(ObjCSelector)
318+
NON_LVALUE_EXPR(ObjCKeyPath)
318319

319320
#define UNCHECKED_EXPR(KIND, BASE) \
320321
NON_LVALUE_EXPR(KIND)
@@ -489,6 +490,7 @@ bool Expr::canAppendCallParentheses() const {
489490
case ExprKind::InterpolatedStringLiteral:
490491
case ExprKind::MagicIdentifierLiteral:
491492
case ExprKind::ObjCSelector:
493+
case ExprKind::ObjCKeyPath:
492494
return true;
493495

494496
case ExprKind::ObjectLiteral:
@@ -1226,3 +1228,43 @@ ArchetypeType *OpenExistentialExpr::getOpenedArchetype() const {
12261228
type = metaTy->getInstanceType();
12271229
return type->castTo<ArchetypeType>();
12281230
}
1231+
1232+
ObjCKeyPathExpr::ObjCKeyPathExpr(SourceLoc keywordLoc, SourceLoc lParenLoc,
1233+
ArrayRef<Identifier> names,
1234+
ArrayRef<SourceLoc> nameLocs,
1235+
SourceLoc rParenLoc)
1236+
: Expr(ExprKind::ObjCKeyPath, /*Implicit=*/nameLocs.empty()),
1237+
KeywordLoc(keywordLoc), LParenLoc(lParenLoc), RParenLoc(rParenLoc)
1238+
{
1239+
// Copy components (which are all names).
1240+
ObjCKeyPathExprBits.NumComponents = names.size();
1241+
for (auto idx : indices(names))
1242+
getComponentsMutable()[idx] = names[idx];
1243+
1244+
assert(nameLocs.empty() || nameLocs.size() == names.size());
1245+
ObjCKeyPathExprBits.HaveSourceLocations = !nameLocs.empty();
1246+
if (ObjCKeyPathExprBits.HaveSourceLocations) {
1247+
memcpy(getNameLocsMutable().data(), nameLocs.data(),
1248+
nameLocs.size() * sizeof(SourceLoc));
1249+
}
1250+
}
1251+
1252+
Identifier ObjCKeyPathExpr::getComponentName(unsigned i) const {
1253+
if (auto decl = getComponentDecl(i))
1254+
return decl->getFullName().getBaseName();
1255+
1256+
return getComponents()[i].get<Identifier>();
1257+
}
1258+
1259+
ObjCKeyPathExpr *ObjCKeyPathExpr::create(ASTContext &ctx,
1260+
SourceLoc keywordLoc, SourceLoc lParenLoc,
1261+
ArrayRef<Identifier> names,
1262+
ArrayRef<SourceLoc> nameLocs,
1263+
SourceLoc rParenLoc) {
1264+
unsigned size = sizeof(ObjCKeyPathExpr)
1265+
+ names.size() * sizeof(Identifier)
1266+
+ nameLocs.size() * sizeof(SourceLoc);
1267+
void *mem = ctx.Allocate(size, alignof(ObjCKeyPathExpr));
1268+
return new (mem) ObjCKeyPathExpr(keywordLoc, lParenLoc, names, nameLocs,
1269+
rParenLoc);
1270+
}

0 commit comments

Comments
 (0)