From 9445a9fca9290ab0f936d86bb8356264ca4f299a Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Tue, 29 Jul 2025 16:49:47 -0700 Subject: [PATCH] [CSOptimizer] Decrease an operator argument score only if requires optional injection This makes sure that optional and non-optional types are ranked uniformly when matched against a generic parameter type that could accept either of them. This is a more general fix for https://github.com/swiftlang/swift/pull/83365 --- lib/Sema/CSOptimizer.cpp | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/lib/Sema/CSOptimizer.cpp b/lib/Sema/CSOptimizer.cpp index 26e513bfb5e6a..15f531700f237 100644 --- a/lib/Sema/CSOptimizer.cpp +++ b/lib/Sema/CSOptimizer.cpp @@ -970,8 +970,6 @@ static void determineBestChoicesInContext( bool hasArgumentCandidates = false; bool isOperator = isOperatorDisjunction(disjunction); - bool isNilCoalescingOperator = - isOperator && isOperatorNamed(disjunction, "??"); for (unsigned i = 0, n = argFuncType->getNumParams(); i != n; ++i) { const auto ¶m = argFuncType->getParams()[i]; @@ -1341,17 +1339,26 @@ static void determineBestChoicesInContext( paramType = paramType->lookThroughAllOptionalTypes(paramOptionals); if (!candidateOptionals.empty() || !paramOptionals.empty()) { + auto requiresOptionalInjection = [&]() { + return paramOptionals.size() > candidateOptionals.size(); + }; + // Can match i.e. Int? to Int or T to Int? if ((paramOptionals.empty() && paramType->is()) || paramOptionals.size() >= candidateOptionals.size()) { auto score = scoreCandidateMatch(genericSig, choice, candidateType, paramType, options); - // Injection lowers the score slightly to comply with - // old behavior where exact matches on operator parameter - // types were always preferred. - return score > 0 && choice->isOperator() ? score.value() - 0.1 - : score; + + if (score > 0) { + // Injection lowers the score slightly to comply with + // old behavior where exact matches on operator parameter + // types were always preferred. + if (choice->isOperator() && requiresOptionalInjection()) + return score.value() - 0.1; + } + + return score; } // Optionality mismatch. @@ -1605,20 +1612,6 @@ static void determineBestChoicesInContext( double bestCandidateScore = 0; llvm::BitVector mismatches(argumentCandidates[argIdx].size()); - // `??` is overloaded on optionality of the second parameter, - // prevent ranking the argument candidates for this parameter - // if there are candidates that come from failable initializer - // overloads because non-optional candidates are always going - // to be better and that can skew the selection. - if (isNilCoalescingOperator && argIdx == 1) { - if (llvm::any_of(argumentCandidates[argIdx], - [](const auto &candidate) { - return candidate.fromInitializerCall && - candidate.type->getOptionalObjectType(); - })) - continue; - } - for (unsigned candidateIdx : indices(argumentCandidates[argIdx])) { // If one of the candidates matched exactly there is no reason