[clang] [libformat] Implement `TemplateTypeParameterKeyword` fixer (PR #192223)

David Bayer via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 16 10:43:37 PDT 2026


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

>From 30b5c00e4b68a7206e898dab520a72c6cb184ee7 Mon Sep 17 00:00:00 2001
From: David Bayer <bayer.david99 at gmail.com>
Date: Thu, 16 Apr 2026 19:43:21 +0200
Subject: [PATCH] [libformat] Implement `TemplateTypeParameterKeyword` fixer

---
 clang/include/clang/Format/Format.h           |  35 ++++
 clang/lib/Format/CMakeLists.txt               |   1 +
 clang/lib/Format/Format.cpp                   |  20 ++
 .../TemplateTypeParameterKeywordFixer.cpp     | 180 ++++++++++++++++++
 .../TemplateTypeParameterKeywordFixer.h       |  37 ++++
 clang/unittests/Format/FormatTest.cpp         |  93 +++++++++
 6 files changed, 366 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..a7221dfbfb091 100644
--- a/clang/include/clang/Format/Format.h
+++ b/clang/include/clang/Format/Format.h
@@ -4085,6 +4085,40 @@ struct FormatStyle {
   /// \version 22
   NumericLiteralCaseStyle NumericLiteralCase;
 
+  /// Whether to rewrite ``typename`` / ``class`` in template parameter lists.
+  ///
+  /// \code
+  ///   Leave:        template <class T, typename U> void f();     // unchanged
+  ///   UseTypename:  template <typename T, typename U> void f();
+  ///   UseClass:     template <class T, class U> 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>``).
+  ///
+  ///   For a template template parameter, ``typename`` is only valid in C++17
+  ///   and later. In earlier standards, ``UseTypename`` still emits ``class``
+  ///   for the template template parameter introducer.
+  /// \endnote
+  ///
+  /// \version 23
+  enum TemplateTypeParameterKeywordOption : int8_t {
+    /// Leave the source spelling unchanged.
+    TTPS_Leave,
+    /// Use ``typename``.
+    TTPS_UseTypename,
+    /// Use ``class``.
+    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 +6068,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..6a200dda48e32
--- /dev/null
+++ b/clang/lib/Format/TemplateTypeParameterKeywordFixer.cpp
@@ -0,0 +1,180 @@
+//===--- 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 "clang/Format/Format.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);
+}
+
+/// \c true when \p Prev is the closing ``>`` of a nested ``template <...>``
+/// prefix, so \p Kw introduces a template template parameter name (C++17
+/// allows ``typename`` there; before C++17 only ``class`` is permitted).
+bool isTemplateTemplateParameterIntroducer(const FormatToken *Prev) {
+  return Prev && Prev->is(tok::greater);
+}
+
+bool allowsTypenameTemplateTemplateIntroducer(const FormatStyle &Style) {
+  switch (Style.Standard) {
+  case FormatStyle::LS_Auto:
+  case FormatStyle::LS_Cpp11:
+  case FormatStyle::LS_Cpp14:
+    return false;
+  default:
+  return true;
+  }
+}
+
+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,
+                 const FormatStyle &Style,
+                 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;
+    if (isTemplateTemplateParameterIntroducer(Prev) && !allowsTypenameTemplateTemplateIntroducer(Style))
+      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, Style, 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,
+                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..60410d71c7a90 100644
--- a/clang/unittests/Format/FormatTest.cpp
+++ b/clang/unittests/Format/FormatTest.cpp
@@ -21925,6 +21925,99 @@ 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, typename 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, class 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);
+  }
+
+  // Verify that before C++14, the fixer doesn't produce template <template <class> typename C>.
+  {
+    FormatStyle S = getLLVMStyle();
+    S.Standard = FormatStyle::LS_Cpp14;
+    S.TemplateTypeParameterKeyword = FormatStyle::TTPS_UseTypename;
+    verifyFormat("template <template <typename U> class C> struct S;",
+                 "template <template <class U> class C> struct S;", S);
+    verifyFormat("template <template <typename U> class C> struct S;",
+                 "template <template <class U> typename C> struct S;", S);
+  }
+  {
+    FormatStyle S = getLLVMStyle();
+    S.Standard = FormatStyle::LS_Cpp17;
+    S.TemplateTypeParameterKeyword = FormatStyle::TTPS_UseTypename;
+    verifyFormat("template <template <typename U> typename C> struct S;",
+                 "template <template <class U> class C> struct S;", S);
+  }
+}
+
 TEST_F(FormatTest, TripleAngleBrackets) {
   verifyFormat("f<<<1, 1>>>();");
   verifyFormat("f<<<1, 1, 1, s>>>();");



More information about the cfe-commits mailing list