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

Reply via email to