Skip to content

[Diagnostics] Improve diagnostic when attempting to pass an Array to a variadic argument #26207

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,15 @@ NOTE(candidate_has_invalid_argument_at_position,none,
"candidate expects value of type %0 at position #%1",
(Type, unsigned))

ERROR(cannot_convert_array_to_variadic,none,
"cannot pass array of type %0 as variadic arguments of type %1",
(Type,Type))
NOTE(candidate_would_match_array_to_variadic,none,
"candidate would match if array elements were passed as"
" variadic arguments of type %0", (Type))
NOTE(suggest_pass_elements_directly,none,
"remove brackets to pass array elements directly", ())

ERROR(cannot_convert_argument_value_generic,none,
"cannot convert value of type %0 (%1) to expected argument type %2 (%3)",
(Type, StringRef, Type, StringRef))
Expand Down
17 changes: 10 additions & 7 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5384,10 +5384,11 @@ Expr *ExprRewriter::coerceCallArguments(
auto params = funcType->getParams();

// Local function to produce a locator to refer to the given parameter.
auto getArgLocator = [&](unsigned argIdx, unsigned paramIdx)
-> ConstraintLocatorBuilder {
auto getArgLocator =
[&](unsigned argIdx, unsigned paramIdx,
ParameterTypeFlags flags) -> ConstraintLocatorBuilder {
return locator.withPathElement(
LocatorPathElt::ApplyArgToParam(argIdx, paramIdx));
LocatorPathElt::ApplyArgToParam(argIdx, paramIdx, flags));
};

bool matchCanFail =
Expand Down Expand Up @@ -5499,8 +5500,9 @@ Expr *ExprRewriter::coerceCallArguments(
}

// Convert the argument.
auto convertedArg = coerceToType(arg, param.getPlainType(),
getArgLocator(argIdx, paramIdx));
auto convertedArg = coerceToType(
arg, param.getPlainType(),
getArgLocator(argIdx, paramIdx, param.getParameterFlags()));
if (!convertedArg)
return nullptr;

Expand Down Expand Up @@ -5620,8 +5622,9 @@ Expr *ExprRewriter::coerceCallArguments(
convertedArg = cs.TC.buildAutoClosureExpr(dc, arg, closureType);
cs.cacheExprTypes(convertedArg);
} else {
convertedArg =
coerceToType(arg, paramType, getArgLocator(argIdx, paramIdx));
convertedArg = coerceToType(
arg, paramType,
getArgLocator(argIdx, paramIdx, param.getParameterFlags()));
}

if (!convertedArg)
Expand Down
15 changes: 10 additions & 5 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3461,11 +3461,16 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
// case (let (_, _, _)) + 1: break
// }
if (callExpr->isImplicit() && overloadName == "~=") {
auto *locator =
CS.getConstraintLocator(callExpr,
{ConstraintLocator::ApplyArgument,
LocatorPathElt::ApplyArgToParam(0, 0)},
/*summaryFlags=*/0);
auto flags = ParameterTypeFlags();
if (calleeInfo.candidates.size() == 1)
if (auto fnType = calleeInfo.candidates[0].getFunctionType())
flags = fnType->getParams()[0].getParameterFlags();

auto *locator = CS.getConstraintLocator(
callExpr,
{ConstraintLocator::ApplyArgument,
LocatorPathElt::ApplyArgToParam(0, 0, flags)},
/*summaryFlags=*/0);

ArgumentMismatchFailure failure(expr, CS, lhsType, rhsType, locator);
return failure.diagnosePatternMatchingMismatch();
Expand Down
49 changes: 46 additions & 3 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4611,9 +4611,11 @@ bool ArgumentMismatchFailure::diagnoseUseOfReferenceEqualityOperator() const {
// one would cover both arguments.
if (getAnchor() == rhs && rhsType->is<FunctionType>()) {
auto &cs = getConstraintSystem();
if (cs.hasFixFor(cs.getConstraintLocator(
binaryOp, {ConstraintLocator::ApplyArgument,
LocatorPathElt::ApplyArgToParam(0, 0)})))
auto info = getFunctionArgApplyInfo(locator);
if (info && cs.hasFixFor(cs.getConstraintLocator(
binaryOp, {ConstraintLocator::ApplyArgument,
LocatorPathElt::ApplyArgToParam(
0, 0, info->getParameterFlagsAtIndex(0))})))
return true;
}

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

return true;
}

void ExpandArrayIntoVarargsFailure::tryDropArrayBracketsFixIt(
Expr *anchor) const {
// If this is an array literal, offer to remove the brackets and pass the
// elements directly as variadic arguments.
if (auto *arrayExpr = dyn_cast<ArrayExpr>(anchor)) {
auto diag = emitDiagnostic(arrayExpr->getLoc(),
diag::suggest_pass_elements_directly);
diag.fixItRemove(arrayExpr->getLBracketLoc())
.fixItRemove(arrayExpr->getRBracketLoc());
// Handle the case where the array literal has a trailing comma.
if (arrayExpr->getNumCommas() == arrayExpr->getNumElements())
diag.fixItRemove(arrayExpr->getCommaLocs().back());
}
}

bool ExpandArrayIntoVarargsFailure::diagnoseAsError() {
if (auto anchor = getAnchor()) {
emitDiagnostic(anchor->getLoc(), diag::cannot_convert_array_to_variadic,
getFromType(), getToType());
tryDropArrayBracketsFixIt(anchor);
// TODO: Array splat fix-it once that's supported.
return true;
}
return false;
}

bool ExpandArrayIntoVarargsFailure::diagnoseAsNote() {
auto overload = getChoiceFor(getLocator());
auto anchor = getAnchor();
if (!overload || !anchor)
return false;

if (auto chosenDecl = overload->choice.getDeclOrNull()) {
emitDiagnostic(chosenDecl, diag::candidate_would_match_array_to_variadic,
getToType());
tryDropArrayBracketsFixIt(anchor);
return true;
}
return false;
}
21 changes: 21 additions & 0 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -1582,8 +1582,25 @@ class InvalidTupleSplatWithSingleParameterFailure final
Type paramTy,
ConstraintLocator *locator)
: FailureDiagnostic(root, cs, locator), ParamType(paramTy) {}
bool diagnoseAsError() override;
};

/// Diagnose situation when an array is passed instead of varargs.
///
/// ```swift
/// func foo(_ x: Int...) {}
/// foo([1,2,3]]) // foo expects varags like foo(1,2,3) instead.
/// ```
class ExpandArrayIntoVarargsFailure final : public ContextualFailure {
public:
ExpandArrayIntoVarargsFailure(Expr *root, ConstraintSystem &cs, Type lhs,
Type rhs, ConstraintLocator *locator)
: ContextualFailure(root, cs, lhs, rhs, locator) {}

bool diagnoseAsError() override;
bool diagnoseAsNote() override;

void tryDropArrayBracketsFixIt(Expr *anchor) const;
};

/// Diagnose a situation there is a mismatch between argument and parameter
Expand Down Expand Up @@ -1715,6 +1732,10 @@ class FunctionArgApplyInfo {
ParameterTypeFlags getParameterFlags() const {
return FnType->getParams()[ParamIdx].getParameterFlags();
}

ParameterTypeFlags getParameterFlagsAtIndex(unsigned idx) const {
return FnType->getParams()[idx].getParameterFlags();
}
};

} // end namespace constraints
Expand Down
30 changes: 30 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -795,6 +795,36 @@ static bool isValueOfRawRepresentable(ConstraintSystem &cs,
return false;
}

ExpandArrayIntoVarargs *
ExpandArrayIntoVarargs::attempt(ConstraintSystem &cs, Type argType,
Type paramType,
ConstraintLocatorBuilder locator) {
auto constraintLocator = cs.getConstraintLocator(locator);
auto elementType = cs.isArrayType(argType);
if (elementType &&
constraintLocator->getLastElementAs<LocatorPathElt::ApplyArgToParam>()
->getParameterFlags()
.isVariadic()) {
auto options = ConstraintSystem::TypeMatchOptions(
ConstraintSystem::TypeMatchFlags::TMF_ApplyingFix |
ConstraintSystem::TypeMatchFlags::TMF_GenerateConstraints);
auto result =
cs.matchTypes(*elementType, paramType,
ConstraintKind::ArgumentConversion, options, locator);
if (result.isSuccess())
return new (cs.getAllocator())
ExpandArrayIntoVarargs(cs, argType, paramType, constraintLocator);
}

return nullptr;
}

bool ExpandArrayIntoVarargs::diagnose(Expr *root, bool asNote) const {
ExpandArrayIntoVarargsFailure failure(
root, getConstraintSystem(), getFromType(), getToType(), getLocator());
return failure.diagnose(asNote);
}

ExplicitlyConstructRawRepresentable *
ExplicitlyConstructRawRepresentable::attempt(ConstraintSystem &cs, Type argType,
Type paramType,
Expand Down
22 changes: 22 additions & 0 deletions lib/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ enum class FixKind : uint8_t {
/// Use raw value type associated with raw representative accessible
/// using `.rawValue` member.
UseValueTypeOfRawRepresentative,
/// If an array was passed to a variadic argument, give a specific diagnostic
/// and offer to drop the brackets if it's a literal.
ExpandArrayIntoVarargs,
};

class ConstraintFix {
Expand Down Expand Up @@ -1342,6 +1345,25 @@ class AllowArgumentMismatch : public ContextualMismatch {
ConstraintLocator *locator);
};

class ExpandArrayIntoVarargs final : public AllowArgumentMismatch {

ExpandArrayIntoVarargs(ConstraintSystem &cs, Type argType, Type paramType,
ConstraintLocator *locator)
: AllowArgumentMismatch(cs, FixKind::ExpandArrayIntoVarargs, argType,
paramType, locator) {}

public:
std::string getName() const override {
return "cannot pass Array elements as variadic arguments";
}

bool diagnose(Expr *root, bool asNote = false) const override;

static ExpandArrayIntoVarargs *attempt(ConstraintSystem &cs, Type argType,
Type paramType,
ConstraintLocatorBuilder locator);
};

class ExplicitlyConstructRawRepresentable final : public AllowArgumentMismatch {
ExplicitlyConstructRawRepresentable(ConstraintSystem &cs, Type argType,
Type paramType,
Expand Down
10 changes: 8 additions & 2 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -913,8 +913,8 @@ ConstraintSystem::TypeMatchResult constraints::matchCallArguments(

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

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

if (auto *fix = ExpandArrayIntoVarargs::attempt(*this, lhs, rhs, locator)) {
conversionsOrFixes.push_back(fix);
break;
}

if (auto *fix = ExplicitlyConstructRawRepresentable::attempt(
*this, lhs, rhs, locator)) {
conversionsOrFixes.push_back(fix);
Expand Down Expand Up @@ -7610,6 +7615,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
case FixKind::SkipUnhandledConstructInFunctionBuilder:
case FixKind::UsePropertyWrapper:
case FixKind::UseWrappedValue:
case FixKind::ExpandArrayIntoVarargs:
case FixKind::UseValueTypeOfRawRepresentative:
case FixKind::ExplicitlyConstructRawRepresentable: {
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
Expand Down
29 changes: 23 additions & 6 deletions lib/Sema/ConstraintLocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ class ConstraintLocator : public llvm::FoldingSetNode {

case TypeParameterRequirement:
case ConditionalRequirement:
case ApplyArgToParam:
return 2;

case ApplyArgToParam:
return 3;
}

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

/// Encode a path element kind and a value into the storage format.
static uint64_t encodeStorage(PathElementKind kind, unsigned value) {
return ((uint64_t)value << 8) | kind;
static uint64_t encodeStorage(PathElementKind kind, uint64_t value) {
return (value << 8) | kind;
}

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

PathElement(PathElementKind kind, uint64_t value0, uint64_t value1,
uint64_t value2)
: storage(encodeStorage(kind, value0 << 32 | value1 << 16 | value2)),
storedKind(StoredKindAndValue) {
assert(numNumericValuesInPathElement(kind) == 3 &&
"Path element kind does not require 3 values");
assert(value0 == getValue(0) && "value0 truncated");
assert(value1 == getValue(1) && "value1 truncated");
assert(value2 == getValue(2) && "value2 truncated");
}

/// Store a path element with an associated pointer, accessible using
/// \c getStoredPointer.
template <typename T>
Expand Down Expand Up @@ -695,11 +708,15 @@ dyn_cast(const LocatorPathElt &) = delete; // Use LocatorPathElt::getAs instead.

class LocatorPathElt::ApplyArgToParam final : public LocatorPathElt {
public:
ApplyArgToParam(unsigned argIdx, unsigned paramIdx)
: LocatorPathElt(ConstraintLocator::ApplyArgToParam, argIdx, paramIdx) {}
ApplyArgToParam(unsigned argIdx, unsigned paramIdx, ParameterTypeFlags flags)
: LocatorPathElt(ConstraintLocator::ApplyArgToParam, argIdx, paramIdx,
flags.toRaw()) {}

unsigned getArgIdx() const { return getValue(0); }
unsigned getParamIdx() const { return getValue(1); }
ParameterTypeFlags getParameterFlags() const {
return ParameterTypeFlags::fromRaw(getValue(2));
}

static bool classof(const LocatorPathElt *elt) {
return elt->getKind() == ConstraintLocator::ApplyArgToParam;
Expand Down
Loading