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..23513e72dab10 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.cpp @@ -0,0 +1,80 @@ +//===----------------------------------------------------------------------===// +// +// 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/ASTMatchers/ASTMatchFinder.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/Support/Error.h" // You'll want this for the inserter +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { +//Constructor +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()); +} +//Language version compatibility checking +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("enumExpr"); + const auto *Cast = Result.Nodes.getNodeAs("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 watring 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()), + ""); + + + // Required error handling for the Inserter + +} + + +} // 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..55d73361d531c --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.h @@ -0,0 +1,36 @@ +//===----------------------------------------------------------------------===// +// +// 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 { + +/// FIXME: Write a short description. +/// +/// 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/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..53b4668599f5d --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-to-underlying.cpp @@ -0,0 +1,89 @@ +// RUN: %check_clang_tidy -std=c++23 %s modernize-use-to-underlying %t + +// Mock std::to_underlying for testing +namespace std { +template +constexpr auto to_underlying(T value) noexcept { + return static_cast<__underlying_type(T)>(value); +} +} + +// Test case 1: Basic enum class to int cast - should warn +enum class MyEnum { A = 1, B = 2 }; + +void test_basic_cast() { + int value = static_cast(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); +} + +// Test case 2: enum class to long cast - should warn +void test_long_cast() { + long value = static_cast(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); +} + +// Test case 3: enum class in expression - should warn +void test_expression() { + int result = static_cast(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; +} + +// Test case 4: Already using std::to_underlying - should NOT warn +void test_already_correct() { + int value = std::to_underlying(MyEnum::B); + // No warning expected +} + +// Test case 5: Casting float to int - should NOT warn (not an enum) +void test_float_cast() { + float y = 8.34; + int z = static_cast(y); + // No warning expected +} + +// Test case 6: Regular (non-scoped) enum - should NOT warn +enum RegularEnum { X = 1, Y = 2 }; + +void test_regular_enum() { + int value = static_cast(RegularEnum::X); + // No warning expected (only enum class should trigger) +} + +// Test case 7: Multiple casts in same function - should warn for each +void test_multiple_casts() { + int a = static_cast(MyEnum::A); + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying] + // CHECK-FIXES: int a = std::to_underlying(MyEnum::A); + + int b = static_cast(MyEnum::B); + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying] + // CHECK-FIXES: int b = std::to_underlying(MyEnum::B); +} + +// Test case 8: enum class with underlying type specified +enum class TypedEnum : unsigned int { First = 0, Second = 1 }; + +void test_typed_enum() { + unsigned int val = static_cast(TypedEnum::First); + // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying] + // CHECK-FIXES: unsigned int val = std::to_underlying(TypedEnum::First); +} + +// Test case 9: Casting to unsigned - should warn +void test_unsigned_cast() { + unsigned value = static_cast(MyEnum::A); + // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying] + // CHECK-FIXES: unsigned value = std::to_underlying(MyEnum::A); +} + +// Test case 10: Nested in function call - should warn +void some_function(int x) {} + +void test_nested_call() { + some_function(static_cast(MyEnum::A)); + // CHECK-MESSAGES: :[[@LINE-1]]:17: warning: use 'std::to_underlying' instead of 'static_cast' for 'enum class' [modernize-use-to-underlying] + // CHECK-FIXES: some_function(std::to_underlying(MyEnum::A)); +} \ No newline at end of file