diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -29,6 +29,7 @@ UnaryStaticAssertCheck.cpp UseAutoCheck.cpp UseBoolLiteralsCheck.cpp + UseConstraintsCheck.cpp UseDefaultMemberInitCheck.cpp UseEmplaceCheck.cpp UseEqualsDefaultCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -30,6 +30,7 @@ #include "UnaryStaticAssertCheck.h" #include "UseAutoCheck.h" #include "UseBoolLiteralsCheck.h" +#include "UseConstraintsCheck.h" #include "UseDefaultMemberInitCheck.h" #include "UseEmplaceCheck.h" #include "UseEqualsDefaultCheck.h" @@ -85,6 +86,8 @@ CheckFactories.registerCheck("modernize-use-auto"); CheckFactories.registerCheck( "modernize-use-bool-literals"); + CheckFactories.registerCheck( + "modernize-use-constraints"); CheckFactories.registerCheck( "modernize-use-default-member-init"); CheckFactories.registerCheck("modernize-use-emplace"); diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h @@ -0,0 +1,33 @@ +//===--- UseConstraintsCheck.h - clang-tidy ---------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTRAINTSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTRAINTSCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { + +/// Replace enable_if with C++20 requires clauses. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-constraints.html +class UseConstraintsCheck : public ClangTidyCheck { +public: + UseConstraintsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus20; + } +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTRAINTSCHECK_H diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp @@ -0,0 +1,459 @@ +//===--- UseConstraintsCheck.cpp - clang-tidy -----------------------------===// +// +// 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 "UseConstraintsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +#include "../utils/LexerUtils.h" + +#include +#include + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +struct EnableIfData { + TemplateSpecializationTypeLoc Loc; + TypeLoc Outer; +}; + +namespace { +AST_MATCHER(FunctionDecl, hasOtherDeclarations) { + auto It = Node.redecls_begin(); + auto EndIt = Node.redecls_end(); + + if (It == EndIt) + return false; + + ++It; + return It != EndIt; +} +} // namespace + +void UseConstraintsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + functionTemplateDecl( + has(functionDecl(unless(hasOtherDeclarations()), isDefinition(), + hasReturnTypeLoc(typeLoc().bind("return"))) + .bind("function"))) + .bind("functionTemplate"), + this); +} + +static std::optional +matchEnableIfSpecializationImplTypename(TypeLoc TheType) { + if (const auto Dep = TheType.getAs()) { + if (Dep.getTypePtr()->getIdentifier()->getName() != "type" || + Dep.getTypePtr()->getKeyword() != ETK_Typename) { + return std::nullopt; + } + TheType = Dep.getQualifierLoc().getTypeLoc(); + } + + if (const auto SpecializationLoc = + TheType.getAs()) { + + const auto *Specialization = + dyn_cast(SpecializationLoc.getTypePtr()); + if (!Specialization) + return std::nullopt; + + const TemplateDecl *TD = + Specialization->getTemplateName().getAsTemplateDecl(); + if (!TD || TD->getName() != "enable_if") + return std::nullopt; + + int NumArgs = SpecializationLoc.getNumArgs(); + if (NumArgs != 1 && NumArgs != 2) + return std::nullopt; + + return SpecializationLoc; + } + return std::nullopt; +} + +static std::optional +matchEnableIfSpecializationImplTrait(TypeLoc TheType) { + if (const auto Elaborated = TheType.getAs()) + TheType = Elaborated.getNamedTypeLoc(); + + if (const auto SpecializationLoc = + TheType.getAs()) { + + const auto *Specialization = + dyn_cast(SpecializationLoc.getTypePtr()); + if (!Specialization) + return std::nullopt; + + const TemplateDecl *TD = + Specialization->getTemplateName().getAsTemplateDecl(); + if (!TD || TD->getName() != "enable_if_t") + return std::nullopt; + + if (!Specialization->isTypeAlias()) + return std::nullopt; + + if (const auto *AliasedType = + dyn_cast(Specialization->getAliasedType())) { + if (AliasedType->getIdentifier()->getName() != "type" || + AliasedType->getKeyword() != ETK_Typename) { + return std::nullopt; + } + } else { + return std::nullopt; + } + int NumArgs = SpecializationLoc.getNumArgs(); + if (NumArgs != 1 && NumArgs != 2) + return std::nullopt; + + return SpecializationLoc; + } + return std::nullopt; +} + +static std::optional +matchEnableIfSpecializationImpl(TypeLoc TheType) { + if (auto EnableIf = matchEnableIfSpecializationImplTypename(TheType)) + return EnableIf; + return matchEnableIfSpecializationImplTrait(TheType); +} + +static std::optional +matchEnableIfSpecialization(TypeLoc TheType) { + if (const auto Pointer = TheType.getAs()) + TheType = Pointer.getPointeeLoc(); + else if (const auto Reference = TheType.getAs()) + TheType = Reference.getPointeeLoc(); + if (const auto Qualified = TheType.getAs()) + TheType = Qualified.getUnqualifiedLoc(); + + if (auto EnableIf = matchEnableIfSpecializationImpl(TheType)) + return EnableIfData{std::move(*EnableIf), TheType}; + return std::nullopt; +} + +static std::pair, const Decl *> +matchTrailingTemplateParam(const FunctionTemplateDecl *FunctionTemplate) { + // For non-type trailing param, match very specifically + // 'template <..., enable_if_type = Default>' where + // enable_if_type is 'enable_if' or 'enable_if_t'. E.g., 'template , int*> = nullptr> + // + // Otherwise, match a trailing default type arg. + // E.g., 'template >>' + + const TemplateParameterList *TemplateParams = + FunctionTemplate->getTemplateParameters(); + if (TemplateParams->size() == 0) + return {}; + + const NamedDecl *LastParam = + TemplateParams->getParam(TemplateParams->size() - 1); + if (const auto *LastTemplateParam = + dyn_cast(LastParam)) { + + if (!LastTemplateParam->hasDefaultArgument() || + !LastTemplateParam->getName().empty()) + return {}; + + return {matchEnableIfSpecialization( + LastTemplateParam->getTypeSourceInfo()->getTypeLoc()), + LastTemplateParam}; + } else if (const auto *LastTemplateParam = + dyn_cast(LastParam)) { + if (LastTemplateParam->hasDefaultArgument() && + LastTemplateParam->getIdentifier() == nullptr) { + return {matchEnableIfSpecialization( + LastTemplateParam->getDefaultArgumentInfo()->getTypeLoc()), + LastTemplateParam}; + } + } + return {}; +} + +template +static SourceLocation getRAngleFileLoc(const SourceManager &SM, + const T &Element) { + // getFileLoc handles the case where the RAngle loc is part of a synthesized + // '>>', which ends up allocating a 'scratch space' buffer in the source + // manager. + return SM.getFileLoc(Element.getRAngleLoc()); +} + +static SourceRange +getConditionRange(ASTContext &Context, + const TemplateSpecializationTypeLoc &EnableIf) { + // TemplateArgumentLoc's SourceRange End is the location of the last token + // (per UnqualifiedId docs). E.g., in `enable_if`, the End + // location will be the first 'B' in 'BBB'. + const LangOptions &LangOpts = Context.getLangOpts(); + const SourceManager &SM = Context.getSourceManager(); + if (EnableIf.getNumArgs() > 1) { + TemplateArgumentLoc NextArg = EnableIf.getArgLoc(1); + return SourceRange( + EnableIf.getLAngleLoc().getLocWithOffset(1), + utils::lexer::findPreviousTokenKind(NextArg.getSourceRange().getBegin(), + SM, LangOpts, tok::comma)); + } else { + return SourceRange(EnableIf.getLAngleLoc().getLocWithOffset(1), + getRAngleFileLoc(SM, EnableIf)); + } +} + +static SourceRange getTypeRange(ASTContext &Context, + const TemplateSpecializationTypeLoc &EnableIf) { + TemplateArgumentLoc Arg = EnableIf.getArgLoc(1); + const LangOptions &LangOpts = Context.getLangOpts(); + const SourceManager &SM = Context.getSourceManager(); + return SourceRange( + utils::lexer::findPreviousTokenKind(Arg.getSourceRange().getBegin(), SM, + LangOpts, tok::comma) + .getLocWithOffset(1), + getRAngleFileLoc(SM, EnableIf)); +} + +static std::optional +getTypeText(ASTContext &Context, + const TemplateSpecializationTypeLoc &EnableIf) { + if (EnableIf.getNumArgs() > 1) { + const LangOptions &LangOpts = Context.getLangOpts(); + const SourceManager &SM = Context.getSourceManager(); + bool Invalid = false; + StringRef Text = Lexer::getSourceText(CharSourceRange::getCharRange( + getTypeRange(Context, EnableIf)), + SM, LangOpts, &Invalid) + .trim(); + if (Invalid) + return std::nullopt; + + return Text; + } + + return "void"; +} + +static std::optional +findInsertionForConstraint(const FunctionDecl *Function, ASTContext &Context) { + SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + if (const auto *Constructor = dyn_cast(Function)) { + for (const CXXCtorInitializer *Init : Constructor->inits()) { + if (Init->getSourceOrder() == 0) + return utils::lexer::findPreviousTokenKind(Init->getSourceLocation(), + SM, LangOpts, tok::colon); + } + if (Constructor->init_begin() != Constructor->init_end()) + return std::nullopt; + } + if (Function->isDeleted()) { + SourceLocation FunctionEnd = Function->getSourceRange().getEnd(); + return utils::lexer::findNextAnyTokenKind(FunctionEnd, SM, LangOpts, + tok::equal, tok::equal); + } + const Stmt *Body = Function->getBody(); + if (!Body) + return std::nullopt; + + return Body->getBeginLoc(); +} + +bool isPrimaryExpression(const Expr *Expression) { + // This function is an incomplete approximation of checking whether + // an Expr is a primary expression. In particular, if this function + // returns true, the expression is a primary expression. The converse + // is not necessarily true. + + if (const auto *Cast = dyn_cast(Expression)) + Expression = Cast->getSubExprAsWritten(); + if (isa(Expression)) + return true; + + return false; +} + +static std::optional getConditionText(const Expr *ConditionExpr, + SourceRange ConditionRange, + ASTContext &Context) { + SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + SourceLocation PrevTokenLoc = ConditionRange.getEnd(); + if (PrevTokenLoc.isInvalid()) + return std::nullopt; + + const bool SkipComments = false; + Token PrevToken; + std::tie(PrevToken, PrevTokenLoc) = utils::lexer::getPreviousTokenAndStart( + PrevTokenLoc, SM, LangOpts, SkipComments); + bool EndsWithDoubleSlash = + PrevToken.is(tok::comment) && + Lexer::getSourceText(CharSourceRange::getCharRange( + PrevTokenLoc, PrevTokenLoc.getLocWithOffset(2)), + SM, LangOpts) == "//"; + + bool Invalid = false; + llvm::StringRef ConditionText = Lexer::getSourceText( + CharSourceRange::getCharRange(ConditionRange), SM, LangOpts, &Invalid); + if (Invalid) + return std::nullopt; + + auto AddParens = [&](llvm::StringRef Text) -> std::string { + if (isPrimaryExpression(ConditionExpr)) + return Text.str(); + return "(" + Text.str() + ")"; + }; + + if (EndsWithDoubleSlash) + return AddParens(ConditionText); + return AddParens(ConditionText.trim()); +} + +static std::vector handleReturnType(const FunctionDecl *Function, + const TypeLoc &ReturnType, + const EnableIfData &EnableIf, + ASTContext &Context) { + TemplateArgumentLoc EnableCondition = EnableIf.Loc.getArgLoc(0); + + SourceRange ConditionRange = getConditionRange(Context, EnableIf.Loc); + + std::optional ConditionText = getConditionText( + EnableCondition.getSourceExpression(), ConditionRange, Context); + std::optional TypeText = getTypeText(Context, EnableIf.Loc); + if (!TypeText) + return {}; + + SmallVector ExistingConstraints; + Function->getAssociatedConstraints(ExistingConstraints); + if (ExistingConstraints.size() > 0) { + // FIXME - Support adding new constraints to existing ones. Do we need to + // consider subsumption? + return {}; + } + + std::optional ConstraintInsertionLoc = + findInsertionForConstraint(Function, Context); + if (!ConstraintInsertionLoc) + return {}; + + std::vector FixIts; + FixIts.push_back(FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(EnableIf.Outer.getSourceRange()), + *TypeText)); + FixIts.push_back(FixItHint::CreateInsertion( + *ConstraintInsertionLoc, "requires " + *ConditionText + " ")); + return FixIts; +} + +static std::vector +handleTrailingTemplateType(const FunctionTemplateDecl *FunctionTemplate, + const FunctionDecl *Function, + const Decl *LastTemplateParam, + const EnableIfData &EnableIf, ASTContext &Context) { + SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + TemplateArgumentLoc EnableCondition = EnableIf.Loc.getArgLoc(0); + + SourceRange ConditionRange = getConditionRange(Context, EnableIf.Loc); + + std::optional ConditionText = getConditionText( + EnableCondition.getSourceExpression(), ConditionRange, Context); + if (!ConditionText) + return {}; + + SmallVector ExistingConstraints; + Function->getAssociatedConstraints(ExistingConstraints); + if (ExistingConstraints.size() > 0) { + // FIXME - Support adding new constraints to existing ones. Do we need to + // consider subsumption? + return {}; + } + + SourceRange RemovalRange; + const TemplateParameterList *TemplateParams = + FunctionTemplate->getTemplateParameters(); + if (!TemplateParams || TemplateParams->size() == 0) + return {}; + + if (TemplateParams->size() == 1) { + RemovalRange = + SourceRange(TemplateParams->getTemplateLoc(), + getRAngleFileLoc(SM, *TemplateParams).getLocWithOffset(1)); + } else { + RemovalRange = + SourceRange(utils::lexer::findPreviousTokenKind( + LastTemplateParam->getSourceRange().getBegin(), SM, + LangOpts, tok::comma), + getRAngleFileLoc(SM, *TemplateParams)); + } + + std::optional ConstraintInsertionLoc = + findInsertionForConstraint(Function, Context); + if (!ConstraintInsertionLoc) + return {}; + + std::vector FixIts; + FixIts.push_back( + FixItHint::CreateRemoval(CharSourceRange::getCharRange(RemovalRange))); + FixIts.push_back(FixItHint::CreateInsertion( + *ConstraintInsertionLoc, "requires " + *ConditionText + " ")); + return FixIts; +} + +void UseConstraintsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *FunctionTemplate = + Result.Nodes.getNodeAs("functionTemplate"); + const auto *Function = Result.Nodes.getNodeAs("function"); + const auto *ReturnType = Result.Nodes.getNodeAs("return"); + if (!FunctionTemplate || !Function || !ReturnType) + return; + + // Check for + // + // Case 1. Return type of function + // + // template <...> + // enable_if function(); + // + // Case 2. Trailing template parameter + // + // template <..., enable_if = Type{}> + // type function(); + // + // or + // + // template <..., typename = enable_if> + // type function(); + // + + // Case 1. Return type of function + if (auto EnableIf = matchEnableIfSpecialization(*ReturnType)) { + diag(ReturnType->getBeginLoc(), + "use C++20 requires constraints instead of enable_if") + << handleReturnType(Function, *ReturnType, *EnableIf, *Result.Context); + return; + } + + // Case 2. Trailing template parameter + if (auto [EnableIf, LastTemplateParam] = + matchTrailingTemplateParam(FunctionTemplate); + EnableIf && LastTemplateParam) { + diag(LastTemplateParam->getSourceRange().getBegin(), + "use C++20 requires constraints instead of enable_if") + << handleTrailingTemplateType(FunctionTemplate, Function, + LastTemplateParam, *EnableIf, + *Result.Context); + return; + } +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h b/clang-tools-extra/clang-tidy/utils/LexerUtils.h --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h @@ -13,6 +13,7 @@ #include "clang/Basic/TokenKinds.h" #include "clang/Lex/Lexer.h" #include +#include namespace clang { @@ -23,6 +24,9 @@ /// Returns previous token or ``tok::unknown`` if not found. Token getPreviousToken(SourceLocation Location, const SourceManager &SM, const LangOptions &LangOpts, bool SkipComments = true); +std::pair +getPreviousTokenAndStart(SourceLocation Location, const SourceManager &SM, + const LangOptions &LangOpts, bool SkipComments = true); SourceLocation findPreviousTokenStart(SourceLocation Start, const SourceManager &SM, diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp @@ -10,17 +10,19 @@ #include "clang/AST/AST.h" #include "clang/Basic/SourceManager.h" #include +#include namespace clang::tidy::utils::lexer { -Token getPreviousToken(SourceLocation Location, const SourceManager &SM, - const LangOptions &LangOpts, bool SkipComments) { +std::pair +getPreviousTokenAndStart(SourceLocation Location, const SourceManager &SM, + const LangOptions &LangOpts, bool SkipComments) { Token Token; Token.setKind(tok::unknown); Location = Location.getLocWithOffset(-1); if (Location.isInvalid()) - return Token; + return {Token, Location}; auto StartOfFile = SM.getLocForStartOfFile(SM.getFileID(Location)); while (Location != StartOfFile) { @@ -31,6 +33,13 @@ } Location = Location.getLocWithOffset(-1); } + return {Token, Location}; +} + +Token getPreviousToken(SourceLocation Location, const SourceManager &SM, + const LangOptions &LangOpts, bool SkipComments) { + auto [Token, Start] = + getPreviousTokenAndStart(Location, SM, LangOpts, SkipComments); return Token; } diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -116,17 +116,10 @@ New checks ^^^^^^^^^^ -- New :doc:`bugprone-multi-level-implicit-pointer-conversion - ` check. +- New :doc:`modernize-use-constraints + ` check. - Detects implicit conversions between pointers of different levels of - indirection. - -- New :doc:`performance-enum-size - ` check. - - Recommends the smallest possible underlying type for an ``enum`` or ``enum`` - class based on the range of its enumerators. + Replace ``enable_if`` with C++20 requires clauses. New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -297,6 +297,7 @@ `modernize-unary-static-assert `_, "Yes" `modernize-use-auto `_, "Yes" `modernize-use-bool-literals `_, "Yes" + `modernize-use-constraints `_, "Yes" `modernize-use-default-member-init `_, "Yes" `modernize-use-emplace `_, "Yes" `modernize-use-equals-default `_, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constraints.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constraints.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constraints.rst @@ -0,0 +1,24 @@ +.. title:: clang-tidy - modernize-use-constraints + +modernize-use-constraints +====================== + +Replace ``enable_if`` with C++20 requires clauses. + +``enable_if`` is a SFINAE mechanism for selecting the desired function or class +template based on type traits or other requirements. ``enable_if`` changes the +meta-arity of the template, and has other `adverse side effects `_ +in the code. C++20 introduces concepts and constraints as a cleaner language +provided solution to achieve the same outcome. + +This check replaces common ``enable_if`` patterns that can be replaced +by C++20 requires clauses. Uses that cannot be replaced automatically +will emit a diagnostic without a replacement + +.. code-block:: c++ + + template + std::enable_if_t only_if_t_has_the_trait() { ... } + + template = 0> + void another_version() { ... } diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints-first-greatergreater.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints-first-greatergreater.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints-first-greatergreater.cpp @@ -0,0 +1,22 @@ +// RUN: %check_clang_tidy -std=c++20 %s modernize-use-constraints %t -- -- -fno-delayed-template-parsing + +// NOLINTBEGIN +namespace std { +template struct enable_if { }; + +template struct enable_if { typedef T type; }; + +template +using enable_if_t = typename enable_if::type; + +} // namespace std +// NOLINTEND + +// Separate test file for the case where the first '>>' token part of +// an enable_if expression correctly handles the synthesized token. + +template > +void first_greatergreater_is_enable_if() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void first_greatergreater_is_enable_if() requires T::some_value {{{$}} diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints.cpp @@ -0,0 +1,726 @@ +// RUN: %check_clang_tidy -std=c++20 %s modernize-use-constraints %t -- -- -fno-delayed-template-parsing + +// NOLINTBEGIN +namespace std { +template struct enable_if { }; + +template struct enable_if { typedef T type; }; + +template +using enable_if_t = typename enable_if::type; + +} // namespace std +// NOLINTEND + +template +struct ConsumeVariadic; + +struct Obj { +}; + +namespace enable_if_in_return_type { + +//////////////////////////////// +// Section 1: enable_if in return type of function +//////////////////////////////// + +//////////////////////////////// +// General tests +//////////////////////////////// + +template +typename std::enable_if::type basic() { + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}Obj basic() requires T::some_value {{{$}} + +template +std::enable_if_t basic_t() { + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}Obj basic_t() requires T::some_value {{{$}} + +template +auto basic_trailing() -> typename std::enable_if::type { + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-3]]:26: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}auto basic_trailing() -> Obj requires T::some_value {{{$}} + +template +typename std::enable_if::type existing_constraint() requires (T::another_value) { + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}typename std::enable_if::type existing_constraint() requires (T::another_value) {{{$}} + +template +typename std::enable_if::type decl_without_def(); + +template +typename std::enable_if::type decl_with_separate_def(); + +template +typename std::enable_if::type decl_with_separate_def() { + return Obj{}; +} +// FIXME - Support definitions with separate decls + +template +std::enable_if_t no_dependent_type(U) { + return Obj{}; +} +// FIXME - Support non-dependent enable_ifs. Low priority though... + +template +typename std::enable_if::type* pointer_of_enable_if() { + return nullptr; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if() requires T::some_value {{{$}} + +template +std::enable_if_t* pointer_of_enable_if_t() { + return nullptr; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t() requires T::some_value {{{$}} + +template +const std::enable_if_t* const_pointer_of_enable_if_t() { + return nullptr; +} +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}const int* const_pointer_of_enable_if_t() requires T::some_value {{{$}} + +template +std::enable_if_t const * const_pointer_of_enable_if_t2() { + return nullptr; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}int const * const_pointer_of_enable_if_t2() requires T::some_value {{{$}} + + +template +std::enable_if_t& reference_of_enable_if_t() { + static int x; return x; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}int& reference_of_enable_if_t() requires T::some_value {{{$}} + +template +const std::enable_if_t& const_reference_of_enable_if_t() { + static int x; return x; +} +// CHECK-MESSAGES: :[[@LINE-3]]:7: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}const int& const_reference_of_enable_if_t() requires T::some_value {{{$}} + +template +typename std::enable_if::type enable_if_default_void() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void enable_if_default_void() requires T::some_value {{{$}} + +template +std::enable_if_t enable_if_t_default_void() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void enable_if_t_default_void() requires T::some_value {{{$}} + +template +std::enable_if_t* enable_if_t_default_void_pointer() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void* enable_if_t_default_void_pointer() requires T::some_value {{{$}} + +namespace using_namespace_std { + +using namespace std; + +template +typename enable_if::type with_typename() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void with_typename() requires T::some_value {{{$}} + +template +enable_if_t with_t() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void with_t() requires T::some_value {{{$}} + +template +typename enable_if::type with_typename_and_type() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}int with_typename_and_type() requires T::some_value {{{$}} + +template +enable_if_t with_t_and_type() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}int with_t_and_type() requires T::some_value {{{$}} + +} // namespace using_namespace_std + + +//////////////////////////////// +// Negative tests - incorrect uses of enable_if +//////////////////////////////// +template +std::enable_if not_enable_if() { + return {}; +} +template +typename std::enable_if::type123 not_enable_if_wrong_type() { + return {}; +} +template +typename std::enable_if_t::type not_enable_if_t() { + return {}; +} +template +typename std::enable_if_t::type123 not_enable_if_t_again() { + return {}; +} +template +std::enable_if* not_pointer_of_enable_if() { + return nullptr; +} +template +typename std::enable_if::type123 * not_pointer_of_enable_if_t() { + return nullptr; +} + + +namespace primary_expression_tests { + +//////////////////////////////// +// Primary/non-primary expression tests +//////////////////////////////// + +template struct Traits; + +template +std::enable_if_t::value> type_trait_value() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void type_trait_value() requires Traits::value {{{$}} + +template +std::enable_if_t::member()> type_trait_member_call() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void type_trait_member_call() requires (Traits::member()) {{{$}} + +template +std::enable_if_t::value> negate() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void negate() requires (!Traits::value) {{{$}} + +template +std::enable_if_t::value1 && Traits::value2> conjunction() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void conjunction() requires (Traits::value1 && Traits::value2) {{{$}} + +template +std::enable_if_t::value1 || Traits::value2> disjunction() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void disjunction() requires (Traits::value1 || Traits::value2) {{{$}} + +template +std::enable_if_t::value1 && !Traits::value2> conjunction_with_negate() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void conjunction_with_negate() requires (Traits::value1 && !Traits::value2) {{{$}} + +template +std::enable_if_t::value1 == (Traits::value2 + 5)> complex_operators() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}void complex_operators() requires (Traits::value1 == (Traits::value2 + 5)) {{{$}} + +} // namespace primary_expression_tests + + +//////////////////////////////// +// Functions with specifier +//////////////////////////////// + +template +constexpr typename std::enable_if::type constexpr_decl() { + return 10; +} +// CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}constexpr int constexpr_decl() requires T::some_value {{{$}} + +template +static inline constexpr typename std::enable_if::type static_inline_constexpr_decl() { + return 10; +} +// CHECK-MESSAGES: :[[@LINE-3]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}static inline constexpr int static_inline_constexpr_decl() requires T::some_value {{{$}} + +template +static +typename std::enable_if::type +static_decl() { + return 10; +} +// CHECK-MESSAGES: :[[@LINE-4]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}static{{$}} +// CHECK-FIXES-NEXT: {{^}}int{{$}} +// CHECK-FIXES-NEXT: {{^}}static_decl() requires T::some_value {{{$}} + +template +constexpr /* comment */ typename std::enable_if::type constexpr_comment_decl() { + return 10; +} +// CHECK-MESSAGES: :[[@LINE-3]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}constexpr /* comment */ int constexpr_comment_decl() requires T::some_value {{{$}} + + +//////////////////////////////// +// Class definition tests +//////////////////////////////// + +struct AClass { + + template + static typename std::enable_if::type static_method() { + return Obj{}; + } + // CHECK-MESSAGES: :[[@LINE-3]]:10: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} static Obj static_method() requires T::some_value {{{$}} + + template + typename std::enable_if::type member() { + return Obj{}; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} Obj member() requires T::some_value {{{$}} + + template + typename std::enable_if::type const_qualifier() const { + return Obj{}; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} Obj const_qualifier() const requires T::some_value {{{$}} + + template + typename std::enable_if::type rvalue_ref_qualifier() && { + return Obj{}; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} Obj rvalue_ref_qualifier() && requires T::some_value {{{$}} + + template + typename std::enable_if::type rvalue_ref_qualifier_comment() /* c1 */ && /* c2 */ { + return Obj{}; + } + // CHECK-MESSAGES: :[[@LINE-3]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} Obj rvalue_ref_qualifier_comment() /* c1 */ && /* c2 */ requires T::some_value {{{$}} + + template + std::enable_if_t operator=(T&&) = delete; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} AClass& operator=(T&&) requires T::some_value = delete; + + template + std::enable_if_t operator=(ConsumeVariadic) noexcept(requires (T t) { t = 4; }) = delete; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} AClass& operator=(ConsumeVariadic) noexcept(requires (T t) { t = 4; }) requires T::some_value = delete; + +}; + + +//////////////////////////////// +// Comments and whitespace tests +//////////////////////////////// + +template +typename std::enable_if::type leading_comment() { + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}Obj leading_comment() requires /* check1 */ T::some_value {{{$}} + +template +typename std::enable_if::type body_on_next_line() +{ + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-4]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}Obj body_on_next_line(){{$}} +// CHECK-FIXES-NEXT: {{^}}requires T::some_value {{{$}} + +template +typename std::enable_if< /* check1 */ T::some_value, Obj>::type leading_comment_whitespace() { + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}Obj leading_comment_whitespace() requires /* check1 */ T::some_value {{{$}} + +template +typename std::enable_if::type leading_and_trailing_comment() { + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}Obj leading_and_trailing_comment() requires /* check1 */ T::some_value /* check2 */ {{{$}} + +template +typename std::enable_if::type condition_on_two_lines() { + return Obj{}; +} +// CHECK-MESSAGES: :[[@LINE-4]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}Obj condition_on_two_lines() requires (T::some_value &&{{$}} +// CHECK-FIXES-NEXT: U::another_value) {{{$}} + +template +typename std::enable_if :: type* pointer_of_enable_if_t_with_spaces() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t_with_spaces() requires T::some_value {{{$}} + +template +typename std::enable_if :: /*c*/ type* pointer_of_enable_if_t_with_comment() { +} +// CHECK-MESSAGES: :[[@LINE-2]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t_with_comment() requires T::some_value {{{$}} + +template +std::enable_if_t trailing_slash_slash_comment() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void trailing_slash_slash_comment() requires T::some_value // comment{{$}} +// CHECK-FIXES-NEXT: {{^}} {{{$}} + +} // namespace enable_if_in_return_type + + +namespace enable_if_trailing_non_type_parameter { + +//////////////////////////////// +// Section 2: enable_if as final template non-type parameter +//////////////////////////////// + +template ::type = 0> +void basic() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void basic() requires T::some_value {{{$}} + +template = 0> +void basic_t() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void basic_t() requires T::some_value {{{$}} + +template class U, class V, std::enable_if_t = 0> +void basic_many_template_params() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:61: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template class U, class V>{{$}} +// CHECK-FIXES-NEXT: {{^}}void basic_many_template_params() requires T::some_value {{{$}} + +template = 0> +void no_dependent_type() { +} +// FIXME - Support non-dependent enable_ifs. Low priority though... + +struct ABaseClass { + ABaseClass(); + ABaseClass(int); +}; + +template +struct AClass : ABaseClass { + template = 0> + void no_other_template_params() { + } + // CHECK-MESSAGES: :[[@LINE-3]]:13: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} {{$}} + // CHECK-FIXES-NEXT: {{^}} void no_other_template_params() requires T::some_value {{{$}} + + template = 0> + AClass() {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass() requires U::some_value {}{{$}} + + template = 0> + AClass(int) : data(0) {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass(int) requires U::some_value : data(0) {}{{$}} + + template = 0> + AClass(int, int) : AClass(0) {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass(int, int) requires U::some_value : AClass(0) {}{{$}} + + template = 0> + AClass(int, int, int) : ABaseClass(0) {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass(int, int, int) requires U::some_value : ABaseClass(0) {}{{$}} + + template = 0> + AClass(int, int, int, int) : data2(), data() {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass(int, int, int, int) requires U::some_value : data2(), data() {}{{$}} + + int data; + int data2; +}; + +template +struct AClass2 : ABaseClass { + + template = 0> + AClass2() {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass2() requires U::some_value {}{{$}} + + template = 0> + AClass2(int) : data2(0) {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass2(int) requires U::some_value : data2(0) {}{{$}} + + int data = 10; + int data2; + int data3; +}; + +template * = 0> +void pointer_type() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void pointer_type() requires T::some_value {{{$}} + +template * = nullptr> +void param_on_newline() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void param_on_newline() requires T::some_value {{{$}} + +template ::value, T>* = nullptr> +void param_split_on_two_lines() { +} +// CHECK-MESSAGES: :[[@LINE-5]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void param_split_on_two_lines() requires ConsumeVariadic::value {{{$}} + +template * = nullptr> +void trailing_slash_slash_comment() { +} +// CHECK-MESSAGES: :[[@LINE-4]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void trailing_slash_slash_comment() requires T::some_value // comment{{$}} +// CHECK-FIXES-NEXT: {{^}} {{{$}} + +template * = nullptr, std::enable_if_t* = nullptr> +void two_enable_ifs() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:67: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template * = nullptr>{{$}} +// CHECK-FIXES-NEXT: {{^}}void two_enable_ifs() requires T::another_value {{{$}} + +//////////////////////////////// +// Negative tests +//////////////////////////////// + +template V = 0> +void non_type_param_has_name() { +} +template > +void non_type_param_has_no_default() { +} +template V> +void non_type_param_has_name_and_no_default() { +} +template ...> +void non_type_variadic() { +} +template = 0, int = 0> +void non_type_not_last() { +} + +#define TEMPLATE_REQUIRES(U, IF) template = 0> +TEMPLATE_REQUIRES(U, U::some_value) +void macro_entire_enable_if() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:1: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-MESSAGES: :[[@LINE-5]]:56: note: expanded from macro 'TEMPLATE_REQUIRES' +// CHECK-FIXES: {{^}}TEMPLATE_REQUIRES(U, U::some_value) +// CHECK-FIXES-NEXT: {{^}}void macro_entire_enable_if() {{{$}} + +#define CONDITION U::some_value +template = 0> +void macro_condition() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void macro_condition() requires CONDITION {{{$}} + +#undef CONDITION +#define CONDITION !U::some_value +template = 0> +void macro_condition_not_primary() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void macro_condition_not_primary() requires (CONDITION) {{{$}} + +} // namespace enable_if_trailing_non_type_parameter + + +namespace enable_if_trailing_type_parameter { + +//////////////////////////////// +// Section 3: enable_if as final template nameless defaulted type parameter +//////////////////////////////// + +template ::type> +void basic() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void basic() requires T::some_value {{{$}} + +template > +void basic_t() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void basic_t() requires T::some_value {{{$}} + +template class U, class V, typename = std::enable_if_t> +void basic_many_template_params() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:61: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template class U, class V>{{$}} +// CHECK-FIXES-NEXT: {{^}}void basic_many_template_params() requires T::some_value {{{$}} + +struct ABaseClass { + ABaseClass(); + ABaseClass(int); +}; + +template +struct AClass : ABaseClass { + template > + void no_other_template_params() { + } + // CHECK-MESSAGES: :[[@LINE-3]]:13: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} {{$}} + // CHECK-FIXES-NEXT: {{^}} void no_other_template_params() requires T::some_value {{{$}} + + template > + AClass() {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass() requires U::some_value {}{{$}} + + template > + AClass(int) : data(0) {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass(int) requires U::some_value : data(0) {}{{$}} + + template > + AClass(int, int) : AClass(0) {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass(int, int) requires U::some_value : AClass(0) {}{{$}} + + template > + AClass(int, int, int) : ABaseClass(0) {} + // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] + // CHECK-FIXES: {{^}} template {{$}} + // CHECK-FIXES-NEXT: {{^}} AClass(int, int, int) requires U::some_value : ABaseClass(0) {}{{$}} + + int data; +}; + +template *> +void pointer_type() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void pointer_type() requires T::some_value {{{$}} + +template &> +void reference_type() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void reference_type() requires T::some_value {{{$}} + +template *> +void param_on_newline() { +} +// CHECK-MESSAGES: :[[@LINE-3]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void param_on_newline() requires T::some_value {{{$}} + +template ::value>> +void param_split_on_two_lines() { +} +// CHECK-MESSAGES: :[[@LINE-5]]:11: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints] +// CHECK-FIXES: {{^}}template {{$}} +// CHECK-FIXES-NEXT: {{^}}void param_split_on_two_lines() requires ConsumeVariadic::value {{{$}} + + +//////////////////////////////// +// Negative tests +//////////////////////////////// + +template > +void param_has_name() { +} + +template , typename = int> +void not_last_param() { +} + +} // namespace enable_if_trailing_type_parameter