Skip to content

Commit aeaa26d

Browse files
committed
[CSDiagnostics] Add missing arguments failure
Currently only supports closures, but could be easily expanded to other types of situations e.g. function/member calls.
1 parent c664ac0 commit aeaa26d

File tree

7 files changed

+158
-76
lines changed

7 files changed

+158
-76
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 26 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -5775,8 +5775,8 @@ bool FailureDiagnosis::diagnoseClosureExpr(
57755775
// If we have a contextual type available for this closure, apply it to the
57765776
// ParamDecls in our parameter list. This ensures that any uses of them get
57775777
// appropriate types.
5778-
if (contextualType && contextualType->is<AnyFunctionType>()) {
5779-
auto fnType = contextualType->getAs<AnyFunctionType>();
5778+
if (contextualType && contextualType->is<FunctionType>()) {
5779+
auto fnType = contextualType->getAs<FunctionType>();
57805780
auto *params = CE->getParameters();
57815781
auto inferredArgs = fnType->getParams();
57825782

@@ -5791,36 +5791,6 @@ bool FailureDiagnosis::diagnoseClosureExpr(
57915791
unsigned inferredArgCount = inferredArgs.size();
57925792

57935793
if (actualArgCount != inferredArgCount) {
5794-
// If the closure didn't specify any arguments and it is in a context that
5795-
// needs some, produce a fixit to turn "{...}" into "{ _,_ in ...}".
5796-
if (actualArgCount == 0 && CE->getInLoc().isInvalid()) {
5797-
auto diag =
5798-
diagnose(CE->getStartLoc(), diag::closure_argument_list_missing,
5799-
inferredArgCount);
5800-
std::string fixText; // Let's provide fixits for up to 10 args.
5801-
5802-
if (inferredArgCount <= 10) {
5803-
fixText += " _";
5804-
for (unsigned i = 0; i < inferredArgCount - 1; i ++) {
5805-
fixText += ",_";
5806-
}
5807-
fixText += " in ";
5808-
}
5809-
5810-
if (!fixText.empty()) {
5811-
// Determine if there is already a space after the { in the closure to
5812-
// make sure we introduce the right whitespace.
5813-
auto afterBrace = CE->getStartLoc().getAdvancedLoc(1);
5814-
auto text = CS.TC.Context.SourceMgr.extractText({afterBrace, 1});
5815-
if (text.size() == 1 && text == " ")
5816-
fixText = fixText.erase(fixText.size() - 1);
5817-
else
5818-
fixText = fixText.erase(0, 1);
5819-
diag.fixItInsertAfter(CE->getStartLoc(), fixText);
5820-
}
5821-
return true;
5822-
}
5823-
58245794
if (inferredArgCount == 1 && actualArgCount > 1) {
58255795
auto *argTupleTy = inferredArgs.front().getOldType()->getAs<TupleType>();
58265796
// Let's see if inferred argument is actually a tuple inside of Paren.
@@ -5956,49 +5926,33 @@ bool FailureDiagnosis::diagnoseClosureExpr(
59565926
}
59575927
}
59585928

5959-
bool onlyAnonymousParams =
5960-
std::all_of(params->begin(), params->end(), [](ParamDecl *param) {
5961-
return !param->hasName();
5962-
});
5963-
5964-
// Okay, the wrong number of arguments was used, complain about that.
5965-
// Before doing so, strip attributes off the function type so that they
5966-
// don't confuse the issue.
5967-
fnType = FunctionType::get(fnType->getParams(), fnType->getResult(),
5968-
fnType->getExtInfo());
5969-
auto diag = diagnose(
5970-
params->getStartLoc(), diag::closure_argument_list_tuple, fnType,
5971-
inferredArgCount, actualArgCount, (actualArgCount == 1));
5972-
5973-
// If closure expects no parameters but N was given,
5974-
// and all of them are anonymous let's suggest removing them.
5975-
if (inferredArgCount == 0 && onlyAnonymousParams) {
5976-
auto inLoc = CE->getInLoc();
5977-
auto &sourceMgr = CS.getASTContext().SourceMgr;
5978-
5979-
if (inLoc.isValid())
5980-
diag.fixItRemoveChars(params->getStartLoc(),
5981-
Lexer::getLocForEndOfToken(sourceMgr, inLoc));
5929+
// Extraneous arguments.
5930+
if (inferredArgCount < actualArgCount) {
5931+
auto diag = diagnose(
5932+
params->getStartLoc(), diag::closure_argument_list_tuple, fnType,
5933+
inferredArgCount, actualArgCount, (actualArgCount == 1));
5934+
5935+
bool onlyAnonymousParams =
5936+
std::all_of(params->begin(), params->end(),
5937+
[](ParamDecl *param) { return !param->hasName(); });
5938+
5939+
// If closure expects no parameters but N was given,
5940+
// and all of them are anonymous let's suggest removing them.
5941+
if (inferredArgCount == 0 && onlyAnonymousParams) {
5942+
auto inLoc = CE->getInLoc();
5943+
auto &sourceMgr = CS.getASTContext().SourceMgr;
5944+
5945+
if (inLoc.isValid())
5946+
diag.fixItRemoveChars(params->getStartLoc(),
5947+
Lexer::getLocForEndOfToken(sourceMgr, inLoc));
5948+
}
59825949
return true;
59835950
}
59845951

5985-
// If the number of parameters is less than number of inferred
5986-
// and all of the parameters are anonymous, let's suggest a fix-it
5987-
// with the rest of the missing parameters.
5988-
if (actualArgCount < inferredArgCount) {
5989-
SmallString<32> fixIt;
5990-
llvm::raw_svector_ostream OS(fixIt);
5991-
5992-
OS << ",";
5993-
auto numMissing = inferredArgCount - actualArgCount;
5994-
for (unsigned i = 0; i != numMissing; ++i) {
5995-
OS << ((onlyAnonymousParams) ? "_" : "<#arg#>");
5996-
OS << ((i == numMissing - 1) ? " " : ",");
5997-
}
5998-
5999-
diag.fixItInsertAfter(params->getEndLoc(), OS.str());
6000-
}
6001-
return true;
5952+
MissingArgumentsFailure failure(
5953+
expr, CS, fnType, inferredArgCount - actualArgCount,
5954+
CS.getConstraintLocator(CE, ConstraintLocator::ContextualType));
5955+
return failure.diagnoseAsError();
60025956
}
60035957

60045958
// Coerce parameter types here only if there are no unresolved

lib/Sema/CSDiagnostics.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1931,3 +1931,81 @@ bool ImplicitInitOnNonConstMetatypeFailure::diagnoseAsError() {
19311931
.fixItInsert(loc, ".init");
19321932
return true;
19331933
}
1934+
1935+
bool MissingArgumentsFailure::diagnoseAsError() {
1936+
auto *locator = getLocator();
1937+
auto path = locator->getPath();
1938+
1939+
// TODO: Currently this is only intended to diagnose contextual failures.
1940+
if (!(path.back().getKind() == ConstraintLocator::ApplyArgToParam ||
1941+
path.back().getKind() == ConstraintLocator::ContextualType))
1942+
return false;
1943+
1944+
if (auto *closure = dyn_cast<ClosureExpr>(getAnchor()))
1945+
return diagnoseTrailingClosure(closure);
1946+
1947+
return false;
1948+
}
1949+
1950+
bool MissingArgumentsFailure::diagnoseTrailingClosure(ClosureExpr *closure) {
1951+
auto diff = Fn->getNumParams() - NumSynthesized;
1952+
1953+
// If the closure didn't specify any arguments and it is in a context that
1954+
// needs some, produce a fixit to turn "{...}" into "{ _,_ in ...}".
1955+
if (diff == 0) {
1956+
auto diag =
1957+
emitDiagnostic(closure->getStartLoc(),
1958+
diag::closure_argument_list_missing, NumSynthesized);
1959+
1960+
std::string fixText; // Let's provide fixits for up to 10 args.
1961+
if (Fn->getNumParams() <= 10) {
1962+
fixText += " ";
1963+
interleave(
1964+
Fn->getParams(),
1965+
[&fixText](const AnyFunctionType::Param &param) { fixText += '_'; },
1966+
[&fixText] { fixText += ','; });
1967+
fixText += " in ";
1968+
}
1969+
1970+
if (!fixText.empty()) {
1971+
// Determine if there is already a space after the { in the closure to
1972+
// make sure we introduce the right whitespace.
1973+
auto afterBrace = closure->getStartLoc().getAdvancedLoc(1);
1974+
auto text = getASTContext().SourceMgr.extractText({afterBrace, 1});
1975+
if (text.size() == 1 && text == " ")
1976+
fixText = fixText.erase(fixText.size() - 1);
1977+
else
1978+
fixText = fixText.erase(0, 1);
1979+
diag.fixItInsertAfter(closure->getStartLoc(), fixText);
1980+
}
1981+
1982+
return true;
1983+
}
1984+
1985+
auto params = closure->getParameters();
1986+
bool onlyAnonymousParams =
1987+
std::all_of(params->begin(), params->end(),
1988+
[](ParamDecl *param) { return !param->hasName(); });
1989+
1990+
auto diag =
1991+
emitDiagnostic(params->getStartLoc(), diag::closure_argument_list_tuple,
1992+
resolveType(Fn), Fn->getNumParams(), diff, diff == 1);
1993+
1994+
// If the number of parameters is less than number of inferred
1995+
// let's try to suggest a fix-it with the rest of the missing parameters.
1996+
if (!closure->hasExplicitResultType() &&
1997+
closure->getInLoc().isValid()) {
1998+
SmallString<32> fixIt;
1999+
llvm::raw_svector_ostream OS(fixIt);
2000+
2001+
OS << ",";
2002+
for (unsigned i = 0; i != NumSynthesized; ++i) {
2003+
OS << ((onlyAnonymousParams) ? "_" : "<#arg#>");
2004+
OS << ((i == NumSynthesized - 1) ? " " : ",");
2005+
}
2006+
2007+
diag.fixItInsertAfter(params->getEndLoc(), OS.str());
2008+
}
2009+
2010+
return true;
2011+
}

lib/Sema/CSDiagnostics.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,28 @@ class ImplicitInitOnNonConstMetatypeFailure final
877877
bool diagnoseAsError() override;
878878
};
879879

880+
class MissingArgumentsFailure final : public FailureDiagnostic {
881+
using Param = AnyFunctionType::Param;
882+
883+
FunctionType *Fn;
884+
unsigned NumSynthesized;
885+
886+
public:
887+
MissingArgumentsFailure(Expr *root, ConstraintSystem &cs,
888+
FunctionType *funcType,
889+
unsigned numSynthesized,
890+
ConstraintLocator *locator)
891+
: FailureDiagnostic(root, cs, locator), Fn(funcType),
892+
NumSynthesized(numSynthesized) {}
893+
894+
bool diagnoseAsError() override;
895+
896+
private:
897+
/// If missing arguments come from trailing closure,
898+
/// let's produce tailored diagnostics.
899+
bool diagnoseTrailingClosure(ClosureExpr *closure);
900+
};
901+
880902
} // end namespace constraints
881903
} // end namespace swift
882904

lib/Sema/CSFix.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,9 @@ AllowInvalidInitRef::create(RefKind kind, ConstraintSystem &cs, Type baseTy,
344344
}
345345

346346
bool AddMissingArguments::diagnose(Expr *root, bool asNote) const {
347-
return false;
347+
MissingArgumentsFailure failure(root, getConstraintSystem(), Fn,
348+
NumSynthesized, getLocator());
349+
return failure.diagnose(asNote);
348350
}
349351

350352
AddMissingArguments *

test/Constraints/closures.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,3 +875,28 @@ struct rdar43866352<Options> {
875875
callback = { (options: Options) in } // expected-error {{cannot assign value of type '(inout Options) -> ()' to type '(inout _) -> Void'}}
876876
}
877877
}
878+
879+
extension Hashable {
880+
var self_: Self {
881+
return self
882+
}
883+
}
884+
885+
do {
886+
struct S<
887+
C : Collection,
888+
I : Hashable,
889+
R : Numeric
890+
> {
891+
init(_ arr: C,
892+
id: KeyPath<C.Element, I>,
893+
content: @escaping (C.Element) -> R) {}
894+
}
895+
896+
func foo(_ arr: [Int]) {
897+
_ = S(arr, id: \.self_) {
898+
// expected-error@-1 {{contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored}} {{30-30=_ in }}
899+
return 42
900+
}
901+
}
902+
}

test/Constraints/tuple-arguments-unsupported.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ test3(.success()) // expected-error {{missing argument for parameter #1 in call}
1111

1212
func toString(indexes: Int?...) -> String {
1313
let _ = indexes.reduce(0) { print($0); return $0.0 + ($0.1 ?? 0)}
14-
// expected-error@-1 {{contextual closure type '(_, Int?) throws -> _' expects 2 arguments, but 1 was used in closure body}}
14+
// expected-error@-1 {{contextual closure type '(Int, Int?) throws -> Int' expects 2 arguments, but 1 was used in closure body}}
1515
let _ = indexes.reduce(0) { (true ? $0 : (1, 2)).0 + ($0.1 ?? 0) }
16-
// expected-error@-1 {{contextual closure type '(_, Int?) throws -> _' expects 2 arguments, but 1 was used in closure body}}
16+
// expected-error@-1 {{contextual closure type '(Int, Int?) throws -> Int' expects 2 arguments, but 1 was used in closure body}}
1717
_ = ["Hello", "Foo"].sorted { print($0); return $0.0.count > ($0).1.count }
18-
// expected-error@-1 {{argument passed to call that takes no arguments}}
18+
// expected-error@-1 {{contextual closure type '(String, String) throws -> Bool' expects 2 arguments, but 1 was used in closure body}}
1919
}
2020

2121
func doit(_ x: Int) -> Bool { return x > 0 }

test/decl/protocol/req/associated_type_inference.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ public class CorePromise<U> : Thenable { // expected-error{{type 'CorePromise<U>
200200
public func then(_ success: @escaping (_ t: U, _: CorePromise<U>) -> U) -> Self {
201201
return self.then() { (t: U) -> U in // expected-error{{contextual closure type '(U, CorePromise<U>) -> U' expects 2 arguments, but 1 was used in closure body}}
202202
return success(t: t, self)
203+
// expected-error@-1 {{extraneous argument label 't:' in call}}
203204
}
204205
}
205206
}

0 commit comments

Comments
 (0)