[CSOptimizer] Add support for opened existential arguments

Check whether it would be possible to match a parameter type
by opening an existential type of the candidate argument.

Resolves: rdar://158159462
This commit is contained in:
Pavel Yaskevich
2025-08-14 13:52:38 -07:00
parent 90224bf927
commit 0d33af78ca
3 changed files with 46 additions and 11 deletions

View File

@@ -15,6 +15,7 @@
//===----------------------------------------------------------------------===//
#include "TypeChecker.h"
#include "OpenedExistentials.h"
#include "swift/AST/ConformanceLookup.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/GenericSignature.h"
@@ -1197,18 +1198,20 @@ static void determineBestChoicesInContext(
// - Superclass conversion
// - Array-to-pointer conversion
// - Value to existential conversion
// - Existential opening
// - Exact match on top-level types
//
// In situations when it's not possible to determine whether a candidate
// type matches a parameter type (i.e. when partially resolved generic
// types are matched) this function is going to produce \c std::nullopt
// instead of `0` that indicates "not a match".
std::function<std::optional<double>(GenericSignature, ValueDecl *, Type,
Type, MatchOptions)>
std::function<std::optional<double>(GenericSignature, ValueDecl *,
std::optional<unsigned>, Type, Type,
MatchOptions)>
scoreCandidateMatch =
[&](GenericSignature genericSig, ValueDecl *choice,
Type candidateType, Type paramType,
MatchOptions options) -> std::optional<double> {
std::optional<unsigned> paramIdx, Type candidateType,
Type paramType, MatchOptions options) -> std::optional<double> {
auto areEqual = [&](Type a, Type b) {
return a->getDesugaredType()->isEqual(b->getDesugaredType());
};
@@ -1260,7 +1263,7 @@ static void determineBestChoicesInContext(
// This helps to determine whether there are any generic
// overloads that are a possible match.
auto score =
scoreCandidateMatch(genericSig, choice, candidateType,
scoreCandidateMatch(genericSig, choice, paramIdx, candidateType,
paramType, options - MatchFlag::Literal);
if (score == 0)
return 0;
@@ -1347,8 +1350,8 @@ static void determineBestChoicesInContext(
if ((paramOptionals.empty() &&
paramType->is<GenericTypeParamType>()) ||
paramOptionals.size() >= candidateOptionals.size()) {
auto score = scoreCandidateMatch(genericSig, choice, candidateType,
paramType, options);
auto score = scoreCandidateMatch(genericSig, choice, paramIdx,
candidateType, paramType, options);
if (score > 0) {
// Injection lowers the score slightly to comply with
@@ -1394,6 +1397,21 @@ static void determineBestChoicesInContext(
if (paramType->isAny())
return 1;
// Check if a candidate could be matched to a parameter by
// an existential opening.
if (options.contains(MatchFlag::OnParam) &&
candidateType->getMetatypeInstanceType()->isExistentialType()) {
if (auto *genericParam = paramType->getMetatypeInstanceType()
->getAs<GenericTypeParamType>()) {
if (canOpenExistentialAt(choice, *paramIdx, genericParam,
candidateType->getMetatypeInstanceType())) {
// Lower the score slightly for operators to make sure that
// concrete overloads are always preferred over generic ones.
return choice->isOperator() ? 0.9 : 1;
}
}
}
// Check protocol requirement(s) if this parameter is a
// generic parameter type.
if (genericSig && paramType->isTypeParameter()) {
@@ -1661,9 +1679,10 @@ static void determineBestChoicesInContext(
options |= MatchFlag::StringInterpolation;
// The specifier for a candidate only matters for `inout` check.
auto candidateScore = scoreCandidateMatch(
genericSig, decl, candidate.type->getWithoutSpecifierType(),
paramType, options);
auto candidateScore =
scoreCandidateMatch(genericSig, decl, paramIdx,
candidate.type->getWithoutSpecifierType(),
paramType, options);
if (!candidateScore)
continue;
@@ -1706,6 +1725,7 @@ static void determineBestChoicesInContext(
(score > 0 || !hasArgumentCandidates)) {
if (llvm::any_of(resultTypes, [&](const Type candidateResultTy) {
return scoreCandidateMatch(genericSig, decl,
/*paramIdx=*/std::nullopt,
overloadType->getResult(),
candidateResultTy,
/*options=*/{}) > 0;

View File

@@ -591,3 +591,18 @@ protocol PP3 {
associatedtype A
}
protocol PP4 {
}
do {
func test<T>(env: T) where T: PP4 {}
func test(env: PP4? = nil) {
guard let env else {
return
}
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
test(env: env)
}
}

View File

@@ -1,4 +1,4 @@
// {"kind":"typecheck","signature":"swift::constraints::ConstraintSystem::isArgumentGenericFunction(swift::Type, swift::Expr*)","signatureAssert":"Assertion failed: (!getFixedType(tyvar)), function getUnboundBindOverloadDisjunction"}
// RUN: not --crash %target-swift-frontend -typecheck %s
// RUN: not %target-swift-frontend -typecheck %s
{
print($0) $00 + 0. / 1