Skip to content

Commit 525fe44

Browse files
authored
[clang-tidy] Add new check modernize-use-designated-initializers (#80541)
1 parent 73aab2f commit 525fe44

File tree

15 files changed

+745
-166
lines changed

15 files changed

+745
-166
lines changed

clang-tools-extra/clang-tidy/modernize/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ add_clang_library(clangTidyModernizeModule
3131
UseBoolLiteralsCheck.cpp
3232
UseConstraintsCheck.cpp
3333
UseDefaultMemberInitCheck.cpp
34+
UseDesignatedInitializersCheck.cpp
3435
UseEmplaceCheck.cpp
3536
UseEqualsDefaultCheck.cpp
3637
UseEqualsDeleteCheck.cpp

clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "UseBoolLiteralsCheck.h"
3333
#include "UseConstraintsCheck.h"
3434
#include "UseDefaultMemberInitCheck.h"
35+
#include "UseDesignatedInitializersCheck.h"
3536
#include "UseEmplaceCheck.h"
3637
#include "UseEqualsDefaultCheck.h"
3738
#include "UseEqualsDeleteCheck.h"
@@ -68,6 +69,8 @@ class ModernizeModule : public ClangTidyModule {
6869
CheckFactories.registerCheck<MakeSharedCheck>("modernize-make-shared");
6970
CheckFactories.registerCheck<MakeUniqueCheck>("modernize-make-unique");
7071
CheckFactories.registerCheck<PassByValueCheck>("modernize-pass-by-value");
72+
CheckFactories.registerCheck<UseDesignatedInitializersCheck>(
73+
"modernize-use-designated-initializers");
7174
CheckFactories.registerCheck<UseStartsEndsWithCheck>(
7275
"modernize-use-starts-ends-with");
7376
CheckFactories.registerCheck<UseStdNumbersCheck>(
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
namespace {
36+
37+
struct Designators {
38+
39+
Designators(const InitListExpr *InitList) : InitList(InitList) {
40+
assert(InitList->isSyntacticForm());
41+
};
42+
43+
unsigned size() { return getCached().size(); }
44+
45+
std::optional<llvm::StringRef> operator[](const SourceLocation &Location) {
46+
const auto Designators = getCached();
47+
const auto Result = Designators.find(Location);
48+
if (Result == Designators.end())
49+
return {};
50+
const llvm::StringRef Designator = Result->getSecond();
51+
return (Designator.front() == '.' ? Designator.substr(1) : Designator)
52+
.trim("\0"); // Trim NULL characters appearing on Windows in the
53+
// name.
54+
}
55+
56+
private:
57+
using LocationToNameMap = llvm::DenseMap<clang::SourceLocation, std::string>;
58+
59+
std::optional<LocationToNameMap> CachedDesignators;
60+
const InitListExpr *InitList;
61+
62+
LocationToNameMap &getCached() {
63+
return CachedDesignators ? *CachedDesignators
64+
: CachedDesignators.emplace(
65+
utils::getUnwrittenDesignators(InitList));
66+
}
67+
};
68+
69+
unsigned getNumberOfDesignated(const InitListExpr *SyntacticInitList) {
70+
return llvm::count_if(*SyntacticInitList, [](auto *InitExpr) {
71+
return isa<DesignatedInitExpr>(InitExpr);
72+
});
73+
}
74+
75+
AST_MATCHER(CXXRecordDecl, isAggregate) { return Node.isAggregate(); }
76+
77+
AST_MATCHER(CXXRecordDecl, isPOD) { return Node.isPOD(); }
78+
79+
AST_MATCHER(InitListExpr, isFullyDesignated) {
80+
if (const InitListExpr *SyntacticForm =
81+
Node.isSyntacticForm() ? &Node : Node.getSyntacticForm())
82+
return getNumberOfDesignated(SyntacticForm) == SyntacticForm->getNumInits();
83+
return true;
84+
}
85+
86+
AST_MATCHER(InitListExpr, hasMoreThanOneElement) {
87+
return Node.getNumInits() > 1;
88+
}
89+
90+
} // namespace
91+
92+
UseDesignatedInitializersCheck::UseDesignatedInitializersCheck(
93+
StringRef Name, ClangTidyContext *Context)
94+
: ClangTidyCheck(Name, Context), IgnoreSingleElementAggregates(Options.get(
95+
IgnoreSingleElementAggregatesName,
96+
IgnoreSingleElementAggregatesDefault)),
97+
RestrictToPODTypes(
98+
Options.get(RestrictToPODTypesName, RestrictToPODTypesDefault)),
99+
IgnoreMacros(
100+
Options.getLocalOrGlobal(IgnoreMacrosName, IgnoreMacrosDefault)) {}
101+
102+
void UseDesignatedInitializersCheck::registerMatchers(MatchFinder *Finder) {
103+
const auto HasBaseWithFields =
104+
hasAnyBase(hasType(cxxRecordDecl(has(fieldDecl()))));
105+
Finder->addMatcher(
106+
initListExpr(
107+
hasType(cxxRecordDecl(RestrictToPODTypes ? isPOD() : isAggregate(),
108+
unless(HasBaseWithFields))
109+
.bind("type")),
110+
IgnoreSingleElementAggregates ? hasMoreThanOneElement() : anything(),
111+
unless(isFullyDesignated()))
112+
.bind("init"),
113+
this);
114+
}
115+
116+
void UseDesignatedInitializersCheck::check(
117+
const MatchFinder::MatchResult &Result) {
118+
const auto *InitList = Result.Nodes.getNodeAs<InitListExpr>("init");
119+
const auto *Type = Result.Nodes.getNodeAs<CXXRecordDecl>("type");
120+
if (!Type || !InitList)
121+
return;
122+
const auto *SyntacticInitList = InitList->getSyntacticForm();
123+
if (!SyntacticInitList)
124+
return;
125+
Designators Designators{SyntacticInitList};
126+
const unsigned NumberOfDesignated = getNumberOfDesignated(SyntacticInitList);
127+
if (SyntacticInitList->getNumInits() - NumberOfDesignated >
128+
Designators.size())
129+
return;
130+
131+
// If the whole initializer list is un-designated, issue only one warning and
132+
// a single fix-it for the whole expression.
133+
if (0 == NumberOfDesignated) {
134+
if (IgnoreMacros && InitList->getBeginLoc().isMacroID())
135+
return;
136+
{
137+
DiagnosticBuilder Diag =
138+
diag(InitList->getLBraceLoc(),
139+
"use designated initializer list to initialize %0");
140+
Diag << Type << InitList->getSourceRange();
141+
for (const Stmt *InitExpr : *SyntacticInitList) {
142+
const auto Designator = Designators[InitExpr->getBeginLoc()];
143+
if (Designator && !Designator->empty())
144+
Diag << FixItHint::CreateInsertion(InitExpr->getBeginLoc(),
145+
("." + *Designator + "=").str());
146+
}
147+
}
148+
diag(Type->getBeginLoc(), "aggregate type is defined here",
149+
DiagnosticIDs::Note);
150+
return;
151+
}
152+
153+
// In case that only a few elements are un-designated (not all as before), the
154+
// check offers dedicated issues and fix-its for each of them.
155+
for (const auto *InitExpr : *SyntacticInitList) {
156+
if (isa<DesignatedInitExpr>(InitExpr))
157+
continue;
158+
if (IgnoreMacros && InitExpr->getBeginLoc().isMacroID())
159+
continue;
160+
const auto Designator = Designators[InitExpr->getBeginLoc()];
161+
if (!Designator || Designator->empty()) {
162+
// There should always be a designator. If there's unexpectedly none, we
163+
// at least report a generic diagnostic.
164+
diag(InitExpr->getBeginLoc(), "use designated init expression")
165+
<< InitExpr->getSourceRange();
166+
} else {
167+
diag(InitExpr->getBeginLoc(),
168+
"use designated init expression to initialize field '%0'")
169+
<< InitExpr->getSourceRange() << *Designator
170+
<< FixItHint::CreateInsertion(InitExpr->getBeginLoc(),
171+
("." + *Designator + "=").str());
172+
}
173+
}
174+
}
175+
176+
void UseDesignatedInitializersCheck::storeOptions(
177+
ClangTidyOptions::OptionMap &Opts) {
178+
Options.store(Opts, IgnoreSingleElementAggregatesName,
179+
IgnoreSingleElementAggregates);
180+
Options.store(Opts, RestrictToPODTypesName, RestrictToPODTypes);
181+
Options.store(Opts, IgnoreMacrosName, IgnoreMacros);
182+
}
183+
184+
} // namespace clang::tidy::modernize
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//===--- UseDesignatedInitializersCheck.h - clang-tidy ----------*- C++ -*-===//
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+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEDESIGNATEDINITIALIZERSCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEDESIGNATEDINITIALIZERSCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::modernize {
15+
16+
/// Finds initializer lists for aggregate type that could be
17+
/// written as designated initializers instead.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/modernize/use-designated-initializers.html
21+
class UseDesignatedInitializersCheck : public ClangTidyCheck {
22+
public:
23+
UseDesignatedInitializersCheck(StringRef Name, ClangTidyContext *Context);
24+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
25+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
26+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
27+
28+
std::optional<TraversalKind> getCheckTraversalKind() const override {
29+
return TK_IgnoreUnlessSpelledInSource;
30+
}
31+
32+
private:
33+
bool IgnoreSingleElementAggregates;
34+
bool RestrictToPODTypes;
35+
bool IgnoreMacros;
36+
};
37+
38+
} // namespace clang::tidy::modernize
39+
40+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USEDESIGNATEDINITIALIZERSCHECK_H

clang-tools-extra/clang-tidy/utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ add_clang_library(clangTidyUtils
77
Aliasing.cpp
88
ASTUtils.cpp
99
DeclRefExprUtils.cpp
10+
DesignatedInitializers.cpp
1011
ExceptionAnalyzer.cpp
1112
ExceptionSpecAnalyzer.cpp
1213
ExprSequence.cpp

0 commit comments

Comments
 (0)