Skip to content

Commit 16271c2

Browse files
committed
[clang-tidy] Refactor how NamedDecl are renamed
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 61f1f13 commit 16271c2

File tree

2 files changed

+111
-97
lines changed

2 files changed

+111
-97
lines changed

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

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

6363
namespace {
64+
/// Returns the function that \p Method is overridding. If There are none or
65+
/// multiple overrides it returns nullptr. If the overridden function itself is
66+
/// overridding then it will recurse up to find the first decl of the function.
67+
const CXXMethodDecl *getOverrideMethod(const CXXMethodDecl *Method) {
68+
if (Method->size_overridden_methods() != 1)
69+
return nullptr;
70+
71+
while (true) {
72+
Method = *Method->begin_overridden_methods();
73+
assert(Method && "Overridden method shouldn't be null");
74+
unsigned NumOverrides = Method->size_overridden_methods();
75+
if (NumOverrides == 0)
76+
return Method;
77+
if (NumOverrides > 1)
78+
return nullptr;
79+
}
80+
}
81+
82+
bool hasNoName(const NamedDecl *Decl) {
83+
return !Decl->getIdentifier() || Decl->getName().empty();
84+
}
85+
86+
const NamedDecl *getFailureForNamedDecl(const NamedDecl *ND) {
87+
const auto *Canonical = cast<NamedDecl>(ND->getCanonicalDecl());
88+
if (Canonical != ND)
89+
return Canonical;
90+
91+
if (const auto *Method = dyn_cast<CXXMethodDecl>(ND)) {
92+
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method))
93+
Canonical = cast<NamedDecl>(Overridden->getCanonicalDecl());
94+
95+
if (Canonical != ND)
96+
return Canonical;
97+
}
98+
99+
return ND;
100+
}
101+
64102
class NameLookup {
65103
llvm::PointerIntPair<const NamedDecl *, 1, bool> Data;
66104

@@ -132,24 +170,6 @@ static NameLookup findDeclInBases(const CXXRecordDecl &Parent,
132170
return NameLookup(Found); // If nullptr, decl wasn't found.
133171
}
134172

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-
153173
namespace {
154174

155175
/// Callback supplies macros to RenamerClangTidyCheck::checkMacro
@@ -192,10 +212,6 @@ class RenamerClangTidyVisitor
192212
: Check(Check), SM(SM),
193213
AggressiveDependentMemberLookup(AggressiveDependentMemberLookup) {}
194214

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

201217
bool shouldVisitImplicitCode() const { return false; }
@@ -246,29 +262,10 @@ class RenamerClangTidyVisitor
246262
}
247263

248264
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);
265+
SourceRange UsageRange =
266+
DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())
267+
.getSourceRange();
268+
Check->addUsage(Decl, UsageRange, SM);
272269
return true;
273270
}
274271

@@ -413,82 +410,97 @@ void RenamerClangTidyCheck::registerPPCallbacks(
413410
std::make_unique<RenamerClangTidyCheckPPCallbacks>(SM, this));
414411
}
415412

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

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();
421+
// Get the spelling location for performing the fix. This is necessary because
422+
// macros can map the same spelling location to different source locations,
423+
// and we only want to fix the token once, before it is expanded by the macro.
424+
SourceLocation FixLocation = UsageRange.getBegin();
428425
FixLocation = SourceMgr.getSpellingLoc(FixLocation);
429426
if (FixLocation.isInvalid())
430-
return;
427+
return {NamingCheckFailures.end(), false};
428+
429+
auto EmplaceResult = NamingCheckFailures.try_emplace(FailureId);
430+
NamingCheckFailure &Failure = EmplaceResult.first->second;
431431

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

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

442440
if (SourceMgr.isWrittenInScratchSpace(FixLocation))
443441
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;
444442

445-
if (!utils::rangeCanBeFixed(Range, &SourceMgr))
443+
if (!utils::rangeCanBeFixed(UsageRange, &SourceMgr))
446444
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;
445+
446+
return EmplaceResult;
447447
}
448448

449-
void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl, SourceRange Range,
449+
void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl,
450+
SourceRange UsageRange,
450451
const SourceManager &SourceMgr) {
451-
// Don't keep track for non-identifier names.
452-
auto *II = Decl->getIdentifier();
453-
if (!II)
452+
if (hasNoName(Decl))
453+
return;
454+
455+
// Ignore ClassTemplateSpecializationDecl which are creating duplicate
456+
// replacements with CXXRecordDecl.
457+
if (isa<ClassTemplateSpecializationDecl>(Decl))
454458
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-
}
464459

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

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);
472+
NamingCheckId FailureId(FailureDecl->getLocation(), FailureDecl->getName());
473+
474+
auto [FailureIter, NewFailure] = addUsage(FailureId, UsageRange, SourceMgr);
475+
476+
if (FailureIter == NamingCheckFailures.end()) {
477+
// Nothing to do if the usage wasn't accepted.
478+
return;
479+
}
480+
if (!NewFailure) {
481+
// FailureInfo has already been provided.
482+
return;
483+
}
484+
485+
// Update the stored failure with info regarding the FailureDecl.
486+
NamingCheckFailure &Failure = FailureIter->second;
487+
Failure.Info = std::move(*MaybeFailure);
488+
489+
// Don't overwritte the failure status if it was already set.
490+
if (!Failure.shouldFix()) {
491+
return;
492+
}
493+
const IdentifierTable &Idents = FailureDecl->getASTContext().Idents;
494+
auto CheckNewIdentifier = Idents.find(Failure.Info.Fixup);
480495
if (CheckNewIdentifier != Idents.end()) {
481496
const IdentifierInfo *Ident = CheckNewIdentifier->second;
482497
if (Ident->isKeyword(getLangOpts()))
483498
Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword;
484499
else if (Ident->hasMacroDefinition())
485500
Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition;
486-
} else if (!isValidAsciiIdentifier(Info.Fixup)) {
501+
} else if (!isValidAsciiIdentifier(Failure.Info.Fixup)) {
487502
Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier;
488503
}
489-
490-
Failure.Info = std::move(Info);
491-
addUsage(Decl, Range, SourceMgr);
492504
}
493505

494506
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)