Skip to content

Commit ba067bd

Browse files
DougGregortkremenek
authored andcommitted
Implement SE-0062: Referencing Objective-C key-paths (#2640)
* 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. * Fix a test for the #keyPath expression. * [IDE] Code completion for Objective-C #keyPath expressions. Implement code completion support for Objective-C #keyPath expressions, using semantic analysis of the partially-typed keypath argument to provide an appropriate set of results (i.e., just properties and types). This implements all of the necessary parts of SE-0062 / SR-1237 / rdar://problem/25710611, although at some point I'd like to follow it up with some warnings to help migrate existing string literals to * Fix the # of Expr bits used by ObjCKeyPathExpr. * Add SE-0062 to the changelog.
1 parent 2fd2ccf commit ba067bd

27 files changed

+1102
-22
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ Swift 3.0
219219
let sel2 = #selector(setter: UIView.backgroundColor) // sel2 has type Selector
220220
```
221221

222+
* [SE-0062](https://github.com/apple/swift-evolution/blob/master/proposals/0062-objc-keypaths.md): A key-path can now be formed with `#keyPath`. For example:
223+
224+
```swift
225+
person.valueForKeyPath(#keyPath(Person.bestFriend.lastName))
226+
```
222227

223228
Swift 2.2
224229
---------

include/swift/AST/DiagnosticsParse.def

Lines changed: 13 additions & 1 deletion
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

include/swift/AST/DiagnosticsSema.def

Lines changed: 21 additions & 1 deletion
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

include/swift/AST/Expr.h

Lines changed: 119 additions & 0 deletions
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 + 9 };
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

include/swift/AST/ExprNodes.def

Lines changed: 1 addition & 0 deletions
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

include/swift/IDE/CodeCompletion.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,8 @@ enum class CompletionKind {
487487
PostfixExprParen,
488488
SuperExpr,
489489
SuperExprDot,
490+
KeyPathExpr,
491+
KeyPathExprDot,
490492
TypeSimpleBeginning,
491493
TypeIdentifierWithDot,
492494
TypeIdentifierWithoutDot,

include/swift/Parse/CodeCompletionCallbacks.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,14 @@ class CodeCompletionCallbacks {
165165
/// a dot.
166166
virtual void completeExprSuperDot(SuperRefExpr *SRE) = 0;
167167

168+
/// \brief Complete the argument to an Objective-C #keyPath
169+
/// expression.
170+
///
171+
/// \param KPE A partial #keyPath expression that can be used to
172+
/// provide context. This will be \c NULL if no components of the
173+
/// #keyPath argument have been parsed yet.
174+
virtual void completeExprKeyPath(ObjCKeyPathExpr *KPE, bool HasDot) = 0;
175+
168176
/// \brief Complete the beginning of type-simple -- no tokens provided
169177
/// by user.
170178
virtual void completeTypeSimpleBeginning() = 0;

include/swift/Parse/Parser.h

Lines changed: 1 addition & 0 deletions
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();

include/swift/Parse/Tokens.def

Lines changed: 1 addition & 0 deletions
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)

include/swift/Sema/IDETypeChecking.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,20 @@ namespace swift {
7777
/// \returns True on applied, false on not applied.
7878
bool isExtensionApplied(DeclContext &DC, Type Ty, const ExtensionDecl *ED);
7979

80+
/// The kind of type checking to perform for code completion.
81+
enum class CompletionTypeCheckKind {
82+
/// Type check the expression as normal.
83+
Normal,
84+
85+
/// Type check the argument to an Objective-C #keyPath.
86+
ObjCKeyPath,
87+
};
88+
8089
/// \brief Return the type of an expression parsed during code completion, or
8190
/// None on error.
8291
Optional<Type> getTypeOfCompletionContextExpr(ASTContext &Ctx,
8392
DeclContext *DC,
93+
CompletionTypeCheckKind kind,
8494
Expr *&parsedExpr);
8595

8696
/// Typecheck the sequence expression \p parsedExpr for code completion.

0 commit comments

Comments
 (0)