https://github.com/hjanuschka created https://github.com/llvm/llvm-project/pull/182015
Add new clang-tidy check that suggests replacing `const` with `constexpr` on local variables with literal types and compile-time constant initializers. Using `constexpr` makes the compile-time nature of the value explicit and enables the compiler to enforce it. For example: ```cpp const int x = 42; // -> constexpr int x = 42; const double d = 3.14; // -> constexpr double d = 3.14; const int s = sizeof(int); // -> constexpr int s = sizeof(int); int const y = 2 + 3 * 4; // -> int constexpr y = 2 + 3 * 4; ``` The check only triggers for local, non-static, non-volatile variables of literal type whose initializer is a C++11 constant expression. Pointer and reference types are skipped. >From 4fb37e7ed1a8eb8da7b77724c4ca3bcda0a00ba7 Mon Sep 17 00:00:00 2001 From: Helmut Januschka <[email protected]> Date: Wed, 18 Feb 2026 14:02:48 +0100 Subject: [PATCH] [clang-tidy] Add modernize-use-constexpr check Suggests replacing 'const' with 'constexpr' on local variables with literal types and compile-time constant initializers. --- .../clang-tidy/modernize/CMakeLists.txt | 1 + .../modernize/ModernizeTidyModule.cpp | 2 + .../modernize/UseConstexprCheck.cpp | 117 ++++++++++++++++++ .../clang-tidy/modernize/UseConstexprCheck.h | 34 +++++ clang-tools-extra/docs/ReleaseNotes.rst | 6 + .../docs/clang-tidy/checks/list.rst | 1 + .../checks/modernize/use-constexpr.rst | 30 +++++ .../checkers/modernize/use-constexpr.cpp | 113 +++++++++++++++++ 8 files changed, 304 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/modernize/use-constexpr.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/modernize/use-constexpr.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt index cc4cc7a02b594..ca4b2502d05fd 100644 --- a/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/modernize/CMakeLists.txt @@ -33,6 +33,7 @@ add_clang_library(clangTidyModernizeModule STATIC UnaryStaticAssertCheck.cpp UseAutoCheck.cpp UseBoolLiteralsCheck.cpp + UseConstexprCheck.cpp UseConstraintsCheck.cpp UseDefaultMemberInitCheck.cpp UseDesignatedInitializersCheck.cpp diff --git a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp index fcb860d8c5298..2e62889a1c207 100644 --- a/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/modernize/ModernizeTidyModule.cpp @@ -33,6 +33,7 @@ #include "UnaryStaticAssertCheck.h" #include "UseAutoCheck.h" #include "UseBoolLiteralsCheck.h" +#include "UseConstexprCheck.h" #include "UseConstraintsCheck.h" #include "UseDefaultMemberInitCheck.h" #include "UseDesignatedInitializersCheck.h" @@ -119,6 +120,7 @@ class ModernizeModule : public ClangTidyModule { CheckFactories.registerCheck<UseAutoCheck>("modernize-use-auto"); CheckFactories.registerCheck<UseBoolLiteralsCheck>( "modernize-use-bool-literals"); + CheckFactories.registerCheck<UseConstexprCheck>("modernize-use-constexpr"); CheckFactories.registerCheck<UseConstraintsCheck>( "modernize-use-constraints"); CheckFactories.registerCheck<UseDefaultMemberInitCheck>( diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.cpp b/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.cpp new file mode 100644 index 0000000000000..7bf983e451bc4 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.cpp @@ -0,0 +1,117 @@ +//===--- UseConstexprCheck.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 "UseConstexprCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::modernize { + +void UseConstexprCheck::registerMatchers(MatchFinder *Finder) { + // Match local const variables with initializers that are not already + // constexpr, not static, and not dependent. + Finder->addMatcher( + varDecl(hasLocalStorage(), hasType(qualType(isConstQualified())), + hasInitializer(expr().bind("init")), unless(isConstexpr())) + .bind("var"), + this); +} + +void UseConstexprCheck::check(const MatchFinder::MatchResult &Result) { + const auto *VD = Result.Nodes.getNodeAs<VarDecl>("var"); + if (!VD) + return; + + // Skip if the variable is static or thread_local. + if (VD->isStaticLocal() || VD->getTSCSpec() != TSCS_unspecified) + return; + + // Skip volatile variables. + if (VD->getType().isVolatileQualified()) + return; + + // Skip dependent types (in uninstantiated templates). + if (VD->getType()->isDependentType()) + return; + + // The type must be a literal type for constexpr. + if (!VD->getType()->isLiteralType(*Result.Context)) + return; + + // Skip reference types -- constexpr references need static storage + // duration objects, which is too restrictive for local variables. + if (VD->getType()->isReferenceType()) + return; + + // Skip pointer types -- the 'const' qualifier position makes the + // fix-it non-trivial (e.g. 'int *const p' vs 'const int *p'). + if (VD->getType()->isPointerType()) + return; + + const Expr *Init = VD->getInit(); + if (!Init) + return; + + // Skip if the initializer is value-dependent (template context). + if (Init->isValueDependent()) + return; + + // Check if the initializer is a C++11 constant expression. + if (!Init->isCXX11ConstantExpr(*Result.Context)) + return; + + // Find the 'const' token to replace with 'constexpr'. + const SourceLocation VarLoc = VD->getLocation(); + const SourceLocation DeclBegin = VD->getBeginLoc(); + + if (DeclBegin.isInvalid() || DeclBegin.isMacroID() || VarLoc.isMacroID()) + return; + + const SourceManager &SM = *Result.SourceManager; + const LangOptions &LO = Result.Context->getLangOpts(); + + // Get source text from the start of the declaration to the variable + // name. This covers both 'const int x' and 'int const x'. + const CharSourceRange DeclRange = + CharSourceRange::getCharRange(DeclBegin, VarLoc); + const StringRef DeclText = Lexer::getSourceText(DeclRange, SM, LO); + + // Find 'const' as a whole word in the declaration text. + SourceLocation ConstLoc; + size_t Pos = 0; + while ((Pos = DeclText.find("const", Pos)) != StringRef::npos) { + // Make sure it's not part of 'constexpr' or 'consteval' etc. + const size_t End = Pos + 5; + const bool WordStart = + (Pos == 0 || !isAlphanumeric(DeclText[Pos - 1])); + const bool WordEnd = + (End >= DeclText.size() || !isAlphanumeric(DeclText[End])); + if (WordStart && WordEnd) { + ConstLoc = DeclBegin.getLocWithOffset(Pos); + break; + } + Pos = End; + } + + if (ConstLoc.isValid()) { + diag(VD->getLocation(), "variable %0 can be declared 'constexpr'") + << VD + << FixItHint::CreateReplacement( + CharSourceRange::getTokenRange(ConstLoc, ConstLoc), + "constexpr"); + } else { + // Couldn't find 'const' token; emit diagnostic without fix-it. + diag(VD->getLocation(), "variable %0 can be declared 'constexpr'") + << VD; + } +} + +} // namespace clang::tidy::modernize diff --git a/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.h b/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.h new file mode 100644 index 0000000000000..994155905f803 --- /dev/null +++ b/clang-tools-extra/clang-tidy/modernize/UseConstexprCheck.h @@ -0,0 +1,34 @@ +//===--- UseConstexprCheck.h - clang-tidy ------------------------*- C++ -*-===// +// +// 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_USECONSTEXPRCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTEXPRCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::modernize { + +/// Finds ``const`` local variables with literal types and compile-time +/// constant initializers that can be declared ``constexpr``. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-constexpr.html +class UseConstexprCheck : public ClangTidyCheck { +public: + UseConstexprCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } +}; + +} // namespace clang::tidy::modernize + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MODERNIZE_USECONSTEXPRCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 68bab88146241..ae53221edb2a0 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -121,6 +121,12 @@ New checks ``llvm::to_vector(llvm::make_filter_range(...))`` that can be replaced with ``llvm::map_to_vector`` and ``llvm::filter_to_vector``. +- New :doc:`modernize-use-constexpr + <clang-tidy/checks/modernize/use-constexpr>` check. + + Finds ``const`` local variables with literal types and compile-time + constant initializers that can be declared ``constexpr``. + - New :doc:`modernize-use-string-view <clang-tidy/checks/modernize/use-string-view>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index c475870ed7b31..85854d51da3d9 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -313,6 +313,7 @@ Clang-Tidy Checks :doc:`modernize-unary-static-assert <modernize/unary-static-assert>`, "Yes" :doc:`modernize-use-auto <modernize/use-auto>`, "Yes" :doc:`modernize-use-bool-literals <modernize/use-bool-literals>`, "Yes" + :doc:`modernize-use-constexpr <modernize/use-constexpr>`, "Yes" :doc:`modernize-use-constraints <modernize/use-constraints>`, "Yes" :doc:`modernize-use-default-member-init <modernize/use-default-member-init>`, "Yes" :doc:`modernize-use-designated-initializers <modernize/use-designated-initializers>`, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constexpr.rst b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constexpr.rst new file mode 100644 index 0000000000000..48fdf6f921d9a --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/modernize/use-constexpr.rst @@ -0,0 +1,30 @@ +.. title:: clang-tidy - modernize-use-constexpr + +modernize-use-constexpr +======================= + +Finds ``const`` local variables with literal types and compile-time +constant initializers that can be declared ``constexpr``. + +Using ``constexpr`` makes the compile-time nature of the value +explicit and enables the compiler to enforce it. + +For example: + +.. code-block:: c++ + + const int x = 42; // -> constexpr int x = 42; + const double d = 3.14; // -> constexpr double d = 3.14; + const bool b = true; // -> constexpr bool b = true; + const int s = sizeof(int); // -> constexpr int s = sizeof(int); + +The check only triggers when all of the following are true: + +- The variable is a local variable (not global or static). +- The variable has ``const`` qualification (top-level). +- The type is a literal type. +- The initializer is a C++11 constant expression. +- The variable is not already ``constexpr``. +- The variable is not ``volatile``. +- The variable is not a reference or pointer type. +- The declaration is not in a macro. diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constexpr.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constexpr.cpp new file mode 100644 index 0000000000000..6e6d8fb1ebe27 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-constexpr.cpp @@ -0,0 +1,113 @@ +// RUN: %check_clang_tidy -std=c++17-or-later %s modernize-use-constexpr %t + +// Positive: integer literal +void test_int_literal() { + const int x = 42; + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: variable 'x' can be declared 'constexpr' [modernize-use-constexpr] + // CHECK-FIXES: constexpr int x = 42; +} + +// Positive: arithmetic expression of literals +void test_arithmetic() { + const int y = 2 + 3 * 4; + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: variable 'y' can be declared 'constexpr' [modernize-use-constexpr] + // CHECK-FIXES: constexpr int y = 2 + 3 * 4; +} + +// Positive: bool literal +void test_bool() { + const bool b = true; + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: variable 'b' can be declared 'constexpr' [modernize-use-constexpr] + // CHECK-FIXES: constexpr bool b = true; +} + +// Positive: char literal +void test_char() { + const char c = 'a'; + // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: variable 'c' can be declared 'constexpr' [modernize-use-constexpr] + // CHECK-FIXES: constexpr char c = 'a'; +} + +// Positive: float literal +void test_float() { + const double d = 3.14; + // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: variable 'd' can be declared 'constexpr' [modernize-use-constexpr] + // CHECK-FIXES: constexpr double d = 3.14; +} + +// Positive: enum value +enum Color { Red, Green, Blue }; +void test_enum() { + const Color c = Red; + // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: variable 'c' can be declared 'constexpr' [modernize-use-constexpr] + // CHECK-FIXES: constexpr Color c = Red; +} + +// Positive: sizeof expression +void test_sizeof() { + const int s = sizeof(int); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: variable 's' can be declared 'constexpr' [modernize-use-constexpr] + // CHECK-FIXES: constexpr int s = sizeof(int); +} + +// Positive: east const style +void test_east_const() { + int const x = 10; + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: variable 'x' can be declared 'constexpr' [modernize-use-constexpr] + // CHECK-FIXES: int constexpr x = 10; +} + +// Negative: already constexpr +void test_already_constexpr() { + constexpr int x = 42; +} + +// Negative: non-const variable +void test_non_const() { + int x = 42; +} + +// Negative: non-constant initializer +int compute(); +void test_non_constant_init() { + const int x = compute(); +} + +// Negative: reference type +void test_reference() { + int val = 42; + const int &r = val; +} + +// Negative: volatile +void test_volatile() { + const volatile int x = 42; +} + +// Negative: static local +void test_static() { + static const int x = 42; +} + +// Negative: non-literal type (std::string mock) +struct NonLiteral { + NonLiteral(const char *); + ~NonLiteral(); +}; +void test_non_literal() { + const NonLiteral s = "hello"; +} + +// Negative: pointer type (fix-it is non-trivial) +void test_pointer() { + int *const p = nullptr; + const int *q = nullptr; +} + +// Negative: global variable (not local) +const int global_val = 42; + +// Negative: variable depends on function argument +void test_depends_on_arg(int n) { + const int x = n + 1; +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
