Skip to content

Commit 6c5185f

Browse files
committed
Improve diagnostic when attempting to pass an Array to a variadic argument
- Give a more specific diagnostic which indicates the parameter is variadic - If the argument is an Array literal, offer to drop the brackets
1 parent f73c4c4 commit 6c5185f

File tree

10 files changed

+235
-23
lines changed

10 files changed

+235
-23
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,15 @@ NOTE(candidate_has_invalid_argument_at_position,none,
384384
"candidate expects value of type %0 at position #%1",
385385
(Type, unsigned))
386386

387+
ERROR(cannot_convert_array_to_variadic,none,
388+
"cannot pass array of type %0 as variadic arguments of type %1",
389+
(Type,Type))
390+
NOTE(candidate_would_match_array_to_variadic,none,
391+
"candidate would match if array elements were passed as"
392+
" variadic arguments of type %0", (Type))
393+
NOTE(suggest_pass_elements_directly,none,
394+
"remove brackets to pass array elements directly", ())
395+
387396
ERROR(cannot_convert_argument_value_generic,none,
388397
"cannot convert value of type %0 (%1) to expected argument type %2 (%3)",
389398
(Type, StringRef, Type, StringRef))

lib/Sema/CSApply.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5384,10 +5384,11 @@ Expr *ExprRewriter::coerceCallArguments(
53845384
auto params = funcType->getParams();
53855385

53865386
// Local function to produce a locator to refer to the given parameter.
5387-
auto getArgLocator = [&](unsigned argIdx, unsigned paramIdx)
5388-
-> ConstraintLocatorBuilder {
5387+
auto getArgLocator =
5388+
[&](unsigned argIdx, unsigned paramIdx,
5389+
ParameterTypeFlags flags) -> ConstraintLocatorBuilder {
53895390
return locator.withPathElement(
5390-
LocatorPathElt::ApplyArgToParam(argIdx, paramIdx));
5391+
LocatorPathElt::ApplyArgToParam(argIdx, paramIdx, flags));
53915392
};
53925393

53935394
bool matchCanFail =
@@ -5499,8 +5500,9 @@ Expr *ExprRewriter::coerceCallArguments(
54995500
}
55005501

55015502
// Convert the argument.
5502-
auto convertedArg = coerceToType(arg, param.getPlainType(),
5503-
getArgLocator(argIdx, paramIdx));
5503+
auto convertedArg = coerceToType(
5504+
arg, param.getPlainType(),
5505+
getArgLocator(argIdx, paramIdx, param.getParameterFlags()));
55045506
if (!convertedArg)
55055507
return nullptr;
55065508

@@ -5620,8 +5622,9 @@ Expr *ExprRewriter::coerceCallArguments(
56205622
convertedArg = cs.TC.buildAutoClosureExpr(dc, arg, closureType);
56215623
cs.cacheExprTypes(convertedArg);
56225624
} else {
5623-
convertedArg =
5624-
coerceToType(arg, paramType, getArgLocator(argIdx, paramIdx));
5625+
convertedArg = coerceToType(
5626+
arg, paramType,
5627+
getArgLocator(argIdx, paramIdx, param.getParameterFlags()));
56255628
}
56265629

56275630
if (!convertedArg)

lib/Sema/CSDiag.cpp

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3461,11 +3461,16 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
34613461
// case (let (_, _, _)) + 1: break
34623462
// }
34633463
if (callExpr->isImplicit() && overloadName == "~=") {
3464-
auto *locator =
3465-
CS.getConstraintLocator(callExpr,
3466-
{ConstraintLocator::ApplyArgument,
3467-
LocatorPathElt::ApplyArgToParam(0, 0)},
3468-
/*summaryFlags=*/0);
3464+
auto flags = ParameterTypeFlags();
3465+
if (calleeInfo.candidates.size() == 1)
3466+
if (auto fnType = calleeInfo.candidates[0].getFunctionType())
3467+
flags = fnType->getParams()[0].getParameterFlags();
3468+
3469+
auto *locator = CS.getConstraintLocator(
3470+
callExpr,
3471+
{ConstraintLocator::ApplyArgument,
3472+
LocatorPathElt::ApplyArgToParam(0, 0, flags)},
3473+
/*summaryFlags=*/0);
34693474

34703475
ArgumentMismatchFailure failure(expr, CS, lhsType, rhsType, locator);
34713476
return failure.diagnosePatternMatchingMismatch();

lib/Sema/CSDiagnostics.cpp

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4611,9 +4611,11 @@ bool ArgumentMismatchFailure::diagnoseUseOfReferenceEqualityOperator() const {
46114611
// one would cover both arguments.
46124612
if (getAnchor() == rhs && rhsType->is<FunctionType>()) {
46134613
auto &cs = getConstraintSystem();
4614-
if (cs.hasFixFor(cs.getConstraintLocator(
4615-
binaryOp, {ConstraintLocator::ApplyArgument,
4616-
LocatorPathElt::ApplyArgToParam(0, 0)})))
4614+
auto info = getFunctionArgApplyInfo(locator);
4615+
if (info && cs.hasFixFor(cs.getConstraintLocator(
4616+
binaryOp, {ConstraintLocator::ApplyArgument,
4617+
LocatorPathElt::ApplyArgToParam(
4618+
0, 0, info->getParameterFlagsAtIndex(0))})))
46174619
return true;
46184620
}
46194621

@@ -4763,3 +4765,44 @@ bool ArgumentMismatchFailure::diagnoseArchetypeMismatch() const {
47634765

47644766
return true;
47654767
}
4768+
4769+
void ExpandArrayIntoVarargsFailure::tryDropArrayBracketsFixIt(
4770+
Expr *anchor) const {
4771+
// If this is an array literal, offer to remove the brackets and pass the
4772+
// elements directly as variadic arguments.
4773+
if (auto *arrayExpr = dyn_cast<ArrayExpr>(anchor)) {
4774+
auto diag = emitDiagnostic(arrayExpr->getLoc(),
4775+
diag::suggest_pass_elements_directly);
4776+
diag.fixItRemove(arrayExpr->getLBracketLoc())
4777+
.fixItRemove(arrayExpr->getRBracketLoc());
4778+
// Handle the case where the array literal has a trailing comma.
4779+
if (arrayExpr->getNumCommas() == arrayExpr->getNumElements())
4780+
diag.fixItRemove(arrayExpr->getCommaLocs().back());
4781+
}
4782+
}
4783+
4784+
bool ExpandArrayIntoVarargsFailure::diagnoseAsError() {
4785+
if (auto anchor = getAnchor()) {
4786+
emitDiagnostic(anchor->getLoc(), diag::cannot_convert_array_to_variadic,
4787+
getFromType(), getToType());
4788+
tryDropArrayBracketsFixIt(anchor);
4789+
// TODO: Array splat fix-it once that's supported.
4790+
return true;
4791+
}
4792+
return false;
4793+
}
4794+
4795+
bool ExpandArrayIntoVarargsFailure::diagnoseAsNote() {
4796+
auto overload = getChoiceFor(getLocator());
4797+
auto anchor = getAnchor();
4798+
if (!overload || !anchor)
4799+
return false;
4800+
4801+
if (auto chosenDecl = overload->choice.getDeclOrNull()) {
4802+
emitDiagnostic(chosenDecl, diag::candidate_would_match_array_to_variadic,
4803+
getToType());
4804+
tryDropArrayBracketsFixIt(anchor);
4805+
return true;
4806+
}
4807+
return false;
4808+
}

lib/Sema/CSDiagnostics.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,8 +1582,25 @@ class InvalidTupleSplatWithSingleParameterFailure final
15821582
Type paramTy,
15831583
ConstraintLocator *locator)
15841584
: FailureDiagnostic(root, cs, locator), ParamType(paramTy) {}
1585+
bool diagnoseAsError() override;
1586+
};
1587+
1588+
/// Diagnose situation when an array is passed instead of varargs.
1589+
///
1590+
/// ```swift
1591+
/// func foo(_ x: Int...) {}
1592+
/// foo([1,2,3]]) // foo expects varags like foo(1,2,3) instead.
1593+
/// ```
1594+
class ExpandArrayIntoVarargsFailure final : public ContextualFailure {
1595+
public:
1596+
ExpandArrayIntoVarargsFailure(Expr *root, ConstraintSystem &cs, Type lhs,
1597+
Type rhs, ConstraintLocator *locator)
1598+
: ContextualFailure(root, cs, lhs, rhs, locator) {}
15851599

15861600
bool diagnoseAsError() override;
1601+
bool diagnoseAsNote() override;
1602+
1603+
void tryDropArrayBracketsFixIt(Expr *anchor) const;
15871604
};
15881605

15891606
/// Diagnose a situation there is a mismatch between argument and parameter
@@ -1715,6 +1732,10 @@ class FunctionArgApplyInfo {
17151732
ParameterTypeFlags getParameterFlags() const {
17161733
return FnType->getParams()[ParamIdx].getParameterFlags();
17171734
}
1735+
1736+
ParameterTypeFlags getParameterFlagsAtIndex(unsigned idx) const {
1737+
return FnType->getParams()[idx].getParameterFlags();
1738+
}
17181739
};
17191740

17201741
} // end namespace constraints

lib/Sema/CSFix.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,36 @@ static bool isValueOfRawRepresentable(ConstraintSystem &cs,
795795
return false;
796796
}
797797

798+
ExpandArrayIntoVarargs *
799+
ExpandArrayIntoVarargs::attempt(ConstraintSystem &cs, Type argType,
800+
Type paramType,
801+
ConstraintLocatorBuilder locator) {
802+
auto constraintLocator = cs.getConstraintLocator(locator);
803+
auto elementType = cs.isArrayType(argType);
804+
if (elementType &&
805+
constraintLocator->getLastElementAs<LocatorPathElt::ApplyArgToParam>()
806+
->getParameterFlags()
807+
.isVariadic()) {
808+
auto options = ConstraintSystem::TypeMatchOptions(
809+
ConstraintSystem::TypeMatchFlags::TMF_ApplyingFix |
810+
ConstraintSystem::TypeMatchFlags::TMF_GenerateConstraints);
811+
auto result =
812+
cs.matchTypes(*elementType, paramType,
813+
ConstraintKind::ArgumentConversion, options, locator);
814+
if (result.isSuccess())
815+
return new (cs.getAllocator())
816+
ExpandArrayIntoVarargs(cs, argType, paramType, constraintLocator);
817+
}
818+
819+
return nullptr;
820+
}
821+
822+
bool ExpandArrayIntoVarargs::diagnose(Expr *root, bool asNote) const {
823+
ExpandArrayIntoVarargsFailure failure(
824+
root, getConstraintSystem(), getFromType(), getToType(), getLocator());
825+
return failure.diagnose(asNote);
826+
}
827+
798828
ExplicitlyConstructRawRepresentable *
799829
ExplicitlyConstructRawRepresentable::attempt(ConstraintSystem &cs, Type argType,
800830
Type paramType,

lib/Sema/CSFix.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ enum class FixKind : uint8_t {
204204
/// Use raw value type associated with raw representative accessible
205205
/// using `.rawValue` member.
206206
UseValueTypeOfRawRepresentative,
207+
/// If an array was passed to a variadic argument, give a specific diagnostic
208+
/// and offer to drop the brackets if it's a literal.
209+
ExpandArrayIntoVarargs,
207210
};
208211

209212
class ConstraintFix {
@@ -1342,6 +1345,25 @@ class AllowArgumentMismatch : public ContextualMismatch {
13421345
ConstraintLocator *locator);
13431346
};
13441347

1348+
class ExpandArrayIntoVarargs final : public AllowArgumentMismatch {
1349+
1350+
ExpandArrayIntoVarargs(ConstraintSystem &cs, Type argType, Type paramType,
1351+
ConstraintLocator *locator)
1352+
: AllowArgumentMismatch(cs, FixKind::ExpandArrayIntoVarargs, argType,
1353+
paramType, locator) {}
1354+
1355+
public:
1356+
std::string getName() const override {
1357+
return "cannot pass Array elements as variadic arguments";
1358+
}
1359+
1360+
bool diagnose(Expr *root, bool asNote = false) const override;
1361+
1362+
static ExpandArrayIntoVarargs *attempt(ConstraintSystem &cs, Type argType,
1363+
Type paramType,
1364+
ConstraintLocatorBuilder locator);
1365+
};
1366+
13451367
class ExplicitlyConstructRawRepresentable final : public AllowArgumentMismatch {
13461368
ExplicitlyConstructRawRepresentable(ConstraintSystem &cs, Type argType,
13471369
Type paramType,

lib/Sema/CSSimplify.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -913,8 +913,8 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(
913913

914914
// Compare each of the bound arguments for this parameter.
915915
for (auto argIdx : parameterBindings[paramIdx]) {
916-
auto loc = locator.withPathElement(
917-
LocatorPathElt::ApplyArgToParam(argIdx, paramIdx));
916+
auto loc = locator.withPathElement(LocatorPathElt::ApplyArgToParam(
917+
argIdx, paramIdx, param.getParameterFlags()));
918918
auto argTy = argsWithLabels[argIdx].getOldType();
919919

920920
bool matchingAutoClosureResult = param.isAutoClosure();
@@ -2598,6 +2598,11 @@ bool ConstraintSystem::repairFailures(
25982598
elt.castTo<LocatorPathElt::ApplyArgToParam>().getParamIdx() == 1)
25992599
break;
26002600

2601+
if (auto *fix = ExpandArrayIntoVarargs::attempt(*this, lhs, rhs, locator)) {
2602+
conversionsOrFixes.push_back(fix);
2603+
break;
2604+
}
2605+
26012606
if (auto *fix = ExplicitlyConstructRawRepresentable::attempt(
26022607
*this, lhs, rhs, locator)) {
26032608
conversionsOrFixes.push_back(fix);
@@ -7610,6 +7615,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
76107615
case FixKind::SkipUnhandledConstructInFunctionBuilder:
76117616
case FixKind::UsePropertyWrapper:
76127617
case FixKind::UseWrappedValue:
7618+
case FixKind::ExpandArrayIntoVarargs:
76137619
case FixKind::UseValueTypeOfRawRepresentative:
76147620
case FixKind::ExplicitlyConstructRawRepresentable: {
76157621
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;

lib/Sema/ConstraintLocator.h

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,10 @@ class ConstraintLocator : public llvm::FoldingSetNode {
184184

185185
case TypeParameterRequirement:
186186
case ConditionalRequirement:
187-
case ApplyArgToParam:
188187
return 2;
188+
189+
case ApplyArgToParam:
190+
return 3;
189191
}
190192

191193
llvm_unreachable("Unhandled PathElementKind in switch.");
@@ -282,12 +284,12 @@ class ConstraintLocator : public llvm::FoldingSetNode {
282284
uint64_t storedKind : 3;
283285

284286
/// Encode a path element kind and a value into the storage format.
285-
static uint64_t encodeStorage(PathElementKind kind, unsigned value) {
286-
return ((uint64_t)value << 8) | kind;
287+
static uint64_t encodeStorage(PathElementKind kind, uint64_t value) {
288+
return (value << 8) | kind;
287289
}
288290

289291
/// Decode a storage value into path element kind and value.
290-
static std::pair<PathElementKind, unsigned>
292+
static std::pair<PathElementKind, uint64_t>
291293
decodeStorage(uint64_t storage) {
292294
return { (PathElementKind)((unsigned)storage & 0xFF), storage >> 8 };
293295
}
@@ -323,6 +325,17 @@ class ConstraintLocator : public llvm::FoldingSetNode {
323325
assert(value1 == getValue(1) && "value1 truncated");
324326
}
325327

328+
PathElement(PathElementKind kind, uint64_t value0, uint64_t value1,
329+
uint64_t value2)
330+
: storage(encodeStorage(kind, value0 << 32 | value1 << 16 | value2)),
331+
storedKind(StoredKindAndValue) {
332+
assert(numNumericValuesInPathElement(kind) == 3 &&
333+
"Path element kind does not require 3 values");
334+
assert(value0 == getValue(0) && "value0 truncated");
335+
assert(value1 == getValue(1) && "value1 truncated");
336+
assert(value2 == getValue(2) && "value2 truncated");
337+
}
338+
326339
/// Store a path element with an associated pointer, accessible using
327340
/// \c getStoredPointer.
328341
template <typename T>
@@ -695,11 +708,15 @@ dyn_cast(const LocatorPathElt &) = delete; // Use LocatorPathElt::getAs instead.
695708

696709
class LocatorPathElt::ApplyArgToParam final : public LocatorPathElt {
697710
public:
698-
ApplyArgToParam(unsigned argIdx, unsigned paramIdx)
699-
: LocatorPathElt(ConstraintLocator::ApplyArgToParam, argIdx, paramIdx) {}
711+
ApplyArgToParam(unsigned argIdx, unsigned paramIdx, ParameterTypeFlags flags)
712+
: LocatorPathElt(ConstraintLocator::ApplyArgToParam, argIdx, paramIdx,
713+
flags.toRaw()) {}
700714

701715
unsigned getArgIdx() const { return getValue(0); }
702716
unsigned getParamIdx() const { return getValue(1); }
717+
ParameterTypeFlags getParameterFlags() const {
718+
return ParameterTypeFlags::fromRaw(getValue(2));
719+
}
703720

704721
static bool classof(const LocatorPathElt *elt) {
705722
return elt->getKind() == ConstraintLocator::ApplyArgToParam;

0 commit comments

Comments
 (0)