Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.cpp
Original file line number Diff line number Diff line change
@@ -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()) {}
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// 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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
StringRef EnumExprText = Lexer::getSourceText(
const 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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::string Replacement = ("std::to_underlying(" + EnumExprText + ")").str();
const 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
37 changes: 37 additions & 0 deletions clang-tools-extra/clang-tidy/modernize/UseToUnderlyingCheck.h
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
}