Skip to content

Commit 1de7b3f

Browse files
authored
Merge pull request #80303 from hamishknight/off-the-chain
[Sema] Fix optional chaining behavior with postfix operators
2 parents a5a84f1 + e30e5a7 commit 1de7b3f

File tree

2 files changed

+111
-46
lines changed

2 files changed

+111
-46
lines changed

lib/Sema/PreCheckTarget.cpp

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -317,54 +317,78 @@ static bool findNonMembers(ArrayRef<LookupResultEntry> lookupResults,
317317
return AllDeclRefs;
318318
}
319319

320+
namespace {
321+
enum class MemberChainKind {
322+
OptionalBind, // A 'x?.y' optional binding chain
323+
UnresolvedMember, // A '.foo.bar' chain
324+
};
325+
} // end anonymous namespace
326+
320327
/// Find the next element in a chain of members. If this expression is (or
321328
/// could be) the base of such a chain, this will return \c nullptr.
322-
static Expr *getMemberChainSubExpr(Expr *expr) {
329+
static Expr *getMemberChainSubExpr(Expr *expr, MemberChainKind kind) {
323330
assert(expr && "getMemberChainSubExpr called with null expr!");
324-
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(expr)) {
331+
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(expr))
325332
return UDE->getBase();
326-
} else if (auto *CE = dyn_cast<CallExpr>(expr)) {
333+
if (auto *CE = dyn_cast<CallExpr>(expr))
327334
return CE->getFn();
328-
} else if (auto *BOE = dyn_cast<BindOptionalExpr>(expr)) {
335+
if (auto *BOE = dyn_cast<BindOptionalExpr>(expr))
329336
return BOE->getSubExpr();
330-
} else if (auto *FVE = dyn_cast<ForceValueExpr>(expr)) {
337+
if (auto *FVE = dyn_cast<ForceValueExpr>(expr))
331338
return FVE->getSubExpr();
332-
} else if (auto *SE = dyn_cast<SubscriptExpr>(expr)) {
339+
if (auto *SE = dyn_cast<SubscriptExpr>(expr))
333340
return SE->getBase();
334-
} else if (auto *DSE = dyn_cast<DotSelfExpr>(expr)) {
341+
if (auto *DSE = dyn_cast<DotSelfExpr>(expr))
335342
return DSE->getSubExpr();
336-
} else if (auto *USE = dyn_cast<UnresolvedSpecializeExpr>(expr)) {
343+
if (auto *USE = dyn_cast<UnresolvedSpecializeExpr>(expr))
337344
return USE->getSubExpr();
338-
} else if (auto *CCE = dyn_cast<CodeCompletionExpr>(expr)) {
345+
if (auto *CCE = dyn_cast<CodeCompletionExpr>(expr))
339346
return CCE->getBase();
340-
} else {
341-
return nullptr;
347+
348+
if (kind == MemberChainKind::OptionalBind) {
349+
// We allow postfix operators to be part of the optional member chain, e.g:
350+
//
351+
// for?.bar++
352+
// x.y?^.foo()
353+
//
354+
// Note this behavior is specific to optional chains, we treat e.g
355+
// `.foo^` as `(.foo)^`.
356+
if (auto *PO = dyn_cast<PostfixUnaryExpr>(expr))
357+
return PO->getOperand();
358+
359+
// Unresolved member chains can themselves be nested in optional chains
360+
// since optional chains can include postfix operators.
361+
if (auto *UME = dyn_cast<UnresolvedMemberChainResultExpr>(expr))
362+
return UME->getSubExpr();
342363
}
364+
365+
return nullptr;
343366
}
344367

345368
UnresolvedMemberExpr *TypeChecker::getUnresolvedMemberChainBase(Expr *expr) {
346-
if (auto *subExpr = getMemberChainSubExpr(expr))
369+
if (auto *subExpr =
370+
getMemberChainSubExpr(expr, MemberChainKind::UnresolvedMember)) {
347371
return getUnresolvedMemberChainBase(subExpr);
348-
else
349-
return dyn_cast<UnresolvedMemberExpr>(expr);
372+
}
373+
return dyn_cast<UnresolvedMemberExpr>(expr);
350374
}
351375

352376
static bool isBindOptionalMemberChain(Expr *expr) {
353-
if (isa<BindOptionalExpr>(expr)) {
377+
if (isa<BindOptionalExpr>(expr))
354378
return true;
355-
} else if (auto *base = getMemberChainSubExpr(expr)) {
379+
380+
if (auto *base = getMemberChainSubExpr(expr, MemberChainKind::OptionalBind))
356381
return isBindOptionalMemberChain(base);
357-
} else {
358-
return false;
359-
}
382+
383+
return false;
360384
}
361385

362386
/// Whether this expression sits at the end of a chain of member accesses.
363-
static bool isMemberChainTail(Expr *expr, Expr *parent) {
387+
static bool isMemberChainTail(Expr *expr, Expr *parent, MemberChainKind kind) {
364388
assert(expr && "isMemberChainTail called with null expr!");
365389
// If this expression's parent is not itself part of a chain (or, this expr
366390
// has no parent expr), this must be the tail of the chain.
367-
return !parent || getMemberChainSubExpr(parent) != expr;
391+
return !parent || getMemberChainSubExpr(parent, kind) != expr;
368392
}
369393

370394
static bool isValidForwardReference(ValueDecl *D, DeclContext *DC,
@@ -1092,6 +1116,10 @@ class PreCheckTarget final : public ASTWalker {
10921116
/// Pull some operator expressions into the optional chain.
10931117
OptionalEvaluationExpr *hoistOptionalEvaluationExprIfNeeded(Expr *E);
10941118

1119+
/// Wrap an unresolved member or optional bind chain in an
1120+
/// UnresolvedMemberChainResultExpr or OptionalEvaluationExpr respectively.
1121+
Expr *wrapMemberChainIfNeeded(Expr *E);
1122+
10951123
/// Whether the given expression "looks like" a (possibly sugared) type. For
10961124
/// example, `(foo, bar)` "looks like" a type, but `foo + bar` does not.
10971125
bool exprLooksLikeAType(Expr *expr);
@@ -1464,24 +1492,8 @@ class PreCheckTarget final : public ASTWalker {
14641492
return Action::Continue(OEE);
14651493
}
14661494

1467-
auto *parent = Parent.getAsExpr();
1468-
if (isMemberChainTail(expr, parent)) {
1469-
Expr *wrapped = expr;
1470-
// If we find an unresolved member chain, wrap it in an
1471-
// UnresolvedMemberChainResultExpr (unless this has already been done).
1472-
if (auto *UME = TypeChecker::getUnresolvedMemberChainBase(expr)) {
1473-
if (!parent || !isa<UnresolvedMemberChainResultExpr>(parent)) {
1474-
wrapped = new (ctx) UnresolvedMemberChainResultExpr(expr, UME);
1475-
}
1476-
}
1477-
// Wrap optional chain in an OptionalEvaluationExpr.
1478-
if (isBindOptionalMemberChain(expr)) {
1479-
if (!parent || !isa<OptionalEvaluationExpr>(parent)) {
1480-
wrapped = new (ctx) OptionalEvaluationExpr(wrapped);
1481-
}
1482-
}
1483-
expr = wrapped;
1484-
}
1495+
expr = wrapMemberChainIfNeeded(expr);
1496+
14851497
return Action::Continue(expr);
14861498
}
14871499

@@ -2653,7 +2665,6 @@ Expr *PreCheckTarget::simplifyTypeConstructionWithLiteralArg(Expr *E) {
26532665
///
26542666
/// foo? = newFoo // LHS of the assignment operator
26552667
/// foo?.bar += value // LHS of 'assignment: true' precedence group operators.
2656-
/// for?.bar++ // Postfix operator.
26572668
///
26582669
/// In such cases, the operand is constructed to be an 'OperatorEvaluationExpr'
26592670
/// wrapping the actual operand. This function hoist it and wraps the entire
@@ -2678,16 +2689,34 @@ PreCheckTarget::hoistOptionalEvaluationExprIfNeeded(Expr *expr) {
26782689
}
26792690
}
26802691
}
2681-
} else if (auto *postfixE = dyn_cast<PostfixUnaryExpr>(expr)) {
2682-
if (auto *OEE = dyn_cast<OptionalEvaluationExpr>(postfixE->getOperand())) {
2683-
postfixE->setOperand(OEE->getSubExpr());
2684-
OEE->setSubExpr(postfixE);
2685-
return OEE;
2686-
}
26872692
}
26882693
return nullptr;
26892694
}
26902695

2696+
Expr *PreCheckTarget::wrapMemberChainIfNeeded(Expr *E) {
2697+
auto *parent = Parent.getAsExpr();
2698+
Expr *wrapped = E;
2699+
2700+
// If the parent is already wrapped, we've already formed the member chain.
2701+
if (parent && (isa<OptionalEvaluationExpr>(parent) ||
2702+
isa<UnresolvedMemberChainResultExpr>(parent))) {
2703+
return E;
2704+
}
2705+
2706+
// If we find an unresolved member chain, wrap it in an
2707+
// UnresolvedMemberChainResultExpr.
2708+
if (isMemberChainTail(E, parent, MemberChainKind::UnresolvedMember)) {
2709+
if (auto *UME = TypeChecker::getUnresolvedMemberChainBase(E))
2710+
wrapped = new (Ctx) UnresolvedMemberChainResultExpr(E, UME);
2711+
}
2712+
// Wrap optional chain in an OptionalEvaluationExpr.
2713+
if (isMemberChainTail(E, parent, MemberChainKind::OptionalBind)) {
2714+
if (isBindOptionalMemberChain(E))
2715+
wrapped = new (Ctx) OptionalEvaluationExpr(wrapped);
2716+
}
2717+
return wrapped;
2718+
}
2719+
26912720
bool ConstraintSystem::preCheckTarget(SyntacticElementTarget &target) {
26922721
auto *DC = target.getDeclContext();
26932722
auto &ctx = DC->getASTContext();

test/SILGen/member_chains.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// RUN: %target-swift-emit-silgen -verify %s
2+
3+
struct ImplicitMembers: Equatable {
4+
static var implicit = ImplicitMembers()
5+
6+
static var optional: ImplicitMembers? = ImplicitMembers()
7+
static func createOptional() -> ImplicitMembers? {
8+
ImplicitMembers()
9+
}
10+
11+
var another: ImplicitMembers { ImplicitMembers() }
12+
var anotherOptional: ImplicitMembers? { ImplicitMembers() }
13+
}
14+
15+
// Make sure we can SILGen these without issue.
16+
17+
postfix operator ^
18+
postfix func ^ (_ lhs: ImplicitMembers) -> Int { 0 }
19+
20+
extension Int {
21+
func foo() {}
22+
var optionalMember: Int? { 0 }
23+
}
24+
25+
// https://github.com/swiftlang/swift/issues/80265
26+
// Make sure optional chaining looks through postfix operators.
27+
var x: ImplicitMembers?
28+
let _ = x?^.foo()
29+
let _ = x?^.optionalMember?.foo()
30+
let _ = x?.another^.optionalMember?.foo()
31+
32+
// Make sure the unresolved member chain extends up to the postfix operator,
33+
// but the optional chain covers the entire expr.
34+
let _ = .optional?^.foo()
35+
let _ = .createOptional()?^.foo()
36+
let _ = .implicit.anotherOptional?^.foo()

0 commit comments

Comments
 (0)