[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