[clang] Don't wrap immediate invocations in ConstantExprs within constexpr initializers (PR #89565)

Daniel M. Katz via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 22 10:28:50 PDT 2024


https://github.com/katzdm updated https://github.com/llvm/llvm-project/pull/89565

>From a3f8a8648e2002273d47d7886b29fb02c728b5b7 Mon Sep 17 00:00:00 2001
From: Dan Katz <katzdm at gmail.com>
Date: Tue, 16 Apr 2024 17:14:50 -0400
Subject: [PATCH 1/5] Fix bug with constexpr initialization.

---
 clang/lib/Parse/ParseDecl.cpp                 | 12 ++++++-
 clang/test/CXX/expr/expr.const/p6-2a.cpp      |  7 ++--
 clang/test/SemaCXX/builtin_vectorelements.cpp |  4 +--
 clang/test/SemaCXX/cxx2a-consteval.cpp        | 32 ++++++++++++-------
 clang/unittests/Support/TimeProfilerTest.cpp  |  1 -
 5 files changed, 36 insertions(+), 20 deletions(-)

diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 583232f2d610d0..0ea6fccaa7eb34 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2554,6 +2554,13 @@ Decl *Parser::ParseDeclarationAfterDeclarator(
   return ParseDeclarationAfterDeclaratorAndAttributes(D, TemplateInfo);
 }
 
+static bool isConstexprVariable(const Decl *D) {
+  if (const VarDecl *Var = dyn_cast_or_null<VarDecl>(D))
+    return Var->isConstexpr();
+
+  return false;
+}
+
 Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
     Declarator &D, const ParsedTemplateInfo &TemplateInfo, ForRangeInit *FRI) {
   // RAII type used to track whether we're inside an initializer.
@@ -2561,9 +2568,12 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
     Parser &P;
     Declarator &D;
     Decl *ThisDecl;
+    llvm::SaveAndRestore<bool> ConstantContext;
 
     InitializerScopeRAII(Parser &P, Declarator &D, Decl *ThisDecl)
-        : P(P), D(D), ThisDecl(ThisDecl) {
+        : P(P), D(D), ThisDecl(ThisDecl),
+          ConstantContext(P.Actions.isConstantEvaluatedOverride,
+                          isConstexprVariable(ThisDecl)) {
       if (ThisDecl && P.getLangOpts().CPlusPlus) {
         Scope *S = nullptr;
         if (D.getCXXScopeSpec().isSet()) {
diff --git a/clang/test/CXX/expr/expr.const/p6-2a.cpp b/clang/test/CXX/expr/expr.const/p6-2a.cpp
index a937474d53b221..8b940b2ee9fb2a 100644
--- a/clang/test/CXX/expr/expr.const/p6-2a.cpp
+++ b/clang/test/CXX/expr/expr.const/p6-2a.cpp
@@ -43,12 +43,11 @@ struct Temporary {
 constexpr Temporary t = {3}; // expected-error {{must have constant destruction}} expected-note {{created here}} expected-note {{in call}}
 
 namespace P1073R3 {
-consteval int f() { return 42; } // expected-note 2 {{declared here}}
+consteval int f() { return 42; } // expected-note {{declared here}}
 consteval auto g() { return f; }
 consteval int h(int (*p)() = g()) { return p(); }
 constexpr int r = h();
-constexpr auto e = g();  // expected-error {{call to consteval function 'P1073R3::g' is not a constant expression}} \
-                            expected-error {{constexpr variable 'e' must be initialized by a constant expression}} \
-                            expected-note 2 {{pointer to a consteval declaration is not a constant expression}}
+constexpr auto e = g();  // expected-error {{constexpr variable 'e' must be initialized by a constant expression}} \
+                            expected-note {{pointer to a consteval declaration is not a constant expression}}
 static_assert(r == 42);
 } // namespace P1073R3
diff --git a/clang/test/SemaCXX/builtin_vectorelements.cpp b/clang/test/SemaCXX/builtin_vectorelements.cpp
index 59ff09ac72e42d..12f2cbe57bd47d 100644
--- a/clang/test/SemaCXX/builtin_vectorelements.cpp
+++ b/clang/test/SemaCXX/builtin_vectorelements.cpp
@@ -42,11 +42,11 @@ void test_builtin_vectorelements() {
 #include <arm_sve.h>
 
 consteval int consteval_elements() { // expected-error {{consteval function never produces a constant expression}}
-  return __builtin_vectorelements(svuint64_t); // expected-note {{cannot determine number of elements for sizeless vectors in a constant expression}}  // expected-note {{cannot determine number of elements for sizeless vectors in a constant expression}} // expected-note {{cannot determine number of elements for sizeless vectors in a constant expression}}
+  return __builtin_vectorelements(svuint64_t); // expected-note {{cannot determine number of elements for sizeless vectors in a constant expression}} // expected-note {{cannot determine number of elements for sizeless vectors in a constant expression}}
 }
 
 void test_bad_constexpr() {
-  constexpr int eval = consteval_elements(); // expected-error {{initialized by a constant expression}} // expected-error {{not a constant expression}} // expected-note {{in call}} // expected-note {{in call}}
+  constexpr int eval = consteval_elements(); // expected-error {{initialized by a constant expression}} // expected-note {{in call}}
   constexpr int i32 = __builtin_vectorelements(svuint32_t); // expected-error {{initialized by a constant expression}} // expected-note {{cannot determine number of elements for sizeless vectors in a constant expression}}
   constexpr int i16p8 = __builtin_vectorelements(svuint16_t) + 16; // expected-error {{initialized by a constant expression}} // expected-note {{cannot determine number of elements for sizeless vectors in a constant expression}}
   constexpr int lambda = [] { return __builtin_vectorelements(svuint16_t); }(); // expected-error {{initialized by a constant expression}} // expected-note {{cannot determine number of elements for sizeless vectors in a constant expression}} // expected-note {{in call}}
diff --git a/clang/test/SemaCXX/cxx2a-consteval.cpp b/clang/test/SemaCXX/cxx2a-consteval.cpp
index 192621225a543c..d1f3b094f8afd5 100644
--- a/clang/test/SemaCXX/cxx2a-consteval.cpp
+++ b/clang/test/SemaCXX/cxx2a-consteval.cpp
@@ -260,6 +260,18 @@ int(*test)(int)  = l1;
 
 }
 
+namespace lvalue_to_rvalue_init_from_heap {
+
+struct S {
+    int *value;
+    constexpr S(int v) : value(new int {v}) {}
+    constexpr ~S() { delete value; }
+};
+consteval S fn() { return S(5); }
+
+constexpr int a = *fn().value;
+}
+
 namespace std {
 
 template <typename T> struct remove_reference { using type = T; };
@@ -713,7 +725,7 @@ constexpr derp d;
 struct test {
   consteval int operator[](int i) const { return {}; }
   consteval const derp * operator->() const { return &d; }
-  consteval int f() const { return 12; } // expected-note 2{{declared here}}
+  consteval int f() const { return 12; } // expected-note {{declared here}}
 };
 
 constexpr test a;
@@ -726,8 +738,7 @@ constexpr int s = a.operator[](1);
 constexpr int t = a[1];
 constexpr int u = a.operator->()->b;
 constexpr int v = a->b;
-// FIXME: I believe this case should work, but we currently reject.
-constexpr int w = (a.*&test::f)(); // expected-error {{cannot take address of consteval function 'f' outside of an immediate invocation}}
+constexpr int w = (a.*&test::f)();
 constexpr int x = a.f();
 
 // Show that we reject when not in an immediate context.
@@ -1032,17 +1043,15 @@ int f() {
 
 namespace GH57682 {
 void test() {
-  constexpr auto l1 = []() consteval { // expected-error {{cannot take address of consteval call operator of '(lambda at}} \
-                                       // expected-note  2{{declared here}}
+  constexpr auto l1 = []() consteval { // expected-note  {{declared here}}
         return 3;
   };
   constexpr int (*f1)(void) = l1; // expected-error {{constexpr variable 'f1' must be initialized by a constant expression}} \
                                   // expected-note  {{pointer to a consteval declaration is not a constant expression}}
 
 
-  constexpr auto lstatic = []() static consteval { // expected-error {{cannot take address of consteval call operator of '(lambda at}} \
-                                       // expected-note  2{{declared here}} \
-                                       // expected-warning {{extension}}
+  constexpr auto lstatic = []() static consteval { // expected-note  {{declared here}} \
+                                                   // expected-warning {{extension}}
         return 3;
   };
   constexpr int (*f2)(void) = lstatic; // expected-error {{constexpr variable 'f2' must be initialized by a constant expression}} \
@@ -1073,7 +1082,7 @@ struct tester {
 consteval const char* make_name(const char* name) { return name;}
 consteval const char* pad(int P) { return "thestring"; }
 
-int bad = 10; // expected-note 6{{declared here}}
+int bad = 10; // expected-note 5{{declared here}}
 
 tester glob1(make_name("glob1"));
 tester glob2(make_name("glob2"));
@@ -1082,9 +1091,8 @@ tester paddedglob(make_name(pad(bad))); // expected-error {{call to consteval fu
                                         // expected-note {{read of non-const variable 'bad' is not allowed in a constant expression}}
 
 constexpr tester glob3 = { make_name("glob3") };
-constexpr tester glob4 = { make_name(pad(bad)) }; // expected-error {{call to consteval function 'GH58207::make_name' is not a constant expression}} \
-                                                  // expected-error {{constexpr variable 'glob4' must be initialized by a constant expression}} \
-                                                  // expected-note 2{{read of non-const variable 'bad' is not allowed in a constant expression}}
+constexpr tester glob4 = { make_name(pad(bad)) }; // expected-error {{constexpr variable 'glob4' must be initialized by a constant expression}} \
+                                                  // expected-note 1{{read of non-const variable 'bad' is not allowed in a constant expression}}
 
 auto V = make_name(pad(3));
 auto V1 = make_name(pad(bad)); // expected-error {{call to consteval function 'GH58207::make_name' is not a constant expression}} \
diff --git a/clang/unittests/Support/TimeProfilerTest.cpp b/clang/unittests/Support/TimeProfilerTest.cpp
index 97fdbb7232b135..9c1a955743f1ee 100644
--- a/clang/unittests/Support/TimeProfilerTest.cpp
+++ b/clang/unittests/Support/TimeProfilerTest.cpp
@@ -193,7 +193,6 @@ Frontend
 | ParseDeclarationOrFunctionDefinition (test.cc:16:1)
 | | ParseFunctionDefinition (slow_test)
 | | | EvaluateAsInitializer (slow_value)
-| | | EvaluateAsConstantExpr (<test.cc:17:33, col:59>)
 | | | EvaluateAsConstantExpr (<test.cc:18:11, col:37>)
 | ParseDeclarationOrFunctionDefinition (test.cc:22:1)
 | | EvaluateAsConstantExpr (<test.cc:23:31, col:57>)

>From c9eaffe0538dd9435fe6824253fd9bef0b1eff3b Mon Sep 17 00:00:00 2001
From: "Daniel M. Katz" <katzdm at gmail.com>
Date: Mon, 22 Apr 2024 08:59:55 -0400
Subject: [PATCH 2/5] Use dyn_cast without checking for null

Co-authored-by: cor3ntin <corentinjabot at gmail.com>
---
 clang/lib/Parse/ParseDecl.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index 0ea6fccaa7eb34..c8c876bf1fffea 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2555,7 +2555,7 @@ Decl *Parser::ParseDeclarationAfterDeclarator(
 }
 
 static bool isConstexprVariable(const Decl *D) {
-  if (const VarDecl *Var = dyn_cast_or_null<VarDecl>(D))
+  if (const VarDecl *Var = dyn_cast<VarDecl>(D))
     return Var->isConstexpr();
 
   return false;

>From 3b7187244bf8ab6be7a80d79e1abc92458bd2bcd Mon Sep 17 00:00:00 2001
From: Dan Katz <katzdm at gmail.com>
Date: Mon, 22 Apr 2024 09:01:48 -0400
Subject: [PATCH 3/5] Add release notes.

---
 clang/docs/ReleaseNotes.rst | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 45a9a79739a4eb..0dd9896cbb3cd0 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -536,6 +536,8 @@ Bug Fixes to C++ Support
 - Clang now correctly tracks type dependence of by-value captures in lambdas with an explicit
   object parameter.
   Fixes (#GH70604), (#GH79754), (#GH84163), (#GH84425), (#GH86054), (#GH86398), and (#GH86399).
+- Fix a bug in which valid constexpr variable initializers containing immediate function calls were
+  sometimes diagnosed as not constant expressions.
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^

>From 41fe69c5ac581c8ae325f2a13a9157fbd9c2d9a7 Mon Sep 17 00:00:00 2001
From: Dan Katz <katzdm at gmail.com>
Date: Mon, 22 Apr 2024 12:38:11 -0400
Subject: [PATCH 4/5] Re-introduce null check. Use EvaluationContext instead of
 override.

---
 clang/lib/Parse/ParseDecl.cpp | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp
index c8c876bf1fffea..1d92a8c57f16ec 100644
--- a/clang/lib/Parse/ParseDecl.cpp
+++ b/clang/lib/Parse/ParseDecl.cpp
@@ -2555,7 +2555,7 @@ Decl *Parser::ParseDeclarationAfterDeclarator(
 }
 
 static bool isConstexprVariable(const Decl *D) {
-  if (const VarDecl *Var = dyn_cast<VarDecl>(D))
+  if (const VarDecl *Var = dyn_cast_if_present<VarDecl>(D))
     return Var->isConstexpr();
 
   return false;
@@ -2568,12 +2568,13 @@ Decl *Parser::ParseDeclarationAfterDeclaratorAndAttributes(
     Parser &P;
     Declarator &D;
     Decl *ThisDecl;
-    llvm::SaveAndRestore<bool> ConstantContext;
+    EnterExpressionEvaluationContext EC;
 
     InitializerScopeRAII(Parser &P, Declarator &D, Decl *ThisDecl)
         : P(P), D(D), ThisDecl(ThisDecl),
-          ConstantContext(P.Actions.isConstantEvaluatedOverride,
-                          isConstexprVariable(ThisDecl)) {
+          EC(P.Actions, Sema::ExpressionEvaluationContext::ConstantEvaluated,
+             ThisDecl, Sema::ExpressionEvaluationContextRecord::EK_Other,
+             isConstexprVariable(ThisDecl)) {
       if (ThisDecl && P.getLangOpts().CPlusPlus) {
         Scope *S = nullptr;
         if (D.getCXXScopeSpec().isSet()) {

>From cf4ecf035cc82fedceecc9c31e35f81722b867a4 Mon Sep 17 00:00:00 2001
From: Dan Katz <katzdm at gmail.com>
Date: Mon, 22 Apr 2024 13:28:03 -0400
Subject: [PATCH 5/5] Adjust other tests newly failing from change.

---
 clang/test/AST/Interp/intap.cpp                  |  3 +--
 clang/test/CXX/expr/expr.const/p2-0x.cpp         |  4 ++--
 clang/test/Parser/pragma-fenv_access.c           |  3 ---
 clang/test/SemaCXX/constant-expression-cxx11.cpp |  2 +-
 clang/test/SemaCXX/cxx2a-consteval.cpp           | 14 ++++++++------
 clang/test/SemaCXX/ext-int.cpp                   |  2 +-
 6 files changed, 13 insertions(+), 15 deletions(-)

diff --git a/clang/test/AST/Interp/intap.cpp b/clang/test/AST/Interp/intap.cpp
index d4440124856915..1b8ec2dfdab79e 100644
--- a/clang/test/AST/Interp/intap.cpp
+++ b/clang/test/AST/Interp/intap.cpp
@@ -9,8 +9,7 @@ using MaxBitInt = _BitInt(128);
 
 constexpr _BitInt(2) A = 0;
 constexpr _BitInt(2) B = A + 1;
-constexpr _BitInt(2) C = B + 1; // expected-warning {{from 2 to -2}} \
-                                // ref-warning {{from 2 to -2}}
+constexpr _BitInt(2) C = B + 1;
 static_assert(C == -2, "");
 static_assert(C - B == A, ""); // expected-error {{not an integral constant expression}} \
                                // expected-note {{value -3 is outside the range of representable values}} \
diff --git a/clang/test/CXX/expr/expr.const/p2-0x.cpp b/clang/test/CXX/expr/expr.const/p2-0x.cpp
index e3cd057baba75f..86b9b066d6867f 100644
--- a/clang/test/CXX/expr/expr.const/p2-0x.cpp
+++ b/clang/test/CXX/expr/expr.const/p2-0x.cpp
@@ -244,8 +244,8 @@ namespace UndefinedBehavior {
     constexpr int n13 = n5 + n5; // expected-error {{constant expression}} expected-note {{value -4294967296 is outside the range of }}
     constexpr int n14 = n3 - n5; // expected-error {{constant expression}} expected-note {{value 4294967295 is outside the range of }}
     constexpr int n15 = n5 * n5; // expected-error {{constant expression}} expected-note {{value 4611686018427387904 is outside the range of }}
-    constexpr signed char c1 = 100 * 2; // ok expected-warning{{changes value}}
-    constexpr signed char c2 = '\x64' * '\2'; // also ok  expected-warning{{changes value}}
+    constexpr signed char c1 = 100 * 2; // ok - no error from changing value because initializer is constexpr.
+    constexpr signed char c2 = '\x64' * '\2'; // also ok - no error from changing value because initializer is constexpr.
     constexpr long long ll1 = 0x7fffffffffffffff; // ok
     constexpr long long ll2 = ll1 + 1; // expected-error {{constant}} expected-note {{ 9223372036854775808 }}
     constexpr long long ll3 = -ll1 - 1; // ok
diff --git a/clang/test/Parser/pragma-fenv_access.c b/clang/test/Parser/pragma-fenv_access.c
index 76256cff1b49b5..86d40bbab4be93 100644
--- a/clang/test/Parser/pragma-fenv_access.c
+++ b/clang/test/Parser/pragma-fenv_access.c
@@ -32,9 +32,6 @@ int main(void) {
   CONST int not_too_big = 255;
   CONST float fnot_too_big = not_too_big;
   CONST int too_big = 0x7ffffff0;
-#if defined(CPP)
-//expected-warning at +2{{implicit conversion}}
-#endif
   CONST float fbig = too_big; // inexact
 #if !defined(CPP)
 #define static_assert _Static_assert
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index efb391ba0922d8..815d2e1651d4ac 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1961,7 +1961,7 @@ namespace ConstexprConstructorRecovery {
 
 namespace Lifetime {
   void f() {
-    constexpr int &n = n; // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime}} expected-warning {{not yet bound to a value}}
+    constexpr int &n = n; // expected-error {{constant expression}} expected-note {{use of reference outside its lifetime}}
     constexpr int m = m; // expected-error {{constant expression}} expected-note {{read of object outside its lifetime}}
   }
 
diff --git a/clang/test/SemaCXX/cxx2a-consteval.cpp b/clang/test/SemaCXX/cxx2a-consteval.cpp
index d1f3b094f8afd5..bfe27547b67cc2 100644
--- a/clang/test/SemaCXX/cxx2a-consteval.cpp
+++ b/clang/test/SemaCXX/cxx2a-consteval.cpp
@@ -1043,21 +1043,23 @@ int f() {
 
 namespace GH57682 {
 void test() {
-  constexpr auto l1 = []() consteval { // expected-note  {{declared here}}
+  constexpr auto l1 = []() consteval { // expected-error {{cannot take address of consteval call operator of '(lambda at}} \
+                                       // expected-note 2 {{declared here}}
         return 3;
   };
-  constexpr int (*f1)(void) = l1; // expected-error {{constexpr variable 'f1' must be initialized by a constant expression}} \
-                                  // expected-note  {{pointer to a consteval declaration is not a constant expression}}
 
+  int (*f1)(void) = l1;
+
+  constexpr int (*f2)(void) = l1; // expected-error {{constexpr variable 'f2' must be initialized by a constant expression}} \
+                                  // expected-note  {{pointer to a consteval declaration is not a constant expression}}
 
   constexpr auto lstatic = []() static consteval { // expected-note  {{declared here}} \
                                                    // expected-warning {{extension}}
         return 3;
   };
-  constexpr int (*f2)(void) = lstatic; // expected-error {{constexpr variable 'f2' must be initialized by a constant expression}} \
+  constexpr int (*f3)(void) = lstatic; // expected-error {{constexpr variable 'f3' must be initialized by a constant expression}} \
                                        // expected-note  {{pointer to a consteval declaration is not a constant expression}}
-
-}
+  };
 }
 
 namespace GH60286 {
diff --git a/clang/test/SemaCXX/ext-int.cpp b/clang/test/SemaCXX/ext-int.cpp
index 000b871ccd3433..931842b7e3c53b 100644
--- a/clang/test/SemaCXX/ext-int.cpp
+++ b/clang/test/SemaCXX/ext-int.cpp
@@ -25,7 +25,7 @@ _BitInt(33) Declarations(_BitInt(48) &Param) { // Useable in params and returns.
   unsigned _BitInt(1) l;
   signed _BitInt(1) m; // expected-error{{signed _BitInt must have a bit size of at least 2}}
 
-  constexpr _BitInt(6) n = 33; // expected-warning{{implicit conversion from 'int' to 'const _BitInt(6)' changes value from 33 to -31}}
+  constexpr _BitInt(6) n = 33;
   constexpr _BitInt(7) o = 33;
 
   // Check imposed max size.



More information about the cfe-commits mailing list