[clang-tools-extra] 893d53d - [clang-tidy] Implement modernize-use-constraints
Piotr Zegar via cfe-commits
cfe-commits at lists.llvm.org
Sun Jul 30 13:43:33 PDT 2023
Author: Chris Cotter
Date: 2023-07-30T20:43:18Z
New Revision: 893d53d11c0116718ecc816263f12d51c4612e1d
URL: https://github.com/llvm/llvm-project/commit/893d53d11c0116718ecc816263f12d51c4612e1d
DIFF: https://github.com/llvm/llvm-project/commit/893d53d11c0116718ecc816263f12d51c4612e1d.diff
LOG: [clang-tidy] Implement modernize-use-constraints
Add new check to replace enable_if with C++20 constraints
Reviewed By: PiotrZSL
Differential Revision: https://reviews.llvm.org/D141892
Added:
clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp
clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h
clang-tools-extra/docs/clang-tidy/checks/modernize/use-constraints.rst
clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints-first-greatergreater.cpp
clang-tools-extra/test/clang-tidy/checkers/modernize/use-constraints.cpp
Modified:
clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp
clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
clang-tools-extra/clang-tidy/utils/LexerUtils.h
clang-tools-extra/docs/ReleaseNotes.rst
clang-tools-extra/docs/clang-tidy/checks/list.rst
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
index ef10cb0e388264..a14501d13930e0 100644
--- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt
@@ -29,6 +29,7 @@ add_clang_library(clangTidyModernizeModule
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
index 9bbc4dc50ec0e4..73751cf2705068 100644
--- 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 @@ class ModernizeModule : public ClangTidyModule {
CheckFactories.registerCheck<UseAutoCheck>("modernize-use-auto");
CheckFactories.registerCheck<UseBoolLiteralsCheck>(
"modernize-use-bool-literals");
+ CheckFactories.registerCheck<UseConstraintsCheck>(
+ "modernize-use-constraints");
CheckFactories.registerCheck<UseDefaultMemberInitCheck>(
"modernize-use-default-member-init");
CheckFactories.registerCheck<UseEmplaceCheck>("modernize-use-emplace");
diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp
new file mode 100644
index 00000000000000..951cc2f9903ba3
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp
@@ -0,0 +1,488 @@
+//===--- 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 <optional>
+#include <utility>
+
+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<TemplateSpecializationTypeLoc>
+matchEnableIfSpecializationImplTypename(TypeLoc TheType) {
+ if (const auto Dep = TheType.getAs<DependentNameTypeLoc>()) {
+ const IdentifierInfo *Identifier = Dep.getTypePtr()->getIdentifier();
+ if (!Identifier || Identifier->getName() != "type" ||
+ Dep.getTypePtr()->getKeyword() != ETK_Typename) {
+ return std::nullopt;
+ }
+ TheType = Dep.getQualifierLoc().getTypeLoc();
+ }
+
+ if (const auto SpecializationLoc =
+ TheType.getAs<TemplateSpecializationTypeLoc>()) {
+
+ const auto *Specialization =
+ dyn_cast<TemplateSpecializationType>(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<TemplateSpecializationTypeLoc>
+matchEnableIfSpecializationImplTrait(TypeLoc TheType) {
+ if (const auto Elaborated = TheType.getAs<ElaboratedTypeLoc>())
+ TheType = Elaborated.getNamedTypeLoc();
+
+ if (const auto SpecializationLoc =
+ TheType.getAs<TemplateSpecializationTypeLoc>()) {
+
+ const auto *Specialization =
+ dyn_cast<TemplateSpecializationType>(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<DependentNameType>(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<TemplateSpecializationTypeLoc>
+matchEnableIfSpecializationImpl(TypeLoc TheType) {
+ if (auto EnableIf = matchEnableIfSpecializationImplTypename(TheType))
+ return EnableIf;
+ return matchEnableIfSpecializationImplTrait(TheType);
+}
+
+static std::optional<EnableIfData>
+matchEnableIfSpecialization(TypeLoc TheType) {
+ if (const auto Pointer = TheType.getAs<PointerTypeLoc>())
+ TheType = Pointer.getPointeeLoc();
+ else if (const auto Reference = TheType.getAs<ReferenceTypeLoc>())
+ TheType = Reference.getPointeeLoc();
+ if (const auto Qualified = TheType.getAs<QualifiedTypeLoc>())
+ TheType = Qualified.getUnqualifiedLoc();
+
+ if (auto EnableIf = matchEnableIfSpecializationImpl(TheType))
+ return EnableIfData{std::move(*EnableIf), TheType};
+ return std::nullopt;
+}
+
+static std::pair<std::optional<EnableIfData>, const Decl *>
+matchTrailingTemplateParam(const FunctionTemplateDecl *FunctionTemplate) {
+ // For non-type trailing param, match very specifically
+ // 'template <..., enable_if_type<Condition, Type> = Default>' where
+ // enable_if_type is 'enable_if' or 'enable_if_t'. E.g., 'template <typename
+ // T, enable_if_t<is_same_v<T, bool>, int*> = nullptr>
+ //
+ // Otherwise, match a trailing default type arg.
+ // E.g., 'template <typename T, typename = enable_if_t<is_same_v<T, bool>>>'
+
+ const TemplateParameterList *TemplateParams =
+ FunctionTemplate->getTemplateParameters();
+ if (TemplateParams->size() == 0)
+ return {};
+
+ const NamedDecl *LastParam =
+ TemplateParams->getParam(TemplateParams->size() - 1);
+ if (const auto *LastTemplateParam =
+ dyn_cast<NonTypeTemplateParmDecl>(LastParam)) {
+
+ if (!LastTemplateParam->hasDefaultArgument() ||
+ !LastTemplateParam->getName().empty())
+ return {};
+
+ return {matchEnableIfSpecialization(
+ LastTemplateParam->getTypeSourceInfo()->getTypeLoc()),
+ LastTemplateParam};
+ } else if (const auto *LastTemplateParam =
+ dyn_cast<TemplateTypeParmDecl>(LastParam)) {
+ if (LastTemplateParam->hasDefaultArgument() &&
+ LastTemplateParam->getIdentifier() == nullptr) {
+ return {matchEnableIfSpecialization(
+ LastTemplateParam->getDefaultArgumentInfo()->getTypeLoc()),
+ LastTemplateParam};
+ }
+ }
+ return {};
+}
+
+template <typename T>
+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<AAA && BBB>`, 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));
+ }
+
+ 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));
+}
+
+// Returns the original source text of the second argument of a call to
+// enable_if_t. E.g., in enable_if_t<Condition, TheType>, this function
+// returns 'TheType'.
+static std::optional<StringRef>
+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<SourceLocation>
+findInsertionForConstraint(const FunctionDecl *Function, ASTContext &Context) {
+ SourceManager &SM = Context.getSourceManager();
+ const LangOptions &LangOpts = Context.getLangOpts();
+
+ if (const auto *Constructor = dyn_cast<CXXConstructorDecl>(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<ImplicitCastExpr>(Expression))
+ Expression = Cast->getSubExprAsWritten();
+ if (isa<ParenExpr, DependentScopeDeclRefExpr>(Expression))
+ return true;
+
+ return false;
+}
+
+// Return the original source text of an enable_if_t condition, i.e., the
+// first template argument). For example, in
+// 'enable_if_t<FirstCondition || SecondCondition, AType>', the text
+// the text 'FirstCondition || SecondCondition' is returned.
+static std::optional<std::string> 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());
+}
+
+// Handle functions that return enable_if_t, e.g.,
+// template <...>
+// enable_if_t<Condition, ReturnType> function();
+//
+// Return a vector of FixItHints if the code can be replaced with
+// a C++20 requires clause. In the example above, returns FixItHints
+// to result in
+// template <...>
+// ReturnType function() requires Condition {}
+static std::vector<FixItHint> 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<std::string> ConditionText = getConditionText(
+ EnableCondition.getSourceExpression(), ConditionRange, Context);
+ if (!ConditionText)
+ return {};
+
+ std::optional<StringRef> TypeText = getTypeText(Context, EnableIf.Loc);
+ if (!TypeText)
+ return {};
+
+ SmallVector<const Expr *, 3> 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<SourceLocation> ConstraintInsertionLoc =
+ findInsertionForConstraint(Function, Context);
+ if (!ConstraintInsertionLoc)
+ return {};
+
+ std::vector<FixItHint> FixIts;
+ FixIts.push_back(FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(EnableIf.Outer.getSourceRange()),
+ *TypeText));
+ FixIts.push_back(FixItHint::CreateInsertion(
+ *ConstraintInsertionLoc, "requires " + *ConditionText + " "));
+ return FixIts;
+}
+
+// Handle enable_if_t in a trailing template parameter, e.g.,
+// template <..., enable_if_t<Condition, Type> = Type{}>
+// ReturnType function();
+//
+// Return a vector of FixItHints if the code can be replaced with
+// a C++20 requires clause. In the example above, returns FixItHints
+// to result in
+// template <...>
+// ReturnType function() requires Condition {}
+static std::vector<FixItHint>
+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<std::string> ConditionText = getConditionText(
+ EnableCondition.getSourceExpression(), ConditionRange, Context);
+ if (!ConditionText)
+ return {};
+
+ SmallVector<const Expr *, 3> 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<SourceLocation> ConstraintInsertionLoc =
+ findInsertionForConstraint(Function, Context);
+ if (!ConstraintInsertionLoc)
+ return {};
+
+ std::vector<FixItHint> 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<FunctionTemplateDecl>("functionTemplate");
+ const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>("function");
+ const auto *ReturnType = Result.Nodes.getNodeAs<TypeLoc>("return");
+ if (!FunctionTemplate || !Function || !ReturnType)
+ return;
+
+ // Check for
+ //
+ // Case 1. Return type of function
+ //
+ // template <...>
+ // enable_if_t<Condition, ReturnType>::type function() {}
+ //
+ // Case 2. Trailing template parameter
+ //
+ // template <..., enable_if_t<Condition, Type> = Type{}>
+ // ReturnType function() {}
+ //
+ // or
+ //
+ // template <..., typename = enable_if_t<Condition, void>>
+ // ReturnType 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/modernize/UseConstraintsCheck.h b/clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.h
new file mode 100644
index 00000000000000..814160190e0f4e
--- /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/utils/LexerUtils.cpp b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp
index 95e0255b37fd6a..32c9168dc2a2d2 100644
--- 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 <optional>
+#include <utility>
namespace clang::tidy::utils::lexer {
-Token getPreviousToken(SourceLocation Location, const SourceManager &SM,
- const LangOptions &LangOpts, bool SkipComments) {
+std::pair<Token, SourceLocation>
+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 @@ Token getPreviousToken(SourceLocation Location, const SourceManager &SM,
}
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/clang-tidy/utils/LexerUtils.h b/clang-tools-extra/clang-tidy/utils/LexerUtils.h
index cd5c76ee67e031..f4ddb2c6382b57 100644
--- 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 <optional>
+#include <utility>
namespace clang {
@@ -23,6 +24,9 @@ namespace tidy::utils::lexer {
/// Returns previous token or ``tok::unknown`` if not found.
Token getPreviousToken(SourceLocation Location, const SourceManager &SM,
const LangOptions &LangOpts, bool SkipComments = true);
+std::pair<Token, SourceLocation>
+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/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index 260a369fa2fc58..feb94a4e4219f3 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -129,6 +129,11 @@ New checks
Detects implicit conversions between pointers of
diff erent levels of
indirection.
+- New :doc:`modernize-use-constraints
+ <clang-tidy/checks/modernize/use-constraints>` check.
+
+ Replace ``enable_if`` with C++20 requires clauses.
+
- New :doc:`performance-enum-size
<clang-tidy/checks/performance/enum-size>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 78913e2dcf108b..ccfc15585409c4 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -298,6 +298,7 @@ Clang-Tidy Checks
`modernize-unary-static-assert <modernize/unary-static-assert.html>`_, "Yes"
`modernize-use-auto <modernize/use-auto.html>`_, "Yes"
`modernize-use-bool-literals <modernize/use-bool-literals.html>`_, "Yes"
+ `modernize-use-constraints <modernize/use-constraints.html>`_, "Yes"
`modernize-use-default-member-init <modernize/use-default-member-init.html>`_, "Yes"
`modernize-use-emplace <modernize/use-emplace.html>`_, "Yes"
`modernize-use-equals-default <modernize/use-equals-default.html>`_, "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
index 00000000000000..be62dd5823d552
--- /dev/null
+++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constraints.rst
@@ -0,0 +1,70 @@
+.. title:: clang-tidy - modernize-use-constraints
+
+modernize-use-constraints
+=========================
+
+Replace ``std::enable_if`` with C++20 requires clauses.
+
+``std::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
+<https://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0225r0.html>`_
+in the code. C++20 introduces concepts and constraints as a cleaner language
+provided solution to achieve the same outcome.
+
+This check finds some common ``std::enable_if`` patterns that can be replaced
+by C++20 requires clauses. The tool can replace some of these patterns
+automatically, otherwise, the tool will emit a diagnostic without a
+replacement. The tool can detect the following ``std::enable_if`` patterns
+
+1. ``std::enable_if`` in the return type of a function
+2. ``std::enable_if`` as the trailing template parameter for function templates
+
+Other uses, for example, in class templates for function parameters, are not
+currently supported by this tool. Other variants such as ``boost::enable_if``
+are not currently supported by this tool.
+
+Below are some examples of code using ``std::enable_if``.
+
+.. code-block:: c++
+
+ // enable_if in function return type
+ template <typename T>
+ std::enable_if_t<T::some_trait, int> only_if_t_has_the_trait() { ... }
+
+ // enable_if in the trailing template parameter
+ template <typename T, std::enable_if_t<T::some_trait, int> = 0>
+ void another_version() { ... }
+
+ template <typename T>
+ typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
+ return Obj{};
+ }
+
+ template <typename T, std::enable_if_t<T::some_trait, int> = 0>
+ struct my_class {};
+
+The tool will replace the above code with,
+
+.. code-block:: c++
+
+ // warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+ template <typename T>
+ int only_if_t_has_the_trait() requires T::some_trait { ... }
+
+ // warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+ template <typename T>
+ void another_version() requires T::some_trait { ... }
+
+ // The tool will emit a diagnostic for the following, but will
+ // not attempt to replace the code.
+ // warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+ template <typename T>
+ typename std::enable_if<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {
+ return Obj{};
+ }
+
+ // The tool will not emit a diagnostic or attempt to replace the code.
+ template <typename T, std::enable_if_t<T::some_trait, int> = 0>
+ struct my_class {};
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
index 00000000000000..c3b2c184364a7b
--- /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 <bool B, class T = void> struct enable_if { };
+
+template <class T> struct enable_if<true, T> { typedef T type; };
+
+template <bool B, class T = void>
+using enable_if_t = typename enable_if<B, T>::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 <typename T, typename = std::enable_if_t<T::some_value>>
+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
index 00000000000000..3ec44be8a1c8c3
--- /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 <bool B, class T = void> struct enable_if { };
+
+template <class T> struct enable_if<true, T> { typedef T type; };
+
+template <bool B, class T = void>
+using enable_if_t = typename enable_if<B, T>::type;
+
+} // namespace std
+// NOLINTEND
+
+template <typename...>
+struct ConsumeVariadic;
+
+struct Obj {
+};
+
+namespace enable_if_in_return_type {
+
+////////////////////////////////
+// Section 1: enable_if in return type of function
+////////////////////////////////
+
+////////////////////////////////
+// General tests
+////////////////////////////////
+
+template <typename T>
+typename std::enable_if<T::some_value, Obj>::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 <typename T>
+std::enable_if_t<T::some_value, Obj> 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 <typename T>
+auto basic_trailing() -> typename std::enable_if<T::some_value, Obj>::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 T>
+typename std::enable_if<T::some_value, Obj>::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<T::some_value, Obj>::type existing_constraint() requires (T::another_value) {{{$}}
+
+template <typename U>
+typename std::enable_if<U::some_value, Obj>::type decl_without_def();
+
+template <typename U>
+typename std::enable_if<U::some_value, Obj>::type decl_with_separate_def();
+
+template <typename U>
+typename std::enable_if<U::some_value, Obj>::type decl_with_separate_def() {
+ return Obj{};
+}
+// FIXME - Support definitions with separate decls
+
+template <typename U>
+std::enable_if_t<true, Obj> no_dependent_type(U) {
+ return Obj{};
+}
+// FIXME - Support non-dependent enable_ifs. Low priority though...
+
+template <typename T>
+typename std::enable_if<T::some_value, int>::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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value, int>* 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t() requires T::some_value {{{$}}
+
+template <typename T>
+const std::enable_if_t<T::some_value, int>* 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}const int* const_pointer_of_enable_if_t() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value, int> 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int const * const_pointer_of_enable_if_t2() requires T::some_value {{{$}}
+
+
+template <typename T>
+std::enable_if_t<T::some_value, int>& 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int& reference_of_enable_if_t() requires T::some_value {{{$}}
+
+template <typename T>
+const std::enable_if_t<T::some_value, int>& 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}const int& const_reference_of_enable_if_t() requires T::some_value {{{$}}
+
+template <typename T>
+typename std::enable_if<T::some_value>::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 <typename T>
+std::enable_if_t<T::some_value> 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 <typename T>
+std::enable_if_t<T::some_value>* 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 T>
+typename enable_if<T::some_value>::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 <typename T>
+enable_if_t<T::some_value> 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 T>
+typename enable_if<T::some_value, int>::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 <typename T>
+enable_if_t<T::some_value, int> 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 <typename U>
+std::enable_if<U::some_value, Obj> not_enable_if() {
+ return {};
+}
+template <typename U>
+typename std::enable_if<U::some_value, Obj>::type123 not_enable_if_wrong_type() {
+ return {};
+}
+template <typename U>
+typename std::enable_if_t<U::some_value, Obj>::type not_enable_if_t() {
+ return {};
+}
+template <typename U>
+typename std::enable_if_t<U::some_value, Obj>::type123 not_enable_if_t_again() {
+ return {};
+}
+template <typename U>
+std::enable_if<U::some_value, int>* not_pointer_of_enable_if() {
+ return nullptr;
+}
+template <typename U>
+typename std::enable_if<U::some_value, int>::type123 * not_pointer_of_enable_if_t() {
+ return nullptr;
+}
+
+
+namespace primary_expression_tests {
+
+////////////////////////////////
+// Primary/non-primary expression tests
+////////////////////////////////
+
+template <typename T> struct Traits;
+
+template <typename T>
+std::enable_if_t<Traits<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<T>::value {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<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<T>::member()) {{{$}}
+
+template <typename T>
+std::enable_if_t<!Traits<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<T>::value) {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::value1 && Traits<T>::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<T>::value1 && Traits<T>::value2) {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::value1 || Traits<T>::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<T>::value1 || Traits<T>::value2) {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::value1 && !Traits<T>::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<T>::value1 && !Traits<T>::value2) {{{$}}
+
+template <typename T>
+std::enable_if_t<Traits<T>::value1 == (Traits<T>::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<T>::value1 == (Traits<T>::value2 + 5)) {{{$}}
+
+} // namespace primary_expression_tests
+
+
+////////////////////////////////
+// Functions with specifier
+////////////////////////////////
+
+template <typename T>
+constexpr typename std::enable_if<T::some_value, int>::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 <typename T>
+static inline constexpr typename std::enable_if<T::some_value, int>::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 <typename T>
+static
+typename std::enable_if<T::some_value, int>::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 <typename T>
+constexpr /* comment */ typename std::enable_if<T::some_value, int>::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 <typename T>
+ static typename std::enable_if<T::some_value, Obj>::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 T>
+ typename std::enable_if<T::some_value, Obj>::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 T>
+ typename std::enable_if<T::some_value, Obj>::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 T>
+ typename std::enable_if<T::some_value, Obj>::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 T>
+ typename std::enable_if<T::some_value, Obj>::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 <typename T>
+ std::enable_if_t<T::some_value, AClass&> 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<typename T>
+ std::enable_if_t<T::some_value, AClass&> operator=(ConsumeVariadic<T>) 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<T>) noexcept(requires (T t) { t = 4; }) requires T::some_value = delete;
+
+};
+
+
+////////////////////////////////
+// Comments and whitespace tests
+////////////////////////////////
+
+template <typename T>
+typename std::enable_if</* check1 */ T::some_value, Obj>::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 T>
+typename std::enable_if<T::some_value, Obj>::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 T>
+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 T>
+typename std::enable_if</* check1 */ T::some_value /* check2 */, Obj>::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 T, typename U>
+typename std::enable_if<T::some_value &&
+ U::another_value, Obj>::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 T>
+typename std::enable_if<T::some_value, int> :: 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t_with_spaces() requires T::some_value {{{$}}
+
+template <typename T>
+typename std::enable_if<T::some_value, int> :: /*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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}int* pointer_of_enable_if_t_with_comment() requires T::some_value {{{$}}
+
+template <typename T>
+std::enable_if_t<T::some_value // comment
+ > 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 <typename T>{{$}}
+// 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 <typename T, typename std::enable_if<T::some_value, int>::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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic() requires T::some_value {{{$}}
+
+template <typename T, std::enable_if_t<T::some_value, int> = 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic_t() requires T::some_value {{{$}}
+
+template <typename T, template <typename> class U, class V, std::enable_if_t<T::some_value, int> = 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 <typename T, template <typename> class U, class V>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic_many_template_params() requires T::some_value {{{$}}
+
+template <std::enable_if_t<true, int> = 0>
+void no_dependent_type() {
+}
+// FIXME - Support non-dependent enable_ifs. Low priority though...
+
+struct ABaseClass {
+ ABaseClass();
+ ABaseClass(int);
+};
+
+template <typename T>
+struct AClass : ABaseClass {
+ template <std::enable_if_t<T::some_value, int> = 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 <typename U, std::enable_if_t<U::some_value, int> = 0>
+ AClass() {}
+ // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+ // CHECK-FIXES: {{^}} template <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass() requires U::some_value {}{{$}}
+
+ template <typename U, std::enable_if_t<U::some_value, int> = 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 <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass(int) requires U::some_value : data(0) {}{{$}}
+
+ template <typename U, std::enable_if_t<U::some_value, int> = 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 <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass(int, int) requires U::some_value : AClass(0) {}{{$}}
+
+ template <typename U, std::enable_if_t<U::some_value, int> = 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 <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass(int, int, int) requires U::some_value : ABaseClass(0) {}{{$}}
+
+ template <typename U, std::enable_if_t<U::some_value, int> = 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 <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass(int, int, int, int) requires U::some_value : data2(), data() {}{{$}}
+
+ int data;
+ int data2;
+};
+
+template <typename T>
+struct AClass2 : ABaseClass {
+
+ template <typename U, std::enable_if_t<U::some_value, int> = 0>
+ AClass2() {}
+ // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+ // CHECK-FIXES: {{^}} template <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass2() requires U::some_value {}{{$}}
+
+ template <typename U, std::enable_if_t<U::some_value, int> = 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 <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass2(int) requires U::some_value : data2(0) {}{{$}}
+
+ int data = 10;
+ int data2;
+ int data3;
+};
+
+template <typename T, std::enable_if_t<T::some_value, T>* = 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void pointer_type() requires T::some_value {{{$}}
+
+template <typename T,
+ std::enable_if_t<T::some_value, T>* = 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void param_on_newline() requires T::some_value {{{$}}
+
+template <typename T,
+ typename U,
+ std::enable_if_t<
+ ConsumeVariadic<T,
+ U>::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 <typename T,{{$}}
+// CHECK-FIXES-NEXT: {{^}} typename U>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void param_split_on_two_lines() requires ConsumeVariadic<T,{{$}}
+// CHECK-FIXES-NEXT: {{^}} U>::value {{{$}}
+
+template <typename T, std::enable_if_t<T::some_value // comment
+ >* = 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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void trailing_slash_slash_comment() requires T::some_value // comment{{$}}
+// CHECK-FIXES-NEXT: {{^}} {{{$}}
+
+template <typename T, std::enable_if_t<T::some_value>* = nullptr, std::enable_if_t<T::another_value>* = 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 <typename T, std::enable_if_t<T::some_value>* = nullptr>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void two_enable_ifs() requires T::another_value {{{$}}
+
+////////////////////////////////
+// Negative tests
+////////////////////////////////
+
+template <typename U, std::enable_if_t<U::some_value, int> V = 0>
+void non_type_param_has_name() {
+}
+template <typename U, std::enable_if_t<U::some_value, int>>
+void non_type_param_has_no_default() {
+}
+template <typename U, std::enable_if_t<U::some_value, int> V>
+void non_type_param_has_name_and_no_default() {
+}
+template <typename U, std::enable_if_t<U::some_value, int>...>
+void non_type_variadic() {
+}
+template <typename U, std::enable_if_t<U::some_value, int> = 0, int = 0>
+void non_type_not_last() {
+}
+
+#define TEMPLATE_REQUIRES(U, IF) template <typename U, std::enable_if_t<IF, int> = 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 <typename U, std::enable_if_t<CONDITION, int> = 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 <typename U>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void macro_condition() requires CONDITION {{{$}}
+
+#undef CONDITION
+#define CONDITION !U::some_value
+template <typename U, std::enable_if_t<CONDITION, int> = 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 <typename U>{{$}}
+// 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 <typename T, typename = std::enable_if<T::some_value>::type>
+void basic() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic() requires T::some_value {{{$}}
+
+template <typename T, typename = std::enable_if_t<T::some_value>>
+void basic_t() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic_t() requires T::some_value {{{$}}
+
+template <typename T, template <typename> class U, class V, typename = std::enable_if_t<T::some_value>>
+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 <typename T, template <typename> class U, class V>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void basic_many_template_params() requires T::some_value {{{$}}
+
+struct ABaseClass {
+ ABaseClass();
+ ABaseClass(int);
+};
+
+template <typename T>
+struct AClass : ABaseClass {
+ template <typename = std::enable_if_t<T::some_value>>
+ 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 <typename U, typename = std::enable_if_t<U::some_value>>
+ AClass() {}
+ // CHECK-MESSAGES: :[[@LINE-2]]:25: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+ // CHECK-FIXES: {{^}} template <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass() requires U::some_value {}{{$}}
+
+ template <typename U, typename = std::enable_if_t<U::some_value>>
+ 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 <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass(int) requires U::some_value : data(0) {}{{$}}
+
+ template <typename U, typename = std::enable_if_t<U::some_value>>
+ 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 <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass(int, int) requires U::some_value : AClass(0) {}{{$}}
+
+ template <typename U, typename = std::enable_if_t<U::some_value>>
+ 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 <typename U>{{$}}
+ // CHECK-FIXES-NEXT: {{^}} AClass(int, int, int) requires U::some_value : ABaseClass(0) {}{{$}}
+
+ int data;
+};
+
+template <typename T, typename = std::enable_if_t<T::some_value>*>
+void pointer_type() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void pointer_type() requires T::some_value {{{$}}
+
+template <typename T, typename = std::enable_if_t<T::some_value>&>
+void reference_type() {
+}
+// CHECK-MESSAGES: :[[@LINE-3]]:23: warning: use C++20 requires constraints instead of enable_if [modernize-use-constraints]
+// CHECK-FIXES: {{^}}template <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void reference_type() requires T::some_value {{{$}}
+
+template <typename T,
+ typename = std::enable_if_t<T::some_value>*>
+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 <typename T>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void param_on_newline() requires T::some_value {{{$}}
+
+template <typename T,
+ typename U,
+ typename = std::enable_if_t<
+ ConsumeVariadic<T,
+ U>::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 <typename T,{{$}}
+// CHECK-FIXES-NEXT: {{^}} typename U>{{$}}
+// CHECK-FIXES-NEXT: {{^}}void param_split_on_two_lines() requires ConsumeVariadic<T,{{$}}
+// CHECK-FIXES-NEXT: {{^}} U>::value {{{$}}
+
+
+////////////////////////////////
+// Negative tests
+////////////////////////////////
+
+template <typename U, typename Named = std::enable_if_t<U::some_value>>
+void param_has_name() {
+}
+
+template <typename U, typename = std::enable_if_t<U::some_value>, typename = int>
+void not_last_param() {
+}
+
+} // namespace enable_if_trailing_type_parameter
More information about the cfe-commits
mailing list