[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