Skip to content

[Sema] Diagnostic: improve diagnostics for ApplyExpr. #17093

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 7 commits into from
Jul 12, 2018
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
63 changes: 63 additions & 0 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5665,6 +5665,34 @@ static bool shouldTypeCheckFunctionExpr(TypeChecker &TC, DeclContext *DC,
return true;
}

// Check if any candidate of the overload set can accept a specified
// number of arguments, regardless of parameter type or label information.
static bool isViableOverloadSet(const CalleeCandidateInfo &CCI,
size_t numArgs) {
for (unsigned i = 0; i < CCI.size(); ++i) {
auto &&cand = CCI[i];
auto funcDecl = dyn_cast_or_null<AbstractFunctionDecl>(cand.getDecl());
if (!funcDecl)
continue;

auto params = cand.getParameters();
bool hasVariadicParameter = false;
auto pairMatcher = [&](unsigned argIdx, unsigned paramIdx) {
hasVariadicParameter |= params[paramIdx].isVariadic();
return true;
};

auto defaultMap = computeDefaultMap(params, funcDecl, cand.level);
InputMatcher IM(params, defaultMap);
auto result = IM.match(numArgs, pairMatcher);
if (result == InputMatcher::IM_Succeeded)
return true;
if (result == InputMatcher::IM_HasUnclaimedInput && hasVariadicParameter)
return true;
}
return false;
}

bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
// If this call involves trailing closure as an argument,
// let's treat it specially, because re-typecheck of the
Expand Down Expand Up @@ -5826,6 +5854,41 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
// checked function.
CalleeCandidateInfo calleeInfo(fnExpr, hasTrailingClosure, CS);

// In the case that function subexpression was resolved independently in
// the first place, the resolved type may not provide the best diagnostic.
// We consider the number of arguments to decide whether we'd go with it or
// stay with the original one.
if (fnExpr != callExpr->getFn()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just counting arguments isn't necessarily going to work for default arguments and variadics.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @CodaFi here, unfortunately it's not as simple as only matching number of parameters, it needs to account for defaults and variadics as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an alternative approach - just add all of the overloads from original function to existing calleeInfo and filter via evaluateCloseness with the rest?

bool isInstanceMethodAsCurriedMemberOnType = false;
if (!calleeInfo.empty()) {
auto &&cand = calleeInfo[0];
auto decl = cand.getDecl();
if (decl && decl->isInstanceMember() && cand.level == 0 &&
cand.getParameters().size() == 1)
isInstanceMethodAsCurriedMemberOnType = true;
}

// In terms of instance method as curried member on type, we should not
// take the number of arguments into account.
if (!isInstanceMethodAsCurriedMemberOnType) {
size_t numArgs = 1;
auto arg = callExpr->getArg();
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
numArgs = tuple->getNumElements();
}

if (!isViableOverloadSet(calleeInfo, numArgs)) {
CalleeCandidateInfo calleeInfoOrig(callExpr->getFn(),
hasTrailingClosure, CS);
if (isViableOverloadSet(calleeInfoOrig, numArgs)) {
fnExpr = callExpr->getFn();
fnType = getFuncType(CS.getType(fnExpr));
calleeInfo = calleeInfoOrig;
}
}
}
}

// Filter list of the candidates based on the known function type.
if (auto fn = fnType->getAs<AnyFunctionType>()) {
using Closeness = CalleeCandidateInfo::ClosenessResultTy;
Expand Down
112 changes: 71 additions & 41 deletions lib/Sema/CSRanking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -606,8 +606,6 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
auto funcTy2 = openedType2->castTo<FunctionType>();
auto params1 = funcTy1->getParams();
auto params2 = funcTy2->getParams();
llvm::SmallBitVector defaultMapType2 =
computeDefaultMap(params2, decl2, outerDC2->isTypeContext());

unsigned numParams1 = params1.size();
unsigned numParams2 = params2.size();
Expand All @@ -620,8 +618,6 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
params1.back().getType()->is<AnyFunctionType>() &&
params2.back().getType()->is<AnyFunctionType>()) {
compareTrailingClosureParamsSeparately = true;
--numParams1;
--numParams2;
}
}

Expand All @@ -646,56 +642,38 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
return true;
};

for (unsigned param1 = 0, param2 = 0; param2 != numParams2; ++param2) {
// If there is a default for parameter in the second function
// while there are still some parameters left unclaimed in first,
// it could only mean that default parameters are intermixed e.g.
//
// ```swift
// func foo(a: Int) {}
// func foo(q: String = "", a: Int) {}
// ```
// or
// ```swift
// func foo(a: Int, c: Int) {}
// func foo(a: Int, b: Int = 0, c: Int) {}
// ```
// and we shouldn't claim parameter from the first function.
if (param1 < numParams1 && numParams1 != numParams2 &&
defaultMapType2[param2]) {
fewerEffectiveParameters = true;
continue;
}

// If we've claimed all of the parameters from first
// function, the rest of the parameters in second should
// be either default or variadic.
if (param1 >= numParams1) {
if (!defaultMapType2[param2] && !params2[param2].isVariadic())
return false;

fewerEffectiveParameters = true;
continue;
}

auto pairMatcher = [&](unsigned idx1, unsigned idx2) -> bool {
// Emulate behavior from when IUO was a type, where IUOs
// were considered subtypes of plain optionals, but not
// vice-versa. This wouldn't normally happen, but there are
// cases where we can rename imported APIs so that we have a
// name collision, and where the parameter type(s) are the
// same except for details of the kind of optional declared.
auto param1IsIUO = paramIsIUO(decl1, param1);
auto param2IsIUO = paramIsIUO(decl2, param2);
auto param1IsIUO = paramIsIUO(decl1, idx1);
auto param2IsIUO = paramIsIUO(decl2, idx2);
if (param2IsIUO && !param1IsIUO)
return false;

if (!maybeAddSubtypeConstraint(params1[param1], params2[param2]))
if (!maybeAddSubtypeConstraint(params1[idx1], params2[idx2]))
return false;

// claim the parameter as used.
++param1;
return true;
};

auto defaultMap = computeDefaultMap(
params2, decl2, decl2->getDeclContext()->isTypeContext());
auto params2ForMatching = params2;
if (compareTrailingClosureParamsSeparately) {
--numParams1;
params2ForMatching = params2.drop_back();
}

InputMatcher IM(params2ForMatching, defaultMap);
if (IM.match(numParams1, pairMatcher) != InputMatcher::IM_Succeeded)
return false;

fewerEffectiveParameters |= (IM.getNumSkippedParameters() != 0);

if (compareTrailingClosureParamsSeparately)
if (!maybeAddSubtypeConstraint(params1.back(), params2.back()))
knownNonSubtype = true;
Expand Down Expand Up @@ -1385,3 +1363,55 @@ SolutionDiff::SolutionDiff(ArrayRef<Solution> solutions) {
}
}
}

InputMatcher::InputMatcher(const ArrayRef<AnyFunctionType::Param> params,
const llvm::SmallBitVector &defaultValueMap)
: NumSkippedParameters(0), DefaultValueMap(defaultValueMap),
Params(params) {}

InputMatcher::Result
InputMatcher::match(int numInputs,
std::function<bool(unsigned, unsigned)> pairMatcher) {

int inputIdx = 0;
int numParams = Params.size();
for (int i = 0; i < numParams; ++i) {
// If we've claimed all of the inputs, the rest of the parameters should
// be either default or variadic.
if (inputIdx == numInputs) {
if (!DefaultValueMap[i] && !Params[i].isVariadic())
return IM_HasUnmatchedParam;
++NumSkippedParameters;
continue;
}

// If there is a default for parameter, while there are still some
// input left unclaimed, it could only mean that default parameters
// are intermixed e.g.
//
// inputs: (a: Int)
// params: (q: String = "", a: Int)
//
// or
// inputs: (a: Int, c: Int)
// params: (a: Int, b: Int = 0, c: Int)
//
// and we shouldn't claim any input and just skip such parameter.
if ((numInputs - inputIdx) < (numParams - i) && DefaultValueMap[i]) {
++NumSkippedParameters;
continue;
}

// Call custom function to match the input-parameter pair.
if (!pairMatcher(inputIdx, i))
return IM_CustomPairMatcherFailed;

// claim the input as used.
++inputIdx;
}

if (inputIdx < numInputs)
return IM_HasUnclaimedInput;

return IM_Succeeded;
}
19 changes: 19 additions & 0 deletions lib/Sema/CalleeCandidateInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,25 @@ CalleeCandidateInfo::CalleeCandidateInfo(Type baseType,
declName = candidates[0].getDecl()->getBaseName().userFacingName();
}

CalleeCandidateInfo &CalleeCandidateInfo::
operator=(const CalleeCandidateInfo &CCI) {
if (this != &CCI) {
// If the reference member (i.e., CS) is identical, just copy remaining
// members; otherwise, reconstruct the object.
if (&CS == &CCI.CS) {
declName = CCI.declName;
hasTrailingClosure = CCI.hasTrailingClosure;
candidates = CCI.candidates;
closeness = CCI.closeness;
failedArgument = CCI.failedArgument;
} else {
this->~CalleeCandidateInfo();
new (this) CalleeCandidateInfo(CCI);
}
}
return *this;
}

/// Given a set of parameter lists from an overload group, and a list of
/// arguments, emit a diagnostic indicating any partially matching overloads.
void CalleeCandidateInfo::
Expand Down
6 changes: 6 additions & 0 deletions lib/Sema/CalleeCandidateInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ namespace swift {
bool hasTrailingClosure, ConstraintSystem &CS,
bool selfAlreadyApplied = true);

~CalleeCandidateInfo() = default;
CalleeCandidateInfo(const CalleeCandidateInfo &CCI) = default;
CalleeCandidateInfo &operator=(const CalleeCandidateInfo &CCI);
CalleeCandidateInfo(CalleeCandidateInfo &&CCI) = delete;
CalleeCandidateInfo &operator=(CalleeCandidateInfo &&CCI) = delete;

using ClosenessResultTy = std::pair<CandidateCloseness, FailedArgumentInfo>;
using ClosenessPredicate =
const std::function<ClosenessResultTy(UncurriedCandidate)> &;
Expand Down
42 changes: 42 additions & 0 deletions lib/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -3486,6 +3486,48 @@ class OverloadSetCounter : public ASTWalker {
return { true, expr };
}
};

/// \brief Matches array of function parameters to candidate inputs,
/// which can be anything suitable (e.g., parameters, arguments).
///
/// It claims inputs sequentially and tries to pair between an input
/// and the next appropriate parameter. The detailed matching behavior
/// of each pair is specified by a custom function (i.e., pairMatcher).
/// It considers variadic and defaulted arguments when forming proper
/// input-parameter pairs; however, other information like label and
/// type information is not directly used here. It can be implemented
/// in the custom function when necessary.
class InputMatcher {
size_t NumSkippedParameters;
const llvm::SmallBitVector DefaultValueMap;
const ArrayRef<AnyFunctionType::Param> Params;

public:
enum Result {
/// The specified inputs are successfully matched.
IM_Succeeded,
/// There are input(s) left unclaimed while all parameters are matched.
IM_HasUnclaimedInput,
/// There are parateter(s) left unmatched while all inputs are claimed.
IM_HasUnmatchedParam,
/// Custom pair matcher function failure.
IM_CustomPairMatcherFailed,
};

InputMatcher(const ArrayRef<AnyFunctionType::Param> params,
const llvm::SmallBitVector &defaultValueMap);

/// Matching a given array of inputs.
///
/// \param numInputs The number of inputs.
/// \param pairMatcher Custom matching behavior of an input-parameter pair.
/// \return the matching result.
Result
match(int numInputs,
std::function<bool(unsigned inputIdx, unsigned paramIdx)> pairMatcher);

size_t getNumSkippedParameters() const { return NumSkippedParameters; }
};
} // end namespace swift

#endif // LLVM_SWIFT_SEMA_CONSTRAINT_SYSTEM_H
Loading