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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 -} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
