Skip to content

Commit aca3727

Browse files
authored
[clang-tidy] Treat fields in anonymous records as names in enclosing scope when checking name styles (#75701)
Currently, fields in anonymous records are treated as normal record members during naming style check. This can be undesirable in certain situations since these fields are used just like names in their enclosing scopes: ```c++ class Foo { union { int iv_; // warning: invalid case style for public member 'iv_' float fv_; // warning: invalid case style for public member 'fv_' }; }; ``` `iv_` and `fv_` are used in the code like private members of `Foo` but their naming style comes from rules for public members. This PR changes this behavior. It adds a new option `CheckAnonFieldInParent` to `readability-identifier-naming`. When set to `true`, fields in anonymous records will be treated as names in their enclosing scopes when checking name styles. Specifically: - If the anonymous record is defined within the file scope or in a namespace scope, treat its fields as global variables when checking name styles; - If the anonymous record is defined within a function, treat its fields as local variables when checking name styles; - If the anonymous record is defined within a non-anonymous record, treat its fields as non-static record members when checking name styles.
1 parent 898320d commit aca3727

File tree

7 files changed

+398
-93
lines changed

7 files changed

+398
-93
lines changed

clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.cpp

Lines changed: 130 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "IdentifierNamingCheck.h"
1010

1111
#include "../GlobList.h"
12+
#include "../utils/ASTUtils.h"
1213
#include "clang/AST/CXXInheritance.h"
1314
#include "clang/Lex/PPCallbacks.h"
1415
#include "clang/Lex/Preprocessor.h"
@@ -286,7 +287,9 @@ IdentifierNamingCheck::FileStyle IdentifierNamingCheck::getFileStyleFromOptions(
286287
HPTOpt.value_or(IdentifierNamingCheck::HPT_Off));
287288
}
288289
bool IgnoreMainLike = Options.get("IgnoreMainLikeFunctions", false);
289-
return {std::move(Styles), std::move(HNOption), IgnoreMainLike};
290+
bool CheckAnonFieldInParent = Options.get("CheckAnonFieldInParent", false);
291+
return {std::move(Styles), std::move(HNOption), IgnoreMainLike,
292+
CheckAnonFieldInParent};
290293
}
291294

292295
std::string IdentifierNamingCheck::HungarianNotation::getDeclTypeName(
@@ -859,6 +862,8 @@ void IdentifierNamingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
859862
Options.store(Opts, "IgnoreFailedSplit", IgnoreFailedSplit);
860863
Options.store(Opts, "IgnoreMainLikeFunctions",
861864
MainFileStyle->isIgnoringMainLikeFunction());
865+
Options.store(Opts, "CheckAnonFieldInParent",
866+
MainFileStyle->isCheckingAnonFieldInParentScope());
862867
}
863868

864869
bool IdentifierNamingCheck::matchesStyle(
@@ -1111,7 +1116,7 @@ std::string IdentifierNamingCheck::fixupWithStyle(
11111116
StyleKind IdentifierNamingCheck::findStyleKind(
11121117
const NamedDecl *D,
11131118
ArrayRef<std::optional<IdentifierNamingCheck::NamingStyle>> NamingStyles,
1114-
bool IgnoreMainLikeFunctions) const {
1119+
bool IgnoreMainLikeFunctions, bool CheckAnonFieldInParentScope) const {
11151120
assert(D && D->getIdentifier() && !D->getName().empty() && !D->isImplicit() &&
11161121
"Decl must be an explicit identifier with a name.");
11171122

@@ -1185,29 +1190,14 @@ StyleKind IdentifierNamingCheck::findStyleKind(
11851190
}
11861191

11871192
if (const auto *Decl = dyn_cast<FieldDecl>(D)) {
1188-
QualType Type = Decl->getType();
1189-
1190-
if (!Type.isNull() && Type.isConstQualified()) {
1191-
if (NamingStyles[SK_ConstantMember])
1192-
return SK_ConstantMember;
1193-
1194-
if (NamingStyles[SK_Constant])
1195-
return SK_Constant;
1193+
if (CheckAnonFieldInParentScope) {
1194+
const RecordDecl *Record = Decl->getParent();
1195+
if (Record->isAnonymousStructOrUnion()) {
1196+
return findStyleKindForAnonField(Decl, NamingStyles);
1197+
}
11961198
}
11971199

1198-
if (Decl->getAccess() == AS_private && NamingStyles[SK_PrivateMember])
1199-
return SK_PrivateMember;
1200-
1201-
if (Decl->getAccess() == AS_protected && NamingStyles[SK_ProtectedMember])
1202-
return SK_ProtectedMember;
1203-
1204-
if (Decl->getAccess() == AS_public && NamingStyles[SK_PublicMember])
1205-
return SK_PublicMember;
1206-
1207-
if (NamingStyles[SK_Member])
1208-
return SK_Member;
1209-
1210-
return SK_Invalid;
1200+
return findStyleKindForField(Decl, Decl->getType(), NamingStyles);
12111201
}
12121202

12131203
if (const auto *Decl = dyn_cast<ParmVarDecl>(D)) {
@@ -1244,66 +1234,7 @@ StyleKind IdentifierNamingCheck::findStyleKind(
12441234
}
12451235

12461236
if (const auto *Decl = dyn_cast<VarDecl>(D)) {
1247-
QualType Type = Decl->getType();
1248-
1249-
if (Decl->isConstexpr() && NamingStyles[SK_ConstexprVariable])
1250-
return SK_ConstexprVariable;
1251-
1252-
if (!Type.isNull() && Type.isConstQualified()) {
1253-
if (Decl->isStaticDataMember() && NamingStyles[SK_ClassConstant])
1254-
return SK_ClassConstant;
1255-
1256-
if (Decl->isFileVarDecl() && Type.getTypePtr()->isAnyPointerType() &&
1257-
NamingStyles[SK_GlobalConstantPointer])
1258-
return SK_GlobalConstantPointer;
1259-
1260-
if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalConstant])
1261-
return SK_GlobalConstant;
1262-
1263-
if (Decl->isStaticLocal() && NamingStyles[SK_StaticConstant])
1264-
return SK_StaticConstant;
1265-
1266-
if (Decl->isLocalVarDecl() && Type.getTypePtr()->isAnyPointerType() &&
1267-
NamingStyles[SK_LocalConstantPointer])
1268-
return SK_LocalConstantPointer;
1269-
1270-
if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalConstant])
1271-
return SK_LocalConstant;
1272-
1273-
if (Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalConstant])
1274-
return SK_LocalConstant;
1275-
1276-
if (NamingStyles[SK_Constant])
1277-
return SK_Constant;
1278-
}
1279-
1280-
if (Decl->isStaticDataMember() && NamingStyles[SK_ClassMember])
1281-
return SK_ClassMember;
1282-
1283-
if (Decl->isFileVarDecl() && Type.getTypePtr()->isAnyPointerType() &&
1284-
NamingStyles[SK_GlobalPointer])
1285-
return SK_GlobalPointer;
1286-
1287-
if (Decl->isFileVarDecl() && NamingStyles[SK_GlobalVariable])
1288-
return SK_GlobalVariable;
1289-
1290-
if (Decl->isStaticLocal() && NamingStyles[SK_StaticVariable])
1291-
return SK_StaticVariable;
1292-
1293-
if (Decl->isLocalVarDecl() && Type.getTypePtr()->isAnyPointerType() &&
1294-
NamingStyles[SK_LocalPointer])
1295-
return SK_LocalPointer;
1296-
1297-
if (Decl->isLocalVarDecl() && NamingStyles[SK_LocalVariable])
1298-
return SK_LocalVariable;
1299-
1300-
if (Decl->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalVariable])
1301-
return SK_LocalVariable;
1302-
1303-
if (NamingStyles[SK_Variable])
1304-
return SK_Variable;
1305-
1306-
return SK_Invalid;
1237+
return findStyleKindForVar(Decl, Decl->getType(), NamingStyles);
13071238
}
13081239

13091240
if (const auto *Decl = dyn_cast<CXXMethodDecl>(D)) {
@@ -1442,12 +1373,13 @@ IdentifierNamingCheck::getDeclFailureInfo(const NamedDecl *Decl,
14421373
if (!FileStyle.isActive())
14431374
return std::nullopt;
14441375

1445-
return getFailureInfo(HungarianNotation.getDeclTypeName(Decl),
1446-
Decl->getName(), Decl, Loc, FileStyle.getStyles(),
1447-
FileStyle.getHNOption(),
1448-
findStyleKind(Decl, FileStyle.getStyles(),
1449-
FileStyle.isIgnoringMainLikeFunction()),
1450-
SM, IgnoreFailedSplit);
1376+
return getFailureInfo(
1377+
HungarianNotation.getDeclTypeName(Decl), Decl->getName(), Decl, Loc,
1378+
FileStyle.getStyles(), FileStyle.getHNOption(),
1379+
findStyleKind(Decl, FileStyle.getStyles(),
1380+
FileStyle.isIgnoringMainLikeFunction(),
1381+
FileStyle.isCheckingAnonFieldInParentScope()),
1382+
SM, IgnoreFailedSplit);
14511383
}
14521384

14531385
std::optional<RenamerClangTidyCheck::FailureInfo>
@@ -1496,5 +1428,114 @@ IdentifierNamingCheck::getStyleForFile(StringRef FileName) const {
14961428
return It.first->getValue();
14971429
}
14981430

1431+
StyleKind IdentifierNamingCheck::findStyleKindForAnonField(
1432+
const FieldDecl *AnonField,
1433+
ArrayRef<std::optional<NamingStyle>> NamingStyles) const {
1434+
const IndirectFieldDecl *IFD =
1435+
utils::findOutermostIndirectFieldDeclForField(AnonField);
1436+
assert(IFD && "Found an anonymous record field without an IndirectFieldDecl");
1437+
1438+
QualType Type = AnonField->getType();
1439+
1440+
if (const auto *F = dyn_cast<FieldDecl>(IFD->chain().front())) {
1441+
return findStyleKindForField(F, Type, NamingStyles);
1442+
}
1443+
1444+
if (const auto *V = IFD->getVarDecl()) {
1445+
return findStyleKindForVar(V, Type, NamingStyles);
1446+
}
1447+
1448+
return SK_Invalid;
1449+
}
1450+
1451+
StyleKind IdentifierNamingCheck::findStyleKindForField(
1452+
const FieldDecl *Field, QualType Type,
1453+
ArrayRef<std::optional<NamingStyle>> NamingStyles) const {
1454+
if (!Type.isNull() && Type.isConstQualified()) {
1455+
if (NamingStyles[SK_ConstantMember])
1456+
return SK_ConstantMember;
1457+
1458+
if (NamingStyles[SK_Constant])
1459+
return SK_Constant;
1460+
}
1461+
1462+
if (Field->getAccess() == AS_private && NamingStyles[SK_PrivateMember])
1463+
return SK_PrivateMember;
1464+
1465+
if (Field->getAccess() == AS_protected && NamingStyles[SK_ProtectedMember])
1466+
return SK_ProtectedMember;
1467+
1468+
if (Field->getAccess() == AS_public && NamingStyles[SK_PublicMember])
1469+
return SK_PublicMember;
1470+
1471+
if (NamingStyles[SK_Member])
1472+
return SK_Member;
1473+
1474+
return SK_Invalid;
1475+
}
1476+
1477+
StyleKind IdentifierNamingCheck::findStyleKindForVar(
1478+
const VarDecl *Var, QualType Type,
1479+
ArrayRef<std::optional<NamingStyle>> NamingStyles) const {
1480+
if (Var->isConstexpr() && NamingStyles[SK_ConstexprVariable])
1481+
return SK_ConstexprVariable;
1482+
1483+
if (!Type.isNull() && Type.isConstQualified()) {
1484+
if (Var->isStaticDataMember() && NamingStyles[SK_ClassConstant])
1485+
return SK_ClassConstant;
1486+
1487+
if (Var->isFileVarDecl() && Type.getTypePtr()->isAnyPointerType() &&
1488+
NamingStyles[SK_GlobalConstantPointer])
1489+
return SK_GlobalConstantPointer;
1490+
1491+
if (Var->isFileVarDecl() && NamingStyles[SK_GlobalConstant])
1492+
return SK_GlobalConstant;
1493+
1494+
if (Var->isStaticLocal() && NamingStyles[SK_StaticConstant])
1495+
return SK_StaticConstant;
1496+
1497+
if (Var->isLocalVarDecl() && Type.getTypePtr()->isAnyPointerType() &&
1498+
NamingStyles[SK_LocalConstantPointer])
1499+
return SK_LocalConstantPointer;
1500+
1501+
if (Var->isLocalVarDecl() && NamingStyles[SK_LocalConstant])
1502+
return SK_LocalConstant;
1503+
1504+
if (Var->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalConstant])
1505+
return SK_LocalConstant;
1506+
1507+
if (NamingStyles[SK_Constant])
1508+
return SK_Constant;
1509+
}
1510+
1511+
if (Var->isStaticDataMember() && NamingStyles[SK_ClassMember])
1512+
return SK_ClassMember;
1513+
1514+
if (Var->isFileVarDecl() && Type.getTypePtr()->isAnyPointerType() &&
1515+
NamingStyles[SK_GlobalPointer])
1516+
return SK_GlobalPointer;
1517+
1518+
if (Var->isFileVarDecl() && NamingStyles[SK_GlobalVariable])
1519+
return SK_GlobalVariable;
1520+
1521+
if (Var->isStaticLocal() && NamingStyles[SK_StaticVariable])
1522+
return SK_StaticVariable;
1523+
1524+
if (Var->isLocalVarDecl() && Type.getTypePtr()->isAnyPointerType() &&
1525+
NamingStyles[SK_LocalPointer])
1526+
return SK_LocalPointer;
1527+
1528+
if (Var->isLocalVarDecl() && NamingStyles[SK_LocalVariable])
1529+
return SK_LocalVariable;
1530+
1531+
if (Var->isFunctionOrMethodVarDecl() && NamingStyles[SK_LocalVariable])
1532+
return SK_LocalVariable;
1533+
1534+
if (NamingStyles[SK_Variable])
1535+
return SK_Variable;
1536+
1537+
return SK_Invalid;
1538+
}
1539+
14991540
} // namespace readability
15001541
} // namespace clang::tidy

clang-tools-extra/clang-tidy/readability/IdentifierNamingCheck.h

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,11 @@ class IdentifierNamingCheck final : public RenamerClangTidyCheck {
127127
struct FileStyle {
128128
FileStyle() : IsActive(false), IgnoreMainLikeFunctions(false) {}
129129
FileStyle(SmallVectorImpl<std::optional<NamingStyle>> &&Styles,
130-
HungarianNotationOption HNOption, bool IgnoreMainLike)
130+
HungarianNotationOption HNOption, bool IgnoreMainLike,
131+
bool CheckAnonFieldInParent)
131132
: Styles(std::move(Styles)), HNOption(std::move(HNOption)),
132-
IsActive(true), IgnoreMainLikeFunctions(IgnoreMainLike) {}
133+
IsActive(true), IgnoreMainLikeFunctions(IgnoreMainLike),
134+
CheckAnonFieldInParentScope(CheckAnonFieldInParent) {}
133135

134136
ArrayRef<std::optional<NamingStyle>> getStyles() const {
135137
assert(IsActive);
@@ -144,11 +146,16 @@ class IdentifierNamingCheck final : public RenamerClangTidyCheck {
144146
bool isActive() const { return IsActive; }
145147
bool isIgnoringMainLikeFunction() const { return IgnoreMainLikeFunctions; }
146148

149+
bool isCheckingAnonFieldInParentScope() const {
150+
return CheckAnonFieldInParentScope;
151+
}
152+
147153
private:
148154
SmallVector<std::optional<NamingStyle>, 0> Styles;
149155
HungarianNotationOption HNOption;
150156
bool IsActive;
151157
bool IgnoreMainLikeFunctions;
158+
bool CheckAnonFieldInParentScope;
152159
};
153160

154161
IdentifierNamingCheck::FileStyle
@@ -175,7 +182,7 @@ class IdentifierNamingCheck final : public RenamerClangTidyCheck {
175182
StyleKind findStyleKind(
176183
const NamedDecl *D,
177184
ArrayRef<std::optional<IdentifierNamingCheck::NamingStyle>> NamingStyles,
178-
bool IgnoreMainLikeFunctions) const;
185+
bool IgnoreMainLikeFunctions, bool CheckAnonFieldInParentScope) const;
179186

180187
std::optional<RenamerClangTidyCheck::FailureInfo> getFailureInfo(
181188
StringRef Type, StringRef Name, const NamedDecl *ND,
@@ -199,6 +206,19 @@ class IdentifierNamingCheck final : public RenamerClangTidyCheck {
199206

200207
const FileStyle &getStyleForFile(StringRef FileName) const;
201208

209+
/// Find the style kind of a field in an anonymous record.
210+
StyleKind findStyleKindForAnonField(
211+
const FieldDecl *AnonField,
212+
ArrayRef<std::optional<NamingStyle>> NamingStyles) const;
213+
214+
StyleKind findStyleKindForField(
215+
const FieldDecl *Field, QualType Type,
216+
ArrayRef<std::optional<NamingStyle>> NamingStyles) const;
217+
218+
StyleKind
219+
findStyleKindForVar(const VarDecl *Var, QualType Type,
220+
ArrayRef<std::optional<NamingStyle>> NamingStyles) const;
221+
202222
/// Stores the style options as a vector, indexed by the specified \ref
203223
/// StyleKind, for a given directory.
204224
mutable llvm::StringMap<FileStyle> NamingStylesCache;

clang-tools-extra/clang-tidy/utils/ASTUtils.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,28 @@ bool areStatementsIdentical(const Stmt *FirstStmt, const Stmt *SecondStmt,
113113
return DataFirst == DataSecond;
114114
}
115115

116+
const IndirectFieldDecl *
117+
findOutermostIndirectFieldDeclForField(const FieldDecl *FD) {
118+
const RecordDecl *Record = FD->getParent();
119+
assert(Record->isAnonymousStructOrUnion() &&
120+
"FD must be a field in an anonymous record");
121+
122+
const DeclContext *Context = Record;
123+
while (isa<RecordDecl>(Context) &&
124+
cast<RecordDecl>(Context)->isAnonymousStructOrUnion()) {
125+
Context = Context->getParent();
126+
}
127+
128+
// Search for the target IndirectFieldDecl within the located context.
129+
for (const auto *D : Context->decls()) {
130+
const auto *IFD = dyn_cast<IndirectFieldDecl>(D);
131+
if (!IFD)
132+
continue;
133+
if (IFD->getAnonField() == FD)
134+
return IFD;
135+
}
136+
137+
return nullptr;
138+
}
139+
116140
} // namespace clang::tidy::utils

clang-tools-extra/clang-tidy/utils/ASTUtils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ bool rangeCanBeFixed(SourceRange Range, const SourceManager *SM);
4040
bool areStatementsIdentical(const Stmt *FirstStmt, const Stmt *SecondStmt,
4141
const ASTContext &Context, bool Canonical = false);
4242

43+
// Given a field of an anonymous record, find its corresponding
44+
// IndirectFieldDecl in the outermost possible scope.
45+
const IndirectFieldDecl *
46+
findOutermostIndirectFieldDeclForField(const FieldDecl *FD);
47+
4348
} // namespace clang::tidy::utils
4449

4550
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_ASTUTILS_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,10 @@ Changes in existing checks
454454
has been enhanced, particularly within complex types like function pointers
455455
and cases where style checks were omitted when functions started with macros.
456456
Added support for C++20 ``concept`` declarations. ``Camel_Snake_Case`` and
457-
``camel_Snake_Case`` now detect more invalid identifier names.
457+
``camel_Snake_Case`` now detect more invalid identifier names. Fields in
458+
anonymous records (i.e. anonymous structs and unions) now can be checked with
459+
the naming rules associated with their enclosing scopes rather than the naming
460+
rules of public struct/union members.
458461

459462
- Improved :doc:`readability-implicit-bool-conversion
460463
<clang-tidy/checks/readability/implicit-bool-conversion>` check to take

0 commit comments

Comments
 (0)