Skip to content

PrintAsClang: Print #include to import headers used from @cdecl functions #81481

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
47 changes: 32 additions & 15 deletions lib/PrintAsClang/PrintAsClang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,10 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
const FrontendOptions &frontendOpts,
clang::HeaderSearch &clangHeaderSearchInfo,
const llvm::StringMap<StringRef> &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";
Expand Down Expand Up @@ -446,7 +449,7 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem();
llvm::ErrorOr<std::string> cwd = fileSystem.getCurrentWorkingDirectory();

if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
if (useNonModularIncludes) {
assert(cwd && "Access to current working directory required");

for (auto searchDir = clangHeaderSearchInfo.search_dir_begin();
Expand Down Expand Up @@ -498,7 +501,7 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
}
if (seenImports.insert(Name).second) {
out << importDirective << ' ' << Name.str() << importDirectiveLineEnd;
if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
if (useNonModularIncludes) {
if (const clang::Module *underlyingClangModule =
swiftModule->findUnderlyingClangModule()) {
collectClangModuleHeaderIncludes(
Expand All @@ -521,19 +524,21 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl<ImportModuleTy> &imports,
out << importDirective << ' ';
ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out);
out << importDirectiveLineEnd;
if (frontendOpts.EmitClangHeaderWithNonModularIncludes) {
if (useNonModularIncludes) {
collectClangModuleHeaderIncludes(
clangModule, fileManager, requiredTextualIncludes, visitedModules,
includeDirs, cwd.get());
}
}
}

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) {
Expand All @@ -544,8 +549,13 @@ writeImports(raw_ostream &out, llvm::SmallPtrSetImpl<ImportModuleTy> &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";
}
}
}

Expand Down Expand Up @@ -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<ImportModuleTy, 8> imports;
emitExternC(os, [&] {
printModuleContentsAsC(os, imports, *M, interopContext);
});
llvm::raw_string_ostream cModuleContents{moduleContentsScratch};
printModuleContentsAsC(cModuleContents, imports, *M, interopContext);

llvm::StringMap<StringRef> 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<ImportModuleTy, 8> 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<StringRef> exposedModuleHeaderNames;
Expand Down
82 changes: 82 additions & 0 deletions test/PrintAsObjC/cdecl-includes-with-objc.swift
Original file line number Diff line number Diff line change
@@ -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 <CFramework{{[/\\]}}CFramework.h>
// CHECK-NEXT: #import <CoreGraphics.h>
// CHECK-NEXT: #else
// CHECK-NEXT: #include <CFramework{{[/\\]}}CFramework.h>
// CHECK-NEXT: #include <CoreGraphics.h>
// 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
110 changes: 110 additions & 0 deletions test/PrintAsObjC/cdecl-includes.swift
Original file line number Diff line number Diff line change
@@ -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 <CModule_FileA.h>
// CHECK-NEXT: #import <sub{{[/\\]}}CModule_FileB.h>
// CHECK-NEXT: #else
// CHECK-NEXT: #include <CModule_FileA.h>
// CHECK-NEXT: #include <sub{{[/\\]}}CModule_FileB.h>
// 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();
}
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) -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
Expand All @@ -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 <CoreGraphics.h>
// CHECK-NEXT: #import <Mixed/Mixed.h>
// CHECK-NEXT: #import <objc/objc.h>
// CHECK-NEXT: #import <objc/NSObject.h>
// CHECK-NEXT: #else
// CHECK-NEXT: #include <CoreGraphics.h>
// CHECK-NEXT: #include <Mixed/Mixed.h>
// CHECK-NEXT: #include <objc/objc.h>
// CHECK-NEXT: #include <objc/NSObject.h>
// CHECK-NEXT: #endif
Original file line number Diff line number Diff line change
Expand Up @@ -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 <header-regular.h>
// CHECK-NEXT: #elif defined(__OBJC__)
// CHECK-NEXT: #import <header-regular.h>
// CHECK: #else
// CHECK-NEXT: #include <header-regular.h>
// CHECK: #endif
Original file line number Diff line number Diff line change
Expand Up @@ -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 <header-regular.h>
// CHECK-NEXT: #elif defined(__OBJC__)
// CHECK-NEXT: #import <header-regular.h>
// CHECK: #else
// CHECK-NEXT: #include <header-regular.h>
// CHECK: #endif
Original file line number Diff line number Diff line change
Expand Up @@ -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 <header-symlink.h>
// CHECK-NEXT: #else
// CHECK: #include <header-symlink.h>
// CHECK: #endif