diff --git a/include/swift/Frontend/FrontendOptions.h b/include/swift/Frontend/FrontendOptions.h index 297f9ba20a94e..df60c6fee22c6 100644 --- a/include/swift/Frontend/FrontendOptions.h +++ b/include/swift/Frontend/FrontendOptions.h @@ -485,6 +485,10 @@ class FrontendOptions { /// crashes the compiler. bool emptyABIDescriptor = false; + /// Augment modular imports in any emitted ObjC headers with equivalent + /// textual imports + bool EmitClangHeaderWithNonModularIncludes = false; + private: static bool canActionEmitDependencies(ActionType); static bool canActionEmitReferenceDependencies(ActionType); diff --git a/include/swift/Option/Options.td b/include/swift/Option/Options.td index 0262d92d51fb4..ffc9df7f07526 100644 --- a/include/swift/Option/Options.td +++ b/include/swift/Option/Options.td @@ -566,6 +566,9 @@ def emit_objc_header_path : Separate<["-"], "emit-objc-header-path">, Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath, SupplementaryOutput]>, MetaVarName<"">, HelpText<"Emit an Objective-C header file to ">; +def emit_clang_header_nonmodular_includes : Flag<["-"], "emit-clang-header-nonmodular-includes">, + Flags<[FrontendOption, NoInteractiveOption, SupplementaryOutput]>, + HelpText<"Augment emitted Objective-C header with textual imports for every included modular import">; def emit_clang_header_path : Separate<["-"], "emit-clang-header-path">, Flags<[FrontendOption, NoDriverOption, NoInteractiveOption, ArgumentIsPath, diff --git a/include/swift/PrintAsClang/PrintAsClang.h b/include/swift/PrintAsClang/PrintAsClang.h index 74de1f5bc5cac..131c20f31fe22 100644 --- a/include/swift/PrintAsClang/PrintAsClang.h +++ b/include/swift/PrintAsClang/PrintAsClang.h @@ -17,6 +17,10 @@ #include "swift/AST/AttrKind.h" #include "swift/AST/Identifier.h" +namespace clang { +class HeaderSearch; +} + namespace swift { class FrontendOptions; class IRGenOptions; @@ -36,7 +40,8 @@ class ValueDecl; bool printAsClangHeader(raw_ostream &out, ModuleDecl *M, StringRef bridgingHeader, const FrontendOptions &frontendOpts, - const IRGenOptions &irGenOpts); + const IRGenOptions &irGenOpts, + clang::HeaderSearch &headerSearchInfo); } #endif diff --git a/lib/Frontend/ArgsToFrontendOptionsConverter.cpp b/lib/Frontend/ArgsToFrontendOptionsConverter.cpp index 4a12bd9ba83f4..a9354dc84fedf 100644 --- a/lib/Frontend/ArgsToFrontendOptionsConverter.cpp +++ b/lib/Frontend/ArgsToFrontendOptionsConverter.cpp @@ -85,6 +85,9 @@ bool ArgsToFrontendOptionsConverter::convert( Opts.FrontendParseableOutput |= Args.hasArg(OPT_frontend_parseable_output); Opts.ExplicitInterfaceBuild |= Args.hasArg(OPT_explicit_interface_module_build); + Opts.EmitClangHeaderWithNonModularIncludes |= + Args.hasArg(OPT_emit_clang_header_nonmodular_includes); + // FIXME: Remove this flag Opts.EnableLibraryEvolution |= Args.hasArg(OPT_enable_resilience); diff --git a/lib/FrontendTool/FrontendTool.cpp b/lib/FrontendTool/FrontendTool.cpp index c73f879e227cb..b2df1192d3dbd 100644 --- a/lib/FrontendTool/FrontendTool.cpp +++ b/lib/FrontendTool/FrontendTool.cpp @@ -69,6 +69,8 @@ #include "swift/Subsystems.h" #include "swift/SymbolGraphGen/SymbolGraphOptions.h" +#include "clang/Lex/Preprocessor.h" + #include "llvm/ADT/Statistic.h" #include "llvm/ADT/StringMap.h" #include "llvm/IR/LLVMContext.h" @@ -164,17 +166,17 @@ static bool writeSIL(SILModule &SM, const PrimarySpecificPaths &PSPs, /// \returns true if there were any errors /// /// \see swift::printAsClangHeader -static bool printAsClangHeaderIfNeeded(StringRef outputPath, ModuleDecl *M, - StringRef bridgingHeader, - const FrontendOptions &frontendOpts, - const IRGenOptions &irGenOpts) { +static bool printAsClangHeaderIfNeeded( + StringRef outputPath, ModuleDecl *M, StringRef bridgingHeader, + const FrontendOptions &frontendOpts, const IRGenOptions &irGenOpts, + clang::HeaderSearch &clangHeaderSearchInfo) { if (outputPath.empty()) return false; - return withOutputFile(M->getDiags(), outputPath, - [&](raw_ostream &out) -> bool { - return printAsClangHeader(out, M, bridgingHeader, - frontendOpts, irGenOpts); - }); + return withOutputFile( + M->getDiags(), outputPath, [&](raw_ostream &out) -> bool { + return printAsClangHeader(out, M, bridgingHeader, frontendOpts, + irGenOpts, clangHeaderSearchInfo); + }); } /// Prints the stable module interface for \p M to \p outputPath. @@ -925,7 +927,10 @@ static bool emitAnyWholeModulePostTypeCheckSupplementaryOutputs( hadAnyError |= printAsClangHeaderIfNeeded( Invocation.getClangHeaderOutputPathForAtMostOnePrimary(), Instance.getMainModule(), BridgingHeaderPathForPrint, opts, - Invocation.getIRGenOptions()); + Invocation.getIRGenOptions(), + Context.getClangModuleLoader() + ->getClangPreprocessor() + .getHeaderSearchInfo()); } // Only want the header if there's been any errors, ie. there's not much diff --git a/lib/PrintAsClang/PrintAsClang.cpp b/lib/PrintAsClang/PrintAsClang.cpp index 43a247038b97b..448c8047567c6 100644 --- a/lib/PrintAsClang/PrintAsClang.cpp +++ b/lib/PrintAsClang/PrintAsClang.cpp @@ -23,8 +23,12 @@ #include "swift/ClangImporter/ClangImporter.h" #include "swift/Frontend/FrontendOptions.h" +#include "clang/Basic/FileManager.h" #include "clang/Basic/Module.h" +#include "clang/Lex/HeaderSearch.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" using namespace swift; @@ -386,9 +390,133 @@ static int compareImportModulesByName(const ImportModuleTy *left, return 1; } +// Makes the provided path absolute and removes any "." or ".." segments from +// the path +static llvm::SmallString<128> normalizePath(const llvm::StringRef path) { + llvm::SmallString<128> result = path; + llvm::sys::path::remove_dots(result, /* remove_dot_dot */ true); + llvm::sys::fs::make_absolute(result); + return result; +} + +// Collect the set of header includes needed to import the given Clang module +// into an ObjectiveC program. Modeled after collectModuleHeaderIncludes in the +// Clang frontend (FrontendAction.cpp) +// Augment requiredTextualIncludes with the set of headers required. +static void collectClangModuleHeaderIncludes( + const clang::Module *clangModule, clang::FileManager &fileManager, + llvm::SmallSet, 10> &requiredTextualIncludes, + llvm::SmallSet &visitedModules, + const llvm::SmallSet, 10> &includeDirs, + const llvm::StringRef cwd) { + + if (!visitedModules.insert(clangModule).second) + return; + + auto addHeader = [&](llvm::StringRef headerPath, + llvm::StringRef pathRelativeToRootModuleDir) { + if (!clangModule->Directory) + return; + + llvm::SmallString<128> textualInclude = normalizePath(headerPath); + llvm::SmallString<128> containingSearchDirPath; + + for (auto &includeDir : includeDirs) { + if (textualInclude.startswith(includeDir)) { + if (includeDir.size() > containingSearchDirPath.size()) { + containingSearchDirPath = includeDir; + } + } + } + + if (!containingSearchDirPath.empty()) { + llvm::SmallString<128> prefixToRemove = + llvm::formatv("{0}/", containingSearchDirPath); + llvm::sys::path::replace_path_prefix(textualInclude, prefixToRemove, ""); + } else { + // If we cannot find find the module map on the search path, + // fallback to including the header using the provided path relative + // to the module map + textualInclude = pathRelativeToRootModuleDir; + } + + if (clangModule->getTopLevelModule()->IsFramework) { + llvm::SmallString<32> frameworkName = + clangModule->getTopLevelModuleName(); + llvm::SmallString<64> oldFrameworkPrefix = + llvm::formatv("{0}.framework/Headers", frameworkName); + llvm::sys::path::replace_path_prefix(textualInclude, oldFrameworkPrefix, + frameworkName); + } + + requiredTextualIncludes.insert(textualInclude); + }; + + if (clang::Module::Header umbrellaHeader = clangModule->getUmbrellaHeader()) { + addHeader(umbrellaHeader.Entry->tryGetRealPathName(), + umbrellaHeader.PathRelativeToRootModuleDirectory); + } else if (clang::Module::DirectoryName umbrellaDir = + clangModule->getUmbrellaDir()) { + SmallString<128> nativeUmbrellaDirPath; + std::error_code errorCode; + llvm::sys::path::native(umbrellaDir.Entry->getName(), + nativeUmbrellaDirPath); + llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem(); + for (llvm::vfs::recursive_directory_iterator + dir(fileSystem, nativeUmbrellaDirPath, errorCode), + end; + dir != end && !errorCode; dir.increment(errorCode)) { + + if (llvm::StringSwitch(llvm::sys::path::extension(dir->path())) + .Cases(".h", ".H", ".hh", ".hpp", true) + .Default(false)) { + + // Compute path to the header relative to the root of the module + // (location of the module map) First compute the relative path from + // umbrella directory to header file + SmallVector pathComponents; + auto pathIt = llvm::sys::path::rbegin(dir->path()); + + for (int i = 0; i != dir.level() + 1; ++i, ++pathIt) + pathComponents.push_back(*pathIt); + // Then append this to the path from module root to umbrella dir + SmallString<128> relativeHeaderPath; + if (umbrellaDir.PathRelativeToRootModuleDirectory != ".") + relativeHeaderPath += umbrellaDir.PathRelativeToRootModuleDirectory; + + for (auto it = pathComponents.rbegin(), end = pathComponents.rend(); + it != end; ++it) { + llvm::sys::path::append(relativeHeaderPath, *it); + } + + addHeader(dir->path(), relativeHeaderPath); + } + } + } else { + for (clang::Module::HeaderKind headerKind : + {clang::Module::HK_Normal, clang::Module::HK_Textual}) { + for (const clang::Module::Header &header : + clangModule->Headers[headerKind]) { + addHeader(header.Entry->tryGetRealPathName(), + header.PathRelativeToRootModuleDirectory); + } + } + for (auto submodule : clangModule->submodules()) { + if (submodule->IsExplicit) + continue; + + collectClangModuleHeaderIncludes(submodule, fileManager, + requiredTextualIncludes, visitedModules, + includeDirs, cwd); + } + } +} + static void writeImports(raw_ostream &out, llvm::SmallPtrSetImpl &imports, ModuleDecl &M, StringRef bridgingHeader, + const FrontendOptions &frontendOpts, + clang::HeaderSearch &clangHeaderSearchInfo, bool useCxxImport = false) { // Note: we can't use has_feature(modules) as it's always enabled in C++20 // mode. @@ -413,6 +541,45 @@ static void writeImports(raw_ostream &out, return import == importer->getImportedHeaderModule(); }; + clang::FileSystemOptions fileSystemOptions; + clang::FileManager fileManager{fileSystemOptions}; + + llvm::SmallSet, 10> requiredTextualIncludes; + llvm::SmallSet visitedModules; + llvm::SmallSet, 10> includeDirs; + + llvm::vfs::FileSystem &fileSystem = fileManager.getVirtualFileSystem(); + llvm::ErrorOr cwd = fileSystem.getCurrentWorkingDirectory(); + + if (frontendOpts.EmitClangHeaderWithNonModularIncludes) { + assert(cwd && "Access to current working directory required"); + + for (auto searchDir = clangHeaderSearchInfo.search_dir_begin(); + searchDir != clangHeaderSearchInfo.search_dir_end(); ++searchDir) { + includeDirs.insert(normalizePath(searchDir->getName())); + } + + const clang::Module *foundationModule = clangHeaderSearchInfo.lookupModule( + "Foundation", clang::SourceLocation(), false, false); + const clang::Module *darwinModule = clangHeaderSearchInfo.lookupModule( + "Darwin", clang::SourceLocation(), false, false); + + std::function + collectTransitiveSubmoduleClosure; + collectTransitiveSubmoduleClosure = [&](const clang::Module *module) { + if (!module) + return; + + visitedModules.insert(module); + for (auto submodule : module->submodules()) { + collectTransitiveSubmoduleClosure(submodule); + } + }; + + collectTransitiveSubmoduleClosure(foundationModule); + collectTransitiveSubmoduleClosure(darwinModule); + } + // Track printed names to handle overlay modules. llvm::SmallPtrSet seenImports; bool includeUnderlying = false; @@ -425,8 +592,24 @@ static void writeImports(raw_ostream &out, includeUnderlying = true; continue; } - if (seenImports.insert(Name).second) + if (seenImports.insert(Name).second) { out << importDirective << ' ' << Name.str() << ";\n"; + if (frontendOpts.EmitClangHeaderWithNonModularIncludes) { + if (const clang::Module *underlyingClangModule = + swiftModule->findUnderlyingClangModule()) { + collectClangModuleHeaderIncludes( + underlyingClangModule, fileManager, requiredTextualIncludes, + visitedModules, includeDirs, cwd.get()); + } else if ((underlyingClangModule = + clangHeaderSearchInfo.lookupModule( + Name.str(), clang::SourceLocation(), true, + true))) { + collectClangModuleHeaderIncludes( + underlyingClangModule, fileManager, requiredTextualIncludes, + visitedModules, includeDirs, cwd.get()); + } + } + } } else { const auto *clangModule = import.get(); assert(clangModule->isSubModule() && @@ -434,9 +617,21 @@ static void writeImports(raw_ostream &out, out << importDirective << ' '; ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out); out << ";\n"; + + if (frontendOpts.EmitClangHeaderWithNonModularIncludes) { + collectClangModuleHeaderIncludes( + clangModule, fileManager, requiredTextualIncludes, visitedModules, + includeDirs, cwd.get()); + } } } + if (frontendOpts.EmitClangHeaderWithNonModularIncludes) { + out << "#else\n"; + for (auto header : requiredTextualIncludes) { + out << "#import <" << header << ">\n"; + } + } out << "#endif\n\n"; if (includeUnderlying) { @@ -490,7 +685,8 @@ static std::string computeMacroGuard(const ModuleDecl *M) { bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, StringRef bridgingHeader, const FrontendOptions &frontendOpts, - const IRGenOptions &irGenOpts) { + const IRGenOptions &irGenOpts, + clang::HeaderSearch &clangHeaderSearchInfo) { llvm::PrettyStackTraceString trace("While generating Clang header"); SwiftToClangInteropContext interopContext(*M, irGenOpts); @@ -500,8 +696,10 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, llvm::raw_string_ostream objcModuleContents{objcModuleContentsBuf}; printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext); writePrologue(os, M->getASTContext(), computeMacroGuard(M)); - emitObjCConditional(os, - [&] { writeImports(os, imports, *M, bridgingHeader); }); + emitObjCConditional(os, [&] { + writeImports(os, imports, *M, bridgingHeader, frontendOpts, + clangHeaderSearchInfo); + }); writePostImportPrologue(os, *M); emitObjCConditional(os, [&] { os << objcModuleContents.str(); }); emitCxxConditional(os, [&] { @@ -530,8 +728,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, moduleContents, *M, interopContext, /*requiresExposedAttribute=*/requiresExplicitExpose); // FIXME: In ObjC++ mode, we do not need to reimport duplicate modules. - writeImports(os, deps.imports, *M, bridgingHeader, /*useCxxImport=*/true); - + writeImports(os, deps.imports, *M, bridgingHeader, frontendOpts, + clangHeaderSearchInfo, /*useCxxImport=*/true); // Embed the standard library directly. if (defaultDependencyBehavior && deps.dependsOnStandardLibrary) { assert(!M->isStdlibModule()); diff --git a/test/PrintAsObjC/Inputs/custom-modules/header_subdirectory/header-regular.h b/test/PrintAsObjC/Inputs/custom-modules/header_subdirectory/header-regular.h new file mode 100644 index 0000000000000..3cd40f8478946 --- /dev/null +++ b/test/PrintAsObjC/Inputs/custom-modules/header_subdirectory/header-regular.h @@ -0,0 +1,5 @@ +#import + +@interface Baz : NSObject +- (void)baz; +@end diff --git a/test/PrintAsObjC/Inputs/custom-modules/header_subdirectory/header-symlink.h b/test/PrintAsObjC/Inputs/custom-modules/header_subdirectory/header-symlink.h new file mode 120000 index 0000000000000..22b3ea9dcbace --- /dev/null +++ b/test/PrintAsObjC/Inputs/custom-modules/header_subdirectory/header-symlink.h @@ -0,0 +1 @@ +../header_symlink_targets/foo.h \ No newline at end of file diff --git a/test/PrintAsObjC/Inputs/custom-modules/header_symlink_targets/foo.h b/test/PrintAsObjC/Inputs/custom-modules/header_symlink_targets/foo.h new file mode 100644 index 0000000000000..596e8bc8033d9 --- /dev/null +++ b/test/PrintAsObjC/Inputs/custom-modules/header_symlink_targets/foo.h @@ -0,0 +1,5 @@ +#import + +@interface Foo : NSObject +- (void)baz; +@end diff --git a/test/PrintAsObjC/Inputs/custom-modules/module.map b/test/PrintAsObjC/Inputs/custom-modules/module.map index e21e9744723a9..5bb018257a2f4 100644 --- a/test/PrintAsObjC/Inputs/custom-modules/module.map +++ b/test/PrintAsObjC/Inputs/custom-modules/module.map @@ -52,3 +52,9 @@ module MiserablePileOfSecrets { header "MiserablePileOfSecrets.h" export * } + +module EmitClangHeaderNonmodularIncludesStressTest { + header "header_subdirectory/header-regular.h" + header "header_subdirectory/header-symlink.h" + export * +} diff --git a/test/PrintAsObjC/emit-clang-header-nonmodular-includes-mock-sdk.swift b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-mock-sdk.swift new file mode 100644 index 0000000000000..e1506bc67e041 --- /dev/null +++ b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-mock-sdk.swift @@ -0,0 +1,38 @@ +// REQUIRES: objc_interop + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -F %S/Inputs/ -typecheck -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 + +import Foundation +import Mixed + +public class HelloWorld: NSObject { + @objc public func sayHello() { + print("Hello, World!") + } + + @objc public func getPoint() -> CGPoint { + return CGPoint(x: 1, y: 1) + } + + @objc public func getIntAlias() -> CIntAlias { + let result: CInt = 0 + return result + } +} + +// 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: @import Mixed; +// CHECK-NEXT: @import ObjectiveC; +// CHECK-NEXT: #else +// CHECK-NEXT: #import +// CHECK-NEXT: #import +// CHECK-NEXT: #import +// CHECK-NEXT: #import +// 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 new file mode 100644 index 0000000000000..c2944a1cbee92 --- /dev/null +++ b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-modulemap-not-in-include-dir.swift @@ -0,0 +1,21 @@ +// REQUIRES: objc_interop + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -Xcc -fmodule-map-file=%S/Inputs/custom-modules/module.map -Xcc -I%S/Inputs/custom-modules/header_subdirectory/ -I%S/Inputs/custom-modules/ -emit-objc-header-path %t/textual-imports.h -emit-clang-header-nonmodular-includes %s +// RUN: %FileCheck %s < %t/textual-imports.h + +// The module map does not lie in a provided include dir, but we should still include and NOT because +// of the provided include dir pointing into header_subdirectory. + +import EmitClangHeaderNonmodularIncludesStressTest + +public class Bar : Baz {} + +// 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 EmitClangHeaderNonmodularIncludesStressTest; +// CHECK-NEXT: #else +// CHECK: #import +// 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 new file mode 100644 index 0000000000000..0f817e0bbaf2f --- /dev/null +++ b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-path-normalization.swift @@ -0,0 +1,19 @@ +// REQUIRES: objc_interop + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -Xcc -fmodule-map-file=%S/Inputs/custom-modules/module.map -Xcc -I%S/Inputs/custom-modules/./header_subdirectory/ -I%S/Inputs/custom-modules/ -emit-objc-header-path %t/textual-imports.h -emit-clang-header-nonmodular-includes %s +// RUN: %FileCheck %s < %t/textual-imports.h + +// The period in the provided include directory above should not break the system. +import EmitClangHeaderNonmodularIncludesStressTest + +public class Bar : Baz {} + +// 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 EmitClangHeaderNonmodularIncludesStressTest; +// CHECK-NEXT: #else +// CHECK: #import +// 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 new file mode 100644 index 0000000000000..b0f1e15dfa852 --- /dev/null +++ b/test/PrintAsObjC/emit-clang-header-nonmodular-includes-symlinked-header.swift @@ -0,0 +1,21 @@ +// REQUIRES: objc_interop + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -typecheck -Xcc -fmodule-map-file=%S/Inputs/custom-modules/module.map -Xcc -I%S/Inputs/custom-modules/header_subdirectory/ -I%S/Inputs/custom-modules/ -emit-objc-header-path %t/textual-imports.h -emit-clang-header-nonmodular-includes %s +// RUN: %FileCheck %s < %t/textual-imports.h + +// Make sure the include we get is not based on the resolved symlink, but rather the path to the symlink itself. + +import EmitClangHeaderNonmodularIncludesStressTest + +public class Bar : Foo {} + + +// 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 EmitClangHeaderNonmodularIncludesStressTest; +// CHECK-NEXT: #else +// CHECK: #import +// CHECK: #endif