[clang] [libformat] Implement `TemplateTypeParameterKeyword` fixer (PR #192223)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 15 02:51:22 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-format
Author: David Bayer (davebayer)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/192223.diff
6 Files Affected:
- (modified) clang/include/clang/Format/Format.h (+33)
- (modified) clang/lib/Format/CMakeLists.txt (+1)
- (modified) clang/lib/Format/Format.cpp (+20)
- (added) clang/lib/Format/TemplateTypeParameterKeywordFixer.cpp (+158)
- (added) clang/lib/Format/TemplateTypeParameterKeywordFixer.h (+37)
- (modified) clang/unittests/Format/FormatTest.cpp (+74)
``````````diff
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>>>();");
``````````
</details>
https://github.com/llvm/llvm-project/pull/192223
More information about the cfe-commits
mailing list