xref: /llvm-project/clang-tools-extra/clang-tidy/readability/QualifiedAutoCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
136fcbb83SNathan James //===--- QualifiedAutoCheck.cpp - clang-tidy ------------------------------===//
236fcbb83SNathan James //
336fcbb83SNathan James // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
436fcbb83SNathan James // See https://llvm.org/LICENSE.txt for license information.
536fcbb83SNathan James // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
636fcbb83SNathan James //
736fcbb83SNathan James //===----------------------------------------------------------------------===//
836fcbb83SNathan James 
936fcbb83SNathan James #include "QualifiedAutoCheck.h"
1036fcbb83SNathan James #include "../utils/LexerUtils.h"
1136fcbb83SNathan James #include "clang/ASTMatchers/ASTMatchers.h"
1236fcbb83SNathan James #include "llvm/ADT/SmallVector.h"
1371f55735SKazu Hirata #include <optional>
1436fcbb83SNathan James 
1536fcbb83SNathan James using namespace clang::ast_matchers;
1636fcbb83SNathan James 
17*7d2ea6c4SCarlos Galvez namespace clang::tidy::readability {
1836fcbb83SNathan James 
1936fcbb83SNathan James namespace {
2036fcbb83SNathan James 
2136fcbb83SNathan James // FIXME move to ASTMatchers
AST_MATCHER_P(QualType,hasUnqualifiedType,ast_matchers::internal::Matcher<QualType>,InnerMatcher)2236fcbb83SNathan James AST_MATCHER_P(QualType, hasUnqualifiedType,
2336fcbb83SNathan James               ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
2436fcbb83SNathan James   return InnerMatcher.matches(Node.getUnqualifiedType(), Finder, Builder);
2536fcbb83SNathan James }
2636fcbb83SNathan James 
2736fcbb83SNathan James enum class Qualifier { Const, Volatile, Restrict };
2836fcbb83SNathan James 
findQualToken(const VarDecl * Decl,Qualifier Qual,const MatchFinder::MatchResult & Result)29f71ffd3bSKazu Hirata std::optional<Token> findQualToken(const VarDecl *Decl, Qualifier Qual,
3036fcbb83SNathan James                                    const MatchFinder::MatchResult &Result) {
3136fcbb83SNathan James   // Since either of the locs can be in a macro, use `makeFileCharRange` to be
3236fcbb83SNathan James   // sure that we have a consistent `CharSourceRange`, located entirely in the
3336fcbb83SNathan James   // source file.
3436fcbb83SNathan James 
3525afe91fSSimon Pilgrim   assert((Qual == Qualifier::Const || Qual == Qualifier::Volatile ||
3625afe91fSSimon Pilgrim           Qual == Qualifier::Restrict) &&
3725afe91fSSimon Pilgrim          "Invalid Qualifier");
3836fcbb83SNathan James 
3936fcbb83SNathan James   SourceLocation BeginLoc = Decl->getQualifierLoc().getBeginLoc();
4036fcbb83SNathan James   if (BeginLoc.isInvalid())
4136fcbb83SNathan James     BeginLoc = Decl->getBeginLoc();
4236fcbb83SNathan James   SourceLocation EndLoc = Decl->getLocation();
4336fcbb83SNathan James 
4436fcbb83SNathan James   CharSourceRange FileRange = Lexer::makeFileCharRange(
4536fcbb83SNathan James       CharSourceRange::getCharRange(BeginLoc, EndLoc), *Result.SourceManager,
4636fcbb83SNathan James       Result.Context->getLangOpts());
4736fcbb83SNathan James 
4836fcbb83SNathan James   if (FileRange.isInvalid())
49cd8702efSKazu Hirata     return std::nullopt;
5036fcbb83SNathan James 
5136fcbb83SNathan James   tok::TokenKind Tok =
5236fcbb83SNathan James       Qual == Qualifier::Const
5336fcbb83SNathan James           ? tok::kw_const
5436fcbb83SNathan James           : Qual == Qualifier::Volatile ? tok::kw_volatile : tok::kw_restrict;
5536fcbb83SNathan James 
5636fcbb83SNathan James   return utils::lexer::getQualifyingToken(Tok, FileRange, *Result.Context,
5736fcbb83SNathan James                                           *Result.SourceManager);
5836fcbb83SNathan James }
5936fcbb83SNathan James 
60f71ffd3bSKazu Hirata std::optional<SourceRange>
getTypeSpecifierLocation(const VarDecl * Var,const MatchFinder::MatchResult & Result)6136fcbb83SNathan James getTypeSpecifierLocation(const VarDecl *Var,
6236fcbb83SNathan James                          const MatchFinder::MatchResult &Result) {
6336fcbb83SNathan James   SourceRange TypeSpecifier(
6436fcbb83SNathan James       Var->getTypeSpecStartLoc(),
6536fcbb83SNathan James       Var->getTypeSpecEndLoc().getLocWithOffset(Lexer::MeasureTokenLength(
6636fcbb83SNathan James           Var->getTypeSpecEndLoc(), *Result.SourceManager,
6736fcbb83SNathan James           Result.Context->getLangOpts())));
6836fcbb83SNathan James 
6936fcbb83SNathan James   if (TypeSpecifier.getBegin().isMacroID() ||
7036fcbb83SNathan James       TypeSpecifier.getEnd().isMacroID())
71cd8702efSKazu Hirata     return std::nullopt;
7236fcbb83SNathan James   return TypeSpecifier;
7336fcbb83SNathan James }
7436fcbb83SNathan James 
mergeReplacementRange(SourceRange & TypeSpecifier,const Token & ConstToken)75f71ffd3bSKazu Hirata std::optional<SourceRange> mergeReplacementRange(SourceRange &TypeSpecifier,
7636fcbb83SNathan James                                                  const Token &ConstToken) {
7736fcbb83SNathan James   if (TypeSpecifier.getBegin().getLocWithOffset(-1) == ConstToken.getEndLoc()) {
7836fcbb83SNathan James     TypeSpecifier.setBegin(ConstToken.getLocation());
79cd8702efSKazu Hirata     return std::nullopt;
8036fcbb83SNathan James   }
8136fcbb83SNathan James   if (TypeSpecifier.getEnd().getLocWithOffset(1) == ConstToken.getLocation()) {
8236fcbb83SNathan James     TypeSpecifier.setEnd(ConstToken.getEndLoc());
83cd8702efSKazu Hirata     return std::nullopt;
8436fcbb83SNathan James   }
8536fcbb83SNathan James   return SourceRange(ConstToken.getLocation(), ConstToken.getEndLoc());
8636fcbb83SNathan James }
8736fcbb83SNathan James 
isPointerConst(QualType QType)8836fcbb83SNathan James bool isPointerConst(QualType QType) {
8936fcbb83SNathan James   QualType Pointee = QType->getPointeeType();
9036fcbb83SNathan James   assert(!Pointee.isNull() && "can't have a null Pointee");
9136fcbb83SNathan James   return Pointee.isConstQualified();
9236fcbb83SNathan James }
9336fcbb83SNathan James 
isAutoPointerConst(QualType QType)9436fcbb83SNathan James bool isAutoPointerConst(QualType QType) {
9536fcbb83SNathan James   QualType Pointee =
9636fcbb83SNathan James       cast<AutoType>(QType->getPointeeType().getTypePtr())->desugar();
9736fcbb83SNathan James   assert(!Pointee.isNull() && "can't have a null Pointee");
9836fcbb83SNathan James   return Pointee.isConstQualified();
9936fcbb83SNathan James }
10036fcbb83SNathan James 
10136fcbb83SNathan James } // namespace
10236fcbb83SNathan James 
storeOptions(ClangTidyOptions::OptionMap & Opts)1038a68c40aSNathan James void QualifiedAutoCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
1048a68c40aSNathan James   Options.store(Opts, "AddConstToQualified", AddConstToQualified);
1058a68c40aSNathan James }
1068a68c40aSNathan James 
registerMatchers(MatchFinder * Finder)10736fcbb83SNathan James void QualifiedAutoCheck::registerMatchers(MatchFinder *Finder) {
10836fcbb83SNathan James   auto ExplicitSingleVarDecl =
10936fcbb83SNathan James       [](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher,
11036fcbb83SNathan James          llvm::StringRef ID) {
11136fcbb83SNathan James         return declStmt(
11236fcbb83SNathan James             unless(isInTemplateInstantiation()),
11336fcbb83SNathan James             hasSingleDecl(
11436fcbb83SNathan James                 varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));
11536fcbb83SNathan James       };
11636fcbb83SNathan James   auto ExplicitSingleVarDeclInTemplate =
11736fcbb83SNathan James       [](const ast_matchers::internal::Matcher<VarDecl> &InnerMatcher,
11836fcbb83SNathan James          llvm::StringRef ID) {
11936fcbb83SNathan James         return declStmt(
12036fcbb83SNathan James             isInTemplateInstantiation(),
12136fcbb83SNathan James             hasSingleDecl(
12236fcbb83SNathan James                 varDecl(unless(isImplicit()), InnerMatcher).bind(ID)));
12336fcbb83SNathan James       };
12436fcbb83SNathan James 
12536fcbb83SNathan James   auto IsBoundToType = refersToType(equalsBoundNode("type"));
126c9e46219SMatheus Izvekov   auto UnlessFunctionType = unless(hasUnqualifiedDesugaredType(functionType()));
127c9e46219SMatheus Izvekov   auto IsAutoDeducedToPointer = [](const auto &...InnerMatchers) {
128c9e46219SMatheus Izvekov     return autoType(hasDeducedType(
129c9e46219SMatheus Izvekov         hasUnqualifiedDesugaredType(pointerType(pointee(InnerMatchers...)))));
130c9e46219SMatheus Izvekov   };
13136fcbb83SNathan James 
13236fcbb83SNathan James   Finder->addMatcher(
133c9e46219SMatheus Izvekov       ExplicitSingleVarDecl(hasType(IsAutoDeducedToPointer(UnlessFunctionType)),
13436fcbb83SNathan James                             "auto"),
13536fcbb83SNathan James       this);
13636fcbb83SNathan James 
13736fcbb83SNathan James   Finder->addMatcher(
13836fcbb83SNathan James       ExplicitSingleVarDeclInTemplate(
139c9e46219SMatheus Izvekov           allOf(hasType(IsAutoDeducedToPointer(
140c9e46219SMatheus Izvekov                     hasUnqualifiedType(qualType().bind("type")),
141c9e46219SMatheus Izvekov                     UnlessFunctionType)),
14236fcbb83SNathan James                 anyOf(hasAncestor(
14336fcbb83SNathan James                           functionDecl(hasAnyTemplateArgument(IsBoundToType))),
14436fcbb83SNathan James                       hasAncestor(classTemplateSpecializationDecl(
14536fcbb83SNathan James                           hasAnyTemplateArgument(IsBoundToType))))),
14636fcbb83SNathan James           "auto"),
14736fcbb83SNathan James       this);
1488a68c40aSNathan James   if (!AddConstToQualified)
1498a68c40aSNathan James     return;
15036fcbb83SNathan James   Finder->addMatcher(ExplicitSingleVarDecl(
15136fcbb83SNathan James                          hasType(pointerType(pointee(autoType()))), "auto_ptr"),
15236fcbb83SNathan James                      this);
15336fcbb83SNathan James   Finder->addMatcher(
15436fcbb83SNathan James       ExplicitSingleVarDecl(hasType(lValueReferenceType(pointee(autoType()))),
15536fcbb83SNathan James                             "auto_ref"),
15636fcbb83SNathan James       this);
15736fcbb83SNathan James }
15836fcbb83SNathan James 
check(const MatchFinder::MatchResult & Result)15936fcbb83SNathan James void QualifiedAutoCheck::check(const MatchFinder::MatchResult &Result) {
16036fcbb83SNathan James   if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto")) {
16136fcbb83SNathan James     SourceRange TypeSpecifier;
162f71ffd3bSKazu Hirata     if (std::optional<SourceRange> TypeSpec =
16336fcbb83SNathan James             getTypeSpecifierLocation(Var, Result)) {
16436fcbb83SNathan James       TypeSpecifier = *TypeSpec;
16536fcbb83SNathan James     } else
16636fcbb83SNathan James       return;
16736fcbb83SNathan James 
16836fcbb83SNathan James     llvm::SmallVector<SourceRange, 4> RemoveQualifiersRange;
16936fcbb83SNathan James     auto CheckQualifier = [&](bool IsPresent, Qualifier Qual) {
17036fcbb83SNathan James       if (IsPresent) {
171f71ffd3bSKazu Hirata         std::optional<Token> Token = findQualToken(Var, Qual, Result);
17236fcbb83SNathan James         if (!Token || Token->getLocation().isMacroID())
17336fcbb83SNathan James           return true; // Disregard this VarDecl.
174f71ffd3bSKazu Hirata         if (std::optional<SourceRange> Result =
17536fcbb83SNathan James                 mergeReplacementRange(TypeSpecifier, *Token))
17636fcbb83SNathan James           RemoveQualifiersRange.push_back(*Result);
17736fcbb83SNathan James       }
17836fcbb83SNathan James       return false;
17936fcbb83SNathan James     };
18036fcbb83SNathan James 
18136fcbb83SNathan James     bool IsLocalConst = Var->getType().isLocalConstQualified();
18236fcbb83SNathan James     bool IsLocalVolatile = Var->getType().isLocalVolatileQualified();
18336fcbb83SNathan James     bool IsLocalRestrict = Var->getType().isLocalRestrictQualified();
18436fcbb83SNathan James 
1858a68c40aSNathan James     if (CheckQualifier(IsLocalConst, Qualifier::Const) ||
1868a68c40aSNathan James         CheckQualifier(IsLocalVolatile, Qualifier::Volatile) ||
1878a68c40aSNathan James         CheckQualifier(IsLocalRestrict, Qualifier::Restrict))
18836fcbb83SNathan James       return;
18936fcbb83SNathan James 
19036fcbb83SNathan James     // Check for bridging the gap between the asterisk and name.
19136fcbb83SNathan James     if (Var->getLocation() == TypeSpecifier.getEnd().getLocWithOffset(1))
19236fcbb83SNathan James       TypeSpecifier.setEnd(TypeSpecifier.getEnd().getLocWithOffset(1));
19336fcbb83SNathan James 
19436fcbb83SNathan James     CharSourceRange FixItRange = CharSourceRange::getCharRange(TypeSpecifier);
19536fcbb83SNathan James     if (FixItRange.isInvalid())
19636fcbb83SNathan James       return;
19736fcbb83SNathan James 
19836fcbb83SNathan James     SourceLocation FixitLoc = FixItRange.getBegin();
19936fcbb83SNathan James     for (SourceRange &Range : RemoveQualifiersRange) {
20036fcbb83SNathan James       if (Range.getBegin() < FixitLoc)
20136fcbb83SNathan James         FixitLoc = Range.getBegin();
20236fcbb83SNathan James     }
20336fcbb83SNathan James 
20436fcbb83SNathan James     std::string ReplStr = [&] {
20536fcbb83SNathan James       llvm::StringRef PtrConst = isPointerConst(Var->getType()) ? "const " : "";
20636fcbb83SNathan James       llvm::StringRef LocalConst = IsLocalConst ? "const " : "";
20736fcbb83SNathan James       llvm::StringRef LocalVol = IsLocalVolatile ? "volatile " : "";
20836fcbb83SNathan James       llvm::StringRef LocalRestrict = IsLocalRestrict ? "__restrict " : "";
20936fcbb83SNathan James       return (PtrConst + "auto *" + LocalConst + LocalVol + LocalRestrict)
21036fcbb83SNathan James           .str();
21136fcbb83SNathan James     }();
21236fcbb83SNathan James 
21336fcbb83SNathan James     DiagnosticBuilder Diag =
2141a721b6aSNathan James         diag(FixitLoc,
2151a721b6aSNathan James              "'%select{|const }0%select{|volatile }1%select{|__restrict }2auto "
2161a721b6aSNathan James              "%3' can be declared as '%4%3'")
2171a721b6aSNathan James         << IsLocalConst << IsLocalVolatile << IsLocalRestrict << Var->getName()
2181a721b6aSNathan James         << ReplStr;
21936fcbb83SNathan James 
22036fcbb83SNathan James     for (SourceRange &Range : RemoveQualifiersRange) {
2218a68c40aSNathan James       Diag << FixItHint::CreateRemoval(CharSourceRange::getCharRange(Range));
22236fcbb83SNathan James     }
22336fcbb83SNathan James 
22436fcbb83SNathan James     Diag << FixItHint::CreateReplacement(FixItRange, ReplStr);
22536fcbb83SNathan James     return;
22636fcbb83SNathan James   }
22736fcbb83SNathan James   if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ptr")) {
22836fcbb83SNathan James     if (!isPointerConst(Var->getType()))
22936fcbb83SNathan James       return; // Pointer isn't const, no need to add const qualifier.
23036fcbb83SNathan James     if (!isAutoPointerConst(Var->getType()))
231ade0662cSSalman Javed       return; // Const isn't wrapped in the auto type, so must be declared
23236fcbb83SNathan James               // explicitly.
23336fcbb83SNathan James 
23436fcbb83SNathan James     if (Var->getType().isLocalConstQualified()) {
235f71ffd3bSKazu Hirata       std::optional<Token> Token = findQualToken(Var, Qualifier::Const, Result);
23636fcbb83SNathan James       if (!Token || Token->getLocation().isMacroID())
23736fcbb83SNathan James         return;
23836fcbb83SNathan James     }
23936fcbb83SNathan James     if (Var->getType().isLocalVolatileQualified()) {
240f71ffd3bSKazu Hirata       std::optional<Token> Token =
24136fcbb83SNathan James           findQualToken(Var, Qualifier::Volatile, Result);
24236fcbb83SNathan James       if (!Token || Token->getLocation().isMacroID())
24336fcbb83SNathan James         return;
24436fcbb83SNathan James     }
24536fcbb83SNathan James     if (Var->getType().isLocalRestrictQualified()) {
246f71ffd3bSKazu Hirata       std::optional<Token> Token =
24736fcbb83SNathan James           findQualToken(Var, Qualifier::Restrict, Result);
24836fcbb83SNathan James       if (!Token || Token->getLocation().isMacroID())
24936fcbb83SNathan James         return;
25036fcbb83SNathan James     }
25136fcbb83SNathan James 
252f71ffd3bSKazu Hirata     if (std::optional<SourceRange> TypeSpec =
25336fcbb83SNathan James             getTypeSpecifierLocation(Var, Result)) {
2548a68c40aSNathan James       if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
2558a68c40aSNathan James           TypeSpec->getEnd().isMacroID())
25636fcbb83SNathan James         return;
2578a68c40aSNathan James       SourceLocation InsertPos = TypeSpec->getBegin();
2581a721b6aSNathan James       diag(InsertPos,
2591a721b6aSNathan James            "'auto *%select{|const }0%select{|volatile }1%2' can be declared as "
2601a721b6aSNathan James            "'const auto *%select{|const }0%select{|volatile }1%2'")
2611a721b6aSNathan James           << Var->getType().isLocalConstQualified()
2621a721b6aSNathan James           << Var->getType().isLocalVolatileQualified() << Var->getName()
2631a721b6aSNathan James           << FixItHint::CreateInsertion(InsertPos, "const ");
2648a68c40aSNathan James     }
26536fcbb83SNathan James     return;
26636fcbb83SNathan James   }
26736fcbb83SNathan James   if (const auto *Var = Result.Nodes.getNodeAs<VarDecl>("auto_ref")) {
26836fcbb83SNathan James     if (!isPointerConst(Var->getType()))
26936fcbb83SNathan James       return; // Pointer isn't const, no need to add const qualifier.
27036fcbb83SNathan James     if (!isAutoPointerConst(Var->getType()))
271ade0662cSSalman Javed       // Const isn't wrapped in the auto type, so must be declared explicitly.
27236fcbb83SNathan James       return;
27336fcbb83SNathan James 
274f71ffd3bSKazu Hirata     if (std::optional<SourceRange> TypeSpec =
27536fcbb83SNathan James             getTypeSpecifierLocation(Var, Result)) {
2768a68c40aSNathan James       if (TypeSpec->isInvalid() || TypeSpec->getBegin().isMacroID() ||
2778a68c40aSNathan James           TypeSpec->getEnd().isMacroID())
27836fcbb83SNathan James         return;
2798a68c40aSNathan James       SourceLocation InsertPos = TypeSpec->getBegin();
2808a68c40aSNathan James       diag(InsertPos, "'auto &%0' can be declared as 'const auto &%0'")
2818a68c40aSNathan James           << Var->getName() << FixItHint::CreateInsertion(InsertPos, "const ");
2828a68c40aSNathan James     }
28336fcbb83SNathan James     return;
28436fcbb83SNathan James   }
28536fcbb83SNathan James }
28636fcbb83SNathan James 
287*7d2ea6c4SCarlos Galvez } // namespace clang::tidy::readability
288