Skip to content

Commit 38c9d2e

Browse files
authored
Merge pull request #41992 from DougGregor/implicit-existential-opening-erase-or-reject
2 parents 025ee1e + 50b113e commit 38c9d2e

File tree

6 files changed

+160
-18
lines changed

6 files changed

+160
-18
lines changed

include/swift/Sema/ConstraintSystem.h

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

51625162
public:
5163+
/// If the given argument, specified by its type and expression, a reference
5164+
/// to a generic function?
5165+
bool isArgumentGenericFunction(Type argType, Expr *argExpr);
5166+
51635167
// Given a type variable, attempt to find the disjunction of
51645168
// bind overloads associated with it. This may return null in cases where
51655169
// the disjunction has either not been created or binds the type variable
@@ -5690,7 +5694,8 @@ Expr *getArgumentLabelTargetExpr(Expr *fn);
56905694
/// variable and anything that depends on it to their non-dependent bounds.
56915695
Type typeEraseOpenedExistentialReference(Type type, Type existentialBaseType,
56925696
TypeVariableType *openedTypeVar,
5693-
const DeclContext *useDC);
5697+
const DeclContext *useDC,
5698+
TypePosition outermostPosition);
56945699

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

lib/AST/Decl.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4048,9 +4048,16 @@ GenericParameterReferenceInfo swift::findGenericParameterReferences(
40484048
sig, genericParam, param.getPlainType(), TypePosition::Invariant);
40494049
continue;
40504050
}
4051+
4052+
// Parameters are contravariant, but if we're prior to the skipped
4053+
// parameter treat them as invariant because we're not allowed to
4054+
// reference the parameter at all.
4055+
TypePosition position = TypePosition::Contravariant;
4056+
if (skipParamIndex && paramIdx < *skipParamIndex)
4057+
position = TypePosition::Invariant;
4058+
40514059
inputInfo |= ::findGenericParameterReferences(
4052-
sig, genericParam, param.getParameterType(),
4053-
TypePosition::Contravariant);
4060+
sig, genericParam, param.getParameterType(), position);
40544061
}
40554062

40564063
// A covariant Self result inside a parameter will not be bona fide.

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(
@@ -11327,7 +11342,8 @@ ConstraintSystem::simplifyApplicableFnConstraint(
1132711342
if (result2->hasTypeVariable() && !openedExistentials.empty()) {
1132811343
for (const auto &opened : openedExistentials) {
1132911344
result2 = typeEraseOpenedExistentialReference(
11330-
result2, opened.second->getExistentialType(), opened.first, DC);
11345+
result2, opened.second->getExistentialType(), opened.first, DC,
11346+
TypePosition::Covariant);
1133111347
}
1133211348
}
1133311349

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
@@ -6444,6 +6446,51 @@ bool ConstraintSystem::isReadOnlyKeyPathComponent(
64446446
return false;
64456447
}
64466448

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

test/Constraints/opened_existentials.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,35 @@ 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+
func takeValueAndClosureBackwards<T: P>(body: (T) -> Void, _ value: T) { }
185+
// expected-note@-1{{required by global function 'takeValueAndClosureBackwards(body:_:)' where 'T' = 'any P'}}
186+
187+
func genericFunctionTakingP<T: P>(_: T) { }
188+
func genericFunctionTakingPQ<T: P & Q>(_: T) { }
189+
190+
func overloadedGenericFunctionTakingP<T: P>(_: T) -> Int { 0 }
191+
func overloadedGenericFunctionTakingP<T: P>(_: T) { }
192+
193+
func testTakeValueAndClosure(p: any P) {
194+
// Type-erase when not provided with a generic function.
195+
takeValueAndClosure(p) { x in
196+
print(x)
197+
let _: Int = x // expected-error{{cannot convert value of type 'any P' to specified type 'Int'}}
198+
}
199+
200+
// Do not erase when referring to a generic function.
201+
takeValueAndClosure(p, body: genericFunctionTakingP)
202+
takeValueAndClosure(p, body: overloadedGenericFunctionTakingP)
203+
takeValueAndClosure(p, body: genericFunctionTakingPQ) // expected-error{{global function 'genericFunctionTakingPQ' requires that 'T' conform to 'Q'}}
204+
205+
// Do not allow opening if there are any uses of the the type parameter before
206+
// the opened parameter. This maintains left-to-right evaluation order.
207+
takeValueAndClosureBackwards( // expected-error{{type 'any P' cannot conform to 'P'}}
208+
// expected-note@-1{{only concrete types such as structs, enums and classes can conform to protocols}}
209+
body: { x in x as Int }, // expected-error{{'any P' is not convertible to 'Int'}}
210+
// expected-note@-1{{did you mean to use 'as!' to force downcast?}}
211+
p)
212+
}

test/SILGen/opened_existentials.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// RUN: %target-swift-emit-silgen -enable-experimental-opened-existential-types %s | %FileCheck %s
2+
3+
public protocol P { }
4+
5+
func f() -> String {
6+
print("f()")
7+
return "Hello"
8+
}
9+
10+
func g<T: P> (_ value: String, _: T) -> String {
11+
print("g()")
12+
return value + ", world"
13+
}
14+
15+
extension Int: P { }
16+
17+
func getP() -> any P {
18+
return 17
19+
}
20+
21+
// CHECK: sil [ossa] @$s19opened_existentials4testSSyF : $@convention(thin) () -> @owned String
22+
public func test() -> String {
23+
// FIXME: This demonstrates that we are opening the existential out of
24+
// order. This test will break when we properly update the existential-opening
25+
// logic to wait until the argument is evaluated.
26+
27+
// CHECK: [[PSTACK:%.*]] = alloc_stack $P
28+
// CHECK: [[GETP:%.*]] = function_ref @$s19opened_existentials4getPAA1P_pyF : $@convention(thin) () -> @out P // user: %2
29+
// CHECK: [[P:%.*]] = apply [[GETP]]([[PSTACK]]) : $@convention(thin) () -> @out P
30+
// CHECK: [[OPENEDP:%.*]] = open_existential_addr immutable_access [[PSTACK]] : $*P to $*@opened
31+
// CHECK: [[F:%.*]] = function_ref @$s19opened_existentials1fSSyF : $@convention(thin) () -> @owned String
32+
// CHECK: [[F_RESULT:%.*]] = apply [[F]]() : $@convention(thin) () -> @owned String
33+
g(f(), getP())
34+
}
35+

0 commit comments

Comments
 (0)