Skip to content

[Frontend] SE-0463: Enable SendableCompletionHandlers feature by default #80004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ LANGUAGE_FEATURE(BuiltinEmplaceTypedThrows, 0, "Builtin.emplace typed throws")
SUPPRESSIBLE_LANGUAGE_FEATURE(MemorySafetyAttributes, 458, "@unsafe attribute")
LANGUAGE_FEATURE(ValueGenerics, 452, "Value generics feature (integer generics)")
LANGUAGE_FEATURE(RawIdentifiers, 451, "Raw identifiers")
LANGUAGE_FEATURE(SendableCompletionHandlers, 463, "Objective-C completion handler parameters are imported as @Sendable")

// Swift 6
UPCOMING_FEATURE(ConciseMagicFile, 274, 6)
Expand Down Expand Up @@ -343,10 +344,6 @@ EXPERIMENTAL_FEATURE(ForwardModeDifferentiation, false)
/// conformances.
EXPERIMENTAL_FEATURE(AdditiveArithmeticDerivedConformances, false)

/// Whether Objective-C completion handler parameters are imported as
/// @Sendable.
EXPERIMENTAL_FEATURE(SendableCompletionHandlers, false)

/// Enables opaque type erasure without also enabling implict dynamic
EXPERIMENTAL_FEATURE(OpaqueTypeErasure, true)

Expand Down
19 changes: 17 additions & 2 deletions lib/Sema/TypeCheckDeclObjC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3889,12 +3889,27 @@ class ObjCImplementationChecker {
diagnoseVTableUse(cand);
return;

case MatchOutcome::WrongSendability:
case MatchOutcome::WrongSendability: {
auto concurrencyLevel = req->getASTContext().LangOpts.StrictConcurrencyLevel;

DiagnosticBehavior behavior;
switch (concurrencyLevel) {
case StrictConcurrency::Complete:
behavior = DiagnosticBehavior::Warning;
break;

case StrictConcurrency::Targeted:
case StrictConcurrency::Minimal:
behavior = DiagnosticBehavior::Ignore;
break;
}

diagnose(cand, diag::objc_implementation_sendability_mismatch, cand,
getMemberType(cand), getMemberType(req))
.limitBehaviorWithPreconcurrency(DiagnosticBehavior::Warning,
.limitBehaviorWithPreconcurrency(behavior,
/*preconcurrency*/ true);
return;
}

case MatchOutcome::WrongImplicitObjCName:
case MatchOutcome::WrongExplicitObjCName: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ class TestConformanceWithoutStripping : InnerSendableTypes {
@objc public required init(callback: @escaping () -> Void) {}
// expected-error@-1 {{initializer 'init(callback:)' should not be 'required' to match initializer declared by the header}}

@objc func compute(completionHandler: @escaping () -> Void) {}
// expected-warning@-1 {{sendability of function types in instance method 'compute(completionHandler:)' of type '(@escaping () -> Void) -> ()' does not match type '(@escaping @Sendable () -> Void) -> Void' declared by the header}}
@objc func compute(completionHandler: @escaping () -> Void) {} // Ok (no warnings with minimal checking)
}

// Methods deliberately has no `@Sendable` to make sure that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ void doSomethingConcurrently(__attribute__((noescape)) void SWIFT_SENDABLE (^blo
-(void) testWithCallback:(NSString *)name handler:(MAIN_ACTOR void (^)(NSDictionary<NSString *, SWIFT_SENDABLE id> *, NSError * _Nullable))handler;
@end

@interface SwiftImpl : NSObject
-(id)initWithCallback: (void (^ SWIFT_SENDABLE)(void)) callback;
-(void)computeWithCompletionHandler: (void (^)(void)) handler;
@end

#pragma clang assume_nonnull end

//--- main.swift
Expand Down Expand Up @@ -153,3 +158,11 @@ class TestConformanceWithoutStripping : InnerSendableTypes {
func test(withCallback name: String, handler: @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) { // Ok
}
}

@objc @implementation extension SwiftImpl {
@objc public required init(callback: @escaping () -> Void) {}
// expected-error@-1 {{initializer 'init(callback:)' should not be 'required' to match initializer declared by the header}}

@objc func compute(completionHandler: @escaping () -> Void) {}
// expected-warning@-1 {{sendability of function types in instance method 'compute(completionHandler:)' of type '(@escaping () -> Void) -> ()' does not match type '(@escaping @Sendable () -> Void) -> Void' declared by the header}}
}
28 changes: 14 additions & 14 deletions test/SILGen/objc_async.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ func testSlowServer(slowServer: SlowServer) async throws {
// CHECK: [[RESUME_BUF:%.*]] = alloc_stack $Int
// CHECK: [[STRINGINIT:%.*]] = function_ref @$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF :
// CHECK: [[ARG:%.*]] = apply [[STRINGINIT]]
// CHECK: [[METHOD:%.*]] = objc_method {{.*}} $@convention(objc_method) (NSString, @convention(block) (Int) -> (), SlowServer) -> ()
// CHECK: [[METHOD:%.*]] = objc_method {{.*}} $@convention(objc_method) (NSString, @convention(block) @Sendable (Int) -> (), SlowServer) -> ()
// CHECK: [[CONT:%.*]] = get_async_continuation_addr Int, [[RESUME_BUF]]
// CHECK: [[WRAPPED:%.*]] = struct $UnsafeContinuation<Int, Never> ([[CONT]] : $Builtin.RawUnsafeContinuation)
// CHECK: [[BLOCK_STORAGE:%.*]] = alloc_stack $@block_storage Any
// CHECK: [[CONT_SLOT:%.*]] = project_block_storage [[BLOCK_STORAGE]]
// CHECK: [[CONT_SLOT_ADDR:%.*]] = init_existential_addr [[CONT_SLOT]]
// CHECK: store [[WRAPPED]] to [trivial] [[CONT_SLOT_ADDR]]
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[INT_COMPLETION_BLOCK:.*]] : $@convention(c) (@inout_aliasable @block_storage Any, Int) -> ()
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[INT_COMPLETION_BLOCK:.*]] : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, Int) -> ()
// CHECK: [[BLOCK:%.*]] = init_block_storage_header [[BLOCK_STORAGE]] {{.*}}, invoke [[BLOCK_IMPL]]
// CHECK: apply [[METHOD]]([[ARG]], [[BLOCK]], %0)
// CHECK: [[COPY:%.*]] = copy_value [[ARG]]
Expand All @@ -33,14 +33,14 @@ func testSlowServer(slowServer: SlowServer) async throws {
let _: Int = await slowServer.doSomethingSlowNullably("mail")

// CHECK: [[RESUME_BUF:%.*]] = alloc_stack $String
// CHECK: [[METHOD:%.*]] = objc_method {{.*}} $@convention(objc_method) (@convention(block) (Optional<NSString>, Optional<NSError>) -> (), SlowServer) -> ()
// CHECK: [[METHOD:%.*]] = objc_method {{.*}} $@convention(objc_method) (@convention(block) @Sendable (Optional<NSString>, Optional<NSError>) -> (), SlowServer) -> ()
// CHECK: [[CONT:%.*]] = get_async_continuation_addr [throws] String, [[RESUME_BUF]]
// CHECK: [[WRAPPED:%.*]] = struct $UnsafeContinuation<String, any Error> ([[CONT]] : $Builtin.RawUnsafeContinuation)
// CHECK: [[BLOCK_STORAGE:%.*]] = alloc_stack $@block_storage Any
// CHECK: [[CONT_SLOT:%.*]] = project_block_storage [[BLOCK_STORAGE]]
// CHECK: [[CONT_SLOT_ADDR:%.*]] = init_existential_addr [[CONT_SLOT]]
// CHECK: store [[WRAPPED]] to [trivial] [[CONT_SLOT_ADDR]]
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[STRING_COMPLETION_THROW_BLOCK:.*]] : $@convention(c) (@inout_aliasable @block_storage Any, Optional<NSString>, Optional<NSError>) -> ()
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[STRING_COMPLETION_THROW_BLOCK:.*]] : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, Optional<NSString>, Optional<NSError>) -> ()
// CHECK: [[BLOCK:%.*]] = init_block_storage_header [[BLOCK_STORAGE]] {{.*}}, invoke [[BLOCK_IMPL]]
// CHECK: apply [[METHOD]]([[BLOCK]], %0)
// CHECK: await_async_continuation [[CONT]] {{.*}}, resume [[RESUME:bb[0-9]+]], error [[ERROR:bb[0-9]+]]
Expand All @@ -50,18 +50,18 @@ func testSlowServer(slowServer: SlowServer) async throws {
// CHECK: dealloc_stack [[RESUME_BUF]]
let _: String = try await slowServer.findAnswer()

// CHECK: objc_method {{.*}} $@convention(objc_method) (NSString, @convention(block) () -> (), SlowServer) -> ()
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[VOID_COMPLETION_BLOCK:.*]] : $@convention(c) (@inout_aliasable @block_storage Any) -> ()
// CHECK: objc_method {{.*}} $@convention(objc_method) (NSString, @convention(block) @Sendable () -> (), SlowServer) -> ()
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[VOID_COMPLETION_BLOCK:.*]] : $@convention(c) @Sendable (@inout_aliasable @block_storage Any) -> ()
await slowServer.serverRestart("somewhere")

// CHECK: function_ref @[[STRING_NONZERO_FLAG_THROW_BLOCK:.*]] : $@convention(c) (@inout_aliasable @block_storage Any, {{.*}}Bool, Optional<NSString>, Optional<NSError>) -> ()
// CHECK: function_ref @[[STRING_NONZERO_FLAG_THROW_BLOCK:.*]] : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, {{.*}}Bool, Optional<NSString>, Optional<NSError>) -> ()
let _: String = try await slowServer.doSomethingFlaggy()
// CHECK: function_ref @[[STRING_ZERO_FLAG_THROW_BLOCK:.*]] : $@convention(c) (@inout_aliasable @block_storage Any, Optional<NSString>, {{.*}}Bool, Optional<NSError>) -> ()
// CHECK: function_ref @[[STRING_ZERO_FLAG_THROW_BLOCK:.*]] : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, Optional<NSString>, {{.*}}Bool, Optional<NSError>) -> ()
let _: String = try await slowServer.doSomethingZeroFlaggy()
// CHECK: function_ref @[[STRING_STRING_ZERO_FLAG_THROW_BLOCK:.*]] : $@convention(c) (@inout_aliasable @block_storage Any, {{.*}}Bool, Optional<NSString>, Optional<NSError>, Optional<NSString>) -> ()
// CHECK: function_ref @[[STRING_STRING_ZERO_FLAG_THROW_BLOCK:.*]] : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, {{.*}}Bool, Optional<NSString>, Optional<NSError>, Optional<NSString>) -> ()
let _: (String, String) = try await slowServer.doSomethingMultiResultFlaggy()

// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[NSSTRING_INT_THROW_COMPLETION_BLOCK:.*]] : $@convention(c) (@inout_aliasable @block_storage Any, Optional<NSString>, Int, Optional<NSError>) -> ()
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[NSSTRING_INT_THROW_COMPLETION_BLOCK:.*]] : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, Optional<NSString>, Int, Optional<NSError>) -> ()
let (_, _): (String, Int) = try await slowServer.findMultipleAnswers()

let (_, _): (Bool, Bool) = try await slowServer.findDifferentlyFlavoredBooleans()
Expand Down Expand Up @@ -189,14 +189,14 @@ func testSlowServerFromMain(slowServer: SlowServer) async throws {
// CHECK: [[RESUME_BUF:%.*]] = alloc_stack $Int
// CHECK: [[STRINGINIT:%.*]] = function_ref @$sSS10FoundationE19_bridgeToObjectiveCSo8NSStringCyF :
// CHECK: [[ARG:%.*]] = apply [[STRINGINIT]]
// CHECK: [[METHOD:%.*]] = objc_method {{.*}} $@convention(objc_method) (NSString, @convention(block) (Int) -> (), SlowServer) -> ()
// CHECK: [[METHOD:%.*]] = objc_method {{.*}} $@convention(objc_method) (NSString, @convention(block) @Sendable (Int) -> (), SlowServer) -> ()
// CHECK: [[CONT:%.*]] = get_async_continuation_addr Int, [[RESUME_BUF]]
// CHECK: [[WRAPPED:%.*]] = struct $UnsafeContinuation<Int, Never> ([[CONT]] : $Builtin.RawUnsafeContinuation)
// CHECK: [[BLOCK_STORAGE:%.*]] = alloc_stack $@block_storage Any
// CHECK: [[CONT_SLOT:%.*]] = project_block_storage [[BLOCK_STORAGE]]
// CHECK: [[CONT_SLOT_ANY:%.*]] = init_existential_addr [[CONT_SLOT]]
// CHECK: store [[WRAPPED]] to [trivial] [[CONT_SLOT_ANY]]
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[INT_COMPLETION_BLOCK:.*]] : $@convention(c) (@inout_aliasable @block_storage Any, Int) -> ()
// CHECK: [[BLOCK_IMPL:%.*]] = function_ref @[[INT_COMPLETION_BLOCK:.*]] : $@convention(c) @Sendable (@inout_aliasable @block_storage Any, Int) -> ()
// CHECK: [[BLOCK:%.*]] = init_block_storage_header [[BLOCK_STORAGE]] {{.*}}, invoke [[BLOCK_IMPL]]
// CHECK: apply [[METHOD]]([[ARG]], [[BLOCK]], %0)
// CHECK: [[COPY:%.*]] = copy_value [[ARG]]
Expand All @@ -223,10 +223,10 @@ func testThrowingMethodFromMain(slowServer: SlowServer) async -> String {
// CHECK: [[PROJECTED:%.*]] = project_block_storage [[STORE_ALLOC]] : $*@block_storage
// CHECK: [[PROJECTED_ANY:%.*]] = init_existential_addr [[PROJECTED]]
// CHECK: store [[CONT]] to [trivial] [[PROJECTED_ANY]]
// CHECK: [[INVOKER:%.*]] = function_ref @$sSo8NSStringCSgSo7NSErrorCSgIeyByy_SSTz_
// CHECK: [[INVOKER:%.*]] = function_ref @$sSo8NSStringCSgSo7NSErrorCSgIeyBhyy_SSTz_
// CHECK: [[BLOCK:%.*]] = init_block_storage_header [[STORE_ALLOC]] {{.*}}, invoke [[INVOKER]]
// CHECK: [[OPTIONAL_BLK:%.*]] = enum {{.*}}, #Optional.some!enumelt, [[BLOCK]]
// CHECK: apply [[METH]]([[STRING_ARG]], [[OPTIONAL_BLK]], {{%.*}}) : $@convention(objc_method) (NSString, Optional<@convention(block) (Optional<NSString>, Optional<NSError>) -> ()>, SlowServer) -> ()
// CHECK: apply [[METH]]([[STRING_ARG]], [[OPTIONAL_BLK]], {{%.*}}) : $@convention(objc_method) (NSString, Optional<@convention(block) @Sendable (Optional<NSString>, Optional<NSError>) -> ()>, SlowServer) -> ()
// CHECK: [[STRING_ARG_COPY:%.*]] = copy_value [[STRING_ARG]] : $NSString
// CHECK: dealloc_stack [[STORE_ALLOC]] : $*@block_storage Any
// CHECK: destroy_value [[STRING_ARG]] : $NSString
Expand Down
Loading