Skip to content

Commit 50b113e

Browse files
committed
Don't open an existential call argument if its type information is needed earlier.
To ensure that we do not accept code that would require an existential call argument to be evaluated prior to the call, don't open existential call arguments if the generic parameter that would capture the opened existential type is used in any prior parameter. Note that code generation currently moves the opening of the existential call argument outside of the call, which is a compiler bug. This change ensures that we don't accept code would *require* this out-of-order evaluation and adds a test so we know when we fix this.
1 parent 50451d2 commit 50b113e

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

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.

test/Constraints/opened_existentials.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ func testReturningOpaqueTypes(p: any P) {
181181

182182
// Type-erasing vs. opening for parameters after the opened one.
183183
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'}}
184186

185187
func genericFunctionTakingP<T: P>(_: T) { }
186188
func genericFunctionTakingPQ<T: P & Q>(_: T) { }
@@ -199,4 +201,12 @@ func testTakeValueAndClosure(p: any P) {
199201
takeValueAndClosure(p, body: genericFunctionTakingP)
200202
takeValueAndClosure(p, body: overloadedGenericFunctionTakingP)
201203
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)
202212
}

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)