Skip to content

Conversation

shafik
Copy link
Collaborator

@shafik shafik commented Oct 16, 2025

When this diagnostic was implemented it fired for all non-static data members. I added checking so that it only fires for constructors and destructors before C++26 (which now allows this). Some special care was required for the destructor case since constexpr constuctors were not allowed in C++11. Modified existing test to cover the new conditions.

Also filed bug report on bad diagnostic found while writing the tests: #163683

Fixes: #97266

…ly diagnoses on constructors and destructors

When this diagnostic was implemented it fired for all non-static data members.
I added checking so that it only fires for contructors and destructors before
C++26 (which now allows this). Some special care was required for the destructor
case since constexpr constuctors were not allowed in C++11. Modified existing
test to cover the new conditions.

Fixes: llvm#97266
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 16, 2025
@llvmbot
Copy link
Member

llvmbot commented Oct 16, 2025

@llvm/pr-subscribers-clang

Author: Shafik Yaghmour (shafik)

Changes

When this diagnostic was implemented it fired for all non-static data members. I added checking so that it only fires for constructors and destructors before C++26 (which now allows this). Some special care was required for the destructor case since constexpr constuctors were not allowed in C++11. Modified existing test to cover the new conditions.

Also filed bug report on bad diagnostic found while writing the tests: #163683

Fixes: #97266


Full diff: https://github.com/llvm/llvm-project/pull/163690.diff

3 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+1-1)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+6-4)
  • (modified) clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp (+8-2)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 40bc7b9a4e45e..0eae2b76ff9b9 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3026,7 +3026,7 @@ def warn_cxx17_compat_constexpr_virtual : Warning<
   "virtual constexpr functions are incompatible with "
   "C++ standards before C++20">, InGroup<CXXPre20Compat>, DefaultIgnore;
 def err_constexpr_virtual_base : Error<
-  "constexpr %select{member function|constructor}0 not allowed in "
+  "constexpr %select{destructor|constructor}0 not allowed in "
   "%select{struct|interface|class}1 with virtual base "
   "%plural{1:class|:classes}2">;
 def note_non_literal_incomplete : Note<
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index d41ab126c426f..da94b0070dfcb 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -1918,8 +1918,10 @@ static bool CheckConstexprMissingReturn(Sema &SemaRef, const FunctionDecl *Dcl);
 
 bool Sema::CheckConstexprFunctionDefinition(const FunctionDecl *NewFD,
                                             CheckConstexprKind Kind) {
-  const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(NewFD);
-  if (MD && MD->isInstance()) {
+  if ((!getLangOpts().CPlusPlus26 && isa<CXXConstructorDecl>(NewFD)) ||
+      ((getLangOpts().CPlusPlus20 && !getLangOpts().CPlusPlus26) &&
+       isa<CXXDestructorDecl>(NewFD))) {
+    const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(NewFD);
     // C++11 [dcl.constexpr]p4:
     //  The definition of a constexpr constructor shall satisfy the following
     //  constraints:
@@ -1933,8 +1935,8 @@ bool Sema::CheckConstexprFunctionDefinition(const FunctionDecl *NewFD,
         return false;
 
       Diag(NewFD->getLocation(), diag::err_constexpr_virtual_base)
-        << isa<CXXConstructorDecl>(NewFD)
-        << getRecordDiagFromTagKind(RD->getTagKind()) << RD->getNumVBases();
+          << isa<CXXConstructorDecl>(NewFD)
+          << getRecordDiagFromTagKind(RD->getTagKind()) << RD->getNumVBases();
       for (const auto &I : RD->vbases())
         Diag(I.getBeginLoc(), diag::note_constexpr_virtual_base_here)
             << I.getSourceRange();
diff --git a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp
index 51990ee4341d2..5a06d686b4c0f 100644
--- a/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.spec/dcl.constexpr/p3.cpp
@@ -84,10 +84,16 @@ struct T3 {
 struct U {
   constexpr U SelfReturn() const;
   constexpr int SelfParam(U) const;
+  constexpr ~U();  // beforecxx20-error {{destructor cannot be declared constexpr}}
 };
 
-struct V : virtual U { // expected-note {{here}}
-  constexpr int F() const { return 0; } // expected-error {{constexpr member function not allowed in struct with virtual base class}}
+struct V : virtual U { // expected-note {{here}} //aftercxx20-note {{here}}
+  constexpr V() {}  // expected-error {{constexpr constructor not allowed in struct with virtual base class}}
+  constexpr ~V() {} // aftercxx20-error {{constexpr destructor not allowed in struct with virtual base class}}
+                    // beforecxx20-error@-1 {{destructor cannot be declared constexpr}}
+                    // beforecxx14-error@-2 {{constexpr function's return type 'void' is not a literal type}}
+		    // ^ FIXME this is just a bad diagnostic
+  constexpr int f() const { return 0; }
 };
 
 //  or a compound-statememt that contains only [CXX11]

Comment on lines 1921 to 1924
if ((!getLangOpts().CPlusPlus26 && isa<CXXConstructorDecl>(NewFD)) ||
((getLangOpts().CPlusPlus20 && !getLangOpts().CPlusPlus26) &&
isa<CXXDestructorDecl>(NewFD))) {
const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(NewFD);
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels like you are trying to partially implement https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3533r2.html as a drive-by?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That is not really my intent but leaving it enabled for C++26 is also not correct. I can leave it enabled for C++26 but it is such a trivial addition it seems silly not to. If the objections are strong I can take out the C++26 check.

Copy link
Contributor

@Fznamznon Fznamznon left a comment

Choose a reason for hiding this comment

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

Needs a release note

const CXXMethodDecl *MD = dyn_cast<CXXMethodDecl>(NewFD);
if (MD && MD->isInstance()) {
if ((!getLangOpts().CPlusPlus26 && isa<CXXConstructorDecl>(NewFD)) ||
((getLangOpts().CPlusPlus20 && !getLangOpts().CPlusPlus26) &&
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to check for C++20? I think some other function will diagnose that destructor can't be constexpr.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We don't have explicit constexpr destructor before C++20 and so this would be a spurious diagnostic.

Copy link
Contributor

@Fznamznon Fznamznon left a comment

Choose a reason for hiding this comment

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

LGTM. @cor3ntin ?

@cor3ntin
Copy link
Contributor

if we are doing something special for C++26, I'd like to see tests for it.

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

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clang rejects regular constexpr member function in struct with virtual base class

4 participants