Skip to content

Commit 5976980

Browse files
authored
Merge pull request #17093 from dingobye/SR-7918
[Sema] Diagnostic: improve diagnostics for ApplyExpr.
2 parents b02d554 + 95b91ee commit 5976980

File tree

7 files changed

+291
-45
lines changed

7 files changed

+291
-45
lines changed

lib/Sema/CSDiag.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5742,6 +5742,34 @@ static bool shouldTypeCheckFunctionExpr(TypeChecker &TC, DeclContext *DC,
57425742
return true;
57435743
}
57445744

5745+
// Check if any candidate of the overload set can accept a specified
5746+
// number of arguments, regardless of parameter type or label information.
5747+
static bool isViableOverloadSet(const CalleeCandidateInfo &CCI,
5748+
size_t numArgs) {
5749+
for (unsigned i = 0; i < CCI.size(); ++i) {
5750+
auto &&cand = CCI[i];
5751+
auto funcDecl = dyn_cast_or_null<AbstractFunctionDecl>(cand.getDecl());
5752+
if (!funcDecl)
5753+
continue;
5754+
5755+
auto params = cand.getParameters();
5756+
bool hasVariadicParameter = false;
5757+
auto pairMatcher = [&](unsigned argIdx, unsigned paramIdx) {
5758+
hasVariadicParameter |= params[paramIdx].isVariadic();
5759+
return true;
5760+
};
5761+
5762+
auto defaultMap = computeDefaultMap(params, funcDecl, cand.level);
5763+
InputMatcher IM(params, defaultMap);
5764+
auto result = IM.match(numArgs, pairMatcher);
5765+
if (result == InputMatcher::IM_Succeeded)
5766+
return true;
5767+
if (result == InputMatcher::IM_HasUnclaimedInput && hasVariadicParameter)
5768+
return true;
5769+
}
5770+
return false;
5771+
}
5772+
57455773
bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
57465774
// If this call involves trailing closure as an argument,
57475775
// let's treat it specially, because re-typecheck of the
@@ -5903,6 +5931,41 @@ bool FailureDiagnosis::visitApplyExpr(ApplyExpr *callExpr) {
59035931
// checked function.
59045932
CalleeCandidateInfo calleeInfo(fnExpr, hasTrailingClosure, CS);
59055933

5934+
// In the case that function subexpression was resolved independently in
5935+
// the first place, the resolved type may not provide the best diagnostic.
5936+
// We consider the number of arguments to decide whether we'd go with it or
5937+
// stay with the original one.
5938+
if (fnExpr != callExpr->getFn()) {
5939+
bool isInstanceMethodAsCurriedMemberOnType = false;
5940+
if (!calleeInfo.empty()) {
5941+
auto &&cand = calleeInfo[0];
5942+
auto decl = cand.getDecl();
5943+
if (decl && decl->isInstanceMember() && cand.level == 0 &&
5944+
cand.getParameters().size() == 1)
5945+
isInstanceMethodAsCurriedMemberOnType = true;
5946+
}
5947+
5948+
// In terms of instance method as curried member on type, we should not
5949+
// take the number of arguments into account.
5950+
if (!isInstanceMethodAsCurriedMemberOnType) {
5951+
size_t numArgs = 1;
5952+
auto arg = callExpr->getArg();
5953+
if (auto tuple = dyn_cast<TupleExpr>(arg)) {
5954+
numArgs = tuple->getNumElements();
5955+
}
5956+
5957+
if (!isViableOverloadSet(calleeInfo, numArgs)) {
5958+
CalleeCandidateInfo calleeInfoOrig(callExpr->getFn(),
5959+
hasTrailingClosure, CS);
5960+
if (isViableOverloadSet(calleeInfoOrig, numArgs)) {
5961+
fnExpr = callExpr->getFn();
5962+
fnType = getFuncType(CS.getType(fnExpr));
5963+
calleeInfo = calleeInfoOrig;
5964+
}
5965+
}
5966+
}
5967+
}
5968+
59065969
// Filter list of the candidates based on the known function type.
59075970
if (auto fn = fnType->getAs<AnyFunctionType>()) {
59085971
using Closeness = CalleeCandidateInfo::ClosenessResultTy;

lib/Sema/CSRanking.cpp

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -606,8 +606,6 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
606606
auto funcTy2 = openedType2->castTo<FunctionType>();
607607
auto params1 = funcTy1->getParams();
608608
auto params2 = funcTy2->getParams();
609-
llvm::SmallBitVector defaultMapType2 =
610-
computeDefaultMap(params2, decl2, outerDC2->isTypeContext());
611609

612610
unsigned numParams1 = params1.size();
613611
unsigned numParams2 = params2.size();
@@ -620,8 +618,6 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
620618
params1.back().getType()->is<AnyFunctionType>() &&
621619
params2.back().getType()->is<AnyFunctionType>()) {
622620
compareTrailingClosureParamsSeparately = true;
623-
--numParams1;
624-
--numParams2;
625621
}
626622
}
627623

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

649-
for (unsigned param1 = 0, param2 = 0; param2 != numParams2; ++param2) {
650-
// If there is a default for parameter in the second function
651-
// while there are still some parameters left unclaimed in first,
652-
// it could only mean that default parameters are intermixed e.g.
653-
//
654-
// ```swift
655-
// func foo(a: Int) {}
656-
// func foo(q: String = "", a: Int) {}
657-
// ```
658-
// or
659-
// ```swift
660-
// func foo(a: Int, c: Int) {}
661-
// func foo(a: Int, b: Int = 0, c: Int) {}
662-
// ```
663-
// and we shouldn't claim parameter from the first function.
664-
if (param1 < numParams1 && numParams1 != numParams2 &&
665-
defaultMapType2[param2]) {
666-
fewerEffectiveParameters = true;
667-
continue;
668-
}
669-
670-
// If we've claimed all of the parameters from first
671-
// function, the rest of the parameters in second should
672-
// be either default or variadic.
673-
if (param1 >= numParams1) {
674-
if (!defaultMapType2[param2] && !params2[param2].isVariadic())
675-
return false;
676-
677-
fewerEffectiveParameters = true;
678-
continue;
679-
}
680-
645+
auto pairMatcher = [&](unsigned idx1, unsigned idx2) -> bool {
681646
// Emulate behavior from when IUO was a type, where IUOs
682647
// were considered subtypes of plain optionals, but not
683648
// vice-versa. This wouldn't normally happen, but there are
684649
// cases where we can rename imported APIs so that we have a
685650
// name collision, and where the parameter type(s) are the
686651
// same except for details of the kind of optional declared.
687-
auto param1IsIUO = paramIsIUO(decl1, param1);
688-
auto param2IsIUO = paramIsIUO(decl2, param2);
652+
auto param1IsIUO = paramIsIUO(decl1, idx1);
653+
auto param2IsIUO = paramIsIUO(decl2, idx2);
689654
if (param2IsIUO && !param1IsIUO)
690655
return false;
691656

692-
if (!maybeAddSubtypeConstraint(params1[param1], params2[param2]))
657+
if (!maybeAddSubtypeConstraint(params1[idx1], params2[idx2]))
693658
return false;
694659

695-
// claim the parameter as used.
696-
++param1;
660+
return true;
661+
};
662+
663+
auto defaultMap = computeDefaultMap(
664+
params2, decl2, decl2->getDeclContext()->isTypeContext());
665+
auto params2ForMatching = params2;
666+
if (compareTrailingClosureParamsSeparately) {
667+
--numParams1;
668+
params2ForMatching = params2.drop_back();
697669
}
698670

671+
InputMatcher IM(params2ForMatching, defaultMap);
672+
if (IM.match(numParams1, pairMatcher) != InputMatcher::IM_Succeeded)
673+
return false;
674+
675+
fewerEffectiveParameters |= (IM.getNumSkippedParameters() != 0);
676+
699677
if (compareTrailingClosureParamsSeparately)
700678
if (!maybeAddSubtypeConstraint(params1.back(), params2.back()))
701679
knownNonSubtype = true;
@@ -1385,3 +1363,55 @@ SolutionDiff::SolutionDiff(ArrayRef<Solution> solutions) {
13851363
}
13861364
}
13871365
}
1366+
1367+
InputMatcher::InputMatcher(const ArrayRef<AnyFunctionType::Param> params,
1368+
const llvm::SmallBitVector &defaultValueMap)
1369+
: NumSkippedParameters(0), DefaultValueMap(defaultValueMap),
1370+
Params(params) {}
1371+
1372+
InputMatcher::Result
1373+
InputMatcher::match(int numInputs,
1374+
std::function<bool(unsigned, unsigned)> pairMatcher) {
1375+
1376+
int inputIdx = 0;
1377+
int numParams = Params.size();
1378+
for (int i = 0; i < numParams; ++i) {
1379+
// If we've claimed all of the inputs, the rest of the parameters should
1380+
// be either default or variadic.
1381+
if (inputIdx == numInputs) {
1382+
if (!DefaultValueMap[i] && !Params[i].isVariadic())
1383+
return IM_HasUnmatchedParam;
1384+
++NumSkippedParameters;
1385+
continue;
1386+
}
1387+
1388+
// If there is a default for parameter, while there are still some
1389+
// input left unclaimed, it could only mean that default parameters
1390+
// are intermixed e.g.
1391+
//
1392+
// inputs: (a: Int)
1393+
// params: (q: String = "", a: Int)
1394+
//
1395+
// or
1396+
// inputs: (a: Int, c: Int)
1397+
// params: (a: Int, b: Int = 0, c: Int)
1398+
//
1399+
// and we shouldn't claim any input and just skip such parameter.
1400+
if ((numInputs - inputIdx) < (numParams - i) && DefaultValueMap[i]) {
1401+
++NumSkippedParameters;
1402+
continue;
1403+
}
1404+
1405+
// Call custom function to match the input-parameter pair.
1406+
if (!pairMatcher(inputIdx, i))
1407+
return IM_CustomPairMatcherFailed;
1408+
1409+
// claim the input as used.
1410+
++inputIdx;
1411+
}
1412+
1413+
if (inputIdx < numInputs)
1414+
return IM_HasUnclaimedInput;
1415+
1416+
return IM_Succeeded;
1417+
}

lib/Sema/CalleeCandidateInfo.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,25 @@ CalleeCandidateInfo::CalleeCandidateInfo(Type baseType,
880880
declName = candidates[0].getDecl()->getBaseName().userFacingName();
881881
}
882882

883+
CalleeCandidateInfo &CalleeCandidateInfo::
884+
operator=(const CalleeCandidateInfo &CCI) {
885+
if (this != &CCI) {
886+
// If the reference member (i.e., CS) is identical, just copy remaining
887+
// members; otherwise, reconstruct the object.
888+
if (&CS == &CCI.CS) {
889+
declName = CCI.declName;
890+
hasTrailingClosure = CCI.hasTrailingClosure;
891+
candidates = CCI.candidates;
892+
closeness = CCI.closeness;
893+
failedArgument = CCI.failedArgument;
894+
} else {
895+
this->~CalleeCandidateInfo();
896+
new (this) CalleeCandidateInfo(CCI);
897+
}
898+
}
899+
return *this;
900+
}
901+
883902
/// Given a set of parameter lists from an overload group, and a list of
884903
/// arguments, emit a diagnostic indicating any partially matching overloads.
885904
void CalleeCandidateInfo::

lib/Sema/CalleeCandidateInfo.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,12 @@ namespace swift {
188188
bool hasTrailingClosure, ConstraintSystem &CS,
189189
bool selfAlreadyApplied = true);
190190

191+
~CalleeCandidateInfo() = default;
192+
CalleeCandidateInfo(const CalleeCandidateInfo &CCI) = default;
193+
CalleeCandidateInfo &operator=(const CalleeCandidateInfo &CCI);
194+
CalleeCandidateInfo(CalleeCandidateInfo &&CCI) = delete;
195+
CalleeCandidateInfo &operator=(CalleeCandidateInfo &&CCI) = delete;
196+
191197
using ClosenessResultTy = std::pair<CandidateCloseness, FailedArgumentInfo>;
192198
using ClosenessPredicate =
193199
const std::function<ClosenessResultTy(UncurriedCandidate)> &;

lib/Sema/ConstraintSystem.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3486,6 +3486,48 @@ class OverloadSetCounter : public ASTWalker {
34863486
return { true, expr };
34873487
}
34883488
};
3489+
3490+
/// \brief Matches array of function parameters to candidate inputs,
3491+
/// which can be anything suitable (e.g., parameters, arguments).
3492+
///
3493+
/// It claims inputs sequentially and tries to pair between an input
3494+
/// and the next appropriate parameter. The detailed matching behavior
3495+
/// of each pair is specified by a custom function (i.e., pairMatcher).
3496+
/// It considers variadic and defaulted arguments when forming proper
3497+
/// input-parameter pairs; however, other information like label and
3498+
/// type information is not directly used here. It can be implemented
3499+
/// in the custom function when necessary.
3500+
class InputMatcher {
3501+
size_t NumSkippedParameters;
3502+
const llvm::SmallBitVector DefaultValueMap;
3503+
const ArrayRef<AnyFunctionType::Param> Params;
3504+
3505+
public:
3506+
enum Result {
3507+
/// The specified inputs are successfully matched.
3508+
IM_Succeeded,
3509+
/// There are input(s) left unclaimed while all parameters are matched.
3510+
IM_HasUnclaimedInput,
3511+
/// There are parateter(s) left unmatched while all inputs are claimed.
3512+
IM_HasUnmatchedParam,
3513+
/// Custom pair matcher function failure.
3514+
IM_CustomPairMatcherFailed,
3515+
};
3516+
3517+
InputMatcher(const ArrayRef<AnyFunctionType::Param> params,
3518+
const llvm::SmallBitVector &defaultValueMap);
3519+
3520+
/// Matching a given array of inputs.
3521+
///
3522+
/// \param numInputs The number of inputs.
3523+
/// \param pairMatcher Custom matching behavior of an input-parameter pair.
3524+
/// \return the matching result.
3525+
Result
3526+
match(int numInputs,
3527+
std::function<bool(unsigned inputIdx, unsigned paramIdx)> pairMatcher);
3528+
3529+
size_t getNumSkippedParameters() const { return NumSkippedParameters; }
3530+
};
34893531
} // end namespace swift
34903532

34913533
#endif // LLVM_SWIFT_SEMA_CONSTRAINT_SYSTEM_H

0 commit comments

Comments
 (0)