Skip to content

Commit 0a0a34c

Browse files
committed
[CSApply] Add support for l-value to l-value and inout unsafe casts
`any Sendable` -> `Any` in generic argument positions should be supported for l-value and inout types as well otherwise it won't be possible to call setters and mutating methods.
1 parent 79f6b07 commit 0a0a34c

4 files changed

+172
-2
lines changed

lib/Sema/CSApply.cpp

+30-1
Original file line numberDiff line numberDiff line change
@@ -7389,13 +7389,42 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
73897389
// coercion.
73907390
case TypeKind::LValue: {
73917391
auto fromLValue = cast<LValueType>(desugaredFromType);
7392+
7393+
auto injectUnsafeLValueCast = [&](Type fromObjType,
7394+
Type toObjType) -> Expr * {
7395+
auto restriction = solution.getConversionRestriction(
7396+
fromObjType->getCanonicalType(), toObjType->getCanonicalType());
7397+
ASSERT(restriction == ConversionRestrictionKind::DeepEquality);
7398+
return cs.cacheType(
7399+
new (ctx) ABISafeConversionExpr(expr, LValueType::get(toObjType)));
7400+
};
7401+
7402+
// @lvalue <A> -> @lvalue <B> is only allowed if there is a
7403+
// deep equality conversion restriction between the types.
7404+
// This supports `any Sendable` -> `Any` conversion in generic
7405+
// argument positions.
7406+
if (auto *toLValue = toType->getAs<LValueType>()) {
7407+
return injectUnsafeLValueCast(fromLValue->getObjectType(),
7408+
toLValue->getObjectType());
7409+
}
7410+
73927411
auto toIO = toType->getAs<InOutType>();
73937412
if (!toIO)
73947413
return coerceToType(cs.addImplicitLoadExpr(expr), toType, locator);
73957414

7415+
// @lvalue <A> -> inout <B> has to use an unsafe cast <A> -> <B>:
7416+
// @lvalue <A> <cast to> @lvalue B -> inout B.
7417+
//
7418+
// This can happen due to any Sendable -> Any conversion in generic
7419+
// argument positions. We need to inject a cast to get @l-value to
7420+
// match `inout` type exactly.
7421+
if (!toIO->getObjectType()->isEqual(fromLValue->getObjectType())) {
7422+
expr = injectUnsafeLValueCast(fromLValue->getObjectType(),
7423+
toIO->getObjectType());
7424+
}
7425+
73967426
// In an 'inout' operator like "i += 1", the operand is converted from
73977427
// an implicit lvalue to an inout argument.
7398-
assert(toIO->getObjectType()->isEqual(fromLValue->getObjectType()));
73997428
return cs.cacheType(new (ctx) InOutExpr(expr->getStartLoc(), expr,
74007429
toIO->getObjectType(),
74017430
/*isImplicit*/ true));

test/Concurrency/sendable_to_any_for_generic_arguments.swift

+25
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,28 @@ struct TestGeneral {
150150
let _: S<((Any) -> Void) -> Void> = funcInFunc // Ok
151151
}
152152
}
153+
154+
// Make sure that properties and subscripts and mutating methods work.
155+
extension Dictionary where Key == String, Value == Any {
156+
subscript<T>(entry object: T) -> T? {
157+
get { nil }
158+
set { }
159+
}
160+
161+
var test: Int? {
162+
get { nil }
163+
set { }
164+
}
165+
166+
mutating func testMutating() {}
167+
}
168+
169+
func test_subscript_computed_property_and_mutating_access(u: User) {
170+
_ = u.dict[entry: ""] // Ok
171+
u.dict[entry: 42] = 42 // Ok
172+
173+
_ = u.dict.test // Ok
174+
u.dict.test = 42 // Ok
175+
176+
u.dict.testMutating() // Ok
177+
}

test/Interpreter/sendable_erasure_to_any_in_preconcurrency.swift

+38-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ class C {
1212

1313
extension Dictionary where Key == String, Value == Any {
1414
func answer() -> Int { self["a"] as! Int }
15+
16+
subscript(entry e: String) -> (any Sendable)? {
17+
get { self["Entry#" + e] }
18+
set { self["Entry#" + e] = newValue }
19+
}
20+
21+
var entryB: (any Sendable)? {
22+
get { self["Entry#B"] }
23+
set { self["Entry#B"] = newValue }
24+
}
25+
26+
mutating func addEntry() {
27+
self["ultimate question"] = "???"
28+
}
1529
}
1630

1731
extension Array where Element == Any {
@@ -29,13 +43,36 @@ struct Test {
2943

3044

3145
func test() {
32-
let c = C()
46+
var c = C()
3347

3448
print(c.dict.answer())
3549
// CHECK: 42
3650
print(c.arr.answer())
3751
// CHECK: 42
3852

53+
print(c.dict[entry: "A"] ?? "no A")
54+
// CHECK: no A
55+
56+
// Insert a new value
57+
c.dict[entry: "A"] = "forty two"
58+
59+
// Make sure that the dictionary got mutated
60+
print(c.dict[entry: "A"] ?? "no A")
61+
// CHECK: forty two
62+
63+
print(c.dict.entryB ?? "no B")
64+
// CHECK: no B
65+
66+
// Insert new value
67+
c.dict.entryB = (q: "", a: 42)
68+
69+
print(c.dict.entryB ?? "no B")
70+
// CHECK: (q: "", a: 42)
71+
72+
c.dict.addEntry()
73+
print(c.dict["ultimate question"] ?? "no question")
74+
// CHECK: ???
75+
3976
let v1 = Test(data: S(v: 42))
4077
let v2 = Test(data: S(v: "ultimate question"))
4178

test/SILGen/sendable_to_any_for_generic_arguments.swift

+79
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,82 @@ struct TestGeneral {
182182
accepts_any(test.data)
183183
}
184184
}
185+
186+
extension Dictionary where Key == String, Value == Any {
187+
subscript<T>(entry object: T) -> T? {
188+
get { nil }
189+
set { }
190+
}
191+
192+
var test: Int? {
193+
get { nil }
194+
set { }
195+
}
196+
197+
mutating func testMutating() {}
198+
}
199+
200+
func test_subscript_computed_property_and_mutating_access(u: User) {
201+
// CHECK: [[DICT_GETTER:%.*]] = class_method %0, #User.dict!getter : (User) -> () -> [String : any Sendable], $@convention(method) (@guaranteed User) -> @owned Dictionary<String, any Sendable>
202+
// CHECK-NEXT: [[DICT:%.*]] = apply [[DICT_GETTER]]({{.*}}) : $@convention(method) (@guaranteed User) -> @owned Dictionary<String, any Sendable>
203+
// CHECK-NEXT: [[ANY_DICT:%.*]] = unchecked_bitwise_cast [[DICT]] to $Dictionary<String, Any>
204+
// CHECK-NEXT: [[ANY_DICT_COPY:%.*]] = copy_value [[ANY_DICT]]
205+
// CHECK-NEXT: [[BORROWED_COPY:%.*]] = begin_borrow [[ANY_DICT_COPY]]
206+
// CHECK: [[SUBSCRIPT_GETTER:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE5entryqd__Sgqd___tcluig
207+
// CHECK-NEXT: {{.*}} = apply [[SUBSCRIPT_GETTER]]<String, Any, String>({{.*}}, [[BORROWED_COPY]])
208+
_ = u.dict[entry: ""]
209+
210+
// CHECK: [[DICT_GETTER:%.*]] = class_method %0, #User.dict!modify : (User) -> () -> (), $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
211+
// CHECK-NEXT: ([[DICT_ADDR:%.*]], {{.*}}) = begin_apply [[DICT_GETTER]]({{.*}}) : $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
212+
// CHECK-NEXT: [[ANY_DICT:%.*]] = alloc_stack $Dictionary<String, Any>
213+
// CHECK-NEXT: [[LOADED_DICT:%.*]] = load [copy] [[DICT_ADDR]]
214+
// CHECK-NEXT: [[ANY_LOADED_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_DICT]] to $Dictionary<String, Any>
215+
// CHECK-NEXT: [[COPIED_ANY_DICT:%.*]] = copy_value [[ANY_LOADED_DICT]]
216+
// CHECK-NEXT: store [[COPIED_ANY_DICT]] to [init] [[ANY_DICT]]
217+
// CHECK: [[SUBSCRIPT_SETTER:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE5entryqd__Sgqd___tcluis
218+
// CHECK-NEXT: %48 = apply [[SUBSCRIPT_SETTER]]<String, Any, Int>({{.*}}, [[ANY_DICT]])
219+
// CHECK-NEXT: [[LOADED_ANY_DICT:%.*]] = load [take] [[ANY_DICT]]
220+
// CHECK-NEXT: [[SENDABLE_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_ANY_DICT]] to $Dictionary<String, any Sendable>
221+
// CHECK-NEXT: [[COPIED_SENDABLE_DICT:%.*]] = copy_value [[SENDABLE_DICT]]
222+
// CHECK-NEXT: assign [[COPIED_SENDABLE_DICT]] to [[DICT_ADDR]]
223+
u.dict[entry: 42] = 42
224+
225+
// CHECK: [[DICT_GETTER:%.*]] = class_method %0, #User.dict!getter : (User) -> () -> [String : any Sendable], $@convention(method) (@guaranteed User) -> @owned Dictionary<String, any Sendable>
226+
// CHECK-NEXT: [[SENDABLE_DICT:%.*]] = apply [[DICT_GETTER]]({{.*}}) : $@convention(method) (@guaranteed User) -> @owned Dictionary<String, any Sendable>
227+
// CHECK-NEXT: [[DICT_CAST_TO_ANY:%.*]] = unchecked_bitwise_cast [[SENDABLE_DICT]] to $Dictionary<String, Any>
228+
// CHECK-NEXT: [[ANY_DICT_COPY:%.*]] = copy_value [[DICT_CAST_TO_ANY]]
229+
// CHECK-NEXT: [[ANY_DICT:%.*]] = begin_borrow [[ANY_DICT_COPY]]
230+
// CHECK: [[GETTER:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE4testSiSgvg
231+
// CHECK-NEXT: {{.*}} = apply [[GETTER]]([[ANY_DICT]]) : $@convention(method) (@guaranteed Dictionary<String, Any>) -> Optional<Int>
232+
_ = u.dict.test
233+
234+
// CHECK: [[DICT_GETTER:%.*]] = class_method %0, #User.dict!modify : (User) -> () -> (), $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
235+
// CHECK-NEXT: ([[DICT:%.*]], {{.*}}) = begin_apply [[DICT_GETTER]]({{.*}}) : $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
236+
// CHECK-NEXT: [[ANY_DICT:%.*]] = alloc_stack $Dictionary<String, Any>
237+
// CHECK-NEXT: [[LOADED_DICT:%.*]] = load [copy] [[DICT]]
238+
// CHECK-NEXT: [[CASTED_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_DICT]] to $Dictionary<String, Any>
239+
// CHECK-NEXT: [[COPIED_CASTED_DICT:%.*]] = copy_value [[CASTED_DICT]]
240+
// CHECK-NEXT: store [[COPIED_CASTED_DICT]] to [init] [[ANY_DICT]]
241+
// CHECK: [[SETTER:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE4testSiSgvs
242+
// CHECK-NEXT: {{.*}} = apply [[SETTER]]({{.*}}, [[ANY_DICT]]) : $@convention(method) (Optional<Int>, @inout Dictionary<String, Any>) -> ()
243+
// CHECK-NEXT: [[LOADED_ANY_DICT:%.*]] = load [take] [[ANY_DICT]]
244+
// CHECK-NEXT: [[SENDABLE_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_ANY_DICT]] to $Dictionary<String, any Sendable>
245+
// CHECK-NEXT: [[COPIED_SENDABLE_DICT:%.*]] = copy_value [[SENDABLE_DICT]]
246+
// CHECK-NEXT: assign [[COPIED_SENDABLE_DICT]] to [[DICT]]
247+
u.dict.test = 42
248+
249+
// CHECK: [[DICT_GETTER:%.*]] = class_method %0, #User.dict!modify : (User) -> () -> (), $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
250+
// CHECK-NEXT: ([[DICT:%.*]], {{.*}}) = begin_apply [[DICT_GETTER:%.*]](%0) : $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
251+
// CHECK-NEXT: [[ANY_DICT:%.*]] = alloc_stack $Dictionary<String, Any>
252+
// CHECK-NEXT: [[LOADED_DICT:%.*]] = load [copy] [[DICT]]
253+
// CHECK-NEXT: [[CASTED_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_DICT]] to $Dictionary<String, Any>
254+
// CHECK-NEXT: [[COPIED_DICT:%.*]] = copy_value [[CASTED_DICT]]
255+
// CHECK-NEXT: store [[COPIED_DICT]] to [init] [[ANY_DICT]]
256+
// CHECK: [[MUTATING_METHOD:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE12testMutatingyyF : $@convention(method) (@inout Dictionary<String, Any>) -> ()
257+
// CHECK-NEXT: %101 = apply [[MUTATING_METHOD]]([[ANY_DICT]]) : $@convention(method) (@inout Dictionary<String, Any>) -> ()
258+
// CHECK-NEXT: [[LOADED_ANY_DICT:%.*]] = load [take] [[ANY_DICT]]
259+
// CHECK-NEXT: [[SENDABLE_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_ANY_DICT]] to $Dictionary<String, any Sendable>
260+
// CHECK-NEXT: [[COPIED_SENDABLE_DICT:%.*]] = copy_value [[SENDABLE_DICT]]
261+
// CHECK-NEXT: assign [[COPIED_SENDABLE_DICT]] to [[DICT]]
262+
u.dict.testMutating()
263+
}

0 commit comments

Comments
 (0)