diff --git a/lib/PrintAsClang/PrintAsClang.cpp b/lib/PrintAsClang/PrintAsClang.cpp index 9ae35dd83a35f..e62087e33a4d3 100644 --- a/lib/PrintAsClang/PrintAsClang.cpp +++ b/lib/PrintAsClang/PrintAsClang.cpp @@ -409,7 +409,10 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl &imports, const FrontendOptions &frontendOpts, clang::HeaderSearch &clangHeaderSearchInfo, const llvm::StringMap &exposedModuleHeaderNames, - bool useCxxImport = false) { + bool useCxxImport = false, + bool useNonModularIncludes = false) { + useNonModularIncludes |= frontendOpts.EmitClangHeaderWithNonModularIncludes; + // Note: we can't use has_feature(modules) as it's always enabled in C++20 // mode. out << "#if __has_feature(objc_modules)\n"; @@ -446,7 +449,7 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl &imports, llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem(); llvm::ErrorOr cwd = fileSystem.getCurrentWorkingDirectory(); - if (frontendOpts.EmitClangHeaderWithNonModularIncludes) { + if (useNonModularIncludes) { assert(cwd && "Access to current working directory required"); for (auto searchDir = clangHeaderSearchInfo.search_dir_begin(); @@ -498,7 +501,7 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl &imports, } if (seenImports.insert(Name).second) { out << importDirective << ' ' << Name.str() << importDirectiveLineEnd; - if (frontendOpts.EmitClangHeaderWithNonModularIncludes) { + if (useNonModularIncludes) { if (const clang::Module *underlyingClangModule = swiftModule->findUnderlyingClangModule()) { collectClangModuleHeaderIncludes( @@ -521,7 +524,7 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl &imports, out << importDirective << ' '; ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out); out << importDirectiveLineEnd; - if (frontendOpts.EmitClangHeaderWithNonModularIncludes) { + if (useNonModularIncludes) { collectClangModuleHeaderIncludes( clangModule, fileManager, requiredTextualIncludes, visitedModules, includeDirs, cwd.get()); @@ -529,11 +532,13 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl &imports, } } - if (frontendOpts.EmitClangHeaderWithNonModularIncludes) { - out << "#else\n"; - for (auto header : requiredTextualIncludes) { + if (useNonModularIncludes && !requiredTextualIncludes.empty()) { + out << "#elif defined(__OBJC__)\n"; + for (auto header : requiredTextualIncludes) out << "#import <" << header << ">\n"; - } + out << "#else\n"; + for (auto header : requiredTextualIncludes) + out << "#include <" << header << ">\n"; } out << "#endif\n\n"; for (const auto header : textualIncludes) { @@ -544,8 +549,13 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl &imports, if (bridgingHeader.empty()) out << "#import <" << M.getName().str() << '/' << M.getName().str() << ".h>\n\n"; - else - out << "#import \"" << bridgingHeader << "\"\n\n"; + else { + out << "#if defined(__OBJC__)\n"; + out << "#import \"" << bridgingHeader << "\"\n"; + out << "#else\n"; + out << "#include \"" << bridgingHeader << "\"\n"; + out << "#endif\n\n"; + } } } @@ -606,17 +616,24 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, writePrologue(os, M->getASTContext(), computeMacroGuard(M)); // C content (@cdecl) + std::string moduleContentsScratch; if (M->getASTContext().LangOpts.hasFeature(Feature::CDecl)) { SmallPtrSet imports; - emitExternC(os, [&] { - printModuleContentsAsC(os, imports, *M, interopContext); - }); + llvm::raw_string_ostream cModuleContents{moduleContentsScratch}; + printModuleContentsAsC(cModuleContents, imports, *M, interopContext); + + llvm::StringMap exposedModuleHeaderNames; + writeImports(os, imports, *M, bridgingHeader, frontendOpts, + clangHeaderSearchInfo, exposedModuleHeaderNames, + /*useCxxImport=*/false, /*useNonModularIncludes*/true); + + emitExternC(os, [&] { os << "\n" << cModuleContents.str(); }); + moduleContentsScratch.clear(); } // Objective-C content SmallPtrSet imports; - std::string objcModuleContentsBuf; - llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf}; + llvm::raw_string_ostream objcModuleContents{moduleContentsScratch}; printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext); emitObjCConditional(os, [&] { llvm::StringMap exposedModuleHeaderNames; diff --git a/test/PrintAsObjC/cdecl-includes-with-objc.swift b/test/PrintAsObjC/cdecl-includes-with-objc.swift new file mode 100644 index 0000000000000..317157002a091 --- /dev/null +++ b/test/PrintAsObjC/cdecl-includes-with-objc.swift @@ -0,0 +1,82 @@ +/// Include module for use from both C and Objective-C @cdecl variants. + +// RUN: %empty-directory(%t) +// RUN: split-file %s %t --leading-lines + +/// Generate the compatibility header cdecl.h +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %t/Lib.swift \ +// RUN: -emit-module -o %t -verify -F %t \ +// RUN: -emit-clang-header-path %t/cdecl.h \ +// RUN: -enable-experimental-feature CDecl + +/// Check compatibility header directly +// RUN: %FileCheck %s --input-file %t/cdecl.h +// RUN: %check-in-clang %t/cdecl.h -F %t +// RUN: %check-in-clang-c %t/cdecl.h -F %t +// RUN: %check-in-clang-cxx %t/cdecl.h -F %t + +// REQUIRES: swift_feature_CDecl +// REQUIRES: objc_interop + +//--- CFramework.framework/Modules/module.modulemap + +framework module CFramework { + umbrella header "CFramework.h" +} + +//--- CFramework.framework/Headers/CFramework.h + +typedef int IntFromCFramework; + +//--- Lib.swift + +import CFramework +import CoreGraphics +import Foundation + +// CHECK-NOT: Foundation; + +/// Imports for C variant to @_cdecl + +// CHECK: #if __has_feature(objc_modules) +// CHECK: @import CFramework; +// CHECK-NEXT: @import CoreGraphics; +// CHECK-NEXT: #elif defined(__OBJC__) +// CHECK-NEXT: #import +// CHECK-NEXT: #import +// CHECK-NEXT: #else +// CHECK-NEXT: #include +// CHECK-NEXT: #include +// CHECK-NEXT: #endif + +// CHECK: #if defined(__cplusplus) +// CHECK: extern "C" { +// CHECK: #endif + +@cdecl("get_int_alias") +public func getIntAlias() -> IntFromCFramework { 42 } +// CHECK: SWIFT_EXTERN IntFromCFramework get_int_alias(void) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT; + +@cdecl("imports_cgpoint") +public func importsCGPoint(pt: CGPoint) { } +// CHECK: SWIFT_EXTERN void imports_cgpoint(CGPoint pt) SWIFT_NOEXCEPT; + +// CHECK: #if defined(__cplusplus) +// CHECK: } // extern "C" +// CHECK: #endif + +/// Imports for Objective-C variant to @_cdecl + +@_cdecl("imports_cgpoint_objc") +public func importsCGPointObjC(pt: CGPoint) { } +// CHECK: #if defined(__OBJC__) +// CHECK: #if __has_feature(objc_modules) +// CHECK-NEXT: #if __has_warning("-Watimport-in-framework-header") +// CHECK-NEXT: #pragma clang diagnostic ignored "-Watimport-in-framework-header" +// CHECK-NEXT: #endif +// CHECK-NEXT: @import CoreGraphics; +// CHECK-NEXT: #endif + +// CHECK: #if defined(__OBJC__) +// CHECK: SWIFT_EXTERN void imports_cgpoint_objc(CGPoint pt) SWIFT_NOEXCEPT; +// CHECK: #endif diff --git a/test/PrintAsObjC/cdecl-includes.swift b/test/PrintAsObjC/cdecl-includes.swift new file mode 100644 index 0000000000000..2538a06c2f754 --- /dev/null +++ b/test/PrintAsObjC/cdecl-includes.swift @@ -0,0 +1,110 @@ +/// Print #includes for C clients and reference imported types. +/// This test shouldn't require the objc runtime. + +// RUN: %empty-directory(%t) +// RUN: split-file %s %t --leading-lines + +/// Generate the compatibility header cdecl.h +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) %t/Lib.swift \ +// RUN: -emit-module -verify -o %t -I %t \ +// RUN: -import-bridging-header %t/BridgingHeader.h \ +// RUN: -emit-clang-header-path %t/cdecl.h \ +// RUN: -disable-objc-interop \ +// RUN: -enable-experimental-feature CDecl + +/// Check compatibility header directly +// RUN: %FileCheck %s --input-file %t/cdecl.h +// RUN: %check-in-clang-c -I %t %t/cdecl.h \ +// RUN: -isysroot %S/../Inputs/clang-importer-sdk + +/// Compile a client against the compatibility header +// RUN: %clang-no-modules -c %t/Client.c -I %t -Werror \ +// RUN: -isysroot %S/../Inputs/clang-importer-sdk + +// REQUIRES: swift_feature_CDecl + +//--- module.modulemap + +module CModule { + header "CModule_FileA.h" + header "sub/CModule_FileB.h" +} + +//--- CModule_FileA.h + +struct CStruct { int a; }; + +//--- sub/CModule_FileB.h + +union CUnion { long a; float b; }; + +//--- Dependency.h + +typedef enum TKTimeSetting { + TKTimeSettingLight, + TKTimeSettingNormal, + TKTimeSettingDark +} TKTimeSetting; + +//--- BridgingHeader.h + +#include "Dependency.h" + +//--- Lib.swift + +import CModule + +// CHECK: #if __has_feature(objc_modules) +// CHECK: @import CModule; +// CHECK-NEXT: #elif defined(__OBJC__) +// CHECK-NEXT: #import +// CHECK-NEXT: #import +// CHECK-NEXT: #else +// CHECK-NEXT: #include +// CHECK-NEXT: #include +// CHECK-NEXT: #endif + +// CHECK: #if defined(__OBJC__) +// CHECK: #import " +// CHECK-SAME: BridgingHeader.h" +// CHECK-NEXT: #else +// CHECK-NEXT: #include " +// CHECK-SAME: BridgingHeader.h" +// CHECK-NEXT: #endif + +// CHECK-NOT: BridgingHeader + +// CHECK: #if defined(__cplusplus) +// CHECK: extern "C" { +// CHECK: #endif + +@cdecl("mirror_struct") +public func a_mirrorStruct(_ a: CStruct) -> CStruct { a } +// CHECK: SWIFT_EXTERN struct CStruct mirror_struct(struct CStruct a) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT; + +@cdecl("mirror_union") +public func b_mirrorStruct(_ a: CUnion) -> CUnion { a } +// CHECK: SWIFT_EXTERN union CUnion mirror_union(union CUnion a) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT; + + +@cdecl("TKGetDefaultToastSetting") +public func c_defaultToastSetting() -> TKTimeSetting { TKTimeSettingNormal } // It would be nice to import TKTimeSettingNormal as a member. +// CHECK: SWIFT_EXTERN TKTimeSetting TKGetDefaultToastSetting(void) SWIFT_NOEXCEPT SWIFT_WARN_UNUSED_RESULT; + +// CHECK: #if defined(__cplusplus) +// CHECK-NEXT: } +// CHECK-NEXT: #endif + +//--- Client.c + +#include "cdecl.h" + +int main() { + struct CStruct s = { 42 }; + struct CStruct s_out = mirror_struct(s); + + union CUnion u = { 43 }; + union CUnion u_out = mirror_union(u); + + TKTimeSetting def = TKGetDefaultToastSetting(); +} diff --git a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-mock-sdk.swift b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-mock-sdk.swift index fa2fe7cef598a..d053c463c5488 100644 --- a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-mock-sdk.swift +++ b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-mock-sdk.swift @@ -4,6 +4,7 @@ // RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -F %S/Inputs/ -typecheck -verify -emit-objc-header-path %t/textual-imports.h -emit-clang-header-nonmodular-includes %s // RUN: %FileCheck %s < %t/textual-imports.h // RUN: %check-in-clang -fno-modules -Qunused-arguments %t/textual-imports.h -F %S/Inputs +// RUN: %check-in-clang-c %t/textual-imports.h -F %S/Inputs import Foundation import Mixed @@ -30,9 +31,14 @@ public class HelloWorld: NSObject { // CHECK-NEXT: @import CoreGraphics; // CHECK-NEXT: @import Mixed; // CHECK-NEXT: @import ObjectiveC; -// CHECK-NEXT: #else +// CHECK-NEXT: #elif defined(__OBJC__) // CHECK-NEXT: #import // CHECK-NEXT: #import // CHECK-NEXT: #import // CHECK-NEXT: #import +// CHECK-NEXT: #else +// CHECK-NEXT: #include +// CHECK-NEXT: #include +// CHECK-NEXT: #include +// CHECK-NEXT: #include // CHECK-NEXT: #endif diff --git a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-modulemap-not-in-include-dir.swift b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-modulemap-not-in-include-dir.swift index 5c073aba1e779..d2e5454b787df 100644 --- a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-modulemap-not-in-include-dir.swift +++ b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-modulemap-not-in-include-dir.swift @@ -16,6 +16,8 @@ public class Bar : Baz {} // CHECK-NEXT: #pragma clang diagnostic ignored "-Watimport-in-framework-header" // CHECK-NEXT: #endif // CHECK-NEXT: @import EmitClangHeaderNonmodularIncludesStressTest; -// CHECK-NEXT: #else -// CHECK: #import +// CHECK-NEXT: #elif defined(__OBJC__) +// CHECK-NEXT: #import +// CHECK: #else +// CHECK-NEXT: #include // CHECK: #endif diff --git a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-path-normalization.swift b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-path-normalization.swift index abd61424ed432..3e4ca18b47648 100644 --- a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-path-normalization.swift +++ b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-path-normalization.swift @@ -14,6 +14,8 @@ public class Bar : Baz {} // CHECK-NEXT: #pragma clang diagnostic ignored "-Watimport-in-framework-header" // CHECK-NEXT: #endif // CHECK-NEXT: @import EmitClangHeaderNonmodularIncludesStressTest; -// CHECK-NEXT: #else -// CHECK: #import +// CHECK-NEXT: #elif defined(__OBJC__) +// CHECK-NEXT: #import +// CHECK: #else +// CHECK-NEXT: #include // CHECK: #endif diff --git a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-symlinked-header.swift b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-symlinked-header.swift index 9e4547a83c430..4ac059c77112c 100644 --- a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-symlinked-header.swift +++ b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-symlinked-header.swift @@ -16,6 +16,8 @@ public class Bar : Foo {} // CHECK-NEXT: #pragma clang diagnostic ignored "-Watimport-in-framework-header" // CHECK-NEXT: #endif // CHECK-NEXT: @import EmitClangHeaderNonmodularIncludesStressTest; -// CHECK-NEXT: #else +// CHECK-NEXT: #elif defined(__OBJC__) // CHECK: #import +// CHECK-NEXT: #else +// CHECK: #include // CHECK: #endif