Skip to content

Commit b1bc1db

Browse files
authored
[clang-tidy] Refactor how NamedDecl are renamed (#88735)
The handling of renaming failures and multiple usages related to those failures is currently spread over several functions. Identifying the failure NamedDecl for a given usage is also duplicated, once when creating failures and again when identify usages. There are currently two ways to a failed NamedDecl from a usage: use the canonical decl or use the overridden method. With new methods about to be added, a cleanup was in order. The data flow is simplified as follows: * The visitor always forwards NamedDecls to addUsage(NamedDecl). * addUsage(NamedDecl) determines the failed NamedDecl and determines potential new names based on that failure. Usages are registered using addUsage(NamingCheckId). * addUsage(NamingCheckId) is now protected and its single responsibility is maintaining the integrity of the failure/usage map.
1 parent 7115ed0 commit b1bc1db

File tree

4 files changed

+121
-98
lines changed

4 files changed

+121
-98
lines changed

clang-tools-extra/clang-tidy/bugprone/ReservedIdentifierCheck.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,11 @@ std::optional<RenamerClangTidyCheck::FailureInfo>
178178
ReservedIdentifierCheck::getDeclFailureInfo(const NamedDecl *Decl,
179179
const SourceManager &) const {
180180
assert(Decl && Decl->getIdentifier() && !Decl->getName().empty() &&
181-
!Decl->isImplicit() &&
182181
"Decl must be an explicit identifier with a name.");
182+
// Implicit identifiers cannot fail.
183+
if (Decl->isImplicit())
184+
return std::nullopt;
185+
183186
return getFailureInfoImpl(
184187
Decl->getName(), isa<TranslationUnitDecl>(Decl->getDeclContext()),
185188
/*IsMacro = */ false, getLangOpts(), Invert, AllowedIdentifiers);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,6 +1374,10 @@ IdentifierNamingCheck::getFailureInfo(
13741374
std::optional<RenamerClangTidyCheck::FailureInfo>
13751375
IdentifierNamingCheck::getDeclFailureInfo(const NamedDecl *Decl,
13761376
const SourceManager &SM) const {
1377+
// Implicit identifiers cannot be renamed.
1378+
if (Decl->isImplicit())
1379+
return std::nullopt;
1380+
13771381
SourceLocation Loc = Decl->getLocation();
13781382
const FileStyle &FileStyle = getStyleForFile(SM.getFilename(Loc));
13791383
if (!FileStyle.isActive())

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

Lines changed: 105 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ struct DenseMapInfo<clang::tidy::RenamerClangTidyCheck::NamingCheckId> {
6161
namespace clang::tidy {
6262

6363
namespace {
64+
6465
class NameLookup {
6566
llvm::PointerIntPair<const NamedDecl *, 1, bool> Data;
6667

@@ -78,6 +79,7 @@ class NameLookup {
7879
operator bool() const { return !hasMultipleResolutions(); }
7980
const NamedDecl *operator*() const { return getDecl(); }
8081
};
82+
8183
} // namespace
8284

8385
static const NamedDecl *findDecl(const RecordDecl &RecDecl,
@@ -91,6 +93,44 @@ static const NamedDecl *findDecl(const RecordDecl &RecDecl,
9193
return nullptr;
9294
}
9395

96+
/// Returns the function that \p Method is overridding. If There are none or
97+
/// multiple overrides it returns nullptr. If the overridden function itself is
98+
/// overridding then it will recurse up to find the first decl of the function.
99+
static const CXXMethodDecl *getOverrideMethod(const CXXMethodDecl *Method) {
100+
if (Method->size_overridden_methods() != 1)
101+
return nullptr;
102+
103+
while (true) {
104+
Method = *Method->begin_overridden_methods();
105+
assert(Method && "Overridden method shouldn't be null");
106+
unsigned NumOverrides = Method->size_overridden_methods();
107+
if (NumOverrides == 0)
108+
return Method;
109+
if (NumOverrides > 1)
110+
return nullptr;
111+
}
112+
}
113+
114+
static bool hasNoName(const NamedDecl *Decl) {
115+
return !Decl->getIdentifier() || Decl->getName().empty();
116+
}
117+
118+
static const NamedDecl *getFailureForNamedDecl(const NamedDecl *ND) {
119+
const auto *Canonical = cast<NamedDecl>(ND->getCanonicalDecl());
120+
if (Canonical != ND)
121+
return Canonical;
122+
123+
if (const auto *Method = dyn_cast<CXXMethodDecl>(ND)) {
124+
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method))
125+
Canonical = cast<NamedDecl>(Overridden->getCanonicalDecl());
126+
127+
if (Canonical != ND)
128+
return Canonical;
129+
}
130+
131+
return ND;
132+
}
133+
94134
/// Returns a decl matching the \p DeclName in \p Parent or one of its base
95135
/// classes. If \p AggressiveTemplateLookup is `true` then it will check
96136
/// template dependent base classes as well.
@@ -132,24 +172,6 @@ static NameLookup findDeclInBases(const CXXRecordDecl &Parent,
132172
return NameLookup(Found); // If nullptr, decl wasn't found.
133173
}
134174

135-
/// Returns the function that \p Method is overridding. If There are none or
136-
/// multiple overrides it returns nullptr. If the overridden function itself is
137-
/// overridding then it will recurse up to find the first decl of the function.
138-
static const CXXMethodDecl *getOverrideMethod(const CXXMethodDecl *Method) {
139-
if (Method->size_overridden_methods() != 1)
140-
return nullptr;
141-
142-
while (true) {
143-
Method = *Method->begin_overridden_methods();
144-
assert(Method && "Overridden method shouldn't be null");
145-
unsigned NumOverrides = Method->size_overridden_methods();
146-
if (NumOverrides == 0)
147-
return Method;
148-
if (NumOverrides > 1)
149-
return nullptr;
150-
}
151-
}
152-
153175
namespace {
154176

155177
/// Callback supplies macros to RenamerClangTidyCheck::checkMacro
@@ -192,10 +214,6 @@ class RenamerClangTidyVisitor
192214
: Check(Check), SM(SM),
193215
AggressiveDependentMemberLookup(AggressiveDependentMemberLookup) {}
194216

195-
static bool hasNoName(const NamedDecl *Decl) {
196-
return !Decl->getIdentifier() || Decl->getName().empty();
197-
}
198-
199217
bool shouldVisitTemplateInstantiations() const { return true; }
200218

201219
bool shouldVisitImplicitCode() const { return false; }
@@ -246,29 +264,10 @@ class RenamerClangTidyVisitor
246264
}
247265

248266
bool VisitNamedDecl(NamedDecl *Decl) {
249-
if (hasNoName(Decl))
250-
return true;
251-
252-
const auto *Canonical = cast<NamedDecl>(Decl->getCanonicalDecl());
253-
if (Canonical != Decl) {
254-
Check->addUsage(Canonical, Decl->getLocation(), SM);
255-
return true;
256-
}
257-
258-
// Fix overridden methods
259-
if (const auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
260-
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method)) {
261-
Check->addUsage(Overridden, Method->getLocation(), SM);
262-
return true; // Don't try to add the actual decl as a Failure.
263-
}
264-
}
265-
266-
// Ignore ClassTemplateSpecializationDecl which are creating duplicate
267-
// replacements with CXXRecordDecl.
268-
if (isa<ClassTemplateSpecializationDecl>(Decl))
269-
return true;
270-
271-
Check->checkNamedDecl(Decl, SM);
267+
SourceRange UsageRange =
268+
DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())
269+
.getSourceRange();
270+
Check->addUsage(Decl, UsageRange, SM);
272271
return true;
273272
}
274273

@@ -413,82 +412,97 @@ void RenamerClangTidyCheck::registerPPCallbacks(
413412
std::make_unique<RenamerClangTidyCheckPPCallbacks>(SM, this));
414413
}
415414

416-
void RenamerClangTidyCheck::addUsage(
417-
const RenamerClangTidyCheck::NamingCheckId &Decl, SourceRange Range,
418-
const SourceManager &SourceMgr) {
415+
std::pair<RenamerClangTidyCheck::NamingCheckFailureMap::iterator, bool>
416+
RenamerClangTidyCheck::addUsage(
417+
const RenamerClangTidyCheck::NamingCheckId &FailureId,
418+
SourceRange UsageRange, const SourceManager &SourceMgr) {
419419
// Do nothing if the provided range is invalid.
420-
if (Range.isInvalid())
421-
return;
420+
if (UsageRange.isInvalid())
421+
return {NamingCheckFailures.end(), false};
422422

423-
// If we have a source manager, use it to convert to the spelling location for
424-
// performing the fix. This is necessary because macros can map the same
425-
// spelling location to different source locations, and we only want to fix
426-
// the token once, before it is expanded by the macro.
427-
SourceLocation FixLocation = Range.getBegin();
423+
// Get the spelling location for performing the fix. This is necessary because
424+
// macros can map the same spelling location to different source locations,
425+
// and we only want to fix the token once, before it is expanded by the macro.
426+
SourceLocation FixLocation = UsageRange.getBegin();
428427
FixLocation = SourceMgr.getSpellingLoc(FixLocation);
429428
if (FixLocation.isInvalid())
430-
return;
429+
return {NamingCheckFailures.end(), false};
430+
431+
auto EmplaceResult = NamingCheckFailures.try_emplace(FailureId);
432+
NamingCheckFailure &Failure = EmplaceResult.first->second;
431433

432434
// Try to insert the identifier location in the Usages map, and bail out if it
433435
// is already in there
434-
RenamerClangTidyCheck::NamingCheckFailure &Failure =
435-
NamingCheckFailures[Decl];
436436
if (!Failure.RawUsageLocs.insert(FixLocation).second)
437-
return;
437+
return EmplaceResult;
438438

439-
if (!Failure.shouldFix())
440-
return;
439+
if (Failure.FixStatus != RenamerClangTidyCheck::ShouldFixStatus::ShouldFix)
440+
return EmplaceResult;
441441

442442
if (SourceMgr.isWrittenInScratchSpace(FixLocation))
443443
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;
444444

445-
if (!utils::rangeCanBeFixed(Range, &SourceMgr))
445+
if (!utils::rangeCanBeFixed(UsageRange, &SourceMgr))
446446
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;
447+
448+
return EmplaceResult;
447449
}
448450

449-
void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl, SourceRange Range,
451+
void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl,
452+
SourceRange UsageRange,
450453
const SourceManager &SourceMgr) {
451-
// Don't keep track for non-identifier names.
452-
auto *II = Decl->getIdentifier();
453-
if (!II)
454+
if (hasNoName(Decl))
455+
return;
456+
457+
// Ignore ClassTemplateSpecializationDecl which are creating duplicate
458+
// replacements with CXXRecordDecl.
459+
if (isa<ClassTemplateSpecializationDecl>(Decl))
454460
return;
455-
if (const auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
456-
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method))
457-
Decl = Overridden;
458-
}
459-
Decl = cast<NamedDecl>(Decl->getCanonicalDecl());
460-
return addUsage(
461-
RenamerClangTidyCheck::NamingCheckId(Decl->getLocation(), II->getName()),
462-
Range, SourceMgr);
463-
}
464461

465-
void RenamerClangTidyCheck::checkNamedDecl(const NamedDecl *Decl,
466-
const SourceManager &SourceMgr) {
467-
std::optional<FailureInfo> MaybeFailure = getDeclFailureInfo(Decl, SourceMgr);
462+
// We don't want to create a failure for every NamedDecl we find. Ideally
463+
// there is just one NamedDecl in every group of "related" NamedDecls that
464+
// becomes the failure. This NamedDecl and all of its related NamedDecls
465+
// become usages. E.g. Since NamedDecls are Redeclarable, only the canonical
466+
// NamedDecl becomes the failure and all redeclarations become usages.
467+
const NamedDecl *FailureDecl = getFailureForNamedDecl(Decl);
468+
469+
std::optional<FailureInfo> MaybeFailure =
470+
getDeclFailureInfo(FailureDecl, SourceMgr);
468471
if (!MaybeFailure)
469472
return;
470473

471-
FailureInfo &Info = *MaybeFailure;
472-
NamingCheckFailure &Failure =
473-
NamingCheckFailures[NamingCheckId(Decl->getLocation(), Decl->getName())];
474-
SourceRange Range =
475-
DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())
476-
.getSourceRange();
477-
478-
const IdentifierTable &Idents = Decl->getASTContext().Idents;
479-
auto CheckNewIdentifier = Idents.find(Info.Fixup);
474+
NamingCheckId FailureId(FailureDecl->getLocation(), FailureDecl->getName());
475+
476+
auto [FailureIter, NewFailure] = addUsage(FailureId, UsageRange, SourceMgr);
477+
478+
if (FailureIter == NamingCheckFailures.end()) {
479+
// Nothing to do if the usage wasn't accepted.
480+
return;
481+
}
482+
if (!NewFailure) {
483+
// FailureInfo has already been provided.
484+
return;
485+
}
486+
487+
// Update the stored failure with info regarding the FailureDecl.
488+
NamingCheckFailure &Failure = FailureIter->second;
489+
Failure.Info = std::move(*MaybeFailure);
490+
491+
// Don't overwritte the failure status if it was already set.
492+
if (!Failure.shouldFix()) {
493+
return;
494+
}
495+
const IdentifierTable &Idents = FailureDecl->getASTContext().Idents;
496+
auto CheckNewIdentifier = Idents.find(Failure.Info.Fixup);
480497
if (CheckNewIdentifier != Idents.end()) {
481498
const IdentifierInfo *Ident = CheckNewIdentifier->second;
482499
if (Ident->isKeyword(getLangOpts()))
483500
Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword;
484501
else if (Ident->hasMacroDefinition())
485502
Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition;
486-
} else if (!isValidAsciiIdentifier(Info.Fixup)) {
503+
} else if (!isValidAsciiIdentifier(Failure.Info.Fixup)) {
487504
Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier;
488505
}
489-
490-
Failure.Info = std::move(Info);
491-
addUsage(Decl, Range, SourceMgr);
492506
}
493507

494508
void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) {

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,9 @@ class RenamerClangTidyCheck : public ClangTidyCheck {
115115
void expandMacro(const Token &MacroNameTok, const MacroInfo *MI,
116116
const SourceManager &SourceMgr);
117117

118-
void addUsage(const RenamerClangTidyCheck::NamingCheckId &Decl,
119-
SourceRange Range, const SourceManager &SourceMgr);
120-
121-
/// Convenience method when the usage to be added is a NamedDecl.
122118
void addUsage(const NamedDecl *Decl, SourceRange Range,
123119
const SourceManager &SourceMgr);
124120

125-
void checkNamedDecl(const NamedDecl *Decl, const SourceManager &SourceMgr);
126-
127121
protected:
128122
/// Overridden by derived classes, returns information about if and how a Decl
129123
/// failed the check. A 'std::nullopt' result means the Decl did not fail the
@@ -158,6 +152,14 @@ class RenamerClangTidyCheck : public ClangTidyCheck {
158152
const NamingCheckFailure &Failure) const = 0;
159153

160154
private:
155+
// Manage additions to the Failure/usage map
156+
//
157+
// return the result of NamingCheckFailures::try_emplace() if the usage was
158+
// accepted.
159+
std::pair<NamingCheckFailureMap::iterator, bool>
160+
addUsage(const RenamerClangTidyCheck::NamingCheckId &FailureId,
161+
SourceRange UsageRange, const SourceManager &SourceMgr);
162+
161163
NamingCheckFailureMap NamingCheckFailures;
162164
const bool AggressiveDependentMemberLookup;
163165
};

0 commit comments

Comments
 (0)