diff --git a/include/swift/AST/DiagnosticsFrontend.def b/include/swift/AST/DiagnosticsFrontend.def index c2ebf1b73e9bb..8060e8b9f64a4 100644 --- a/include/swift/AST/DiagnosticsFrontend.def +++ b/include/swift/AST/DiagnosticsFrontend.def @@ -612,6 +612,11 @@ ERROR(package_cmo_requires_library_evolution, none, WARNING(internal_bridging_header_without_library_evolution,none, "using internal bridging headers without library evolution can cause instability", ()) +ERROR(error_invalid_clang_header_access_level, none, + "invalid minimum clang header access level '%0'; chose from " + "'public'|'package'|'internal'", + (StringRef)) + ERROR(experimental_not_supported_in_production,none, "experimental feature '%0' cannot be enabled in production compiler", (StringRef)) diff --git a/include/swift/Frontend/FrontendOptions.h b/include/swift/Frontend/FrontendOptions.h index 68101735374a6..db55d8650492f 100644 --- a/include/swift/Frontend/FrontendOptions.h +++ b/include/swift/Frontend/FrontendOptions.h @@ -13,6 +13,7 @@ #ifndef SWIFT_FRONTEND_FRONTENDOPTIONS_H #define SWIFT_FRONTEND_FRONTENDOPTIONS_H +#include "swift/AST/AttrKind.h" #include "swift/Basic/FileTypes.h" #include "swift/Basic/PathRemapper.h" #include "swift/Basic/Version.h" @@ -501,6 +502,10 @@ class FrontendOptions { /// header. std::optional ClangHeaderExposedDecls; + // Include declarations that are at least as visible as the acces specified + // by -emit-clang-header-min-access + std::optional ClangHeaderMinAccess; + struct ClangHeaderExposedImportedModule { std::string moduleName; std::string headerName; diff --git a/include/swift/Option/Options.td b/include/swift/Option/Options.td index 10ffc1b2dc6e9..dc06d7c943559 100644 --- a/include/swift/Option/Options.td +++ b/include/swift/Option/Options.td @@ -808,6 +808,11 @@ def emit_clang_header_path : Separate<["-"], "emit-clang-header-path">, HelpText<"Emit an Objective-C and C++ header file to ">, Alias; +def emit_clang_header_min_access : Separate<["-"], "emit-clang-header-min-access">, + Flags<[FrontendOption, NoInteractiveOption, ArgumentIsPath, CacheInvariant]>, + MetaVarName<"">, + HelpText<"The minimum access level of declarations to include in the emitted header.>">; + def static : Flag<["-"], "static">, Flags<[FrontendOption, ModuleInterfaceOption, NoInteractiveOption]>, HelpText<"Make this module statically linkable and make the output of -emit-library a static library.">; diff --git a/lib/Frontend/ArgsToFrontendOptionsConverter.cpp b/lib/Frontend/ArgsToFrontendOptionsConverter.cpp index 8e20de012dea8..ab36e6de720fe 100644 --- a/lib/Frontend/ArgsToFrontendOptionsConverter.cpp +++ b/lib/Frontend/ArgsToFrontendOptionsConverter.cpp @@ -14,7 +14,7 @@ #include "ArgsToFrontendInputsConverter.h" #include "ArgsToFrontendOutputsConverter.h" -#include "clang/Driver/Driver.h" +#include "swift/AST/AttrKind.h" #include "swift/AST/DiagnosticsFrontend.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/Platform.h" @@ -24,18 +24,19 @@ #include "swift/Parse/Lexer.h" #include "swift/Parse/ParseVersion.h" #include "swift/Strings.h" +#include "clang/Driver/Driver.h" #include "llvm/ADT/STLExtras.h" -#include "llvm/TargetParser/Triple.h" #include "llvm/CAS/ObjectStore.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/Compression.h" -#include "llvm/Support/PrefixMapper.h" -#include "llvm/Support/Process.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/LineIterator.h" #include "llvm/Support/Path.h" +#include "llvm/Support/PrefixMapper.h" +#include "llvm/Support/Process.h" +#include "llvm/TargetParser/Triple.h" using namespace swift; using namespace llvm::opt; @@ -399,6 +400,17 @@ bool ArgsToFrontendOptionsConverter::convert( HasExposeAttrOrImplicitDeps) .Default(std::nullopt); } + if (const Arg *A = Args.getLastArg(OPT_emit_clang_header_min_access)) { + Opts.ClangHeaderMinAccess = + llvm::StringSwitch>(A->getValue()) + .Case("public", AccessLevel::Public) + .Case("package", AccessLevel::Package) + .Case("internal", AccessLevel::Internal) + .Default(std::nullopt); + if (!Opts.ClangHeaderMinAccess) + Diags.diagnose(SourceLoc(), diag::error_invalid_clang_header_access_level, + A->getValue()); + } for (const auto &arg : Args.getAllArgValues(options::OPT_clang_header_expose_module)) { auto splitArg = StringRef(arg).split('='); diff --git a/lib/PrintAsClang/ModuleContentsWriter.cpp b/lib/PrintAsClang/ModuleContentsWriter.cpp index f8b14a94eabf1..59d223ccf4817 100644 --- a/lib/PrintAsClang/ModuleContentsWriter.cpp +++ b/lib/PrintAsClang/ModuleContentsWriter.cpp @@ -20,6 +20,7 @@ #include "PrintSwiftToClangCoreScaffold.h" #include "SwiftToClangInteropContext.h" +#include "swift/AST/AttrKind.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsSema.h" #include "swift/AST/ExistentialLayout.h" @@ -1151,16 +1152,21 @@ class ModuleWriter { }; } // end anonymous namespace -static AccessLevel getRequiredAccess(const ModuleDecl &M) { +static AccessLevel getRequiredAccess(const ModuleDecl &M, + std::optional minAccess) { + if (minAccess) + return *minAccess; return M.isExternallyConsumed() ? AccessLevel::Public : AccessLevel::Internal; } void swift::printModuleContentsAsObjC( raw_ostream &os, llvm::SmallPtrSetImpl &imports, - ModuleDecl &M, SwiftToClangInteropContext &interopContext) { + ModuleDecl &M, SwiftToClangInteropContext &interopContext, + std::optional minAccess) { llvm::raw_null_ostream prologueOS; llvm::StringSet<> exposedModules; - ModuleWriter(os, prologueOS, imports, M, interopContext, getRequiredAccess(M), + ModuleWriter(os, prologueOS, imports, M, interopContext, + getRequiredAccess(M, minAccess), /*requiresExposedAttribute=*/false, exposedModules, OutputLanguageMode::ObjC) .write(); @@ -1168,10 +1174,12 @@ void swift::printModuleContentsAsObjC( void swift::printModuleContentsAsC( raw_ostream &os, llvm::SmallPtrSetImpl &imports, - ModuleDecl &M, SwiftToClangInteropContext &interopContext) { + ModuleDecl &M, SwiftToClangInteropContext &interopContext, + std::optional minAccess) { llvm::raw_null_ostream prologueOS; llvm::StringSet<> exposedModules; - ModuleWriter(os, prologueOS, imports, M, interopContext, getRequiredAccess(M), + ModuleWriter(os, prologueOS, imports, M, interopContext, + getRequiredAccess(M, minAccess), /*requiresExposedAttribute=*/false, exposedModules, OutputLanguageMode::C) .write(); @@ -1179,7 +1187,8 @@ void swift::printModuleContentsAsC( EmittedClangHeaderDependencyInfo swift::printModuleContentsAsCxx( raw_ostream &os, ModuleDecl &M, SwiftToClangInteropContext &interopContext, - bool requiresExposedAttribute, llvm::StringSet<> &exposedModules) { + AccessLevel minAccess, bool requiresExposedAttribute, + llvm::StringSet<> &exposedModules) { std::string moduleContentsBuf; llvm::raw_string_ostream moduleOS{moduleContentsBuf}; std::string modulePrologueBuf; @@ -1197,8 +1206,8 @@ EmittedClangHeaderDependencyInfo swift::printModuleContentsAsCxx( // FIXME: Use getRequiredAccess once @expose is supported. ModuleWriter writer(moduleOS, prologueOS, info.imports, M, interopContext, - AccessLevel::Public, requiresExposedAttribute, - exposedModules, OutputLanguageMode::Cxx); + minAccess, requiresExposedAttribute, exposedModules, + OutputLanguageMode::Cxx); writer.write(); info.dependsOnStandardLibrary = writer.isStdlibRequired(); if (M.isStdlibModule()) { diff --git a/lib/PrintAsClang/ModuleContentsWriter.h b/lib/PrintAsClang/ModuleContentsWriter.h index 0e7edca689caa..ebe695a8064d9 100644 --- a/lib/PrintAsClang/ModuleContentsWriter.h +++ b/lib/PrintAsClang/ModuleContentsWriter.h @@ -35,12 +35,14 @@ using ImportModuleTy = PointerUnion; void printModuleContentsAsObjC(raw_ostream &os, llvm::SmallPtrSetImpl &imports, ModuleDecl &M, - SwiftToClangInteropContext &interopContext); + SwiftToClangInteropContext &interopContext, + std::optional minAccess); void printModuleContentsAsC(raw_ostream &os, llvm::SmallPtrSetImpl &imports, ModuleDecl &M, - SwiftToClangInteropContext &interopContext); + SwiftToClangInteropContext &interopContext, + std::optional minAccess); struct EmittedClangHeaderDependencyInfo { /// The set of imported modules used by this module. @@ -52,9 +54,11 @@ struct EmittedClangHeaderDependencyInfo { /// Prints the declarations of \p M to \p os in C++ language mode. /// /// \returns Dependencies required by this module. -EmittedClangHeaderDependencyInfo printModuleContentsAsCxx( - raw_ostream &os, ModuleDecl &M, SwiftToClangInteropContext &interopContext, - bool requiresExposedAttribute, llvm::StringSet<> &exposedModules); +EmittedClangHeaderDependencyInfo +printModuleContentsAsCxx(raw_ostream &os, ModuleDecl &M, + SwiftToClangInteropContext &interopContext, + AccessLevel minAccess, bool requiresExposedAttribute, + llvm::StringSet<> &exposedModules); } // end namespace swift diff --git a/lib/PrintAsClang/PrintAsClang.cpp b/lib/PrintAsClang/PrintAsClang.cpp index 26be623ed744b..981b911fd4064 100644 --- a/lib/PrintAsClang/PrintAsClang.cpp +++ b/lib/PrintAsClang/PrintAsClang.cpp @@ -17,6 +17,7 @@ #include "SwiftToClangInteropContext.h" #include "swift/AST/ASTContext.h" +#include "swift/AST/AttrKind.h" #include "swift/AST/Module.h" #include "swift/AST/PrettyStackTrace.h" #include "swift/Basic/Assertions.h" @@ -620,7 +621,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, if (M->getASTContext().LangOpts.hasFeature(Feature::CDecl)) { SmallPtrSet imports; llvm::raw_string_ostream cModuleContents{moduleContentsScratch}; - printModuleContentsAsC(cModuleContents, imports, *M, interopContext); + printModuleContentsAsC(cModuleContents, imports, *M, interopContext, + frontendOpts.ClangHeaderMinAccess); llvm::StringMap exposedModuleHeaderNames; writeImports(os, imports, *M, bridgingHeader, frontendOpts, @@ -634,7 +636,8 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, // Objective-C content SmallPtrSet imports; llvm::raw_string_ostream objcModuleContents{moduleContentsScratch}; - printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext); + printModuleContentsAsObjC(objcModuleContents, imports, *M, interopContext, + frontendOpts.ClangHeaderMinAccess); emitObjCConditional(os, [&] { llvm::StringMap exposedModuleHeaderNames; writeImports(os, imports, *M, bridgingHeader, frontendOpts, @@ -685,6 +688,7 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, llvm::raw_string_ostream moduleContents{moduleContentsBuf}; auto deps = printModuleContentsAsCxx( moduleContents, *M, interopContext, + frontendOpts.ClangHeaderMinAccess.value_or(AccessLevel::Public), /*requiresExposedAttribute=*/requiresExplicitExpose, exposedModules); // FIXME: In ObjC++ mode, we do not need to reimport duplicate modules. llvm::StringMap exposedModuleHeaderNames; @@ -701,9 +705,10 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, auto macroGuard = computeMacroGuard(M->getASTContext().getStdlibModule()); os << "#ifndef " << macroGuard << "\n"; os << "#define " << macroGuard << "\n"; - printModuleContentsAsCxx( - os, *M->getASTContext().getStdlibModule(), interopContext, - /*requiresExposedAttribute=*/true, exposedModules); + printModuleContentsAsCxx(os, *M->getASTContext().getStdlibModule(), + interopContext, AccessLevel::Public, + /*requiresExposedAttribute=*/true, + exposedModules); os << "#endif // " << macroGuard << "\n"; } diff --git a/lib/PrintAsClang/SwiftToClangInteropContext.cpp b/lib/PrintAsClang/SwiftToClangInteropContext.cpp index e364ed5d43f30..4336d09684133 100644 --- a/lib/PrintAsClang/SwiftToClangInteropContext.cpp +++ b/lib/PrintAsClang/SwiftToClangInteropContext.cpp @@ -20,7 +20,7 @@ SwiftToClangInteropContext::SwiftToClangInteropContext( ModuleDecl &mod, const IRGenOptions &irGenOpts) : mod(mod), irGenOpts(irGenOpts) {} -SwiftToClangInteropContext::~SwiftToClangInteropContext() {} +SwiftToClangInteropContext::~SwiftToClangInteropContext() = default; IRABIDetailsProvider &SwiftToClangInteropContext::getIrABIDetails() { if (!irABIDetails) diff --git a/test/Interop/SwiftToCxx/core/set-min-access-level-and-expose-explicitly.swift b/test/Interop/SwiftToCxx/core/set-min-access-level-and-expose-explicitly.swift new file mode 100644 index 0000000000000..c967d350709fc --- /dev/null +++ b/test/Interop/SwiftToCxx/core/set-min-access-level-and-expose-explicitly.swift @@ -0,0 +1,46 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -module-name Core -typecheck -verify -emit-clang-header-path %t/core.h -clang-header-expose-decls=has-expose-attr -emit-clang-header-min-access public -package-name Core +// RUN: %FileCheck %s --check-prefix CHECK-PUBLIC < %t/core.h + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -module-name Core -typecheck -verify -emit-clang-header-path %t/core.h -clang-header-expose-decls=has-expose-attr -emit-clang-header-min-access package -package-name Core +// RUN: %FileCheck %s --check-prefix CHECK-PACKAGE < %t/core.h + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -module-name Core -typecheck -verify -emit-clang-header-path %t/core.h -clang-header-expose-decls=has-expose-attr -emit-clang-header-min-access internal -package-name Core +// RUN: %FileCheck %s --check-prefix CHECK-INTERNAL < %t/core.h + +@_expose(Cxx) +public func publicFunc(_ x: Int) -> Int { + return x +} + +@_expose(Cxx) +package func packageFunc(_ x: Int) -> Int { + return x +} + +@_expose(Cxx) +internal func internalFunc(_ x: Int) -> Int { + return x +} + +@_expose(Cxx) +private func privateFunc(_ x: Int) -> Int { + return x +} + +// CHECK-PUBLIC-NOT: internalFunc +// CHECK-PUBLIC-NOT: packageFunc +// CHECK-PUBLIC-NOT: privateFunc +// CHECK-PUBLIC: SWIFT_INLINE_THUNK swift::Int publicFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core10publicFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { + +// CHECK-PACKAGE-NOT: internalFunc +// CHECK-PACKAGE: SWIFT_INLINE_THUNK swift::Int packageFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core11packageFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-PACKAGE-NOT: privateFunc +// CHECK-PACKAGE: SWIFT_INLINE_THUNK swift::Int publicFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core10publicFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { + +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int internalFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core12internalFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int packageFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core11packageFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-INTERNAL-NOT: privateFunc +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int publicFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core10publicFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { diff --git a/test/Interop/SwiftToCxx/core/set-min-access-level.swift b/test/Interop/SwiftToCxx/core/set-min-access-level.swift new file mode 100644 index 0000000000000..9cb7ac209e4c1 --- /dev/null +++ b/test/Interop/SwiftToCxx/core/set-min-access-level.swift @@ -0,0 +1,79 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -module-name Core -typecheck -verify -emit-clang-header-path %t/core.h -clang-header-expose-decls=all-public -emit-clang-header-min-access public -package-name Core +// RUN: %FileCheck %s --check-prefix CHECK-PUBLIC < %t/core.h + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -module-name Core -typecheck -verify -emit-clang-header-path %t/core.h -clang-header-expose-decls=all-public -emit-clang-header-min-access package -package-name Core +// RUN: %FileCheck %s --check-prefix CHECK-PACKAGE < %t/core.h + +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -module-name Core -typecheck -verify -emit-clang-header-path %t/core.h -clang-header-expose-decls=all-public -emit-clang-header-min-access internal -package-name Core +// RUN: %FileCheck %s --check-prefix CHECK-INTERNAL < %t/core.h + +// RUN: %empty-directory(%t) +// RUN: not %target-swift-frontend %s -module-name Core -typecheck -emit-clang-header-path %t/core.h -clang-header-expose-decls=all-public -emit-clang-header-min-access inernal -package-name Core 2>&1 | %FileCheck %s --check-prefix CHECK-DIAGNOSTIC + +public func publicFunc(_ x: Int) -> Int { + return x +} + +package func packageFunc(_ x: Int) -> Int { + return x +} + +internal func internalFunc(_ x: Int) -> Int { + return x +} + +private func privateFunc(_ x: Int) -> Int { + return x +} + +public struct S { + public func publicMethod(_ x: Int) -> Int { + return x + } + + package func packageMethod(_ x: Int) -> Int { + return x + } + + internal func internalMethod(_ x: Int) -> Int { + return x + } + + private func privateMethod(_ x: Int) -> Int { + return x + } + + private var x: Int +} + +// CHECK-PUBLIC-NOT: internalFunc +// CHECK-PUBLIC-NOT: packageFunc +// CHECK-PUBLIC-NOT: privateFunc +// CHECK-PUBLIC: SWIFT_INLINE_THUNK swift::Int publicFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core10publicFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-PUBLIC: SWIFT_INLINE_THUNK swift::Int S::publicMethod(swift::Int x) const { +// CHECK-PUBLIC-NOT: packageMethod +// CHECK-PUBLIC-NOT: internalMethod +// CHECK-PUBLIC-NOT: privateMethod + +// CHECK-PACKAGE-NOT: internalFunc +// CHECK-PACKAGE: SWIFT_INLINE_THUNK swift::Int packageFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core11packageFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-PACKAGE-NOT: privateFunc +// CHECK-PACKAGE: SWIFT_INLINE_THUNK swift::Int publicFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core10publicFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-PACKAGE: SWIFT_INLINE_THUNK swift::Int S::publicMethod(swift::Int x) const { +// CHECK-PACKAGE: SWIFT_INLINE_THUNK swift::Int S::packageMethod(swift::Int x) const { +// CHECK-PACKAGE-NOT: internalMethod +// CHECK-PACKAGE-NOT: privateMethod + +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int internalFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core12internalFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int packageFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core11packageFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-INTERNAL-NOT: privateFunc +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int publicFunc(swift::Int x) noexcept SWIFT_SYMBOL("s:4Core10publicFuncyS2iF") SWIFT_WARN_UNUSED_RESULT { +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int S::publicMethod(swift::Int x) const { +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int S::packageMethod(swift::Int x) const { +// CHECK-INTERNAL: SWIFT_INLINE_THUNK swift::Int S::internalMethod(swift::Int x) const { +// CHECK-INTERNAL-NOT: privateMethod + +// CHECK-DIAGNOSTIC: error: invalid minimum clang header access level 'inernal'; chose from 'public'|'package'|'internal'