Skip to content

Commit 8f7e71a

Browse files
authored
Merge pull request #78487 from xedin/Sendable-to-Any-for-lvalues
[CSApply] Sendable-to-Any: Add support for l-value to l-value and inout unsafe casts
2 parents 29d1b9b + 0a0a34c commit 8f7e71a

File tree

7 files changed

+198
-21
lines changed

7 files changed

+198
-21
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1846,6 +1846,9 @@ class Solution {
18461846
/// locator.
18471847
ArgumentList *getArgumentList(ConstraintLocator *locator) const;
18481848

1849+
std::optional<ConversionRestrictionKind>
1850+
getConversionRestriction(CanType type1, CanType type2) const;
1851+
18491852
SWIFT_DEBUG_DUMP;
18501853

18511854
/// Dump this solution.

lib/Sema/CSApply.cpp

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,7 +1716,16 @@ namespace {
17161716
// pass it as an inout qualified type.
17171717
auto selfParamTy = isDynamic ? selfTy : containerTy;
17181718

1719-
if (selfTy->isEqual(baseTy))
1719+
// If type equality check fails we need to check whether the types
1720+
// are the same with deep equality restriction since `any Sendable`
1721+
// to `Any` conversion is now supported in generic argument positions
1722+
// of @preconcurrency declarations. i.e. referencing a member on
1723+
// `[any Sendable]` if member declared in an extension that expects
1724+
// `Element` to be equal to `Any`.
1725+
if (selfTy->isEqual(baseTy) ||
1726+
solution.getConversionRestriction(baseTy->getCanonicalType(),
1727+
selfTy->getCanonicalType()) ==
1728+
ConversionRestrictionKind::DeepEquality)
17201729
if (cs.getType(base)->is<LValueType>())
17211730
selfParamTy = InOutType::get(selfTy);
17221731

@@ -7375,13 +7384,42 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
73757384
// coercion.
73767385
case TypeKind::LValue: {
73777386
auto fromLValue = cast<LValueType>(desugaredFromType);
7387+
7388+
auto injectUnsafeLValueCast = [&](Type fromObjType,
7389+
Type toObjType) -> Expr * {
7390+
auto restriction = solution.getConversionRestriction(
7391+
fromObjType->getCanonicalType(), toObjType->getCanonicalType());
7392+
ASSERT(restriction == ConversionRestrictionKind::DeepEquality);
7393+
return cs.cacheType(
7394+
new (ctx) ABISafeConversionExpr(expr, LValueType::get(toObjType)));
7395+
};
7396+
7397+
// @lvalue <A> -> @lvalue <B> is only allowed if there is a
7398+
// deep equality conversion restriction between the types.
7399+
// This supports `any Sendable` -> `Any` conversion in generic
7400+
// argument positions.
7401+
if (auto *toLValue = toType->getAs<LValueType>()) {
7402+
return injectUnsafeLValueCast(fromLValue->getObjectType(),
7403+
toLValue->getObjectType());
7404+
}
7405+
73787406
auto toIO = toType->getAs<InOutType>();
73797407
if (!toIO)
73807408
return coerceToType(cs.addImplicitLoadExpr(expr), toType, locator);
73817409

7410+
// @lvalue <A> -> inout <B> has to use an unsafe cast <A> -> <B>:
7411+
// @lvalue <A> <cast to> @lvalue B -> inout B.
7412+
//
7413+
// This can happen due to any Sendable -> Any conversion in generic
7414+
// argument positions. We need to inject a cast to get @l-value to
7415+
// match `inout` type exactly.
7416+
if (!toIO->getObjectType()->isEqual(fromLValue->getObjectType())) {
7417+
expr = injectUnsafeLValueCast(fromLValue->getObjectType(),
7418+
toIO->getObjectType());
7419+
}
7420+
73827421
// In an 'inout' operator like "i += 1", the operand is converted from
73837422
// an implicit lvalue to an inout argument.
7384-
assert(toIO->getObjectType()->isEqual(fromLValue->getObjectType()));
73857423
return cs.cacheType(new (ctx) InOutExpr(expr->getStartLoc(), expr,
73867424
toIO->getObjectType(),
73877425
/*isImplicit*/ true));
@@ -7948,24 +7986,7 @@ ExprRewriter::coerceSelfArgumentToType(Expr *expr,
79487986
Type baseTy, ValueDecl *member,
79497987
ConstraintLocatorBuilder locator) {
79507988
Type toType = adjustSelfTypeForMember(expr, baseTy, member, dc);
7951-
7952-
// If our expression already has the right type, we're done.
7953-
Type fromType = cs.getType(expr);
7954-
if (fromType->isEqual(toType))
7955-
return expr;
7956-
7957-
// If we're coercing to an rvalue type, just do it.
7958-
auto toInOutTy = toType->getAs<InOutType>();
7959-
if (!toInOutTy)
7960-
return coerceToType(expr, toType, locator);
7961-
7962-
assert(fromType->is<LValueType>() && "Can only convert lvalues to inout");
7963-
7964-
// Use InOutExpr to convert it to an explicit inout argument for the
7965-
// receiver.
7966-
return cs.cacheType(new (ctx) InOutExpr(expr->getStartLoc(), expr,
7967-
toInOutTy->getInOutObjectType(),
7968-
/*isImplicit*/ true));
7989+
return coerceToType(expr, toType, locator);
79697990
}
79707991

79717992
Expr *ExprRewriter::convertLiteralInPlace(

lib/Sema/CSSimplify.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3737,6 +3737,10 @@ static bool matchSendableExistentialToAnyInGenericArgumentPosition(
37373737
return isPreconcurrencyContext(
37383738
cs.getConstraintLocator(UDE->getBase()));
37393739
}
3740+
if (auto *SE = getAsExpr<SubscriptExpr>(calleeLoc->getAnchor())) {
3741+
return isPreconcurrencyContext(
3742+
cs.getConstraintLocator(SE->getBase()));
3743+
}
37403744
return false;
37413745
}
37423746

lib/Sema/ConstraintSystem.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3949,6 +3949,14 @@ ArgumentList *Solution::getArgumentList(ConstraintLocator *locator) const {
39493949
return nullptr;
39503950
}
39513951

3952+
std::optional<ConversionRestrictionKind>
3953+
Solution::getConversionRestriction(CanType type1, CanType type2) const {
3954+
auto restriction = ConstraintRestrictions.find({type1, type2});
3955+
if (restriction != ConstraintRestrictions.end())
3956+
return restriction->second;
3957+
return std::nullopt;
3958+
}
3959+
39523960
#ifndef NDEBUG
39533961
/// Given an apply expr, returns true if it is expected to have a direct callee
39543962
/// overload, resolvable using `getChoiceFor`. Otherwise, returns false.

test/Concurrency/sendable_to_any_for_generic_arguments.swift

Lines changed: 25 additions & 0 deletions
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

Lines changed: 38 additions & 1 deletion
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

Lines changed: 79 additions & 0 deletions
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)