From 50035f42d83f02aae739b8baccf60268ae0f86aa Mon Sep 17 00:00:00 2001 From: Egor Zhdan Date: Thu, 13 Nov 2025 12:59:24 +0000 Subject: [PATCH] [cxx-interop] Wrap methods that take Obj-C params in `#if defined(__OBJC__)` When generating a C++ header for a Swift module, we wrap declarations that require Objective-C++ in `#if defined(__OBJC__)` blocks, to make the headers usable with Obj-C interop disabled. This logic wasn't being applied to Swift methods and initializers that take an Objective-C class. rdar://164465358 --- lib/AST/SwiftNameTranslation.cpp | 4 +++ .../Inputs/module.modulemap | 4 +++ .../ObjCToSwiftToObjCxx/Inputs/objc-types.h | 3 ++ .../bridge-objc-types-back-to-objcxx.swift | 16 +++++++--- .../objc-types-in-cxx-mode.swift | 32 +++++++++++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 test/Interop/ObjCToSwiftToObjCxx/Inputs/module.modulemap create mode 100644 test/Interop/ObjCToSwiftToObjCxx/Inputs/objc-types.h create mode 100644 test/Interop/ObjCToSwiftToObjCxx/objc-types-in-cxx-mode.swift diff --git a/lib/AST/SwiftNameTranslation.cpp b/lib/AST/SwiftNameTranslation.cpp index 92d7938da39..70eebb5b65e 100644 --- a/lib/AST/SwiftNameTranslation.cpp +++ b/lib/AST/SwiftNameTranslation.cpp @@ -266,6 +266,10 @@ bool swift::cxx_translation::isObjCxxOnly(const ValueDecl *VD) { bool swift::cxx_translation::isObjCxxOnly(const clang::Decl *D, const ASTContext &ctx) { + // Check if this is decl can only be referred to from Objective-C. + if (isa(D)) + return true; + // By default, we import all modules in Obj-C++ mode, so there is no robust // way to tell if something is coming from an Obj-C module. Use the // requirements and the language options to check if we should actually diff --git a/test/Interop/ObjCToSwiftToObjCxx/Inputs/module.modulemap b/test/Interop/ObjCToSwiftToObjCxx/Inputs/module.modulemap new file mode 100644 index 00000000000..0fb810c7ce7 --- /dev/null +++ b/test/Interop/ObjCToSwiftToObjCxx/Inputs/module.modulemap @@ -0,0 +1,4 @@ +module ObjCTypes { + header "objc-types.h" + export * +} diff --git a/test/Interop/ObjCToSwiftToObjCxx/Inputs/objc-types.h b/test/Interop/ObjCToSwiftToObjCxx/Inputs/objc-types.h new file mode 100644 index 00000000000..a64e88ea26f --- /dev/null +++ b/test/Interop/ObjCToSwiftToObjCxx/Inputs/objc-types.h @@ -0,0 +1,3 @@ +@interface ObjCKlass +-(ObjCKlass * _Nonnull) init; +@end diff --git a/test/Interop/ObjCToSwiftToObjCxx/bridge-objc-types-back-to-objcxx.swift b/test/Interop/ObjCToSwiftToObjCxx/bridge-objc-types-back-to-objcxx.swift index 934553a28eb..05341fb9852 100644 --- a/test/Interop/ObjCToSwiftToObjCxx/bridge-objc-types-back-to-objcxx.swift +++ b/test/Interop/ObjCToSwiftToObjCxx/bridge-objc-types-back-to-objcxx.swift @@ -105,10 +105,19 @@ public class KVOCookieMonster { // CHECK-NEXT: SWIFT_EXTERN id _Nullable $s9UseObjCTy03retB17CProtocolNullableSo0bE0_pSgyF(void) SWIFT_NOEXCEPT SWIFT_CALL; // retObjCProtocolNullable() // CHECK-NEXT: #endif // CHECK: ObjCKlass *_Nonnull $s9UseObjCTy03retB5ClassSo0B6CKlassCyF(void) SWIFT_NOEXCEPT SWIFT_CALL; +// CHECK-NEXT: #endif // defined(__OBJC__) +// CHECK-NEXT: #if defined(__OBJC__) // CHECK-NEXT: ObjCKlass *_Nullable $s9UseObjCTy03retB13ClassNullableSo0B6CKlassCSgyF(void) SWIFT_NOEXCEPT SWIFT_CALL; +// CHECK-NEXT: #endif // defined(__OBJC__) +// CHECK-NEXT: #if defined(__OBJC__) // CHECK-NEXT: void $s9UseObjCTy04takeB6CClassyySo0B6CKlassCF(ObjCKlass *_Nonnull x) SWIFT_NOEXCEPT SWIFT_CALL; +// CHECK-NEXT: #endif // defined(__OBJC__) +// CHECK-NEXT: #if defined(__OBJC__) // CHECK-NEXT: void $s9UseObjCTy04takeB11CClassInoutyySo0B6CKlassCzF(ObjCKlass *_Nonnull __strong * _Nonnull x) SWIFT_NOEXCEPT SWIFT_CALL; +// CHECK-NEXT: #endif // defined(__OBJC__) +// CHECK-NEXT: #if defined(__OBJC__) // CHECK-NEXT: void $s9UseObjCTy04takeB14CClassNullableyySo0B6CKlassCSgF(ObjCKlass *_Nullable x) SWIFT_NOEXCEPT SWIFT_CALL; +// CHECK-NEXT: #endif // defined(__OBJC__) // CHECK-NEXT: #if defined(__OBJC__) // CHECK-NEXT: SWIFT_EXTERN void $s9UseObjCTy04takeB9CProtocolyySo0bE0_pF(id _Nonnull x) SWIFT_NOEXCEPT SWIFT_CALL; // takeObjCProtocol(_:) // CHECK-NEXT: #endif @@ -124,17 +133,16 @@ public class KVOCookieMonster { // CHECK-NEXT: } // CHECK-NEXT: }; -// CHECK: #if defined(__OBJC__) -// CHECK-NEXT: SWIFT_INLINE_THUNK id _Nonnull retObjCProtocol() noexcept SWIFT_SYMBOL("s:9UseObjCTy03retB9CProtocolSo0bE0_pyF") SWIFT_WARN_UNUSED_RESULT { +// CHECK: SWIFT_INLINE_THUNK id _Nonnull retObjCProtocol() noexcept SWIFT_SYMBOL("s:9UseObjCTy03retB9CProtocolSo0bE0_pyF") SWIFT_WARN_UNUSED_RESULT { // CHECK-NEXT: return (__bridge_transfer id )(__bridge void *)UseObjCTy::_impl::$s9UseObjCTy03retB9CProtocolSo0bE0_pyF(); // CHECK-NEXT: } -// CHECK-NEXT: #endif +// CHECK-NEXT: #endif // defined(__OBJC__) // CHECK: #if defined(__OBJC__) // CHECK-NEXT: SWIFT_INLINE_THUNK id _Nullable retObjCProtocolNullable() noexcept SWIFT_SYMBOL("s:9UseObjCTy03retB17CProtocolNullableSo0bE0_pSgyF") SWIFT_WARN_UNUSED_RESULT { // CHECK-NEXT: return (__bridge_transfer id )(__bridge void *)UseObjCTy::_impl::$s9UseObjCTy03retB17CProtocolNullableSo0bE0_pSgyF(); // CHECK-NEXT: } -// CHECK-NEXT: #endif +// CHECK-NEXT: #endif // defined(__OBJC__) // CHECK: SWIFT_INLINE_THUNK ObjCKlass *_Nonnull retObjClass() noexcept SWIFT_SYMBOL({{.*}}) SWIFT_WARN_UNUSED_RESULT { // CHECK-NEXT: return (__bridge_transfer ObjCKlass *)(__bridge void *)UseObjCTy::_impl::$s9UseObjCTy03retB5ClassSo0B6CKlassCyF(); diff --git a/test/Interop/ObjCToSwiftToObjCxx/objc-types-in-cxx-mode.swift b/test/Interop/ObjCToSwiftToObjCxx/objc-types-in-cxx-mode.swift new file mode 100644 index 00000000000..344bd62e225 --- /dev/null +++ b/test/Interop/ObjCToSwiftToObjCxx/objc-types-in-cxx-mode.swift @@ -0,0 +1,32 @@ +// RUN: %target-swift-frontend %s -module-name ObjCInCXX -typecheck -verify -I %S/Inputs -emit-clang-header-path %t/ObjCInCXX.h -I %t -cxx-interoperability-mode=default +// RUN: %FileCheck %s < %t/ObjCInCXX.h + +// Make sure the header is valid when parsed in C++ language mode, without Obj-C. +// RUN: %target-interop-build-clang -fobjc-arc -c -x c++-header %t/ObjCInCXX.h -o %t/o.o + +// REQUIRES: objc_interop + +import ObjCTypes + +public struct HasObjCInit { + private let privateField: Int = 0 + + public init(_ o: ObjCKlass) {} +} + +public struct HasObjCMethod { + private let privateField: Int = 0 + + public func takesObjCKlass(_ o: ObjCKlass) {} +} + +// CHECK: SWIFT_INLINE_THUNK HasObjCInit HasObjCInit::init(ObjCKlass *_Nonnull o) { +// CHECK: return {{.*}} { +// CHECK: } +// CHECK-NEXT: } +// CHECK-NEXT: #endif // defined(__OBJC__) + +// CHECK: SWIFT_INLINE_THUNK void HasObjCMethod::takesObjCKlass(ObjCKlass *_Nonnull o) const { +// CHECK-NEXT: ObjCInCXX::{{.*}} +// CHECK-NEXT: } +// CHECK-NEXT: #endif // defined(__OBJC__)