[clang] [clang-format] Don't break module names (PR #193834)

Björn Schäpers via cfe-commits cfe-commits at lists.llvm.org
Sun May 3 05:14:25 PDT 2026


https://github.com/HazardyKnusperkeks updated https://github.com/llvm/llvm-project/pull/193834

>From 7daef8a0b83b592566da0d69e97b675d589d2756 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Sch=C3=A4pers?= <bjoern at hazardy.de>
Date: Thu, 23 Apr 2026 22:41:59 +0200
Subject: [PATCH 1/2] [clang-format] Don't break module names

And somewhat streamline module handling.

Fixes #193676
---
 clang/lib/Format/TokenAnnotator.cpp      | 13 +++++++++----
 clang/lib/Format/TokenAnnotator.h        | 19 +++++++++++++++++++
 clang/lib/Format/UnwrappedLineParser.cpp |  5 +----
 clang/unittests/Format/FormatTest.cpp    | 19 ++++++++++++++-----
 4 files changed, 43 insertions(+), 13 deletions(-)

diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index 898759cb8ea1b..ba11b655fdb1b 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -1392,10 +1392,8 @@ class AnnotatingParser {
         }
         break;
       }
-      if (Line.First->isOneOf(Keywords.kw_module, Keywords.kw_import) ||
-          Line.First->startsSequence(tok::kw_export, Keywords.kw_module) ||
-          Line.First->startsSequence(tok::kw_export, Keywords.kw_import)) {
-        Tok->setType(TT_ModulePartitionColon);
+      if (Line.isCppModuleLine(Keywords)) {
+        Tok->setFinalizedType(TT_ModulePartitionColon);
       } else if (Line.First->is(tok::kw_asm)) {
         Tok->setType(TT_InlineASMColon);
       } else if (Contexts.back().ColonIsDictLiteral || Style.isProto()) {
@@ -2006,6 +2004,13 @@ class AnnotatingParser {
         return Type;
     }
 
+    if (IsCpp && Line.isCppModuleLine(Keywords)) {
+      while (CurrentToken)
+        if (!consumeToken())
+          return LT_Invalid;
+      return LT_ImportStatement;
+    }
+
     // Directly allow to 'import <string-literal>' to support protocol buffer
     // definitions (github.com/google/protobuf) or missing "#" (either way we
     // should not break the line).
diff --git a/clang/lib/Format/TokenAnnotator.h b/clang/lib/Format/TokenAnnotator.h
index 597dd890ee990..c2a1f65f901e5 100644
--- a/clang/lib/Format/TokenAnnotator.h
+++ b/clang/lib/Format/TokenAnnotator.h
@@ -116,6 +116,25 @@ class AnnotatedLine {
     return First && First->is(tok::comment) && !First->getNextNonComment();
   }
 
+  bool isCppModuleLine(const AdditionalKeywords &Keywords) const {
+    if (!First)
+      return false;
+    if (startsWith(tok::kw_export, Keywords.kw_module) ||
+        startsWith(tok::kw_export, Keywords.kw_import)) {
+      return true;
+    }
+
+    if (First->isNoneOf(Keywords.kw_module, Keywords.kw_import))
+      return false;
+
+    // Pre C++20 code using import (or module) as identifier e.g. for a
+    // namespace.
+    if (auto Next = First->getNextNonComment())
+      return Next->isNoneOf(tok::coloncolon, tok::period, tok::star);
+
+    return false;
+  }
+
   /// \c true if this line starts with the given tokens in order, ignoring
   /// comments.
   template <typename... Ts> bool startsWith(Ts... Tokens) const {
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index c2b3da3def640..3393536ed8bba 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -1361,11 +1361,8 @@ bool UnwrappedLineParser::parseModuleImport() {
 
   nextToken();
   while (!eof()) {
-    if (FormatTok->is(tok::colon)) {
-      FormatTok->setFinalizedType(TT_ModulePartitionColon);
-    }
     // Handle import <foo/bar.h> as we would an include statement.
-    else if (FormatTok->is(tok::less)) {
+    if (FormatTok->is(tok::less)) {
       nextToken();
       while (FormatTok->isNoneOf(tok::semi, tok::greater) && !eof()) {
         // Mark tokens up to the trailing line comments as implicit string
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index 44c52034f83cb..b36cbd7cc579a 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -14072,11 +14072,6 @@ TEST_F(FormatTest, HandlesIncludeDirectives) {
   Style.AlwaysBreakBeforeMultilineStrings = true;
   Style.ColumnLimit = 0;
   verifyFormat("#import \"abc.h\"", Style);
-
-  // But 'import' might also be a regular C++ namespace.
-  verifyFormat("import::SomeFunction(aaaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
-               "                     aaaaaaaaaaaaaaaaaaaaaaaaaaaaa);");
-  verifyFormat("import::Bar foo(val ? 2 : 1);");
 }
 
 //===----------------------------------------------------------------------===//
@@ -24630,8 +24625,22 @@ TEST_F(FormatTest, Cpp20ModulesSupport) {
   verifyFormat("module", Style);
   verifyFormat("export", Style);
 
+  Style.ColumnLimit = 10;
+  verifyFormat("import Foo.Bar;", Style);
+  verifyFormat("export import Foo.Bar;", Style);
+  verifyFormat("export module Foo.Bar;", Style);
+  verifyFormat("import Foo.Bar:Baz;", Style);
+  verifyFormat("export import Foo.Bar:Baz;", Style);
+  verifyFormat("export module Foo.Bar:Baz;", Style);
+
+  // Somewhat gracefully handle import in pre C++20 code.
   verifyFormat("import /* not keyword */ = val ? 2 : 1;");
   verifyFormat("_world->import<engine_module>();");
+
+  // But 'import' might also be a regular C++ namespace.
+  verifyFormat("import::SomeFunction(aaaaaaaaaaaaaaaaaaaaaaaaaaa,\n"
+               "                     aaaaaaaaaaaaaaaaaaaaaaaaaaaaa);");
+  verifyFormat("import::Bar foo(val ? 2 : 1);");
 }
 
 TEST_F(FormatTest, CoroutineForCoawait) {

>From 05042e337a2767e1535d9b50d6feb6171fbe3ece Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Sch=C3=A4pers?= <bjoern at hazardy.de>
Date: Sun, 3 May 2026 14:14:10 +0200
Subject: [PATCH 2/2] Use new line type for cpp modules

---
 clang/lib/Format/TokenAnnotator.cpp         |  2 +-
 clang/lib/Format/TokenAnnotator.h           | 10 ++++--
 clang/lib/Format/UnwrappedLineFormatter.cpp |  3 +-
 clang/lib/Format/UnwrappedLineParser.cpp    | 39 +++++++++------------
 clang/unittests/Format/FormatTest.cpp       |  8 ++++-
 5 files changed, 34 insertions(+), 28 deletions(-)

diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp
index ba11b655fdb1b..aa4e6f4f67d75 100644
--- a/clang/lib/Format/TokenAnnotator.cpp
+++ b/clang/lib/Format/TokenAnnotator.cpp
@@ -2008,7 +2008,7 @@ class AnnotatingParser {
       while (CurrentToken)
         if (!consumeToken())
           return LT_Invalid;
-      return LT_ImportStatement;
+      return LT_CppModuleStatement;
     }
 
     // Directly allow to 'import <string-literal>' to support protocol buffer
diff --git a/clang/lib/Format/TokenAnnotator.h b/clang/lib/Format/TokenAnnotator.h
index c2a1f65f901e5..6eb346a54c906 100644
--- a/clang/lib/Format/TokenAnnotator.h
+++ b/clang/lib/Format/TokenAnnotator.h
@@ -35,6 +35,7 @@ enum LineType {
   LT_CommentAbovePPDirective,
   LT_RequiresExpression,
   LT_SimpleRequirement,
+  LT_CppModuleStatement,
 };
 
 enum ScopeType {
@@ -127,10 +128,13 @@ class AnnotatedLine {
     if (First->isNoneOf(Keywords.kw_module, Keywords.kw_import))
       return false;
 
-    // Pre C++20 code using import (or module) as identifier e.g. for a
+    // Pre-C++20 code using import (or module) as identifier e.g. for a
     // namespace.
-    if (auto Next = First->getNextNonComment())
-      return Next->isNoneOf(tok::coloncolon, tok::period, tok::star);
+    if (auto Next = First->getNextNonComment()) {
+      return !Next->isMemberAccess() &&
+             Next->isNoneOf(tok::coloncolon, tok::star) &&
+             !Next->isStringLiteral();
+    }
 
     return false;
   }
diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp
index 42eabc065b1a8..22d866723f4af 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -1563,11 +1563,12 @@ unsigned UnwrappedLineFormatter::format(
 
       NextLine = Joiner.getNextMergedLine(DryRun, IndentTracker);
       unsigned ColumnLimit = getColumnLimit(TheLine.InPPDirective, NextLine);
-      bool FitsIntoOneLine =
+      const bool FitsIntoOneLine =
           !TheLine.ContainsMacroCall &&
           (TheLine.Last->TotalLength + Indent <= ColumnLimit ||
            (TheLine.Type == LT_ImportStatement &&
             (!Style.isJavaScript() || !Style.JavaScriptWrapImports)) ||
+           TheLine.Type == LT_CppModuleStatement ||
            (Style.isCSharp() &&
             TheLine.InPPDirective)); // don't split #regions in C#
       if (Style.ColumnLimit == 0) {
diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp
index 3393536ed8bba..6ccc254c6214d 100644
--- a/clang/lib/Format/UnwrappedLineParser.cpp
+++ b/clang/lib/Format/UnwrappedLineParser.cpp
@@ -1359,26 +1359,21 @@ bool UnwrappedLineParser::parseModuleImport() {
     return false;
   }
 
-  nextToken();
-  while (!eof()) {
+  for (nextToken(); !eof(); nextToken()) {
     // Handle import <foo/bar.h> as we would an include statement.
     if (FormatTok->is(tok::less)) {
-      nextToken();
-      while (FormatTok->isNoneOf(tok::semi, tok::greater) && !eof()) {
-        // Mark tokens up to the trailing line comments as implicit string
-        // literals.
-        if (FormatTok->isNot(tok::comment) &&
-            !FormatTok->TokenText.starts_with("//")) {
-          FormatTok->setFinalizedType(TT_ImplicitStringLiteral);
-        }
-        nextToken();
+
+      for (nextToken(); FormatTok->isNoneOf(tok::semi, tok::greater) && !eof();
+           nextToken()) {
+        // Mark tokens as implicit string literals, so that import <A/Foo> will
+        // neither be broken nor have a space added.
+        FormatTok->setFinalizedType(TT_ImplicitStringLiteral);
       }
     }
     if (FormatTok->is(tok::semi)) {
       nextToken();
       break;
     }
-    nextToken();
   }
 
   addUnwrappedLine();
@@ -1623,14 +1618,6 @@ void UnwrappedLineParser::parseStructuralElement(
     }
     break;
   case tok::kw_export:
-    if (Style.isJavaScript()) {
-      parseJavaScriptEs6ImportExport();
-      return;
-    }
-    if (Style.isVerilog()) {
-      parseVerilogExtern();
-      return;
-    }
     if (IsCpp) {
       nextToken();
       if (FormatTok->is(tok::kw_namespace)) {
@@ -1644,6 +1631,14 @@ void UnwrappedLineParser::parseStructuralElement(
       if (FormatTok->is(Keywords.kw_import) && parseModuleImport())
         return;
     }
+    if (Style.isJavaScript()) {
+      parseJavaScriptEs6ImportExport();
+      return;
+    }
+    if (Style.isVerilog()) {
+      parseVerilogExtern();
+      return;
+    }
     break;
   case tok::kw_inline:
     nextToken();
@@ -1663,6 +1658,8 @@ void UnwrappedLineParser::parseStructuralElement(
       return;
     }
     if (FormatTok->is(Keywords.kw_import)) {
+      if (IsCpp && parseModuleImport())
+        return;
       if (Style.isJavaScript()) {
         parseJavaScriptEs6ImportExport();
         return;
@@ -1683,8 +1680,6 @@ void UnwrappedLineParser::parseStructuralElement(
         parseVerilogExtern();
         return;
       }
-      if (IsCpp && parseModuleImport())
-        return;
     }
     if (IsCpp && FormatTok->isOneOf(Keywords.kw_signals, Keywords.kw_qsignals,
                                     Keywords.kw_slots, Keywords.kw_qslots)) {
diff --git a/clang/unittests/Format/FormatTest.cpp b/clang/unittests/Format/FormatTest.cpp
index b36cbd7cc579a..50966a02a6632 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -24625,6 +24625,10 @@ TEST_F(FormatTest, Cpp20ModulesSupport) {
   verifyFormat("module", Style);
   verifyFormat("export", Style);
 
+  verifyFormat("module :private;", Style);
+  verifyFormat("import <Foo/Bar> /* comment */;", Style);
+  verifyFormat("import <Foo/Bar>; // Trailing comment", Style);
+
   Style.ColumnLimit = 10;
   verifyFormat("import Foo.Bar;", Style);
   verifyFormat("export import Foo.Bar;", Style);
@@ -24632,8 +24636,10 @@ TEST_F(FormatTest, Cpp20ModulesSupport) {
   verifyFormat("import Foo.Bar:Baz;", Style);
   verifyFormat("export import Foo.Bar:Baz;", Style);
   verifyFormat("export module Foo.Bar:Baz;", Style);
+  verifyFormat("import <Foo/Bar> /* comment */;", Style);
+  verifyFormat("import <Foo/Bar>; // Trailing comment", Style);
 
-  // Somewhat gracefully handle import in pre C++20 code.
+  // Somewhat gracefully handle import in pre-C++20 code.
   verifyFormat("import /* not keyword */ = val ? 2 : 1;");
   verifyFormat("_world->import<engine_module>();");
 



More information about the cfe-commits mailing list