Skip to content

Commit 78e2e93

Browse files
committed
[Diagnostics] Add a diagnostic for invalid mutating member ref on r-value base
1 parent 1e8ae99 commit 78e2e93

File tree

3 files changed

+72
-18
lines changed

3 files changed

+72
-18
lines changed

lib/Sema/CSDiagnostics.cpp

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ Expr *FailureDiagnostic::getArgumentExprFor(Expr *anchor) const {
105105
return nullptr;
106106
}
107107

108+
Expr *FailureDiagnostic::getBaseExprFor(Expr *anchor) const {
109+
if (!anchor)
110+
return nullptr;
111+
112+
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(anchor))
113+
return UDE->getBase();
114+
else if (auto *SE = dyn_cast<SubscriptExpr>(anchor))
115+
return SE->getBase();
116+
else if (auto *MRE = dyn_cast<MemberRefExpr>(anchor))
117+
return MRE->getBase();
118+
119+
return nullptr;
120+
}
121+
108122
Optional<SelectedOverload> FailureDiagnostic::getChoiceFor(Expr *expr) const {
109123
auto &cs = getConstraintSystem();
110124
return getOverloadChoiceIfAvailable(cs.getCalleeLocator(expr));
@@ -1484,13 +1498,11 @@ bool AssignmentFailure::diagnoseAsError() {
14841498
}
14851499

14861500
if (auto IE = dyn_cast<IfExpr>(immInfo.first)) {
1487-
if (isLoadedLValue(IE)) {
1488-
emitDiagnostic(Loc, DeclDiagnostic,
1489-
"result of conditional operator '? :' is never mutable")
1490-
.highlight(IE->getQuestionLoc())
1491-
.highlight(IE->getColonLoc());
1492-
return true;
1493-
}
1501+
emitDiagnostic(Loc, DeclDiagnostic,
1502+
"result of conditional operator '? :' is never mutable")
1503+
.highlight(IE->getQuestionLoc())
1504+
.highlight(IE->getColonLoc());
1505+
return true;
14941506
}
14951507

14961508
emitDiagnostic(Loc, TypeDiagnostic, getType(destExpr))
@@ -3459,3 +3471,25 @@ bool SkipUnhandledConstructInFunctionBuilderFailure::diagnoseAsNote() {
34593471
diagnosePrimary(/*asNote=*/true);
34603472
return true;
34613473
}
3474+
3475+
bool MutatingMemberRefOnImmutableBase::diagnoseAsError() {
3476+
auto *anchor = getRawAnchor();
3477+
auto baseExpr = getBaseExprFor(anchor);
3478+
if (!baseExpr)
3479+
return false;
3480+
3481+
auto diagIDsubelt = diag::cannot_pass_rvalue_mutating_subelement;
3482+
auto diagIDmember = diag::cannot_pass_rvalue_mutating;
3483+
3484+
if (auto *storage = dyn_cast<AbstractStorageDecl>(Member)) {
3485+
if (storage->isGetterMutating()) {
3486+
diagIDsubelt = diag::cannot_pass_rvalue_mutating_getter_subelement;
3487+
diagIDmember = diag::cannot_pass_rvalue_mutating_getter;
3488+
}
3489+
}
3490+
3491+
auto &cs = getConstraintSystem();
3492+
AssignmentFailure failure(baseExpr, cs, anchor->getLoc(), diagIDsubelt,
3493+
diagIDmember);
3494+
return failure.diagnoseAsError();
3495+
}

lib/Sema/CSDiagnostics.h

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ class FailureDiagnostic {
168168
/// in the root expression or `nullptr` otherwise.
169169
Expr *findParentExpr(Expr *subExpr) const;
170170

171+
/// If given expression is some kind of a member reference e.g.
172+
/// `x.foo` or `x[0]` extract and return its base expression.
173+
Expr *getBaseExprFor(Expr *anchor) const;
174+
171175
/// \returns An argument expression if given anchor is a call, member
172176
/// reference or subscript, nullptr otherwise.
173177
Expr *getArgumentExprFor(Expr *anchor) const;
@@ -708,16 +712,6 @@ class AssignmentFailure final : public FailureDiagnostic {
708712

709713
static Diag<StringRef> findDeclDiagonstic(ASTContext &ctx, Expr *destExpr);
710714

711-
static bool isLoadedLValue(Expr *expr) {
712-
expr = expr->getSemanticsProvidingExpr();
713-
if (isa<LoadExpr>(expr))
714-
return true;
715-
if (auto ifExpr = dyn_cast<IfExpr>(expr))
716-
return isLoadedLValue(ifExpr->getThenExpr()) &&
717-
isLoadedLValue(ifExpr->getElseExpr());
718-
return false;
719-
}
720-
721715
/// Retrive an member reference associated with given member
722716
/// looking through dynamic member lookup on the way.
723717
Optional<OverloadChoice> getMemberRef(ConstraintLocator *locator) const;
@@ -1152,6 +1146,30 @@ class InaccessibleMemberFailure final : public FailureDiagnostic {
11521146
bool diagnoseAsError() override;
11531147
};
11541148

1149+
/// Diagnose an attempt to reference member marked as `mutating`
1150+
/// on immutable base e.g. `let` variable:
1151+
///
1152+
/// ```swift
1153+
/// struct S {
1154+
/// mutating func foo(_ i: Int) {}
1155+
/// func foo(_ f: Float) {}
1156+
/// }
1157+
///
1158+
/// func bar(_ s: S, _ answer: Int) {
1159+
/// s.foo(answer)
1160+
/// }
1161+
/// ```
1162+
class MutatingMemberRefOnImmutableBase final : public FailureDiagnostic {
1163+
ValueDecl *Member;
1164+
1165+
public:
1166+
MutatingMemberRefOnImmutableBase(Expr *root, ConstraintSystem &cs,
1167+
ValueDecl *member,
1168+
ConstraintLocator *locator)
1169+
: FailureDiagnostic(root, cs, locator), Member(member) {}
1170+
1171+
bool diagnoseAsError() override;
1172+
};
11551173

11561174
// Diagnose an attempt to use AnyObject as the root type of a KeyPath
11571175
//

lib/Sema/CSFix.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,9 @@ bool SkipUnhandledConstructInFunctionBuilder::diagnose(Expr *root,
645645
}
646646

647647
bool AllowMutatingMemberOrRValueBase::diagnose(Expr *root, bool asNote) const {
648-
return false;
648+
auto &cs = getConstraintSystem();
649+
MutatingMemberRefOnImmutableBase failure(root, cs, getMember(), getLocator());
650+
return failure.diagnose(asNote);
649651
}
650652

651653
AllowMutatingMemberOrRValueBase *

0 commit comments

Comments
 (0)