[clang-tools-extra] New check for static_cast of an enum class type (PR #167212)
via cfe-commits
cfe-commits at lists.llvm.org
Sun Nov 9 02:16:11 PST 2025
https://github.com/arjunkonidala created https://github.com/llvm/llvm-project/pull/167212
It warns the user to use to_underlying function in the utility header (which is introduced in c++23 )instead of static_cast with a hard coded tyep to prevent truncation errors.
>From e0302c72fbd692247c5b7db3f4a3d86ecb3f07e7 Mon Sep 17 00:00:00 2001
From: arjunkonidala <cs23btech11026 at iith.ac.in>
Date: Sun, 9 Nov 2025 15:18:37 +0530
Subject: [PATCH 1/3] "New check for static_cast of an enum class type"
---
.../modernize/UseToUnderlyingCheck.cpp | 77 +++++++++++++++++++
.../modernize/UseToUnderlyingCheck.h | 37 +++++++++
.../modernize/use-to-underlying.cpp | 42 ++++++++++
3 files changed, 156 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.h
create mode 100644 clang-tools-extra/clang-tidy/modernize/use-to-underlying.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.cpp
new file mode 100644
index 0000000000000..4ef2305d2d210
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.cpp
@@ -0,0 +1,77 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "UseToUnderlyingCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Lex/Lexer.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::modernize {
+
+UseToUnderlyingCheck::UseToUnderlyingCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ Inserter(Options.getLocalOrGlobal("IncludeStyle",
+ utils::IncludeSorter::IS_LLVM),
+ areDiagsSelfContained()) {}
+//
+void UseToUnderlyingCheck::registerPPCallbacks(const SourceManager &SM,
+ Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) {
+ Inserter.registerPreprocessor(PP);
+}
+
+void UseToUnderlyingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "IncludeStyle", Inserter.getStyle());
+}
+
+bool UseToUnderlyingCheck::isLanguageVersionSupported(
+ const LangOptions &LangOpts) const {
+ return LangOpts.CPlusPlus23;
+}
+
+void UseToUnderlyingCheck::registerMatchers(MatchFinder *Finder) {
+ // FIXME: Add matchers.
+ Finder->addMatcher(
+ cxxStaticCastExpr( // C++ cast
+ hasDestinationType(
+ isInteger()), // casting to any type of integer (int,long,etc)
+ hasSourceExpression( // is an enum class
+ expr(hasType(enumType(hasDeclaration(enumDecl(isScoped())))))
+ .bind("enumExpr"))) // giving the name enumExpr
+ .bind("castExpr"), // giving the name castExpr
+ this);
+}
+
+void UseToUnderlyingCheck::check(const MatchFinder::MatchResult &Result) {
+ // Acquiring the enumExpr and castExpr using getNodeAS
+ const auto *Enum = Result.Nodes.getNodeAs<Expr>("enumExpr");
+ const auto *Cast = Result.Nodes.getNodeAs<CXXStaticCastExpr>("castExpr");
+
+ // getting contents of that node using getsourcetext
+ StringRef EnumExprText = Lexer::getSourceText(
+ CharSourceRange::getTokenRange(Enum->getSourceRange()),
+ *Result.SourceManager, getLangOpts());
+ // Suggestion to the user regarding the cast expr
+ std::string Replacement = ("std::to_underlying(" + EnumExprText + ")").str();
+ // gives and warning message if static cast is used instead to_underlying
+ auto Diag = diag(
+ Cast->getBeginLoc(),
+ "use 'std::to_underlying' instead of 'static_cast' for 'enum class'");
+ // suggest and hint for fixing it.
+ Diag << FixItHint::CreateReplacement(Cast->getSourceRange(), Replacement);
+
+ Diag << Inserter.createIncludeInsertion(
+ Result.Context->getSourceManager().getFileID(Cast->getBeginLoc()),
+ "<utility>");
+}
+
+} // namespace clang::tidy::modernize
diff --git a/clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.h b/clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.h
new file mode 100644
index 0000000000000..823bae6115cc7
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.h
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_MODERNIZE_USETOUNDERLYINGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USETOUNDERLYINGCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "../utils/IncludeInserter.h"
+
+namespace clang::tidy::modernize {
+
+/// Warns user to use to_underlying function from utility header instead of static_cast<T> for a enum class type
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-to-underlying.html
+class UseToUnderlyingCheck : public ClangTidyCheck {
+public:
+ UseToUnderlyingCheck(StringRef Name, ClangTidyContext *Context);
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override;
+ void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
+ Preprocessor *ModuleExpanderPP) override;
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+private:
+ utils::IncludeInserter Inserter;
+};
+
+} // namespace clang::tidy::modernize
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USETOUNDERLYINGCHECK_H
diff --git a/clang-tools-extra/clang-tidy/modernize/use-to-underlying.cpp b/clang-tools-extra/clang-tidy/modernize/use-to-underlying.cpp
new file mode 100644
index 0000000000000..8eb4fa0c67921
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/modernize/use-to-underlying.cpp
@@ -0,0 +1,42 @@
+// RUN: %check_clang_tidy -std=c++23 %s modernize-use-to-underlying %t
+
+namespace std {
+template<typename T>
+constexpr auto to_underlying(T value) noexcept {
+ return static_cast<__underlying_type(T)>(value);
+}
+}
+
+
+enum class MyEnum { A = 1, B = 2 };
+
+void test_basic_cast() {
+ int value = static_cast<int>(MyEnum::A);
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
+ // CHECK-FIXES: int value = std::to_underlying(MyEnum::A);
+}
+
+
+void test_long_cast() {
+ long value = static_cast<long>(MyEnum::B);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
+ // CHECK-FIXES: long value = std::to_underlying(MyEnum::B);
+}
+
+
+void test_expression() {
+ int result = static_cast<int>(MyEnum::A) + 10;
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
+ // CHECK-FIXES: int result = std::to_underlying(MyEnum::A) + 10;
+}
+
+void test_already_correct() {
+ int value = std::to_underlying(MyEnum::B);
+ // No warning expected
+}
+
+void test_float_cast() {
+ float y = 8.34;
+ int z = static_cast<int>(y);
+ // No warning expected
+}
>From 680769d85ac30bacc8f4f3f64b8a3db996db6039 Mon Sep 17 00:00:00 2001
From: arjunkonidala <cs23btech11026 at iith.ac.in>
Date: Sun, 9 Nov 2025 15:24:11 +0530
Subject: [PATCH 2/3] "added test file"
---
.../checkers/modernize/use-to-underlying.cpp | 42 +++++++++++++++++++
1 file changed, 42 insertions(+)
create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-to-underlying.cpp
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-to-underlying.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-to-underlying.cpp
new file mode 100644
index 0000000000000..8eb4fa0c67921
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-to-underlying.cpp
@@ -0,0 +1,42 @@
+// RUN: %check_clang_tidy -std=c++23 %s modernize-use-to-underlying %t
+
+namespace std {
+template<typename T>
+constexpr auto to_underlying(T value) noexcept {
+ return static_cast<__underlying_type(T)>(value);
+}
+}
+
+
+enum class MyEnum { A = 1, B = 2 };
+
+void test_basic_cast() {
+ int value = static_cast<int>(MyEnum::A);
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
+ // CHECK-FIXES: int value = std::to_underlying(MyEnum::A);
+}
+
+
+void test_long_cast() {
+ long value = static_cast<long>(MyEnum::B);
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
+ // CHECK-FIXES: long value = std::to_underlying(MyEnum::B);
+}
+
+
+void test_expression() {
+ int result = static_cast<int>(MyEnum::A) + 10;
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
+ // CHECK-FIXES: int result = std::to_underlying(MyEnum::A) + 10;
+}
+
+void test_already_correct() {
+ int value = std::to_underlying(MyEnum::B);
+ // No warning expected
+}
+
+void test_float_cast() {
+ float y = 8.34;
+ int z = static_cast<int>(y);
+ // No warning expected
+}
>From 5c0a150d4f7b8188c825839a75d7226cb7d8e482 Mon Sep 17 00:00:00 2001
From: arjunkonidala <cs23btech11026 at iith.ac.in>
Date: Sun, 9 Nov 2025 15:29:32 +0530
Subject: [PATCH 3/3] "removed the file in wrong location"
---
.../modernize/use-to-underlying.cpp | 42 -------------------
1 file changed, 42 deletions(-)
delete mode 100644 clang-tools-extra/clang-tidy/modernize/use-to-underlying.cpp
diff --git a/clang-tools-extra/clang-tidy/modernize/use-to-underlying.cpp b/clang-tools-extra/clang-tidy/modernize/use-to-underlying.cpp
deleted file mode 100644
index 8eb4fa0c67921..0000000000000
--- a/clang-tools-extra/clang-tidy/modernize/use-to-underlying.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-// RUN: %check_clang_tidy -std=c++23 %s modernize-use-to-underlying %t
-
-namespace std {
-template<typename T>
-constexpr auto to_underlying(T value) noexcept {
- return static_cast<__underlying_type(T)>(value);
-}
-}
-
-
-enum class MyEnum { A = 1, B = 2 };
-
-void test_basic_cast() {
- int value = static_cast<int>(MyEnum::A);
- // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
- // CHECK-FIXES: int value = std::to_underlying(MyEnum::A);
-}
-
-
-void test_long_cast() {
- long value = static_cast<long>(MyEnum::B);
- // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
- // CHECK-FIXES: long value = std::to_underlying(MyEnum::B);
-}
-
-
-void test_expression() {
- int result = static_cast<int>(MyEnum::A) + 10;
- // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying]
- // CHECK-FIXES: int result = std::to_underlying(MyEnum::A) + 10;
-}
-
-void test_already_correct() {
- int value = std::to_underlying(MyEnum::B);
- // No warning expected
-}
-
-void test_float_cast() {
- float y = 8.34;
- int z = static_cast<int>(y);
- // No warning expected
-}
More information about the cfe-commits
mailing list