diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 792568c22d518..c691a16bd3b17 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -659,7 +659,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated { HasAnyUnavailableValues : 1 ); - SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1, + SWIFT_INLINE_BITFIELD(ModuleDecl, TypeDecl, 1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1, /// If the module is compiled as static library. StaticLibrary : 1, @@ -709,7 +709,10 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated { /// If the map from @objc provided name to top level swift::Decl in this /// module is populated - ObjCNameLookupCachePopulated : 1 + ObjCNameLookupCachePopulated : 1, + + /// Whether this module has been built with C++ interoperability enabled. + HasCxxInteroperability : 1 ); SWIFT_INLINE_BITFIELD(PrecedenceGroupDecl, Decl, 1+2, diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 314bfab93b424..f5bd8f400f2a0 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -874,6 +874,12 @@ ERROR(need_hermetic_seal_to_import_module,none, "module %0 was built with -experimental-hermetic-seal-at-link, but " "current compilation does not have -experimental-hermetic-seal-at-link", (Identifier)) +ERROR(need_cxx_interop_to_import_module,none, + "module %0 was built with C++ interoperability enabled, but " + "current compilation does not enable C++ interoperability", + (Identifier)) +NOTE(enable_cxx_interop_docs,none, + "visit https://www.swift.org/documentation/cxx-interop/project-build-setup to learn how to enable C++ interoperability", ()) ERROR(modularization_issue_decl_moved,Fatal, "reference to %select{top-level declaration|type}0 %1 broken by a context change; " diff --git a/include/swift/AST/Module.h b/include/swift/AST/Module.h index b15b881b5bf29..b1f0c7f6a24a9 100644 --- a/include/swift/AST/Module.h +++ b/include/swift/AST/Module.h @@ -654,6 +654,14 @@ class ModuleDecl Bits.ModuleDecl.HasHermeticSealAtLink = enabled; } + /// Returns true if this module was built with C++ interoperability enabled. + bool hasCxxInteroperability() const { + return Bits.ModuleDecl.HasCxxInteroperability; + } + void setHasCxxInteroperability(bool enabled = true) { + Bits.ModuleDecl.HasCxxInteroperability = enabled; + } + /// \returns true if this module is a system module; note that the StdLib is /// considered a system module. bool isSystemModule() const { diff --git a/include/swift/Basic/LangOptions.h b/include/swift/Basic/LangOptions.h index 782803d85d6fc..8f77bc68ae512 100644 --- a/include/swift/Basic/LangOptions.h +++ b/include/swift/Basic/LangOptions.h @@ -313,6 +313,10 @@ namespace swift { /// Imports getters and setters as computed properties. bool CxxInteropGettersSettersAsProperties = false; + /// Should the compiler require C++ interoperability to be enabled + /// when importing Swift modules that enable C++ interoperability. + bool RequireCxxInteropToImportCxxInteropModule = true; + /// On Darwin platforms, use the pre-stable ABI's mark bit for Swift /// classes instead of the stable ABI's bit. This is needed when /// targeting OSes prior to macOS 10.14.4 and iOS 12.2, where diff --git a/include/swift/Option/FrontendOptions.td b/include/swift/Option/FrontendOptions.td index df09e0f4ac622..8e8ed8d5b46bb 100644 --- a/include/swift/Option/FrontendOptions.td +++ b/include/swift/Option/FrontendOptions.td @@ -865,6 +865,11 @@ def cxx_interop_getters_setters_as_properties : HelpText<"Import getters and setters as computed properties in Swift">, Flags<[FrontendOption, HelpHidden]>; +def cxx_interop_disable_requirement_at_import : + Flag<["-"], "disable-cxx-interop-requirement-at-import">, + HelpText<"Do not require C++ interoperability to be enabled when importing a Swift module that enables C++ interoperability">, + Flags<[FrontendOption, HelpHidden]>; + def use_malloc : Flag<["-"], "use-malloc">, HelpText<"Allocate internal data structures using malloc " "(for memory debugging)">; diff --git a/include/swift/Serialization/Validation.h b/include/swift/Serialization/Validation.h index b2404b8516579..a82380e74963a 100644 --- a/include/swift/Serialization/Validation.h +++ b/include/swift/Serialization/Validation.h @@ -131,6 +131,7 @@ class ExtendedValidationInfo { unsigned IsBuiltFromInterface : 1; unsigned IsAllowModuleWithCompilerErrorsEnabled : 1; unsigned IsConcurrencyChecked : 1; + unsigned HasCxxInteroperability : 1; } Bits; public: ExtendedValidationInfo() : Bits() {} @@ -232,6 +233,10 @@ class ExtendedValidationInfo { void setIsConcurrencyChecked(bool val = true) { Bits.IsConcurrencyChecked = val; } + bool hasCxxInteroperability() const { return Bits.HasCxxInteroperability; } + void setHasCxxInteroperability(bool val) { + Bits.HasCxxInteroperability = val; + } }; struct SearchPath { diff --git a/lib/AST/Module.cpp b/lib/AST/Module.cpp index 0c695de33dfba..dd8a02dcb3f1b 100644 --- a/lib/AST/Module.cpp +++ b/lib/AST/Module.cpp @@ -646,6 +646,7 @@ ModuleDecl::ModuleDecl(Identifier name, ASTContext &ctx, Bits.ModuleDecl.HasHermeticSealAtLink = 0; Bits.ModuleDecl.IsConcurrencyChecked = 0; Bits.ModuleDecl.ObjCNameLookupCachePopulated = 0; + Bits.ModuleDecl.HasCxxInteroperability = 0; } void ModuleDecl::setIsSystemModule(bool flag) { diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 6849bc3f8e690..c75d5a467a4bd 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -1043,6 +1043,8 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args, Args.hasArg(OPT_experimental_c_foreign_reference_types); Opts.CxxInteropGettersSettersAsProperties = Args.hasArg(OPT_cxx_interop_getters_setters_as_properties); + Opts.RequireCxxInteropToImportCxxInteropModule = + !Args.hasArg(OPT_cxx_interop_disable_requirement_at_import); Opts.VerifyAllSubstitutionMaps |= Args.hasArg(OPT_verify_all_substitution_maps); diff --git a/lib/Frontend/Frontend.cpp b/lib/Frontend/Frontend.cpp index b4ff90136cf24..461c34938b15c 100644 --- a/lib/Frontend/Frontend.cpp +++ b/lib/Frontend/Frontend.cpp @@ -1319,6 +1319,9 @@ ModuleDecl *CompilerInstance::getMainModule() const { MainModule->setResilienceStrategy(ResilienceStrategy::Resilient); if (Invocation.getLangOptions().isSwiftVersionAtLeast(6)) MainModule->setIsConcurrencyChecked(true); + if (Invocation.getLangOptions().EnableCXXInterop && + Invocation.getLangOptions().RequireCxxInteropToImportCxxInteropModule) + MainModule->setHasCxxInteroperability(); // Register the main module with the AST context. Context->addLoadedModule(MainModule); diff --git a/lib/Serialization/ModuleFile.h b/lib/Serialization/ModuleFile.h index 919f0f577e162..158b4f5edd0d6 100644 --- a/lib/Serialization/ModuleFile.h +++ b/lib/Serialization/ModuleFile.h @@ -621,6 +621,11 @@ class ModuleFile return Core->Bits.HasHermeticSealAtLink; } + /// Whether this module was built with C++ interoperability enabled. + bool hasCxxInteroperability() const { + return Core->Bits.HasCxxInteroperability; + } + /// Whether the module is resilient. ('-enable-library-evolution') ResilienceStrategy getResilienceStrategy() const { return ResilienceStrategy(Core->Bits.ResilienceStrategy); diff --git a/lib/Serialization/ModuleFileSharedCore.cpp b/lib/Serialization/ModuleFileSharedCore.cpp index a96dea3cd0106..d4c325e22c6ea 100644 --- a/lib/Serialization/ModuleFileSharedCore.cpp +++ b/lib/Serialization/ModuleFileSharedCore.cpp @@ -181,6 +181,9 @@ static bool readOptionsBlock(llvm::BitstreamCursor &cursor, case options_block::MODULE_EXPORT_AS_NAME: extendedInfo.setExportAsName(blobData); break; + case options_block::HAS_CXX_INTEROPERABILITY_ENABLED: + extendedInfo.setHasCxxInteroperability(true); + break; default: // Unknown options record, possibly for use by a future version of the // module format. @@ -1378,6 +1381,7 @@ ModuleFileSharedCore::ModuleFileSharedCore( Bits.IsAllowModuleWithCompilerErrorsEnabled = extInfo.isAllowModuleWithCompilerErrorsEnabled(); Bits.IsConcurrencyChecked = extInfo.isConcurrencyChecked(); + Bits.HasCxxInteroperability = extInfo.hasCxxInteroperability(); MiscVersion = info.miscVersion; ModuleABIName = extInfo.getModuleABIName(); ModulePackageName = extInfo.getModulePackageName(); diff --git a/lib/Serialization/ModuleFileSharedCore.h b/lib/Serialization/ModuleFileSharedCore.h index 9ffdd6138a25a..13824c66a3407 100644 --- a/lib/Serialization/ModuleFileSharedCore.h +++ b/lib/Serialization/ModuleFileSharedCore.h @@ -386,8 +386,11 @@ class ModuleFileSharedCore { /// \c true if this module was built with complete checking for concurrency. unsigned IsConcurrencyChecked: 1; + /// Whether this module is built with C++ interoperability enabled. + unsigned HasCxxInteroperability : 1; + // Explicitly pad out to the next word boundary. - unsigned : 4; + unsigned : 3; } Bits = {}; static_assert(sizeof(ModuleBits) <= 8, "The bit set should be small"); diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index e2f871b82df3f..885790790ccd7 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 790; // add `out` kind to mark uninitialized instruction +const uint16_t SWIFTMODULE_VERSION_MINOR = 791; // HasCxxInteroperability /// A standard hash seed used for all string hashes in a serialized module. /// @@ -888,6 +888,7 @@ namespace options_block { EXTERNAL_SEARCH_PLUGIN_PATH, COMPILER_PLUGIN_LIBRARY_PATH, COMPILER_PLUGIN_EXECUTABLE_PATH, + HAS_CXX_INTEROPERABILITY_ENABLED, }; using SDKPathLayout = BCRecordLayout< @@ -976,6 +977,10 @@ namespace options_block { MODULE_EXPORT_AS_NAME, BCBlob >; + + using HasCxxInteroperabilityEnabledLayout = BCRecordLayout< + HAS_CXX_INTEROPERABILITY_ENABLED + >; } /// The record types within the input block. diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index 02bc669075a96..8f1758049b6cb 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -843,6 +843,7 @@ void Serializer::writeBlockInfoBlock() { BLOCK_RECORD(options_block, IS_ALLOW_MODULE_WITH_COMPILER_ERRORS_ENABLED); BLOCK_RECORD(options_block, MODULE_ABI_NAME); BLOCK_RECORD(options_block, IS_CONCURRENCY_CHECKED); + BLOCK_RECORD(options_block, HAS_CXX_INTEROPERABILITY_ENABLED); BLOCK_RECORD(options_block, MODULE_PACKAGE_NAME); BLOCK_RECORD(options_block, MODULE_EXPORT_AS_NAME); BLOCK_RECORD(options_block, PLUGIN_SEARCH_PATH); @@ -1090,6 +1091,12 @@ void Serializer::writeHeader(const SerializationOptions &options) { IsConcurrencyChecked.emit(ScratchRecord); } + if (M->hasCxxInteroperability()) { + options_block::HasCxxInteroperabilityEnabledLayout + CxxInteroperabilityEnabled(Out); + CxxInteroperabilityEnabled.emit(ScratchRecord); + } + if (options.SerializeOptionsForDebugging) { options_block::SDKPathLayout SDKPath(Out); options_block::XCCLayout XCC(Out); diff --git a/lib/Serialization/SerializedModuleLoader.cpp b/lib/Serialization/SerializedModuleLoader.cpp index 03984e311e507..6b952b96295ae 100644 --- a/lib/Serialization/SerializedModuleLoader.cpp +++ b/lib/Serialization/SerializedModuleLoader.cpp @@ -824,6 +824,8 @@ LoadedFile *SerializedModuleLoaderBase::loadAST( M.setABIName(Ctx.getIdentifier(loadedModuleFile->getModuleABIName())); if (loadedModuleFile->isConcurrencyChecked()) M.setIsConcurrencyChecked(); + if (loadedModuleFile->hasCxxInteroperability()) + M.setHasCxxInteroperability(); if (!loadedModuleFile->getModulePackageName().empty()) { M.setPackageName(Ctx.getIdentifier(loadedModuleFile->getModulePackageName())); } @@ -896,6 +898,19 @@ LoadedFile *SerializedModuleLoaderBase::loadAST( diag::need_hermetic_seal_to_import_module, M.getName()); } + // Non-resilient modules built with C++ interoperability enabled + // are typically incompatible with clients that do not enable + // C++ interoperability. + if (M.hasCxxInteroperability() && + M.getResilienceStrategy() != ResilienceStrategy::Resilient && + !Ctx.LangOpts.EnableCXXInterop && + Ctx.LangOpts.RequireCxxInteropToImportCxxInteropModule) { + auto loc = diagLoc.value_or(SourceLoc()); + Ctx.Diags.diagnose(loc, diag::need_cxx_interop_to_import_module, + M.getName()); + Ctx.Diags.diagnose(loc, diag::enable_cxx_interop_docs); + } + return fileUnit; } diff --git a/test/Interop/Cxx/implementation-only-imports/import-implementation-only-cxx-interop-module-without-interop.swift b/test/Interop/Cxx/implementation-only-imports/import-implementation-only-cxx-interop-module-without-interop.swift index ecb08c10d0641..1f8a69da51a32 100644 --- a/test/Interop/Cxx/implementation-only-imports/import-implementation-only-cxx-interop-module-without-interop.swift +++ b/test/Interop/Cxx/implementation-only-imports/import-implementation-only-cxx-interop-module-without-interop.swift @@ -1,11 +1,11 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend %S/Inputs/use-module-a-impl-only.swift -I %S/Inputs/ -module-name UseModuleAImplOnly -emit-module -emit-module-path %t/UseModuleAImplOnly.swiftmodule -cxx-interoperability-mode=default +// RUN: %target-swift-frontend %S/Inputs/use-module-a-impl-only.swift -I %S/Inputs/ -module-name UseModuleAImplOnly -emit-module -emit-module-path %t/UseModuleAImplOnly.swiftmodule -cxx-interoperability-mode=default -enable-library-evolution // RUN: %target-swift-frontend %s -typecheck -module-name TestMod -I %t -I %S/Inputs // Check that we have used something from CxxShim in 'UseModuleAImplOnly' -// RUN: %target-swift-frontend %S/Inputs/use-module-a-impl-only.swift -I %S/Inputs/ -module-name UseModuleAImplOnly -emit-module -emit-module-path %t/UseModuleAImplOnly.swiftmodule -cxx-interoperability-mode=default -emit-sil -o - | %FileCheck %s +// RUN: %target-swift-frontend %S/Inputs/use-module-a-impl-only.swift -I %S/Inputs/ -module-name UseModuleAImplOnly -emit-module -emit-module-path %t/UseModuleAImplOnly.swiftmodule -cxx-interoperability-mode=default -emit-sil -o - -enable-library-evolution | %FileCheck %s // CHECK: __swift_interopStaticCast import UseModuleAImplOnly diff --git a/test/Interop/Cxx/implementation-only-imports/import-implementation-only-cxxstdlib-interop-module-without-interop.swift b/test/Interop/Cxx/implementation-only-imports/import-implementation-only-cxxstdlib-interop-module-without-interop.swift index 430fce25bbbf7..f020d6c941dec 100644 --- a/test/Interop/Cxx/implementation-only-imports/import-implementation-only-cxxstdlib-interop-module-without-interop.swift +++ b/test/Interop/Cxx/implementation-only-imports/import-implementation-only-cxxstdlib-interop-module-without-interop.swift @@ -1,6 +1,6 @@ // RUN: %empty-directory(%t) -// RUN: %target-swift-frontend %S/Inputs/use-cxx-stdlib-impl-only.swift -I %S/Inputs/ -module-name UseCxxStdlibImplOnly -emit-module -emit-module-path %t/UseCxxStdlibImplOnly.swiftmodule -cxx-interoperability-mode=default +// RUN: %target-swift-frontend %S/Inputs/use-cxx-stdlib-impl-only.swift -I %S/Inputs/ -module-name UseCxxStdlibImplOnly -emit-module -emit-module-path %t/UseCxxStdlibImplOnly.swiftmodule -cxx-interoperability-mode=default -enable-library-evolution // RUN: %target-swift-frontend %s -typecheck -module-name TestMod -I %t -I %S/Inputs diff --git a/test/Interop/Cxx/modules/prohibit-import-into-non-interop-compilation.swift b/test/Interop/Cxx/modules/prohibit-import-into-non-interop-compilation.swift new file mode 100644 index 0000000000000..ce36822467f41 --- /dev/null +++ b/test/Interop/Cxx/modules/prohibit-import-into-non-interop-compilation.swift @@ -0,0 +1,21 @@ +// RUN: %empty-directory(%t) + +// RUN: %target-swift-frontend %s -module-name UsesCxxInterop -emit-module -emit-module-path %t/UsesCxxInterop.swiftmodule -cxx-interoperability-mode=default + +// RUN: %target-swift-frontend %s -D USE -typecheck -module-name TestMod -I %t -verify + +// RUN: %target-swift-frontend %s -D USE -typecheck -module-name TestMod -I %t -disable-cxx-interop-requirement-at-import + +// Verify that 'disable-cxx-interop-requirement-at-import' works for the module being built. +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -module-name UsesCxxInterop -emit-module -emit-module-path %t/UsesCxxInterop.swiftmodule -cxx-interoperability-mode=default -disable-cxx-interop-requirement-at-import +// RUN: %target-swift-frontend %s -D USE -typecheck -module-name TestMod -I %t + +#if USE +import UsesCxxInterop // expected-error {{module 'UsesCxxInterop' was built with C++ interoperability enabled, but current compilation does not enable C++ interoperability}} +// expected-note@-1 {{visit https://www.swift.org/documentation/cxx-interop/project-build-setup to learn how to enable C++ interoperability}} + +#else +public func testFun() { } + +#endif