[clang] [clang][modules-driver] Add support for C++ named modules and `import std` (PR #193312)

Naveen Seth Hanig via cfe-commits cfe-commits at lists.llvm.org
Wed Apr 22 01:59:32 PDT 2026


https://github.com/naveen-seth updated https://github.com/llvm/llvm-project/pull/193312

>From 54e0baf40802216e133fb8553fbe8f81d67f53fa Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at oulook.com>
Date: Tue, 21 Apr 2026 22:25:23 +0200
Subject: [PATCH 1/4] [clang][modules-driver] Add support for C++ named modules
 and `import std`

This patch adds basic support for explicit module builds using C++ named
modules, managed natively by the Clang driver, including the use of Standard
library modules.
This follows #187606, which adds the same support for explicit Clang module
builds.

This currently compiles object files even for Standard library modules, which
will be addressed in a follow-up patch.

RFC:
https://discourse.llvm.org/t/rfc-modules-support-simple-c-20-modules-use-from-the-clang-driver-without-a-build-system

(Caching of built modules is not included in this patch.)
---
 .../clang/Basic/DiagnosticDriverKinds.td      |   2 +
 clang/include/clang/Driver/ModulesDriver.h    |   4 +
 clang/lib/Driver/Driver.cpp                   |   2 +
 clang/lib/Driver/ModulesDriver.cpp            | 105 +++++++++++++++--
 .../modules-driver-both-modules-types.cpp     | 111 ++++++++++++++++++
 .../modules-driver-cxx-modules-only.cpp       |  89 ++++++++++++++
 .../test/Driver/modules-driver-import-std.cpp |  60 ++++++++++
 .../modules-driver-incompatible-options.cpp   |  10 ++
 8 files changed, 373 insertions(+), 10 deletions(-)
 create mode 100644 clang/test/Driver/modules-driver-both-modules-types.cpp
 create mode 100644 clang/test/Driver/modules-driver-cxx-modules-only.cpp
 create mode 100644 clang/test/Driver/modules-driver-import-std.cpp
 create mode 100644 clang/test/Driver/modules-driver-incompatible-options.cpp

diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index f217180933b9b..724b639c4889f 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -613,6 +613,8 @@ def err_drv_reduced_module_output_overrided : Warning<
   "please consider use '-fmodule-output=' to specify the output file for reduced BMI explicitly">,
   InGroup<DiagGroup<"reduced-bmi-output-overrided">>;
 
+def err_drv_modules_driver_requires_reduced_bmi : Error<
+  "-fmodules-driver' is incompatible with '-fno-modules-reduced-bmi">;
 def remark_performing_driver_managed_module_build : Remark<
   "performing driver managed module build">, InGroup<ModulesDriver>;
 def remark_modules_manifest_not_found : Remark<
diff --git a/clang/include/clang/Driver/ModulesDriver.h b/clang/include/clang/Driver/ModulesDriver.h
index 4f5fe7a7dfc1a..7146d2f6b143f 100644
--- a/clang/include/clang/Driver/ModulesDriver.h
+++ b/clang/include/clang/Driver/ModulesDriver.h
@@ -32,6 +32,10 @@ class Compilation;
 
 namespace clang::driver::modules {
 
+/// Emits diagnostics for arguments incompatible with -fmodules-driver.
+void diagnoseModulesDriverArgs(llvm::opt::DerivedArgList &DAL,
+                               DiagnosticsEngine &Diags);
+
 /// The parsed Standard library module manifest.
 struct StdModuleManifest {
   struct Module {
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 2cbe3179892ff..1e6a7b5e59d0f 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -1832,6 +1832,8 @@ Compilation *Driver::BuildCompilation(ArrayRef<const char *> ArgList) {
   if (UseModulesDriver) {
     Diags.Report(diag::remark_performing_driver_managed_module_build);
 
+    modules::diagnoseModulesDriverArgs(C->getArgs(), Diags);
+
     // Read the Standard library module manifest and, if available, add all
     // discovered modules to this Compilation. Jobs for modules specified in
     // the manifest that are not required by any command-line input are pruned
diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp
index 82865f903590f..f2b994720b411 100644
--- a/clang/lib/Driver/ModulesDriver.cpp
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -21,6 +21,7 @@
 #include "clang/Driver/Job.h"
 #include "clang/Driver/Tool.h"
 #include "clang/Driver/ToolChain.h"
+#include "clang/Driver/Types.h"
 #include "clang/Frontend/StandaloneDiagnostic.h"
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/DepthFirstIterator.h"
@@ -47,6 +48,14 @@ using namespace clang;
 using namespace driver;
 using namespace modules;
 
+void driver::modules::diagnoseModulesDriverArgs(llvm::opt::DerivedArgList &DAL,
+                                                DiagnosticsEngine &Diags) {
+  if (!DAL.hasFlag(options::OPT_fmodules_reduced_bmi,
+                   options::OPT_fno_modules_reduced_bmi, true)) {
+    Diags.Report(diag::err_drv_modules_driver_requires_reduced_bmi);
+  }
+}
+
 namespace clang::driver::modules {
 static bool fromJSON(const llvm::json::Value &Params,
                      StdModuleManifest::Module::LocalArguments &LocalArgs,
@@ -1231,6 +1240,16 @@ static SmallVector<JobNode *> createNodesForUnusedStdlibModuleJobs(
   return StdlibModuleNodesToPrune;
 }
 
+// Returns the derived argument list for the tool chain responsible
+// for creating \p Job.
+static const DerivedArgList &getToolChainArgs(Compilation &C,
+                                              const Command &Job) {
+  const auto &TC = Job.getCreator().getToolChain();
+  const auto &SourceAction = Job.getSource();
+  return C.getArgsForToolChain(&TC, SourceAction.getOffloadingArch(),
+                               SourceAction.getOffloadingDeviceKind());
+}
+
 /// Creates a job for the Clang module described by \p MD.
 static std::unique_ptr<Command>
 createClangModulePrecompileJob(Compilation &C, const Command &ImportingJob,
@@ -1242,9 +1261,7 @@ createClangModulePrecompileJob(Compilation &C, const Command &ImportingJob,
   Action *PA = C.MakeAction<PrecompileJobAction>(IA, types::ID::TY_ModuleFile);
   PA->propagateOffloadInfo(&ImportingJob.getSource());
 
-  const auto &TC = ImportingJob.getCreator().getToolChain();
-  const auto &TCArgs = C.getArgsForToolChain(&TC, PA->getOffloadingArch(),
-                                             PA->getOffloadingDeviceKind());
+  const auto &TCArgs = getToolChainArgs(C, ImportingJob);
 
   const auto &BuildArgs = MD.getBuildArguments();
   ArgStringList JobArgs;
@@ -1298,12 +1315,7 @@ installScanCommandLines(Compilation &C,
     ArgStringList JobArgs;
     JobArgs.reserve(BuildArgs.size());
 
-    const auto &SourceAction = Job.getSource();
-    const auto &TC = Job.getCreator().getToolChain();
-    auto &TCArgs =
-        C.getArgsForToolChain(&TC, SourceAction.getOffloadingArch(),
-                              SourceAction.getOffloadingDeviceKind());
-
+    auto &TCArgs = getToolChainArgs(C, Job);
     for (const auto &Arg : BuildArgs)
       JobArgs.push_back(TCArgs.MakeArgString(Arg));
 
@@ -1506,6 +1518,79 @@ static void createAndConnectRoot(CompilationGraph &Graph) {
   }
 }
 
+/// Creates a temporary output path for \p ModuleName.
+static std::string createModuleOutputPath(const Compilation &C,
+                                          StringRef ModuleName) {
+  auto ModuleOutputPath = C.getDriver().GetTemporaryPath(
+      ModuleName, types::getTypeTempSuffix(types::TY_ModuleFile));
+  // Sanitize the ':' included in parition names. It is illegal for filenames on
+  // Windows.
+  llvm::replace(ModuleOutputPath, ':', '_');
+  return ModuleOutputPath;
+}
+
+/// Adds arguments to output the module produced by \p Node at \p OutputPath.
+static void configureNamedModuleOutputArgs(Compilation &C,
+                                           NamedModuleJobNode &Node,
+                                           StringRef ModuleOutputPath) {
+  auto &Job = *Node.Job;
+  const auto &TCArgs = getToolChainArgs(C, Job);
+  auto JobArgs = Job.getArguments();
+
+  JobArgs.push_back(
+      TCArgs.MakeArgString("-fmodule-output=" + ModuleOutputPath));
+  // Ensure that this job will emit a module. If the main input for this job
+  // is not of types::CXXModule, we need to explicitly add this.
+  if (Job.getInputInfos().front().getType() != types::TY_CXXModule) {
+    JobArgs.push_back(TCArgs.MakeArgString("-fmodules-reduced-bmi"));
+  }
+
+  Job.replaceArguments(std::move(JobArgs));
+}
+
+/// Propagates -fmodule-file mappings to dependent jobs of the named module
+/// described by \p Node.
+static void propagateModuleFileMappingArgs(Compilation &C,
+                                           NamedModuleJobNode &Node,
+                                           StringRef ModuleOutputPath) {
+  const StringRef ModuleName = Node.InputDeps.ModuleName;
+
+  auto DependentNodes = llvm::drop_begin(llvm::depth_first<CGNode *>(&Node));
+  auto DependentScannedNodes = llvm::map_range(
+      llvm::make_filter_range(DependentNodes, llvm::IsaPred<ScannedJobNode>),
+      llvm::CastTo<ScannedJobNode>);
+
+  for (ScannedJobNode *DependentNode : DependentScannedNodes) {
+    auto &DependentJob = *DependentNode->Job;
+    const auto &TCArgs = getToolChainArgs(C, DependentJob);
+    auto JobArgs = DependentJob.getArguments();
+    JobArgs.push_back(TCArgs.MakeArgString("-fmodule-file=" + ModuleName + "=" +
+                                           ModuleOutputPath));
+    DependentJob.replaceArguments(std::move(JobArgs));
+  }
+}
+
+/// Finalizes command lines for C++20 named module dependencies.
+///
+/// The command lines produced by dependency scanning are only adjusted to
+/// handle discovered Clang modules. For C++20 named modules, we update the
+/// command-lines here.
+static void fixupNamedModuleCommandLines(Compilation &C,
+                                         CompilationGraph &Graph) {
+  const auto NamedModuleNodes = llvm::map_range(
+      llvm::make_filter_range(Graph, llvm::IsaPred<NamedModuleJobNode>),
+      llvm::CastTo<NamedModuleJobNode>);
+
+  for (NamedModuleJobNode *Node : NamedModuleNodes) {
+    const StringRef ModuleName = Node->InputDeps.ModuleName;
+    const auto ModuleOutputPath = createModuleOutputPath(C, ModuleName);
+    C.addTempFile(C.getArgs().MakeArgString(ModuleOutputPath));
+
+    configureNamedModuleOutputArgs(C, *Node, ModuleOutputPath);
+    propagateModuleFileMappingArgs(C, *Node, ModuleOutputPath);
+  }
+}
+
 /// Moves jobs from \p Graph into \p C in the graph's topological order.
 static void feedJobsBackIntoCompilation(Compilation &C,
                                         CompilationGraph &&Graph) {
@@ -1575,7 +1660,7 @@ void driver::modules::runModulesDriver(
   if (!Diags.isLastDiagnosticIgnored())
     llvm::WriteGraph<const CompilationGraph *>(llvm::errs(), &Graph);
 
-  // TODO: Fix-up command-lines for named module imports.
+  fixupNamedModuleCommandLines(C, Graph);
 
   feedJobsBackIntoCompilation(C, std::move(Graph));
 }
diff --git a/clang/test/Driver/modules-driver-both-modules-types.cpp b/clang/test/Driver/modules-driver-both-modules-types.cpp
new file mode 100644
index 0000000000000..3c42261914c4d
--- /dev/null
+++ b/clang/test/Driver/modules-driver-both-modules-types.cpp
@@ -0,0 +1,111 @@
+// Checks that -fmodules-driver correctly handles compilations using both
+// Standard C++20 modules and Clang modules.
+// Importing a Standard C++20 module into Clang module is not supported yet.
+
+// RUN: split-file %s %t
+// RUN: rm -rf %t/modules-cache
+
+// RUN: %clang -c -std=c++23 \
+// RUN:   -fmodules-driver -Rmodules-driver \
+// RUN:   -fmodules -Rmodule-import \
+// RUN:   -fmodule-map-file=%t/module.modulemap \
+// RUN:   -fmodules-cache-path=%t/modules-cache \
+// RUN:   %t/main.cpp %t/A.cppm %t/A-part1.cppm %t/A-part1-impl.cpp 2>&1 \
+// RUN:   | sed 's:\\\\\?:/:g' \
+// RUN:   | FileCheck -DPREFIX=%/t --check-prefix=CHECK-REMARKS %s
+
+// The scan itself will also produce [-Rmodule-import] remarks.
+// Let's skip past them, we only care about the final -cc1 commands.
+// CHECK-REMARKS:       clang: remark: printing module dependency graph [-Rmodules-driver]
+// CHECK-REMARKS-NEXT:  digraph "Module Dependency Graph" {
+// CHECK-REMARKS:       }
+
+// CHECK-REMARKS: [[PREFIX]]/A-part1-impl.cpp:2:2: remark: importing module 'root' from
+// CHECK-REMARKS: [[PREFIX]]/A-part1.cppm:2:2: remark: importing module 'root' from
+// CHECK-REMARKS: [[PREFIX]]/A.cppm:2:2: remark: importing module 'root' from
+// CHECK-REMARKS: [[PREFIX]]/A.cppm:4:8: remark: importing module 'A:part1' from
+// CHECK-REMARKS: [[PREFIX]]/A.cppm:4:8: remark: importing module 'root' into 'A:part1' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'A' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'root' into 'A' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'A:part1' into 'A' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'root' into 'A:part1' from
+
+// RUN: %clang -std=c++23 \
+// RUN:   -fmodules-driver -Rmodules-driver \
+// RUN:   -fmodules -Rmodule-import \
+// RUN:   -fmodule-map-file=%t/module.modulemap \
+// RUN:   -fmodules-cache-path=%t/modules-cache \
+// RUN:   %t/main.cpp %t/A.cppm %t/A-part1.cppm %t/A-part1-impl.cpp \
+// RUN:   -### 2>&1 \
+// RUN:   | sed 's:\\\\\?:/:g' \
+// RUN:   | FileCheck -DPREFIX=%/t --check-prefix=CHECK-CC1 %s
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "-o" "[[ROOTPCM:[^"]+]]"
+// CHECK-CC1-SAME: "-emit-module"
+// CHECK-CC1-SAME: "[[PREFIX]]/module.modulemap"
+// CHECK-CC1-SAME: "-fmodule-name=root"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "[[PREFIX]]/A-part1-impl.cpp"
+// CHECK-CC1-SAME: "-fmodule-file=root=[[ROOTPCM]]"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-output=[[A_PART1_IMPL_PCM:[^"]+]]"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "[[PREFIX]]/A-part1.cppm"
+// CHECK-CC1-SAME: "-fmodule-file=root=[[ROOTPCM]]"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-output=[[A_PART1_PCM:[^"]+]]"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "[[PREFIX]]/A.cppm"
+// CHECK-CC1-SAME: "-fmodule-file=root=[[ROOTPCM]]"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-output=[[A_PCM:[^"]+]]"
+// CHECK-CC1-SAME: "-fmodule-file=A:part1=[[A_PART1_PCM]]"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "[[PREFIX]]/main.cpp"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-file=A=[[A_PCM]]"
+// CHECK-CC1-SAME: "-fmodule-file=A:part1=[[A_PART1_PCM]]"
+
+//--- main.cpp
+import A;
+
+int main() {
+  a();
+}
+
+//--- A.cppm
+module;
+#include "root.h"
+export module A;
+export import :part1;
+
+export int a() {
+  return part1() + root();
+}
+
+//--- A-part1.cppm
+module;
+#include "root.h"
+export module A:part1;
+export int part1();
+
+//--- A-part1-impl.cpp
+module;
+#include "root.h"
+module A:part1_impl;
+
+int part1() {
+  return root();
+}
+
+//--- module.modulemap
+module root { header "root.h" export * }
+
+//--- root.h
+inline int root() { return 1; }
diff --git a/clang/test/Driver/modules-driver-cxx-modules-only.cpp b/clang/test/Driver/modules-driver-cxx-modules-only.cpp
new file mode 100644
index 0000000000000..b3a3cef9a8ee0
--- /dev/null
+++ b/clang/test/Driver/modules-driver-cxx-modules-only.cpp
@@ -0,0 +1,89 @@
+// Checks that -fmodules-driver correctly handles compilations using
+// Standard C++20 modules.
+
+// RUN: split-file %s %t
+
+// RUN: %clang -c -std=c++23 \
+// RUN:   -fmodules-driver -Rmodules-driver -Rmodule-import \
+// RUN:   %t/main.cpp %t/A.cppm %t/A-part1.cppm %t/A-part1-impl.cpp %t/B.cppm 2>&1 \
+// RUN:   | sed 's:\\\\\?:/:g' \
+// RUN:   | FileCheck -DPREFIX=%/t --check-prefix=CHECK-REMARKS %s
+
+// CHECK-REMARKS: [[PREFIX]]/A.cppm:2:8: remark: importing module 'A:part1' from
+// CHECK-REMARKS: [[PREFIX]]/A.cppm:3:1: remark: importing module 'B' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'A' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'A:part1' into 'A' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:1:1: remark: importing module 'B' into 'A' from
+// CHECK-REMARKS: [[PREFIX]]/main.cpp:2:1: remark: importing module 'B' from
+
+// RUN: %clang -std=c++23 \
+// RUN:   -fmodules-driver -Rmodules-driver -Rmodule-import \
+// RUN:   %t/main.cpp %t/A.cppm %t/A-part1.cppm %t/A-part1-impl.cpp %t/B.cppm \
+// RUN:   -### 2>&1 \
+// RUN:   | sed 's:\\\\\?:/:g' \
+// RUN:   | FileCheck --check-prefix=CHECK-CC1 %s
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "{{.*}}/B.cppm"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-output=[[B_PCM:[^"]+]]"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "{{.*}}/A-part1-impl.cpp"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-output=[[A_PART1_IMPL_PCM:[^"]+]]"
+// CHECK-CC1-SAME: "-fmodules-reduced-bmi"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "{{.*}}/A-part1.cppm"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-output=[[A_PART1_PCM:[^"]+]]"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "{{.*}}/A.cppm"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-output=[[A_PCM:[^"]+]]"
+// CHECK-CC1-SAME: "-fmodule-file=A:part1=[[A_PART1_PCM]]"
+// CHECK-CC1-SAME: "-fmodule-file=B=[[B_PCM]]"
+
+// CHECK-CC1: "-cc1"
+// CHECK-CC1-SAME: "{{.*}}/main.cpp"
+// CHECK-CC1-SAME: "-fno-implicit-modules"
+// CHECK-CC1-SAME: "-fmodule-file=A=[[A_PCM]]"
+// CHECK-CC1-SAME: "-fmodule-file=A:part1=[[A_PART1_PCM]]"
+// CHECK-CC1-SAME: "-fmodule-file=B=[[B_PCM]]"
+
+//--- main.cpp
+import A;
+import B;
+
+int main() {
+  return a() + b();
+}
+
+//--- A.cppm
+export module A;
+export import :part1;
+import B;
+
+export int a() {
+  return part1() + b();
+}
+
+//--- A-part1.cppm
+export module A:part1;
+export int part1();
+
+//--- A-part1-impl.cpp
+module A:part1_impl;
+
+int part1() {
+  return 30;
+}
+
+//--- B.cppm
+export module B;
+
+export int b() {
+  return 12;
+}
diff --git a/clang/test/Driver/modules-driver-import-std.cpp b/clang/test/Driver/modules-driver-import-std.cpp
new file mode 100644
index 0000000000000..25dc059b73ec7
--- /dev/null
+++ b/clang/test/Driver/modules-driver-import-std.cpp
@@ -0,0 +1,60 @@
+// Checks that -fmodules-driver correctly handles the import of Standard library
+// modules.
+
+// The standard library modules manifest (libc++.modules.json) is discovered
+// relative to the installed C++ standard library runtime libraries
+// We need to create them in order for Clang to find the manifest.
+// RUN: rm -rf %t && split-file %s %t
+// RUN: mkdir -p %t/Inputs/usr/lib/x86_64-linux-gnu
+// RUN: touch %t/Inputs/usr/lib/x86_64-linux-gnu/libc++.so
+// RUN: touch %t/Inputs/usr/lib/x86_64-linux-gnu/libc++.a
+
+// RUN: sed "s|DIR|%/t|g" %t/libc++.modules.json.in > \
+// RUN:   %t/Inputs/usr/lib/x86_64-linux-gnu/libc++.modules.json
+
+// RUN: mkdir -p %t/Inputs/usr/lib/share/libc++/v1
+// RUN: cat %t/std.cppm > %t/Inputs/usr/lib/share/libc++/v1/std.cppm
+// RUN: cat %t/std.compat.cppm > %t/Inputs/usr/lib/share/libc++/v1/std.compat.cppm
+
+//--- libc++.modules.json.in
+{
+  "version": 1,
+  "revision": 1,
+  "modules": [
+    {
+      "logical-name": "std",
+      "source-path": "../share/libc++/v1/std.cppm",
+      "is-std-library": true
+    },
+    {
+      "logical-name": "std.compat",
+      "source-path": "../share/libc++/v1/std.compat.cppm",
+      "is-std-library": true
+    }
+  ]
+}
+
+//--- std.cppm
+export module std;
+
+//--- std.compat.cppm
+export module std.compat;
+import std;
+
+//--- main.cpp
+import std.compat;
+import std;
+
+int main() {}
+
+// RUN: %clang -std=c++23 -c -fmodules-driver -Rmodules-driver -Rmodule-import \
+// RUN:   -stdlib=libc++ \
+// RUN:   -resource-dir=%t/Inputs/usr/lib/x86_64-linux-gnu \
+// RUN:   --target=x86_64-linux-gnu \
+// RUN:   %t/main.cpp 2>&1 \
+// RUN:   | sed 's:\\\\\?:/:g' \
+// RUN:   | FileCheck -DPREFIX=%/t %s
+
+// CHECK: [[PREFIX]]/main.cpp:1:1: remark: importing module 'std.compat' from
+// CHECK: [[PREFIX]]/main.cpp:1:1: remark: importing module 'std' into 'std.compat' from
+// CHECK: [[PREFIX]]/main.cpp:2:1: remark: importing module 'std' from
diff --git a/clang/test/Driver/modules-driver-incompatible-options.cpp b/clang/test/Driver/modules-driver-incompatible-options.cpp
new file mode 100644
index 0000000000000..0d1cae94f8975
--- /dev/null
+++ b/clang/test/Driver/modules-driver-incompatible-options.cpp
@@ -0,0 +1,10 @@
+// Checks for diagnostics that report incompatibilities between
+// -fmodules-driver and other options.
+
+// RUN: split-file %s %t
+// RUN: not %clang -std=c++20 -fmodules-driver -fno-modules-reduced-bmi main.cpp -###
+
+// CHECK: clang: error: -fmodules-driver' is incompatible with '-fno-modules-reduced-bmi'
+
+//--- main.cpp
+int main() {}

>From 44ecead46091f911f2e00c6140eeefbf0b4122c8 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at oulook.com>
Date: Tue, 21 Apr 2026 23:27:39 +0200
Subject: [PATCH 2/4] Fix path creation for Windows

---
 clang/lib/Driver/ModulesDriver.cpp | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp
index f2b994720b411..7668af14fae9b 100644
--- a/clang/lib/Driver/ModulesDriver.cpp
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -1521,11 +1521,12 @@ static void createAndConnectRoot(CompilationGraph &Graph) {
 /// Creates a temporary output path for \p ModuleName.
 static std::string createModuleOutputPath(const Compilation &C,
                                           StringRef ModuleName) {
-  auto ModuleOutputPath = C.getDriver().GetTemporaryPath(
-      ModuleName, types::getTypeTempSuffix(types::TY_ModuleFile));
   // Sanitize the ':' included in parition names. It is illegal for filenames on
   // Windows.
-  llvm::replace(ModuleOutputPath, ':', '_');
+  SmallString<32> SanitizedModuleName(ModuleName);
+  llvm::replace(SanitizedModuleName, ':', '_');
+  auto ModuleOutputPath = C.getDriver().GetTemporaryPath(
+      SanitizedModuleName, types::getTypeTempSuffix(types::TY_ModuleFile));
   return ModuleOutputPath;
 }
 

>From 5bc7e633f2ccdfad4b5a0bc8214c88df3ec06939 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at oulook.com>
Date: Wed, 22 Apr 2026 00:49:14 +0200
Subject: [PATCH 3/4] Improve wording

---
 clang/lib/Driver/ModulesDriver.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp
index 7668af14fae9b..692debb6dbb3c 100644
--- a/clang/lib/Driver/ModulesDriver.cpp
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -1549,11 +1549,11 @@ static void configureNamedModuleOutputArgs(Compilation &C,
   Job.replaceArguments(std::move(JobArgs));
 }
 
-/// Propagates -fmodule-file mappings to dependent jobs of the named module
-/// described by \p Node.
-static void propagateModuleFileMappingArgs(Compilation &C,
-                                           NamedModuleJobNode &Node,
-                                           StringRef ModuleOutputPath) {
+/// Propagates the -fmodule-file mapping for the named module described by \p
+/// Node to each dependent job.
+static void propagateModuleFileMappingArg(Compilation &C,
+                                          NamedModuleJobNode &Node,
+                                          StringRef ModuleOutputPath) {
   const StringRef ModuleName = Node.InputDeps.ModuleName;
 
   auto DependentNodes = llvm::drop_begin(llvm::depth_first<CGNode *>(&Node));
@@ -1588,7 +1588,7 @@ static void fixupNamedModuleCommandLines(Compilation &C,
     C.addTempFile(C.getArgs().MakeArgString(ModuleOutputPath));
 
     configureNamedModuleOutputArgs(C, *Node, ModuleOutputPath);
-    propagateModuleFileMappingArgs(C, *Node, ModuleOutputPath);
+    propagateModuleFileMappingArg(C, *Node, ModuleOutputPath);
   }
 }
 

>From 781a985145e34b1a14ddbf7df6d6afd4cd54e582 Mon Sep 17 00:00:00 2001
From: Naveen Seth Hanig <naveen.hanig at oulook.com>
Date: Wed, 22 Apr 2026 10:49:33 +0200
Subject: [PATCH 4/4] Use "-" as seperator and fix diagnostic

---
 clang/include/clang/Basic/DiagnosticDriverKinds.td        | 2 +-
 clang/lib/Driver/ModulesDriver.cpp                        | 2 +-
 clang/test/Driver/modules-driver-incompatible-options.cpp | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index 724b639c4889f..85dc4bc9f8fd7 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -614,7 +614,7 @@ def err_drv_reduced_module_output_overrided : Warning<
   InGroup<DiagGroup<"reduced-bmi-output-overrided">>;
 
 def err_drv_modules_driver_requires_reduced_bmi : Error<
-  "-fmodules-driver' is incompatible with '-fno-modules-reduced-bmi">;
+  "'-fmodules-driver' is currently incompatible with '-fno-modules-reduced-bmi'">;
 def remark_performing_driver_managed_module_build : Remark<
   "performing driver managed module build">, InGroup<ModulesDriver>;
 def remark_modules_manifest_not_found : Remark<
diff --git a/clang/lib/Driver/ModulesDriver.cpp b/clang/lib/Driver/ModulesDriver.cpp
index 692debb6dbb3c..a44f297c556e9 100644
--- a/clang/lib/Driver/ModulesDriver.cpp
+++ b/clang/lib/Driver/ModulesDriver.cpp
@@ -1524,7 +1524,7 @@ static std::string createModuleOutputPath(const Compilation &C,
   // Sanitize the ':' included in parition names. It is illegal for filenames on
   // Windows.
   SmallString<32> SanitizedModuleName(ModuleName);
-  llvm::replace(SanitizedModuleName, ':', '_');
+  llvm::replace(SanitizedModuleName, ':', '-');
   auto ModuleOutputPath = C.getDriver().GetTemporaryPath(
       SanitizedModuleName, types::getTypeTempSuffix(types::TY_ModuleFile));
   return ModuleOutputPath;
diff --git a/clang/test/Driver/modules-driver-incompatible-options.cpp b/clang/test/Driver/modules-driver-incompatible-options.cpp
index 0d1cae94f8975..88de9a6eac0e9 100644
--- a/clang/test/Driver/modules-driver-incompatible-options.cpp
+++ b/clang/test/Driver/modules-driver-incompatible-options.cpp
@@ -4,7 +4,7 @@
 // RUN: split-file %s %t
 // RUN: not %clang -std=c++20 -fmodules-driver -fno-modules-reduced-bmi main.cpp -###
 
-// CHECK: clang: error: -fmodules-driver' is incompatible with '-fno-modules-reduced-bmi'
+// CHECK: clang: error: '-fmodules-driver' is currently incompatible with '-fno-modules-reduced-bmi'
 
 //--- main.cpp
 int main() {}



More information about the cfe-commits mailing list