Skip to content

Commit 366a3a0

Browse files
authored
Merge pull request #4981 from xedin/SR-2242
SR-2242: poor diagnostic when argument label is omitted
2 parents 453a790 + 054e3e4 commit 366a3a0

File tree

4 files changed

+190
-132
lines changed

4 files changed

+190
-132
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 140 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,9 @@ CalleeCandidateInfo::evaluateCloseness(DeclContext *dc, Type candArgListType,
12741274
void missingArgument(unsigned paramIdx) override {
12751275
result = CC_ArgumentCountMismatch;
12761276
}
1277+
void missingLabel(unsigned paramIdx) override {
1278+
result = CC_ArgumentLabelMismatch;
1279+
}
12771280
void outOfOrderArgument(unsigned argIdx, unsigned prevArgIdx) override {
12781281
result = CC_ArgumentLabelMismatch;
12791282
}
@@ -4671,152 +4674,159 @@ static bool diagnoseSingleCandidateFailures(CalleeCandidateInfo &CCI,
46714674
CCI.closeness != CC_ArgumentCountMismatch)
46724675
return false;
46734676

4674-
SmallVector<Identifier, 4> correctNames;
4675-
unsigned OOOArgIdx = ~0U, OOOPrevArgIdx = ~0U;
4676-
unsigned extraArgIdx = ~0U, missingParamIdx = ~0U;
4677-
46784677
// If we have a single candidate that failed to match the argument list,
46794678
// attempt to use matchCallArguments to diagnose the problem.
4680-
struct OurListener : public MatchCallArgumentListener {
4681-
SmallVectorImpl<Identifier> &correctNames;
4682-
unsigned &OOOArgIdx, &OOOPrevArgIdx;
4683-
unsigned &extraArgIdx, &missingParamIdx;
4679+
class ArgumentDiagnostic : public MatchCallArgumentListener {
4680+
TypeChecker &TC;
4681+
Expr *ArgExpr;
4682+
llvm::SmallVectorImpl<CallArgParam> &Parameters;
4683+
llvm::SmallVectorImpl<CallArgParam> &Arguments;
4684+
4685+
CalleeCandidateInfo CandidateInfo;
4686+
4687+
// Indicates if problem has been found and diagnostic was emitted.
4688+
bool Diagnosed = false;
4689+
// Indicates if functions we are trying to call is a subscript.
4690+
bool IsSubscript;
4691+
4692+
// Stores parameter bindings determined by call to matchCallArguments.
4693+
SmallVector<ParamBinding, 4> Bindings;
46844694

46854695
public:
4686-
OurListener(SmallVectorImpl<Identifier> &correctNames,
4687-
unsigned &OOOArgIdx, unsigned &OOOPrevArgIdx,
4688-
unsigned &extraArgIdx, unsigned &missingParamIdx)
4689-
: correctNames(correctNames),
4690-
OOOArgIdx(OOOArgIdx), OOOPrevArgIdx(OOOPrevArgIdx),
4691-
extraArgIdx(extraArgIdx), missingParamIdx(missingParamIdx) {}
4692-
void extraArgument(unsigned argIdx) override {
4693-
extraArgIdx = argIdx;
4694-
}
4695-
void missingArgument(unsigned paramIdx) override {
4696-
missingParamIdx = paramIdx;
4696+
ArgumentDiagnostic(Expr *argExpr,
4697+
llvm::SmallVectorImpl<CallArgParam> &params,
4698+
llvm::SmallVectorImpl<CallArgParam> &args,
4699+
CalleeCandidateInfo &CCI, bool isSubscript)
4700+
: TC(CCI.CS->TC), ArgExpr(argExpr), Parameters(params), Arguments(args),
4701+
CandidateInfo(CCI), IsSubscript(isSubscript) {}
4702+
4703+
void extraArgument(unsigned extraArgIdx) override {
4704+
auto name = Arguments[extraArgIdx].Label;
4705+
Expr *arg = ArgExpr;
4706+
4707+
auto tuple = dyn_cast<TupleExpr>(ArgExpr);
4708+
if (tuple)
4709+
arg = tuple->getElement(extraArgIdx);
4710+
4711+
auto loc = arg->getLoc();
4712+
if (tuple && extraArgIdx == tuple->getNumElements() - 1 &&
4713+
tuple->hasTrailingClosure())
4714+
TC.diagnose(loc, diag::extra_trailing_closure_in_call)
4715+
.highlight(arg->getSourceRange());
4716+
else if (Parameters.empty())
4717+
TC.diagnose(loc, diag::extra_argument_to_nullary_call)
4718+
.highlight(ArgExpr->getSourceRange());
4719+
else if (name.empty())
4720+
TC.diagnose(loc, diag::extra_argument_positional)
4721+
.highlight(arg->getSourceRange());
4722+
else
4723+
TC.diagnose(loc, diag::extra_argument_named, name)
4724+
.highlight(arg->getSourceRange());
4725+
4726+
Diagnosed = true;
46974727
}
4698-
void outOfOrderArgument(unsigned argIdx, unsigned prevArgIdx) override{
4699-
OOOArgIdx = argIdx;
4700-
OOOPrevArgIdx = prevArgIdx;
4728+
4729+
void missingArgument(unsigned missingParamIdx) override {
4730+
Identifier name = Parameters[missingParamIdx].Label;
4731+
auto loc = ArgExpr->getStartLoc();
4732+
if (name.empty())
4733+
TC.diagnose(loc, diag::missing_argument_positional,
4734+
missingParamIdx + 1);
4735+
else
4736+
TC.diagnose(loc, diag::missing_argument_named, name);
4737+
4738+
auto candidate = CandidateInfo[0];
4739+
if (candidate.getDecl())
4740+
TC.diagnose(candidate.getDecl(), diag::decl_declared_here,
4741+
candidate.getDecl()->getFullName());
4742+
4743+
Diagnosed = true;
47014744
}
4702-
bool relabelArguments(ArrayRef<Identifier> newNames) override {
4703-
correctNames.append(newNames.begin(), newNames.end());
4704-
return true;
4745+
4746+
void missingLabel(unsigned paramIdx) override {
4747+
auto tuple = cast<TupleExpr>(ArgExpr);
4748+
TC.diagnose(tuple->getElement(paramIdx)->getStartLoc(),
4749+
diag::missing_argument_labels, false,
4750+
Parameters[paramIdx].Label.str(), IsSubscript);
4751+
4752+
Diagnosed = true;
47054753
}
4706-
} listener(correctNames, OOOArgIdx, OOOPrevArgIdx,
4707-
extraArgIdx, missingParamIdx);
47084754

4709-
// Use matchCallArguments to determine how close the argument list is (in
4710-
// shape) to the specified candidates parameters. This ignores the
4711-
// concrete types of the arguments, looking only at the argument labels.
4712-
SmallVector<ParamBinding, 4> paramBindings;
4713-
if (!matchCallArguments(args, params, CCI.hasTrailingClosure,
4714-
/*allowFixes:*/true, listener, paramBindings))
4715-
return false;
4755+
void outOfOrderArgument(unsigned argIdx, unsigned prevArgIdx) override {
4756+
auto tuple = cast<TupleExpr>(ArgExpr);
4757+
Identifier first = tuple->getElementName(argIdx);
4758+
Identifier second = tuple->getElementName(prevArgIdx);
4759+
4760+
// Build a mapping from arguments to parameters.
4761+
SmallVector<unsigned, 4> argBindings(tuple->getNumElements());
4762+
for (unsigned paramIdx = 0; paramIdx != Bindings.size(); ++paramIdx) {
4763+
for (auto argIdx : Bindings[paramIdx])
4764+
argBindings[argIdx] = paramIdx;
4765+
}
47164766

4767+
auto argRange = [&](unsigned argIdx, Identifier label) -> SourceRange {
4768+
auto range = tuple->getElement(argIdx)->getSourceRange();
4769+
if (!label.empty())
4770+
range.Start = tuple->getElementNameLoc(argIdx);
47174771

4718-
// If we are missing a parameter, diagnose that.
4719-
if (missingParamIdx != ~0U) {
4720-
Identifier name = params[missingParamIdx].Label;
4721-
auto loc = argExpr->getStartLoc();
4722-
if (name.empty())
4723-
TC.diagnose(loc, diag::missing_argument_positional,
4724-
missingParamIdx+1);
4725-
else
4726-
TC.diagnose(loc, diag::missing_argument_named, name);
4727-
4728-
if (candidate.getDecl())
4729-
TC.diagnose(candidate.getDecl(), diag::decl_declared_here,
4730-
candidate.getDecl()->getFullName());
4731-
return true;
4732-
}
4772+
unsigned paramIdx = argBindings[argIdx];
4773+
if (Bindings[paramIdx].size() > 1)
4774+
range.End = tuple->getElement(Bindings[paramIdx].back())->getEndLoc();
47334775

4734-
if (extraArgIdx != ~0U) {
4735-
auto name = args[extraArgIdx].Label;
4736-
Expr *arg = argExpr;
4737-
auto tuple = dyn_cast<TupleExpr>(argExpr);
4738-
if (tuple)
4739-
arg = tuple->getElement(extraArgIdx);
4740-
auto loc = arg->getLoc();
4741-
if (tuple && extraArgIdx == tuple->getNumElements()-1 &&
4742-
tuple->hasTrailingClosure())
4743-
TC.diagnose(loc, diag::extra_trailing_closure_in_call)
4744-
.highlight(arg->getSourceRange());
4745-
else if (params.empty())
4746-
TC.diagnose(loc, diag::extra_argument_to_nullary_call)
4747-
.highlight(argExpr->getSourceRange());
4748-
else if (name.empty())
4749-
TC.diagnose(loc, diag::extra_argument_positional)
4750-
.highlight(arg->getSourceRange());
4751-
else
4752-
TC.diagnose(loc, diag::extra_argument_named, name)
4753-
.highlight(arg->getSourceRange());
4754-
return true;
4755-
}
4776+
return range;
4777+
};
47564778

4757-
// If this is an argument label mismatch, then diagnose that error now.
4758-
if (!correctNames.empty() &&
4759-
diagnoseArgumentLabelError(TC, argExpr, correctNames,
4760-
isa<SubscriptExpr>(fnExpr)))
4761-
return true;
4779+
auto firstRange = argRange(argIdx, first);
4780+
auto secondRange = argRange(prevArgIdx, second);
4781+
4782+
SourceLoc diagLoc = firstRange.Start;
4783+
4784+
if (first.empty() && second.empty()) {
4785+
TC.diagnose(diagLoc, diag::argument_out_of_order_unnamed_unnamed,
4786+
argIdx + 1, prevArgIdx + 1)
4787+
.fixItExchange(firstRange, secondRange);
4788+
} else if (first.empty() && !second.empty()) {
4789+
TC.diagnose(diagLoc, diag::argument_out_of_order_unnamed_named,
4790+
argIdx + 1, second)
4791+
.fixItExchange(firstRange, secondRange);
4792+
} else if (!first.empty() && second.empty()) {
4793+
TC.diagnose(diagLoc, diag::argument_out_of_order_named_unnamed, first,
4794+
prevArgIdx + 1)
4795+
.fixItExchange(firstRange, secondRange);
4796+
} else {
4797+
TC.diagnose(diagLoc, diag::argument_out_of_order_named_named, first,
4798+
second)
4799+
.fixItExchange(firstRange, secondRange);
4800+
}
47624801

4763-
// If we have an out-of-order argument, diagnose it as such.
4764-
if (OOOArgIdx != ~0U && isa<TupleExpr>(argExpr)) {
4765-
auto tuple = cast<TupleExpr>(argExpr);
4766-
Identifier first = tuple->getElementName(OOOArgIdx);
4767-
Identifier second = tuple->getElementName(OOOPrevArgIdx);
4768-
4769-
// Build a mapping from arguments to parameters.
4770-
SmallVector<unsigned, 4> argBindings(tuple->getNumElements());
4771-
for (unsigned paramIdx = 0; paramIdx != paramBindings.size(); ++paramIdx) {
4772-
for (auto argIdx : paramBindings[paramIdx])
4773-
argBindings[argIdx] = paramIdx;
4774-
}
4775-
4776-
auto firstRange = tuple->getElement(OOOArgIdx)->getSourceRange();
4777-
if (!first.empty()) {
4778-
firstRange.Start = tuple->getElementNameLoc(OOOArgIdx);
4779-
}
4780-
unsigned OOOParamIdx = argBindings[OOOArgIdx];
4781-
if (paramBindings[OOOParamIdx].size() > 1) {
4782-
firstRange.End =
4783-
tuple->getElement(paramBindings[OOOParamIdx].back())->getEndLoc();
4784-
}
4785-
4786-
auto secondRange = tuple->getElement(OOOPrevArgIdx)->getSourceRange();
4787-
if (!second.empty()) {
4788-
secondRange.Start = tuple->getElementNameLoc(OOOPrevArgIdx);
4789-
}
4790-
unsigned OOOPrevParamIdx = argBindings[OOOPrevArgIdx];
4791-
if (paramBindings[OOOPrevParamIdx].size() > 1) {
4792-
secondRange.End =
4793-
tuple->getElement(paramBindings[OOOPrevParamIdx].back())->getEndLoc();
4794-
}
4795-
4796-
SourceLoc diagLoc = firstRange.Start;
4797-
4798-
if (first.empty() && second.empty()) {
4799-
TC.diagnose(diagLoc, diag::argument_out_of_order_unnamed_unnamed,
4800-
OOOArgIdx + 1, OOOPrevArgIdx + 1)
4801-
.fixItExchange(firstRange, secondRange);
4802-
} else if (first.empty() && !second.empty()) {
4803-
TC.diagnose(diagLoc, diag::argument_out_of_order_unnamed_named,
4804-
OOOArgIdx + 1, second)
4805-
.fixItExchange(firstRange, secondRange);
4806-
} else if (!first.empty() && second.empty()) {
4807-
TC.diagnose(diagLoc, diag::argument_out_of_order_named_unnamed,
4808-
first, OOOPrevArgIdx + 1)
4809-
.fixItExchange(firstRange, secondRange);
4810-
} else {
4811-
TC.diagnose(diagLoc, diag::argument_out_of_order_named_named,
4812-
first, second)
4813-
.fixItExchange(firstRange, secondRange);
4802+
Diagnosed = true;
48144803
}
48154804

4816-
return true;
4817-
}
4805+
bool relabelArguments(ArrayRef<Identifier> newNames) override {
4806+
assert (!newNames.empty() && "No arguments were re-labeled");
48184807

4819-
return false;
4808+
// Let's diagnose labeling problem but only related to corrected ones.
4809+
if (diagnoseArgumentLabelError(TC, ArgExpr, newNames, IsSubscript))
4810+
Diagnosed = true;
4811+
4812+
return true;
4813+
}
4814+
4815+
bool diagnose() {
4816+
// Use matchCallArguments to determine how close the argument list is (in
4817+
// shape) to the specified candidates parameters. This ignores the
4818+
// concrete types of the arguments, looking only at the argument labels.
4819+
matchCallArguments(Arguments, Parameters,
4820+
CandidateInfo.hasTrailingClosure,
4821+
/*allowFixes:*/ true, *this, Bindings);
4822+
4823+
return Diagnosed;
4824+
}
4825+
};
4826+
4827+
return ArgumentDiagnostic(argExpr, params, args, CCI,
4828+
isa<SubscriptExpr>(fnExpr))
4829+
.diagnose();
48204830
}
48214831

48224832
/// If the candidate set has been narrowed down to a specific structural

lib/Sema/CSSimplify.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ void MatchCallArgumentListener::extraArgument(unsigned argIdx) { }
2828

2929
void MatchCallArgumentListener::missingArgument(unsigned paramIdx) { }
3030

31+
void MatchCallArgumentListener::missingLabel(unsigned paramIdx) {}
32+
3133
void MatchCallArgumentListener::outOfOrderArgument(unsigned argIdx,
3234
unsigned prevArgIdx) {
3335
}
@@ -454,6 +456,27 @@ matchCallArguments(ArrayRef<CallArgParam> args,
454456
}
455457

456458
unsigned prevArgIdx = parameterBindings[prevParamIdx].front();
459+
460+
// First let's double check if out-of-order argument is nothing
461+
// more than a simple label mismatch, because in situation where
462+
// one argument requires label and another one doesn't, but caller
463+
// doesn't provide either, problem is going to be identified as
464+
// out-of-order argument instead of label mismatch.
465+
auto &parameter = params[prevArgIdx];
466+
if (parameter.hasLabel()) {
467+
auto expectedLabel = parameter.Label;
468+
auto argumentLabel = args[argIdx].Label;
469+
470+
// If there is a label but it's incorrect it can only mean
471+
// situation like this: expected (x, _ y) got (y, _ x).
472+
if (argumentLabel.empty() ||
473+
(expectedLabel.compare(argumentLabel) != 0 &&
474+
args[prevArgIdx].Label.empty())) {
475+
listener.missingLabel(prevArgIdx);
476+
return true;
477+
}
478+
}
479+
457480
listener.outOfOrderArgument(argIdx, prevArgIdx);
458481
return true;
459482
}

lib/Sema/ConstraintSystem.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,6 +2243,11 @@ class MatchCallArgumentListener {
22432243
/// \param paramIdx The index of the parameter that is missing an argument.
22442244
virtual void missingArgument(unsigned paramIdx);
22452245

2246+
/// Indicate that there was no label given when one was expected by parameter.
2247+
///
2248+
/// \param paramIndex The index of the parameter that is missing a label.
2249+
virtual void missingLabel(unsigned paramIndex);
2250+
22462251
/// Indicates that an argument is out-of-order with respect to a previously-
22472252
/// seen argument.
22482253
///

test/Constraints/diagnostics.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -713,10 +713,9 @@ func nilComparison(i: Int, o: AnyObject) {
713713
_ = o !== nil // expected-warning {{comparing non-optional value of type 'AnyObject' to nil always returns true}}
714714
}
715715

716-
// FIXME: Bad diagnostic
717716
func secondArgumentNotLabeled(a:Int, _ b: Int) { }
718717
secondArgumentNotLabeled(10, 20)
719-
// expected-error@-1 {{unnamed argument #2 must precede unnamed argument #1}}
718+
// expected-error@-1 {{missing argument label 'a' in call}}
720719

721720
// <rdar://problem/23709100> QoI: incorrect ambiguity error due to implicit conversion
722721
func testImplConversion(a : Float?) -> Bool {}
@@ -795,3 +794,24 @@ func valueForKey<K>(_ key: K) -> CacheValue? {
795794
let cache = NSCache<K, CacheValue>()
796795
return cache.object(forKey: key)?.value // expected-error {{ambiguous reference to member 'value(x:)'}}
797796
}
797+
798+
// SR-2242: poor diagnostic when argument label is omitted
799+
800+
func r27212391(x: Int, _ y: Int) {
801+
let _: Int = x + y
802+
}
803+
804+
func r27212391(a: Int, x: Int, _ y: Int) {
805+
let _: Int = a + x + y
806+
}
807+
808+
r27212391(3, 5) // expected-error {{missing argument label 'x' in call}}
809+
r27212391(3, y: 5) // expected-error {{missing argument label 'x' in call}}
810+
r27212391(3, x: 5) // expected-error {{argument 'x' must precede unnamed argument #1}}
811+
r27212391(y: 3, x: 5) // expected-error {{argument 'x' must precede argument 'y'}}
812+
r27212391(y: 3, 5) // expected-error {{incorrect argument label in call (have 'y:_:', expected 'x:_:')}}
813+
r27212391(x: 3, x: 5) // expected-error {{extraneous argument label 'x:' in call}}
814+
r27212391(a: 1, 3, y: 5) // expected-error {{missing argument label 'x' in call}}
815+
r27212391(1, x: 3, y: 5) // expected-error {{missing argument label 'a' in call}}
816+
r27212391(a: 1, y: 3, x: 5) // expected-error {{argument 'x' must precede argument 'y'}}
817+
r27212391(a: 1, 3, x: 5) // expected-error {{argument 'x' must precede unnamed argument #2}}

0 commit comments

Comments
 (0)