Permalink
Browse files

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.
  • Loading branch information...
DougGregor committed May 19, 2016
1 parent 79c83c7 commit 9f0cec4984d7bb23eb242d9eca7c82039310f52d
@@ -1081,11 +1081,23 @@ ERROR(expected_type_after_as,none,
ERROR(string_interpolation_extra,none,
"extra tokens after interpolated string expression", ())
// Keypath expressions.
ERROR(expr_keypath_expected_lparen,PointsToFirstBadToken,
"expected '(' following '#keyPath'", ())
ERROR(expr_keypath_expected_property_or_type,PointsToFirstBadToken,
"expected property or type name within '#keyPath(...)'", ())
ERROR(expr_keypath_expected_rparen,PointsToFirstBadToken,
"expected ')' to complete '#keyPath' expression", ())
ERROR(expr_keypath_compound_name,none,
"cannot use compound name %0 in '#keyPath' expression", (DeclName))
// Selector expressions.
ERROR(expr_selector_expected_lparen,PointsToFirstBadToken,
"expected '(' following '#selector'", ())
ERROR(expr_selector_expected_expr,PointsToFirstBadToken,
ERROR(expr_selector_expected_method_expr,PointsToFirstBadToken,
"expected expression naming a method within '#selector(...)'", ())
ERROR(expr_selector_expected_property_expr,PointsToFirstBadToken,
"expected expression naming a property within '#selector(...)'", ())
ERROR(expr_selector_expected_rparen,PointsToFirstBadToken,
"expected ')' to complete '#selector' expression", ())
@@ -368,6 +368,26 @@ ERROR(noescape_functiontype_mismatch,none,
"invalid conversion from non-escaping function of type %0 to "
"potentially escaping function type %1", (Type, Type))
// Key-path expressions.
ERROR(expr_keypath_no_objc_runtime,none,
"'#keyPath' can only be used with the Objective-C runtime", ())
ERROR(expression_unused_keypath_result,none,
"result of '#keyPath' is unused", ())
ERROR(expr_keypath_non_objc_property,none,
"argument of '#keyPath' refers to non-'@objc' property %0",
(DeclName))
ERROR(stdlib_anyobject_not_found,none,
"broken standard library: cannot find 'AnyObject' protocol", ())
ERROR(expr_keypath_type_of_property,none,
"cannot refer to type member %0 within instance of type %1",
(DeclName, Type))
ERROR(expr_keypath_generic_type,none,
"'#keyPath' cannot refer to generic type %0", (DeclName))
ERROR(expr_keypath_not_property,none,
"'#keyPath' cannot refer to %0 %1", (DescriptiveDeclKind, DeclName))
ERROR(expr_keypath_empty,none,
"empty '#keyPath' does not refer to a property", ())
// Selector expressions.
ERROR(expr_selector_no_objc_runtime,none,
"'#selector' can only be used with the Objective-C runtime", ())
@@ -401,7 +421,7 @@ ERROR(expr_selector_not_objc,none,
"argument of '#selector' refers to %0 %1 that is not exposed to "
"Objective-C",
(DescriptiveDeclKind, DeclName))
NOTE(expr_selector_make_objc,none,
NOTE(make_decl_objc,none,
"add '@objc' to expose this %0 to Objective-C",
(DescriptiveDeclKind))
@@ -311,6 +311,19 @@ class alignas(8) Expr {
enum { NumObjCSelectorExprBits = NumExprBits + 2 };
static_assert(NumObjCSelectorExprBits <= 32, "fits in an unsigned");
class ObjCKeyPathExprBitfields {
friend class ObjCKeyPathExpr;
unsigned : NumExprBits;
/// The number of components in the selector path.
unsigned NumComponents : 8;
/// Whether the names have corresponding source locations.
unsigned HaveSourceLocations : 1;
};
enum { NumObjCKeyPathExprBits = NumExprBits + 17 };
static_assert(NumObjCKeyPathExprBits <= 32, "fits in an unsigned");
protected:
union {
ExprBitfields ExprBits;
@@ -333,6 +346,7 @@ class alignas(8) Expr {
CollectionUpcastConversionExprBitfields CollectionUpcastConversionExprBits;
TupleShuffleExprBitfields TupleShuffleExprBits;
ObjCSelectorExprBitfields ObjCSelectorExprBits;
ObjCKeyPathExprBitfields ObjCKeyPathExprBits;
};
private:
@@ -3860,6 +3874,111 @@ class ObjCSelectorExpr : public Expr {
}
};
/// Produces a keypath string for the given referenced property.
///
/// \code
/// #keyPath(Person.friends.firstName)
/// \endcode
class ObjCKeyPathExpr : public Expr {
SourceLoc KeywordLoc;
SourceLoc LParenLoc;
SourceLoc RParenLoc;
Expr *SemanticExpr = nullptr;
/// A single stored component, which will be either an identifier or
/// a resolved declaration.
typedef llvm::PointerUnion<Identifier, ValueDecl *> StoredComponent;
ObjCKeyPathExpr(SourceLoc keywordLoc, SourceLoc lParenLoc,
ArrayRef<Identifier> names,
ArrayRef<SourceLoc> nameLocs,
SourceLoc rParenLoc);
/// Retrieve a mutable version of the "components" array, for
/// initialization purposes.
MutableArrayRef<StoredComponent> getComponentsMutable() {
return { reinterpret_cast<StoredComponent *>(this + 1), getNumComponents() };
}
/// Retrieve the "components" storage.
ArrayRef<StoredComponent> getComponents() const {
return { reinterpret_cast<StoredComponent const *>(this + 1),
getNumComponents() };
}
/// Retrieve a mutable version of the name locations array, for
/// initialization purposes.
MutableArrayRef<SourceLoc> getNameLocsMutable() {
if (!ObjCKeyPathExprBits.HaveSourceLocations) return { };
auto mutableComponents = getComponentsMutable();
return { reinterpret_cast<SourceLoc *>(mutableComponents.end()),
mutableComponents.size() };
}
public:
/// Create a new #keyPath expression.
///
/// \param nameLocs The locations of the names in the key-path,
/// which must either have the same number of entries as \p names or
/// must be empty.
static ObjCKeyPathExpr *create(ASTContext &ctx,
SourceLoc keywordLoc, SourceLoc lParenLoc,
ArrayRef<Identifier> names,
ArrayRef<SourceLoc> nameLocs,
SourceLoc rParenLoc);
SourceLoc getLoc() const { return KeywordLoc; }
SourceRange getSourceRange() const {
return SourceRange(KeywordLoc, RParenLoc);
}
/// Retrieve the number of components in the key-path.
unsigned getNumComponents() const {
return ObjCKeyPathExprBits.NumComponents;
}
/// Retrieve's the name for the (i)th component;
Identifier getComponentName(unsigned i) const;
/// Retrieve's the declaration corresponding to the (i)th component,
/// or null if this component has not yet been resolved.
ValueDecl *getComponentDecl(unsigned i) const {
return getComponents()[i].dyn_cast<ValueDecl *>();
}
/// Retrieve the location corresponding to the (i)th name.
///
/// If no location information is available, returns an empty
/// \c DeclNameLoc.
SourceLoc getComponentNameLoc(unsigned i) const {
if (!ObjCKeyPathExprBits.HaveSourceLocations) return { };
auto components = getComponents();
ArrayRef<SourceLoc> nameLocs(
reinterpret_cast<SourceLoc const *>(components.end()),
components.size());
return nameLocs[i];
}
/// Retrieve the semantic expression, which will be \c NULL prior to
/// type checking and a string literal after type checking.
Expr *getSemanticExpr() const { return SemanticExpr; }
/// Set the semantic expression.
void setSemanticExpr(Expr *expr) { SemanticExpr = expr; }
/// Resolve the given component to the given declaration.
void resolveComponent(unsigned idx, ValueDecl *decl) {
getComponentsMutable()[idx] = decl;
}
static bool classof(const Expr *E) {
return E->getKind() == ExprKind::ObjCKeyPath;
}
};
#undef SWIFT_FORWARD_SOURCE_LOCS_TO
} // end namespace swift
@@ -158,6 +158,7 @@ EXPR(CodeCompletion, Expr)
UNCHECKED_EXPR(UnresolvedPattern, Expr)
EXPR(EditorPlaceholder, Expr)
EXPR(ObjCSelector, Expr)
EXPR(ObjCKeyPath, Expr)
#undef EXPR_RANGE
#undef UNCHECKED_EXPR
@@ -1109,6 +1109,7 @@ class Parser {
bool isExprBasic);
ParserResult<Expr> parseExprPostfix(Diag<> ID, bool isExprBasic);
ParserResult<Expr> parseExprUnary(Diag<> ID, bool isExprBasic);
ParserResult<Expr> parseExprKeyPath();
ParserResult<Expr> parseExprSelector();
ParserResult<Expr> parseExprSuper();
ParserResult<Expr> parseExprConfiguration();
@@ -195,6 +195,7 @@ POUND_KEYWORD(if)
POUND_KEYWORD(else)
POUND_KEYWORD(elseif)
POUND_KEYWORD(endif)
POUND_KEYWORD(keyPath)
POUND_KEYWORD(line)
POUND_KEYWORD(setline)
POUND_KEYWORD(sourceLocation)
@@ -2226,6 +2226,24 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
printRec(E->getSubExpr());
OS << ')';
}
void visitObjCKeyPathExpr(ObjCKeyPathExpr *E) {
printCommon(E, "keypath_expr");
for (unsigned i = 0, n = E->getNumComponents(); i != n; ++i) {
OS << "\n";
OS.indent(Indent + 2);
OS << "component=";
if (auto decl = E->getComponentDecl(i))
decl->dumpRef(OS);
else
OS << E->getComponentName(i);
}
if (auto semanticE = E->getSemanticExpr()) {
OS << '\n';
printRec(semanticE);
}
OS << ")";
}
};
} // end anonymous namespace.
@@ -833,6 +833,11 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
return E;
}
Expr *visitObjCKeyPathExpr(ObjCKeyPathExpr *E) {
HANDLE_SEMANTIC_EXPR(E);
return E;
}
//===--------------------------------------------------------------------===//
// Everything Else
//===--------------------------------------------------------------------===//
@@ -315,6 +315,7 @@ void Expr::propagateLValueAccessKind(AccessKind accessKind,
NON_LVALUE_EXPR(DefaultValue)
NON_LVALUE_EXPR(CodeCompletion)
NON_LVALUE_EXPR(ObjCSelector)
NON_LVALUE_EXPR(ObjCKeyPath)
#define UNCHECKED_EXPR(KIND, BASE) \
NON_LVALUE_EXPR(KIND)
@@ -489,6 +490,7 @@ bool Expr::canAppendCallParentheses() const {
case ExprKind::InterpolatedStringLiteral:
case ExprKind::MagicIdentifierLiteral:
case ExprKind::ObjCSelector:
case ExprKind::ObjCKeyPath:
return true;
case ExprKind::ObjectLiteral:
@@ -1226,3 +1228,43 @@ ArchetypeType *OpenExistentialExpr::getOpenedArchetype() const {
type = metaTy->getInstanceType();
return type->castTo<ArchetypeType>();
}
ObjCKeyPathExpr::ObjCKeyPathExpr(SourceLoc keywordLoc, SourceLoc lParenLoc,
ArrayRef<Identifier> names,
ArrayRef<SourceLoc> nameLocs,
SourceLoc rParenLoc)
: Expr(ExprKind::ObjCKeyPath, /*Implicit=*/nameLocs.empty()),
KeywordLoc(keywordLoc), LParenLoc(lParenLoc), RParenLoc(rParenLoc)
{
// Copy components (which are all names).
ObjCKeyPathExprBits.NumComponents = names.size();
for (auto idx : indices(names))
getComponentsMutable()[idx] = names[idx];
assert(nameLocs.empty() || nameLocs.size() == names.size());
ObjCKeyPathExprBits.HaveSourceLocations = !nameLocs.empty();
if (ObjCKeyPathExprBits.HaveSourceLocations) {
memcpy(getNameLocsMutable().data(), nameLocs.data(),
nameLocs.size() * sizeof(SourceLoc));
}
}
Identifier ObjCKeyPathExpr::getComponentName(unsigned i) const {
if (auto decl = getComponentDecl(i))
return decl->getFullName().getBaseName();
return getComponents()[i].get<Identifier>();
}
ObjCKeyPathExpr *ObjCKeyPathExpr::create(ASTContext &ctx,
SourceLoc keywordLoc, SourceLoc lParenLoc,
ArrayRef<Identifier> names,
ArrayRef<SourceLoc> nameLocs,
SourceLoc rParenLoc) {
unsigned size = sizeof(ObjCKeyPathExpr)
+ names.size() * sizeof(Identifier)
+ nameLocs.size() * sizeof(SourceLoc);
void *mem = ctx.Allocate(size, alignof(ObjCKeyPathExpr));
return new (mem) ObjCKeyPathExpr(keywordLoc, lParenLoc, names, nameLocs,
rParenLoc);
}
Oops, something went wrong.

1 comment on commit 9f0cec4

@lattner

This comment has been minimized.

Collaborator

lattner commented on 9f0cec4 May 19, 2016

Awesome!

Please sign in to comment.