[CSOptimizer] Emulate old behavior related to favoring of unary calls to members

Preserves old behavior where for unary calls to members
the solver would not consider choices that didn't match on
the number of parameters (regardless of defaults) and only
exact matches were favored.
This commit is contained in:
Pavel Yaskevich
2024-09-05 09:59:35 -07:00
parent 670127abd6
commit 527de22bec
2 changed files with 71 additions and 3 deletions

View File

@@ -91,6 +91,11 @@ void forEachDisjunctionChoice(
}
}
static bool isOverloadedDeclRef(Constraint *disjunction) {
assert(disjunction->getKind() == ConstraintKind::Disjunction);
return disjunction->getLocator()->directlyAt<OverloadedDeclRefExpr>();
}
} // end anonymous namespace
/// Given a set of disjunctions, attempt to determine
@@ -232,6 +237,7 @@ static void determineBestChoicesInContext(
enum class MatchFlag {
OnParam = 0x01,
Literal = 0x02,
ExactOnly = 0x04,
};
using MatchOptions = OptionSet<MatchFlag>;
@@ -250,6 +256,9 @@ static void determineBestChoicesInContext(
scoreCandidateMatch = [&](GenericSignature genericSig,
Type candidateType, Type paramType,
MatchOptions options) -> double {
if (options.contains(MatchFlag::ExactOnly))
return candidateType->isEqual(paramType) ? 1 : 0;
// Exact match between candidate and parameter types.
if (candidateType->isEqual(paramType))
return options.contains(MatchFlag::Literal) ? 0.3 : 1;
@@ -267,17 +276,17 @@ static void determineBestChoicesInContext(
paramType = paramType->lookThroughAllOptionalTypes(paramOptionals);
if (!candidateOptionals.empty() || !paramOptionals.empty()) {
if (paramOptionals.size() >= candidateOptionals.size())
if (paramOptionals.size() >= candidateOptionals.size()) {
return scoreCandidateMatch(genericSig, candidateType, paramType,
options);
}
// Optionality mismatch.
return 0;
}
}
// Candidate could be injected into optional parameter type
// or converted to a superclass.
// Candidate could be converted to a superclass.
if (isSubclassOf(candidateType, paramType))
return 1;
@@ -343,6 +352,8 @@ static void determineBestChoicesInContext(
double bestScore = 0.0;
SmallVector<std::pair<Constraint *, double>, 2> favoredChoices;
bool isOverloadedDeclRefDisjunction = isOverloadedDeclRef(disjunction);
forEachDisjunctionChoice(
cs, disjunction,
[&](Constraint *choice, ValueDecl *decl, FunctionType *overloadType) {
@@ -367,6 +378,20 @@ static void determineBestChoicesInContext(
if (!matchings)
return;
bool favorExactMatchesOnly = false;
// Preserves old behavior where for unary calls to members
// the solver would not consider choices that didn't match on
// the number of parameters (regardless of defaults) and only
// exact matches were favored.
if (!isOverloadedDeclRefDisjunction && argumentList->size() == 1) {
// Old behavior completely disregarded the fact that some of
// the parameters could be defaulted.
if (overloadType->getNumParams() != 1)
return;
favorExactMatchesOnly = true;
}
double score = 0.0;
for (unsigned paramIdx = 0, n = overloadType->getNumParams();
paramIdx != n; ++paramIdx) {
@@ -444,6 +469,8 @@ static void determineBestChoicesInContext(
MatchOptions options(MatchFlag::OnParam);
if (isLiteralDefault)
options |= MatchFlag::Literal;
if (favorExactMatchesOnly)
options |= MatchFlag::ExactOnly;
auto score = scoreCandidateMatch(genericSig, candidateType,
paramType, options);

View File

@@ -0,0 +1,41 @@
// RUN: %target-typecheck-verify-swift
func entity(_: Int) -> Int {
0
}
struct Test {
func test(_ v: Int) -> Int { v }
func test(_ v: Int?) -> Int? { v }
}
func test_ternary_literal(v: Test) -> Int? {
true ? v.test(0) : nil // Ok
}
func test_ternary(v: Test) -> Int? {
true ? v.test(entity(0)) : nil // Ok
}
do {
struct TestFloat {
func test(_ v: Float) -> Float { v } // expected-note {{found this candidate}}
func test(_ v: Float?) -> Float? { v } // expected-note {{found this candidate}}
}
func test_ternary_non_default_literal(v: TestFloat) -> Float? {
true ? v.test(1.0) : nil // expected-error {{ambiguous use of 'test'}}
}
}
do {
struct Test {
init(a: Int, b: Int = 0) throws {}
init?(a: Int?) {}
}
func test(v: Int) -> Test? {
return Test(a: v) // Ok
}
}