Skip to content

Commit 5e08f7e

Browse files
committed
[CSApply/SILGen] Implement any Sendable to Any erasure for generic arguments
In non-strict concurrency mode when `@preconcurrency` declarations are involved `any Sendable` should be treated as `Any` in generic argument positions to support passing types that (partially) adopted concurrency annotations to types that haven't yet done so.
1 parent 1a5f00b commit 5e08f7e

File tree

4 files changed

+246
-0
lines changed

4 files changed

+246
-0
lines changed

lib/SILGen/SILGenExpr.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ namespace {
497497
RValue visitCovariantReturnConversionExpr(
498498
CovariantReturnConversionExpr *E,
499499
SGFContext C);
500+
RValue visitUnsafeCastExpr(UnsafeCastExpr *E, SGFContext C);
500501
RValue visitErasureExpr(ErasureExpr *E, SGFContext C);
501502
RValue visitAnyHashableErasureExpr(AnyHashableErasureExpr *E, SGFContext C);
502503
RValue visitForcedCheckedCastExpr(ForcedCheckedCastExpr *E,
@@ -2132,6 +2133,24 @@ RValue RValueEmitter::visitExtractFunctionIsolationExpr(
21322133
return RValue(SGF, E, result);
21332134
}
21342135

2136+
RValue RValueEmitter::visitUnsafeCastExpr(UnsafeCastExpr *E, SGFContext C) {
2137+
ManagedValue original = SGF.emitRValueAsSingleValue(E->getSubExpr());
2138+
SILType resultType = SGF.getLoweredType(E->getType());
2139+
2140+
if (resultType == original.getType())
2141+
return RValue(SGF, E, original);
2142+
2143+
ManagedValue result;
2144+
if (original.getType().isAddress()) {
2145+
ASSERT(resultType.isAddress());
2146+
result = SGF.B.createUncheckedAddrCast(E, original, resultType);
2147+
} else {
2148+
result = SGF.B.createUncheckedBitCast(E, original, resultType);
2149+
}
2150+
2151+
return RValue(SGF, E, result);
2152+
}
2153+
21352154
RValue RValueEmitter::visitErasureExpr(ErasureExpr *E, SGFContext C) {
21362155
if (auto result = tryEmitAsBridgingConversion(SGF, E, false, C)) {
21372156
return RValue(SGF, E, *result);

lib/Sema/CSApply.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7034,6 +7034,18 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
70347034
}
70357035
}
70367036

7037+
if (!(ctx.isSwiftVersionAtLeast(6) ||
7038+
ctx.LangOpts.StrictConcurrencyLevel ==
7039+
StrictConcurrency::Complete)) {
7040+
auto erasedFromType = fromType->stripConcurrency(
7041+
/*recursive=*/true, /*dropGlobalActor=*/false);
7042+
auto erasedToType = toType->stripConcurrency(
7043+
/*recursive=*/true, /*dropGlobalActor=*/false);
7044+
7045+
if (erasedFromType->isEqual(erasedToType))
7046+
return cs.cacheType(new (ctx) UnsafeCastExpr(expr, toType));
7047+
}
7048+
70377049
auto &err = llvm::errs();
70387050
err << "fromType->getCanonicalType() = ";
70397051
fromType->getCanonicalType()->dump(err);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// RUN: %target-run-simple-swift | %FileCheck %s
2+
3+
// REQUIRES: executable_test
4+
5+
// UNSUPPORTED: use_os_stdlib
6+
// UNSUPPORTED: back_deployment_runtime
7+
8+
class C {
9+
@preconcurrency var dict: [String : any Sendable] = ["a": 42]
10+
@preconcurrency var arr: [any Sendable] = [42]
11+
}
12+
13+
extension Dictionary where Key == String, Value == Any {
14+
func answer() -> Int { self["a"] as! Int }
15+
}
16+
17+
extension Array where Element == Any {
18+
func answer() -> Int { self.first! as! Int }
19+
}
20+
21+
struct S<T> {
22+
let v: T
23+
}
24+
25+
struct Test {
26+
@preconcurrency var data: S<any Sendable>
27+
}
28+
29+
30+
func test() {
31+
let c = C()
32+
33+
print(c.dict.answer())
34+
// CHECK: 42
35+
print(c.arr.answer())
36+
// CHECK: 42
37+
38+
let v1 = Test(data: S(v: 42))
39+
let v2 = Test(data: S(v: "ultimate question"))
40+
41+
func expectsAny(_ s: S<Any>) { print(s.v) }
42+
43+
expectsAny(v1.data)
44+
// CHECK: 42
45+
expectsAny(v2.data)
46+
// CHECK: ultimate question
47+
48+
func sameType<T>(_ data: S<T>, with: T.Type) -> T {
49+
data.v
50+
}
51+
52+
print(sameType(v1.data, with: Any.self))
53+
// CHECK: 42
54+
print(sameType(v2.data, with: Any.self))
55+
// CHECK: ultimate question
56+
}
57+
58+
test()
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// RUN: %target-swift-emit-silgen %s -verify | %FileCheck %s
2+
3+
class User {
4+
@preconcurrency var dict: [String : any Sendable] = [:]
5+
@preconcurrency var arr: [any Sendable] = []
6+
// Note: No Set because `any Sendable` is not Hashable
7+
}
8+
9+
extension Dictionary where Key == String, Value == Any {
10+
func onlyWhenValueAny() {}
11+
}
12+
13+
extension Array where Element == Any {
14+
func onlyWhenValueAny() {}
15+
}
16+
17+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments31test_conditional_on_collections1uyAA4UserC_tF
18+
// CHECK: unchecked_bitwise_cast {{.*}} to $Dictionary<String, Any>
19+
// CHECK: unchecked_bitwise_cast {{.*}} to $Array<Any>
20+
func test_conditional_on_collections(u: User) {
21+
u.dict.onlyWhenValueAny()
22+
u.arr.onlyWhenValueAny()
23+
}
24+
25+
// Check that `any Sendable` extension is preferred.
26+
27+
extension Dictionary where Key == String, Value == Any {
28+
func noAmbiguity() {}
29+
}
30+
31+
extension Array where Element == Any {
32+
func noAmbiguity() {}
33+
}
34+
35+
extension Dictionary where Key == String, Value == any Sendable {
36+
func noAmbiguity() {}
37+
}
38+
39+
extension Array where Element == any Sendable {
40+
func noAmbiguity() {}
41+
}
42+
43+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments41test_no_ambiguity_with_Sendable_extension1uyAA4UserC_tF
44+
// CHECK-NOT: unchecked_bitwise_cast {{.*}}
45+
// CHECK: [[DICT_METHOD_REF:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszs8Sendable_pRs_rlE11noAmbiguityyyF : $@convention(method) (@guaranteed Dictionary<String, any Sendable>) -> ()
46+
// CHECK-NEXT: {{.*}} = apply [[DICT_METHOD_REF]]({{.*}}) : $@convention(method) (@guaranteed Dictionary<String, any Sendable>) -> ()
47+
// CHECK-NOT: unchecked_bitwise_cast {{.*}}
48+
// CHECK: [[ARR_METHOD_REF:%.*]] = function_ref @$sSa37sendable_to_any_for_generic_argumentss8Sendable_pRszlE11noAmbiguityyyF : $@convention(method) (@guaranteed Array<any Sendable>) -> ()
49+
// CHECK-NEXT: {{.*}} = apply [[ARR_METHOD_REF]]({{.*}}) : $@convention(method) (@guaranteed Array<any Sendable>) -> ()
50+
func test_no_ambiguity_with_Sendable_extension(u: User) {
51+
u.dict.noAmbiguity()
52+
u.arr.noAmbiguity()
53+
}
54+
55+
struct S<T> {
56+
}
57+
58+
extension S where T == Any {
59+
func anyOnly() {}
60+
}
61+
62+
struct TestGeneral {
63+
@preconcurrency var v: S<any Sendable>
64+
@preconcurrency var optV: S<[(any Sendable)?]>
65+
66+
func accepts_any(_: S<Any>) {}
67+
func accepts_opt_any(_: S<[Any?]>) {}
68+
func sameType<T>(_: S<T>, _: T.Type) {}
69+
70+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments11TestGeneralV15test_contextualAA1SVyypGyF
71+
// CHECK: [[V_REF:%.*]] = struct_extract %0, #TestGeneral.v
72+
// CHECK-NEXT: {{.*}} = unchecked_trivial_bit_cast [[V_REF]] to $S<Any>
73+
func test_contextual() -> S<Any> {
74+
v
75+
}
76+
77+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments11TestGeneralV15test_member_refyyF
78+
// CHECK: [[V_REF:%.*]] = struct_extract %0, #TestGeneral.v
79+
// CHECK-NEXT: [[V_WITH_ANY:%.*]] = unchecked_trivial_bit_cast [[V_REF:%.*]] to $S<Any>
80+
// CHECK: [[MEMBER_REF:%.*]] = function_ref @$s37sendable_to_any_for_generic_arguments1SVAAypRszlE0C4OnlyyyF : $@convention(method) (S<Any>) -> ()
81+
// CHECK-NEXT: {{.*}} = apply [[MEMBER_REF]]([[V_WITH_ANY:%.*]]) : $@convention(method) (S<Any>) -> ()
82+
func test_member_ref() {
83+
v.anyOnly()
84+
}
85+
86+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments11TestGeneralV24test_passing_as_argumentyyF
87+
// CHECK: [[V_REF:%.*]] = struct_extract %0, #TestGeneral.v
88+
// CHECK-NEXT: [[V_ANY:%.*]] = unchecked_trivial_bit_cast [[V_REF]] to $S<Any>
89+
// CHECK: [[MEMBER_REF:%.*]] = function_ref @$s37sendable_to_any_for_generic_arguments11TestGeneralV08accepts_C0yyAA1SVyypGF : $@convention(method) (S<Any>, TestGeneral) -> ()
90+
// CHECK-NEXT: {{.*}} = apply [[MEMBER_REF]]([[V_ANY]], %0) : $@convention(method) (S<Any>, TestGeneral) -> ()
91+
func test_passing_as_argument() {
92+
accepts_any(v)
93+
}
94+
95+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments11TestGeneralV39test_passing_as_argument_through_member1tyAC_tF
96+
// CHECK: [[V_REF:%.*]] = struct_extract %0, #TestGeneral.v
97+
// CHECK-NEXT: [[V_ANY:%.*]] = unchecked_trivial_bit_cast [[V_REF]] to $S<Any>
98+
// CHECK: [[MEMBER_REF:%.*]] = function_ref @$s37sendable_to_any_for_generic_arguments11TestGeneralV08accepts_C0yyAA1SVyypGF : $@convention(method) (S<Any>, TestGeneral) -> ()
99+
// CHECK-NEXT: {{.*}} = apply [[MEMBER_REF]]([[V_ANY]], %1) : $@convention(method) (S<Any>, TestGeneral) -> ()
100+
func test_passing_as_argument_through_member(t: TestGeneral) {
101+
accepts_any(t.v)
102+
}
103+
104+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments11TestGeneralV23test_complex_contextualAA1SVySayypSgGGyF
105+
// CHECK: [[V_REF:%.*]] = struct_extract %0, #TestGeneral.optV
106+
// CHECK-NEXT: {{.*}} = unchecked_trivial_bit_cast [[V_REF]] to $S<Array<Optional<Any>>>
107+
func test_complex_contextual() -> S<[Any?]> {
108+
optV
109+
}
110+
111+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments11TestGeneralV26test_complex_with_argument1tyAC_tF
112+
// CHECK: [[SELF_V_REF:%.*]] = struct_extract %1, #TestGeneral.optV
113+
// CHECK-NEXT: [[SELF_V_ANY:%.*]] = unchecked_trivial_bit_cast [[SELF_V_REF]] to $S<Array<Optional<Any>>>
114+
// CHECK: [[MEMBER_REF_1:%.*]] = function_ref @$s37sendable_to_any_for_generic_arguments11TestGeneralV012accepts_opt_C0yyAA1SVySayypSgGGF : $@convention(method) (S<Array<Optional<Any>>>, TestGeneral) -> ()
115+
// CHECK-NEXT: {{.*}} = apply [[MEMBER_REF_1]]([[SELF_V_ANY]], %1) : $@convention(method) (S<Array<Optional<Any>>>, TestGeneral) -> ()
116+
// CHECK: [[V_REF_ON_T:%.*]] = struct_extract %0, #TestGeneral.optV
117+
// CHECK-NEXT: [[V_ANY_ON_T:%.]] = unchecked_trivial_bit_cast [[V_REF_ON_T]] to $S<Array<Optional<Any>>>
118+
// CHECK: [[MEMBER_REF_2:%.*]] = function_ref @$s37sendable_to_any_for_generic_arguments11TestGeneralV012accepts_opt_C0yyAA1SVySayypSgGGF : $@convention(method) (S<Array<Optional<Any>>>, TestGeneral) -> ()
119+
// CHECK-NEXT: {{.*}} = apply [[MEMBER_REF_2]]([[V_ANY_ON_T]], %1) : $@convention(method) (S<Array<Optional<Any>>>, TestGeneral) -> ()
120+
func test_complex_with_argument(t: TestGeneral) {
121+
accepts_opt_any(optV)
122+
accepts_opt_any(t.optV)
123+
}
124+
125+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments11TestGeneralV14test_same_typeyyF
126+
// CHECK: [[V_REF:%.*]] = struct_extract %0, #TestGeneral.v
127+
// CHECK-NEXT: [[V_ANY:%.*]] = unchecked_trivial_bit_cast [[V_REF]] to $S<Any>
128+
// CHECK: [[ANY_METATYPE:%.*]] = metatype $@thick (any Any).Type
129+
// CHECK: [[SAME_TYPE_FN:%.*]] = function_ref @$s37sendable_to_any_for_generic_arguments11TestGeneralV8sameTypeyyAA1SVyxG_xmtlF : $@convention(method) <τ_0_0> (S<τ_0_0>, @thick τ_0_0.Type, TestGeneral) -> ()
130+
// CHECK-NEXT: %7 = apply %6<Any>([[V_ANY]], [[ANY_METATYPE]], %0)
131+
func test_same_type() {
132+
sameType(v, Any.self)
133+
}
134+
135+
// CHECK-LABEL: sil hidden [ossa] @$s37sendable_to_any_for_generic_arguments11TestGeneralV18test_address_castsyyF
136+
// CHECK: [[DATA_REF:%.*]] = struct_element_addr %2, #<abstract function>Test.data
137+
// CHECK-NEXT: [[DATA_COPY:%.*]] = alloc_stack $V<any Sendable>
138+
// CHECK-NEXT: copy_addr [[DATA_REF]] to [init] [[DATA_COPY]]
139+
// CHECK-NEXT: [[DATA_ANY:%.*]] = unchecked_addr_cast [[DATA_COPY]] to $*V<Any>
140+
// CHECK: [[ACCEPTS_ANY:%.*]] = function_ref @$s37sendable_to_any_for_generic_arguments11TestGeneralV18test_address_castsyyF08accepts_C0L_yyAcDyyF1VL_VyypGF : $@convention(thin) (@in_guaranteed V<Any>) -> ()
141+
// CHECK-NEXT: {{.*}} = apply [[ACCEPTS_ANY]]([[DATA_ANY]]) : $@convention(thin) (@in_guaranteed V<Any>) -> ()
142+
func test_address_casts() {
143+
struct V<T> {
144+
let v: T
145+
}
146+
147+
struct Test {
148+
@preconcurrency var data: V<any Sendable>
149+
}
150+
151+
152+
func accepts_any(_: V<Any>) {}
153+
154+
let test = Test(data: V(v: 42))
155+
accepts_any(test.data)
156+
}
157+
}

0 commit comments

Comments
 (0)