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

View File

@@ -591,3 +591,18 @@ protocol PP3 {
associatedtype A 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"} // {"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 print($0) $00 + 0. / 1