Skip to content

Commit a5649a1

Browse files
authored
Merge pull request swiftlang#83735 from xedin/rdar-158159462
[CSOptimizer] Add support for opened existential arguments
2 parents b1c8382 + 0d33af7 commit a5649a1

File tree

5 files changed

+77
-26
lines changed

5 files changed

+77
-26
lines changed

lib/Sema/CSOptimizer.cpp

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
//===----------------------------------------------------------------------===//
1616

1717
#include "TypeChecker.h"
18+
#include "OpenedExistentials.h"
1819
#include "swift/AST/ConformanceLookup.h"
1920
#include "swift/AST/ExistentialLayout.h"
2021
#include "swift/AST/GenericSignature.h"
@@ -1197,18 +1198,20 @@ static void determineBestChoicesInContext(
11971198
// - Superclass conversion
11981199
// - Array-to-pointer conversion
11991200
// - Value to existential conversion
1201+
// - Existential opening
12001202
// - Exact match on top-level types
12011203
//
12021204
// In situations when it's not possible to determine whether a candidate
12031205
// type matches a parameter type (i.e. when partially resolved generic
12041206
// types are matched) this function is going to produce \c std::nullopt
12051207
// instead of `0` that indicates "not a match".
1206-
std::function<std::optional<double>(GenericSignature, ValueDecl *, Type,
1207-
Type, MatchOptions)>
1208+
std::function<std::optional<double>(GenericSignature, ValueDecl *,
1209+
std::optional<unsigned>, Type, Type,
1210+
MatchOptions)>
12081211
scoreCandidateMatch =
12091212
[&](GenericSignature genericSig, ValueDecl *choice,
1210-
Type candidateType, Type paramType,
1211-
MatchOptions options) -> std::optional<double> {
1213+
std::optional<unsigned> paramIdx, Type candidateType,
1214+
Type paramType, MatchOptions options) -> std::optional<double> {
12121215
auto areEqual = [&](Type a, Type b) {
12131216
return a->getDesugaredType()->isEqual(b->getDesugaredType());
12141217
};
@@ -1260,7 +1263,7 @@ static void determineBestChoicesInContext(
12601263
// This helps to determine whether there are any generic
12611264
// overloads that are a possible match.
12621265
auto score =
1263-
scoreCandidateMatch(genericSig, choice, candidateType,
1266+
scoreCandidateMatch(genericSig, choice, paramIdx, candidateType,
12641267
paramType, options - MatchFlag::Literal);
12651268
if (score == 0)
12661269
return 0;
@@ -1347,8 +1350,8 @@ static void determineBestChoicesInContext(
13471350
if ((paramOptionals.empty() &&
13481351
paramType->is<GenericTypeParamType>()) ||
13491352
paramOptionals.size() >= candidateOptionals.size()) {
1350-
auto score = scoreCandidateMatch(genericSig, choice, candidateType,
1351-
paramType, options);
1353+
auto score = scoreCandidateMatch(genericSig, choice, paramIdx,
1354+
candidateType, paramType, options);
13521355

13531356
if (score > 0) {
13541357
// Injection lowers the score slightly to comply with
@@ -1394,6 +1397,21 @@ static void determineBestChoicesInContext(
13941397
if (paramType->isAny())
13951398
return 1;
13961399

1400+
// Check if a candidate could be matched to a parameter by
1401+
// an existential opening.
1402+
if (options.contains(MatchFlag::OnParam) &&
1403+
candidateType->getMetatypeInstanceType()->isExistentialType()) {
1404+
if (auto *genericParam = paramType->getMetatypeInstanceType()
1405+
->getAs<GenericTypeParamType>()) {
1406+
if (canOpenExistentialAt(choice, *paramIdx, genericParam,
1407+
candidateType->getMetatypeInstanceType())) {
1408+
// Lower the score slightly for operators to make sure that
1409+
// concrete overloads are always preferred over generic ones.
1410+
return choice->isOperator() ? 0.9 : 1;
1411+
}
1412+
}
1413+
}
1414+
13971415
// Check protocol requirement(s) if this parameter is a
13981416
// generic parameter type.
13991417
if (genericSig && paramType->isTypeParameter()) {
@@ -1661,9 +1679,10 @@ static void determineBestChoicesInContext(
16611679
options |= MatchFlag::StringInterpolation;
16621680

16631681
// The specifier for a candidate only matters for `inout` check.
1664-
auto candidateScore = scoreCandidateMatch(
1665-
genericSig, decl, candidate.type->getWithoutSpecifierType(),
1666-
paramType, options);
1682+
auto candidateScore =
1683+
scoreCandidateMatch(genericSig, decl, paramIdx,
1684+
candidate.type->getWithoutSpecifierType(),
1685+
paramType, options);
16671686

16681687
if (!candidateScore)
16691688
continue;
@@ -1706,6 +1725,7 @@ static void determineBestChoicesInContext(
17061725
(score > 0 || !hasArgumentCandidates)) {
17071726
if (llvm::any_of(resultTypes, [&](const Type candidateResultTy) {
17081727
return scoreCandidateMatch(genericSig, decl,
1728+
/*paramIdx=*/std::nullopt,
17091729
overloadType->getResult(),
17101730
candidateResultTy,
17111731
/*options=*/{}) > 0;

lib/Sema/OpenedExistentials.cpp

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -677,16 +677,6 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
677677
if (!typeVar || !genericParam)
678678
return std::nullopt;
679679

680-
// Only allow opening the innermost generic parameters.
681-
auto genericContext = callee->getAsGenericContext();
682-
if (!genericContext || !genericContext->isGeneric())
683-
return std::nullopt;
684-
685-
auto genericSig = callee->getInnermostDeclContext()
686-
->getGenericSignatureOfContext().getCanonicalSignature();
687-
if (genericParam->getDepth() < genericSig->getMaxDepth())
688-
return std::nullopt;
689-
690680
// The binding could be an existential metatype. Get the instance type for
691681
// conformance checks and to build an opened existential signature. If the
692682
// instance type is not an existential type, i.e., the metatype is nested,
@@ -695,6 +685,28 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
695685
if (!existentialTy->isExistentialType())
696686
return std::nullopt;
697687

688+
if (!canOpenExistentialAt(callee, paramIdx, genericParam, existentialTy))
689+
return std::nullopt;
690+
691+
return std::pair(typeVar, bindingTy);
692+
}
693+
694+
bool swift::canOpenExistentialAt(ValueDecl *callee, unsigned paramIdx,
695+
GenericTypeParamType *genericParam,
696+
Type existentialTy) {
697+
ASSERT(existentialTy->isExistentialType());
698+
699+
// Only allow opening the innermost generic parameters.
700+
auto genericContext = callee->getAsGenericContext();
701+
if (!genericContext || !genericContext->isGeneric())
702+
return false;
703+
704+
auto genericSig = callee->getInnermostDeclContext()
705+
->getGenericSignatureOfContext()
706+
.getCanonicalSignature();
707+
if (genericParam->getDepth() < genericSig->getMaxDepth())
708+
return false;
709+
698710
auto &ctx = callee->getASTContext();
699711

700712
// If the existential argument conforms to all of protocol requirements on
@@ -715,7 +727,7 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
715727
}
716728

717729
if (!containsNonSelfConformance)
718-
return std::nullopt;
730+
return false;
719731
}
720732

721733
auto existentialSig = ctx.getOpenedExistentialSignature(existentialTy);
@@ -726,10 +738,7 @@ swift::canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
726738
callee, existentialSig.OpenedSig, genericParam,
727739
existentialSig.SelfType->castTo<GenericTypeParamType>(),
728740
/*skipParamIdx=*/paramIdx);
729-
if (referenceInfo.hasNonCovariantRef())
730-
return std::nullopt;
731-
732-
return std::pair(typeVar, bindingTy);
741+
return !referenceInfo.hasNonCovariantRef();
733742
}
734743

735744
/// For each occurrence of a type **type** in `refTy` that satisfies

lib/Sema/OpenedExistentials.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,13 @@ std::optional<std::pair<TypeVariableType *, Type>>
150150
canOpenExistentialCallArgument(ValueDecl *callee, unsigned paramIdx,
151151
Type paramTy, Type argTy);
152152

153+
/// A limited form of the check performed by \c canOpenExistentialCallArgument
154+
/// that assumes that a declaration where parameter came from, the parameter
155+
/// itself, and the types involved have been validated already.
156+
bool canOpenExistentialAt(ValueDecl *callee, unsigned paramIdx,
157+
GenericTypeParamType *genericParam,
158+
Type existentialTy);
159+
153160
/// Given a type that includes an existential type that has been opened to
154161
/// the given type variable, replace the opened type variable and its member
155162
/// types with their upper bounds.

test/Constraints/opened_existentials.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,3 +591,18 @@ protocol PP3 {
591591
associatedtype A
592592
}
593593

594+
protocol PP4 {
595+
}
596+
597+
do {
598+
func test<T>(env: T) where T: PP4 {}
599+
600+
func test(env: PP4? = nil) {
601+
guard let env else {
602+
return
603+
}
604+
605+
// CHECK: open_existential_expr {{.*}} location={{.*}}:[[@LINE+1]]:{{[0-9]+}} range=
606+
test(env: env)
607+
}
608+
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
// {"kind":"typecheck","signature":"swift::constraints::ConstraintSystem::isArgumentGenericFunction(swift::Type, swift::Expr*)","signatureAssert":"Assertion failed: (!getFixedType(tyvar)), function getUnboundBindOverloadDisjunction"}
2-
// RUN: not --crash %target-swift-frontend -typecheck %s
2+
// RUN: not %target-swift-frontend -typecheck %s
33
{
44
print($0) $00 + 0. / 1

0 commit comments

Comments
 (0)