Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create new clang-tidy check to add namespaces to symbol references #70621

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

daltairwalter
Copy link

@daltairwalter daltairwalter commented Oct 30, 2023

What should be done when "using namespace" has been abused or used in header files? This check will update all references to include full namespace qualification.

Closes #64441

@github-actions
Copy link

github-actions bot commented Oct 30, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

}

std::string getIdentifierString(const IdentifierInfo *identifier) {
return (identifier) ? identifier->getNameStart() : "(nullptr)";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are brackets around identifier necessary?

limitToPattern(Options.get("LimitToPattern", "")),
onlyExpandUsingNamespace(Options.get("OnlyExpandUsingNamespace", true)),
diagnosticLevel(Options.get("DiagnosticLevel", 0)) {
auto limitString = limitToPattern.str();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not use auto unless type is explicitly spelled in same statement or iterator. Same in other places.

for (; currentTargetIndex < targetContextVector.size();
++currentTargetIndex) {
auto currentContext = targetContextVector[currentTargetIndex];
if (auto currentNamespace = dyn_cast<NamespaceDecl>(currentContext)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const auto *. Same in other places.

readability-use-explicit-namespaces
===================================

This check detects and fixes references to members of namespaces where the namespace is not explicity specified in the reference. By default, this will only change references that are found through a using namespace directive.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please synchronize first statement with Release Notes. This check is omitted. Please follow 80 characters limit. Double space before This check.


This check detects and fixes references to members of namespaces where the namespace is not explicity specified in the reference. By default, this will only change references that are found through a using namespace directive.

Example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other documentation for code block examples.

string something = "something"; // this is not change by default because it is referenced through using std::string instead of using namespace std
std::cout << something;

Options:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other documentation for options examples.

// CHECK-MESSAGES: :[[@LINE+1]]:2: warning: Missing namespace qualifiers foo::
doSomething();
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excessive newline.

}

std::string getIdentifierString(const IdentifierInfo *identifier) {
return (identifier) ? identifier->getNameStart() : "(nullptr)";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use getName() and return StringRef instead of std::string

return (identifier) ? identifier->getNameStart() : "(nullptr)";
}

std::string getMatchContext(const char *match, const DynTypedNode &node) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use StringRef instead of "const char*"

return out.str();
}

std::string trueFalseString(bool value) { return value ? "true" : "false"; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return StringRef


std::string getMatchContext(const char *match, const DynTypedNode &node) {
std::stringstream out;
out << match << "(" << node.getNodeKind().asStringRef().str() << ")";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid std::stringstream, it's expensive to create, just merge those strings, you may use SmallString

UseExplicitNamespacesCheck::UseExplicitNamespacesCheck(
StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
limitToPattern(Options.get("LimitToPattern", "")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General: Variables and members should start with Upcase.

@@ -0,0 +1,22 @@
// RUN: %check_clang_tidy %s readability-use-explicit-namespaces %t

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests with:

  • templates
  • operators
  • classes
  • fixes
  • ...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there tools or resources for this? I locally have done a lot more testing, but it is very clear how to turn this into test cases.

Options:
LimitToPattern - only change things that match pattern e.g. "value: std" would limit changes to the std namespace
OnlyExpandUsingNamespace - defaults to true meaning that only references found by using namespace are changed
DiagnosticLevel - add diagnostic information about the choices being made by this check and what it is internally using
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option may not be needed or should be described better,

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may make sense to remove DiagnosticLevel if clang-tidy has a similar concept already - could you identify this?

Comment on lines +30 to +53

IdentifierInfo *
getTypeNestedNameIdentfierRecursive(const Type *type,
std::string &nestedTypeInfo);
bool matchesNamespaceLimits(
const std::vector<const DeclContext *> &targetContextVector);

void diagOut(const SourceLocation &sourcePosition,
const std::string &message);

void processTransform(NestedNameSpecifierLoc nestedName,
const SourceLocation &sourcePosition,
const NamedDecl *target,
const DeclContext *referenceContext, bool usingShadow,
const std::string &context);

void processTypePiecesRecursive(NestedNameSpecifierLoc nestedName,
const TypeLoc &typeLoc,
const DeclContext *declContext,
const std::string &context);

void processTypePieces(TypeSourceInfo *typeSourceInfo,
const DeclContext *declContext,
const std::string &context);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make all those methods private, and if they not needed make them non-class members private methods in .cpp

if (currentNamespace->isAnonymousNamespace()) {
return false;
}
if (currentNamespace->getIdentifier()->getName().str() == name) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may crash, getIdentifier may return nullptr;

return true;
}
size_t currentTargetIndex = 0;
auto findMatch = [&currentTargetIndex,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extract this lambda to separate function

size_t currentTargetIndex = 0;
auto findMatch = [&currentTargetIndex,
&targetContextVector](const std::string &name) -> bool {
while (currentTargetIndex < targetContextVector.size()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use iterators.

while (currentTargetIndex < targetContextVector.size()) {
auto currentContext = targetContextVector[currentTargetIndex];
++currentTargetIndex;
if (auto currentNamespace = dyn_cast<NamespaceDecl>(currentContext)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (auto currentNamespace = dyn_cast<NamespaceDecl>(currentContext)) {
if (const auto* CurrentNamespace = dyn_cast<NamespaceDecl>(currentContext)) {

if (!currentNamespace->isInline()) {
return false;
}
} else {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reduce amount of code blocks, and things like if/else/return

}
return false;
};
for (size_t i = 0; i < limitToPatternVector.size(); ++i) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use iterators or range-for

return true;
}

IdentifierInfo *UseExplicitNamespacesCheck::getTypeNestedNameIdentfierRecursive(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed to make check working, remove custom diagnostic.

Copy link
Member

@PiotrZSL PiotrZSL left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like an idea, but this check require lot of work.
Todo:

  • Update to LLVM codding standard - https://llvm.org/docs/CodingStandards.html
  • Remove any custom diagnostic and things (noise) that aren't needed to make check function
  • Update code to C++17
  • Add more test
  • Improve documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

clang-tidy: fully qualify names from a given namespace (e.g. clean up using namespace std;)
4 participants