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
