https://github.com/davebayer created 
https://github.com/llvm/llvm-project/pull/192223

This PR introduces `TemplateTypeParameterKeyword` fixer for `clang-format`. The 
purpose of this PR is to allow projects to specify which keyword they want to 
use in `template <...>` - `class` or `typename`.

I have no previous experience with `clang-format`, I've used cursor to 
implement the functionality for me. I've tested it on my project and it seems 
to be working fine.

>From 0db9131ccd1906af0e58214b13d5a84c1584c5c9 Mon Sep 17 00:00:00 2001
From: David Bayer <[email protected]>
Date: Wed, 15 Apr 2026 11:45:36 +0200
Subject: [PATCH] [libformat] Implement `TemplateTypeParameterKeyword` fixer

---
 clang/include/clang/Format/Format.h           |  33 ++++
 clang/lib/Format/CMakeLists.txt               |   1 +
 clang/lib/Format/Format.cpp                   |  20 +++
 .../TemplateTypeParameterKeywordFixer.cpp     | 158 ++++++++++++++++++
 .../TemplateTypeParameterKeywordFixer.h       |  37 ++++
 clang/unittests/Format/FormatTest.cpp         |  74 ++++++++
 6 files changed, 323 insertions(+)
 create mode 100644 clang/lib/Format/TemplateTypeParameterKeywordFixer.cpp
 create mode 100644 clang/lib/Format/TemplateTypeParameterKeywordFixer.h

diff --git a/clang/include/clang/Format/Format.h 
b/clang/include/clang/Format/Format.h
index 48ce5aa2bdfa1..7e23ea576ef28 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -4085,6 +4085,38 @@ struct FormatStyle {
   /// \version 22
   NumericLiteralCaseStyle NumericLiteralCase;
 
+  /// Whether to rewrite ``typename`` / ``class`` when introducing a type
+  /// template parameter or a template template parameter in a
+  /// ``template <...>`` clause.
+  ///
+  /// \code
+  ///   Leave:        template <class T> void f();     // unchanged
+  ///   UseTypename:  template <typename T> void f();
+  ///   UseClass:     template <class T> void f();
+  /// \endcode
+  ///
+  /// This applies to template declarations such as ``template <class T>``,
+  /// ``template <template <class U> class C>``, and generic lambdas like
+  /// ``[]<class T>() {}``.
+  ///
+  /// \note
+  ///   The fixer only replaces a ``class`` or ``typename`` token when it can
+  ///   determine that the token introduces a template parameter name (not an
+  ///   elaborated type specifier such as ``template <class C::M *p>``).
+  /// \endnote
+  ///
+  /// \version 23
+  enum TemplateTypeParameterKeywordOption : int8_t {
+    /// Leave the source spelling unchanged.
+    TTPS_Leave,
+    /// Use ``typename`` for type and template template parameters.
+    TTPS_UseTypename,
+    /// Use ``class`` for the template type parameter keyword.
+    TTPS_UseClass,
+  };
+  /// \see TemplateTypeParameterKeywordOption
+  TemplateTypeParameterKeywordOption TemplateTypeParameterKeyword;
+
   /// Controls bin-packing Objective-C protocol conformance list
   /// items into as few lines as possible when they go over ``ColumnLimit``.
   ///
@@ -6034,6 +6066,7 @@ struct FormatStyle {
            NamespaceIndentation == R.NamespaceIndentation &&
            NamespaceMacros == R.NamespaceMacros &&
            NumericLiteralCase == R.NumericLiteralCase &&
+           TemplateTypeParameterKeyword == R.TemplateTypeParameterKeyword &&
            ObjCBinPackProtocolList == R.ObjCBinPackProtocolList &&
            ObjCBlockIndentWidth == R.ObjCBlockIndentWidth &&
            ObjCBreakBeforeNestedBlockParam ==
diff --git a/clang/lib/Format/CMakeLists.txt b/clang/lib/Format/CMakeLists.txt
index 50c0683dc9b7f..7ba852aaf8027 100644
--- a/clang/lib/Format/CMakeLists.txt
+++ b/clang/lib/Format/CMakeLists.txt
@@ -18,6 +18,7 @@ add_clang_library(clangFormat
   ObjCPropertyAttributeOrderFixer.cpp
   QualifierAlignmentFixer.cpp
   SortJavaScriptImports.cpp
+  TemplateTypeParameterKeywordFixer.cpp
   TokenAnalyzer.cpp
   TokenAnnotator.cpp
   UnwrappedLineFormatter.cpp
diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp
index 48e139ea9d058..de61695da96de 100644
--- a/clang/lib/Format/Format.cpp
+++ b/clang/lib/Format/Format.cpp
@@ -17,6 +17,7 @@
 #include "IntegerLiteralSeparatorFixer.h"
 #include "NamespaceEndCommentsFixer.h"
 #include "NumericLiteralCaseFixer.h"
+#include "TemplateTypeParameterKeywordFixer.h"
 #include "ObjCPropertyAttributeOrderFixer.h"
 #include "QualifierAlignmentFixer.h"
 #include "SortJavaScriptImports.h"
@@ -490,6 +491,16 @@ template <> struct 
ScalarEnumerationTraits<FormatStyle::JavaScriptQuoteStyle> {
   }
 };
 
+template <>
+struct 
ScalarEnumerationTraits<FormatStyle::TemplateTypeParameterKeywordOption> {
+  static void enumeration(IO &IO,
+                          FormatStyle::TemplateTypeParameterKeywordOption 
&Value) {
+    IO.enumCase(Value, "Leave", FormatStyle::TTPS_Leave);
+    IO.enumCase(Value, "UseTypename", FormatStyle::TTPS_UseTypename);
+    IO.enumCase(Value, "UseClass", FormatStyle::TTPS_UseClass);
+  }
+};
+
 template <> struct MappingTraits<FormatStyle::KeepEmptyLinesStyle> {
   static void mapping(IO &IO, FormatStyle::KeepEmptyLinesStyle &Value) {
     IO.mapOptional("AtEndOfFile", Value.AtEndOfFile);
@@ -1429,6 +1440,8 @@ template <> struct MappingTraits<FormatStyle> {
     IO.mapOptional("TableGenBreakInsideDAGArg",
                    Style.TableGenBreakInsideDAGArg);
     IO.mapOptional("TabWidth", Style.TabWidth);
+    IO.mapOptional("TemplateTypeParameterKeyword",
+                   Style.TemplateTypeParameterKeyword);
     IO.mapOptional("TemplateNames", Style.TemplateNames);
     IO.mapOptional("TypeNames", Style.TypeNames);
     IO.mapOptional("TypenameMacros", Style.TypenameMacros);
@@ -1886,6 +1899,7 @@ FormatStyle getLLVMStyle(FormatStyle::LanguageKind 
Language) {
                                   /*HexDigit=*/FormatStyle::NLCS_Leave,
                                   /*Prefix=*/FormatStyle::NLCS_Leave,
                                   /*Suffix=*/FormatStyle::NLCS_Leave};
+  LLVMStyle.TemplateTypeParameterKeyword = FormatStyle::TTPS_Leave;
   LLVMStyle.ObjCBinPackProtocolList = FormatStyle::BPS_Auto;
   LLVMStyle.ObjCBlockIndentWidth = 2;
   LLVMStyle.ObjCBreakBeforeNestedBlockParam = true;
@@ -4185,6 +4199,12 @@ reformat(const FormatStyle &Style, StringRef Code,
   });
 
   if (Style.isCpp()) {
+    if (Style.TemplateTypeParameterKeyword != FormatStyle::TTPS_Leave) {
+      Passes.emplace_back([&](const Environment &Env) {
+        return TemplateTypeParameterKeywordFixer(Env, Expanded).process();
+      });
+    }
+
     if (Style.QualifierAlignment != FormatStyle::QAS_Leave)
       addQualifierAlignmentFixerPasses(Expanded, Passes);
 
diff --git a/clang/lib/Format/TemplateTypeParameterKeywordFixer.cpp 
b/clang/lib/Format/TemplateTypeParameterKeywordFixer.cpp
new file mode 100644
index 0000000000000..2175d8f2f9e01
--- /dev/null
+++ b/clang/lib/Format/TemplateTypeParameterKeywordFixer.cpp
@@ -0,0 +1,158 @@
+//===--- TemplateTypeParameterKeywordFixer.cpp ------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file implements TemplateTypeParameterKeywordFixer, a TokenAnalyzer
+/// that rewrites \c typename and \c class when they introduce type template
+/// parameters or template template parameters, according to
+/// \c FormatStyle::TemplateTypeParameterKeyword.
+///
+//===----------------------------------------------------------------------===//
+
+#include "TemplateTypeParameterKeywordFixer.h"
+#include "FormatToken.h"
+#include "llvm/Support/Error.h"
+
+using llvm::cantFail;
+
+namespace clang {
+namespace format {
+namespace {
+
+bool angleIntroducesTemplateParameterList(const FormatToken *LT) {
+  const FormatToken *Prev = LT->getPreviousNonComment();
+  if (!Prev)
+    return false;
+  if (Prev->is(tok::kw_template))
+    return true;
+  // Generic lambda
+  return Prev->is(tok::r_square);
+}
+
+bool isStrictlyBetween(const SourceManager &SM, SourceLocation X,
+                       SourceLocation Low, SourceLocation High) {
+  return SM.isBeforeInTranslationUnit(Low, X) &&
+         SM.isBeforeInTranslationUnit(X, High);
+}
+
+bool isInsideMatchingAngleRange(const FormatToken *Kw,
+                                const SourceManager &SM) {
+  SourceLocation KwLoc = Kw->getStartOfNonWhitespace();
+  for (const FormatToken *T = Kw->getPreviousNonComment(); T;
+       T = T->getPreviousNonComment()) {
+    if (!T->is(tok::less))
+      continue;
+    if (!angleIntroducesTemplateParameterList(T))
+      continue;
+    if (!T->MatchingParen)
+      continue;
+    SourceLocation OpenLoc = T->getStartOfNonWhitespace();
+    SourceLocation CloseLoc = T->MatchingParen->getStartOfNonWhitespace();
+    if (isStrictlyBetween(SM, KwLoc, OpenLoc, CloseLoc))
+      return true;
+  }
+  return false;
+}
+
+bool introducesTypeOrTemplateTemplateParameterName(const FormatToken *Kw) {
+  const FormatToken *N = Kw->getNextNonComment();
+  if (!N)
+    return false;
+  if (N->is(tok::ellipsis))
+    N = N->getNextNonComment();
+  if (!N)
+    return false;
+  if (N->isOneOf(tok::comma, tok::greater, tok::equal, tok::colon,
+                  tok::kw_requires))
+    return true;
+  if (!N->Tok.getIdentifierInfo())
+    return false;
+  const FormatToken *AfterName = N->getNextNonComment();
+  return !AfterName || !AfterName->is(tok::coloncolon);
+}
+
+bool prevIsTemplateParameterDelimiter(const FormatToken *Prev) {
+  return Prev && Prev->isOneOf(tok::less, tok::comma, tok::greater);
+}
+
+llvm::StringRef 
replacementKeyword(FormatStyle::TemplateTypeParameterKeywordOption O) {
+  switch (O) {
+  case FormatStyle::TTPS_UseTypename:
+    return "typename";
+  case FormatStyle::TTPS_UseClass:
+    return "class";
+  case FormatStyle::TTPS_Leave:
+    break;
+  }
+  return {};
+}
+
+void processLine(AnnotatedLine *Line, const SourceManager &SM,
+                 AffectedRangeManager &AffectedRangeMgr,
+                 FormatStyle::TemplateTypeParameterKeywordOption Opt,
+                 tooling::Replacements *Fixes) {
+  if (!Line->Affected || Line->InPPDirective || Line->InMacroBody)
+    return;
+
+  for (FormatToken *Tok = Line->First; Tok; Tok = Tok->Next) {
+    if (Tok->Finalized)
+      continue;
+    if (!Tok->isOneOf(tok::kw_typename, tok::kw_class))
+      continue;
+
+    const FormatToken *Prev = Tok->getPreviousNonComment();
+    if (!prevIsTemplateParameterDelimiter(Prev))
+      continue;
+    if (!isInsideMatchingAngleRange(Tok, SM))
+      continue;
+    if (!introducesTypeOrTemplateTemplateParameterName(Tok))
+      continue;
+
+    llvm::StringRef NewText = replacementKeyword(Opt);
+    if (NewText.empty() || NewText == Tok->TokenText)
+      continue;
+
+    SourceLocation Loc = Tok->Tok.getLocation();
+    unsigned Length = Tok->TokenText.size();
+    if (!AffectedRangeMgr.affectsCharSourceRange(
+            CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length))))
+      continue;
+
+    cantFail(Fixes->add(tooling::Replacement(SM, Loc, Length, NewText.str())));
+  }
+
+  for (AnnotatedLine *Child : Line->Children)
+    processLine(Child, SM, AffectedRangeMgr, Opt, Fixes);
+}
+
+} // namespace
+
+TemplateTypeParameterKeywordFixer::TemplateTypeParameterKeywordFixer(
+    const Environment &Env, const FormatStyle &Style)
+    : TokenAnalyzer(Env, Style) {}
+
+std::pair<tooling::Replacements, unsigned>
+TemplateTypeParameterKeywordFixer::analyze(
+    TokenAnnotator &, SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
+    FormatTokenLexer &) {
+  if (Style.TemplateTypeParameterKeyword == FormatStyle::TTPS_Leave)
+    return {};
+
+  AffectedRangeMgr.computeAffectedLines(AnnotatedLines);
+  tooling::Replacements Fixes;
+  const SourceManager &SM = Env.getSourceManager();
+
+  for (AnnotatedLine *Line : AnnotatedLines)
+    processLine(Line, SM, AffectedRangeMgr, Style.TemplateTypeParameterKeyword,
+                &Fixes);
+
+  return {Fixes, 0};
+}
+
+} // namespace format
+} // namespace clang
diff --git a/clang/lib/Format/TemplateTypeParameterKeywordFixer.h 
b/clang/lib/Format/TemplateTypeParameterKeywordFixer.h
new file mode 100644
index 0000000000000..169e7d598b551
--- /dev/null
+++ b/clang/lib/Format/TemplateTypeParameterKeywordFixer.h
@@ -0,0 +1,37 @@
+//===--- TemplateTypeParameterKeywordFixer.h --------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Rewrites ``typename`` / ``class`` introducing type and template template
+/// parameters in ``template <...>`` clauses according to FormatStyle.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_FORMAT_TEMPLATETYPEPARAMETERKEYWORDFIXER_H
+#define LLVM_CLANG_LIB_FORMAT_TEMPLATETYPEPARAMETERKEYWORDFIXER_H
+
+#include "TokenAnalyzer.h"
+
+namespace clang {
+namespace format {
+
+class TemplateTypeParameterKeywordFixer : public TokenAnalyzer {
+public:
+  TemplateTypeParameterKeywordFixer(const Environment &Env,
+                                    const FormatStyle &Style);
+
+  std::pair<tooling::Replacements, unsigned>
+  analyze(TokenAnnotator &Annotator,
+          SmallVectorImpl<AnnotatedLine *> &AnnotatedLines,
+          FormatTokenLexer &Tokens) override;
+};
+
+} // namespace format
+} // namespace clang
+
+#endif
diff --git a/clang/unittests/Format/FormatTest.cpp 
b/clang/unittests/Format/FormatTest.cpp
index 457695cc09dcc..ab90ed1bcd6c2 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -21925,6 +21925,80 @@ TEST_F(FormatTest, SpaceAfterTemplateKeyword) {
   verifyFormat("template<int> void foo();", Style);
 }
 
+TEST_F(FormatTest, TemplateTypeParameterKeyword) {
+  {
+    FormatStyle S = getLLVMStyle();
+    S.TemplateTypeParameterKeyword = FormatStyle::TTPS_Leave;
+    verifyFormat("template <class T> void f();", S);
+    verifyFormat("template <typename T> void f();", S);
+    verifyFormat("template <class A, class B> void g();", S);
+    verifyFormat("template <typename A, typename B> void g();", S);
+    verifyFormat("template <class A, class B, typename C, class D> void h();", 
S);
+    verifyFormat("template <typename A, typename B, class C, typename D> "
+                 "void h();",
+                 S);
+    verifyFormat("template <class A, int N, class B> void j();", S);
+    verifyFormat("template <typename A, int N, typename B> void j();", S);
+    verifyFormat("template <template <class U> class C> struct S;", S);
+    verifyFormat("template <template <typename U> typename C> struct S;", S);
+    verifyFormat("void h() {\n  []<class T>(T t) {}();\n}", S);
+    verifyFormat("void h() {\n  []<typename T>(T t) {}();\n}", S);
+    verifyFormat("template <class C::M *p> void nttp();", S);
+    verifyFormat("template <typename C::type V> void nttp();", S);
+    verifyFormat("template <template <typename T::type> class U> void f();", 
S);
+    verifyFormat("template <template <typename T::type> typename U> void f();",
+                 S);
+  }
+  {
+    FormatStyle S = getLLVMStyle();
+    S.TemplateTypeParameterKeyword = FormatStyle::TTPS_UseTypename;
+    verifyFormat("template <typename T> void f();",
+                 "template <class T> void f();", S);
+    verifyFormat("template <typename A, typename B> void g();",
+                 "template <class A, class B> void g();", S);
+    verifyFormat(
+        "template <typename A, typename B, class C, typename D> void h();",
+        "template <class A, class B, class C, class D> void h();", S);
+    verifyFormat("template <typename A, int N, typename B> void j();",
+                 "template <class A, int N, class B> void j();", S);
+    verifyFormat("template <template <typename U> typename C> struct S;",
+                 "template <template <class U> class C> struct S;", S);
+    verifyFormat("void h() {\n  []<typename T>(T t) {}();\n}",
+                 "void h() {\n  []<class T>(T t) {}();\n}", S);
+    verifyFormat("template <class C::M *p> void nttp();",
+                 "template <class C::M *p> void nttp();", S);
+    verifyFormat("template <typename C::type V> void nttp();",
+                 "template <typename C::type V> void nttp();", S);
+    verifyFormat("template <template <typename T::type> typename U> void f();",
+                 "template <template <typename T::type> class U> void f();", 
S);
+  }
+  {
+    FormatStyle S = getLLVMStyle();
+    S.TemplateTypeParameterKeyword = FormatStyle::TTPS_UseClass;
+    verifyFormat("template <class T> void f();",
+                 "template <typename T> void f();", S);
+    verifyFormat("template <class A, class B> void g();",
+                 "template <typename A, typename B> void g();", S);
+    verifyFormat("template <class A, class B, typename C, class D> void h();",
+                 "template <typename A, typename B, typename C, typename D> "
+                 "void h();",
+                 S);
+    verifyFormat("template <class A, int N, class B> void j();",
+                 "template <typename A, int N, typename B> void j();", S);
+    verifyFormat("template <template <class U> class C> struct S;",
+                 "template <template <typename U> typename C> struct S;", S);
+    verifyFormat("void h() {\n  []<class T>(T t) {}();\n}",
+                 "void h() {\n  []<typename T>(T t) {}();\n}", S);
+    verifyFormat("template <class C::M *p> void nttp();",
+                 "template <class C::M *p> void nttp();", S);
+    verifyFormat("template <typename C::type V> void nttp();",
+                 "template <typename C::type V> void nttp();", S);
+    verifyFormat("template <template <typename T::type> class U> void f();",
+                 "template <template <typename T::type> typename U> void f();",
+                 S);
+  }
+}
+
 TEST_F(FormatTest, TripleAngleBrackets) {
   verifyFormat("f<<<1, 1>>>();");
   verifyFormat("f<<<1, 1, 1, s>>>();");

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to