| 1 | //===--- UseConstraintsCheck.cpp - clang-tidy -----------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include "UseConstraintsCheck.h" |
| 10 | #include "clang/AST/ASTContext.h" |
| 11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 12 | #include "clang/Lex/Lexer.h" |
| 13 | |
| 14 | #include "../utils/LexerUtils.h" |
| 15 | |
| 16 | #include <optional> |
| 17 | #include <utility> |
| 18 | |
| 19 | using namespace clang::ast_matchers; |
| 20 | |
| 21 | namespace clang::tidy::modernize { |
| 22 | |
| 23 | struct EnableIfData { |
| 24 | TemplateSpecializationTypeLoc Loc; |
| 25 | TypeLoc Outer; |
| 26 | }; |
| 27 | |
| 28 | namespace { |
| 29 | AST_MATCHER(FunctionDecl, hasOtherDeclarations) { |
| 30 | auto It = Node.redecls_begin(); |
| 31 | auto EndIt = Node.redecls_end(); |
| 32 | |
| 33 | if (It == EndIt) |
| 34 | return false; |
| 35 | |
| 36 | ++It; |
| 37 | return It != EndIt; |
| 38 | } |
| 39 | } // namespace |
| 40 | |
| 41 | void UseConstraintsCheck::registerMatchers(MatchFinder *Finder) { |
| 42 | Finder->addMatcher( |
| 43 | NodeMatch: functionTemplateDecl( |
| 44 | // Skip external libraries included as system headers |
| 45 | unless(isExpansionInSystemHeader()), |
| 46 | has(functionDecl(unless(hasOtherDeclarations()), isDefinition(), |
| 47 | hasReturnTypeLoc(ReturnMatcher: typeLoc().bind(ID: "return" ))) |
| 48 | .bind(ID: "function" ))) |
| 49 | .bind(ID: "functionTemplate" ), |
| 50 | Action: this); |
| 51 | } |
| 52 | |
| 53 | static std::optional<TemplateSpecializationTypeLoc> |
| 54 | matchEnableIfSpecializationImplTypename(TypeLoc TheType) { |
| 55 | if (const auto Dep = TheType.getAs<DependentNameTypeLoc>()) { |
| 56 | const IdentifierInfo *Identifier = Dep.getTypePtr()->getIdentifier(); |
| 57 | ElaboratedTypeKeyword Keyword = Dep.getTypePtr()->getKeyword(); |
| 58 | if (!Identifier || Identifier->getName() != "type" || |
| 59 | (Keyword != ElaboratedTypeKeyword::Typename && |
| 60 | Keyword != ElaboratedTypeKeyword::None)) { |
| 61 | return std::nullopt; |
| 62 | } |
| 63 | TheType = Dep.getQualifierLoc().getTypeLoc(); |
| 64 | if (TheType.isNull()) |
| 65 | return std::nullopt; |
| 66 | } |
| 67 | |
| 68 | if (const auto SpecializationLoc = |
| 69 | TheType.getAs<TemplateSpecializationTypeLoc>()) { |
| 70 | |
| 71 | const auto *Specialization = |
| 72 | dyn_cast<TemplateSpecializationType>(SpecializationLoc.getTypePtr()); |
| 73 | if (!Specialization) |
| 74 | return std::nullopt; |
| 75 | |
| 76 | const TemplateDecl *TD = |
| 77 | Specialization->getTemplateName().getAsTemplateDecl(); |
| 78 | if (!TD || TD->getName() != "enable_if" ) |
| 79 | return std::nullopt; |
| 80 | |
| 81 | int NumArgs = SpecializationLoc.getNumArgs(); |
| 82 | if (NumArgs != 1 && NumArgs != 2) |
| 83 | return std::nullopt; |
| 84 | |
| 85 | return SpecializationLoc; |
| 86 | } |
| 87 | return std::nullopt; |
| 88 | } |
| 89 | |
| 90 | static std::optional<TemplateSpecializationTypeLoc> |
| 91 | matchEnableIfSpecializationImplTrait(TypeLoc TheType) { |
| 92 | if (const auto Elaborated = TheType.getAs<ElaboratedTypeLoc>()) |
| 93 | TheType = Elaborated.getNamedTypeLoc(); |
| 94 | |
| 95 | if (const auto SpecializationLoc = |
| 96 | TheType.getAs<TemplateSpecializationTypeLoc>()) { |
| 97 | |
| 98 | const auto *Specialization = |
| 99 | dyn_cast<TemplateSpecializationType>(SpecializationLoc.getTypePtr()); |
| 100 | if (!Specialization) |
| 101 | return std::nullopt; |
| 102 | |
| 103 | const TemplateDecl *TD = |
| 104 | Specialization->getTemplateName().getAsTemplateDecl(); |
| 105 | if (!TD || TD->getName() != "enable_if_t" ) |
| 106 | return std::nullopt; |
| 107 | |
| 108 | if (!Specialization->isTypeAlias()) |
| 109 | return std::nullopt; |
| 110 | |
| 111 | if (const auto *AliasedType = |
| 112 | dyn_cast<DependentNameType>(Specialization->getAliasedType())) { |
| 113 | ElaboratedTypeKeyword Keyword = AliasedType->getKeyword(); |
| 114 | if (AliasedType->getIdentifier()->getName() != "type" || |
| 115 | (Keyword != ElaboratedTypeKeyword::Typename && |
| 116 | Keyword != ElaboratedTypeKeyword::None)) { |
| 117 | return std::nullopt; |
| 118 | } |
| 119 | } else { |
| 120 | return std::nullopt; |
| 121 | } |
| 122 | int NumArgs = SpecializationLoc.getNumArgs(); |
| 123 | if (NumArgs != 1 && NumArgs != 2) |
| 124 | return std::nullopt; |
| 125 | |
| 126 | return SpecializationLoc; |
| 127 | } |
| 128 | return std::nullopt; |
| 129 | } |
| 130 | |
| 131 | static std::optional<TemplateSpecializationTypeLoc> |
| 132 | matchEnableIfSpecializationImpl(TypeLoc TheType) { |
| 133 | if (auto EnableIf = matchEnableIfSpecializationImplTypename(TheType)) |
| 134 | return EnableIf; |
| 135 | return matchEnableIfSpecializationImplTrait(TheType); |
| 136 | } |
| 137 | |
| 138 | static std::optional<EnableIfData> |
| 139 | matchEnableIfSpecialization(TypeLoc TheType) { |
| 140 | if (const auto Pointer = TheType.getAs<PointerTypeLoc>()) |
| 141 | TheType = Pointer.getPointeeLoc(); |
| 142 | else if (const auto Reference = TheType.getAs<ReferenceTypeLoc>()) |
| 143 | TheType = Reference.getPointeeLoc(); |
| 144 | if (const auto Qualified = TheType.getAs<QualifiedTypeLoc>()) |
| 145 | TheType = Qualified.getUnqualifiedLoc(); |
| 146 | |
| 147 | if (auto EnableIf = matchEnableIfSpecializationImpl(TheType)) |
| 148 | return EnableIfData{std::move(*EnableIf), TheType}; |
| 149 | return std::nullopt; |
| 150 | } |
| 151 | |
| 152 | static std::pair<std::optional<EnableIfData>, const Decl *> |
| 153 | matchTrailingTemplateParam(const FunctionTemplateDecl *FunctionTemplate) { |
| 154 | // For non-type trailing param, match very specifically |
| 155 | // 'template <..., enable_if_type<Condition, Type> = Default>' where |
| 156 | // enable_if_type is 'enable_if' or 'enable_if_t'. E.g., 'template <typename |
| 157 | // T, enable_if_t<is_same_v<T, bool>, int*> = nullptr> |
| 158 | // |
| 159 | // Otherwise, match a trailing default type arg. |
| 160 | // E.g., 'template <typename T, typename = enable_if_t<is_same_v<T, bool>>>' |
| 161 | |
| 162 | const TemplateParameterList *TemplateParams = |
| 163 | FunctionTemplate->getTemplateParameters(); |
| 164 | if (TemplateParams->size() == 0) |
| 165 | return {}; |
| 166 | |
| 167 | const NamedDecl *LastParam = |
| 168 | TemplateParams->getParam(Idx: TemplateParams->size() - 1); |
| 169 | if (const auto *LastTemplateParam = |
| 170 | dyn_cast<NonTypeTemplateParmDecl>(LastParam)) { |
| 171 | |
| 172 | if (!LastTemplateParam->hasDefaultArgument() || |
| 173 | !LastTemplateParam->getName().empty()) |
| 174 | return {}; |
| 175 | |
| 176 | return {matchEnableIfSpecialization( |
| 177 | LastTemplateParam->getTypeSourceInfo()->getTypeLoc()), |
| 178 | LastTemplateParam}; |
| 179 | } |
| 180 | if (const auto *LastTemplateParam = |
| 181 | dyn_cast<TemplateTypeParmDecl>(LastParam)) { |
| 182 | if (LastTemplateParam->hasDefaultArgument() && |
| 183 | LastTemplateParam->getIdentifier() == nullptr) { |
| 184 | return { |
| 185 | matchEnableIfSpecialization(LastTemplateParam->getDefaultArgument() |
| 186 | .getTypeSourceInfo() |
| 187 | ->getTypeLoc()), |
| 188 | LastTemplateParam}; |
| 189 | } |
| 190 | } |
| 191 | return {}; |
| 192 | } |
| 193 | |
| 194 | template <typename T> |
| 195 | static SourceLocation getRAngleFileLoc(const SourceManager &SM, |
| 196 | const T &Element) { |
| 197 | // getFileLoc handles the case where the RAngle loc is part of a synthesized |
| 198 | // '>>', which ends up allocating a 'scratch space' buffer in the source |
| 199 | // manager. |
| 200 | return SM.getFileLoc(Loc: Element.getRAngleLoc()); |
| 201 | } |
| 202 | |
| 203 | static SourceRange |
| 204 | getConditionRange(ASTContext &Context, |
| 205 | const TemplateSpecializationTypeLoc &EnableIf) { |
| 206 | // TemplateArgumentLoc's SourceRange End is the location of the last token |
| 207 | // (per UnqualifiedId docs). E.g., in `enable_if<AAA && BBB>`, the End |
| 208 | // location will be the first 'B' in 'BBB'. |
| 209 | const LangOptions &LangOpts = Context.getLangOpts(); |
| 210 | const SourceManager &SM = Context.getSourceManager(); |
| 211 | if (EnableIf.getNumArgs() > 1) { |
| 212 | TemplateArgumentLoc NextArg = EnableIf.getArgLoc(i: 1); |
| 213 | return {EnableIf.getLAngleLoc().getLocWithOffset(Offset: 1), |
| 214 | utils::lexer::findPreviousTokenKind( |
| 215 | Start: NextArg.getSourceRange().getBegin(), SM, LangOpts, TK: tok::comma)}; |
| 216 | } |
| 217 | |
| 218 | return {EnableIf.getLAngleLoc().getLocWithOffset(Offset: 1), |
| 219 | getRAngleFileLoc(SM, Element: EnableIf)}; |
| 220 | } |
| 221 | |
| 222 | static SourceRange getTypeRange(ASTContext &Context, |
| 223 | const TemplateSpecializationTypeLoc &EnableIf) { |
| 224 | TemplateArgumentLoc Arg = EnableIf.getArgLoc(i: 1); |
| 225 | const LangOptions &LangOpts = Context.getLangOpts(); |
| 226 | const SourceManager &SM = Context.getSourceManager(); |
| 227 | return {utils::lexer::findPreviousTokenKind(Start: Arg.getSourceRange().getBegin(), |
| 228 | SM, LangOpts, TK: tok::comma) |
| 229 | .getLocWithOffset(Offset: 1), |
| 230 | getRAngleFileLoc(SM, Element: EnableIf)}; |
| 231 | } |
| 232 | |
| 233 | // Returns the original source text of the second argument of a call to |
| 234 | // enable_if_t. E.g., in enable_if_t<Condition, TheType>, this function |
| 235 | // returns 'TheType'. |
| 236 | static std::optional<StringRef> |
| 237 | getTypeText(ASTContext &Context, |
| 238 | const TemplateSpecializationTypeLoc &EnableIf) { |
| 239 | if (EnableIf.getNumArgs() > 1) { |
| 240 | const LangOptions &LangOpts = Context.getLangOpts(); |
| 241 | const SourceManager &SM = Context.getSourceManager(); |
| 242 | bool Invalid = false; |
| 243 | StringRef Text = Lexer::getSourceText(Range: CharSourceRange::getCharRange( |
| 244 | R: getTypeRange(Context, EnableIf)), |
| 245 | SM, LangOpts, Invalid: &Invalid) |
| 246 | .trim(); |
| 247 | if (Invalid) |
| 248 | return std::nullopt; |
| 249 | |
| 250 | return Text; |
| 251 | } |
| 252 | |
| 253 | return "void" ; |
| 254 | } |
| 255 | |
| 256 | static std::optional<SourceLocation> |
| 257 | findInsertionForConstraint(const FunctionDecl *Function, ASTContext &Context) { |
| 258 | SourceManager &SM = Context.getSourceManager(); |
| 259 | const LangOptions &LangOpts = Context.getLangOpts(); |
| 260 | |
| 261 | if (const auto *Constructor = dyn_cast<CXXConstructorDecl>(Val: Function)) { |
| 262 | for (const CXXCtorInitializer *Init : Constructor->inits()) { |
| 263 | if (Init->getSourceOrder() == 0) |
| 264 | return utils::lexer::findPreviousTokenKind(Start: Init->getSourceLocation(), |
| 265 | SM, LangOpts, TK: tok::colon); |
| 266 | } |
| 267 | if (!Constructor->inits().empty()) |
| 268 | return std::nullopt; |
| 269 | } |
| 270 | if (Function->isDeleted()) { |
| 271 | SourceLocation FunctionEnd = Function->getSourceRange().getEnd(); |
| 272 | return utils::lexer::findNextAnyTokenKind(Start: FunctionEnd, SM, LangOpts, |
| 273 | TK: tok::equal, TKs: tok::equal); |
| 274 | } |
| 275 | const Stmt *Body = Function->getBody(); |
| 276 | if (!Body) |
| 277 | return std::nullopt; |
| 278 | |
| 279 | return Body->getBeginLoc(); |
| 280 | } |
| 281 | |
| 282 | static bool isPrimaryExpression(const Expr *Expression) { |
| 283 | // This function is an incomplete approximation of checking whether |
| 284 | // an Expr is a primary expression. In particular, if this function |
| 285 | // returns true, the expression is a primary expression. The converse |
| 286 | // is not necessarily true. |
| 287 | |
| 288 | if (const auto *Cast = dyn_cast<ImplicitCastExpr>(Val: Expression)) |
| 289 | Expression = Cast->getSubExprAsWritten(); |
| 290 | if (isa<ParenExpr, DependentScopeDeclRefExpr>(Val: Expression)) |
| 291 | return true; |
| 292 | |
| 293 | return false; |
| 294 | } |
| 295 | |
| 296 | // Return the original source text of an enable_if_t condition, i.e., the |
| 297 | // first template argument). For example, in |
| 298 | // 'enable_if_t<FirstCondition || SecondCondition, AType>', the text |
| 299 | // the text 'FirstCondition || SecondCondition' is returned. |
| 300 | static std::optional<std::string> getConditionText(const Expr *ConditionExpr, |
| 301 | SourceRange ConditionRange, |
| 302 | ASTContext &Context) { |
| 303 | SourceManager &SM = Context.getSourceManager(); |
| 304 | const LangOptions &LangOpts = Context.getLangOpts(); |
| 305 | |
| 306 | SourceLocation PrevTokenLoc = ConditionRange.getEnd(); |
| 307 | if (PrevTokenLoc.isInvalid()) |
| 308 | return std::nullopt; |
| 309 | |
| 310 | const bool = false; |
| 311 | Token PrevToken; |
| 312 | std::tie(args&: PrevToken, args&: PrevTokenLoc) = utils::lexer::getPreviousTokenAndStart( |
| 313 | Location: PrevTokenLoc, SM, LangOpts, SkipComments); |
| 314 | bool EndsWithDoubleSlash = |
| 315 | PrevToken.is(K: tok::comment) && |
| 316 | Lexer::getSourceText(Range: CharSourceRange::getCharRange( |
| 317 | B: PrevTokenLoc, E: PrevTokenLoc.getLocWithOffset(Offset: 2)), |
| 318 | SM, LangOpts) == "//" ; |
| 319 | |
| 320 | bool Invalid = false; |
| 321 | llvm::StringRef ConditionText = Lexer::getSourceText( |
| 322 | Range: CharSourceRange::getCharRange(R: ConditionRange), SM, LangOpts, Invalid: &Invalid); |
| 323 | if (Invalid) |
| 324 | return std::nullopt; |
| 325 | |
| 326 | auto AddParens = [&](llvm::StringRef Text) -> std::string { |
| 327 | if (isPrimaryExpression(Expression: ConditionExpr)) |
| 328 | return Text.str(); |
| 329 | return "(" + Text.str() + ")" ; |
| 330 | }; |
| 331 | |
| 332 | if (EndsWithDoubleSlash) |
| 333 | return AddParens(ConditionText); |
| 334 | return AddParens(ConditionText.trim()); |
| 335 | } |
| 336 | |
| 337 | // Handle functions that return enable_if_t, e.g., |
| 338 | // template <...> |
| 339 | // enable_if_t<Condition, ReturnType> function(); |
| 340 | // |
| 341 | // Return a vector of FixItHints if the code can be replaced with |
| 342 | // a C++20 requires clause. In the example above, returns FixItHints |
| 343 | // to result in |
| 344 | // template <...> |
| 345 | // ReturnType function() requires Condition {} |
| 346 | static std::vector<FixItHint> handleReturnType(const FunctionDecl *Function, |
| 347 | const TypeLoc &ReturnType, |
| 348 | const EnableIfData &EnableIf, |
| 349 | ASTContext &Context) { |
| 350 | TemplateArgumentLoc EnableCondition = EnableIf.Loc.getArgLoc(0); |
| 351 | |
| 352 | SourceRange ConditionRange = getConditionRange(Context, EnableIf.Loc); |
| 353 | |
| 354 | std::optional<std::string> ConditionText = getConditionText( |
| 355 | ConditionExpr: EnableCondition.getSourceExpression(), ConditionRange, Context); |
| 356 | if (!ConditionText) |
| 357 | return {}; |
| 358 | |
| 359 | std::optional<StringRef> TypeText = getTypeText(Context, EnableIf.Loc); |
| 360 | if (!TypeText) |
| 361 | return {}; |
| 362 | |
| 363 | SmallVector<AssociatedConstraint, 3> ExistingConstraints; |
| 364 | Function->getAssociatedConstraints(ACs&: ExistingConstraints); |
| 365 | if (!ExistingConstraints.empty()) { |
| 366 | // FIXME - Support adding new constraints to existing ones. Do we need to |
| 367 | // consider subsumption? |
| 368 | return {}; |
| 369 | } |
| 370 | |
| 371 | std::optional<SourceLocation> ConstraintInsertionLoc = |
| 372 | findInsertionForConstraint(Function, Context); |
| 373 | if (!ConstraintInsertionLoc) |
| 374 | return {}; |
| 375 | |
| 376 | std::vector<FixItHint> FixIts; |
| 377 | FixIts.push_back(x: FixItHint::CreateReplacement( |
| 378 | RemoveRange: CharSourceRange::getTokenRange(R: EnableIf.Outer.getSourceRange()), |
| 379 | Code: *TypeText)); |
| 380 | FixIts.push_back(x: FixItHint::CreateInsertion( |
| 381 | InsertionLoc: *ConstraintInsertionLoc, Code: "requires " + *ConditionText + " " )); |
| 382 | return FixIts; |
| 383 | } |
| 384 | |
| 385 | // Handle enable_if_t in a trailing template parameter, e.g., |
| 386 | // template <..., enable_if_t<Condition, Type> = Type{}> |
| 387 | // ReturnType function(); |
| 388 | // |
| 389 | // Return a vector of FixItHints if the code can be replaced with |
| 390 | // a C++20 requires clause. In the example above, returns FixItHints |
| 391 | // to result in |
| 392 | // template <...> |
| 393 | // ReturnType function() requires Condition {} |
| 394 | static std::vector<FixItHint> |
| 395 | handleTrailingTemplateType(const FunctionTemplateDecl *FunctionTemplate, |
| 396 | const FunctionDecl *Function, |
| 397 | const Decl *LastTemplateParam, |
| 398 | const EnableIfData &EnableIf, ASTContext &Context) { |
| 399 | SourceManager &SM = Context.getSourceManager(); |
| 400 | const LangOptions &LangOpts = Context.getLangOpts(); |
| 401 | |
| 402 | TemplateArgumentLoc EnableCondition = EnableIf.Loc.getArgLoc(0); |
| 403 | |
| 404 | SourceRange ConditionRange = getConditionRange(Context, EnableIf.Loc); |
| 405 | |
| 406 | std::optional<std::string> ConditionText = getConditionText( |
| 407 | ConditionExpr: EnableCondition.getSourceExpression(), ConditionRange, Context); |
| 408 | if (!ConditionText) |
| 409 | return {}; |
| 410 | |
| 411 | SmallVector<AssociatedConstraint, 3> ExistingConstraints; |
| 412 | Function->getAssociatedConstraints(ACs&: ExistingConstraints); |
| 413 | if (!ExistingConstraints.empty()) { |
| 414 | // FIXME - Support adding new constraints to existing ones. Do we need to |
| 415 | // consider subsumption? |
| 416 | return {}; |
| 417 | } |
| 418 | |
| 419 | SourceRange RemovalRange; |
| 420 | const TemplateParameterList *TemplateParams = |
| 421 | FunctionTemplate->getTemplateParameters(); |
| 422 | if (!TemplateParams || TemplateParams->size() == 0) |
| 423 | return {}; |
| 424 | |
| 425 | if (TemplateParams->size() == 1) { |
| 426 | RemovalRange = |
| 427 | SourceRange(TemplateParams->getTemplateLoc(), |
| 428 | getRAngleFileLoc(SM, Element: *TemplateParams).getLocWithOffset(Offset: 1)); |
| 429 | } else { |
| 430 | RemovalRange = |
| 431 | SourceRange(utils::lexer::findPreviousTokenKind( |
| 432 | Start: LastTemplateParam->getSourceRange().getBegin(), SM, |
| 433 | LangOpts, TK: tok::comma), |
| 434 | getRAngleFileLoc(SM, Element: *TemplateParams)); |
| 435 | } |
| 436 | |
| 437 | std::optional<SourceLocation> ConstraintInsertionLoc = |
| 438 | findInsertionForConstraint(Function, Context); |
| 439 | if (!ConstraintInsertionLoc) |
| 440 | return {}; |
| 441 | |
| 442 | std::vector<FixItHint> FixIts; |
| 443 | FixIts.push_back( |
| 444 | x: FixItHint::CreateRemoval(RemoveRange: CharSourceRange::getCharRange(R: RemovalRange))); |
| 445 | FixIts.push_back(x: FixItHint::CreateInsertion( |
| 446 | InsertionLoc: *ConstraintInsertionLoc, Code: "requires " + *ConditionText + " " )); |
| 447 | return FixIts; |
| 448 | } |
| 449 | |
| 450 | void UseConstraintsCheck::check(const MatchFinder::MatchResult &Result) { |
| 451 | const auto *FunctionTemplate = |
| 452 | Result.Nodes.getNodeAs<FunctionTemplateDecl>(ID: "functionTemplate" ); |
| 453 | const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(ID: "function" ); |
| 454 | const auto *ReturnType = Result.Nodes.getNodeAs<TypeLoc>(ID: "return" ); |
| 455 | if (!FunctionTemplate || !Function || !ReturnType) |
| 456 | return; |
| 457 | |
| 458 | // Check for |
| 459 | // |
| 460 | // Case 1. Return type of function |
| 461 | // |
| 462 | // template <...> |
| 463 | // enable_if_t<Condition, ReturnType>::type function() {} |
| 464 | // |
| 465 | // Case 2. Trailing template parameter |
| 466 | // |
| 467 | // template <..., enable_if_t<Condition, Type> = Type{}> |
| 468 | // ReturnType function() {} |
| 469 | // |
| 470 | // or |
| 471 | // |
| 472 | // template <..., typename = enable_if_t<Condition, void>> |
| 473 | // ReturnType function() {} |
| 474 | // |
| 475 | |
| 476 | // Case 1. Return type of function |
| 477 | if (auto EnableIf = matchEnableIfSpecialization(*ReturnType)) { |
| 478 | diag(Loc: ReturnType->getBeginLoc(), |
| 479 | Description: "use C++20 requires constraints instead of enable_if" ) |
| 480 | << handleReturnType(Function, *ReturnType, *EnableIf, *Result.Context); |
| 481 | return; |
| 482 | } |
| 483 | |
| 484 | // Case 2. Trailing template parameter |
| 485 | if (auto [EnableIf, LastTemplateParam] = |
| 486 | matchTrailingTemplateParam(FunctionTemplate); |
| 487 | EnableIf && LastTemplateParam) { |
| 488 | diag(LastTemplateParam->getSourceRange().getBegin(), |
| 489 | "use C++20 requires constraints instead of enable_if" ) |
| 490 | << handleTrailingTemplateType(FunctionTemplate, Function, |
| 491 | LastTemplateParam, *EnableIf, |
| 492 | *Result.Context); |
| 493 | return; |
| 494 | } |
| 495 | } |
| 496 | |
| 497 | } // namespace clang::tidy::modernize |
| 498 | |