xref: /llvm-project/clang-tools-extra/clang-tidy/modernize/UseDesignatedInitializersCheck.cpp (revision 27ef549af2c2f60d05f38db1ecc7a8ad7294351d)
1 //===--- UseDesignatedInitializersCheck.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 "UseDesignatedInitializersCheck.h"
10 #include "../utils/DesignatedInitializers.h"
11 #include "clang/AST/APValue.h"
12 #include "clang/AST/Decl.h"
13 #include "clang/AST/Expr.h"
14 #include "clang/AST/Stmt.h"
15 #include "clang/ASTMatchers/ASTMatchFinder.h"
16 #include "clang/ASTMatchers/ASTMatchers.h"
17 #include "clang/ASTMatchers/ASTMatchersMacros.h"
18 #include "clang/Basic/Diagnostic.h"
19 #include "clang/Lex/Lexer.h"
20 
21 using namespace clang::ast_matchers;
22 
23 namespace clang::tidy::modernize {
24 
25 static constexpr char IgnoreSingleElementAggregatesName[] =
26     "IgnoreSingleElementAggregates";
27 static constexpr bool IgnoreSingleElementAggregatesDefault = true;
28 
29 static constexpr char RestrictToPODTypesName[] = "RestrictToPODTypes";
30 static constexpr bool RestrictToPODTypesDefault = false;
31 
32 static constexpr char IgnoreMacrosName[] = "IgnoreMacros";
33 static constexpr bool IgnoreMacrosDefault = true;
34 
35 static constexpr char StrictCStandardComplianceName[] =
36     "StrictCStandardCompliance";
37 static constexpr bool StrictCStandardComplianceDefault = true;
38 
39 static constexpr char StrictCppStandardComplianceName[] =
40     "StrictCppStandardCompliance";
41 static constexpr bool StrictCppStandardComplianceDefault = true;
42 
43 namespace {
44 
45 struct Designators {
46 
47   Designators(const InitListExpr *InitList) : InitList(InitList) {
48     assert(InitList->isSyntacticForm());
49   };
50 
51   unsigned size() { return getCached().size(); }
52 
53   std::optional<llvm::StringRef> operator[](const SourceLocation &Location) {
54     const auto &Designators = getCached();
55     const auto Result = Designators.find(Location);
56     if (Result == Designators.end())
57       return {};
58     const llvm::StringRef Designator = Result->getSecond();
59     return (Designator.front() == '.' ? Designator.substr(1) : Designator)
60         .trim("\0"); // Trim NULL characters appearing on Windows in the
61                      // name.
62   }
63 
64 private:
65   using LocationToNameMap = llvm::DenseMap<clang::SourceLocation, std::string>;
66 
67   std::optional<LocationToNameMap> CachedDesignators;
68   const InitListExpr *InitList;
69 
70   LocationToNameMap &getCached() {
71     return CachedDesignators ? *CachedDesignators
72                              : CachedDesignators.emplace(
73                                    utils::getUnwrittenDesignators(InitList));
74   }
75 };
76 
77 unsigned getNumberOfDesignated(const InitListExpr *SyntacticInitList) {
78   return llvm::count_if(*SyntacticInitList, [](auto *InitExpr) {
79     return isa<DesignatedInitExpr>(InitExpr);
80   });
81 }
82 
83 AST_MATCHER(CXXRecordDecl, isAggregate) {
84   return Node.hasDefinition() && Node.isAggregate();
85 }
86 
87 AST_MATCHER(CXXRecordDecl, isPOD) {
88   return Node.hasDefinition() && Node.isPOD();
89 }
90 
91 AST_MATCHER(InitListExpr, isFullyDesignated) {
92   if (const InitListExpr *SyntacticForm =
93           Node.isSyntacticForm() ? &Node : Node.getSyntacticForm())
94     return getNumberOfDesignated(SyntacticForm) == SyntacticForm->getNumInits();
95   return true;
96 }
97 
98 AST_MATCHER(InitListExpr, hasMoreThanOneElement) {
99   return Node.getNumInits() > 1;
100 }
101 
102 } // namespace
103 
104 UseDesignatedInitializersCheck::UseDesignatedInitializersCheck(
105     StringRef Name, ClangTidyContext *Context)
106     : ClangTidyCheck(Name, Context), IgnoreSingleElementAggregates(Options.get(
107                                          IgnoreSingleElementAggregatesName,
108                                          IgnoreSingleElementAggregatesDefault)),
109       RestrictToPODTypes(
110           Options.get(RestrictToPODTypesName, RestrictToPODTypesDefault)),
111       IgnoreMacros(
112           Options.getLocalOrGlobal(IgnoreMacrosName, IgnoreMacrosDefault)),
113       StrictCStandardCompliance(Options.get(StrictCStandardComplianceName,
114                                             StrictCStandardComplianceDefault)),
115       StrictCppStandardCompliance(
116           Options.get(StrictCppStandardComplianceName,
117                       StrictCppStandardComplianceDefault)) {}
118 
119 void UseDesignatedInitializersCheck::registerMatchers(MatchFinder *Finder) {
120   const auto HasBaseWithFields =
121       hasAnyBase(hasType(cxxRecordDecl(has(fieldDecl()))));
122   Finder->addMatcher(
123       initListExpr(
124           hasType(cxxRecordDecl(RestrictToPODTypes ? isPOD() : isAggregate(),
125                                 unless(HasBaseWithFields))
126                       .bind("type")),
127           IgnoreSingleElementAggregates ? hasMoreThanOneElement() : anything(),
128           unless(isFullyDesignated()))
129           .bind("init"),
130       this);
131 }
132 
133 void UseDesignatedInitializersCheck::check(
134     const MatchFinder::MatchResult &Result) {
135   const auto *InitList = Result.Nodes.getNodeAs<InitListExpr>("init");
136   const auto *Type = Result.Nodes.getNodeAs<CXXRecordDecl>("type");
137   if (!Type || !InitList)
138     return;
139   const auto *SyntacticInitList = InitList->getSyntacticForm();
140   if (!SyntacticInitList)
141     return;
142   Designators Designators{SyntacticInitList};
143   const unsigned NumberOfDesignated = getNumberOfDesignated(SyntacticInitList);
144   if (SyntacticInitList->getNumInits() - NumberOfDesignated >
145       Designators.size())
146     return;
147 
148   // If the whole initializer list is un-designated, issue only one warning and
149   // a single fix-it for the whole expression.
150   if (0 == NumberOfDesignated) {
151     if (IgnoreMacros && InitList->getBeginLoc().isMacroID())
152       return;
153     {
154       DiagnosticBuilder Diag =
155           diag(InitList->getLBraceLoc(),
156                "use designated initializer list to initialize %0");
157       Diag << Type << InitList->getSourceRange();
158       for (const Stmt *InitExpr : *SyntacticInitList) {
159         const auto Designator = Designators[InitExpr->getBeginLoc()];
160         if (Designator && !Designator->empty())
161           Diag << FixItHint::CreateInsertion(InitExpr->getBeginLoc(),
162                                              ("." + *Designator + "=").str());
163       }
164     }
165     diag(Type->getBeginLoc(), "aggregate type is defined here",
166          DiagnosticIDs::Note);
167     return;
168   }
169 
170   // In case that only a few elements are un-designated (not all as before), the
171   // check offers dedicated issues and fix-its for each of them.
172   for (const auto *InitExpr : *SyntacticInitList) {
173     if (isa<DesignatedInitExpr>(InitExpr))
174       continue;
175     if (IgnoreMacros && InitExpr->getBeginLoc().isMacroID())
176       continue;
177     const auto Designator = Designators[InitExpr->getBeginLoc()];
178     if (!Designator || Designator->empty()) {
179       // There should always be a designator. If there's unexpectedly none, we
180       // at least report a generic diagnostic.
181       diag(InitExpr->getBeginLoc(), "use designated init expression")
182           << InitExpr->getSourceRange();
183     } else {
184       diag(InitExpr->getBeginLoc(),
185            "use designated init expression to initialize field '%0'")
186           << InitExpr->getSourceRange() << *Designator
187           << FixItHint::CreateInsertion(InitExpr->getBeginLoc(),
188                                         ("." + *Designator + "=").str());
189     }
190   }
191 }
192 
193 void UseDesignatedInitializersCheck::storeOptions(
194     ClangTidyOptions::OptionMap &Opts) {
195   Options.store(Opts, IgnoreSingleElementAggregatesName,
196                 IgnoreSingleElementAggregates);
197   Options.store(Opts, RestrictToPODTypesName, RestrictToPODTypes);
198   Options.store(Opts, IgnoreMacrosName, IgnoreMacros);
199   Options.store(Opts, StrictCStandardComplianceName, StrictCStandardCompliance);
200   Options.store(Opts, StrictCppStandardComplianceName,
201                 StrictCppStandardCompliance);
202 }
203 
204 } // namespace clang::tidy::modernize
205