brintos

brintos / llvm-project-archived public Read only

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