Skip to content

Commit 96598d8

Browse files
authored
Merge pull request #26207 from owenv/new-vararg-conversion-diag
[Diagnostics] Improve diagnostic when attempting to pass an Array to a variadic argument
2 parents d6c92dc + 6c5185f commit 96598d8

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
@@ -4678,9 +4678,11 @@ bool ArgumentMismatchFailure::diagnoseUseOfReferenceEqualityOperator() const {
46784678
// one would cover both arguments.
46794679
if (getAnchor() == rhs && rhsType->is<FunctionType>()) {
46804680
auto &cs = getConstraintSystem();
4681-
if (cs.hasFixFor(cs.getConstraintLocator(
4682-
binaryOp, {ConstraintLocator::ApplyArgument,
4683-
LocatorPathElt::ApplyArgToParam(0, 0)})))
4681+
auto info = getFunctionArgApplyInfo(locator);
4682+
if (info && cs.hasFixFor(cs.getConstraintLocator(
4683+
binaryOp, {ConstraintLocator::ApplyArgument,
4684+
LocatorPathElt::ApplyArgToParam(
4685+
0, 0, info->getParameterFlagsAtIndex(0))})))
46844686
return true;
46854687
}
46864688

@@ -4830,3 +4832,44 @@ bool ArgumentMismatchFailure::diagnoseArchetypeMismatch() const {
48304832

48314833
return true;
48324834
}
4835+
4836+
void ExpandArrayIntoVarargsFailure::tryDropArrayBracketsFixIt(
4837+
Expr *anchor) const {
4838+
// If this is an array literal, offer to remove the brackets and pass the
4839+
// elements directly as variadic arguments.
4840+
if (auto *arrayExpr = dyn_cast<ArrayExpr>(anchor)) {
4841+
auto diag = emitDiagnostic(arrayExpr->getLoc(),
4842+
diag::suggest_pass_elements_directly);
4843+
diag.fixItRemove(arrayExpr->getLBracketLoc())
4844+
.fixItRemove(arrayExpr->getRBracketLoc());
4845+
// Handle the case where the array literal has a trailing comma.
4846+
if (arrayExpr->getNumCommas() == arrayExpr->getNumElements())
4847+
diag.fixItRemove(arrayExpr->getCommaLocs().back());
4848+
}
4849+
}
4850+
4851+
bool ExpandArrayIntoVarargsFailure::diagnoseAsError() {
4852+
if (auto anchor = getAnchor()) {
4853+
emitDiagnostic(anchor->getLoc(), diag::cannot_convert_array_to_variadic,
4854+
getFromType(), getToType());
4855+
tryDropArrayBracketsFixIt(anchor);
4856+
// TODO: Array splat fix-it once that's supported.
4857+
return true;
4858+
}
4859+
return false;
4860+
}
4861+
4862+
bool ExpandArrayIntoVarargsFailure::diagnoseAsNote() {
4863+
auto overload = getChoiceFor(getLocator());
4864+
auto anchor = getAnchor();
4865+
if (!overload || !anchor)
4866+
return false;
4867+
4868+
if (auto chosenDecl = overload->choice.getDeclOrNull()) {
4869+
emitDiagnostic(chosenDecl, diag::candidate_would_match_array_to_variadic,
4870+
getToType());
4871+
tryDropArrayBracketsFixIt(anchor);
4872+
return true;
4873+
}
4874+
return false;
4875+
}

lib/Sema/CSDiagnostics.h

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

15901604
bool diagnoseAsError() override;
1605+
bool diagnoseAsNote() override;
1606+
1607+
void tryDropArrayBracketsFixIt(Expr *anchor) const;
15911608
};
15921609

15931610
/// Diagnose a situation there is a mismatch between argument and parameter
@@ -1719,6 +1736,10 @@ class FunctionArgApplyInfo {
17191736
ParameterTypeFlags getParameterFlags() const {
17201737
return FnType->getParams()[ParamIdx].getParameterFlags();
17211738
}
1739+
1740+
ParameterTypeFlags getParameterFlagsAtIndex(unsigned idx) const {
1741+
return FnType->getParams()[idx].getParameterFlags();
1742+
}
17221743
};
17231744

17241745
} // end namespace constraints

lib/Sema/CSFix.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,36 @@ static bool isValueOfRawRepresentable(ConstraintSystem &cs,
815815
return false;
816816
}
817817

818+
ExpandArrayIntoVarargs *
819+
ExpandArrayIntoVarargs::attempt(ConstraintSystem &cs, Type argType,
820+
Type paramType,
821+
ConstraintLocatorBuilder locator) {
822+
auto constraintLocator = cs.getConstraintLocator(locator);
823+
auto elementType = cs.isArrayType(argType);
824+
if (elementType &&
825+
constraintLocator->getLastElementAs<LocatorPathElt::ApplyArgToParam>()
826+
->getParameterFlags()
827+
.isVariadic()) {
828+
auto options = ConstraintSystem::TypeMatchOptions(
829+
ConstraintSystem::TypeMatchFlags::TMF_ApplyingFix |
830+
ConstraintSystem::TypeMatchFlags::TMF_GenerateConstraints);
831+
auto result =
832+
cs.matchTypes(*elementType, paramType,
833+
ConstraintKind::ArgumentConversion, options, locator);
834+
if (result.isSuccess())
835+
return new (cs.getAllocator())
836+
ExpandArrayIntoVarargs(cs, argType, paramType, constraintLocator);
837+
}
838+
839+
return nullptr;
840+
}
841+
842+
bool ExpandArrayIntoVarargs::diagnose(Expr *root, bool asNote) const {
843+
ExpandArrayIntoVarargsFailure failure(
844+
root, getConstraintSystem(), getFromType(), getToType(), getLocator());
845+
return failure.diagnose(asNote);
846+
}
847+
818848
ExplicitlyConstructRawRepresentable *
819849
ExplicitlyConstructRawRepresentable::attempt(ConstraintSystem &cs, Type argType,
820850
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 {
@@ -1359,6 +1362,25 @@ class AllowArgumentMismatch : public ContextualMismatch {
13591362
ConstraintLocator *locator);
13601363
};
13611364

1365+
class ExpandArrayIntoVarargs final : public AllowArgumentMismatch {
1366+
1367+
ExpandArrayIntoVarargs(ConstraintSystem &cs, Type argType, Type paramType,
1368+
ConstraintLocator *locator)
1369+
: AllowArgumentMismatch(cs, FixKind::ExpandArrayIntoVarargs, argType,
1370+
paramType, locator) {}
1371+
1372+
public:
1373+
std::string getName() const override {
1374+
return "cannot pass Array elements as variadic arguments";
1375+
}
1376+
1377+
bool diagnose(Expr *root, bool asNote = false) const override;
1378+
1379+
static ExpandArrayIntoVarargs *attempt(ConstraintSystem &cs, Type argType,
1380+
Type paramType,
1381+
ConstraintLocatorBuilder locator);
1382+
};
1383+
13621384
class ExplicitlyConstructRawRepresentable final : public AllowArgumentMismatch {
13631385
ExplicitlyConstructRawRepresentable(ConstraintSystem &cs, Type argType,
13641386
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();
@@ -2626,6 +2626,11 @@ bool ConstraintSystem::repairFailures(
26262626
elt.castTo<LocatorPathElt::ApplyArgToParam>().getParamIdx() == 1)
26272627
break;
26282628

2629+
if (auto *fix = ExpandArrayIntoVarargs::attempt(*this, lhs, rhs, locator)) {
2630+
conversionsOrFixes.push_back(fix);
2631+
break;
2632+
}
2633+
26292634
if (auto *fix = ExplicitlyConstructRawRepresentable::attempt(
26302635
*this, lhs, rhs, locator)) {
26312636
conversionsOrFixes.push_back(fix);
@@ -7638,6 +7643,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
76387643
case FixKind::SkipUnhandledConstructInFunctionBuilder:
76397644
case FixKind::UsePropertyWrapper:
76407645
case FixKind::UseWrappedValue:
7646+
case FixKind::ExpandArrayIntoVarargs:
76417647
case FixKind::UseValueTypeOfRawRepresentative:
76427648
case FixKind::ExplicitlyConstructRawRepresentable: {
76437649
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)