Skip to content

Commit 81a69f3

Browse files
authored
Merge pull request #26014 from xedin/diagnose-mutable-methods
[Diagnostics] Port invalid reference to `mutating` member diagnostics to new framework
2 parents 659c497 + 0316bb1 commit 81a69f3

File tree

6 files changed

+160
-148
lines changed

6 files changed

+160
-148
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 2 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,6 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
469469
CalleeCandidateInfo &calleeInfo,
470470
SourceLoc applyLoc);
471471

472-
/// Produce diagnostic for failures related to attributes associated with
473-
/// candidate functions/methods e.g. mutability.
474-
bool diagnoseMethodAttributeFailures(ApplyExpr *expr,
475-
ArrayRef<Identifier> argLabels,
476-
bool hasTrailingClosure,
477-
CalleeCandidateInfo &candidates);
478-
479472
/// Produce diagnostic for failures related to unfulfilled requirements
480473
/// of the generic parameters used as arguments.
481474
bool diagnoseArgumentGenericRequirements(TypeChecker &TC, Expr *callExpr,
@@ -777,14 +770,8 @@ void FailureDiagnosis::diagnoseUnviableLookupResults(
777770
}
778771
case MemberLookupResult::UR_MutatingMemberOnRValue:
779772
case MemberLookupResult::UR_MutatingGetterOnRValue: {
780-
auto diagIDsubelt = diag::cannot_pass_rvalue_mutating_subelement;
781-
auto diagIDmember = diag::cannot_pass_rvalue_mutating;
782-
if (firstProblem == MemberLookupResult::UR_MutatingGetterOnRValue) {
783-
diagIDsubelt = diag::cannot_pass_rvalue_mutating_getter_subelement;
784-
diagIDmember = diag::cannot_pass_rvalue_mutating_getter;
785-
}
786-
assert(baseExpr && "Cannot have a mutation failure without a base");
787-
AssignmentFailure failure(baseExpr, CS, loc, diagIDsubelt, diagIDmember);
773+
MutatingMemberRefOnImmutableBase failure(E, CS, choice->getDecl(),
774+
CS.getConstraintLocator(E));
788775
(void)failure.diagnose();
789776
return;
790777
}
@@ -3979,86 +3966,6 @@ bool FailureDiagnosis::diagnoseNilLiteralComparison(
39793966
return true;
39803967
}
39813968

3982-
bool FailureDiagnosis::diagnoseMethodAttributeFailures(
3983-
swift::ApplyExpr *callExpr, ArrayRef<Identifier> argLabels,
3984-
bool hasTrailingClosure, CalleeCandidateInfo &candidates) {
3985-
auto UDE = dyn_cast<UnresolvedDotExpr>(callExpr->getFn());
3986-
if (!UDE)
3987-
return false;
3988-
3989-
auto argExpr = callExpr->getArg();
3990-
auto argType = CS.getType(argExpr);
3991-
3992-
// If type of the argument hasn't been established yet, we can't diagnose.
3993-
if (!argType || isUnresolvedOrTypeVarType(argType))
3994-
return false;
3995-
3996-
// Let's filter our candidate list based on that type.
3997-
candidates.filterListArgs(decomposeArgType(argType, argLabels));
3998-
3999-
if (candidates.closeness == CC_ExactMatch)
4000-
return false;
4001-
4002-
// And if filtering didn't give an exact match, such means that problem
4003-
// might be related to function attributes which is best diagnosed by
4004-
// unviable member candidates, if any.
4005-
auto base = UDE->getBase();
4006-
auto baseType = CS.getType(base);
4007-
4008-
// This handles following situation:
4009-
// struct S {
4010-
// mutating func f(_ i: Int) {}
4011-
// func f(_ f: Float) {}
4012-
// }
4013-
//
4014-
// Given struct has an overloaded method "f" with a single argument of
4015-
// multiple different types, one of the overloads is marked as
4016-
// "mutating", which means it can only be applied on LValue base type.
4017-
// So when struct is used like this:
4018-
//
4019-
// let answer: Int = 42
4020-
// S().f(answer)
4021-
//
4022-
// Constraint system generator is going to pick `f(_ f: Float)` as
4023-
// only possible overload candidate because "base" of the call is immutable
4024-
// and contextual information about argument type is not available yet.
4025-
// Such leads to incorrect contextual conversion failure diagnostic because
4026-
// type of the argument is going to resolved as (Int) no matter what.
4027-
// To workaround that fact and improve diagnostic of such cases we are going
4028-
// to try and collect all unviable candidates for a given call and check if
4029-
// at least one of them matches established argument type before even trying
4030-
// to re-check argument expression.
4031-
auto results = CS.performMemberLookup(
4032-
ConstraintKind::ValueMember, UDE->getName(), baseType,
4033-
UDE->getFunctionRefKind(), CS.getConstraintLocator(UDE),
4034-
/*includeInaccessibleMembers=*/false);
4035-
4036-
if (results.UnviableCandidates.empty())
4037-
return false;
4038-
4039-
SmallVector<OverloadChoice, 2> choices;
4040-
for (auto &unviable : results.UnviableCandidates)
4041-
choices.push_back(OverloadChoice(baseType, unviable.getDecl(),
4042-
UDE->getFunctionRefKind()));
4043-
4044-
CalleeCandidateInfo unviableCandidates(baseType, choices, hasTrailingClosure,
4045-
CS);
4046-
4047-
// Filter list of the unviable candidates based on the
4048-
// already established type of the argument expression.
4049-
unviableCandidates.filterListArgs(decomposeArgType(argType, argLabels));
4050-
4051-
// If one of the unviable candidates matches arguments exactly,
4052-
// that means that actual problem is related to function attributes.
4053-
if (unviableCandidates.closeness == CC_ExactMatch) {
4054-
diagnoseUnviableLookupResults(results, UDE, baseType, base, UDE->getName(),
4055-
UDE->getNameLoc(), UDE->getLoc());
4056-
return true;
4057-
}
4058-
4059-
return false;
4060-
}
4061-
40623969
bool FailureDiagnosis::diagnoseArgumentGenericRequirements(
40633970
TypeChecker &TC, Expr *callExpr, Expr *fnExpr, Expr *argExpr,
40643971
CalleeCandidateInfo &candidates, ArrayRef<Identifier> argLabels) {
@@ -4811,14 +4718,6 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
48114718
callExpr->getArg(), argLabels))
48124719
return true;
48134720

4814-
// There might be a candidate with correct argument types but it's not
4815-
// used by constraint solver because it doesn't have correct attributes,
4816-
// let's try to diagnose such situation there right before type checking
4817-
// argument expression, because that would overwrite original argument types.
4818-
if (diagnoseMethodAttributeFailures(callExpr, argLabels, hasTrailingClosure,
4819-
calleeInfo))
4820-
return true;
4821-
48224721
Type argType; // argument list, if known.
48234722
if (auto FTy = fnType->getAs<AnyFunctionType>()) {
48244723
argType = FunctionType::composeInput(CS.getASTContext(), FTy->getParams(),

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: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -342,14 +342,16 @@ AllowMemberRefOnExistential::create(ConstraintSystem &cs, Type baseType,
342342
}
343343

344344
bool AllowMemberRefOnExistential::diagnose(Expr *root, bool asNote) const {
345-
auto failure = InvalidMemberRefOnExistential(root, getConstraintSystem(),
346-
BaseType, Name, getLocator());
345+
auto failure =
346+
InvalidMemberRefOnExistential(root, getConstraintSystem(), getBaseType(),
347+
getMemberName(), getLocator());
347348
return failure.diagnose(asNote);
348349
}
349350

350351
bool AllowTypeOrInstanceMember::diagnose(Expr *root, bool asNote) const {
351352
auto failure = AllowTypeOrInstanceMemberFailure(
352-
root, getConstraintSystem(), BaseType, Member, UsedName, getLocator());
353+
root, getConstraintSystem(), getBaseType(), getMember(), getMemberName(),
354+
getLocator());
353355
return failure.diagnose(asNote);
354356
}
355357

@@ -472,15 +474,17 @@ MoveOutOfOrderArgument *MoveOutOfOrderArgument::create(
472474
}
473475

474476
bool AllowInaccessibleMember::diagnose(Expr *root, bool asNote) const {
475-
InaccessibleMemberFailure failure(root, getConstraintSystem(), Member,
477+
InaccessibleMemberFailure failure(root, getConstraintSystem(), getMember(),
476478
getLocator());
477479
return failure.diagnose(asNote);
478480
}
479481

480482
AllowInaccessibleMember *
481-
AllowInaccessibleMember::create(ConstraintSystem &cs, ValueDecl *member,
483+
AllowInaccessibleMember::create(ConstraintSystem &cs, Type baseType,
484+
ValueDecl *member, DeclName name,
482485
ConstraintLocator *locator) {
483-
return new (cs.getAllocator()) AllowInaccessibleMember(cs, member, locator);
486+
return new (cs.getAllocator())
487+
AllowInaccessibleMember(cs, baseType, member, name, locator);
484488
}
485489

486490
bool AllowAnyObjectKeyPathRoot::diagnose(Expr *root, bool asNote) const {
@@ -639,3 +643,17 @@ bool SkipUnhandledConstructInFunctionBuilder::diagnose(Expr *root,
639643
root, getConstraintSystem(), unhandled, builder, getLocator());
640644
return failure.diagnose(asNote);
641645
}
646+
647+
bool AllowMutatingMemberOnRValueBase::diagnose(Expr *root, bool asNote) const {
648+
auto &cs = getConstraintSystem();
649+
MutatingMemberRefOnImmutableBase failure(root, cs, getMember(), getLocator());
650+
return failure.diagnose(asNote);
651+
}
652+
653+
AllowMutatingMemberOnRValueBase *
654+
AllowMutatingMemberOnRValueBase::create(ConstraintSystem &cs, Type baseType,
655+
ValueDecl *member, DeclName name,
656+
ConstraintLocator *locator) {
657+
return new (cs.getAllocator())
658+
AllowMutatingMemberOnRValueBase(cs, baseType, member, name, locator);
659+
}

0 commit comments

Comments
 (0)