Skip to content

[clang-tidy] add modernize-use-constexpr check #146553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

5chmidti
Copy link
Contributor

@5chmidti 5chmidti commented Jul 1, 2025

This check finds all functions and variables that can be declared as
constexpr, using the specified standard version to check if the
requirements are met.

Fixes #115622

@llvmbot
Copy link
Member

llvmbot commented Jul 1, 2025

@llvm/pr-subscribers-clang-tidy

Author: Julian Schmidt (5chmidti)

Changes

This check finds all functions and variables that can be declared as
constexpr, using the specified standard version to check if the
requirements are met.

Fixes #115622


Patch is 95.12 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/146553.diff

9 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/misc/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/misc/ConstexprCheck.cpp (+936)
  • (added) clang-tools-extra/clang-tidy/misc/ConstexprCheck.h (+43)
  • (modified) clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp (+3)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
  • (added) clang-tools-extra/docs/clang-tidy/checks/misc/constexpr.rst (+40)
  • (added) clang-tools-extra/test/clang-tidy/checkers/misc/constexpr-cxx20.cpp (+36)
  • (added) clang-tools-extra/test/clang-tidy/checkers/misc/constexpr-ref.cpp (+383)
  • (added) clang-tools-extra/test/clang-tidy/checkers/misc/constexpr.cpp (+562)
diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index fd7affd22a463..4535df3451c95 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -19,6 +19,7 @@ set_target_properties(genconfusable PROPERTIES FOLDER "Clang Tools Extra/Sourceg
 
 add_clang_library(clangTidyMiscModule STATIC
   ConstCorrectnessCheck.cpp
+  ConstexprCheck.cpp
   CoroutineHostileRAIICheck.cpp
   DefinitionsInHeadersCheck.cpp
   ConfusableIdentifierCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/ConstexprCheck.cpp b/clang-tools-extra/clang-tidy/misc/ConstexprCheck.cpp
new file mode 100644
index 0000000000000..270ac1dd0fa48
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/ConstexprCheck.cpp
@@ -0,0 +1,936 @@
+//===--- ConstexprCheck.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 "ConstexprCheck.h"
+#include "../utils/ASTUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/OperationKinds.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/StmtCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Basic/Specifiers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/Support/Casting.h"
+#include <cstddef>
+#include <functional>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::misc {
+
+namespace {
+AST_MATCHER(FunctionDecl, locationPermitsConstexpr) {
+  const bool IsInMainFile =
+      Finder->getASTContext().getSourceManager().isInMainFile(
+          Node.getLocation());
+
+  if (IsInMainFile && Node.hasExternalFormalLinkage())
+    return false;
+  if (!IsInMainFile && !Node.isInlined())
+    return false;
+
+  return true;
+}
+
+AST_MATCHER(Expr, isCXX11ConstantExpr) {
+  return !Node.isValueDependent() &&
+         Node.isCXX11ConstantExpr(Finder->getASTContext());
+}
+
+AST_MATCHER(DeclaratorDecl, isInMacro) {
+  const SourceRange R =
+      SourceRange(Node.getInnerLocStart(), Node.getLocation());
+
+  return Node.getLocation().isMacroID() || Node.getEndLoc().isMacroID() ||
+         utils::rangeContainsMacroExpansion(
+             R, &Finder->getASTContext().getSourceManager()) ||
+         utils::rangeIsEntirelyWithinMacroArgument(
+             R, &Finder->getASTContext().getSourceManager());
+}
+
+AST_MATCHER(Decl, hasNoRedecl) {
+  // There is always the actual declaration
+  return !Node.redecls().empty() &&
+         std::next(Node.redecls_begin()) == Node.redecls_end();
+}
+
+AST_MATCHER(Decl, allRedeclsInSameFile) {
+  const SourceManager &SM = Finder->getASTContext().getSourceManager();
+  const SourceLocation L = Node.getLocation();
+  for (const Decl *ReDecl : Node.redecls()) {
+    if (!SM.isWrittenInSameFile(L, ReDecl->getLocation()))
+      return false;
+  }
+  return true;
+}
+
+AST_MATCHER(FunctionDecl, isConstexprSpecified) {
+  return Node.isConstexprSpecified();
+}
+
+bool satisfiesConstructorPropertiesUntil20(const CXXConstructorDecl *Ctor,
+                                           ASTContext &Ctx) {
+  const CXXRecordDecl *Rec = Ctor->getParent();
+  llvm::SmallPtrSet<const RecordDecl *, 8> Bases{};
+  for (const CXXBaseSpecifier Base : Rec->bases()) {
+    Bases.insert(Base.getType()->getAsRecordDecl());
+  }
+  llvm::SmallPtrSet<const FieldDecl *, 8> Fields{Rec->field_begin(),
+                                                 Rec->field_end()};
+  llvm::SmallPtrSet<const FieldDecl *, 4> Indirects{};
+
+  for (const CXXCtorInitializer *const Init : Ctor->inits()) {
+    const Type *InitType = Init->getBaseClass();
+    if (InitType && InitType->isRecordType()) {
+      const auto *ConstructingInit =
+          llvm::dyn_cast<CXXConstructExpr>(Init->getInit());
+      if (ConstructingInit &&
+          !ConstructingInit->getConstructor()->isConstexprSpecified())
+        return false;
+    }
+
+    if (Init->isBaseInitializer()) {
+      Bases.erase(Init->getBaseClass()->getAsRecordDecl());
+      continue;
+    }
+
+    if (Init->isMemberInitializer()) {
+      const FieldDecl *Field = Init->getMember();
+
+      if (Field->isAnonymousStructOrUnion())
+        Indirects.insert(Field);
+
+      Fields.erase(Field);
+      continue;
+    }
+  }
+
+  for (const auto &Match :
+       match(cxxRecordDecl(forEach(indirectFieldDecl().bind("indirect"))), *Rec,
+             Ctx)) {
+    const auto *IField = Match.getNodeAs<IndirectFieldDecl>("indirect");
+
+    size_t NumInitializations = false;
+    for (const NamedDecl *ND : IField->chain())
+      NumInitializations += Indirects.erase(llvm::dyn_cast<FieldDecl>(ND));
+
+    if (NumInitializations != 1)
+      return false;
+
+    for (const NamedDecl *ND : IField->chain())
+      Fields.erase(llvm::dyn_cast<FieldDecl>(ND));
+  }
+
+  if (!Fields.empty())
+    return false;
+
+  return true;
+}
+
+const Type *unwrapPointee(const Type *T) {
+  if (!T->isPointerOrReferenceType())
+    return T;
+
+  while (T && T->isPointerOrReferenceType()) {
+    if (T->isReferenceType()) {
+      const QualType QType = T->getPointeeType();
+      if (!QType.isNull())
+        T = QType.getTypePtr();
+    } else
+      T = T->getPointeeOrArrayElementType();
+  }
+
+  return T;
+}
+
+bool isLiteralType(QualType QT, const ASTContext &Ctx,
+                   const bool ConservativeLiteralType);
+
+bool isLiteralType(const Type *T, const ASTContext &Ctx,
+                   const bool ConservativeLiteralType) {
+  if (!T)
+    return false;
+
+  if (!T->isLiteralType(Ctx))
+    return false;
+
+  if (!ConservativeLiteralType)
+    return T->isLiteralType(Ctx) && !T->isVoidType();
+
+  if (T->isIncompleteType() || T->isIncompleteArrayType())
+    return false;
+
+  T = unwrapPointee(T);
+  if (!T)
+    return false;
+
+  assert(!T->isPointerOrReferenceType());
+
+  if (T->isIncompleteType() || T->isIncompleteArrayType())
+    return false;
+
+  if (T->isLiteralType(Ctx))
+    return true;
+
+  if (const auto *Rec = T->getAsCXXRecordDecl()) {
+    if (llvm::any_of(Rec->ctors(), [](const CXXConstructorDecl *Ctor) {
+          return !Ctor->isCopyOrMoveConstructor() &&
+                 Ctor->isConstexprSpecified();
+        }))
+      return false;
+
+    for (const CXXBaseSpecifier Base : Rec->bases()) {
+      if (!isLiteralType(Base.getType(), Ctx, ConservativeLiteralType))
+        return false;
+    }
+  }
+
+  if (const Type *ArrayElementType = T->getArrayElementTypeNoTypeQual())
+    return isLiteralType(ArrayElementType, Ctx, ConservativeLiteralType);
+
+  return false;
+}
+
+bool isLiteralType(QualType QT, const ASTContext &Ctx,
+                   const bool ConservativeLiteralType) {
+  return isLiteralType(QT.getTypePtr(), Ctx, ConservativeLiteralType);
+}
+
+bool satisfiesProperties11(
+    const FunctionDecl *FDecl, ASTContext &Ctx,
+    const bool ConservativeLiteralType,
+    const bool AddConstexprToMethodOfClassWithoutConstexprConstructor) {
+  if (FDecl->isConstexprSpecified()) {
+    return true;
+  }
+  const LangOptions LO = Ctx.getLangOpts();
+  const CXXMethodDecl *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);
+  if (Method && !Method->isStatic() &&
+      !Method->getParent()->hasConstexprNonCopyMoveConstructor() &&
+      !AddConstexprToMethodOfClassWithoutConstexprConstructor)
+    return false;
+
+  if (Method &&
+      (Method->isVirtual() ||
+       !match(cxxMethodDecl(hasBody(cxxTryStmt())), *Method, Ctx).empty()))
+    return false;
+
+  if (const auto *Ctor = llvm::dyn_cast<CXXConstructorDecl>(FDecl);
+      Ctor && (!satisfiesConstructorPropertiesUntil20(Ctor, Ctx) ||
+               llvm::any_of(Ctor->getParent()->bases(),
+                            [](const CXXBaseSpecifier &Base) {
+                              return Base.isVirtual();
+                            })))
+    return false;
+
+  if (const auto *Dtor = llvm::dyn_cast<CXXDestructorDecl>(FDecl);
+      Dtor && !Dtor->isTrivial())
+    return false;
+
+  if (!isLiteralType(FDecl->getReturnType(), Ctx, ConservativeLiteralType))
+    return false;
+
+  for (const ParmVarDecl *Param : FDecl->parameters())
+    if (!isLiteralType(Param->getType(), Ctx, ConservativeLiteralType))
+      return false;
+
+  class Visitor11 : public clang::RecursiveASTVisitor<Visitor11> {
+  public:
+    using Base = clang::RecursiveASTVisitor<Visitor11>;
+    bool shouldVisitImplicitCode() const { return true; }
+
+    Visitor11(ASTContext &Ctx, bool ConservativeLiteralType)
+        : Ctx(Ctx), ConservativeLiteralType(ConservativeLiteralType) {}
+
+    bool WalkUpFromNullStmt(NullStmt *) {
+      Possible = false;
+      return true;
+    }
+    bool WalkUpFromDeclStmt(DeclStmt *DS) {
+      for (const Decl *D : DS->decls())
+        if (!llvm::isa<StaticAssertDecl, TypedefNameDecl, UsingDecl,
+                       UsingDirectiveDecl>(D)) {
+          Possible = false;
+          return false;
+        }
+      return true;
+    }
+
+    bool WalkUpFromExpr(Expr *) { return true; }
+    bool WalkUpFromCompoundStmt(CompoundStmt *S) {
+      for (const DynTypedNode &Node : Ctx.getParents(*S))
+        if (Node.get<FunctionDecl>() != nullptr)
+          return true;
+
+      Possible = false;
+      return false;
+    }
+    bool WalkUpFromStmt(Stmt *) {
+      Possible = false;
+      return false;
+    }
+
+    bool WalkUpFromReturnStmt(ReturnStmt *) {
+      ++NumReturns;
+      if (NumReturns != 1U) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool WalkUpFromCastExpr(CastExpr *CE) {
+      if (llvm::is_contained(
+              {
+                  CK_LValueBitCast,
+                  CK_IntegralToPointer,
+                  CK_PointerToIntegral,
+              },
+              CE->getCastKind())) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool TraverseCXXDynamicCastExpr(CXXDynamicCastExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    bool TraverseCXXReinterpretCastExpr(CXXReinterpretCastExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    bool TraverseType(QualType QT) {
+      if (QT.isNull())
+        return true;
+      if (!isLiteralType(QT, Ctx, ConservativeLiteralType)) {
+        Possible = false;
+        return false;
+      }
+      return Base::TraverseType(QT);
+    }
+
+    bool WalkUpFromCXXConstructExpr(CXXConstructExpr *CE) {
+      if (const auto *Ctor = CE->getConstructor();
+          Ctor && !Ctor->isConstexprSpecified()) {
+        Possible = false;
+        return false;
+      }
+
+      return true;
+    }
+    bool WalkUpFromCallExpr(CallExpr *CE) {
+      if (const auto *FDecl =
+              llvm::dyn_cast_if_present<FunctionDecl>(CE->getCalleeDecl());
+          FDecl && !FDecl->isConstexprSpecified()) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool TraverseCXXNewExpr(CXXNewExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    ASTContext &Ctx;
+    const bool ConservativeLiteralType;
+    bool Possible = true;
+    size_t NumReturns = 0;
+  };
+
+  Visitor11 V{Ctx, ConservativeLiteralType};
+  V.TraverseDecl(const_cast<FunctionDecl *>(FDecl));
+  if (!V.Possible)
+    return false;
+
+  return true;
+}
+
+// The only difference between C++14 and C++17 is that `constexpr` lambdas
+// can be used in C++17.
+bool satisfiesProperties1417(
+    const FunctionDecl *FDecl, ASTContext &Ctx,
+    const bool ConservativeLiteralType,
+    const bool AddConstexprToMethodOfClassWithoutConstexprConstructor) {
+  if (FDecl->isConstexprSpecified())
+    return true;
+
+  const LangOptions LO = Ctx.getLangOpts();
+  const CXXMethodDecl *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);
+  if (Method && !Method->isStatic() &&
+      !Method->getParent()->hasConstexprNonCopyMoveConstructor() &&
+      !AddConstexprToMethodOfClassWithoutConstexprConstructor)
+    return false;
+
+  if (Method && Method->isVirtual())
+    return false;
+
+  if (llvm::isa<CXXConstructorDecl>(FDecl) &&
+      llvm::any_of(
+          Method->getParent()->bases(),
+          [](const CXXBaseSpecifier &Base) { return Base.isVirtual(); }))
+    return false;
+
+  if (!isLiteralType(FDecl->getReturnType(), Ctx, ConservativeLiteralType))
+    return false;
+
+  for (const ParmVarDecl *Param : FDecl->parameters())
+    if (!isLiteralType(Param->getType(), Ctx, ConservativeLiteralType))
+      return false;
+
+  class Visitor14 : public clang::RecursiveASTVisitor<Visitor14> {
+  public:
+    using Base = clang::RecursiveASTVisitor<Visitor14>;
+    bool shouldVisitImplicitCode() const { return true; }
+
+    Visitor14(bool CXX17, ASTContext &Ctx, bool ConservativeLiteralType,
+              bool AddConstexprToMethodOfClassWithoutConstexprConstructor)
+        : CXX17(CXX17), Ctx(Ctx),
+          ConservativeLiteralType(ConservativeLiteralType),
+          AddConstexprToMethodOfClassWithoutConstexprConstructor(
+              AddConstexprToMethodOfClassWithoutConstexprConstructor) {}
+
+    bool TraverseGotoStmt(GotoStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseLabelStmt(LabelStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseCXXTryStmt(CXXTryStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseGCCAsmStmt(GCCAsmStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseMSAsmStmt(MSAsmStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseDecompositionDecl(DecompositionDecl * /*DD*/) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseVarDecl(VarDecl *VD) {
+      const auto StorageDur = VD->getStorageDuration();
+      Possible = VD->hasInit() &&
+                 isLiteralType(VD->getType(), VD->getASTContext(),
+                               ConservativeLiteralType) &&
+                 (StorageDur != StorageDuration::SD_Static &&
+                  StorageDur != StorageDuration::SD_Thread);
+      return Possible && Base::TraverseVarDecl(VD);
+    }
+    bool TraverseLambdaExpr(LambdaExpr *LE) {
+      if (CXX17) {
+        Possible = satisfiesProperties1417(
+            LE->getCallOperator(), Ctx, ConservativeLiteralType,
+            AddConstexprToMethodOfClassWithoutConstexprConstructor);
+        return Possible;
+      }
+      Possible = false;
+      return false;
+    }
+    bool TraverseCXXNewExpr(CXXNewExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    bool TraverseDeclRefExpr(DeclRefExpr *DRef) {
+      if (const auto *D = llvm::dyn_cast_if_present<VarDecl>(DRef->getDecl());
+          D && !D->isLocalVarDeclOrParm() && D->hasGlobalStorage()) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool WalkUpFromCastExpr(CastExpr *CE) {
+      if (llvm::is_contained(
+              {
+                  CK_LValueBitCast,
+                  CK_IntegralToPointer,
+                  CK_PointerToIntegral,
+              },
+              CE->getCastKind())) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool TraverseCXXDynamicCastExpr(CXXDynamicCastExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    bool TraverseCXXReinterpretCastExpr(CXXReinterpretCastExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    const bool CXX17;
+    bool Possible = true;
+    ASTContext &Ctx;
+    const bool ConservativeLiteralType;
+    const bool AddConstexprToMethodOfClassWithoutConstexprConstructor;
+  };
+
+  Visitor14 V{Ctx.getLangOpts().CPlusPlus17 != 0, Ctx, ConservativeLiteralType,
+              AddConstexprToMethodOfClassWithoutConstexprConstructor};
+  V.TraverseDecl(const_cast<FunctionDecl *>(FDecl));
+  if (!V.Possible)
+    return false;
+
+  if (const auto *Ctor = llvm::dyn_cast<CXXConstructorDecl>(FDecl);
+      Ctor && !satisfiesConstructorPropertiesUntil20(Ctor, Ctx))
+    return false;
+
+  if (const auto *Dtor = llvm::dyn_cast<CXXDestructorDecl>(FDecl);
+      Dtor && !Dtor->isTrivial())
+    return false;
+
+  class BodyVisitor : public clang::RecursiveASTVisitor<BodyVisitor> {
+  public:
+    using Base = clang::RecursiveASTVisitor<BodyVisitor>;
+    bool shouldVisitImplicitCode() const { return true; }
+
+    explicit BodyVisitor(ASTContext &Ctx, bool ConservativeLiteralType)
+        : Ctx(Ctx), ConservativeLiteralType(ConservativeLiteralType) {}
+
+    bool TraverseType(QualType QT) {
+      if (QT.isNull())
+        return true;
+      if (!isLiteralType(QT, Ctx, ConservativeLiteralType)) {
+        Possible = false;
+        return false;
+      }
+      return Base::TraverseType(QT);
+    }
+
+    bool WalkUpFromCXXConstructExpr(CXXConstructExpr *CE) {
+      if (const auto *Ctor = CE->getConstructor();
+          Ctor && !Ctor->isConstexprSpecified()) {
+        Possible = false;
+        return false;
+      }
+
+      return true;
+    }
+    bool WalkUpFromCallExpr(CallExpr *CE) {
+      if (const auto *FDecl =
+              llvm::dyn_cast_if_present<FunctionDecl>(CE->getCalleeDecl());
+          FDecl && !FDecl->isConstexprSpecified()) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool TraverseCXXNewExpr(CXXNewExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    ASTContext &Ctx;
+    const bool ConservativeLiteralType;
+    bool Possible = true;
+  };
+
+  if (FDecl->hasBody() && ConservativeLiteralType) {
+    BodyVisitor Visitor(Ctx, ConservativeLiteralType);
+    Visitor.TraverseStmt(FDecl->getBody());
+    if (!Visitor.Possible)
+      return false;
+  }
+  return true;
+}
+
+bool satisfiesProperties20(
+    const FunctionDecl *FDecl, ASTContext &Ctx,
+    const bool ConservativeLiteralType,
+    const bool AddConstexprToMethodOfClassWithoutConstexprConstructor) {
+  if (FDecl->isConstexprSpecified()) {
+    return true;
+  }
+  const LangOptions LO = Ctx.getLangOpts();
+  const CXXMethodDecl *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);
+  if (Method && !Method->isStatic() &&
+      !Method->getParent()->hasConstexprNonCopyMoveConstructor() &&
+      !AddConstexprToMethodOfClassWithoutConstexprConstructor)
+    return false;
+
+  if (FDecl->hasBody() && llvm::isa<CoroutineBodyStmt>(FDecl->getBody()))
+    return false;
+
+  if ((llvm::isa<CXXConstructorDecl>(FDecl) ||
+       llvm::isa<CXXDestructorDecl>(FDecl)) &&
+      llvm::any_of(
+          Method->getParent()->bases(),
+          [](const CXXBaseSpecifier &Base) { return Base.isVirtual(); }))
+    return false;
+
+  if (!isLiteralType(FDecl->getReturnType(), Ctx, ConservativeLiteralType))
+    return false;
+
+  for (const ParmVarDecl *Param : FDecl->parameters())
+    if (!isLiteralType(Param->getType(), Ctx, ConservativeLiteralType))
+      return false;
+
+  class Visitor20 : public clang::RecursiveASTVisitor<Visitor20> {
+  public:
+    bool shouldVisitImplicitCode() const { return true; }
+
+    Visitor20(bool ConservativeLiteralType)
+        : ConservativeLiteralType(ConservativeLiteralType) {}
+
+    bool TraverseGotoStmt(GotoStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseLabelStmt(LabelStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseCXXTryStmt(CXXTryStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseGCCAsmStmt(GCCAsmStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseMSAsmStmt(MSAsmStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseDecompositionDecl(DecompositionDecl * /*DD*/) {
+      Possible = f...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Jul 1, 2025

@llvm/pr-subscribers-clang-tools-extra

Author: Julian Schmidt (5chmidti)

Changes

This check finds all functions and variables that can be declared as
constexpr, using the specified standard version to check if the
requirements are met.

Fixes #115622


Patch is 95.12 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/146553.diff

9 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/misc/CMakeLists.txt (+1)
  • (added) clang-tools-extra/clang-tidy/misc/ConstexprCheck.cpp (+936)
  • (added) clang-tools-extra/clang-tidy/misc/ConstexprCheck.h (+43)
  • (modified) clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp (+3)
  • (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
  • (added) clang-tools-extra/docs/clang-tidy/checks/misc/constexpr.rst (+40)
  • (added) clang-tools-extra/test/clang-tidy/checkers/misc/constexpr-cxx20.cpp (+36)
  • (added) clang-tools-extra/test/clang-tidy/checkers/misc/constexpr-ref.cpp (+383)
  • (added) clang-tools-extra/test/clang-tidy/checkers/misc/constexpr.cpp (+562)
diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
index fd7affd22a463..4535df3451c95 100644
--- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt
@@ -19,6 +19,7 @@ set_target_properties(genconfusable PROPERTIES FOLDER "Clang Tools Extra/Sourceg
 
 add_clang_library(clangTidyMiscModule STATIC
   ConstCorrectnessCheck.cpp
+  ConstexprCheck.cpp
   CoroutineHostileRAIICheck.cpp
   DefinitionsInHeadersCheck.cpp
   ConfusableIdentifierCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/misc/ConstexprCheck.cpp b/clang-tools-extra/clang-tidy/misc/ConstexprCheck.cpp
new file mode 100644
index 0000000000000..270ac1dd0fa48
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/misc/ConstexprCheck.cpp
@@ -0,0 +1,936 @@
+//===--- ConstexprCheck.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 "ConstexprCheck.h"
+#include "../utils/ASTUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/ASTTypeTraits.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/AST/OperationKinds.h"
+#include "clang/AST/RecursiveASTVisitor.h"
+#include "clang/AST/Stmt.h"
+#include "clang/AST/StmtCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/ASTMatchers/ASTMatchersInternal.h"
+#include "clang/ASTMatchers/ASTMatchersMacros.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Basic/Specifiers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/Support/Casting.h"
+#include <cstddef>
+#include <functional>
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::misc {
+
+namespace {
+AST_MATCHER(FunctionDecl, locationPermitsConstexpr) {
+  const bool IsInMainFile =
+      Finder->getASTContext().getSourceManager().isInMainFile(
+          Node.getLocation());
+
+  if (IsInMainFile && Node.hasExternalFormalLinkage())
+    return false;
+  if (!IsInMainFile && !Node.isInlined())
+    return false;
+
+  return true;
+}
+
+AST_MATCHER(Expr, isCXX11ConstantExpr) {
+  return !Node.isValueDependent() &&
+         Node.isCXX11ConstantExpr(Finder->getASTContext());
+}
+
+AST_MATCHER(DeclaratorDecl, isInMacro) {
+  const SourceRange R =
+      SourceRange(Node.getInnerLocStart(), Node.getLocation());
+
+  return Node.getLocation().isMacroID() || Node.getEndLoc().isMacroID() ||
+         utils::rangeContainsMacroExpansion(
+             R, &Finder->getASTContext().getSourceManager()) ||
+         utils::rangeIsEntirelyWithinMacroArgument(
+             R, &Finder->getASTContext().getSourceManager());
+}
+
+AST_MATCHER(Decl, hasNoRedecl) {
+  // There is always the actual declaration
+  return !Node.redecls().empty() &&
+         std::next(Node.redecls_begin()) == Node.redecls_end();
+}
+
+AST_MATCHER(Decl, allRedeclsInSameFile) {
+  const SourceManager &SM = Finder->getASTContext().getSourceManager();
+  const SourceLocation L = Node.getLocation();
+  for (const Decl *ReDecl : Node.redecls()) {
+    if (!SM.isWrittenInSameFile(L, ReDecl->getLocation()))
+      return false;
+  }
+  return true;
+}
+
+AST_MATCHER(FunctionDecl, isConstexprSpecified) {
+  return Node.isConstexprSpecified();
+}
+
+bool satisfiesConstructorPropertiesUntil20(const CXXConstructorDecl *Ctor,
+                                           ASTContext &Ctx) {
+  const CXXRecordDecl *Rec = Ctor->getParent();
+  llvm::SmallPtrSet<const RecordDecl *, 8> Bases{};
+  for (const CXXBaseSpecifier Base : Rec->bases()) {
+    Bases.insert(Base.getType()->getAsRecordDecl());
+  }
+  llvm::SmallPtrSet<const FieldDecl *, 8> Fields{Rec->field_begin(),
+                                                 Rec->field_end()};
+  llvm::SmallPtrSet<const FieldDecl *, 4> Indirects{};
+
+  for (const CXXCtorInitializer *const Init : Ctor->inits()) {
+    const Type *InitType = Init->getBaseClass();
+    if (InitType && InitType->isRecordType()) {
+      const auto *ConstructingInit =
+          llvm::dyn_cast<CXXConstructExpr>(Init->getInit());
+      if (ConstructingInit &&
+          !ConstructingInit->getConstructor()->isConstexprSpecified())
+        return false;
+    }
+
+    if (Init->isBaseInitializer()) {
+      Bases.erase(Init->getBaseClass()->getAsRecordDecl());
+      continue;
+    }
+
+    if (Init->isMemberInitializer()) {
+      const FieldDecl *Field = Init->getMember();
+
+      if (Field->isAnonymousStructOrUnion())
+        Indirects.insert(Field);
+
+      Fields.erase(Field);
+      continue;
+    }
+  }
+
+  for (const auto &Match :
+       match(cxxRecordDecl(forEach(indirectFieldDecl().bind("indirect"))), *Rec,
+             Ctx)) {
+    const auto *IField = Match.getNodeAs<IndirectFieldDecl>("indirect");
+
+    size_t NumInitializations = false;
+    for (const NamedDecl *ND : IField->chain())
+      NumInitializations += Indirects.erase(llvm::dyn_cast<FieldDecl>(ND));
+
+    if (NumInitializations != 1)
+      return false;
+
+    for (const NamedDecl *ND : IField->chain())
+      Fields.erase(llvm::dyn_cast<FieldDecl>(ND));
+  }
+
+  if (!Fields.empty())
+    return false;
+
+  return true;
+}
+
+const Type *unwrapPointee(const Type *T) {
+  if (!T->isPointerOrReferenceType())
+    return T;
+
+  while (T && T->isPointerOrReferenceType()) {
+    if (T->isReferenceType()) {
+      const QualType QType = T->getPointeeType();
+      if (!QType.isNull())
+        T = QType.getTypePtr();
+    } else
+      T = T->getPointeeOrArrayElementType();
+  }
+
+  return T;
+}
+
+bool isLiteralType(QualType QT, const ASTContext &Ctx,
+                   const bool ConservativeLiteralType);
+
+bool isLiteralType(const Type *T, const ASTContext &Ctx,
+                   const bool ConservativeLiteralType) {
+  if (!T)
+    return false;
+
+  if (!T->isLiteralType(Ctx))
+    return false;
+
+  if (!ConservativeLiteralType)
+    return T->isLiteralType(Ctx) && !T->isVoidType();
+
+  if (T->isIncompleteType() || T->isIncompleteArrayType())
+    return false;
+
+  T = unwrapPointee(T);
+  if (!T)
+    return false;
+
+  assert(!T->isPointerOrReferenceType());
+
+  if (T->isIncompleteType() || T->isIncompleteArrayType())
+    return false;
+
+  if (T->isLiteralType(Ctx))
+    return true;
+
+  if (const auto *Rec = T->getAsCXXRecordDecl()) {
+    if (llvm::any_of(Rec->ctors(), [](const CXXConstructorDecl *Ctor) {
+          return !Ctor->isCopyOrMoveConstructor() &&
+                 Ctor->isConstexprSpecified();
+        }))
+      return false;
+
+    for (const CXXBaseSpecifier Base : Rec->bases()) {
+      if (!isLiteralType(Base.getType(), Ctx, ConservativeLiteralType))
+        return false;
+    }
+  }
+
+  if (const Type *ArrayElementType = T->getArrayElementTypeNoTypeQual())
+    return isLiteralType(ArrayElementType, Ctx, ConservativeLiteralType);
+
+  return false;
+}
+
+bool isLiteralType(QualType QT, const ASTContext &Ctx,
+                   const bool ConservativeLiteralType) {
+  return isLiteralType(QT.getTypePtr(), Ctx, ConservativeLiteralType);
+}
+
+bool satisfiesProperties11(
+    const FunctionDecl *FDecl, ASTContext &Ctx,
+    const bool ConservativeLiteralType,
+    const bool AddConstexprToMethodOfClassWithoutConstexprConstructor) {
+  if (FDecl->isConstexprSpecified()) {
+    return true;
+  }
+  const LangOptions LO = Ctx.getLangOpts();
+  const CXXMethodDecl *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);
+  if (Method && !Method->isStatic() &&
+      !Method->getParent()->hasConstexprNonCopyMoveConstructor() &&
+      !AddConstexprToMethodOfClassWithoutConstexprConstructor)
+    return false;
+
+  if (Method &&
+      (Method->isVirtual() ||
+       !match(cxxMethodDecl(hasBody(cxxTryStmt())), *Method, Ctx).empty()))
+    return false;
+
+  if (const auto *Ctor = llvm::dyn_cast<CXXConstructorDecl>(FDecl);
+      Ctor && (!satisfiesConstructorPropertiesUntil20(Ctor, Ctx) ||
+               llvm::any_of(Ctor->getParent()->bases(),
+                            [](const CXXBaseSpecifier &Base) {
+                              return Base.isVirtual();
+                            })))
+    return false;
+
+  if (const auto *Dtor = llvm::dyn_cast<CXXDestructorDecl>(FDecl);
+      Dtor && !Dtor->isTrivial())
+    return false;
+
+  if (!isLiteralType(FDecl->getReturnType(), Ctx, ConservativeLiteralType))
+    return false;
+
+  for (const ParmVarDecl *Param : FDecl->parameters())
+    if (!isLiteralType(Param->getType(), Ctx, ConservativeLiteralType))
+      return false;
+
+  class Visitor11 : public clang::RecursiveASTVisitor<Visitor11> {
+  public:
+    using Base = clang::RecursiveASTVisitor<Visitor11>;
+    bool shouldVisitImplicitCode() const { return true; }
+
+    Visitor11(ASTContext &Ctx, bool ConservativeLiteralType)
+        : Ctx(Ctx), ConservativeLiteralType(ConservativeLiteralType) {}
+
+    bool WalkUpFromNullStmt(NullStmt *) {
+      Possible = false;
+      return true;
+    }
+    bool WalkUpFromDeclStmt(DeclStmt *DS) {
+      for (const Decl *D : DS->decls())
+        if (!llvm::isa<StaticAssertDecl, TypedefNameDecl, UsingDecl,
+                       UsingDirectiveDecl>(D)) {
+          Possible = false;
+          return false;
+        }
+      return true;
+    }
+
+    bool WalkUpFromExpr(Expr *) { return true; }
+    bool WalkUpFromCompoundStmt(CompoundStmt *S) {
+      for (const DynTypedNode &Node : Ctx.getParents(*S))
+        if (Node.get<FunctionDecl>() != nullptr)
+          return true;
+
+      Possible = false;
+      return false;
+    }
+    bool WalkUpFromStmt(Stmt *) {
+      Possible = false;
+      return false;
+    }
+
+    bool WalkUpFromReturnStmt(ReturnStmt *) {
+      ++NumReturns;
+      if (NumReturns != 1U) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool WalkUpFromCastExpr(CastExpr *CE) {
+      if (llvm::is_contained(
+              {
+                  CK_LValueBitCast,
+                  CK_IntegralToPointer,
+                  CK_PointerToIntegral,
+              },
+              CE->getCastKind())) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool TraverseCXXDynamicCastExpr(CXXDynamicCastExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    bool TraverseCXXReinterpretCastExpr(CXXReinterpretCastExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    bool TraverseType(QualType QT) {
+      if (QT.isNull())
+        return true;
+      if (!isLiteralType(QT, Ctx, ConservativeLiteralType)) {
+        Possible = false;
+        return false;
+      }
+      return Base::TraverseType(QT);
+    }
+
+    bool WalkUpFromCXXConstructExpr(CXXConstructExpr *CE) {
+      if (const auto *Ctor = CE->getConstructor();
+          Ctor && !Ctor->isConstexprSpecified()) {
+        Possible = false;
+        return false;
+      }
+
+      return true;
+    }
+    bool WalkUpFromCallExpr(CallExpr *CE) {
+      if (const auto *FDecl =
+              llvm::dyn_cast_if_present<FunctionDecl>(CE->getCalleeDecl());
+          FDecl && !FDecl->isConstexprSpecified()) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool TraverseCXXNewExpr(CXXNewExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    ASTContext &Ctx;
+    const bool ConservativeLiteralType;
+    bool Possible = true;
+    size_t NumReturns = 0;
+  };
+
+  Visitor11 V{Ctx, ConservativeLiteralType};
+  V.TraverseDecl(const_cast<FunctionDecl *>(FDecl));
+  if (!V.Possible)
+    return false;
+
+  return true;
+}
+
+// The only difference between C++14 and C++17 is that `constexpr` lambdas
+// can be used in C++17.
+bool satisfiesProperties1417(
+    const FunctionDecl *FDecl, ASTContext &Ctx,
+    const bool ConservativeLiteralType,
+    const bool AddConstexprToMethodOfClassWithoutConstexprConstructor) {
+  if (FDecl->isConstexprSpecified())
+    return true;
+
+  const LangOptions LO = Ctx.getLangOpts();
+  const CXXMethodDecl *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);
+  if (Method && !Method->isStatic() &&
+      !Method->getParent()->hasConstexprNonCopyMoveConstructor() &&
+      !AddConstexprToMethodOfClassWithoutConstexprConstructor)
+    return false;
+
+  if (Method && Method->isVirtual())
+    return false;
+
+  if (llvm::isa<CXXConstructorDecl>(FDecl) &&
+      llvm::any_of(
+          Method->getParent()->bases(),
+          [](const CXXBaseSpecifier &Base) { return Base.isVirtual(); }))
+    return false;
+
+  if (!isLiteralType(FDecl->getReturnType(), Ctx, ConservativeLiteralType))
+    return false;
+
+  for (const ParmVarDecl *Param : FDecl->parameters())
+    if (!isLiteralType(Param->getType(), Ctx, ConservativeLiteralType))
+      return false;
+
+  class Visitor14 : public clang::RecursiveASTVisitor<Visitor14> {
+  public:
+    using Base = clang::RecursiveASTVisitor<Visitor14>;
+    bool shouldVisitImplicitCode() const { return true; }
+
+    Visitor14(bool CXX17, ASTContext &Ctx, bool ConservativeLiteralType,
+              bool AddConstexprToMethodOfClassWithoutConstexprConstructor)
+        : CXX17(CXX17), Ctx(Ctx),
+          ConservativeLiteralType(ConservativeLiteralType),
+          AddConstexprToMethodOfClassWithoutConstexprConstructor(
+              AddConstexprToMethodOfClassWithoutConstexprConstructor) {}
+
+    bool TraverseGotoStmt(GotoStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseLabelStmt(LabelStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseCXXTryStmt(CXXTryStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseGCCAsmStmt(GCCAsmStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseMSAsmStmt(MSAsmStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseDecompositionDecl(DecompositionDecl * /*DD*/) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseVarDecl(VarDecl *VD) {
+      const auto StorageDur = VD->getStorageDuration();
+      Possible = VD->hasInit() &&
+                 isLiteralType(VD->getType(), VD->getASTContext(),
+                               ConservativeLiteralType) &&
+                 (StorageDur != StorageDuration::SD_Static &&
+                  StorageDur != StorageDuration::SD_Thread);
+      return Possible && Base::TraverseVarDecl(VD);
+    }
+    bool TraverseLambdaExpr(LambdaExpr *LE) {
+      if (CXX17) {
+        Possible = satisfiesProperties1417(
+            LE->getCallOperator(), Ctx, ConservativeLiteralType,
+            AddConstexprToMethodOfClassWithoutConstexprConstructor);
+        return Possible;
+      }
+      Possible = false;
+      return false;
+    }
+    bool TraverseCXXNewExpr(CXXNewExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    bool TraverseDeclRefExpr(DeclRefExpr *DRef) {
+      if (const auto *D = llvm::dyn_cast_if_present<VarDecl>(DRef->getDecl());
+          D && !D->isLocalVarDeclOrParm() && D->hasGlobalStorage()) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool WalkUpFromCastExpr(CastExpr *CE) {
+      if (llvm::is_contained(
+              {
+                  CK_LValueBitCast,
+                  CK_IntegralToPointer,
+                  CK_PointerToIntegral,
+              },
+              CE->getCastKind())) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool TraverseCXXDynamicCastExpr(CXXDynamicCastExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    bool TraverseCXXReinterpretCastExpr(CXXReinterpretCastExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    const bool CXX17;
+    bool Possible = true;
+    ASTContext &Ctx;
+    const bool ConservativeLiteralType;
+    const bool AddConstexprToMethodOfClassWithoutConstexprConstructor;
+  };
+
+  Visitor14 V{Ctx.getLangOpts().CPlusPlus17 != 0, Ctx, ConservativeLiteralType,
+              AddConstexprToMethodOfClassWithoutConstexprConstructor};
+  V.TraverseDecl(const_cast<FunctionDecl *>(FDecl));
+  if (!V.Possible)
+    return false;
+
+  if (const auto *Ctor = llvm::dyn_cast<CXXConstructorDecl>(FDecl);
+      Ctor && !satisfiesConstructorPropertiesUntil20(Ctor, Ctx))
+    return false;
+
+  if (const auto *Dtor = llvm::dyn_cast<CXXDestructorDecl>(FDecl);
+      Dtor && !Dtor->isTrivial())
+    return false;
+
+  class BodyVisitor : public clang::RecursiveASTVisitor<BodyVisitor> {
+  public:
+    using Base = clang::RecursiveASTVisitor<BodyVisitor>;
+    bool shouldVisitImplicitCode() const { return true; }
+
+    explicit BodyVisitor(ASTContext &Ctx, bool ConservativeLiteralType)
+        : Ctx(Ctx), ConservativeLiteralType(ConservativeLiteralType) {}
+
+    bool TraverseType(QualType QT) {
+      if (QT.isNull())
+        return true;
+      if (!isLiteralType(QT, Ctx, ConservativeLiteralType)) {
+        Possible = false;
+        return false;
+      }
+      return Base::TraverseType(QT);
+    }
+
+    bool WalkUpFromCXXConstructExpr(CXXConstructExpr *CE) {
+      if (const auto *Ctor = CE->getConstructor();
+          Ctor && !Ctor->isConstexprSpecified()) {
+        Possible = false;
+        return false;
+      }
+
+      return true;
+    }
+    bool WalkUpFromCallExpr(CallExpr *CE) {
+      if (const auto *FDecl =
+              llvm::dyn_cast_if_present<FunctionDecl>(CE->getCalleeDecl());
+          FDecl && !FDecl->isConstexprSpecified()) {
+        Possible = false;
+        return false;
+      }
+      return true;
+    }
+
+    bool TraverseCXXNewExpr(CXXNewExpr *) {
+      Possible = false;
+      return false;
+    }
+
+    ASTContext &Ctx;
+    const bool ConservativeLiteralType;
+    bool Possible = true;
+  };
+
+  if (FDecl->hasBody() && ConservativeLiteralType) {
+    BodyVisitor Visitor(Ctx, ConservativeLiteralType);
+    Visitor.TraverseStmt(FDecl->getBody());
+    if (!Visitor.Possible)
+      return false;
+  }
+  return true;
+}
+
+bool satisfiesProperties20(
+    const FunctionDecl *FDecl, ASTContext &Ctx,
+    const bool ConservativeLiteralType,
+    const bool AddConstexprToMethodOfClassWithoutConstexprConstructor) {
+  if (FDecl->isConstexprSpecified()) {
+    return true;
+  }
+  const LangOptions LO = Ctx.getLangOpts();
+  const CXXMethodDecl *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);
+  if (Method && !Method->isStatic() &&
+      !Method->getParent()->hasConstexprNonCopyMoveConstructor() &&
+      !AddConstexprToMethodOfClassWithoutConstexprConstructor)
+    return false;
+
+  if (FDecl->hasBody() && llvm::isa<CoroutineBodyStmt>(FDecl->getBody()))
+    return false;
+
+  if ((llvm::isa<CXXConstructorDecl>(FDecl) ||
+       llvm::isa<CXXDestructorDecl>(FDecl)) &&
+      llvm::any_of(
+          Method->getParent()->bases(),
+          [](const CXXBaseSpecifier &Base) { return Base.isVirtual(); }))
+    return false;
+
+  if (!isLiteralType(FDecl->getReturnType(), Ctx, ConservativeLiteralType))
+    return false;
+
+  for (const ParmVarDecl *Param : FDecl->parameters())
+    if (!isLiteralType(Param->getType(), Ctx, ConservativeLiteralType))
+      return false;
+
+  class Visitor20 : public clang::RecursiveASTVisitor<Visitor20> {
+  public:
+    bool shouldVisitImplicitCode() const { return true; }
+
+    Visitor20(bool ConservativeLiteralType)
+        : ConservativeLiteralType(ConservativeLiteralType) {}
+
+    bool TraverseGotoStmt(GotoStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseLabelStmt(LabelStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseCXXTryStmt(CXXTryStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseGCCAsmStmt(GCCAsmStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseMSAsmStmt(MSAsmStmt *) {
+      Possible = false;
+      return false;
+    }
+    bool TraverseDecompositionDecl(DecompositionDecl * /*DD*/) {
+      Possible = f...
[truncated]

Copy link

github-actions bot commented Jul 1, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@5chmidti 5chmidti force-pushed the clang_tidy_constexpr branch from 35a081f to 2a50c3e Compare July 1, 2025 15:55
@carlosgalvezp
Copy link
Contributor

misc-use-constexpr? :)

@5chmidti
Copy link
Contributor Author

5chmidti commented Jul 1, 2025

I ran this check on all of clang/ and clang-tools-extra/ and got no compilation errors from too aggressively changing things to constexpr.

Some perf info from running the checks enabled in LLVM + this check:

Running on: clang/lib/Sema/SemaExprCXX.cpp (1 diag):

===-------------------------------------------------------------------------===
                          clang-tidy checks profiling
===-------------------------------------------------------------------------===
  Total Execution Time: 6.4289 seconds (6.4453 wall clock)

   ---User Time---   --System Time--   --User+System--   ---Wall Time---  --- Name ---
   0.7024 ( 20.6%)   0.7401 ( 24.5%)   1.4424 ( 22.4%)   1.4457 ( 22.4%)  misc-unused-using-decls
   0.6188 ( 18.2%)   0.1852 (  6.1%)   0.8040 ( 12.5%)   0.8069 ( 12.5%)  llvm-qualified-auto
   0.2833 (  8.3%)   0.2724 (  9.0%)   0.5556 (  8.6%)   0.5581 (  8.7%)  llvm-prefer-isa-or-dyn-cast-in-conditionals
   0.2288 (  6.7%)   0.2426 (  8.0%)   0.4714 (  7.3%)   0.4715 (  7.3%)  misc-misleading-identifier
   0.2271 (  6.7%)   0.2140 (  7.1%)   0.4411 (  6.9%)   0.4423 (  6.9%)  misc-confusable-identifiers
   0.2132 (  6.3%)   0.2268 (  7.5%)   0.4400 (  6.8%)   0.4402 (  6.8%)  misc-definitions-in-headers
   0.1938 (  5.7%)   0.2066 (  6.8%)   0.4003 (  6.2%)   0.4008 (  6.2%)  misc-non-copyable-objects
   0.1371 (  4.0%)   0.1386 (  4.6%)   0.2757 (  4.3%)   0.2750 (  4.3%)  misc-unconventional-assign-operator
   0.1235 (  3.6%)   0.1296 (  4.3%)   0.2531 (  3.9%)   0.2549 (  4.0%)  misc-constexpr
   0.1146 (  3.4%)   0.1188 (  3.9%)   0.2334 (  3.6%)   0.2346 (  3.6%)  misc-misplaced-const
   0.1176 (  3.5%)   0.1061 (  3.5%)   0.2238 (  3.5%)   0.2236 (  3.5%)  misc-redundant-expression
   0.1074 (  3.2%)   0.1110 (  3.7%)   0.2184 (  3.4%)   0.2208 (  3.4%)  misc-use-internal-linkage
   0.0665 (  2.0%)   0.0720 (  2.4%)   0.1385 (  2.2%)   0.1400 (  2.2%)  misc-new-delete-overloads
   0.0615 (  1.8%)   0.0640 (  2.1%)   0.1255 (  2.0%)   0.1257 (  2.0%)  llvm-prefer-register-over-unsigned
   0.0548 (  1.6%)   0.0572 (  1.9%)   0.1120 (  1.7%)   0.1121 (  1.7%)  llvm-twine-local
   0.0444 (  1.3%)   0.0328 (  1.1%)   0.0771 (  1.2%)   0.0773 (  1.2%)  llvm-namespace-comment
   0.0385 (  1.1%)   0.0312 (  1.0%)   0.0697 (  1.1%)   0.0695 (  1.1%)  llvm-else-after-return
   0.0325 (  1.0%)   0.0351 (  1.2%)   0.0677 (  1.1%)   0.0674 (  1.0%)  misc-unused-alias-decls
   0.0294 (  0.9%)   0.0255 (  0.8%)   0.0549 (  0.9%)   0.0547 (  0.8%)  misc-static-assert
   0.0108 (  0.3%)   0.0100 (  0.3%)   0.0208 (  0.3%)   0.0207 (  0.3%)  misc-uniqueptr-reset-release
   0.0018 (  0.1%)   0.0018 (  0.1%)   0.0036 (  0.1%)   0.0035 (  0.1%)  misc-misleading-bidirectional
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.0%)  misc-throw-by-value-catch-by-reference
   3.4077 (100.0%)   3.0212 (100.0%)   6.4289 (100.0%)   6.4453 (100.0%)  Total

Running on clang/utils/TableGen/RISCVVEmitter.cpp (3 diags):

===-------------------------------------------------------------------------===
                          clang-tidy checks profiling
===-------------------------------------------------------------------------===
  Total Execution Time: 1.1712 seconds (1.1748 wall clock)

   ---User Time---   --System Time--   --User+System--   ---Wall Time---  --- Name ---
   0.1293 ( 20.2%)   0.1265 ( 23.8%)   0.2557 ( 21.8%)   0.2569 ( 21.9%)  misc-unused-using-decls
   0.0673 ( 10.5%)   0.0576 ( 10.9%)   0.1249 ( 10.7%)   0.1257 ( 10.7%)  llvm-prefer-isa-or-dyn-cast-in-conditionals
   0.0833 ( 13.0%)   0.0275 (  5.2%)   0.1108 (  9.5%)   0.1112 (  9.5%)  llvm-qualified-auto
   0.0434 (  6.8%)   0.0407 (  7.7%)   0.0840 (  7.2%)   0.0843 (  7.2%)  misc-misleading-identifier
   0.0436 (  6.8%)   0.0385 (  7.2%)   0.0820 (  7.0%)   0.0817 (  7.0%)  misc-confusable-identifiers
   0.0417 (  6.5%)   0.0387 (  7.3%)   0.0804 (  6.9%)   0.0806 (  6.9%)  misc-definitions-in-headers
   0.0383 (  6.0%)   0.0359 (  6.8%)   0.0742 (  6.3%)   0.0744 (  6.3%)  misc-non-copyable-objects
   0.0275 (  4.3%)   0.0217 (  4.1%)   0.0492 (  4.2%)   0.0490 (  4.2%)  misc-redundant-expression
   0.0229 (  3.6%)   0.0215 (  4.1%)   0.0445 (  3.8%)   0.0443 (  3.8%)  misc-constexpr
   0.0229 (  3.6%)   0.0203 (  3.8%)   0.0432 (  3.7%)   0.0431 (  3.7%)  misc-unconventional-assign-operator
   0.0200 (  3.1%)   0.0191 (  3.6%)   0.0391 (  3.3%)   0.0391 (  3.3%)  misc-misplaced-const
   0.0199 (  3.1%)   0.0185 (  3.5%)   0.0384 (  3.3%)   0.0390 (  3.3%)  misc-use-internal-linkage
   0.0154 (  2.4%)   0.0081 (  1.5%)   0.0235 (  2.0%)   0.0236 (  2.0%)  llvm-namespace-comment
   0.0114 (  1.8%)   0.0111 (  2.1%)   0.0225 (  1.9%)   0.0229 (  1.9%)  llvm-prefer-register-over-unsigned
   0.0115 (  1.8%)   0.0104 (  2.0%)   0.0219 (  1.9%)   0.0222 (  1.9%)  misc-new-delete-overloads
   0.0104 (  1.6%)   0.0102 (  1.9%)   0.0206 (  1.8%)   0.0207 (  1.8%)  llvm-twine-local
   0.0122 (  1.9%)   0.0083 (  1.6%)   0.0205 (  1.8%)   0.0205 (  1.7%)  misc-static-assert
   0.0108 (  1.7%)   0.0074 (  1.4%)   0.0182 (  1.6%)   0.0181 (  1.5%)  llvm-else-after-return
   0.0069 (  1.1%)   0.0067 (  1.3%)   0.0135 (  1.2%)   0.0135 (  1.2%)  misc-unused-alias-decls
   0.0013 (  0.2%)   0.0012 (  0.2%)   0.0025 (  0.2%)   0.0025 (  0.2%)  misc-uniqueptr-reset-release
   0.0006 (  0.1%)   0.0008 (  0.1%)   0.0014 (  0.1%)   0.0014 (  0.1%)  misc-misleading-bidirectional
   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.0%)   0.0000 (  0.0%)  misc-throw-by-value-catch-by-reference
   0.6406 (100.0%)   0.5306 (100.0%)   1.1712 (100.0%)   1.1748 (100.0%)  Total

@denzor200
Copy link

misc-use-constexpr? :)

modernize-use-constexpr?

@5chmidti 5chmidti force-pushed the clang_tidy_constexpr branch from 2a50c3e to 8d35424 Compare July 1, 2025 16:11
This check finds all functions and variables that can be declared as
`constexpr`, using the specified standard version to check if the
requirements are met.

Fixes llvm#115622
@5chmidti 5chmidti force-pushed the clang_tidy_constexpr branch from 8d35424 to 16d5a3f Compare July 1, 2025 16:18
@5chmidti
Copy link
Contributor Author

5chmidti commented Jul 1, 2025

misc-use-constexpr? :)

modernize-use-constexpr?

Hm. It is indeed also a modernization... +- on which to pick. Let's see what others think.
On one hand, the modernize checks have

  • modernize-use-auto
  • modernize-use-nodiscard
  • modernize-use-noexcept
  • modernize-use-override
  • modernize-use-using

while the misc category has misc-const-correctness, which is also close to this check. Though, constexpr also provides additional benefits and not just modernization (though that can also be said about some of the other modernize checks). It can provide additional safety checks if UB would be encountered at compile-time, e.g.,

constexpr int div(int a, int b) { return a / b; }
int main() {
  constexpr auto res = div(10,0);
  return 0;
}

, or potential performance improvements.

@denzor200
Copy link

constexpr is one of the important additions of C++11. Most of the users will search this check in "modernize" section and will be misled by not finding it there.
"misc-const-correctness" - not a modernize check at all, it even relevant for C++98, so it exists in "misc"

// CHECK-MESSAGES-11: :[[@LINE-1]]:13: warning: function 'g1' can be declared 'constexpr' [misc-use-constexpr]
// CHECK-FIXES-11: constexpr int g1() { return 0; }
static constexpr int A1 = 0;
static int B1 = 0;

Choose a reason for hiding this comment

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

Just asking - this check will never change it to static constexpr int B1 = 0;, is that intended?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've based the check for variables on them being const as a prerequisite. Otherwise, I'd have to pull in the const-correctness logic on top of this check and that would essentially duplicate the runtime cost of the const-correctness check.

Choose a reason for hiding this comment

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

As I understand, this is a job for "misc-const-correctness", and then this check should be runned.
Thus we should write in the doc a recomendation to run this check only after "misc-const-correctness" have done.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will extend the check docs to explain the dependency on const for variables.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@vbvictor
Copy link
Contributor

vbvictor commented Jul 1, 2025

I'm +1 on the modernize-use-constexpr.

@carlosgalvezp
Copy link
Contributor

I'm ok with modernize-use-constexpr as well!

@5chmidti 5chmidti changed the title [clang-tidy] add misc-constexpr check [clang-tidy] add modernize-use-constexpr check Jul 1, 2025
@seanm
Copy link

seanm commented Jul 1, 2025

I tried with ITK and the resulting transformation did not compile.

Many changes were of this form:

-    const double     Max = 1.0 - Min;
+    constexpr  double     Max = 1.0 - Min;

Which is great, though notice the double space after constexpr for some reason.

Other changes were like this:

-  const auto check = [](const auto & ptr) { EXPECT_THROW(itk::Deref(ptr), itk::DerefError); };
+  constexpr const auto check = [](auto & ptr) { EXPECT_THROW(itk::Deref(ptr), itk::DerefError); };

I'm no C++ expert, but is it right to have both const and constexpr here?

Also, I was surprised to see the 2nd const removed. And this removal generates one of the many compiler errors.

Still, this is looking like it'll be great!

@schenker
Copy link
Contributor

schenker commented Jul 1, 2025

I got some fixits that do not compile, e.g.:

std::erase_if(numbers, [](int n) {
return n % 2 == 0;
});

is changed into

std::erase_if(numbers, [](int nconstexpr ) {
return n % 2 == 0;
});

@5chmidti
Copy link
Contributor Author

5chmidti commented Jul 1, 2025

I tried with ITK and the resulting transformation did not compile.

Many changes were of this form:

-    const double     Max = 1.0 - Min;
+    constexpr  double     Max = 1.0 - Min;

Which is great, though notice the double space after constexpr for some reason.

The reason is that when inserting, I add a constexpr with a trailing space, but when it is found, also remove the const token without its corresponding space. This will have to be fixed by running clang-format; we have a few of these additional whitespaces in some checks.

Other changes were like this:

-  const auto check = [](const auto & ptr) { EXPECT_THROW(itk::Deref(ptr), itk::DerefError); };
+  constexpr const auto check = [](auto & ptr) { EXPECT_THROW(itk::Deref(ptr), itk::DerefError); };

I'm no C++ expert, but is it right to have both const and constexpr here?

Also, I was surprised to see the 2nd const removed. And this removal generates one of the many compiler errors.

Eh... yeah. The range I passed to the getQualifyingToken function was too big and included more than it needed. It's fixed now, and I've added a test for it.

Still, this is looking like it'll be great!

Thanks, and thank you very much for trying it out. My current setup is limited in what I can test outside the LLVM codebase.

@5chmidti
Copy link
Contributor Author

5chmidti commented Jul 1, 2025

Thanks for testing this on your codebase so fast as well.

I got some fixits that do not compile, e.g.:

std::erase_if(numbers, [](int n) {
return n % 2 == 0;
});

is changed into

std::erase_if(numbers, [](int nconstexpr ) {
return n % 2 == 0;
});

I've tried to reproduce this but was unable to, could you please provide a small reproducer?
I've tried this, and it did not produce a diagnostic (using C++20):

#include <vector>

int main() {
  std::vector<int> Numbers = {
      0, 1, 2, 3, 4, 5, 6, 7,
  };
  const auto count = std::erase_if(Numbers, [](int N) { return N % 2 == 0; });
}

@5chmidti
Copy link
Contributor Author

5chmidti commented Jul 1, 2025

Sorry for the silly mistakes, btw.

I've updated the docs, added replacement string options, and fixed the const keyword removal.

@schenker
Copy link
Contributor

schenker commented Jul 2, 2025

I've tried to reproduce this but was unable to, could you please provide a small reproducer?

With 127d7f0 I can reporoduce the wrong fixit like this:

test.cpp

#include <vector>
template <typename T> void func() {
  std::vector<int> Numbers = {0, 1};
  const auto count =
  std::erase_if(Numbers, [](int N) { return N % 2 == 0; });
}

commands

clang-tidy -checks=-*,*constexpr* -export-fixes fixes.yaml test.cpp -- --std=c++23
clang-apply-replacements .

result

#include <vector>
template <typename T> void func() {
  std::vector<int> Numbers = {0, 1};
  const auto count =
  std::erase_if(Numbers, [](int Nconstexpr ) { return N % 2 == 0; });
}

if (T->isLiteralType(Ctx))
return true;

if (const auto *Rec = T->getAsCXXRecordDecl()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

auto should not be used - type is not spelled explicitly.

}

bool WalkUpFromCXXConstructExpr(CXXConstructExpr *CE) {
if (const auto *Ctor = CE->getConstructor();
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

return true;

const LangOptions LO = Ctx.getLangOpts();
const CXXMethodDecl *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);
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
const CXXMethodDecl *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);
const auto *Method = llvm::dyn_cast<CXXMethodDecl>(FDecl);

return false;
}
bool TraverseVarDecl(VarDecl *VD) {
const auto StorageDur = VD->getStorageDuration();
Copy link
Contributor

Choose a reason for hiding this comment

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

auto should not be used.

}

bool WalkUpFromCXXConstructExpr(CXXConstructExpr *CE) {
if (const auto *Ctor = CE->getConstructor();
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

Comment on lines +145 to +147
- New :doc:`modernize-use-constexpr
<clang-tidy/checks/modernize/use-constexpr>` check that finds functions and
variables that can be declared `constexpr`.
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
- New :doc:`modernize-use-constexpr
<clang-tidy/checks/modernize/use-constexpr>` check that finds functions and
variables that can be declared `constexpr`.
- New :doc:`modernize-use-constexpr
<clang-tidy/checks/modernize/use-constexpr>` check.
Finds functions and variables that can be declared ``constexpr``.

modernize-use-constexpr
=======================

Finds functions and variables that can be declared 'constexpr'.
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
Finds functions and variables that can be declared 'constexpr'.
Finds functions and variables that can be declared '`constexpr'`.

While a function of a class or struct could be declared ``constexpr``, when
the class itself can never be constructed at compile-time, then adding
``constexpr`` to a member function is superfluous. This option controls if
``constexpr`` should be added anyways. Default is ``false``.
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
``constexpr`` should be added anyways. Default is ``false``.
``constexpr`` should be added anyways. Default is `false`.

.. option:: ConstexprString

The string to use to specify a variable or function as ``constexpr``, for
example, a macro. Default is ``constexpr``.
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
example, a macro. Default is ``constexpr``.
example, a macro. Default is `constexpr`.

.. option:: ConstexprString

The string to use with C++23 to specify a function-local variable as
``static constexpr``, for example, a macro. Default is ``static constexpr``
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
``static constexpr``, for example, a macro. Default is ``static constexpr``
``static constexpr``, for example, a macro. Default is `static constexpr`

@seanm
Copy link

seanm commented Jul 2, 2025

Another compiler error after transformation:

It made this constexpr:

	PUGI_IMPL_FN constexpr bool is_nan(double value)
	{
	#if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__)
		return !!_isnan(value);
	#elif defined(fpclassify) && defined(FP_NAN)
		return fpclassify(value) == FP_NAN;
	#else
		// fallback
		const volatile double v = value;
		return v != v;
	#endif
	}

But then:

/Users/sean/external/VTK/ThirdParty/pugixml/vtkpugixml/src/pugixml.cpp:8413:30: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
 8413 |         PUGI_IMPL_FN constexpr bool is_nan(double value)
      |                                     ^~~~~~
/Users/sean/external/VTK/ThirdParty/pugixml/vtkpugixml/src/pugixml.cpp:8422:15: note: read of volatile-qualified type 'const volatile double' is not allowed in a constant expression
 8422 |                 return v != v;
      |                             ^

@seanm
Copy link

seanm commented Jul 2, 2025

Another interesting case, it seems somehow multiple constexprs got added, ex:

template <typename CellType>
constexpr constexpr constexpr constexpr constexpr constexpr constexpr constexpr int numberOfSidesOfDimension(int dimension);

This actually compiles, though with a -Wduplicate-decl-specifier warning.

@5chmidti
Copy link
Contributor Author

5chmidti commented Jul 2, 2025

Another compiler error after transformation:

It made this constexpr:

	PUGI_IMPL_FN constexpr bool is_nan(double value)
	{
	#if defined(PUGI_IMPL_MSVC_CRT_VERSION) || defined(__BORLANDC__)
		return !!_isnan(value);
	#elif defined(fpclassify) && defined(FP_NAN)
		return fpclassify(value) == FP_NAN;
	#else
		// fallback
		const volatile double v = value;
		return v != v;
	#endif
	}

But then:

/Users/sean/external/VTK/ThirdParty/pugixml/vtkpugixml/src/pugixml.cpp:8413:30: error: constexpr function never produces a constant expression [-Winvalid-constexpr]
 8413 |         PUGI_IMPL_FN constexpr bool is_nan(double value)
      |                                     ^~~~~~
/Users/sean/external/VTK/ThirdParty/pugixml/vtkpugixml/src/pugixml.cpp:8422:15: note: read of volatile-qualified type 'const volatile double' is not allowed in a constant expression
 8422 |                 return v != v;
      |                             ^

I'll take a look

@5chmidti
Copy link
Contributor Author

5chmidti commented Jul 2, 2025

Another interesting case, it seems somehow multiple constexprs got added, ex:

template <typename CellType>
constexpr constexpr constexpr constexpr constexpr constexpr constexpr constexpr int numberOfSidesOfDimension(int dimension);

This actually compiles, though with a -Wduplicate-decl-specifier warning.

I failed to reproduce this with

template <typename T>
static T forwardDeclared();

template <typename T>
static T forwardDeclared() { return T{}; }

static void useForwardDeclared() {
    forwardDeclared<int>() + forwardDeclared<double>() + forwardDeclared<char>();
}
 template <typename T>                                                                                                                                        
-static T forwardDeclared();                                
+constexpr static T forwardDeclared();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
 template <typename T>                                                      
-static T forwardDeclared() { return T{}; }
+constexpr static T forwardDeclared() { return T{}; }                                                                                                                                                                                                                                                                       
                                                                  
 static void useForwardDeclared() {                                                                                                                                                                                                                                                                                         
     forwardDeclared<int>() + forwardDeclared<double>() + forwardDeclared<char>();  

I had seen this one as well, but it was already fixed in the initial commit, at least I thought.

@seanm
Copy link

seanm commented Jul 2, 2025

This actually compiles, though with a -Wduplicate-decl-specifier warning.

I failed to reproduce this...

I don't have a minimal repro case, I was building VTK. It's pretty simple to build it yourself if you're familiar with CMake. I could give you exact command line steps if you need...

@seanm
Copy link

seanm commented Jul 2, 2025

I've tried another project: opencv and the result also fails to compile.

One case I think is because two variables are declared together. i.e.:

int foo, bar;

instead of:

int foo;
int bar;

Concretely:

    const char * const borderMap[] = { "BORDER_CONSTANT", "BORDER_REPLICATE", "BORDER_REFLECT", "BORDER_WRAP", "BORDER_REFLECT_101" },
        * const btype = borderMap[borderType & ~BORDER_ISOLATED];

Got changed to:

    constexpr const char * borderMap[] = { "BORDER_CONSTANT", "BORDER_REPLICATE", "BORDER_REFLECT", "BORDER_WRAP", "BORDER_REFLECT_101" },
        * const btype = borderMap[borderType & ~BORDER_ISOLATED];

which is fine for borderMap but not for btype.

Resulting in:

/Users/sean/external/opencv/modules/imgproc/src/filter.dispatch.cpp:757:17: error: constexpr variable 'btype' must be initialized by a constant expression
  757 |         * const btype = borderMap[borderType & ~BORDER_ISOLATED];
      |                 ^       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

@seanm
Copy link

seanm commented Jul 2, 2025

Another interesting case:

static int testToWindowsExtendedPath()
{
#ifdef _WIN32
  // lots of real, non constant work
  return ret;
#else
  return 0;
#endif
}

For some platforms this can be constexpr; for other platforms (Windows), it cannot. It was transformed to constexpr since I'm on Mac. Not sure what you can do there...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[clang-tidy] suggest adding constexpr when it is possible
8 participants