Skip to content

Commit 21e8a32

Browse files
committed
[CS] Better handle paren fix-its for unresolved chains
I missed this case when previously improving the logic here. As it turns out, using the raw anchor as the root expression from which to derive parent information is insufficient. This is because it may not capture relevant parent exprs not a part of the fix locator. Instead, pass down a function that can be used to derive the parent expressions from the constraint system's own parent map. Also make sure to assign to `expr` for the UnresolvedMemberChainResultExpr case to make sure we correctly check for it as a sub-expression. Finally, now that we're looking at more parent exprs, add logic to handle `try` and `await` parents, as well as ClosureExprs and CollectionExprs. I couldn't come up with a test case for CollectionExpr, as we emit different diagnostics in that case, but it's probably better to tend on the side of being more future proof there. rdar://81512079
1 parent d66562c commit 21e8a32

File tree

5 files changed

+83
-46
lines changed

5 files changed

+83
-46
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5773,9 +5773,9 @@ bool exprNeedsParensBeforeAddingNilCoalescing(DeclContext *DC,
57735773
// Return true if, when replacing "<expr>" with "<expr> as T", parentheses need
57745774
// to be added around the new expression in order to maintain the correct
57755775
// precedence.
5776-
bool exprNeedsParensAfterAddingNilCoalescing(DeclContext *DC,
5777-
Expr *expr,
5778-
Expr *rootExpr);
5776+
bool exprNeedsParensAfterAddingNilCoalescing(
5777+
DeclContext *DC, Expr *expr,
5778+
llvm::function_ref<Expr *(const Expr *)> getParent);
57795779

57805780
/// Return true if, when replacing "<expr>" with "<expr> op <something>",
57815781
/// parentheses must be added around "<expr>" to allow the new operator
@@ -5784,13 +5784,12 @@ bool exprNeedsParensInsideFollowingOperator(DeclContext *DC,
57845784
Expr *expr,
57855785
PrecedenceGroupDecl *followingPG);
57865786

5787-
/// Return true if, when replacing "<expr>" with "<expr> op <something>"
5788-
/// within the given root expression, parentheses must be added around
5789-
/// the new operator to prevent it from binding incorrectly in the
5790-
/// surrounding context.
5787+
/// Return true if, when replacing "<expr>" with "<expr> op <something>",
5788+
/// parentheses must be added around the new operator to prevent it from binding
5789+
/// incorrectly in the surrounding context.
57915790
bool exprNeedsParensOutsideFollowingOperator(
5792-
DeclContext *DC, Expr *expr, Expr *rootExpr,
5793-
PrecedenceGroupDecl *followingPG);
5791+
DeclContext *DC, Expr *expr, PrecedenceGroupDecl *followingPG,
5792+
llvm::function_ref<Expr *(const Expr *)> getParent);
57945793

57955794
/// Determine whether this is a SIMD operator.
57965795
bool isSIMDOperator(ValueDecl *value);

lib/Sema/CSApply.cpp

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7908,23 +7908,21 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
79087908
// nearest ancestor of 'expr' which imposes a minimum precedence on 'expr'.
79097909
// Right now that just means skipping over TupleExpr instances that only exist
79107910
// to hold arguments to binary operators.
7911-
static std::pair<Expr *, unsigned> getPrecedenceParentAndIndex(Expr *expr,
7912-
Expr *rootExpr)
7913-
{
7914-
auto parentMap = rootExpr->getParentMap();
7915-
auto it = parentMap.find(expr);
7916-
if (it == parentMap.end()) {
7911+
static std::pair<Expr *, unsigned> getPrecedenceParentAndIndex(
7912+
Expr *expr, llvm::function_ref<Expr *(const Expr *)> getParent) {
7913+
auto *parent = getParent(expr);
7914+
if (!parent)
79177915
return { nullptr, 0 };
7918-
}
7919-
Expr *parent = it->second;
7920-
7921-
// Look through an unresolved chain wrapper expr, as it has no effect on
7922-
// precedence.
7923-
if (isa<UnresolvedMemberChainResultExpr>(parent)) {
7924-
it = parentMap.find(parent);
7925-
if (it == parentMap.end())
7926-
return {nullptr, 0};
7927-
parent = it->second;
7916+
7917+
// Look through an unresolved chain wrappers, try, and await exprs, as they
7918+
// have no effect on precedence; they will associate the same with any parent
7919+
// operator as their sub-expression would.
7920+
while (isa<UnresolvedMemberChainResultExpr>(parent) ||
7921+
isa<AnyTryExpr>(parent) || isa<AwaitExpr>(parent)) {
7922+
expr = parent;
7923+
parent = getParent(parent);
7924+
if (!parent)
7925+
return { nullptr, 0 };
79287926
}
79297927

79307928
// Handle all cases where the answer isn't just going to be { parent, 0 }.
@@ -7935,18 +7933,14 @@ static std::pair<Expr *, unsigned> getPrecedenceParentAndIndex(Expr *expr,
79357933
assert(elemIt != tupleElems.end() && "expr not found in parent TupleExpr");
79367934
unsigned index = elemIt - tupleElems.begin();
79377935

7938-
it = parentMap.find(parent);
7939-
if (it != parentMap.end()) {
7940-
Expr *gparent = it->second;
7941-
7942-
// Was this tuple just constructed for a binop?
7943-
if (isa<BinaryExpr>(gparent)) {
7936+
// Was this tuple just constructed for a binop?
7937+
if (auto *gparent = getParent(tuple)) {
7938+
if (isa<BinaryExpr>(gparent))
79447939
return { gparent, index };
7945-
}
79467940
}
79477941

79487942
// Must be a tuple literal, function arg list, collection, etc.
7949-
return { parent, index };
7943+
return { tuple, index };
79507944
} else if (auto ifExpr = dyn_cast<IfExpr>(parent)) {
79517945
unsigned index;
79527946
if (expr == ifExpr->getCondExpr()) {
@@ -8001,11 +7995,11 @@ bool swift::exprNeedsParensInsideFollowingOperator(
80017995
/// the new operator to prevent it from binding incorrectly in the
80027996
/// surrounding context.
80037997
bool swift::exprNeedsParensOutsideFollowingOperator(
8004-
DeclContext *DC, Expr *expr, Expr *rootExpr,
8005-
PrecedenceGroupDecl *followingPG) {
7998+
DeclContext *DC, Expr *expr, PrecedenceGroupDecl *followingPG,
7999+
llvm::function_ref<Expr *(const Expr *)> getParent) {
80068000
Expr *parent;
80078001
unsigned index;
8008-
std::tie(parent, index) = getPrecedenceParentAndIndex(expr, rootExpr);
8002+
std::tie(parent, index) = getPrecedenceParentAndIndex(expr, getParent);
80098003
if (!parent)
80108004
return false;
80118005

@@ -8014,6 +8008,9 @@ bool swift::exprNeedsParensOutsideFollowingOperator(
80148008
return false;
80158009
}
80168010

8011+
if (isa<ClosureExpr>(parent) || isa<CollectionExpr>(parent))
8012+
return false;
8013+
80178014
if (parent->isInfixOperator()) {
80188015
auto parentPG = TypeChecker::lookupPrecedenceGroupForInfixOperator(DC,
80198016
parent);
@@ -8044,16 +8041,16 @@ bool swift::exprNeedsParensBeforeAddingNilCoalescing(DeclContext *DC,
80448041
return exprNeedsParensInsideFollowingOperator(DC, expr, asPG);
80458042
}
80468043

8047-
bool swift::exprNeedsParensAfterAddingNilCoalescing(DeclContext *DC,
8048-
Expr *expr,
8049-
Expr *rootExpr) {
8044+
bool swift::exprNeedsParensAfterAddingNilCoalescing(
8045+
DeclContext *DC, Expr *expr,
8046+
llvm::function_ref<Expr *(const Expr *)> getParent) {
80508047
auto &ctx = DC->getASTContext();
80518048
auto asPG = TypeChecker::lookupPrecedenceGroup(
80528049
DC, ctx.Id_NilCoalescingPrecedence, SourceLoc())
80538050
.getSingle();
80548051
if (!asPG)
80558052
return true;
8056-
return exprNeedsParensOutsideFollowingOperator(DC, expr, rootExpr, asPG);
8053+
return exprNeedsParensOutsideFollowingOperator(DC, expr, asPG, getParent);
80578054
}
80588055

80598056
namespace {

lib/Sema/CSDiagnostics.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,7 +1158,6 @@ ASTNode MissingExplicitConversionFailure::getAnchor() const {
11581158
bool MissingExplicitConversionFailure::diagnoseAsError() {
11591159
auto *DC = getDC();
11601160
auto *anchor = castToExpr(getAnchor());
1161-
auto *rawAnchor = castToExpr(getRawAnchor());
11621161

11631162
auto fromType = getFromType();
11641163
auto toType = getToType();
@@ -1184,7 +1183,7 @@ bool MissingExplicitConversionFailure::diagnoseAsError() {
11841183
}
11851184

11861185
bool needsParensInside = exprNeedsParensBeforeAddingAs(anchor);
1187-
bool needsParensOutside = exprNeedsParensAfterAddingAs(anchor, rawAnchor);
1186+
bool needsParensOutside = exprNeedsParensAfterAddingAs(anchor);
11881187

11891188
llvm::SmallString<2> insertBefore;
11901189
llvm::SmallString<32> insertAfter;
@@ -1324,7 +1323,7 @@ void MissingOptionalUnwrapFailure::offerDefaultValueUnwrapFixIt(
13241323
bool needsParensInside =
13251324
exprNeedsParensBeforeAddingNilCoalescing(DC, const_cast<Expr *>(expr));
13261325
bool needsParensOutside = exprNeedsParensAfterAddingNilCoalescing(
1327-
DC, const_cast<Expr *>(expr), castToExpr(getRawAnchor()));
1326+
DC, const_cast<Expr *>(expr), [&](auto *E) { return findParentExpr(E); });
13281327

13291328
llvm::SmallString<2> insertBefore;
13301329
llvm::SmallString<32> insertAfter;

lib/Sema/CSDiagnostics.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -903,15 +903,16 @@ class MissingExplicitConversionFailure final : public ContextualFailure {
903903
asPG);
904904
}
905905

906-
bool exprNeedsParensAfterAddingAs(const Expr *expr, const Expr *rootExpr) {
906+
bool exprNeedsParensAfterAddingAs(const Expr *expr) {
907907
auto *DC = getDC();
908908
auto asPG = TypeChecker::lookupPrecedenceGroup(
909909
DC, DC->getASTContext().Id_CastingPrecedence, SourceLoc()).getSingle();
910910
if (!asPG)
911911
return true;
912912

913913
return exprNeedsParensOutsideFollowingOperator(
914-
DC, const_cast<Expr *>(expr), const_cast<Expr *>(rootExpr), asPG);
914+
DC, const_cast<Expr *>(expr), asPG,
915+
[&](auto *E) { return findParentExpr(E); });
915916
}
916917
};
917918

test/Constraints/diagnostics.swift

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1416,7 +1416,12 @@ func rdar74696023() {
14161416
}
14171417
}
14181418

1419-
func testUnwrapFixIts(x: Int?) {
1419+
extension Int {
1420+
static var optionalIntMember: Int? { 0 }
1421+
static var optionalThrowsMember: Int? { get throws { 0 } }
1422+
}
1423+
1424+
func testUnwrapFixIts(x: Int?) throws {
14201425
let _ = x + 2 // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
14211426
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{11-11=(}} {{12-12= ?? <#default value#>)}}
14221427
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{12-12=!}}
@@ -1442,4 +1447,40 @@ func testUnwrapFixIts(x: Int?) {
14421447
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{16-16= ?? <#default value#>}}
14431448
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{16-16=!}}
14441449
let _ = 2 < x ?? 0
1450+
1451+
let _: Int = (.optionalIntMember) // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1452+
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{35-35= ?? <#default value#>}}
1453+
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{35-35=!}}
1454+
let _: Int = (.optionalIntMember ?? 0)
1455+
1456+
let _ = 1 + .optionalIntMember // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1457+
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{15-15=(}} {{33-33= ?? <#default value#>)}}
1458+
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{33-33=!}}
1459+
let _ = 1 + (.optionalIntMember ?? 0)
1460+
1461+
let _ = try .optionalThrowsMember + 1 // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1462+
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{15-15=(}} {{36-36= ?? <#default value#>)}}
1463+
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{36-36=!}}
1464+
let _ = try (.optionalThrowsMember ?? 0) + 1
1465+
1466+
let _ = .optionalIntMember?.bitWidth > 0 // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1467+
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{39-39= ?? <#default value#>}}
1468+
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{11-11=(}} {{39-39=)!}}
1469+
let _ = (.optionalIntMember?.bitWidth)! > 0
1470+
let _ = .optionalIntMember?.bitWidth ?? 0 > 0
1471+
1472+
let _ = .random() ? .optionalIntMember : 0 // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1473+
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{41-41= ?? <#default value#>}}
1474+
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{41-41=!}}
1475+
let _ = .random() ? .optionalIntMember ?? 0 : 0
1476+
1477+
let _: Int = try try try .optionalThrowsMember // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1478+
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{49-49= ?? <#default value#>}}
1479+
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{49-49=!}}
1480+
let _: Int = try try try .optionalThrowsMember ?? 0
1481+
1482+
let _: Int = try! .optionalThrowsMember // expected-error {{value of optional type 'Int?' must be unwrapped to a value of type 'Int'}}
1483+
// expected-note@-1 {{coalesce using '??' to provide a default when the optional value contains 'nil'}} {{42-42= ?? <#default value#>}}
1484+
// expected-note@-2 {{force-unwrap using '!' to abort execution if the optional value contains 'nil'}} {{42-42=!}}
1485+
let _: Int = try! .optionalThrowsMember ?? 0
14451486
}

0 commit comments

Comments
 (0)