https://github.com/tshakalekholoane updated https://github.com/llvm/llvm-project/pull/210788
>From 09fc8e10504160942c80cbf2fb6fea7d8d1e6929 Mon Sep 17 00:00:00 2001 From: Tshaka Lekholoane <[email protected]> Date: Mon, 20 Jul 2026 16:08:43 +0200 Subject: [PATCH 1/3] [clang-format] Add Natural option for SortIncludes SortIncludes currently orders includes lexicographically with the option to ignore case or extension. This adds another option, Natural, that compares embedded runs of digits as numbers rather than sequences of characters, matching the "natural sort" behaviour found in most file managers and tools like sort when called with the -V option. --- clang/docs/ClangFormatStyleOptions.rst | 10 ++++++ clang/include/clang/Format/Format.h | 11 ++++++- clang/lib/Format/Format.cpp | 35 ++++++++++++++++++--- clang/unittests/Format/ConfigParseTest.cpp | 28 ++++++++++------- clang/unittests/Format/SortIncludesTest.cpp | 34 ++++++++++++++++++++ 5 files changed, 101 insertions(+), 17 deletions(-) diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst index 7b1b7a7384b07..7c5f42b974fb7 100644 --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -6821,6 +6821,16 @@ the configuration (without a prefix: ``Auto``). # include "A.inc" # include "A.h" # include "A-util.h" # include "A.inc" + * ``bool Natural`` Whether or not includes are sorted by natural ordering i.e., whether + embedded runs of digits are compared as numbers rather than sequences of + characters. + + .. code-block:: c++ + + true: false: + #include "A2.h" vs. #include "A10.h" + #include "A10.h" #include "A2.h" + .. _SortJavaStaticImport: diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h index 27b2d8f4a405b..0c8acd6c4dbbb 100644 --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -5074,9 +5074,18 @@ struct FormatStyle { /// # include "A-util.h" # include "A.inc" /// \endcode bool IgnoreExtension; + /// Whether or not includes are sorted by natural ordering i.e., whether + /// embedded runs of digits are compared as numbers rather than sequences of + /// characters. + /// \code + /// true: false: + /// #include "A2.h" vs. #include "A10.h" + /// #include "A10.h" #include "A2.h" + /// \endcode + bool Natural; bool operator==(const SortIncludesOptions &R) const { return Enabled == R.Enabled && IgnoreCase == R.IgnoreCase && - IgnoreExtension == R.IgnoreExtension; + IgnoreExtension == R.IgnoreExtension && Natural == R.Natural; } bool operator!=(const SortIncludesOptions &R) const { return !(*this == R); diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 037111d8e9e5d..8d07a17a3a7e9 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -844,24 +844,33 @@ template <> struct MappingTraits<FormatStyle::SortIncludesOptions> { IO.enumCase(Value, "CaseInsensitive", FormatStyle::SortIncludesOptions{/*Enabled=*/true, /*IgnoreCase=*/true, - /*IgnoreExtension=*/false}); + /*IgnoreExtension=*/false, + /*Natural=*/false}); IO.enumCase(Value, "CaseSensitive", FormatStyle::SortIncludesOptions{/*Enabled=*/true, /*IgnoreCase=*/false, - /*IgnoreExtension=*/false}); + /*IgnoreExtension=*/false, + /*Natural=*/false}); + IO.enumCase(Value, "Natural", + FormatStyle::SortIncludesOptions{/*Enabled=*/true, + /*IgnoreCase=*/false, + /*IgnoreExtension=*/false, + /*Natural=*/true}); // For backward compatibility. IO.enumCase(Value, "false", FormatStyle::SortIncludesOptions{}); IO.enumCase(Value, "true", FormatStyle::SortIncludesOptions{/*Enabled=*/true, /*IgnoreCase=*/false, - /*IgnoreExtension=*/false}); + /*IgnoreExtension=*/false, + /*Natural=*/false}); } static void mapping(IO &IO, FormatStyle::SortIncludesOptions &Value) { IO.mapOptional("Enabled", Value.Enabled); IO.mapOptional("IgnoreCase", Value.IgnoreCase); IO.mapOptional("IgnoreExtension", Value.IgnoreExtension); + IO.mapOptional("Natural", Value.Natural); } }; @@ -1994,7 +2003,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind Language) { LLVMStyle.ShortNamespaceLines = 1; LLVMStyle.SkipMacroDefinitionBody = false; LLVMStyle.SortIncludes = {/*Enabled=*/true, /*IgnoreCase=*/false, - /*IgnoreExtension=*/false}; + /*IgnoreExtension=*/false, /*Natural=*/false}; LLVMStyle.SortJavaStaticImport = FormatStyle::SJSIO_Before; LLVMStyle.SortUsingDeclarations = FormatStyle::SUD_LexicographicNumeric; LLVMStyle.SpaceAfterCStyleCast = false; @@ -3647,6 +3656,24 @@ static void sortCppIncludes(const FormatStyle &Style, LHSFilenameLower = Includes[LHSI].Filename.lower(); RHSFilenameLower = Includes[RHSI].Filename.lower(); } + if (Style.SortIncludes.Natural) { + if (Includes[LHSI].Priority != Includes[RHSI].Priority) + return Includes[LHSI].Priority < Includes[RHSI].Priority; + if (Style.SortIncludes.IgnoreCase) { + if (int Cmp = StringRef(LHSStemLower).compare_numeric(RHSStemLower)) + return Cmp < 0; + } + if (int Cmp = StringRef(LHSStem).compare_numeric(RHSStem)) + return Cmp < 0; + if (Style.SortIncludes.IgnoreCase) { + if (int Cmp = StringRef(LHSFilenameLower) + .compare_numeric(RHSFilenameLower)) { + return Cmp < 0; + } + } + return StringRef(Includes[LHSI].Filename) + .compare_numeric(Includes[RHSI].Filename) < 0; + } return std::tie(Includes[LHSI].Priority, LHSStemLower, LHSStem, LHSFilenameLower, Includes[LHSI].Filename) < std::tie(Includes[RHSI].Priority, RHSStemLower, RHSStem, diff --git a/clang/unittests/Format/ConfigParseTest.cpp b/clang/unittests/Format/ConfigParseTest.cpp index 205cc9bcb6d31..51e59345325a9 100644 --- a/clang/unittests/Format/ConfigParseTest.cpp +++ b/clang/unittests/Format/ConfigParseTest.cpp @@ -1167,20 +1167,24 @@ TEST(ConfigParseTest, ParsesConfiguration) { IncludeStyle.IncludeIsMainSourceRegex, "abc$"); Style.SortIncludes = {}; - CHECK_PARSE( - "SortIncludes: true", SortIncludes, - FormatStyle::SortIncludesOptions( - {/*Enabled=*/true, /*IgnoreCase=*/false, /*IgnoreExtension=*/false})); + CHECK_PARSE("SortIncludes: true", SortIncludes, + FormatStyle::SortIncludesOptions( + {/*Enabled=*/true, /*IgnoreCase=*/false, + /*IgnoreExtension=*/false, /*Natural=*/false})); CHECK_PARSE("SortIncludes: false", SortIncludes, FormatStyle::SortIncludesOptions{}); - CHECK_PARSE( - "SortIncludes: CaseInsensitive", SortIncludes, - FormatStyle::SortIncludesOptions( - {/*Enabled=*/true, /*IgnoreCase=*/true, /*IgnoreExtension=*/false})); - CHECK_PARSE( - "SortIncludes: CaseSensitive", SortIncludes, - FormatStyle::SortIncludesOptions( - {/*Enabled=*/true, /*IgnoreCase=*/false, /*IgnoreExtension=*/false})); + CHECK_PARSE("SortIncludes: CaseInsensitive", SortIncludes, + FormatStyle::SortIncludesOptions( + {/*Enabled=*/true, /*IgnoreCase=*/true, + /*IgnoreExtension=*/false, /*Natural=*/false})); + CHECK_PARSE("SortIncludes: CaseSensitive", SortIncludes, + FormatStyle::SortIncludesOptions( + {/*Enabled=*/true, /*IgnoreCase=*/false, + /*IgnoreExtension=*/false, /*Natural=*/false})); + CHECK_PARSE("SortIncludes: Natural", SortIncludes, + FormatStyle::SortIncludesOptions( + {/*Enabled=*/true, /*IgnoreCase=*/false, + /*IgnoreExtension=*/false, /*Natural=*/true})); CHECK_PARSE("SortIncludes: Never", SortIncludes, FormatStyle::SortIncludesOptions{}); diff --git a/clang/unittests/Format/SortIncludesTest.cpp b/clang/unittests/Format/SortIncludesTest.cpp index 48ecd5d32d034..a6e9e18496f8d 100644 --- a/clang/unittests/Format/SortIncludesTest.cpp +++ b/clang/unittests/Format/SortIncludesTest.cpp @@ -668,6 +668,40 @@ TEST_F(SortIncludesTest, SupportOptionalCaseSensitiveSorting) { sort(UnsortedCode)); } +TEST_F(SortIncludesTest, SupportNaturalSorting) { + FmtStyle.SortIncludes.Natural = true; + verifyFormat("#include \"crypto/chacha8.h\"\n" + "#include \"crypto/chacha12.h\"\n" + "#include \"crypto/chacha20.h\"", + sort("#include \"crypto/chacha12.h\"\n" + "#include \"crypto/chacha8.h\"\n" + "#include \"crypto/chacha20.h\"")); +} + +TEST_F(SortIncludesTest, SupportNaturalSortingWithIgnoreCase) { + FmtStyle.SortIncludes.Natural = true; + FmtStyle.SortIncludes.IgnoreCase = true; + + verifyFormat("#include \"crypto/chacha8.h\"\n" + "#include \"Crypto/ChaCha12.h\"\n" + "#include \"crypto/chacha20.h\"", + sort("#include \"Crypto/ChaCha12.h\"\n" + "#include \"crypto/chacha8.h\"\n" + "#include \"crypto/chacha20.h\"")); +} + +TEST_F(SortIncludesTest, SupportNaturalSortingWithIgnoreExtension) { + FmtStyle.SortIncludes.Natural = true; + FmtStyle.SortIncludes.IgnoreExtension = true; + + verifyFormat("#include \"crypto/chacha8.c\"\n" + "#include \"crypto/chacha12.h\"\n" + "#include \"crypto/chacha20.h\"", + sort("#include \"crypto/chacha12.h\"\n" + "#include \"crypto/chacha20.h\"\n" + "#include \"crypto/chacha8.c\"")); +} + TEST_F(SortIncludesTest, SupportCaseInsensitiveMatching) { // Setup an regex for main includes so we can cover those as well. Style.IncludeIsMainRegex = "([-_](test|unittest))?$"; >From 2a2df408d1b43b695a8bc7d0ce9ae5813ab45067 Mon Sep 17 00:00:00 2001 From: Tshaka Lekholoane <[email protected]> Date: Sat, 25 Jul 2026 12:07:35 +0200 Subject: [PATCH 2/3] [clang-format] Remove duplicate logic in SortIncludes sort comparisons --- clang/lib/Format/Format.cpp | 41 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index 8d07a17a3a7e9..f10481dcc94fd 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -3641,6 +3641,9 @@ static void sortCppIncludes(const FormatStyle &Style, if (Style.SortIncludes.Enabled) { stable_sort(Indices, [&](unsigned LHSI, unsigned RHSI) { + if (Includes[LHSI].Priority != Includes[RHSI].Priority) + return Includes[LHSI].Priority < Includes[RHSI].Priority; + SmallString<128> LHSStem, RHSStem; if (Style.SortIncludes.IgnoreExtension) { LHSStem = Includes[LHSI].Filename; @@ -3648,6 +3651,7 @@ static void sortCppIncludes(const FormatStyle &Style, llvm::sys::path::replace_extension(LHSStem, ""); llvm::sys::path::replace_extension(RHSStem, ""); } + std::string LHSStemLower, RHSStemLower; std::string LHSFilenameLower, RHSFilenameLower; if (Style.SortIncludes.IgnoreCase) { @@ -3656,28 +3660,25 @@ static void sortCppIncludes(const FormatStyle &Style, LHSFilenameLower = Includes[LHSI].Filename.lower(); RHSFilenameLower = Includes[RHSI].Filename.lower(); } - if (Style.SortIncludes.Natural) { - if (Includes[LHSI].Priority != Includes[RHSI].Priority) - return Includes[LHSI].Priority < Includes[RHSI].Priority; - if (Style.SortIncludes.IgnoreCase) { - if (int Cmp = StringRef(LHSStemLower).compare_numeric(RHSStemLower)) - return Cmp < 0; - } - if (int Cmp = StringRef(LHSStem).compare_numeric(RHSStem)) + + const auto Compare = Style.SortIncludes.Natural + ? &StringRef::compare_numeric + : &StringRef::compare; + + if (Style.SortIncludes.IgnoreCase) { + if (int Cmp = (StringRef(LHSStemLower).*Compare)(RHSStemLower)) return Cmp < 0; - if (Style.SortIncludes.IgnoreCase) { - if (int Cmp = StringRef(LHSFilenameLower) - .compare_numeric(RHSFilenameLower)) { - return Cmp < 0; - } - } - return StringRef(Includes[LHSI].Filename) - .compare_numeric(Includes[RHSI].Filename) < 0; } - return std::tie(Includes[LHSI].Priority, LHSStemLower, LHSStem, - LHSFilenameLower, Includes[LHSI].Filename) < - std::tie(Includes[RHSI].Priority, RHSStemLower, RHSStem, - RHSFilenameLower, Includes[RHSI].Filename); + + if (int Cmp = (StringRef(LHSStem).*Compare)(RHSStem)) + return Cmp < 0; + + if (Style.SortIncludes.IgnoreCase) { + if (int Cmp = (StringRef(LHSFilenameLower).*Compare)(RHSFilenameLower)) + return Cmp < 0; + } + + return (Includes[LHSI].Filename.*Compare)(Includes[RHSI].Filename) < 0; }); } >From d4373b7886007d4a243dd2be33bf445c8c5a71fc Mon Sep 17 00:00:00 2001 From: Tshaka Lekholoane <[email protected]> Date: Mon, 27 Jul 2026 13:40:33 +0200 Subject: [PATCH 3/3] [clang-format] Avoid comparing empty stems and use invoke --- clang/lib/Format/Format.cpp | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp index f10481dcc94fd..7d55c738a9ca3 100644 --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -25,6 +25,7 @@ #include "clang/Tooling/Inclusions/HeaderIncludes.h" #include "llvm/ADT/Sequence.h" #include "llvm/ADT/StringSet.h" +#include <functional> #include <limits> #define DEBUG_TYPE "format-formatter" @@ -3644,19 +3645,24 @@ static void sortCppIncludes(const FormatStyle &Style, if (Includes[LHSI].Priority != Includes[RHSI].Priority) return Includes[LHSI].Priority < Includes[RHSI].Priority; - SmallString<128> LHSStem, RHSStem; + auto LHSStem = Includes[LHSI].Filename; + auto RHSStem = Includes[RHSI].Filename; + + SmallString<128> LHSStemStorage, RHSStemStorage; if (Style.SortIncludes.IgnoreExtension) { - LHSStem = Includes[LHSI].Filename; - RHSStem = Includes[RHSI].Filename; - llvm::sys::path::replace_extension(LHSStem, ""); - llvm::sys::path::replace_extension(RHSStem, ""); + LHSStemStorage = Includes[LHSI].Filename; + RHSStemStorage = Includes[RHSI].Filename; + llvm::sys::path::replace_extension(LHSStemStorage, ""); + llvm::sys::path::replace_extension(RHSStemStorage, ""); + LHSStem = LHSStemStorage; + RHSStem = RHSStemStorage; } std::string LHSStemLower, RHSStemLower; std::string LHSFilenameLower, RHSFilenameLower; if (Style.SortIncludes.IgnoreCase) { - LHSStemLower = LHSStem.str().lower(); - RHSStemLower = RHSStem.str().lower(); + LHSStemLower = LHSStem.lower(); + RHSStemLower = RHSStem.lower(); LHSFilenameLower = Includes[LHSI].Filename.lower(); RHSFilenameLower = Includes[RHSI].Filename.lower(); } @@ -3666,19 +3672,22 @@ static void sortCppIncludes(const FormatStyle &Style, : &StringRef::compare; if (Style.SortIncludes.IgnoreCase) { - if (int Cmp = (StringRef(LHSStemLower).*Compare)(RHSStemLower)) + int Cmp = std::invoke(Compare, StringRef(LHSStemLower), RHSStemLower); + if (Cmp != 0) return Cmp < 0; } - if (int Cmp = (StringRef(LHSStem).*Compare)(RHSStem)) + if (int Cmp = std::invoke(Compare, LHSStem, RHSStem); Cmp != 0) return Cmp < 0; if (Style.SortIncludes.IgnoreCase) { - if (int Cmp = (StringRef(LHSFilenameLower).*Compare)(RHSFilenameLower)) + int Cmp = + std::invoke(Compare, StringRef(LHSFilenameLower), RHSFilenameLower); + if (Cmp != 0) return Cmp < 0; } - - return (Includes[LHSI].Filename.*Compare)(Includes[RHSI].Filename) < 0; + return std::invoke(Compare, Includes[LHSI].Filename, + Includes[RHSI].Filename) < 0; }); } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
