2020-01-17 05:29:19 +08:00
|
|
|
//===--- RenamerClangTidyCheck.cpp - clang-tidy ---------------------------===//
|
|
|
|
//
|
|
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "RenamerClangTidyCheck.h"
|
|
|
|
#include "ASTUtils.h"
|
|
|
|
#include "clang/AST/CXXInheritance.h"
|
|
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
2020-11-24 04:04:50 +08:00
|
|
|
#include "clang/Basic/CharInfo.h"
|
2020-01-17 05:29:19 +08:00
|
|
|
#include "clang/Frontend/CompilerInstance.h"
|
|
|
|
#include "clang/Lex/PPCallbacks.h"
|
|
|
|
#include "clang/Lex/Preprocessor.h"
|
|
|
|
#include "llvm/ADT/DenseMapInfo.h"
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
#include "llvm/ADT/PointerIntPair.h"
|
2020-01-17 05:29:19 +08:00
|
|
|
|
|
|
|
#define DEBUG_TYPE "clang-tidy"
|
|
|
|
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
|
|
|
|
namespace llvm {
|
|
|
|
|
|
|
|
/// Specialisation of DenseMapInfo to allow NamingCheckId objects in DenseMaps
|
|
|
|
template <>
|
|
|
|
struct DenseMapInfo<clang::tidy::RenamerClangTidyCheck::NamingCheckId> {
|
|
|
|
using NamingCheckId = clang::tidy::RenamerClangTidyCheck::NamingCheckId;
|
|
|
|
|
|
|
|
static inline NamingCheckId getEmptyKey() {
|
2020-10-20 23:21:38 +08:00
|
|
|
return NamingCheckId(DenseMapInfo<clang::SourceLocation>::getEmptyKey(),
|
|
|
|
"EMPTY");
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline NamingCheckId getTombstoneKey() {
|
2020-10-20 23:21:38 +08:00
|
|
|
return NamingCheckId(DenseMapInfo<clang::SourceLocation>::getTombstoneKey(),
|
|
|
|
"TOMBSTONE");
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned getHashValue(NamingCheckId Val) {
|
|
|
|
assert(Val != getEmptyKey() && "Cannot hash the empty key!");
|
|
|
|
assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!");
|
|
|
|
|
|
|
|
std::hash<NamingCheckId::second_type> SecondHash;
|
2020-10-20 23:21:38 +08:00
|
|
|
return DenseMapInfo<clang::SourceLocation>::getHashValue(Val.first) +
|
|
|
|
SecondHash(Val.second);
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool isEqual(const NamingCheckId &LHS, const NamingCheckId &RHS) {
|
|
|
|
if (RHS == getEmptyKey())
|
|
|
|
return LHS == getEmptyKey();
|
|
|
|
if (RHS == getTombstoneKey())
|
|
|
|
return LHS == getTombstoneKey();
|
|
|
|
return LHS == RHS;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace llvm
|
|
|
|
|
|
|
|
namespace clang {
|
|
|
|
namespace tidy {
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
/// Callback supplies macros to RenamerClangTidyCheck::checkMacro
|
|
|
|
class RenamerClangTidyCheckPPCallbacks : public PPCallbacks {
|
|
|
|
public:
|
|
|
|
RenamerClangTidyCheckPPCallbacks(Preprocessor *PP,
|
|
|
|
RenamerClangTidyCheck *Check)
|
|
|
|
: PP(PP), Check(Check) {}
|
|
|
|
|
|
|
|
/// MacroDefined calls checkMacro for macros in the main file
|
|
|
|
void MacroDefined(const Token &MacroNameTok,
|
|
|
|
const MacroDirective *MD) override {
|
2020-05-27 22:01:17 +08:00
|
|
|
if (MD->getMacroInfo()->isBuiltinMacro())
|
|
|
|
return;
|
|
|
|
if (PP->getSourceManager().isWrittenInBuiltinFile(
|
|
|
|
MacroNameTok.getLocation()))
|
|
|
|
return;
|
|
|
|
if (PP->getSourceManager().isWrittenInCommandLineFile(
|
|
|
|
MacroNameTok.getLocation()))
|
|
|
|
return;
|
2020-01-17 05:29:19 +08:00
|
|
|
Check->checkMacro(PP->getSourceManager(), MacroNameTok, MD->getMacroInfo());
|
|
|
|
}
|
|
|
|
|
|
|
|
/// MacroExpands calls expandMacro for macros in the main file
|
|
|
|
void MacroExpands(const Token &MacroNameTok, const MacroDefinition &MD,
|
|
|
|
SourceRange /*Range*/,
|
|
|
|
const MacroArgs * /*Args*/) override {
|
|
|
|
Check->expandMacro(MacroNameTok, MD.getMacroInfo());
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
Preprocessor *PP;
|
|
|
|
RenamerClangTidyCheck *Check;
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
RenamerClangTidyCheck::RenamerClangTidyCheck(StringRef CheckName,
|
|
|
|
ClangTidyContext *Context)
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
: ClangTidyCheck(CheckName, Context),
|
|
|
|
AggressiveDependentMemberLookup(
|
|
|
|
Options.getLocalOrGlobal("AggressiveDependentMemberLookup", false)) {}
|
2020-01-17 05:29:19 +08:00
|
|
|
RenamerClangTidyCheck::~RenamerClangTidyCheck() = default;
|
|
|
|
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
void RenamerClangTidyCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
|
|
|
|
Options.store(Opts, "AggressiveDependentMemberLookup",
|
|
|
|
AggressiveDependentMemberLookup);
|
|
|
|
}
|
|
|
|
|
2020-01-17 05:29:19 +08:00
|
|
|
void RenamerClangTidyCheck::registerMatchers(MatchFinder *Finder) {
|
|
|
|
Finder->addMatcher(namedDecl().bind("decl"), this);
|
|
|
|
Finder->addMatcher(usingDecl().bind("using"), this);
|
|
|
|
Finder->addMatcher(declRefExpr().bind("declRef"), this);
|
|
|
|
Finder->addMatcher(cxxConstructorDecl(unless(isImplicit())).bind("classRef"),
|
|
|
|
this);
|
|
|
|
Finder->addMatcher(cxxDestructorDecl(unless(isImplicit())).bind("classRef"),
|
|
|
|
this);
|
|
|
|
Finder->addMatcher(typeLoc().bind("typeLoc"), this);
|
|
|
|
Finder->addMatcher(nestedNameSpecifierLoc().bind("nestedNameLoc"), this);
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
auto MemberRestrictions =
|
|
|
|
unless(forFunction(anyOf(isDefaulted(), isImplicit())));
|
|
|
|
Finder->addMatcher(memberExpr(MemberRestrictions).bind("memberExpr"), this);
|
2020-01-17 05:29:19 +08:00
|
|
|
Finder->addMatcher(
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
cxxDependentScopeMemberExpr(MemberRestrictions).bind("depMemberExpr"),
|
2020-01-17 05:29:19 +08:00
|
|
|
this);
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenamerClangTidyCheck::registerPPCallbacks(
|
|
|
|
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
|
|
|
|
ModuleExpanderPP->addPPCallbacks(
|
|
|
|
std::make_unique<RenamerClangTidyCheckPPCallbacks>(ModuleExpanderPP,
|
|
|
|
this));
|
|
|
|
}
|
|
|
|
|
2020-10-19 22:21:05 +08:00
|
|
|
/// Returns the function that \p Method is overridding. If There are none or
|
|
|
|
/// multiple overrides it returns nullptr. If the overridden function itself is
|
|
|
|
/// overridding then it will recurse up to find the first decl of the function.
|
|
|
|
static const CXXMethodDecl *getOverrideMethod(const CXXMethodDecl *Method) {
|
|
|
|
if (Method->size_overridden_methods() != 1)
|
|
|
|
return nullptr;
|
|
|
|
while (true) {
|
|
|
|
Method = *Method->begin_overridden_methods();
|
|
|
|
assert(Method && "Overridden method shouldn't be null");
|
|
|
|
unsigned NumOverrides = Method->size_overridden_methods();
|
|
|
|
if (NumOverrides == 0)
|
|
|
|
return Method;
|
|
|
|
if (NumOverrides > 1)
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-10 01:57:05 +08:00
|
|
|
void RenamerClangTidyCheck::addUsage(
|
|
|
|
const RenamerClangTidyCheck::NamingCheckId &Decl, SourceRange Range,
|
|
|
|
SourceManager *SourceMgr) {
|
2020-01-17 05:29:19 +08:00
|
|
|
// Do nothing if the provided range is invalid.
|
2020-01-31 01:27:32 +08:00
|
|
|
if (Range.isInvalid())
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
// If we have a source manager, use it to convert to the spelling location for
|
|
|
|
// performing the fix. This is necessary because macros can map the same
|
|
|
|
// spelling location to different source locations, and we only want to fix
|
|
|
|
// the token once, before it is expanded by the macro.
|
|
|
|
SourceLocation FixLocation = Range.getBegin();
|
|
|
|
if (SourceMgr)
|
|
|
|
FixLocation = SourceMgr->getSpellingLoc(FixLocation);
|
|
|
|
if (FixLocation.isInvalid())
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Try to insert the identifier location in the Usages map, and bail out if it
|
|
|
|
// is already in there
|
2020-05-10 01:57:05 +08:00
|
|
|
RenamerClangTidyCheck::NamingCheckFailure &Failure =
|
|
|
|
NamingCheckFailures[Decl];
|
2020-10-20 23:21:38 +08:00
|
|
|
if (!Failure.RawUsageLocs.insert(FixLocation).second)
|
2020-08-04 16:27:01 +08:00
|
|
|
return;
|
|
|
|
|
2020-01-17 05:29:19 +08:00
|
|
|
if (!Failure.ShouldFix())
|
|
|
|
return;
|
|
|
|
|
2020-06-23 01:26:17 +08:00
|
|
|
if (SourceMgr && SourceMgr->isWrittenInScratchSpace(FixLocation))
|
|
|
|
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;
|
|
|
|
|
2020-01-17 05:29:19 +08:00
|
|
|
if (!utils::rangeCanBeFixed(Range, SourceMgr))
|
|
|
|
Failure.FixStatus = RenamerClangTidyCheck::ShouldFixStatus::InsideMacro;
|
|
|
|
}
|
|
|
|
|
2020-05-10 01:57:05 +08:00
|
|
|
void RenamerClangTidyCheck::addUsage(const NamedDecl *Decl, SourceRange Range,
|
|
|
|
SourceManager *SourceMgr) {
|
2020-10-19 22:21:05 +08:00
|
|
|
if (const auto *Method = dyn_cast<CXXMethodDecl>(Decl)) {
|
|
|
|
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method))
|
|
|
|
Decl = Overridden;
|
|
|
|
}
|
2020-06-18 22:50:15 +08:00
|
|
|
Decl = cast<NamedDecl>(Decl->getCanonicalDecl());
|
2020-05-10 01:57:05 +08:00
|
|
|
return addUsage(RenamerClangTidyCheck::NamingCheckId(Decl->getLocation(),
|
2020-01-17 05:29:19 +08:00
|
|
|
Decl->getNameAsString()),
|
|
|
|
Range, SourceMgr);
|
|
|
|
}
|
|
|
|
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
const NamedDecl *findDecl(const RecordDecl &RecDecl, StringRef DeclName) {
|
|
|
|
for (const Decl *D : RecDecl.decls()) {
|
|
|
|
if (const auto *ND = dyn_cast<NamedDecl>(D)) {
|
|
|
|
if (ND->getDeclName().isIdentifier() && ND->getName().equals(DeclName))
|
|
|
|
return ND;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
class NameLookup {
|
|
|
|
llvm::PointerIntPair<const NamedDecl *, 1, bool> Data;
|
|
|
|
|
|
|
|
public:
|
|
|
|
explicit NameLookup(const NamedDecl *ND) : Data(ND, false) {}
|
|
|
|
explicit NameLookup(llvm::NoneType) : Data(nullptr, true) {}
|
|
|
|
explicit NameLookup(std::nullptr_t) : Data(nullptr, false) {}
|
|
|
|
NameLookup() : NameLookup(nullptr) {}
|
|
|
|
|
|
|
|
bool hasMultipleResolutions() const { return Data.getInt(); }
|
|
|
|
const NamedDecl *getDecl() const {
|
|
|
|
assert(!hasMultipleResolutions() && "Found multiple decls");
|
|
|
|
return Data.getPointer();
|
|
|
|
}
|
|
|
|
operator bool() const { return !hasMultipleResolutions(); }
|
|
|
|
const NamedDecl *operator*() const { return getDecl(); }
|
|
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
/// Returns a decl matching the \p DeclName in \p Parent or one of its base
|
|
|
|
/// classes. If \p AggressiveTemplateLookup is `true` then it will check
|
|
|
|
/// template dependent base classes as well.
|
|
|
|
/// If a matching decl is found in multiple base classes then it will return a
|
|
|
|
/// flag indicating the multiple resolutions.
|
|
|
|
NameLookup findDeclInBases(const CXXRecordDecl &Parent, StringRef DeclName,
|
|
|
|
bool AggressiveTemplateLookup) {
|
2020-05-15 19:15:35 +08:00
|
|
|
if (!Parent.hasDefinition())
|
|
|
|
return NameLookup(nullptr);
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
if (const NamedDecl *InClassRef = findDecl(Parent, DeclName))
|
|
|
|
return NameLookup(InClassRef);
|
|
|
|
const NamedDecl *Found = nullptr;
|
|
|
|
|
|
|
|
for (CXXBaseSpecifier Base : Parent.bases()) {
|
|
|
|
const auto *Record = Base.getType()->getAsCXXRecordDecl();
|
|
|
|
if (!Record && AggressiveTemplateLookup) {
|
|
|
|
if (const auto *TST =
|
|
|
|
Base.getType()->getAs<TemplateSpecializationType>()) {
|
|
|
|
if (const auto *TD = llvm::dyn_cast_or_null<ClassTemplateDecl>(
|
|
|
|
TST->getTemplateName().getAsTemplateDecl()))
|
|
|
|
Record = TD->getTemplatedDecl();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!Record)
|
|
|
|
continue;
|
|
|
|
if (auto Search =
|
|
|
|
findDeclInBases(*Record, DeclName, AggressiveTemplateLookup)) {
|
|
|
|
if (*Search) {
|
|
|
|
if (Found)
|
|
|
|
return NameLookup(
|
|
|
|
llvm::None); // Multiple decls found in different base classes.
|
|
|
|
Found = *Search;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
return NameLookup(llvm::None); // Propagate multiple resolution back up.
|
|
|
|
}
|
|
|
|
return NameLookup(Found); // If nullptr, decl wasnt found.
|
|
|
|
}
|
|
|
|
|
2020-01-17 05:29:19 +08:00
|
|
|
void RenamerClangTidyCheck::check(const MatchFinder::MatchResult &Result) {
|
|
|
|
if (const auto *Decl =
|
|
|
|
Result.Nodes.getNodeAs<CXXConstructorDecl>("classRef")) {
|
|
|
|
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Decl->getParent(), Decl->getNameInfo().getSourceRange(),
|
|
|
|
Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
|
|
|
|
for (const auto *Init : Decl->inits()) {
|
|
|
|
if (!Init->isWritten() || Init->isInClassMemberInitializer())
|
|
|
|
continue;
|
|
|
|
if (const FieldDecl *FD = Init->getAnyMember())
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(FD, SourceRange(Init->getMemberLocation()),
|
|
|
|
Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
// Note: delegating constructors and base class initializers are handled
|
|
|
|
// via the "typeLoc" matcher.
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (const auto *Decl =
|
|
|
|
Result.Nodes.getNodeAs<CXXDestructorDecl>("classRef")) {
|
|
|
|
|
|
|
|
SourceRange Range = Decl->getNameInfo().getSourceRange();
|
|
|
|
if (Range.getBegin().isInvalid())
|
|
|
|
return;
|
|
|
|
// The first token that will be found is the ~ (or the equivalent trigraph),
|
|
|
|
// we want instead to replace the next token, that will be the identifier.
|
|
|
|
Range.setBegin(CharSourceRange::getTokenRange(Range).getEnd());
|
|
|
|
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Decl->getParent(), Range, Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (const auto *Loc = Result.Nodes.getNodeAs<TypeLoc>("typeLoc")) {
|
2020-03-23 03:09:00 +08:00
|
|
|
UnqualTypeLoc Unqual = Loc->getUnqualifiedLoc();
|
2020-01-17 05:29:19 +08:00
|
|
|
NamedDecl *Decl = nullptr;
|
2020-03-23 03:09:00 +08:00
|
|
|
if (const auto &Ref = Unqual.getAs<TagTypeLoc>())
|
2020-01-17 05:29:19 +08:00
|
|
|
Decl = Ref.getDecl();
|
2020-03-23 03:09:00 +08:00
|
|
|
else if (const auto &Ref = Unqual.getAs<InjectedClassNameTypeLoc>())
|
2020-01-17 05:29:19 +08:00
|
|
|
Decl = Ref.getDecl();
|
2020-03-23 03:09:00 +08:00
|
|
|
else if (const auto &Ref = Unqual.getAs<UnresolvedUsingTypeLoc>())
|
2020-01-17 05:29:19 +08:00
|
|
|
Decl = Ref.getDecl();
|
2020-03-23 03:09:00 +08:00
|
|
|
else if (const auto &Ref = Unqual.getAs<TemplateTypeParmTypeLoc>())
|
2020-01-17 05:29:19 +08:00
|
|
|
Decl = Ref.getDecl();
|
|
|
|
// further TypeLocs handled below
|
|
|
|
|
|
|
|
if (Decl) {
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Decl, Loc->getSourceRange(), Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (const auto &Ref = Loc->getAs<TemplateSpecializationTypeLoc>()) {
|
|
|
|
const TemplateDecl *Decl =
|
|
|
|
Ref.getTypePtr()->getTemplateName().getAsTemplateDecl();
|
|
|
|
|
|
|
|
SourceRange Range(Ref.getTemplateNameLoc(), Ref.getTemplateNameLoc());
|
|
|
|
if (const auto *ClassDecl = dyn_cast<TemplateDecl>(Decl)) {
|
|
|
|
if (const NamedDecl *TemplDecl = ClassDecl->getTemplatedDecl())
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(TemplDecl, Range, Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (const auto &Ref =
|
|
|
|
Loc->getAs<DependentTemplateSpecializationTypeLoc>()) {
|
|
|
|
if (const TagDecl *Decl = Ref.getTypePtr()->getAsTagDecl())
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Decl, Loc->getSourceRange(), Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (const auto *Loc =
|
|
|
|
Result.Nodes.getNodeAs<NestedNameSpecifierLoc>("nestedNameLoc")) {
|
|
|
|
if (const NestedNameSpecifier *Spec = Loc->getNestedNameSpecifier()) {
|
|
|
|
if (const NamespaceDecl *Decl = Spec->getAsNamespace()) {
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Decl, Loc->getLocalSourceRange(), Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (const auto *Decl = Result.Nodes.getNodeAs<UsingDecl>("using")) {
|
|
|
|
for (const auto *Shadow : Decl->shadows())
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Shadow->getTargetDecl(), Decl->getNameInfo().getSourceRange(),
|
|
|
|
Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (const auto *DeclRef = Result.Nodes.getNodeAs<DeclRefExpr>("declRef")) {
|
|
|
|
SourceRange Range = DeclRef->getNameInfo().getSourceRange();
|
2020-05-10 01:57:05 +08:00
|
|
|
addUsage(DeclRef->getDecl(), Range, Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (const auto *MemberRef =
|
|
|
|
Result.Nodes.getNodeAs<MemberExpr>("memberExpr")) {
|
|
|
|
SourceRange Range = MemberRef->getMemberNameInfo().getSourceRange();
|
2020-05-10 01:57:05 +08:00
|
|
|
addUsage(MemberRef->getMemberDecl(), Range, Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
if (const auto *DepMemberRef =
|
|
|
|
Result.Nodes.getNodeAs<CXXDependentScopeMemberExpr>(
|
|
|
|
"depMemberExpr")) {
|
|
|
|
QualType BaseType = DepMemberRef->isArrow()
|
|
|
|
? DepMemberRef->getBaseType()->getPointeeType()
|
|
|
|
: DepMemberRef->getBaseType();
|
|
|
|
if (BaseType.isNull())
|
|
|
|
return;
|
|
|
|
const CXXRecordDecl *Base = BaseType.getTypePtr()->getAsCXXRecordDecl();
|
|
|
|
if (!Base)
|
|
|
|
return;
|
|
|
|
DeclarationName DeclName = DepMemberRef->getMemberNameInfo().getName();
|
|
|
|
if (!DeclName.isIdentifier())
|
|
|
|
return;
|
|
|
|
StringRef DependentName = DeclName.getAsIdentifierInfo()->getName();
|
|
|
|
|
|
|
|
if (NameLookup Resolved = findDeclInBases(
|
|
|
|
*Base, DependentName, AggressiveDependentMemberLookup)) {
|
|
|
|
if (*Resolved)
|
2020-05-10 01:57:05 +08:00
|
|
|
addUsage(*Resolved, DepMemberRef->getMemberNameInfo().getSourceRange(),
|
[clang-tidy] RenamerClangTidy now renames dependent member expr when the member can be resolved
Summary:
Sometimes in templated code Member references are reported as `DependentScopeMemberExpr` because that's what the standard dictates, however in many trivial cases it is easy to resolve the reference to its actual Member.
Take this code:
```
template<typename T>
class A{
int value;
A& operator=(const A& Other){
value = Other.value;
this->value = Other.value;
return *this;
}
};
```
When ran with `clang-tidy file.cpp -checks=readability-identifier-naming --config="{CheckOptions: [{key: readability-identifier-naming.MemberPrefix, value: m_}]}" -fix`
Current behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.value;
this->value = Other.value;
return *this;
}
};
```
As `this->value` and `Other.value` are Dependent they are ignored when creating the fix-its, however this can easily be resolved.
Proposed behaviour:
```
template<typename T>
class A{
int m_value;
A& operator=(const A& Other){
m_value = Other.m_value;
this->m_value = Other.m_value;
return *this;
}
};
```
Reviewers: aaron.ballman, JonasToth, alexfh, hokein, gribozavr2
Reviewed By: aaron.ballman
Subscribers: merge_guards_bot, xazax.hun, cfe-commits
Tags: #clang, #clang-tools-extra
Differential Revision: https://reviews.llvm.org/D73052
2020-05-09 02:30:18 +08:00
|
|
|
Result.SourceManager);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-17 05:29:19 +08:00
|
|
|
if (const auto *Decl = Result.Nodes.getNodeAs<NamedDecl>("decl")) {
|
2020-03-19 06:13:01 +08:00
|
|
|
// Fix using namespace declarations.
|
|
|
|
if (const auto *UsingNS = dyn_cast<UsingDirectiveDecl>(Decl))
|
2020-05-10 01:57:05 +08:00
|
|
|
addUsage(UsingNS->getNominatedNamespaceAsWritten(),
|
2020-06-23 01:26:17 +08:00
|
|
|
UsingNS->getIdentLocation(), Result.SourceManager);
|
2020-03-19 06:13:01 +08:00
|
|
|
|
2020-01-17 05:29:19 +08:00
|
|
|
if (!Decl->getIdentifier() || Decl->getName().empty() || Decl->isImplicit())
|
|
|
|
return;
|
|
|
|
|
2020-06-18 22:50:15 +08:00
|
|
|
const auto *Canonical = cast<NamedDecl>(Decl->getCanonicalDecl());
|
|
|
|
if (Canonical != Decl) {
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Canonical, Decl->getLocation(), Result.SourceManager);
|
2020-06-18 22:50:15 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-01-17 05:29:19 +08:00
|
|
|
// Fix type aliases in value declarations.
|
|
|
|
if (const auto *Value = Result.Nodes.getNodeAs<ValueDecl>("decl")) {
|
|
|
|
if (const Type *TypePtr = Value->getType().getTypePtrOrNull()) {
|
|
|
|
if (const auto *Typedef = TypePtr->getAs<TypedefType>())
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Typedef->getDecl(), Value->getSourceRange(),
|
|
|
|
Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fix type aliases in function declarations.
|
|
|
|
if (const auto *Value = Result.Nodes.getNodeAs<FunctionDecl>("decl")) {
|
|
|
|
if (const auto *Typedef =
|
|
|
|
Value->getReturnType().getTypePtr()->getAs<TypedefType>())
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Typedef->getDecl(), Value->getSourceRange(),
|
|
|
|
Result.SourceManager);
|
2020-01-31 01:27:32 +08:00
|
|
|
for (const ParmVarDecl *Param : Value->parameters()) {
|
|
|
|
if (const TypedefType *Typedef =
|
|
|
|
Param->getType().getTypePtr()->getAs<TypedefType>())
|
2020-06-23 01:26:17 +08:00
|
|
|
addUsage(Typedef->getDecl(), Value->getSourceRange(),
|
|
|
|
Result.SourceManager);
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-19 22:21:05 +08:00
|
|
|
// Fix overridden methods
|
|
|
|
if (const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("decl")) {
|
|
|
|
if (const CXXMethodDecl *Overridden = getOverrideMethod(Method)) {
|
|
|
|
addUsage(Overridden, Method->getLocation());
|
|
|
|
return; // Don't try to add the actual decl as a Failure.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-17 05:29:19 +08:00
|
|
|
// Ignore ClassTemplateSpecializationDecl which are creating duplicate
|
|
|
|
// replacements with CXXRecordDecl.
|
|
|
|
if (isa<ClassTemplateSpecializationDecl>(Decl))
|
|
|
|
return;
|
|
|
|
|
|
|
|
Optional<FailureInfo> MaybeFailure =
|
|
|
|
GetDeclFailureInfo(Decl, *Result.SourceManager);
|
|
|
|
if (!MaybeFailure)
|
|
|
|
return;
|
|
|
|
FailureInfo &Info = *MaybeFailure;
|
|
|
|
NamingCheckFailure &Failure = NamingCheckFailures[NamingCheckId(
|
|
|
|
Decl->getLocation(), Decl->getNameAsString())];
|
|
|
|
SourceRange Range =
|
|
|
|
DeclarationNameInfo(Decl->getDeclName(), Decl->getLocation())
|
|
|
|
.getSourceRange();
|
|
|
|
|
|
|
|
const IdentifierTable &Idents = Decl->getASTContext().Idents;
|
|
|
|
auto CheckNewIdentifier = Idents.find(Info.Fixup);
|
|
|
|
if (CheckNewIdentifier != Idents.end()) {
|
|
|
|
const IdentifierInfo *Ident = CheckNewIdentifier->second;
|
|
|
|
if (Ident->isKeyword(getLangOpts()))
|
|
|
|
Failure.FixStatus = ShouldFixStatus::ConflictsWithKeyword;
|
|
|
|
else if (Ident->hasMacroDefinition())
|
|
|
|
Failure.FixStatus = ShouldFixStatus::ConflictsWithMacroDefinition;
|
2020-11-24 04:04:50 +08:00
|
|
|
} else if (!isValidIdentifier(Info.Fixup)) {
|
|
|
|
Failure.FixStatus = ShouldFixStatus::FixInvalidIdentifier;
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Failure.Info = std::move(Info);
|
2020-05-10 01:57:05 +08:00
|
|
|
addUsage(Decl, Range);
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenamerClangTidyCheck::checkMacro(SourceManager &SourceMgr,
|
|
|
|
const Token &MacroNameTok,
|
|
|
|
const MacroInfo *MI) {
|
|
|
|
Optional<FailureInfo> MaybeFailure =
|
|
|
|
GetMacroFailureInfo(MacroNameTok, SourceMgr);
|
|
|
|
if (!MaybeFailure)
|
|
|
|
return;
|
|
|
|
FailureInfo &Info = *MaybeFailure;
|
|
|
|
StringRef Name = MacroNameTok.getIdentifierInfo()->getName();
|
2020-01-29 17:32:04 +08:00
|
|
|
NamingCheckId ID(MI->getDefinitionLoc(), std::string(Name));
|
2020-01-17 05:29:19 +08:00
|
|
|
NamingCheckFailure &Failure = NamingCheckFailures[ID];
|
|
|
|
SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());
|
|
|
|
|
|
|
|
Failure.Info = std::move(Info);
|
2020-05-10 01:57:05 +08:00
|
|
|
addUsage(ID, Range);
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void RenamerClangTidyCheck::expandMacro(const Token &MacroNameTok,
|
|
|
|
const MacroInfo *MI) {
|
|
|
|
StringRef Name = MacroNameTok.getIdentifierInfo()->getName();
|
2020-01-29 17:32:04 +08:00
|
|
|
NamingCheckId ID(MI->getDefinitionLoc(), std::string(Name));
|
2020-01-17 05:29:19 +08:00
|
|
|
|
|
|
|
auto Failure = NamingCheckFailures.find(ID);
|
|
|
|
if (Failure == NamingCheckFailures.end())
|
|
|
|
return;
|
|
|
|
|
|
|
|
SourceRange Range(MacroNameTok.getLocation(), MacroNameTok.getEndLoc());
|
2020-05-10 01:57:05 +08:00
|
|
|
addUsage(ID, Range);
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static std::string
|
|
|
|
getDiagnosticSuffix(const RenamerClangTidyCheck::ShouldFixStatus FixStatus,
|
|
|
|
const std::string &Fixup) {
|
2020-11-24 04:04:50 +08:00
|
|
|
if (Fixup.empty() ||
|
|
|
|
FixStatus == RenamerClangTidyCheck::ShouldFixStatus::FixInvalidIdentifier)
|
2020-01-17 05:29:19 +08:00
|
|
|
return "; cannot be fixed automatically";
|
|
|
|
if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ShouldFix)
|
|
|
|
return {};
|
|
|
|
if (FixStatus >=
|
|
|
|
RenamerClangTidyCheck::ShouldFixStatus::IgnoreFailureThreshold)
|
|
|
|
return {};
|
|
|
|
if (FixStatus == RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithKeyword)
|
|
|
|
return "; cannot be fixed because '" + Fixup +
|
|
|
|
"' would conflict with a keyword";
|
|
|
|
if (FixStatus ==
|
|
|
|
RenamerClangTidyCheck::ShouldFixStatus::ConflictsWithMacroDefinition)
|
|
|
|
return "; cannot be fixed because '" + Fixup +
|
|
|
|
"' would conflict with a macro definition";
|
|
|
|
llvm_unreachable("invalid ShouldFixStatus");
|
|
|
|
}
|
|
|
|
|
|
|
|
void RenamerClangTidyCheck::onEndOfTranslationUnit() {
|
|
|
|
for (const auto &Pair : NamingCheckFailures) {
|
|
|
|
const NamingCheckId &Decl = Pair.first;
|
|
|
|
const NamingCheckFailure &Failure = Pair.second;
|
|
|
|
|
|
|
|
if (Failure.Info.KindName.empty())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (Failure.ShouldNotify()) {
|
|
|
|
auto DiagInfo = GetDiagInfo(Decl, Failure);
|
|
|
|
auto Diag = diag(Decl.first,
|
|
|
|
DiagInfo.Text + getDiagnosticSuffix(Failure.FixStatus,
|
|
|
|
Failure.Info.Fixup));
|
|
|
|
DiagInfo.ApplyArgs(Diag);
|
|
|
|
|
|
|
|
if (Failure.ShouldFix()) {
|
|
|
|
for (const auto &Loc : Failure.RawUsageLocs) {
|
|
|
|
// We assume that the identifier name is made of one token only. This
|
|
|
|
// is always the case as we ignore usages in macros that could build
|
|
|
|
// identifier names by combining multiple tokens.
|
|
|
|
//
|
|
|
|
// For destructors, we already take care of it by remembering the
|
|
|
|
// location of the start of the identifier and not the start of the
|
|
|
|
// tilde.
|
|
|
|
//
|
|
|
|
// Other multi-token identifiers, such as operators are not checked at
|
|
|
|
// all.
|
2020-10-20 23:21:38 +08:00
|
|
|
Diag << FixItHint::CreateReplacement(SourceRange(Loc),
|
|
|
|
Failure.Info.Fixup);
|
2020-01-17 05:29:19 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace tidy
|
|
|
|
} // namespace clang
|