Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ Swift 3.0
let sel2 = #selector(setter: UIView.backgroundColor) // sel2 has type Selector
```

* [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:

```swift
person.valueForKeyPath(#keyPath(Person.bestFriend.lastName))
```

Swift 2.2
---------
Expand Down
14 changes: 13 additions & 1 deletion include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -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", ())

Expand Down
22 changes: 21 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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", ())
Expand Down Expand Up @@ -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))

Expand Down
119 changes: 119 additions & 0 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 + 9 };
static_assert(NumObjCKeyPathExprBits <= 32, "fits in an unsigned");

protected:
union {
ExprBitfields ExprBits;
Expand All @@ -333,6 +346,7 @@ class alignas(8) Expr {
CollectionUpcastConversionExprBitfields CollectionUpcastConversionExprBits;
TupleShuffleExprBitfields TupleShuffleExprBits;
ObjCSelectorExprBitfields ObjCSelectorExprBits;
ObjCKeyPathExprBitfields ObjCKeyPathExprBits;
};

private:
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/ExprNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions include/swift/IDE/CodeCompletion.h
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,8 @@ enum class CompletionKind {
PostfixExprParen,
SuperExpr,
SuperExprDot,
KeyPathExpr,
KeyPathExprDot,
TypeSimpleBeginning,
TypeIdentifierWithDot,
TypeIdentifierWithoutDot,
Expand Down
8 changes: 8 additions & 0 deletions include/swift/Parse/CodeCompletionCallbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ class CodeCompletionCallbacks {
/// a dot.
virtual void completeExprSuperDot(SuperRefExpr *SRE) = 0;

/// \brief Complete the argument to an Objective-C #keyPath
/// expression.
///
/// \param KPE A partial #keyPath expression that can be used to
/// provide context. This will be \c NULL if no components of the
/// #keyPath argument have been parsed yet.
virtual void completeExprKeyPath(ObjCKeyPathExpr *KPE, bool HasDot) = 0;

/// \brief Complete the beginning of type-simple -- no tokens provided
/// by user.
virtual void completeTypeSimpleBeginning() = 0;
Expand Down
1 change: 1 addition & 0 deletions include/swift/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions include/swift/Parse/Tokens.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions include/swift/Sema/IDETypeChecking.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,20 @@ namespace swift {
/// \returns True on applied, false on not applied.
bool isExtensionApplied(DeclContext &DC, Type Ty, const ExtensionDecl *ED);

/// The kind of type checking to perform for code completion.
enum class CompletionTypeCheckKind {
/// Type check the expression as normal.
Normal,

/// Type check the argument to an Objective-C #keyPath.
ObjCKeyPath,
};

/// \brief Return the type of an expression parsed during code completion, or
/// None on error.
Optional<Type> getTypeOfCompletionContextExpr(ASTContext &Ctx,
DeclContext *DC,
CompletionTypeCheckKind kind,
Expr *&parsedExpr);

/// Typecheck the sequence expression \p parsedExpr for code completion.
Expand Down
18 changes: 18 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions lib/AST/ASTWalker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
//===--------------------------------------------------------------------===//
Expand Down
42 changes: 42 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
}
Loading