diff --git a/clang/docs/ClangFormatStyleOptions.rst b/clang/docs/ClangFormatStyleOptions.rst --- a/clang/docs/ClangFormatStyleOptions.rst +++ b/clang/docs/ClangFormatStyleOptions.rst @@ -3240,7 +3240,7 @@ Setting ``QualifierAlignment`` to something other than `Leave`, COULD lead to incorrect code formatting due to incorrect decisions made due to - clang-formats lack of complete semantic information. + clang-format's lack of complete semantic information. As such extra care should be taken to review code changes made by the use of this option. @@ -4000,6 +4000,45 @@ **TabWidth** (``Unsigned``) :versionbadge:`clang-format 3.7` The number of columns used for tab stops. +**TemplateArgumentKeyword** (``TemplateArgumentStyle``) :versionbadge:`clang-format 14` + Keyword to use for template arguments (``class`` or ``typename``) + + .. warning:: + + Setting ``TemplateArgumentKeyword`` to something other than `Leave`, + COULD lead to incorrect code formatting due to incorrect decisions made + due to clang-format's lack of complete semantic information. As such extra + care should be taken to review code changes made by the use of this + option. + + Possible values: + + * ``TAS_Leave`` (in configuration: ``Leave``) + Don't change `class` to `typename` or vice versa (default). + + .. code-block:: c++ + + template class Map; + template class Y> f() + + * ``TAS_Typename`` (in configuration: ``typename``) + Use `typename` instead of `class`. + + .. code-block:: c++ + + template class Map; + template typename Y> f() + + * ``TAS_Class`` (in configuration: ``class``) + Use `class` instead of `typename`. + + .. code-block:: c++ + + template class Map; + template class Y> f(); + + + **TypenameMacros** (``List of Strings``) :versionbadge:`clang-format 9` A vector of macros that should be interpreted as type declarations instead of as function calls. diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -294,6 +294,10 @@ `const` `volatile` `static` `inline` `constexpr` `restrict` to be controlled relative to the `type`. +- Option ``TemplateArgumentKeyword`` has been added to enforce consistent + usage of either the ``class`` or the ``typename`` keyword when declaring + template arguments. + - Add a ``Custom`` style to ``SpaceBeforeParens``, to better configure the space before parentheses. The custom options can be set using ``SpaceBeforeParensOptions``. diff --git a/clang/include/clang/Format/Format.h b/clang/include/clang/Format/Format.h --- a/clang/include/clang/Format/Format.h +++ b/clang/include/clang/Format/Format.h @@ -1900,7 +1900,7 @@ /// \warning /// Setting ``QualifierAlignment`` to something other than `Leave`, COULD /// lead to incorrect code formatting due to incorrect decisions made due to - /// clang-formats lack of complete semantic information. + /// clang-format's lack of complete semantic information. /// As such extra care should be taken to review code changes made by the use /// of this option. /// \endwarning @@ -3662,6 +3662,44 @@ /// \version 3.7 unsigned TabWidth; + /// Different template argument styles. + enum TemplateArgumentStyle { + /// Don't change `class` to `typename` or vice versa (default). + /// \code + /// template class Map; + /// template class Y> f() + /// \endcode + TAS_Leave, + /// Use `typename` instead of `class`. + /// \code + /// template class Map; + /// template typename Y> f() + /// \endcode + TAS_Typename, // typename + /// Use `class` instead of `typename`. + /// \code + /// template class Map; + /// template class Y> f(); + /// \endcode + TAS_Class // class + }; + + /// Keyword to use for template arguments (``class`` or ``typename``) + /// \warning + /// Setting ``TemplateArgumentKeyword`` to something other than `Leave`, + /// COULD lead to incorrect code formatting due to incorrect decisions made + /// due to clang-format's lack of complete semantic information. As such + /// extra care should be taken to review code changes made by the use of this + /// option. + /// \endwarning + /// \version 14 + TemplateArgumentStyle TemplateArgumentKeyword; + + /// \brief Use ``\r\n`` instead of ``\n`` for line breaks. + /// Also used as fallback if ``DeriveLineEnding`` is true. + /// \version 11 + bool UseCRLF; + /// Different ways to use tab in formatting. enum UseTabStyle : unsigned char { /// Never use tab. @@ -3679,11 +3717,6 @@ UT_Always }; - /// \brief Use ``\r\n`` instead of ``\n`` for line breaks. - /// Also used as fallback if ``DeriveLineEnding`` is true. - /// \version 11 - bool UseCRLF; - /// The way to use tab characters in the resulting file. /// \version 3.7 UseTabStyle UseTab; @@ -3826,6 +3859,7 @@ Standard == R.Standard && StatementAttributeLikeMacros == R.StatementAttributeLikeMacros && StatementMacros == R.StatementMacros && TabWidth == R.TabWidth && + TemplateArgumentKeyword == R.TemplateArgumentKeyword && UseTab == R.UseTab && UseCRLF == R.UseCRLF && TypenameMacros == R.TypenameMacros; } diff --git a/clang/lib/Format/CMakeLists.txt b/clang/lib/Format/CMakeLists.txt --- a/clang/lib/Format/CMakeLists.txt +++ b/clang/lib/Format/CMakeLists.txt @@ -13,6 +13,7 @@ SortJavaScriptImports.cpp TokenAnalyzer.cpp TokenAnnotator.cpp + TemplateArgumentKeywordFixer.cpp UnwrappedLineFormatter.cpp UnwrappedLineParser.cpp UsingDeclarationsSorter.cpp diff --git a/clang/lib/Format/Format.cpp b/clang/lib/Format/Format.cpp --- a/clang/lib/Format/Format.cpp +++ b/clang/lib/Format/Format.cpp @@ -21,6 +21,7 @@ #include "NamespaceEndCommentsFixer.h" #include "QualifierAlignmentFixer.h" #include "SortJavaScriptImports.h" +#include "TemplateArgumentKeywordFixer.h" #include "TokenAnalyzer.h" #include "TokenAnnotator.h" #include "UnwrappedLineFormatter.h" @@ -137,6 +138,14 @@ } }; +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, FormatStyle::TemplateArgumentStyle &Value) { + IO.enumCase(Value, "Leave", FormatStyle::TAS_Leave); + IO.enumCase(Value, "typename", FormatStyle::TAS_Typename); + IO.enumCase(Value, "class", FormatStyle::TAS_Class); + } +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &IO, FormatStyle::ShortFunctionStyle &Value) { IO.enumCase(Value, "None", FormatStyle::SFS_None); @@ -815,6 +824,7 @@ Style.StatementAttributeLikeMacros); IO.mapOptional("StatementMacros", Style.StatementMacros); IO.mapOptional("TabWidth", Style.TabWidth); + IO.mapOptional("TemplateArgumentKeyword", Style.TemplateArgumentKeyword); IO.mapOptional("TypenameMacros", Style.TypenameMacros); IO.mapOptional("UseCRLF", Style.UseCRLF); IO.mapOptional("UseTab", Style.UseTab); @@ -1224,6 +1234,7 @@ LLVMStyle.BitFieldColonSpacing = FormatStyle::BFCS_Both; LLVMStyle.SpacesInAngles = FormatStyle::SIAS_Never; LLVMStyle.SpacesInConditionalStatement = false; + LLVMStyle.TemplateArgumentKeyword = FormatStyle::TAS_Leave; LLVMStyle.PenaltyBreakAssignment = prec::Assignment; LLVMStyle.PenaltyBreakComment = 300; @@ -3038,6 +3049,13 @@ }); } + if (Style.isCpp() && + Style.TemplateArgumentKeyword != FormatStyle::TAS_Leave) { + Passes.emplace_back([&](const Environment &Env) { + return TemplateArgumentKeywordFixer(Env, Expanded).process(); + }); + } + if (Style.Language == FormatStyle::LK_Cpp) { if (Style.FixNamespaceComments) Passes.emplace_back([&](const Environment &Env) { diff --git a/clang/lib/Format/TemplateArgumentKeywordFixer.h b/clang/lib/Format/TemplateArgumentKeywordFixer.h new file mode 100644 --- /dev/null +++ b/clang/lib/Format/TemplateArgumentKeywordFixer.h @@ -0,0 +1,38 @@ +//===--- TemplateArgumentKeywordFixer.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 +/// This file declares TemplateArgumentKeywordFixer, a TokenAnalyzer that +/// enforces consistent usage of `typename` or `class` in template arguments. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_FORMAT_TEMPLATEARGUMENTKEYWORDFIXER_H +#define LLVM_CLANG_LIB_FORMAT_TEMPLATEARGUMENTKEYWORDFIXER_H + +#include "TokenAnalyzer.h" + +namespace clang { +namespace format { + +class TemplateArgumentKeywordFixer : public TokenAnalyzer { +public: + TemplateArgumentKeywordFixer(const Environment &Env, + const FormatStyle &Style); + + std::pair + analyze(TokenAnnotator &Annotator, + SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) override; +}; + +} // end namespace format +} // end namespace clang + +#endif diff --git a/clang/lib/Format/TemplateArgumentKeywordFixer.cpp b/clang/lib/Format/TemplateArgumentKeywordFixer.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/Format/TemplateArgumentKeywordFixer.cpp @@ -0,0 +1,123 @@ +//===--- TemplateArgumentKeywordFixer.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 TemplateArgumentKeywordFixer, a TokenAnalyzer that +/// enforces consistent usage of `typename` or `class` in template arguments. +/// +//===----------------------------------------------------------------------===// + +#include "TemplateArgumentKeywordFixer.h" + +namespace clang { +namespace format { + +TemplateArgumentKeywordFixer::TemplateArgumentKeywordFixer( + const Environment &Env, const FormatStyle &Style) + : TokenAnalyzer(Env, Style) {} + +static void replaceToken(const SourceManager &SourceMgr, + tooling::Replacements &Fixes, const FormatToken *Tok, + StringRef NewText) { + auto Range = CharSourceRange::getCharRange(Tok->getStartOfNonWhitespace(), + Tok->Tok.getEndLoc()); + auto Replacement = tooling::Replacement(SourceMgr, Range, NewText); + auto Err = Fixes.add(Replacement); + if (Err) + llvm::errs() << "Error while replacing template argument keyword: " + << llvm::toString(std::move(Err)) << "\n"; +} + +std::pair +TemplateArgumentKeywordFixer::analyze( + TokenAnnotator &Annotator, SmallVectorImpl &AnnotatedLines, + FormatTokenLexer &Tokens) { + const SourceManager &SourceMgr = Env.getSourceManager(); + AffectedRangeMgr.computeAffectedLines(AnnotatedLines); + tooling::Replacements Fixes; + + StringRef ReplacementStr; + tok::TokenKind TokenKindToReplace; + bool KeepTemplateTemplateKW = false; + switch (Style.TemplateArgumentKeyword) { + case FormatStyle::TAS_Leave: + assert(false && "The `TemplateArgumentKeywordFixer` class should not be " + "instantiated in the first place"); + return {Fixes, 0}; + case FormatStyle::TAS_Typename: + assert(Style.Standard != FormatStyle::LS_Auto); + // For `auto` language version, be conservative and assume we are < C++17. + KeepTemplateTemplateKW = (Style.Standard == FormatStyle::LS_Auto) || + (Style.Standard < FormatStyle::LS_Cpp17); + TokenKindToReplace = tok::kw_class; + ReplacementStr = "typename"; + break; + case FormatStyle::TAS_Class: + TokenKindToReplace = tok::kw_typename; + ReplacementStr = "class"; + break; + } + + for (size_t I = 0, E = AnnotatedLines.size(); I != E; ++I) { + FormatToken *Tok = AnnotatedLines[I]->First; + + // Find the first `template` keyword on this line. + while (Tok && Tok->isNot(tok::kw_template)) + Tok = Tok->Next; + if (!Tok) + continue; + + // Find the corresponding `<`. Might be on the next line. + Tok = Tok->Next; + while (!Tok && I < E) + Tok = AnnotatedLines[++I]->First; + if (Tok->isNot(TT_TemplateOpener) || !Tok->MatchingParen) { + // This is unexpected and indicates a syntax error. + // Just skip over this line and don't bother about it. + continue; + } + FormatToken *EndTok = Tok->MatchingParen; + + // Loop over the remaining tokens in the argument list and + // replace every `class`/`typename` keyword. + while (Tok != EndTok) { + if (Tok->is(TokenKindToReplace)) { + if (!KeepTemplateTemplateKW || + !Tok->Previous->ClosesTemplateDeclaration) { + replaceToken(SourceMgr, Fixes, Tok, ReplacementStr); + } + } + + bool StartedDefaultArg = Tok->is(tok::equal); + auto advanceTok = [&]() { + Tok = Tok->Next; + while (!Tok && I < E) + Tok = AnnotatedLines[++I]->First; + assert(Tok && "Unexpected end of token stream before encountering " + "matching parens"); + }; + advanceTok(); + if (StartedDefaultArg) { + // Default arguments might contain legitimate uses of the `typename` + // keyword which must not be rewritten to `class`. Example: + // > `template class A; + // Skip over the complete default clause. + unsigned NestingLevel = Tok->NestingLevel; + while (NestingLevel != Tok->NestingLevel || + !Tok->isOneOf(tok::comma, tok::greater)) { + advanceTok(); + } + } + } + } + + return {Fixes, 0}; +} + +} // namespace format +} // namespace clang diff --git a/clang/unittests/Format/CMakeLists.txt b/clang/unittests/Format/CMakeLists.txt --- a/clang/unittests/Format/CMakeLists.txt +++ b/clang/unittests/Format/CMakeLists.txt @@ -22,6 +22,7 @@ SortImportsTestJS.cpp SortImportsTestJava.cpp SortIncludesTest.cpp + TemplateArgumentKeywordFixerTest.cpp UsingDeclarationsSorterTest.cpp TokenAnnotatorTest.cpp ) diff --git a/clang/unittests/Format/TemplateArgumentKeywordFixerTest.cpp b/clang/unittests/Format/TemplateArgumentKeywordFixerTest.cpp new file mode 100644 --- /dev/null +++ b/clang/unittests/Format/TemplateArgumentKeywordFixerTest.cpp @@ -0,0 +1,219 @@ +//===- unittest/Format/TemplateArgumentKeywordFixerTest.cpp ---------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "clang/Format/Format.h" + +#include "FormatTestUtils.h" +#include "TestLexer.h" +#include "gtest/gtest.h" + +#include "../../lib/Format/TemplateArgumentKeywordFixer.h" + +using testing::ScopedTrace; + +namespace clang { +namespace format { +namespace { + +class TemplateArgumentKeywordFixerTest : public ::testing::Test { +protected: + std::string format(llvm::StringRef Code, + const std::vector &Ranges, + const FormatStyle &Style) { + LLVM_DEBUG(llvm::errs() << "---\n"); + LLVM_DEBUG(llvm::errs() << Code << "\n\n"); + FormattingAttemptStatus Status; + tooling::Replacements Replaces = + reformat(Style, Code, Ranges, "", &Status); + EXPECT_TRUE(Status.FormatComplete); + auto Result = applyAllReplacements(Code, Replaces); + EXPECT_TRUE(static_cast(Result)); + LLVM_DEBUG(llvm::errs() << "\n" << *Result << "\n\n"); + return *Result; + } + + std::string format(llvm::StringRef Code, const FormatStyle &Style) { + return format(Code, + /*Ranges=*/{1, tooling::Range(0, Code.size())}, Style); + } + + void _verifyFormat(const char *File, int Line, llvm::StringRef Expected, + llvm::StringRef Code, const FormatStyle &Style) { + ScopedTrace t(File, Line, ::testing::Message() << Code.str()); + EXPECT_EQ(Expected.str(), format(Expected, Style)) + << "Expected code is not stable"; + EXPECT_EQ(Expected.str(), format(Code, Style)); + EXPECT_EQ(Expected.str(), format(test::messUp(Code), Style)); + } + + void _verifyFormat(const char *File, int Line, llvm::StringRef Code, + const FormatStyle &Style) { + _verifyFormat(File, Line, Code, Code, Style); + } +}; + +#define verifyFormat(...) _verifyFormat(__FILE__, __LINE__, __VA_ARGS__) + +TEST_F(TemplateArgumentKeywordFixerTest, DisabledByDefault) { + FormatStyle Style = getLLVMStyle(); + EXPECT_EQ(Style.TemplateArgumentKeyword, FormatStyle::TAS_Leave); + verifyFormat("template class A", "template class A", + Style); + verifyFormat("template class A", "template class A", + Style); + verifyFormat("template class A", + "template class A", Style); +} + +TEST_F(TemplateArgumentKeywordFixerTest, FormatsAsClass) { + FormatStyle Style = getLLVMStyle(); + Style.TemplateArgumentKeyword = FormatStyle::TAS_Class; + verifyFormat("template class A", "template class A", + Style); + verifyFormat("template class A", "template class A", + Style); + verifyFormat("template class A;", + "template class A;", Style); + verifyFormat("template class A;", + "template class A;", Style); + verifyFormat("template