https://github.com/purnima-nlp created https://github.com/llvm/llvm-project/pull/210007
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. >From 3bbedb5091fb4cbdc1f35fe2e3cc860b4e7606df Mon Sep 17 00:00:00 2001 From: Purnima Shrivastava <[email protected]> Date: Thu, 16 Jul 2026 13:27:32 +0530 Subject: [PATCH] [clang-tidy] Fix redundant-tag check for hidden tag declarations --- .../clang-tidy/readability/CMakeLists.txt | 2 + .../readability/ReadabilityTidyModule.cpp | 4 + .../readability/RedundantTagCheck.cpp | 94 ++++++++++++++++++ .../readability/RedundantTagCheck.h | 36 +++++++ .../checkers/readability/redundant-tag.cpp | 95 +++++++++++++++++++ 5 files changed, 231 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/readability/RedundantTagCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/readability/RedundantTagCheck.h create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/redundant-tag.cpp 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
