Skip to content

Commit 2707a9c

Browse files
committed
[Diagnostics] Improve diagnostics of overloaded mutating methods
Add special logic to FailureDiagnosis::visitApplyExpr to handle situation like following: struct S { mutating func f(_ i: Int) {} func f(_ f: Float) {} } Given struct has an overloaded method "f" with a single argument of multiple different types, one of the overloads is marked as "mutating", which means it can only be applied on LValue base type. So when struct is used like this: let answer: Int = 42 S().f(answer) Constraint system generator is going to pick `f(_ f: Float)` as only possible overload candidate because "base" of the call is immutable and contextual information about argument type is not available yet. Such leads to incorrect contextual conversion failure diagnostic because type of the argument is going to resolved as (Int) no matter what. To workaround that fact and improve diagnostic of such cases we are going to try and collect all unviable candidates for a given call and check if at least one of them matches established argument type before even trying to re-check argument expression. Resolves: <rdar://problem/28051973>.
1 parent b797411 commit 2707a9c

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2065,6 +2065,13 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
20652065
CalleeCandidateInfo &calleeInfo,
20662066
SourceLoc applyLoc);
20672067

2068+
/// Produce diagnostic for failures related to attributes associated with
2069+
/// candidate functions/methods e.g. mutability.
2070+
bool diagnoseMethodAttributeFailures(ApplyExpr *expr,
2071+
ArrayRef<Identifier> argLabels,
2072+
bool hasTrailingClosure,
2073+
CalleeCandidateInfo &candidates);
2074+
20682075
bool visitExpr(Expr *E);
20692076
bool visitIdentityExpr(IdentityExpr *E);
20702077
bool visitTryExpr(TryExpr *E);
@@ -5272,6 +5279,87 @@ bool FailureDiagnosis::diagnoseNilLiteralComparison(
52725279
return true;
52735280
}
52745281

5282+
bool FailureDiagnosis::diagnoseMethodAttributeFailures(
5283+
swift::ApplyExpr *callExpr, ArrayRef<Identifier> argLabels,
5284+
bool hasTrailingClosure, CalleeCandidateInfo &candidates) {
5285+
auto UDE = dyn_cast<UnresolvedDotExpr>(callExpr->getFn());
5286+
if (!UDE)
5287+
return false;
5288+
5289+
auto argExpr = callExpr->getArg();
5290+
auto argType = argExpr->getType();
5291+
5292+
// If type of the argument hasn't been established yet, we can't diagnose.
5293+
if (!argType || isUnresolvedOrTypeVarType(argType))
5294+
return false;
5295+
5296+
// Let's filter our candidate list based on that type.
5297+
candidates.filterList(argType, argLabels);
5298+
5299+
if (candidates.closeness == CC_ExactMatch)
5300+
return false;
5301+
5302+
// And if filtering didn't give an exact match, such means that problem
5303+
// might be related to function attributes which is best diagnosed by
5304+
// unviable member candidates, if any.
5305+
auto base = UDE->getBase();
5306+
auto baseType = base->getType();
5307+
5308+
// This handles following situation:
5309+
// struct S {
5310+
// mutating func f(_ i: Int) {}
5311+
// func f(_ f: Float) {}
5312+
// }
5313+
//
5314+
// Given struct has an overloaded method "f" with a single argument of
5315+
// multiple different types, one of the overloads is marked as
5316+
// "mutating", which means it can only be applied on LValue base type.
5317+
// So when struct is used like this:
5318+
//
5319+
// let answer: Int = 42
5320+
// S().f(answer)
5321+
//
5322+
// Constraint system generator is going to pick `f(_ f: Float)` as
5323+
// only possible overload candidate because "base" of the call is immutable
5324+
// and contextual information about argument type is not available yet.
5325+
// Such leads to incorrect contextual conversion failure diagnostic because
5326+
// type of the argument is going to resolved as (Int) no matter what.
5327+
// To workaround that fact and improve diagnostic of such cases we are going
5328+
// to try and collect all unviable candidates for a given call and check if
5329+
// at least one of them matches established argument type before even trying
5330+
// to re-check argument expression.
5331+
auto results = CS->performMemberLookup(
5332+
ConstraintKind::ValueMember, UDE->getName(), baseType,
5333+
UDE->getFunctionRefKind(), CS->getConstraintLocator(UDE),
5334+
/*includeInaccessibleMembers=*/false);
5335+
5336+
if (results.UnviableCandidates.empty())
5337+
return false;
5338+
5339+
SmallVector<OverloadChoice, 2> choices;
5340+
for (auto &unviable : results.UnviableCandidates)
5341+
choices.push_back(OverloadChoice(baseType, unviable.first,
5342+
/*isSpecialized=*/false,
5343+
UDE->getFunctionRefKind()));
5344+
5345+
CalleeCandidateInfo unviableCandidates(baseType, choices, hasTrailingClosure,
5346+
CS);
5347+
5348+
// Filter list of the unviable candidates based on the
5349+
// already established type of the argument expression.
5350+
unviableCandidates.filterList(argType, argLabels);
5351+
5352+
// If one of the unviable candidates matches arguments exactly,
5353+
// that means that actual problem is related to function attributes.
5354+
if (unviableCandidates.closeness == CC_ExactMatch) {
5355+
diagnoseUnviableLookupResults(results, baseType, base, UDE->getName(),
5356+
UDE->getNameLoc(), UDE->getLoc());
5357+
return true;
5358+
}
5359+
5360+
return false;
5361+
}
5362+
52755363
/// When initializing Unsafe[Mutable]Pointer<T> from Unsafe[Mutable]RawPointer,
52765364
/// issue a diagnostic that refers to the API for binding memory to a type.
52775365
static bool isCastToTypedPointer(ASTContext &Ctx, const Expr *Fn,
@@ -5445,7 +5533,15 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
54455533
if (diagnoseParameterErrors(calleeInfo, callExpr->getFn(),
54465534
callExpr->getArg(), argLabels))
54475535
return true;
5448-
5536+
5537+
// There might be a candidate with correct argument types but it's not
5538+
// used by constraint solver because it doesn't have correct attributes,
5539+
// let's try to diagnose such situation there right before type checking
5540+
// argument expression, because that would overwrite original argument types.
5541+
if (diagnoseMethodAttributeFailures(callExpr, argLabels, hasTrailingClosure,
5542+
calleeInfo))
5543+
return true;
5544+
54495545
Type argType; // Type of the argument list, if knowable.
54505546
if (auto FTy = fnType->getAs<AnyFunctionType>())
54515547
argType = FTy->getInput();

test/Constraints/overload.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,13 @@ struct X2 {
165165

166166
let x2 = X2(Int.self)
167167
let x2check: X2 = x2 // expected-error{{value of optional type 'X2?' not unwrapped; did you mean to use '!' or '?'?}}
168+
169+
// rdar://problem/28051973
170+
struct R_28051973 {
171+
mutating func f(_ i: Int) {}
172+
@available(*, deprecated, message: "deprecated")
173+
func f(_ f: Float) {}
174+
}
175+
176+
let r28051973: Int = 42
177+
R_28051973().f(r28051973) // expected-error {{cannot use mutating member on immutable value: function call returns immutable value}}

0 commit comments

Comments
 (0)