Skip to content

Commit 50451d2

Browse files
committed
Type-erase contravariant uses of opened existentials in subsequent parameters.
When we open an existential argument in a call to a generic function, type-erase contravariant uses of that opened existential in subsequent parameters. This primarily impacts closure parameters, where we want the closure to be provided with an existential parameter type rather than permit the parameter to have opened existential type. This prevents the opened existential type from being directly exposed in the type system. Note that we do not need to perform this erasure when the argument is a reference to a generic function, because there it is suitable to infer that the generic arguments are the opened archetypes. This subsumes the use case for `_openExistential`.
1 parent 1f39f27 commit 50451d2

File tree

4 files changed

+106
-16
lines changed

4 files changed

+106
-16
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5113,6 +5113,10 @@ class ConstraintSystem {
51135113
SourceLoc referenceLoc);
51145114

51155115
public:
5116+
/// If the given argument, specified by its type and expression, a reference
5117+
/// to a generic function?
5118+
bool isArgumentGenericFunction(Type argType, Expr *argExpr);
5119+
51165120
// Given a type variable, attempt to find the disjunction of
51175121
// bind overloads associated with it. This may return null in cases where
51185122
// the disjunction has either not been created or binds the type variable
@@ -5643,7 +5647,8 @@ Expr *getArgumentLabelTargetExpr(Expr *fn);
56435647
/// variable and anything that depends on it to their non-dependent bounds.
56445648
Type typeEraseOpenedExistentialReference(Type type, Type existentialBaseType,
56455649
TypeVariableType *openedTypeVar,
5646-
const DeclContext *useDC);
5650+
const DeclContext *useDC,
5651+
TypePosition outermostPosition);
56475652

56485653
/// Returns true if a reference to a member on a given base type will apply
56495654
/// its curried self parameter, assuming it has one.

lib/Sema/CSSimplify.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,12 +1432,13 @@ namespace {
14321432
/// \param argTy The type of the argument.
14331433
///
14341434
/// \returns If the argument type is existential and opening it can bind a
1435-
/// generic parameter in the callee, returns the type variable (from the
1436-
/// opened parameter type) the existential type that needs to be opened
1437-
/// (from the argument type), and the adjustements that need to be applied to
1438-
/// the existential type after it is opened.
1435+
/// generic parameter in the callee, returns the generic parameter, type
1436+
/// variable (from the opened parameter type) the existential type that needs
1437+
/// to be opened (from the argument type), and the adjustements that need to be
1438+
/// applied to the existential type after it is opened.
14391439
static Optional<
1440-
std::tuple<TypeVariableType *, Type, OpenedExistentialAdjustments>>
1440+
std::tuple<GenericTypeParamType *, TypeVariableType *, Type,
1441+
OpenedExistentialAdjustments>>
14411442
shouldOpenExistentialCallArgument(
14421443
ValueDecl *callee, unsigned paramIdx, Type paramTy, Type argTy,
14431444
Expr *argExpr, ConstraintSystem &cs) {
@@ -1551,7 +1552,7 @@ shouldOpenExistentialCallArgument(
15511552
referenceInfo.assocTypeRef > TypePosition::Covariant)
15521553
return None;
15531554

1554-
return std::make_tuple(paramTypeVar, argTy, adjustments);
1555+
return std::make_tuple(genericParam, paramTypeVar, argTy, adjustments);
15551556
}
15561557

15571558
// Match the argument of a call to the parameter.
@@ -1831,15 +1832,29 @@ static ConstraintSystem::TypeMatchResult matchCallArguments(
18311832
cs, cs.getConstraintLocator(loc)));
18321833
}
18331834

1835+
// Type-erase any opened existentials from subsequent parameter types
1836+
// unless the argument itself is a generic function, which could handle
1837+
// the opened existentials.
1838+
if (!openedExistentials.empty() && paramTy->hasTypeVariable() &&
1839+
!cs.isArgumentGenericFunction(argTy, argExpr)) {
1840+
for (const auto &opened : openedExistentials) {
1841+
paramTy = typeEraseOpenedExistentialReference(
1842+
paramTy, opened.second->getExistentialType(), opened.first,
1843+
/*FIXME*/cs.DC, TypePosition::Contravariant);
1844+
}
1845+
}
1846+
18341847
// If the argument is an existential type and the parameter is generic,
18351848
// consider opening the existential type.
18361849
if (auto existentialArg = shouldOpenExistentialCallArgument(
18371850
callee, paramIdx, paramTy, argTy, argExpr, cs)) {
18381851
// My kingdom for a decent "if let" in C++.
1852+
GenericTypeParamType *openedGenericParam;
18391853
TypeVariableType *openedTypeVar;
18401854
Type existentialType;
18411855
OpenedExistentialAdjustments adjustments;
1842-
std::tie(openedTypeVar, existentialType, adjustments) = *existentialArg;
1856+
std::tie(openedGenericParam, openedTypeVar, existentialType,
1857+
adjustments) = *existentialArg;
18431858

18441859
OpenedArchetypeType *opened;
18451860
std::tie(argTy, opened) = cs.openExistentialType(
@@ -11314,7 +11329,8 @@ ConstraintSystem::simplifyApplicableFnConstraint(
1131411329
if (result2->hasTypeVariable() && !openedExistentials.empty()) {
1131511330
for (const auto &opened : openedExistentials) {
1131611331
result2 = typeEraseOpenedExistentialReference(
11317-
result2, opened.second->getExistentialType(), opened.first, DC);
11332+
result2, opened.second->getExistentialType(), opened.first, DC,
11333+
TypePosition::Covariant);
1131811334
}
1131911335
}
1132011336

lib/Sema/ConstraintSystem.cpp

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1794,8 +1794,9 @@ static bool isMainDispatchQueueMember(ConstraintLocator *locator) {
17941794
/// \note If a 'Self'-rooted type parameter is bound to a concrete type, this
17951795
/// routine will recurse into the concrete type.
17961796
static Type
1797-
typeEraseCovariantExistentialSelfReferences(Type refTy, Type baseTy,
1798-
const DeclContext *useDC) {
1797+
typeEraseExistentialSelfReferences(
1798+
Type refTy, Type baseTy, const DeclContext *useDC,
1799+
TypePosition outermostPosition) {
17991800
assert(baseTy->isExistentialType());
18001801
if (!refTy->hasTypeParameter()) {
18011802
return refTy;
@@ -1910,12 +1911,12 @@ typeEraseCovariantExistentialSelfReferences(Type refTy, Type baseTy,
19101911
});
19111912
};
19121913

1913-
return transformFn(refTy, TypePosition::Covariant);
1914+
return transformFn(refTy, outermostPosition);
19141915
}
19151916

19161917
Type constraints::typeEraseOpenedExistentialReference(
19171918
Type type, Type existentialBaseType, TypeVariableType *openedTypeVar,
1918-
const DeclContext *useDC) {
1919+
const DeclContext *useDC, TypePosition outermostPosition) {
19191920
Type selfGP = GenericTypeParamType::get(false, 0, 0, type->getASTContext());
19201921

19211922
// First, temporarily reconstitute the 'Self' generic parameter.
@@ -1932,8 +1933,8 @@ Type constraints::typeEraseOpenedExistentialReference(
19321933
});
19331934

19341935
// Then, type-erase occurrences of covariant 'Self'-rooted type parameters.
1935-
type = typeEraseCovariantExistentialSelfReferences(type, existentialBaseType,
1936-
useDC);
1936+
type = typeEraseExistentialSelfReferences(
1937+
type, existentialBaseType, useDC, outermostPosition);
19371938

19381939
// Finally, swap the 'Self'-corresponding type variable back in.
19391940
return type.transformRec([&](TypeBase *t) -> Optional<Type> {
@@ -2260,7 +2261,8 @@ ConstraintSystem::getTypeOfMemberReference(
22602261
outerDC->getSelfInterfaceType()->getCanonicalType());
22612262
auto openedTypeVar = replacements.lookup(selfGP);
22622263
type =
2263-
typeEraseOpenedExistentialReference(type, baseObjTy, openedTypeVar, DC);
2264+
typeEraseOpenedExistentialReference(type, baseObjTy, openedTypeVar, DC,
2265+
TypePosition::Covariant);
22642266
}
22652267

22662268
// Construct an idealized parameter type of the initializer associated
@@ -6436,6 +6438,51 @@ bool ConstraintSystem::isReadOnlyKeyPathComponent(
64366438
return false;
64376439
}
64386440

6441+
bool ConstraintSystem::isArgumentGenericFunction(Type argType, Expr *argExpr) {
6442+
// Only makes sense if the argument type involves type variables somehow.
6443+
if (!argType->hasTypeVariable())
6444+
return false;
6445+
6446+
// Have we bound an overload for the argument already?
6447+
if (argExpr) {
6448+
auto locator = getConstraintLocator(argExpr);
6449+
auto knownOverloadBinding = ResolvedOverloads.find(locator);
6450+
if (knownOverloadBinding != ResolvedOverloads.end()) {
6451+
// If the overload choice is a generic function, then we have a generic
6452+
// function reference.
6453+
auto choice = knownOverloadBinding->second;
6454+
if (auto func = dyn_cast_or_null<AbstractFunctionDecl>(
6455+
choice.choice.getDeclOrNull())) {
6456+
if (func->isGeneric())
6457+
return true;
6458+
}
6459+
6460+
return false;
6461+
}
6462+
}
6463+
6464+
// We might have a type variable referring to an overload set.
6465+
auto argTypeVar = argType->getAs<TypeVariableType>();
6466+
if (!argTypeVar)
6467+
return false;
6468+
6469+
auto disjunction = getUnboundBindOverloadDisjunction(argTypeVar);
6470+
if (!disjunction)
6471+
return false;
6472+
6473+
for (auto constraint : disjunction->getNestedConstraints()) {
6474+
auto *decl = constraint->getOverloadChoice().getDeclOrNull();
6475+
if (!decl)
6476+
continue;
6477+
6478+
if (auto func = dyn_cast<AbstractFunctionDecl>(decl))
6479+
if (func->isGeneric())
6480+
return true;
6481+
}
6482+
6483+
return false;
6484+
}
6485+
64396486
bool ConstraintSystem::participatesInInference(ClosureExpr *closure) const {
64406487
if (closure->hasSingleExpressionBody())
64416488
return true;

test/Constraints/opened_existentials.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,25 @@ func testReturningOpaqueTypes(p: any P) {
178178
_ = createX(p) // expected-error{{type 'any P' cannot conform to 'P'}}
179179
// expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}}
180180
}
181+
182+
// Type-erasing vs. opening for parameters after the opened one.
183+
func takeValueAndClosure<T: P>(_ value: T, body: (T) -> Void) { }
184+
185+
func genericFunctionTakingP<T: P>(_: T) { }
186+
func genericFunctionTakingPQ<T: P & Q>(_: T) { }
187+
188+
func overloadedGenericFunctionTakingP<T: P>(_: T) -> Int { 0 }
189+
func overloadedGenericFunctionTakingP<T: P>(_: T) { }
190+
191+
func testTakeValueAndClosure(p: any P) {
192+
// Type-erase when not provided with a generic function.
193+
takeValueAndClosure(p) { x in
194+
print(x)
195+
let _: Int = x // expected-error{{cannot convert value of type 'any P' to specified type 'Int'}}
196+
}
197+
198+
// Do not erase when referring to a generic function.
199+
takeValueAndClosure(p, body: genericFunctionTakingP)
200+
takeValueAndClosure(p, body: overloadedGenericFunctionTakingP)
201+
takeValueAndClosure(p, body: genericFunctionTakingPQ) // expected-error{{global function 'genericFunctionTakingPQ' requires that 'T' conform to 'Q'}}
202+
}

0 commit comments

Comments
 (0)