Skip to content

[TypeChecker] Adjust witness matching to strip concurrency from ObjC … #71493

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
Feb 9, 2024
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
37 changes: 37 additions & 0 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1169,6 +1169,43 @@ swift::matchWitness(WitnessChecker::RequirementEnvironmentCache &reqEnvCache,
// Match a type in the requirement to a type in the witness.
auto matchTypes = [&](Type reqType,
Type witnessType) -> llvm::Optional<RequirementMatch> {
// `swift_attr` attributes in the type context were ignored before,
// which means that we need to maintain status quo to avoid breaking
// witness matching by stripping everything concurrency related from
// inner types.
auto shouldStripConcurrency = [&]() {
if (!req->isObjC())
return false;

auto &ctx = dc->getASTContext();
return !(ctx.isSwiftVersionAtLeast(6) ||
ctx.LangOpts.StrictConcurrencyLevel ==
StrictConcurrency::Complete);
};

if (shouldStripConcurrency()) {
if (reqType->is<FunctionType>()) {
auto *fnTy = reqType->castTo<FunctionType>();
SmallVector<AnyFunctionType::Param, 4> params;
llvm::transform(fnTy->getParams(), std::back_inserter(params),
[&](const AnyFunctionType::Param &param) {
return param.withType(
param.getPlainType()->stripConcurrency(
/*recursive=*/true,
/*dropGlobalActor=*/true));
});

auto resultTy =
fnTy->getResult()->stripConcurrency(/*recursive=*/true,
/*dropGlobalActor=*/true);

reqType = FunctionType::get(params, resultTy, fnTy->getExtInfo());
} else {
reqType = reqType->stripConcurrency(/*recursive=*/true,
/*dropGlobalActor=*/true);
}
}

cs->addConstraint(ConstraintKind::Bind, reqType, witnessType, locator);
// FIXME: Check whether this has already failed.
return llvm::None;
Expand Down
129 changes: 129 additions & 0 deletions test/Concurrency/sendable_objc_attr_in_type_context_swift5.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// RUN: %empty-directory(%t/src)
// RUN: %empty-directory(%t/sdk)
// RUN: split-file %s %t/src

// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %t/src/main.swift \
// RUN: -import-objc-header %t/src/Test.h \
// RUN: -swift-version 5 \
// RUN: -enable-experimental-feature SendableCompletionHandlers \
// RUN: -module-name main -I %t -verify

// REQUIRES: objc_interop
// REQUIRES: asserts

//--- Test.h
#define SWIFT_SENDABLE __attribute__((__swift_attr__("@Sendable")))
#define NONSENDABLE __attribute__((__swift_attr__("@_nonSendable")))
#define MAIN_ACTOR __attribute__((__swift_attr__("@MainActor")))

#pragma clang assume_nonnull begin

@import Foundation;

@interface MyValue : NSObject
@end

SWIFT_SENDABLE
@protocol P <NSObject>
@end

@interface SendableValue : NSObject<P>
@end

SWIFT_SENDABLE
@interface SendableMyValue : MyValue
@end

typedef void (^CompletionHandler)(void (^ SWIFT_SENDABLE)(void)) SWIFT_SENDABLE;

@interface Test : NSObject
-(void) makeRequest:
(NSString *)typeIdentifier
loadHandler:(void (^SWIFT_SENDABLE )(void (SWIFT_SENDABLE ^)(void)))loadHandler;
-(void) withSendableId: (void (^)(SWIFT_SENDABLE id)) handler;
-(void) withSendableCustom: (void (^)(MyValue *_Nullable SWIFT_SENDABLE)) handler;
-(void) withNonSendable:(NSString *)operation completionHandler:(void (^ _Nullable NONSENDABLE)(NSString *_Nullable, NSError * _Nullable)) handler;
-(void) withAliasCompletionHandler:(CompletionHandler)handler;
@end

// Placement of SWIFT_SENDABLE matters here
void doSomethingConcurrently(__attribute__((noescape)) void SWIFT_SENDABLE (^block)(void));

@interface TestWithSendableID<T: SWIFT_SENDABLE id> : NSObject
-(void) add: (T) object;
@end

@interface TestWithSendableSuperclass<T: MyValue *SWIFT_SENDABLE> : NSObject
-(void) add: (T) object;
@end

@protocol InnerSendableTypes
-(void) test:(NSDictionary<NSString *, SWIFT_SENDABLE id> *)options;
-(void) testWithCallback:(NSString *)name handler:(MAIN_ACTOR void (^)(NSDictionary<NSString *, SWIFT_SENDABLE id> *, NSError * _Nullable))handler;
@end

#pragma clang assume_nonnull end

//--- main.swift

func test_sendable_attr_in_type_context(test: Test) {
let fn: (String?, (any Error)?) -> Void = { _,_ in }

test.withNonSendable("", completionHandler: fn) // Ok

test.makeRequest("id") {
doSomethingConcurrently($0) // Ok
}

test.makeRequest("id") { callback in
_ = { @Sendable in
callback() // Ok
}
}

test.withSendableId { id in
_ = { @Sendable in
print(id) // Ok
}
}

test.withSendableCustom { val in
_ = { @Sendable in
print(val!)
}
}

let _: (@escaping @Sendable (@escaping @Sendable () -> Void) -> Void) -> Void = test.withAliasCompletionHandler

test.withAliasCompletionHandler { callback in
doSomethingConcurrently(callback) // Ok
}

_ = TestWithSendableID<SendableValue>() // Ok

TestWithSendableID().add(MyValue())

TestWithSendableSuperclass().add(SendableMyValue()) // Ok

TestWithSendableSuperclass().add(MyValue())
}

class TestConformanceWithStripping : InnerSendableTypes {
func test(_ options: [String: Any]) { // Ok
}

func test(withCallback name: String, handler: @escaping @MainActor ([String : Any], (any Error)?) -> Void) { // Ok
}
}

class TestConformanceWithoutStripping : InnerSendableTypes {
// expected-error@-1 {{type 'TestConformanceWithoutStripping' does not conform to protocol 'InnerSendableTypes'}}

func test(_ options: [String: any Sendable]) {
// expected-note@-1 {{candidate has non-matching type '([String : any Sendable]) -> ()'}}
}

func test(withCallback name: String, handler: @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) {
// expected-note@-1 {{candidate has non-matching type '(String, @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) -> ()'}}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %t/src/main.swift \
// RUN: -import-objc-header %t/src/Test.h \
// RUN: -swift-version 5 \
// RUN: -strict-concurrency=complete \
// RUN: -enable-experimental-feature SendableCompletionHandlers \
// RUN: -module-name main -I %t -verify
Expand All @@ -14,6 +15,7 @@
//--- Test.h
#define SWIFT_SENDABLE __attribute__((__swift_attr__("@Sendable")))
#define NONSENDABLE __attribute__((__swift_attr__("@_nonSendable")))
#define MAIN_ACTOR __attribute__((__swift_attr__("@MainActor")))

#pragma clang assume_nonnull begin

Expand Down Expand Up @@ -56,6 +58,11 @@ void doSomethingConcurrently(__attribute__((noescape)) void SWIFT_SENDABLE (^blo
-(void) add: (T) object;
@end

@protocol InnerSendableTypes
-(void) test:(NSDictionary<NSString *, SWIFT_SENDABLE id> *)options;
-(void) testWithCallback:(NSString *)name handler:(MAIN_ACTOR void (^)(NSDictionary<NSString *, SWIFT_SENDABLE id> *, NSError * _Nullable))handler;
@end

#pragma clang assume_nonnull end

//--- main.swift
Expand Down Expand Up @@ -105,3 +112,23 @@ func test_sendable_attr_in_type_context(test: Test) {
TestWithSendableSuperclass().add(MyValue())
// expected-warning@-1 3 {{type 'MyValue' does not conform to the 'Sendable' protocol}}
}

class TestConformanceWithStripping : InnerSendableTypes {
// expected-error@-1 {{type 'TestConformanceWithStripping' does not conform to protocol 'InnerSendableTypes'}}

func test(_ options: [String: Any]) {
// expected-note@-1 {{candidate has non-matching type '([String : Any]) -> ()'}}
}

func test(withCallback name: String, handler: @escaping @MainActor ([String : Any], (any Error)?) -> Void) {
// expected-note@-1 {{candidate has non-matching type '(String, @escaping @MainActor ([String : Any], (any Error)?) -> Void) -> ()'}}
}
}

class TestConformanceWithoutStripping : InnerSendableTypes {
func test(_ options: [String: any Sendable]) { // Ok
}

func test(withCallback name: String, handler: @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) { // Ok
}
}
133 changes: 133 additions & 0 deletions test/Concurrency/sendable_objc_attr_in_type_context_swift6.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// RUN: %empty-directory(%t/src)
// RUN: %empty-directory(%t/sdk)
// RUN: split-file %s %t/src

// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck %t/src/main.swift \
// RUN: -import-objc-header %t/src/Test.h \
// RUN: -swift-version 6 \
// RUN: -enable-experimental-feature SendableCompletionHandlers \
// RUN: -module-name main -I %t -verify

// REQUIRES: objc_interop
// REQUIRES: asserts

//--- Test.h
#define SWIFT_SENDABLE __attribute__((__swift_attr__("@Sendable")))
#define NONSENDABLE __attribute__((__swift_attr__("@_nonSendable")))
#define MAIN_ACTOR __attribute__((__swift_attr__("@MainActor")))

#pragma clang assume_nonnull begin

@import Foundation;

@interface MyValue : NSObject
@end

SWIFT_SENDABLE
@protocol P <NSObject>
@end

@interface SendableValue : NSObject<P>
@end

SWIFT_SENDABLE
@interface SendableMyValue : MyValue
@end

typedef void (^CompletionHandler)(void (^ SWIFT_SENDABLE)(void)) SWIFT_SENDABLE;

@interface Test : NSObject
-(void) makeRequest:
(NSString *)typeIdentifier
loadHandler:(void (^SWIFT_SENDABLE )(void (SWIFT_SENDABLE ^)(void)))loadHandler;
-(void) withSendableId: (void (^)(SWIFT_SENDABLE id)) handler;
-(void) withSendableCustom: (void (^)(MyValue *_Nullable SWIFT_SENDABLE)) handler;
-(void) withNonSendable:(NSString *)operation completionHandler:(void (^ _Nullable NONSENDABLE)(NSString *_Nullable, NSError * _Nullable)) handler;
-(void) withAliasCompletionHandler:(CompletionHandler)handler;
@end

// Placement of SWIFT_SENDABLE matters here
void doSomethingConcurrently(__attribute__((noescape)) void SWIFT_SENDABLE (^block)(void));

@interface TestWithSendableID<T: SWIFT_SENDABLE id> : NSObject
-(void) add: (T) object;
@end

@interface TestWithSendableSuperclass<T: MyValue *SWIFT_SENDABLE> : NSObject
-(void) add: (T) object;
@end

@protocol InnerSendableTypes
-(void) test:(NSDictionary<NSString *, SWIFT_SENDABLE id> *)options;
-(void) testWithCallback:(NSString *)name handler:(MAIN_ACTOR void (^)(NSDictionary<NSString *, SWIFT_SENDABLE id> *, NSError * _Nullable))handler;
@end

#pragma clang assume_nonnull end

//--- main.swift

func test_sendable_attr_in_type_context(test: Test) {
let fn: (String?, (any Error)?) -> Void = { _,_ in }

test.withNonSendable("", completionHandler: fn) // Ok

test.makeRequest("id") {
doSomethingConcurrently($0) // Ok
}

test.makeRequest("id") { callback in
_ = { @Sendable in
callback() // Ok
}
}

test.withSendableId { id in
_ = { @Sendable in
print(id) // Ok
}
}

test.withSendableCustom { val in
_ = { @Sendable in
print(val!)
}
}

let _: (@escaping @Sendable (@escaping @Sendable () -> Void) -> Void) -> Void = test.withAliasCompletionHandler

test.withAliasCompletionHandler { callback in
doSomethingConcurrently(callback) // Ok
}

_ = TestWithSendableID<SendableValue>() // Ok

// TOOD(diagnostics): Duplicate diagnostics
TestWithSendableID().add(MyValue())
// expected-error@-1 3 {{type 'MyValue' does not conform to the 'Sendable' protocol}}

TestWithSendableSuperclass().add(SendableMyValue()) // Ok

// TOOD(diagnostics): Duplicate diagnostics
TestWithSendableSuperclass().add(MyValue())
// expected-error@-1 3 {{type 'MyValue' does not conform to the 'Sendable' protocol}}
}

class TestConformanceWithStripping : InnerSendableTypes {
// expected-error@-1 {{type 'TestConformanceWithStripping' does not conform to protocol 'InnerSendableTypes'}}

func test(_ options: [String: Any]) {
// expected-note@-1 {{candidate has non-matching type '([String : Any]) -> ()'}}
}

func test(withCallback name: String, handler: @escaping @MainActor ([String : Any], (any Error)?) -> Void) {
// expected-note@-1 {{candidate has non-matching type '(String, @escaping @MainActor ([String : Any], (any Error)?) -> Void) -> ()'}}
}
}

class TestConformanceWithoutStripping : InnerSendableTypes {
func test(_ options: [String: any Sendable]) { // Ok
}

func test(withCallback name: String, handler: @escaping @MainActor ([String : any Sendable], (any Error)?) -> Void) { // Ok
}
}