Skip to content

[C++Interop] Do not query C++ Standard Library Swift overlays when building Swift modules which were built without C++ interop #81908

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

Merged
merged 2 commits into from
Jun 3, 2025
Merged
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
4 changes: 3 additions & 1 deletion include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace swift {

struct DiagnosticBehavior;
class DiagnosticEngine;
class FrontendOptions;

/// Kind of implicit platform conditions.
enum class PlatformConditionKind {
Expand Down Expand Up @@ -339,7 +340,8 @@ namespace swift {
std::optional<version::Version> FormalCxxInteropMode;

void setCxxInteropFromArgs(llvm::opt::ArgList &Args,
swift::DiagnosticEngine &Diags);
swift::DiagnosticEngine &Diags,
const FrontendOptions &FrontendOpts);

/// The C++ standard library used for the current build. This can differ
/// from the default C++ stdlib on a particular platform when `-Xcc
Expand Down
12 changes: 10 additions & 2 deletions lib/ClangImporter/ClangImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4388,7 +4388,14 @@ ModuleDecl *ClangModuleUnit::getOverlayModule() const {
// FIXME: Include proper source location.
ModuleDecl *M = getParentModule();
ASTContext &Ctx = M->getASTContext();
auto overlay = Ctx.getOverlayModule(this);

ModuleDecl *overlay = nullptr;
// During compilation of a textual interface with no formal C++ interop mode,
// i.e. it was built without C++ interop, avoid querying the 'CxxStdlib' overlay
// for it, since said overlay was not used during compilation of this module.
if (!importer::isCxxStdModule(clangModule) || Ctx.LangOpts.FormalCxxInteropMode)
overlay = Ctx.getOverlayModule(this);

if (overlay) {
Ctx.addLoadedModule(overlay);
} else {
Expand All @@ -4408,7 +4415,8 @@ ModuleDecl *ClangModuleUnit::getOverlayModule() const {
// If this Clang module is a part of the C++ stdlib, and we haven't loaded
// the overlay for it so far, it is a split libc++ module (e.g. std_vector).
// Load the CxxStdlib overlay explicitly.
if (!overlay && importer::isCxxStdModule(clangModule)) {
if (!overlay && importer::isCxxStdModule(clangModule) &&
Ctx.LangOpts.FormalCxxInteropMode) {
ImportPath::Module::Builder builder(Ctx.Id_CxxStdlib);
overlay = owner.loadModule(SourceLoc(), std::move(builder).get());
}
Expand Down
8 changes: 7 additions & 1 deletion lib/DependencyScan/ModuleDependencyScanner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1478,7 +1478,13 @@ void ModuleDependencyScanner::resolveSwiftOverlayDependenciesForModule(

// If the textual interface was built without C++ interop, do not query
// the C++ Standard Library Swift overlay for its compilation.
if (llvm::find(commandLine, "-formal-cxx-interoperability-mode=off") ==
//
// FIXME: We always declare the 'Darwin' module as formally having been built
// without C++Interop, for compatibility with prior versions. Once we are certain
// that we are only building against modules built with support of
// '-formal-cxx-interoperability-mode', this hard-coded check should be removed.
if (moduleID.ModuleName != "Darwin" &&
llvm::find(commandLine, "-formal-cxx-interoperability-mode=off") ==
commandLine.end()) {
for (const auto &clangDepName : allClangDependencies) {
// If this Clang module is a part of the C++ stdlib, and we haven't
Expand Down
3 changes: 2 additions & 1 deletion lib/DriverTool/swift_symbolgraph_extract_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ int swift_symbolgraph_extract_main(ArrayRef<const char *> Args,
Options.AvailabilityIsBlockList = A->getOption().matches(OPT_block_availability_platforms);
}

Invocation.getLangOptions().setCxxInteropFromArgs(ParsedArgs, Diags);
Invocation.getLangOptions().setCxxInteropFromArgs(ParsedArgs, Diags,
Invocation.getFrontendOptions());

std::string InstanceSetupError;
if (CI.setup(Invocation, InstanceSetupError)) {
Expand Down
3 changes: 2 additions & 1 deletion lib/DriverTool/swift_synthesize_interface_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ int swift_synthesize_interface_main(ArrayRef<const char *> Args,
Invocation.setImportSearchPaths(ImportSearchPaths);

Invocation.getLangOptions().EnableObjCInterop = Target.isOSDarwin();
Invocation.getLangOptions().setCxxInteropFromArgs(ParsedArgs, Diags);
Invocation.getLangOptions().setCxxInteropFromArgs(ParsedArgs, Diags,
Invocation.getFrontendOptions());

std::string ModuleCachePath = "";
if (auto *A = ParsedArgs.getLastArg(OPT_module_cache_path)) {
Expand Down
12 changes: 9 additions & 3 deletions lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,8 @@ static void diagnoseCxxInteropCompatMode(Arg *verArg, ArgList &Args,
}

void LangOptions::setCxxInteropFromArgs(ArgList &Args,
swift::DiagnosticEngine &Diags) {
swift::DiagnosticEngine &Diags,
const FrontendOptions &FrontendOpts) {
if (Arg *A = Args.getLastArg(options::OPT_cxx_interoperability_mode)) {
if (Args.hasArg(options::OPT_enable_experimental_cxx_interop)) {
Diags.diagnose(SourceLoc(), diag::dont_enable_interop_and_compat);
Expand Down Expand Up @@ -737,7 +738,12 @@ void LangOptions::setCxxInteropFromArgs(ArgList &Args,
// version, and is either 4, 5, 6, or 7 (even though only 5.9 and 6.* make
// any sense). For now, we don't actually care about the version, so we'll
// just use version 6 (i.e., 'swift-6') to mean that C++ interop mode is on.
if (EnableCXXInterop)
//
// FIXME: We always declare the 'Darwin' module as formally having been built
// without C++Interop, for compatibility with prior versions. Once we are certain
// that we are only building against modules built with support of
// '-formal-cxx-interoperability-mode', this hard-coded check should be removed.
if (EnableCXXInterop && (FrontendOpts.ModuleName.compare("Darwin") != 0))
FormalCxxInteropMode = {6};
else
FormalCxxInteropMode = std::nullopt;
Expand Down Expand Up @@ -1560,7 +1566,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
if (const Arg *A = Args.getLastArg(OPT_clang_target_variant))
Opts.ClangTargetVariant = llvm::Triple(A->getValue());

Opts.setCxxInteropFromArgs(Args, Diags);
Opts.setCxxInteropFromArgs(Args, Diags, FrontendOpts);
if (!Args.hasArg(options::OPT_formal_cxx_interoperability_mode))
ModuleInterfaceOpts.PublicFlags.IgnorableFlags +=
" " + printFormalCxxInteropVersion(Opts);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// RUN: %empty-directory(%t)
// RUN: %empty-directory(%t/module-cache)
// RUN: %empty-directory(%t/deps)
// RUN: split-file %s %t

// RUN: %target-swift-frontend -typecheck %t/clientWithInteropDep.swift -I %t/deps -cxx-interoperability-mode=default -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import -module-cache-path %t/module-cache &> %t/interop_dep.txt
// RUN: cat %t/interop_dep.txt | %FileCheck %s -check-prefix=ENABLE-CHECK

// RUN: %empty-directory(%t/module-cache)
// RUN: %target-swift-frontend -typecheck %t/clientNoInteropDep.swift -I %t/deps -cxx-interoperability-mode=default -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import -module-cache-path %t/module-cache &> %t/no_interop_dep.txt
// RUN: cat %t/no_interop_dep.txt | %FileCheck %s -check-prefix=DISABLE-CHECK

// RUN: %empty-directory(%t/module-cache)
// RUN: %target-swift-frontend -typecheck %t/clientDarwin.swift -I %t/deps -cxx-interoperability-mode=default -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import -module-cache-path %t/module-cache &> %t/darwin_dep.txt
// RUN: cat %t/darwin_dep.txt | %FileCheck %s -check-prefix=DISABLE-CHECK

// ENABLE-CHECK: remark: loaded module 'CxxStdlib' (overlay for a clang dependency)
// DISABLE-CHECK-NOT: remark: loaded module 'CxxStdlib' (overlay for a clang dependency)

//--- deps/bar.h
#include "std_bar.h"
void bar(void);

//--- deps/std_bar.h
void std_bar(void);

//--- deps/module.modulemap
module std_Bar [system] {
header "std_bar.h"
export *
}

module Bar [system] {
header "bar.h"
export *
}

//--- deps/Foo.swiftinterface
// swift-interface-format-version: 1.0
// swift-module-flags: -module-name Foo -enable-library-evolution -Rmodule-loading
import Bar
public struct Foo1 {}

//--- deps/FooNoInterop.swiftinterface
// swift-interface-format-version: 1.0
// swift-module-flags: -module-name FooNoInterop -enable-library-evolution
// swift-module-flags-ignorable: -formal-cxx-interoperability-mode=off -Rmodule-loading
import Bar
public struct Foo2 {}

//--- deps/Darwin.swiftinterface
// swift-interface-format-version: 1.0
// swift-module-flags: -module-name Darwin -enable-library-evolution
// swift-module-flags-ignorable: -Rmodule-loading
import Bar
public struct Foo2 {}

//--- clientWithInteropDep.swift
import Foo

//--- clientNoInteropDep.swift
import FooNoInterop

//--- clientDarwin.swift
import Darwin

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
// RUN: %target-swift-frontend -scan-dependencies -o %t/deps_no_interop_dep.json %t/clientNoInteropDep.swift -I %t/deps -cxx-interoperability-mode=default -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import -verify
// RUN: cat %t/deps_no_interop_dep.json | %FileCheck %s -check-prefix=DISABLE-CHECK

// RUN: %target-swift-frontend -scan-dependencies -o %t/deps_darwin_dep.json %t/clientDarwin.swift -I %t/deps -cxx-interoperability-mode=default -disable-implicit-string-processing-module-import -disable-implicit-concurrency-module-import -verify
// RUN: cat %t/deps_darwin_dep.json | %FileCheck %s -check-prefix=DARWIN-CHECK

//--- deps/bar.h
void bar(void);

Expand All @@ -30,12 +33,21 @@ public struct Foo1 {}
import std_Bar
public struct Foo2 {}

//--- deps/Darwin.swiftinterface
// swift-interface-format-version: 1.0
// swift-module-flags: -module-name Darwin -enable-library-evolution
import std_Bar
public struct Foo3 {}

//--- clientWithInteropDep.swift
import Foo

//--- clientNoInteropDep.swift
import FooNoInterop

//--- clientDarwin.swift
import Darwin

// Ensure that when the 'Foo' dependency was built with C++ interop enabled,
// it gets the C++ standard library overlay for its 'std_*' dependency
//
Expand Down Expand Up @@ -75,4 +87,21 @@ import FooNoInterop
// DISABLE-CHECK: }
// DISABLE-CHECK: ],

// Ensure that the the 'Darwin' dependency does not get the C++ standard library overlay for its 'std_*' dependencies
//
// 'Darwin' as it appears in direct deps
// DARWIN-CHECK: "swift": "Darwin"
// 'Darwin' as it appears in source-import deps
// DARWIN-CHECK: "swift": "Darwin"
// Actual dependency info node
// DARWIN-CHECK: "swift": "Darwin"
// DARWIN-CHECK: "directDependencies": [
// DARWIN-CHECK: {
// DARWIN-CHECK: "swift": "SwiftOnoneSupport"
// DARWIN-CHECK: },
// DARWIN-CHECK: {
// DARWIN-CHECK: "clang": "std_Bar"
// DARWIN-CHECK: }
// DARWIN-CHECK: ],