[clang-tools-extra] solving the issue : [clang-tidy] Report redundant 'class/struct/union/enum' tag in declarations with c++ (issue #209136) (PR #210007)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 16 01:14:40 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tidy
Author: purnima shrivastava (purnima-nlp)
<details>
<summary>Changes</summary>
This patch adds the `readability-redundant-tag` clang-tidy check.
The check matches `TagTypeLoc` nodes and diagnoses redundant `class`, `struct`, `union`, and `enum` keywords in C++ declarations. Before emitting a diagnostic, it performs a lookup in the enclosing `DeclContext` to determine whether the corresponding `TagDecl` is hidden by another declaration.
If the lookup finds a declaration that hides the tag (for example, a variable, function, function template, field, or enum constant), the diagnostic is suppressed because the elaborated type specifier is required for correct name lookup.
also added regression tests covering:
- redundant `class`/`struct`/`union`/`enum` keywords,
- forward declarations,
- qualified and nested types,
- declarations where the tag is hidden by another declaration.
Fixes #<!-- -->209136.
---
Full diff: https://github.com/llvm/llvm-project/pull/210007.diff
5 Files Affected:
- (modified) clang-tools-extra/clang-tidy/readability/CMakeLists.txt (+2)
- (modified) clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp (+4)
- (added) clang-tools-extra/clang-tidy/readability/RedundantTagCheck.cpp (+94)
- (added) clang-tools-extra/clang-tidy/readability/RedundantTagCheck.h (+36)
- (added) clang-tools-extra/test/clang-tidy/checkers/readability/redundant-tag.cpp (+95)
``````````diff
diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
index 8a4b5753de890..edd964354a913 100644
--- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt
@@ -53,6 +53,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
RedundantSmartptrGetCheck.cpp
RedundantStringCStrCheck.cpp
RedundantStringInitCheck.cpp
+ RedundantTagCheck.cpp
RedundantTypenameCheck.cpp
ReferenceToConstructedTemporaryCheck.cpp
SimplifyBooleanExprCheck.cpp
@@ -69,6 +70,7 @@ add_clang_library(clangTidyReadabilityModule STATIC
UseConcisePreprocessorDirectivesCheck.cpp
UseStdMinMaxCheck.cpp
+
LINK_LIBS
clangTidy
clangTidyUtils
diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
index 69b31d6711bcd..ab543f618de00 100644
--- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp
@@ -70,6 +70,8 @@
#include "UseAnyOfAllOfCheck.h"
#include "UseConcisePreprocessorDirectivesCheck.h"
#include "UseStdMinMaxCheck.h"
+#include "RedundantTagCheck.h"
+
namespace clang::tidy {
namespace readability {
@@ -202,6 +204,8 @@ class ReadabilityModule : public ClangTidyModule {
"readability-use-concise-preprocessor-directives");
CheckFactories.registerCheck<UseStdMinMaxCheck>(
"readability-use-std-min-max");
+ CheckFactories.registerCheck<RedundantTagCheck>(
+ "readability-redundant-tag");
}
};
diff --git a/clang-tools-extra/clang-tidy/readability/RedundantTagCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantTagCheck.cpp
new file mode 100644
index 0000000000000..3ef167376fc0b
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/RedundantTagCheck.cpp
@@ -0,0 +1,94 @@
+//===--- RedundantTagCheck.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 "RedundantTagCheck.h"
+
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/TypeLoc.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::readability {
+
+namespace {
+
+static bool canHideTag(const NamedDecl *D) {
+ D = D->getUnderlyingDecl();
+
+ return isa<VarDecl>(D) ||
+ isa<EnumConstantDecl>(D) ||
+ isa<FunctionDecl>(D) ||
+ isa<FunctionTemplateDecl>(D) ||
+ isa<FieldDecl>(D) ||
+ isa<UnresolvedUsingValueDecl>(D);
+}
+
+} // namespace
+
+void RedundantTagCheck::registerMatchers(MatchFinder *Finder) {
+ Finder->addMatcher(
+ typeLoc(unless(hasAncestor(decl(isInstantiated())))).bind("typeLoc"),
+ this);
+}
+
+void RedundantTagCheck::check(
+ const MatchFinder::MatchResult &Result) {
+ const auto *TL = Result.Nodes.getNodeAs<TypeLoc>("typeLoc");
+ if (!TL)
+ return;
+
+ if (TL->getType()->isInstantiationDependentType())
+ return;
+
+ const auto TagTL = TL->getAs<TagTypeLoc>();
+ if (!TagTL)
+ return;
+
+ const TagDecl *TD = TagTL.getDecl();
+ if (!TD)
+ return;
+
+ auto Lookup = TD->getDeclContext()->lookup(TD->getDeclName());
+
+ for (NamedDecl *ND : Lookup) {
+ if (declaresSameEntity(ND, TD))
+ continue;
+
+ if (canHideTag(ND))
+ return;
+ }
+
+ SourceLocation KeywordLoc = TagTL.getElaboratedKeywordLoc();
+ if (KeywordLoc.isInvalid())
+ return;
+
+ Token Tok;
+ if (Lexer::getRawToken(KeywordLoc, Tok,
+ *Result.SourceManager,
+ getLangOpts()))
+ return;
+
+ llvm::StringRef Keyword = Tok.getRawIdentifier();
+
+ if (Keyword != "struct" &&
+ Keyword != "class" &&
+ Keyword != "union" &&
+ Keyword != "enum")
+ return;
+
+ diag(KeywordLoc,
+ "redundant '%0' keyword in C++ declaration")
+ << Keyword
+ << FixItHint::CreateRemoval(KeywordLoc);
+}
+
+} // namespace clang::tidy::readability
\ No newline at end of file
diff --git a/clang-tools-extra/clang-tidy/readability/RedundantTagCheck.h b/clang-tools-extra/clang-tidy/readability/RedundantTagCheck.h
new file mode 100644
index 0000000000000..0b1e5dcd0ca29
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/readability/RedundantTagCheck.h
@@ -0,0 +1,36 @@
+//===--- RedundantTagCheck.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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTTAGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTTAGCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::readability {
+
+class RedundantTagCheck : public ClangTidyCheck {
+public:
+ RedundantTagCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+
+ bool isLanguageVersionSupported(
+ const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus;
+ }
+
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+ std::optional<TraversalKind> getCheckTraversalKind() const override {
+ return TK_IgnoreUnlessSpelledInSource;
+ }
+};
+
+} // namespace clang::tidy::readability
+
+#endif
diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-tag.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-tag.cpp
new file mode 100644
index 0000000000000..f7201a5a797b3
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-tag.cpp
@@ -0,0 +1,95 @@
+//===--- RedundantTagCheck.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
+//
+//===----------------------------------------------------------------------===//
+
+struct Struct {};
+class Class {};
+union Union {};
+enum Enum {};
+
+void basic() {
+ struct Struct s;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant 'struct' keyword in C++ declaration
+ // CHECK-FIXES: Struct s;
+
+ class Class c;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant 'class' keyword in C++ declaration
+ // CHECK-FIXES: Class c;
+
+ union Union u;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant 'union' keyword in C++ declaration
+ // CHECK-FIXES: Union u;
+
+ enum Enum e;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant 'enum' keyword in C++ declaration
+ // CHECK-FIXES: Enum e;
+}
+
+// Hidden by variable (GitHub issue)
+struct Hidden {} Hidden;
+
+void hiddenByVariable() {
+ struct Hidden h;
+}
+
+// Forward declaration
+struct Forward;
+
+void forwardDecl() {
+ struct Forward *p;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant 'struct' keyword in C++ declaration
+ // CHECK-FIXES: Forward *p;
+}
+
+// Namespace-qualified type
+namespace N {
+struct NS {};
+}
+
+void namespaceQualified() {
+ struct N::NS x;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant 'struct' keyword in C++ declaration
+ // CHECK-FIXES: N::NS x;
+}
+
+// Nested type
+struct Outer {
+ struct Inner {};
+};
+
+void nestedType() {
+ struct Outer::Inner x;
+ // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant 'struct' keyword in C++ declaration
+ // CHECK-FIXES: Outer::Inner x;
+}
+
+// Hidden by function
+struct FuncTag {};
+
+void FuncTag();
+
+void hiddenByFunction() {
+ struct FuncTag x;
+}
+
+// Hidden by enum constant
+struct EnumTag {};
+
+enum { EnumTag };
+
+void hiddenByEnumConstant() {
+ struct EnumTag x;
+}
+
+// Hidden by another variable
+struct A {};
+
+A A;
+
+void anotherHiddenVariable() {
+ struct A x;
+}
\ No newline at end of file
``````````
</details>
https://github.com/llvm/llvm-project/pull/210007
More information about the cfe-commits
mailing list