Skip to content

Commit

Permalink
Merge pull request swiftlang#78492 from xedin/Sendable-to-Any-for-lva…
Browse files Browse the repository at this point in the history
…lues-6.1

[6.1][CSApply] Sendable-to-Any: Add support for l-value to l-value and inout unsafe casts
  • Loading branch information
xedin authored Jan 9, 2025
2 parents 89bd00d + 4778032 commit 5abc22e
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 23 deletions.
3 changes: 3 additions & 0 deletions include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,9 @@ class Solution {
/// locator.
ArgumentList *getArgumentList(ConstraintLocator *locator) const;

std::optional<ConversionRestrictionKind>
getConversionRestriction(CanType type1, CanType type2) const;

SWIFT_DEBUG_DUMP;

/// Dump this solution.
Expand Down
63 changes: 41 additions & 22 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1719,7 +1719,16 @@ namespace {
// pass it as an inout qualified type.
auto selfParamTy = isDynamic ? selfTy : containerTy;

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

Expand Down Expand Up @@ -7407,13 +7416,42 @@ Expr *ExprRewriter::coerceToType(Expr *expr, Type toType,
// coercion.
case TypeKind::LValue: {
auto fromLValue = cast<LValueType>(desugaredFromType);

auto injectUnsafeLValueCast = [&](Type fromObjType,
Type toObjType) -> Expr * {
auto restriction = solution.getConversionRestriction(
fromObjType->getCanonicalType(), toObjType->getCanonicalType());
ASSERT(restriction == ConversionRestrictionKind::DeepEquality);
return cs.cacheType(
new (ctx) ABISafeConversionExpr(expr, LValueType::get(toObjType)));
};

// @lvalue <A> -> @lvalue <B> is only allowed if there is a
// deep equality conversion restriction between the types.
// This supports `any Sendable` -> `Any` conversion in generic
// argument positions.
if (auto *toLValue = toType->getAs<LValueType>()) {
return injectUnsafeLValueCast(fromLValue->getObjectType(),
toLValue->getObjectType());
}

auto toIO = toType->getAs<InOutType>();
if (!toIO)
return coerceToType(cs.addImplicitLoadExpr(expr), toType, locator);

// @lvalue <A> -> inout <B> has to use an unsafe cast <A> -> <B>:
// @lvalue <A> <cast to> @lvalue B -> inout B.
//
// This can happen due to any Sendable -> Any conversion in generic
// argument positions. We need to inject a cast to get @l-value to
// match `inout` type exactly.
if (!toIO->getObjectType()->isEqual(fromLValue->getObjectType())) {
expr = injectUnsafeLValueCast(fromLValue->getObjectType(),
toIO->getObjectType());
}

// In an 'inout' operator like "i += 1", the operand is converted from
// an implicit lvalue to an inout argument.
assert(toIO->getObjectType()->isEqual(fromLValue->getObjectType()));
return cs.cacheType(new (ctx) InOutExpr(expr->getStartLoc(), expr,
toIO->getObjectType(),
/*isImplicit*/ true));
Expand Down Expand Up @@ -7980,26 +8018,7 @@ ExprRewriter::coerceSelfArgumentToType(Expr *expr,
Type baseTy, ValueDecl *member,
ConstraintLocatorBuilder locator) {
Type toType = adjustSelfTypeForMember(expr, baseTy, member, dc);

// If our expression already has the right type, we're done.
Type fromType = cs.getType(expr);
if (fromType->isEqual(toType))
return expr;

// If we're coercing to an rvalue type, just do it.
auto toInOutTy = toType->getAs<InOutType>();
if (!toInOutTy)
return coerceToType(expr, toType, locator);

assert(fromType->is<LValueType>() && "Can only convert lvalues to inout");

auto &ctx = cs.getASTContext();

// Use InOutExpr to convert it to an explicit inout argument for the
// receiver.
return cs.cacheType(new (ctx) InOutExpr(expr->getStartLoc(), expr,
toInOutTy->getInOutObjectType(),
/*isImplicit*/ true));
return coerceToType(expr, toType, locator);
}

Expr *ExprRewriter::convertLiteralInPlace(
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3722,6 +3722,10 @@ static bool matchSendableExistentialToAnyInGenericArgumentPosition(
return isPreconcurrencyContext(
cs.getConstraintLocator(UDE->getBase()));
}
if (auto *SE = getAsExpr<SubscriptExpr>(calleeLoc->getAnchor())) {
return isPreconcurrencyContext(
cs.getConstraintLocator(SE->getBase()));
}
return false;
}

Expand Down
8 changes: 8 additions & 0 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3948,6 +3948,14 @@ ArgumentList *Solution::getArgumentList(ConstraintLocator *locator) const {
return nullptr;
}

std::optional<ConversionRestrictionKind>
Solution::getConversionRestriction(CanType type1, CanType type2) const {
auto restriction = ConstraintRestrictions.find({type1, type2});
if (restriction != ConstraintRestrictions.end())
return restriction->second;
return std::nullopt;
}

#ifndef NDEBUG
/// Given an apply expr, returns true if it is expected to have a direct callee
/// overload, resolvable using `getChoiceFor`. Otherwise, returns false.
Expand Down
25 changes: 25 additions & 0 deletions test/Concurrency/sendable_to_any_for_generic_arguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,28 @@ struct TestGeneral {
let _: S<((Any) -> Void) -> Void> = funcInFunc // Ok
}
}

// Make sure that properties and subscripts and mutating methods work.
extension Dictionary where Key == String, Value == Any {
subscript<T>(entry object: T) -> T? {
get { nil }
set { }
}

var test: Int? {
get { nil }
set { }
}

mutating func testMutating() {}
}

func test_subscript_computed_property_and_mutating_access(u: User) {
_ = u.dict[entry: ""] // Ok
u.dict[entry: 42] = 42 // Ok

_ = u.dict.test // Ok
u.dict.test = 42 // Ok

u.dict.testMutating() // Ok
}
39 changes: 38 additions & 1 deletion test/Interpreter/sendable_erasure_to_any_in_preconcurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ class C {

extension Dictionary where Key == String, Value == Any {
func answer() -> Int { self["a"] as! Int }

subscript(entry e: String) -> (any Sendable)? {
get { self["Entry#" + e] }
set { self["Entry#" + e] = newValue }
}

var entryB: (any Sendable)? {
get { self["Entry#B"] }
set { self["Entry#B"] = newValue }
}

mutating func addEntry() {
self["ultimate question"] = "???"
}
}

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


func test() {
let c = C()
var c = C()

print(c.dict.answer())
// CHECK: 42
print(c.arr.answer())
// CHECK: 42

print(c.dict[entry: "A"] ?? "no A")
// CHECK: no A

// Insert a new value
c.dict[entry: "A"] = "forty two"

// Make sure that the dictionary got mutated
print(c.dict[entry: "A"] ?? "no A")
// CHECK: forty two

print(c.dict.entryB ?? "no B")
// CHECK: no B

// Insert new value
c.dict.entryB = (q: "", a: 42)

print(c.dict.entryB ?? "no B")
// CHECK: (q: "", a: 42)

c.dict.addEntry()
print(c.dict["ultimate question"] ?? "no question")
// CHECK: ???

let v1 = Test(data: S(v: 42))
let v2 = Test(data: S(v: "ultimate question"))

Expand Down
79 changes: 79 additions & 0 deletions test/SILGen/sendable_to_any_for_generic_arguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,82 @@ struct TestGeneral {
accepts_any(test.data)
}
}

extension Dictionary where Key == String, Value == Any {
subscript<T>(entry object: T) -> T? {
get { nil }
set { }
}

var test: Int? {
get { nil }
set { }
}

mutating func testMutating() {}
}

func test_subscript_computed_property_and_mutating_access(u: User) {
// CHECK: [[DICT_GETTER:%.*]] = class_method %0 : $User, #User.dict!getter : (User) -> () -> [String : any Sendable], $@convention(method) (@guaranteed User) -> @owned Dictionary<String, any Sendable>
// CHECK-NEXT: [[DICT:%.*]] = apply [[DICT_GETTER]]({{.*}}) : $@convention(method) (@guaranteed User) -> @owned Dictionary<String, any Sendable>
// CHECK-NEXT: [[ANY_DICT:%.*]] = unchecked_bitwise_cast [[DICT]] : $Dictionary<String, any Sendable> to $Dictionary<String, Any>
// CHECK-NEXT: [[ANY_DICT_COPY:%.*]] = copy_value [[ANY_DICT]]
// CHECK-NEXT: [[BORROWED_COPY:%.*]] = begin_borrow [[ANY_DICT_COPY]]
// CHECK: [[SUBSCRIPT_GETTER:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE5entryqd__Sgqd___tcluig
// CHECK-NEXT: {{.*}} = apply [[SUBSCRIPT_GETTER]]<String, Any, String>({{.*}}, [[BORROWED_COPY]])
_ = u.dict[entry: ""]

// CHECK: [[DICT_GETTER:%.*]] = class_method %0 : $User, #User.dict!modify : (User) -> () -> (), $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
// CHECK-NEXT: ([[DICT_ADDR:%.*]], {{.*}}) = begin_apply [[DICT_GETTER]]({{.*}}) : $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
// CHECK-NEXT: [[ANY_DICT:%.*]] = alloc_stack $Dictionary<String, Any>
// CHECK-NEXT: [[LOADED_DICT:%.*]] = load [copy] [[DICT_ADDR]]
// CHECK-NEXT: [[ANY_LOADED_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_DICT]] : $Dictionary<String, any Sendable> to $Dictionary<String, Any>
// CHECK-NEXT: [[COPIED_ANY_DICT:%.*]] = copy_value [[ANY_LOADED_DICT]]
// CHECK-NEXT: store [[COPIED_ANY_DICT]] to [init] [[ANY_DICT]]
// CHECK: [[SUBSCRIPT_SETTER:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE5entryqd__Sgqd___tcluis
// CHECK-NEXT: %48 = apply [[SUBSCRIPT_SETTER]]<String, Any, Int>({{.*}}, [[ANY_DICT]])
// CHECK-NEXT: [[LOADED_ANY_DICT:%.*]] = load [take] [[ANY_DICT]]
// CHECK-NEXT: [[SENDABLE_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_ANY_DICT]] : $Dictionary<String, Any> to $Dictionary<String, any Sendable>
// CHECK-NEXT: [[COPIED_SENDABLE_DICT:%.*]] = copy_value [[SENDABLE_DICT]]
// CHECK-NEXT: assign [[COPIED_SENDABLE_DICT]] to [[DICT_ADDR]]
u.dict[entry: 42] = 42

// CHECK: [[DICT_GETTER:%.*]] = class_method %0 : $User, #User.dict!getter : (User) -> () -> [String : any Sendable], $@convention(method) (@guaranteed User) -> @owned Dictionary<String, any Sendable>
// CHECK-NEXT: [[SENDABLE_DICT:%.*]] = apply [[DICT_GETTER]]({{.*}}) : $@convention(method) (@guaranteed User) -> @owned Dictionary<String, any Sendable>
// CHECK-NEXT: [[DICT_CAST_TO_ANY:%.*]] = unchecked_bitwise_cast [[SENDABLE_DICT]] : $Dictionary<String, any Sendable> to $Dictionary<String, Any>
// CHECK-NEXT: [[ANY_DICT_COPY:%.*]] = copy_value [[DICT_CAST_TO_ANY]]
// CHECK-NEXT: [[ANY_DICT:%.*]] = begin_borrow [[ANY_DICT_COPY]]
// CHECK: [[GETTER:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE4testSiSgvg
// CHECK-NEXT: {{.*}} = apply [[GETTER]]([[ANY_DICT]]) : $@convention(method) (@guaranteed Dictionary<String, Any>) -> Optional<Int>
_ = u.dict.test

// CHECK: [[DICT_GETTER:%.*]] = class_method %0 : $User, #User.dict!modify : (User) -> () -> (), $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
// CHECK-NEXT: ([[DICT:%.*]], {{.*}}) = begin_apply [[DICT_GETTER]]({{.*}}) : $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
// CHECK-NEXT: [[ANY_DICT:%.*]] = alloc_stack $Dictionary<String, Any>
// CHECK-NEXT: [[LOADED_DICT:%.*]] = load [copy] [[DICT]]
// CHECK-NEXT: [[CASTED_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_DICT]] : $Dictionary<String, any Sendable> to $Dictionary<String, Any>
// CHECK-NEXT: [[COPIED_CASTED_DICT:%.*]] = copy_value [[CASTED_DICT]]
// CHECK-NEXT: store [[COPIED_CASTED_DICT]] to [init] [[ANY_DICT]]
// CHECK: [[SETTER:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE4testSiSgvs
// CHECK-NEXT: {{.*}} = apply [[SETTER]]({{.*}}, [[ANY_DICT]]) : $@convention(method) (Optional<Int>, @inout Dictionary<String, Any>) -> ()
// CHECK-NEXT: [[LOADED_ANY_DICT:%.*]] = load [take] [[ANY_DICT]]
// CHECK-NEXT: [[SENDABLE_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_ANY_DICT]] : $Dictionary<String, Any> to $Dictionary<String, any Sendable>
// CHECK-NEXT: [[COPIED_SENDABLE_DICT:%.*]] = copy_value [[SENDABLE_DICT]]
// CHECK-NEXT: assign [[COPIED_SENDABLE_DICT]] to [[DICT]]
u.dict.test = 42

// CHECK: [[DICT_GETTER:%.*]] = class_method %0 : $User, #User.dict!modify : (User) -> () -> (), $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
// CHECK-NEXT: ([[DICT:%.*]], {{.*}}) = begin_apply [[DICT_GETTER:%.*]](%0) : $@yield_once @convention(method) (@guaranteed User) -> @yields @inout Dictionary<String, any Sendable>
// CHECK-NEXT: [[ANY_DICT:%.*]] = alloc_stack $Dictionary<String, Any>
// CHECK-NEXT: [[LOADED_DICT:%.*]] = load [copy] [[DICT]]
// CHECK-NEXT: [[CASTED_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_DICT]] : $Dictionary<String, any Sendable> to $Dictionary<String, Any>
// CHECK-NEXT: [[COPIED_DICT:%.*]] = copy_value [[CASTED_DICT]]
// CHECK-NEXT: store [[COPIED_DICT]] to [init] [[ANY_DICT]]
// CHECK: [[MUTATING_METHOD:%.*]] = function_ref @$sSD37sendable_to_any_for_generic_argumentsSSRszypRs_rlE12testMutatingyyF : $@convention(method) (@inout Dictionary<String, Any>) -> ()
// CHECK-NEXT: %101 = apply [[MUTATING_METHOD]]([[ANY_DICT]]) : $@convention(method) (@inout Dictionary<String, Any>) -> ()
// CHECK-NEXT: [[LOADED_ANY_DICT:%.*]] = load [take] [[ANY_DICT]]
// CHECK-NEXT: [[SENDABLE_DICT:%.*]] = unchecked_bitwise_cast [[LOADED_ANY_DICT]] : $Dictionary<String, Any> to $Dictionary<String, any Sendable>
// CHECK-NEXT: [[COPIED_SENDABLE_DICT:%.*]] = copy_value [[SENDABLE_DICT]]
// CHECK-NEXT: assign [[COPIED_SENDABLE_DICT]] to [[DICT]]
u.dict.testMutating()
}

0 comments on commit 5abc22e

Please sign in to comment.