xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseDesignatedInitializersCheck.cpp (revision 27ef549af2c2f60d05f38db1ecc7a8ad7294351d)
1525fe449SDanny Mösch //===--- UseDesignatedInitializersCheck.cpp - clang-tidy ------------------===//
2525fe449SDanny Mösch //
3525fe449SDanny Mösch // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4525fe449SDanny Mösch // See https://llvm.org/LICENSE.txt for license information.
5525fe449SDanny Mösch // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6525fe449SDanny Mösch //
7525fe449SDanny Mösch //===----------------------------------------------------------------------===//
8525fe449SDanny Mösch 
9525fe449SDanny Mösch #include "UseDesignatedInitializersCheck.h"
10525fe449SDanny Mösch #include "../utils/DesignatedInitializers.h"
11525fe449SDanny Mösch #include "clang/AST/APValue.h"
12525fe449SDanny Mösch #include "clang/AST/Decl.h"
13525fe449SDanny Mösch #include "clang/AST/Expr.h"
14525fe449SDanny Mösch #include "clang/AST/Stmt.h"
15525fe449SDanny Mösch #include "clang/ASTMatchers/ASTMatchFinder.h"
16525fe449SDanny Mösch #include "clang/ASTMatchers/ASTMatchers.h"
17525fe449SDanny Mösch #include "clang/ASTMatchers/ASTMatchersMacros.h"
18525fe449SDanny Mösch #include "clang/Basic/Diagnostic.h"
19525fe449SDanny Mösch #include "clang/Lex/Lexer.h"
20525fe449SDanny Mösch 
21525fe449SDanny Mösch using namespace clang::ast_matchers;
22525fe449SDanny Mösch 
23525fe449SDanny Mösch namespace clang::tidy::modernize {
24525fe449SDanny Mösch 
25525fe449SDanny Mösch static constexpr char IgnoreSingleElementAggregatesName[] =
26525fe449SDanny Mösch     "IgnoreSingleElementAggregates";
27525fe449SDanny Mösch static constexpr bool IgnoreSingleElementAggregatesDefault = true;
28525fe449SDanny Mösch 
29525fe449SDanny Mösch static constexpr char RestrictToPODTypesName[] = "RestrictToPODTypes";
30525fe449SDanny Mösch static constexpr bool RestrictToPODTypesDefault = false;
31525fe449SDanny Mösch 
32525fe449SDanny Mösch static constexpr char IgnoreMacrosName[] = "IgnoreMacros";
33525fe449SDanny Mösch static constexpr bool IgnoreMacrosDefault = true;
34525fe449SDanny Mösch 
3531b84d45SPiotr Zegar static constexpr char StrictCStandardComplianceName[] =
3631b84d45SPiotr Zegar     "StrictCStandardCompliance";
3731b84d45SPiotr Zegar static constexpr bool StrictCStandardComplianceDefault = true;
3831b84d45SPiotr Zegar 
3931b84d45SPiotr Zegar static constexpr char StrictCppStandardComplianceName[] =
4031b84d45SPiotr Zegar     "StrictCppStandardCompliance";
4131b84d45SPiotr Zegar static constexpr bool StrictCppStandardComplianceDefault = true;
4231b84d45SPiotr Zegar 
43525fe449SDanny Mösch namespace {
44525fe449SDanny Mösch 
45525fe449SDanny Mösch struct Designators {
46525fe449SDanny Mösch 
47525fe449SDanny Mösch   Designators(const InitListExpr *InitList) : InitList(InitList) {
48525fe449SDanny Mösch     assert(InitList->isSyntacticForm());
49525fe449SDanny Mösch   };
50525fe449SDanny Mösch 
51525fe449SDanny Mösch   unsigned size() { return getCached().size(); }
52525fe449SDanny Mösch 
53525fe449SDanny Mösch   std::optional<llvm::StringRef> operator[](const SourceLocation &Location) {
5421be2fbdSPiotr Zegar     const auto &Designators = getCached();
55525fe449SDanny Mösch     const auto Result = Designators.find(Location);
56525fe449SDanny Mösch     if (Result == Designators.end())
57525fe449SDanny Mösch       return {};
58525fe449SDanny Mösch     const llvm::StringRef Designator = Result->getSecond();
59525fe449SDanny Mösch     return (Designator.front() == '.' ? Designator.substr(1) : Designator)
60525fe449SDanny Mösch         .trim("\0"); // Trim NULL characters appearing on Windows in the
61525fe449SDanny Mösch                      // name.
62525fe449SDanny Mösch   }
63525fe449SDanny Mösch 
64525fe449SDanny Mösch private:
65525fe449SDanny Mösch   using LocationToNameMap = llvm::DenseMap<clang::SourceLocation, std::string>;
66525fe449SDanny Mösch 
67525fe449SDanny Mösch   std::optional<LocationToNameMap> CachedDesignators;
68525fe449SDanny Mösch   const InitListExpr *InitList;
69525fe449SDanny Mösch 
70525fe449SDanny Mösch   LocationToNameMap &getCached() {
71525fe449SDanny Mösch     return CachedDesignators ? *CachedDesignators
72525fe449SDanny Mösch                              : CachedDesignators.emplace(
73525fe449SDanny Mösch                                    utils::getUnwrittenDesignators(InitList));
74525fe449SDanny Mösch   }
75525fe449SDanny Mösch };
76525fe449SDanny Mösch 
77525fe449SDanny Mösch unsigned getNumberOfDesignated(const InitListExpr *SyntacticInitList) {
78525fe449SDanny Mösch   return llvm::count_if(*SyntacticInitList, [](auto *InitExpr) {
79525fe449SDanny Mösch     return isa<DesignatedInitExpr>(InitExpr);
80525fe449SDanny Mösch   });
81525fe449SDanny Mösch }
82525fe449SDanny Mösch 
83*27ef549aSz1nke AST_MATCHER(CXXRecordDecl, isAggregate) {
84*27ef549aSz1nke   return Node.hasDefinition() && Node.isAggregate();
85*27ef549aSz1nke }
86525fe449SDanny Mösch 
87*27ef549aSz1nke AST_MATCHER(CXXRecordDecl, isPOD) {
88*27ef549aSz1nke   return Node.hasDefinition() && Node.isPOD();
89*27ef549aSz1nke }
90525fe449SDanny Mösch 
91525fe449SDanny Mösch AST_MATCHER(InitListExpr, isFullyDesignated) {
92525fe449SDanny Mösch   if (const InitListExpr *SyntacticForm =
93525fe449SDanny Mösch           Node.isSyntacticForm() ? &Node : Node.getSyntacticForm())
94525fe449SDanny Mösch     return getNumberOfDesignated(SyntacticForm) == SyntacticForm->getNumInits();
95525fe449SDanny Mösch   return true;
96525fe449SDanny Mösch }
97525fe449SDanny Mösch 
98525fe449SDanny Mösch AST_MATCHER(InitListExpr, hasMoreThanOneElement) {
99525fe449SDanny Mösch   return Node.getNumInits() > 1;
100525fe449SDanny Mösch }
101525fe449SDanny Mösch 
102525fe449SDanny Mösch } // namespace
103525fe449SDanny Mösch 
104525fe449SDanny Mösch UseDesignatedInitializersCheck::UseDesignatedInitializersCheck(
105525fe449SDanny Mösch     StringRef Name, ClangTidyContext *Context)
106525fe449SDanny Mösch     : ClangTidyCheck(Name, Context), IgnoreSingleElementAggregates(Options.get(
107525fe449SDanny Mösch                                          IgnoreSingleElementAggregatesName,
108525fe449SDanny Mösch                                          IgnoreSingleElementAggregatesDefault)),
109525fe449SDanny Mösch       RestrictToPODTypes(
110525fe449SDanny Mösch           Options.get(RestrictToPODTypesName, RestrictToPODTypesDefault)),
111525fe449SDanny Mösch       IgnoreMacros(
11231b84d45SPiotr Zegar           Options.getLocalOrGlobal(IgnoreMacrosName, IgnoreMacrosDefault)),
11331b84d45SPiotr Zegar       StrictCStandardCompliance(Options.get(StrictCStandardComplianceName,
11431b84d45SPiotr Zegar                                             StrictCStandardComplianceDefault)),
11531b84d45SPiotr Zegar       StrictCppStandardCompliance(
11631b84d45SPiotr Zegar           Options.get(StrictCppStandardComplianceName,
11731b84d45SPiotr Zegar                       StrictCppStandardComplianceDefault)) {}
118525fe449SDanny Mösch 
119525fe449SDanny Mösch void UseDesignatedInitializersCheck::registerMatchers(MatchFinder *Finder) {
120525fe449SDanny Mösch   const auto HasBaseWithFields =
121525fe449SDanny Mösch       hasAnyBase(hasType(cxxRecordDecl(has(fieldDecl()))));
122525fe449SDanny Mösch   Finder->addMatcher(
123525fe449SDanny Mösch       initListExpr(
124525fe449SDanny Mösch           hasType(cxxRecordDecl(RestrictToPODTypes ? isPOD() : isAggregate(),
125525fe449SDanny Mösch                                 unless(HasBaseWithFields))
126525fe449SDanny Mösch                       .bind("type")),
127525fe449SDanny Mösch           IgnoreSingleElementAggregates ? hasMoreThanOneElement() : anything(),
128525fe449SDanny Mösch           unless(isFullyDesignated()))
129525fe449SDanny Mösch           .bind("init"),
130525fe449SDanny Mösch       this);
131525fe449SDanny Mösch }
132525fe449SDanny Mösch 
133525fe449SDanny Mösch void UseDesignatedInitializersCheck::check(
134525fe449SDanny Mösch     const MatchFinder::MatchResult &Result) {
135525fe449SDanny Mösch   const auto *InitList = Result.Nodes.getNodeAs<InitListExpr>("init");
136525fe449SDanny Mösch   const auto *Type = Result.Nodes.getNodeAs<CXXRecordDecl>("type");
137525fe449SDanny Mösch   if (!Type || !InitList)
138525fe449SDanny Mösch     return;
139525fe449SDanny Mösch   const auto *SyntacticInitList = InitList->getSyntacticForm();
140525fe449SDanny Mösch   if (!SyntacticInitList)
141525fe449SDanny Mösch     return;
142525fe449SDanny Mösch   Designators Designators{SyntacticInitList};
143525fe449SDanny Mösch   const unsigned NumberOfDesignated = getNumberOfDesignated(SyntacticInitList);
144525fe449SDanny Mösch   if (SyntacticInitList->getNumInits() - NumberOfDesignated >
145525fe449SDanny Mösch       Designators.size())
146525fe449SDanny Mösch     return;
147525fe449SDanny Mösch 
148525fe449SDanny Mösch   // If the whole initializer list is un-designated, issue only one warning and
149525fe449SDanny Mösch   // a single fix-it for the whole expression.
150525fe449SDanny Mösch   if (0 == NumberOfDesignated) {
151525fe449SDanny Mösch     if (IgnoreMacros && InitList->getBeginLoc().isMacroID())
152525fe449SDanny Mösch       return;
153525fe449SDanny Mösch     {
154525fe449SDanny Mösch       DiagnosticBuilder Diag =
155525fe449SDanny Mösch           diag(InitList->getLBraceLoc(),
156525fe449SDanny Mösch                "use designated initializer list to initialize %0");
157525fe449SDanny Mösch       Diag << Type << InitList->getSourceRange();
158525fe449SDanny Mösch       for (const Stmt *InitExpr : *SyntacticInitList) {
159525fe449SDanny Mösch         const auto Designator = Designators[InitExpr->getBeginLoc()];
160525fe449SDanny Mösch         if (Designator && !Designator->empty())
161525fe449SDanny Mösch           Diag << FixItHint::CreateInsertion(InitExpr->getBeginLoc(),
162525fe449SDanny Mösch                                              ("." + *Designator + "=").str());
163525fe449SDanny Mösch       }
164525fe449SDanny Mösch     }
165525fe449SDanny Mösch     diag(Type->getBeginLoc(), "aggregate type is defined here",
166525fe449SDanny Mösch          DiagnosticIDs::Note);
167525fe449SDanny Mösch     return;
168525fe449SDanny Mösch   }
169525fe449SDanny Mösch 
170525fe449SDanny Mösch   // In case that only a few elements are un-designated (not all as before), the
171525fe449SDanny Mösch   // check offers dedicated issues and fix-its for each of them.
172525fe449SDanny Mösch   for (const auto *InitExpr : *SyntacticInitList) {
173525fe449SDanny Mösch     if (isa<DesignatedInitExpr>(InitExpr))
174525fe449SDanny Mösch       continue;
175525fe449SDanny Mösch     if (IgnoreMacros && InitExpr->getBeginLoc().isMacroID())
176525fe449SDanny Mösch       continue;
177525fe449SDanny Mösch     const auto Designator = Designators[InitExpr->getBeginLoc()];
178525fe449SDanny Mösch     if (!Designator || Designator->empty()) {
179525fe449SDanny Mösch       // There should always be a designator. If there's unexpectedly none, we
180525fe449SDanny Mösch       // at least report a generic diagnostic.
181525fe449SDanny Mösch       diag(InitExpr->getBeginLoc(), "use designated init expression")
182525fe449SDanny Mösch           << InitExpr->getSourceRange();
183525fe449SDanny Mösch     } else {
184525fe449SDanny Mösch       diag(InitExpr->getBeginLoc(),
185525fe449SDanny Mösch            "use designated init expression to initialize field '%0'")
186525fe449SDanny Mösch           << InitExpr->getSourceRange() << *Designator
187525fe449SDanny Mösch           << FixItHint::CreateInsertion(InitExpr->getBeginLoc(),
188525fe449SDanny Mösch                                         ("." + *Designator + "=").str());
189525fe449SDanny Mösch     }
190525fe449SDanny Mösch   }
191525fe449SDanny Mösch }
192525fe449SDanny Mösch 
193525fe449SDanny Mösch void UseDesignatedInitializersCheck::storeOptions(
194525fe449SDanny Mösch     ClangTidyOptions::OptionMap &Opts) {
195525fe449SDanny Mösch   Options.store(Opts, IgnoreSingleElementAggregatesName,
196525fe449SDanny Mösch                 IgnoreSingleElementAggregates);
197525fe449SDanny Mösch   Options.store(Opts, RestrictToPODTypesName, RestrictToPODTypes);
198525fe449SDanny Mösch   Options.store(Opts, IgnoreMacrosName, IgnoreMacros);
19931b84d45SPiotr Zegar   Options.store(Opts, StrictCStandardComplianceName, StrictCStandardCompliance);
20031b84d45SPiotr Zegar   Options.store(Opts, StrictCppStandardComplianceName,
20131b84d45SPiotr Zegar                 StrictCppStandardCompliance);
202525fe449SDanny Mösch }
203525fe449SDanny Mösch 
204525fe449SDanny Mösch } // namespace clang::tidy::modernize
205