Skip to content

Commit 04ea485

Browse files
authored
[CSSimplify] Increase fix impact when passing closure to a non-function type parameter (#37388)
In overloaded context it's possible that there is an overload that expects a closure but it can have other issues e.g. different number of parameters, so in order to pick a better solution let's always increase a score for overloads where closure is matched against a non-function type parameter. Also a note which points out where exactly anonymous parameters are used in the multi-statement body is helpful to identify problematic and fix the issue. Resolves: rdar://76058892
1 parent 47d11f7 commit 04ea485

File tree

4 files changed

+84
-4
lines changed

4 files changed

+84
-4
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ FIXIT(insert_closure_return_type_placeholder,
279279
"%select{| () }0-> <#Result#> %select{|in }0",
280280
(bool))
281281

282+
NOTE(use_of_anon_closure_param,none,
283+
"anonymous closure parameter %0 is used here", (Identifier))
284+
282285
ERROR(incorrect_explicit_closure_result,none,
283286
"declared closure result %0 is incompatible with contextual type %1",
284287
(Type, Type))

lib/Sema/CSDiagnostics.cpp

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4983,20 +4983,69 @@ bool ExtraneousArgumentsFailure::diagnoseAsError() {
49834983
params->getStartLoc(), diag::closure_argument_list_tuple, fnType,
49844984
fnType->getNumParams(), params->size(), (params->size() == 1));
49854985

4986-
bool onlyAnonymousParams =
4986+
// Unsed parameter is represented by `_` before `in`.
4987+
bool onlyUnusedParams =
49874988
std::all_of(params->begin(), params->end(),
49884989
[](ParamDecl *param) { return !param->hasName(); });
49894990

49904991
// If closure expects no parameters but N was given,
4991-
// and all of them are anonymous let's suggest removing them.
4992-
if (fnType->getNumParams() == 0 && onlyAnonymousParams) {
4992+
// and all of them are unused, let's suggest removing them.
4993+
if (fnType->getNumParams() == 0 && onlyUnusedParams) {
49934994
auto inLoc = closure->getInLoc();
49944995
auto &sourceMgr = getASTContext().SourceMgr;
49954996

4996-
if (inLoc.isValid())
4997+
if (inLoc.isValid()) {
49974998
diag.fixItRemoveChars(params->getStartLoc(),
49984999
Lexer::getLocForEndOfToken(sourceMgr, inLoc));
5000+
return true;
5001+
}
49995002
}
5003+
5004+
diag.flush();
5005+
5006+
// If all of the parameters are anonymous, let's point out references
5007+
// to make it explicit where parameters are used in complex closure body,
5008+
// which helps in situations where braces are missing for potential inner
5009+
// closures e.g.
5010+
//
5011+
// func a(_: () -> Void) {}
5012+
// func b(_: (Int) -> Void) {}
5013+
//
5014+
// a {
5015+
// ...
5016+
// b($0.member)
5017+
// }
5018+
//
5019+
// Here `$0` is associated with `a` since braces around `member` reference
5020+
// are missing.
5021+
if (!closure->hasSingleExpressionBody() &&
5022+
llvm::all_of(params->getArray(),
5023+
[](ParamDecl *P) { return P->isAnonClosureParam(); })) {
5024+
if (auto *body = closure->getBody()) {
5025+
struct ParamRefFinder : public ASTWalker {
5026+
DiagnosticEngine &D;
5027+
ParameterList *Params;
5028+
5029+
ParamRefFinder(DiagnosticEngine &diags, ParameterList *params)
5030+
: D(diags), Params(params) {}
5031+
5032+
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
5033+
if (auto *DRE = dyn_cast<DeclRefExpr>(E)) {
5034+
if (llvm::is_contained(Params->getArray(), DRE->getDecl())) {
5035+
auto *P = cast<ParamDecl>(DRE->getDecl());
5036+
D.diagnose(DRE->getLoc(), diag::use_of_anon_closure_param,
5037+
P->getName());
5038+
}
5039+
}
5040+
return {true, E};
5041+
}
5042+
};
5043+
5044+
ParamRefFinder finder(getASTContext().Diags, params);
5045+
body->walk(finder);
5046+
}
5047+
}
5048+
50005049
return true;
50015050
}
50025051

lib/Sema/CSSimplify.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11412,6 +11412,16 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
1141211412
}))
1141311413
impact += 3;
1141411414

11415+
// Passing a closure to a parameter that doesn't expect one should
11416+
// be scored lower because there might be an overload that expects
11417+
// a closure but has other issues e.g. wrong number of parameters.
11418+
if (!type2->lookThroughAllOptionalTypes()->is<FunctionType>()) {
11419+
auto argument = simplifyLocatorToAnchor(fix->getLocator());
11420+
if (isExpr<ClosureExpr>(argument)) {
11421+
impact += 2;
11422+
}
11423+
}
11424+
1141511425
return recordFix(fix, impact) ? SolutionKind::Error : SolutionKind::Solved;
1141611426
}
1141711427

test/Constraints/closures.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,3 +1114,21 @@ func rdar77022842(argA: Bool? = nil, argB: Bool? = nil) {
11141114
// expected-error@-3 {{expected expression in conditional}}
11151115
} // expected-error {{expected '{' after 'if' condition}}
11161116
}
1117+
1118+
// rdar://76058892 - spurious ambiguity diagnostic
1119+
func rdar76058892() {
1120+
struct S {
1121+
var test: Int = 0
1122+
}
1123+
1124+
func test(_: Int) {}
1125+
func test(_: () -> String) {}
1126+
1127+
func experiment(arr: [S]?) {
1128+
test { // expected-error {{contextual closure type '() -> String' expects 0 arguments, but 1 was used in closure body}}
1129+
if let arr = arr {
1130+
arr.map($0.test) // expected-note {{anonymous closure parameter '$0' is used here}}
1131+
}
1132+
}
1133+
}
1134+
}

0 commit comments

Comments
 (0)