avogelsgesang updated this revision to Diff 396361. avogelsgesang marked 4 inline comments as done. avogelsgesang added a comment.
Thank you for the quick review, @HazardyKnusperkeks! I addressed most of your comments by fixing my code. In two cases, I think there was a misunderstanding and I didn't apply your proposed changes, yet. See my inline replies re "assert on non nullness" and "`typename` vs `Typename` in configuration". If you still think, I should apply your proposed changes, please let me know. I don't have strong opinions on those points and am happy to do so. Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D116290/new/ https://reviews.llvm.org/D116290 Files: clang/docs/ClangFormatStyleOptions.rst clang/docs/ReleaseNotes.rst clang/include/clang/Format/Format.h clang/lib/Format/CMakeLists.txt clang/lib/Format/Format.cpp clang/lib/Format/TemplateArgumentKeywordFixer.cpp clang/lib/Format/TemplateArgumentKeywordFixer.h clang/unittests/Format/CMakeLists.txt clang/unittests/Format/TemplateArgumentKeywordFixerTest.cpp
Index: clang/unittests/Format/TemplateArgumentKeywordFixerTest.cpp =================================================================== --- /dev/null +++ clang/unittests/Format/TemplateArgumentKeywordFixerTest.cpp @@ -0,0 +1,220 @@ +//===- 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<tooling::Range> &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, "<stdin>", &Status); + EXPECT_TRUE(Status.FormatComplete); + auto Result = applyAllReplacements(Code, Replaces); + EXPECT_TRUE(static_cast<bool>(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)); + } +}; + +#define verifyFormat(...) _verifyFormat(__FILE__, __LINE__, __VA_ARGS__) + +TEST_F(TemplateArgumentKeywordFixerTest, DisabledByDefault) { + FormatStyle Style = getLLVMStyle(); + EXPECT_EQ(Style.TemplateArgumentKeyword, FormatStyle::TAS_Leave); + verifyFormat("template <class X> class A", "template <class X> class A", + Style); + verifyFormat("template <typename X> class A", "template <typename X> class A", + Style); + verifyFormat("template <typename X, class Y> class A", + "template <typename X, class Y> class A", Style); +} + +TEST_F(TemplateArgumentKeywordFixerTest, FormatsAsClass) { + FormatStyle Style = getLLVMStyle(); + Style.TemplateArgumentKeyword = FormatStyle::TAS_Class; + verifyFormat("template <class X> class A", "template <class X> class A", + Style); + verifyFormat("template <class X> class A", "template <typename X> class A", + Style); + verifyFormat("template <class X, class Y> class A;", + "template <typename X, typename Y> class A;", Style); + verifyFormat("template <class X, class Y> class A;", + "template <class X, typename Y> class A;", Style); + verifyFormat("template <template <class X> class Y> class A;", + "template <template <typename X> typename Y> class A;", Style); +} + +TEST_F(TemplateArgumentKeywordFixerTest, ClassLeavesOtherTypenames) { + FormatStyle Style = getLLVMStyle(); + Style.TemplateArgumentKeyword = FormatStyle::TAS_Class; + // `typename` outside of a template should stay unchanged + verifyFormat("typename T::nested", "typename T::nested", Style); + // `typename` inside a template should stay unchanged if it refers to a nested + // type + verifyFormat( + "template <class X, class Y = typename X::Nested> class A;", + "template <typename X, typename Y = typename X::Nested> class A;", Style); + // A legitimate `typename` might also occur in a nested list, directly after + // the `,` token It's not sufficient to just check for a comma in front of + // `typename`. + verifyFormat("template <class X, class Y = X<X, typename X::Nested>, class Z " + "= void> class A;", + "template <typename X, typename Y = X<X, typename X::Nested>, " + "typename Z = void> class A;", + Style); + // `typename` might also occur in a function call, not only in a type + verifyFormat("template <class X, int Y = func<typename Y::type>(), class Z = " + "void> class A;", + "template <typename X, int Y = func<typename Y::type>(), " + "typename Z = void> class A;", + Style); + // `typename` might also occur in function call arguments, outside the + // template list + verifyFormat("template <class X, int Y = func(typename X::type{42}), class " + "Z> class A;", + "template <typename X, int Y = func(typename X::type{42}), " + "typename Z> class A;", + Style); +} + +TEST_F(TemplateArgumentKeywordFixerTest, FormatsAsTypename) { + FormatStyle Style = getLLVMStyle(); + Style.TemplateArgumentKeyword = FormatStyle::TAS_Typename; + verifyFormat("template <typename X> class A", "template <class X> class A", + Style); + verifyFormat("template <typename X> class A", "template <typename X> class A", + Style); + verifyFormat("template <typename X, typename Y> class A;", + "template <class X, class Y> class A;", Style); + verifyFormat("template <typename X, typename Y> class A;", + "template <typename X, class Y> class A;", Style); + verifyFormat("template <template <typename X> typename Y> class A;", + "template <template <class X> class Y> class A;", Style); +} + +TEST_F(TemplateArgumentKeywordFixerTest, FormatsAsTypenameCpp14) { + FormatStyle Style = getLLVMStyle(); + Style.Standard = FormatStyle::LS_Cpp14; + Style.TemplateArgumentKeyword = FormatStyle::TAS_Typename; + // In C++14, template-template arguments must not use `typename`! + verifyFormat("template <template <typename X> class Y> class A;", + "template <template <class X> class Y> class A;", Style); + // When `class` is preferred, we replace both `typename`s, also in C++14 + Style.TemplateArgumentKeyword = FormatStyle::TAS_Class; + verifyFormat("template <template <class X> class Y> class A;", + "template <template <typename X> typename Y> class A;", Style); + + // In `auto` mode, we are conservative and still are C++14 compatible + Style.Standard = FormatStyle::LS_Auto; + Style.TemplateArgumentKeyword = FormatStyle::TAS_Typename; + verifyFormat("template <template <typename X> class Y> class A;", + "template <template <class X> class Y> class A;", Style); +} + +TEST_F(TemplateArgumentKeywordFixerTest, FormatsUsing) { + FormatStyle Style = getLLVMStyle(); + Style.Standard = FormatStyle::LS_Cpp14; + Style.TemplateArgumentKeyword = FormatStyle::TAS_Typename; + // We also format template argument lists for `using`. + // There are no special semantics re. deduction guides and the normal + // implementation just works. But better test it before this breaks due to + // some future change to `clang-format` + verifyFormat("template <typename T> using A = B<T[]>;", + "template <class T> using A = B<T[]>; ", Style); +} + +TEST_F(TemplateArgumentKeywordFixerTest, FormatsDeductionGuides) { + FormatStyle Style = getLLVMStyle(); + Style.Standard = FormatStyle::LS_Cpp14; + Style.TemplateArgumentKeyword = FormatStyle::TAS_Typename; + // We also format template argument lists for deduction guides. + // There are no special semantics re. deduction guides and the normal + // implementation just works. But better test it before this breaks due to + // some future change to `clang-format` + verifyFormat("template <typename T> A(const T &, const T &) -> A<T &>;", + "template <class T> A(const T &, const T &) -> A<T &>;", Style); +} + +TEST_F(TemplateArgumentKeywordFixerTest, FormatsPartialCode) { + FormatStyle Style = getLLVMStyle(); + // We use C++14 mode for those cases as it is the most complicated one + Style.Standard = FormatStyle::LS_Cpp14; + Style.TemplateArgumentKeyword = FormatStyle::TAS_Typename; + // The expectations for partial code is: don't crash, don't mangle code. + // We do not expect partial code to be actually rewritten from `class` to + // `typename`. + verifyFormat("template", "template", Style); + verifyFormat("template <", "template<", Style); + verifyFormat("template <clas", "template<clas", Style); + verifyFormat("template <class", "template<class", Style); + verifyFormat("template <class X", "template<class X", Style); + // As soon as we see the closing `>`, we format the whole thing + verifyFormat("template <typename X>", "template<class X>", Style); + verifyFormat("template <typename X> class", "template<class X> class", Style); + verifyFormat("template <typename X> class A", "template<class X> class A", + Style); + + // Similar test case for template-template arguments + verifyFormat("template", "template", Style); + verifyFormat("template <", "template<", Style); + verifyFormat("template <templat", "template<templat", Style); + verifyFormat("template <template <", "template<template<", Style); + verifyFormat("template <template <clas", "template<template<clas", Style); + verifyFormat("template <template <class", "template<template<class", Style); + // We still don't replace `class` by `typename`, because the outer `<` are + // unmatched. This is an implementation detail and should one of your changes + // modifies the behavor here, feel freee to just update the test case. + verifyFormat("template <template <class>", "template<template<class>", Style); + verifyFormat("template <template <class> class", + "template<template<class> class", Style); + verifyFormat("template <template <class> class X", + "template<template<class> class X", Style); + // As soon as we see the closing `>`, we format `class` as `typename`, but + // only where C++14 permits it + verifyFormat("template <template <typename> class X>", + "template<template<class> class X>", Style); + verifyFormat("template <template <typename> class X> class", + "template<template<class> class X> class", Style); + verifyFormat("template <template <typename> class X> class A", + "template<template<class> class X> class A", Style); +} + +} // namespace +} // namespace format +} // namespace clang Index: clang/unittests/Format/CMakeLists.txt =================================================================== --- clang/unittests/Format/CMakeLists.txt +++ clang/unittests/Format/CMakeLists.txt @@ -22,6 +22,7 @@ SortImportsTestJS.cpp SortImportsTestJava.cpp SortIncludesTest.cpp + TemplateArgumentKeywordFixerTest.cpp UsingDeclarationsSorterTest.cpp TokenAnnotatorTest.cpp ) Index: clang/lib/Format/TemplateArgumentKeywordFixer.h =================================================================== --- /dev/null +++ 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`/`class` for 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<tooling::Replacements, unsigned> + analyze(TokenAnnotator &Annotator, + SmallVectorImpl<AnnotatedLine *> &AnnotatedLines, + FormatTokenLexer &Tokens) override; +}; + +} // end namespace format +} // end namespace clang + +#endif Index: clang/lib/Format/TemplateArgumentKeywordFixer.cpp =================================================================== --- /dev/null +++ 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<tooling::Replacements, unsigned> +TemplateArgumentKeywordFixer::analyze( + TokenAnnotator &Annotator, SmallVectorImpl<AnnotatedLine *> &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 X = typename T::Nested> 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 Index: clang/lib/Format/Format.cpp =================================================================== --- clang/lib/Format/Format.cpp +++ 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<FormatStyle::TemplateArgumentStyle> { + 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<FormatStyle::ShortFunctionStyle> { 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) { Index: clang/lib/Format/CMakeLists.txt =================================================================== --- clang/lib/Format/CMakeLists.txt +++ clang/lib/Format/CMakeLists.txt @@ -13,6 +13,7 @@ SortJavaScriptImports.cpp TokenAnalyzer.cpp TokenAnnotator.cpp + TemplateArgumentKeywordFixer.cpp UnwrappedLineFormatter.cpp UnwrappedLineParser.cpp UsingDeclarationsSorter.cpp Index: clang/include/clang/Format/Format.h =================================================================== --- clang/include/clang/Format/Format.h +++ clang/include/clang/Format/Format.h @@ -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<typename Key, class Value> class Map; + /// template<template<class X> class Y> f() + /// \endcode + TAS_Leave, + /// Use `typename` instead of `class`. + /// \code + /// template<typename Key, typename Value> class Map; + /// template<template<typename X> typename Y> f() + /// \endcode + TAS_Typename, // typename + /// Use `class` instead of `typename`. + /// \code + /// template<class Key, class Value> class Map; + /// template<class<class X> 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-formats 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; } Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ 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``. Index: clang/docs/ClangFormatStyleOptions.rst =================================================================== --- clang/docs/ClangFormatStyleOptions.rst +++ clang/docs/ClangFormatStyleOptions.rst @@ -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-formats 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<typename Key, class Value> class Map; + template<template<class X> class Y> f() + + * ``TAS_Typename`` (in configuration: ``typename``) + Use `typename` instead of `class`. + + .. code-block:: c++ + + template<typename Key, typename Value> class Map; + template<template<typename X> typename Y> f() + + * ``TAS_Class`` (in configuration: ``class``) + Use `class` instead of `typename`. + + .. code-block:: c++ + + template<class Key, class Value> class Map; + template<class<class X> 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.
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits