Skip to content

[clang][ExprConst] Allow non-literal types in C++23 #100062

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

Merged
merged 2 commits into from
Jul 24, 2024

Conversation

tbaederr
Copy link
Contributor

Instead of diagnosing non-literal types in C++23, allow them and later diagnose them differently, e.g. because they have a non-constexpr constructor, destructor, etc.

For this test:

struct NonLiteral {
  NonLiteral() {}
};
constexpr int foo() {
  NonLiteral L;
  return 1;
}

// static_assert(foo() == 1);

The current diagnostics with c++20/c++23 are:

~/code/llvm-project/build » clang -c array.cpp -std=c++20
array.cpp:91:14: error: variable of non-literal type 'NonLiteral' cannot be defined in a constexpr function before C++23
   91 |   NonLiteral L;
      |              ^
array.cpp:87:8: note: 'NonLiteral' is not literal because it is not an aggregate and has no constexpr constructors other than copy or move constructors
   87 | struct NonLiteral {
      |        ^
1 error generated.
------------------------------------------------------------
~/code/llvm-project/build » clang -c array.cpp -std=c++23
(no output)

With the static_assert enabled, compiling with -std=c++23 prints:

array.cpp:95:15: error: static assertion expression is not an integral constant expression
   95 | static_assert(foo() == 1);
      |               ^~~~~~~~~~
array.cpp:91:14: note: non-literal type 'NonLiteral' cannot be used in a constant expression
   91 |   NonLiteral L;
      |              ^
array.cpp:95:15: note: in call to 'foo()'
   95 | static_assert(foo() == 1);
      |               ^~~~~
1 error generated.

As mentioned in #60311, this is confusing. The output with c++20 suggests that using c++23 will make the problem go away, but it's diagnosed the same when running the function.

With this commit, the output instead diagnoses why the non-literal type can't be used:

array.cpp:95:15: error: static assertion expression is not an integral constant expression
   95 | static_assert(foo() == 1);
      |               ^~~~~~~~~~
array.cpp:91:14: note: non-constexpr constructor 'NonLiteral' cannot be used in a constant expression
   91 |   NonLiteral L;
      |              ^
array.cpp:95:15: note: in call to 'foo()'
   95 | static_assert(foo() == 1);
      |               ^~~~~
array.cpp:88:3: note: declared here
   88 |   NonLiteral() {}
      |   ^
1 error generated.

Fixes #60311

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 23, 2024
@llvmbot
Copy link
Member

llvmbot commented Jul 23, 2024

@llvm/pr-subscribers-clang

Author: Timm Baeder (tbaederr)

Changes

Instead of diagnosing non-literal types in C++23, allow them and later diagnose them differently, e.g. because they have a non-constexpr constructor, destructor, etc.

For this test:

struct NonLiteral {
  NonLiteral() {}
};
constexpr int foo() {
  NonLiteral L;
  return 1;
}

// static_assert(foo() == 1);

The current diagnostics with c++20/c++23 are:

~/code/llvm-project/build » clang -c array.cpp -std=c++20
array.cpp:91:14: error: variable of non-literal type 'NonLiteral' cannot be defined in a constexpr function before C++23
   91 |   NonLiteral L;
      |              ^
array.cpp:87:8: note: 'NonLiteral' is not literal because it is not an aggregate and has no constexpr constructors other than copy or move constructors
   87 | struct NonLiteral {
      |        ^
1 error generated.
------------------------------------------------------------
~/code/llvm-project/build » clang -c array.cpp -std=c++23
(no output)

With the static_assert enabled, compiling with -std=c++23 prints:

array.cpp:95:15: error: static assertion expression is not an integral constant expression
   95 | static_assert(foo() == 1);
      |               ^~~~~~~~~~
array.cpp:91:14: note: non-literal type 'NonLiteral' cannot be used in a constant expression
   91 |   NonLiteral L;
      |              ^
array.cpp:95:15: note: in call to 'foo()'
   95 | static_assert(foo() == 1);
      |               ^~~~~
1 error generated.

As mentioned in #60311, this is confusing. The output with c++20 suggests that using c++23 will make the problem go away, but it's diagnosed the same when running the function.

With this commit, the output instead diagnoses why the non-literal type can't be used:

array.cpp:95:15: error: static assertion expression is not an integral constant expression
   95 | static_assert(foo() == 1);
      |               ^~~~~~~~~~
array.cpp:91:14: note: non-constexpr constructor 'NonLiteral' cannot be used in a constant expression
   91 |   NonLiteral L;
      |              ^
array.cpp:95:15: note: in call to 'foo()'
   95 | static_assert(foo() == 1);
      |               ^~~~~
array.cpp:88:3: note: declared here
   88 |   NonLiteral() {}
      |   ^
1 error generated.

Fixes #60311


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

5 Files Affected:

  • (modified) clang/lib/AST/ExprConstant.cpp (+3)
  • (modified) clang/test/CXX/drs/cwg18xx.cpp (+8-4)
  • (modified) clang/test/SemaCXX/constant-expression-cxx11.cpp (+11-7)
  • (modified) clang/test/SemaCXX/constant-expression-cxx2b.cpp (+5-5)
  • (modified) clang/test/SemaCXX/cxx23-invalid-constexpr.cpp (+6-7)
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 5af712dd7257b..a402e6f204aaf 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2404,6 +2404,9 @@ static bool CheckLiteralType(EvalInfo &Info, const Expr *E,
   if (!E->isPRValue() || E->getType()->isLiteralType(Info.Ctx))
     return true;
 
+  if (Info.getLangOpts().CPlusPlus23)
+    return true;
+
   // C++1y: A constant initializer for an object o [...] may also invoke
   // constexpr constructors for o and its subobjects even if those objects
   // are of non-literal class types.
diff --git a/clang/test/CXX/drs/cwg18xx.cpp b/clang/test/CXX/drs/cwg18xx.cpp
index 323e56f9c5278..adfdb738e81c9 100644
--- a/clang/test/CXX/drs/cwg18xx.cpp
+++ b/clang/test/CXX/drs/cwg18xx.cpp
@@ -3,8 +3,8 @@
 // RUN: %clang_cc1 -std=c++14 -triple x86_64-unknown-unknown %s -verify=expected,since-cxx14,cxx98-14,cxx11-17,since-cxx11,since-cxx14 -fexceptions -Wno-deprecated-builtins -fcxx-exceptions -pedantic-errors
 // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-unknown %s -verify=expected,since-cxx14,since-cxx17,cxx11-17,since-cxx11,since-cxx14,cxx17 -fexceptions -Wno-deprecated-builtins -fcxx-exceptions -pedantic-errors
 // RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-unknown %s -verify=expected,since-cxx14,since-cxx17,since-cxx20,since-cxx11,since-cxx14 -fexceptions -Wno-deprecated-builtins -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-unknown %s -verify=expected,since-cxx14,since-cxx17,since-cxx20,since-cxx11,since-cxx14 -fexceptions -Wno-deprecated-builtins -fcxx-exceptions -pedantic-errors
-// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown %s -verify=expected,since-cxx14,since-cxx17,since-cxx20,since-cxx11,since-cxx14 -fexceptions -Wno-deprecated-builtins -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++23 -triple x86_64-unknown-unknown %s -verify=expected,since-cxx14,since-cxx17,since-cxx20,since-cxx23,since-cxx11,since-cxx14 -fexceptions -Wno-deprecated-builtins -fcxx-exceptions -pedantic-errors
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown %s -verify=expected,since-cxx14,since-cxx17,since-cxx20,since-cxx23,since-cxx11,since-cxx14 -fexceptions -Wno-deprecated-builtins -fcxx-exceptions -pedantic-errors
 
 #if __cplusplus == 199711L
 #define static_assert(...) __extension__ _Static_assert(__VA_ARGS__)
@@ -480,8 +480,12 @@ namespace cwg1872 { // cwg1872: 9
   static_assert(y == 0);
 #endif
   constexpr int z = A<Z>().f();
-  // since-cxx11-error@-1 {{constexpr variable 'z' must be initialized by a constant expression}}
-  //   since-cxx11-note@-2 {{non-literal type 'A<Z>' cannot be used in a constant expression}}
+  // since-cxx11-error@-1 {{constexpr variable 'z' must be initialized by a constant expression}}a
+#if __cplusplus < 202302L
+  //   since-cxx11-note@-3 {{non-literal type 'A<Z>' cannot be used in a constant expression}}
+#else
+  //   since-cxx23-note@-5 {{cannot construct object of type 'A<cwg1872::Z>' with virtual base class in a constant expression}}
+#endif
 #endif
 }
 
diff --git a/clang/test/SemaCXX/constant-expression-cxx11.cpp b/clang/test/SemaCXX/constant-expression-cxx11.cpp
index efb391ba0922d..6df8a4740d6cc 100644
--- a/clang/test/SemaCXX/constant-expression-cxx11.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx11.cpp
@@ -1,6 +1,6 @@
-// RUN: %clang_cc1 -std=c++23 -isystem %S/Inputs -fsyntax-only -verify=expected,cxx20_23,cxx23    -triple x86_64-linux -Wno-string-plus-int -Wno-pointer-arith -Wno-zero-length-array -Wno-c99-designator -fcxx-exceptions -pedantic %s -Wno-comment -Wno-tautological-pointer-compare -Wno-bool-conversion
-// RUN: %clang_cc1 -std=c++20 -isystem %S/Inputs -fsyntax-only -verify=expected,cxx11_20,cxx20_23 -triple x86_64-linux -Wno-string-plus-int -Wno-pointer-arith -Wno-zero-length-array -Wno-c99-designator -fcxx-exceptions -pedantic %s -Wno-comment -Wno-tautological-pointer-compare -Wno-bool-conversion
-// RUN: %clang_cc1 -std=c++11 -isystem %S/Inputs -fsyntax-only -verify=expected,cxx11_20,cxx11    -triple x86_64-linux -Wno-string-plus-int -Wno-pointer-arith -Wno-zero-length-array -Wno-c99-designator -fcxx-exceptions -pedantic %s -Wno-comment -Wno-tautological-pointer-compare -Wno-bool-conversion
+// RUN: %clang_cc1 -std=c++23 -isystem %S/Inputs -fsyntax-only -verify=expected,cxx20_23,cxx23              -triple x86_64-linux -Wno-string-plus-int -Wno-pointer-arith -Wno-zero-length-array -Wno-c99-designator -fcxx-exceptions -pedantic %s -Wno-comment -Wno-tautological-pointer-compare -Wno-bool-conversion
+// RUN: %clang_cc1 -std=c++20 -isystem %S/Inputs -fsyntax-only -verify=expected,cxx11_20,cxx20_23,pre-cxx23 -triple x86_64-linux -Wno-string-plus-int -Wno-pointer-arith -Wno-zero-length-array -Wno-c99-designator -fcxx-exceptions -pedantic %s -Wno-comment -Wno-tautological-pointer-compare -Wno-bool-conversion
+// RUN: %clang_cc1 -std=c++11 -isystem %S/Inputs -fsyntax-only -verify=expected,cxx11_20,cxx11,pre-cxx23    -triple x86_64-linux -Wno-string-plus-int -Wno-pointer-arith -Wno-zero-length-array -Wno-c99-designator -fcxx-exceptions -pedantic %s -Wno-comment -Wno-tautological-pointer-compare -Wno-bool-conversion
 
 namespace StaticAssertFoldTest {
 
@@ -1011,10 +1011,12 @@ constexpr bool b(int n) { return &n; }
 static_assert(b(0), "");
 
 struct NonLiteral {
-  NonLiteral();
+  NonLiteral(); // cxx23-note {{declared here}}
   int f();
 };
-constexpr int k = NonLiteral().f(); // expected-error {{constant expression}} expected-note {{non-literal type 'NonLiteral'}}
+constexpr int k = NonLiteral().f(); // expected-error {{constant expression}} \
+				    // pre-cxx23-note {{non-literal type 'NonLiteral'}} \
+				    // cxx23-note {{non-constexpr constructor 'NonLiteral' cannot be used in a constant expression}}
 
 }
 
@@ -1270,8 +1272,10 @@ static_assert(makeComplexWrap(1,0) != complex(0, 1), "");
 
 namespace PR11595 {
   struct A { constexpr bool operator==(int x) const { return true; } };
-  struct B { B(); A& x; };
-  static_assert(B().x == 3, "");  // expected-error {{constant expression}} expected-note {{non-literal type 'B' cannot be used in a constant expression}}
+  struct B { B(); A& x; }; // cxx23-note {{declared here}}
+  static_assert(B().x == 3, "");  // expected-error {{constant expression}} \
+				  // pre-cxx23-note {{non-literal type 'B' cannot be used in a constant expression}} \
+				  // cxx23-note {{non-constexpr constructor 'B' cannot be used in a constant expression}}
 
   constexpr bool f(int k) { // cxx11_20-error {{constexpr function never produces a constant expression}}
     return B().x == k; // cxx11_20-note {{non-literal type 'B' cannot be used in a constant expression}}
diff --git a/clang/test/SemaCXX/constant-expression-cxx2b.cpp b/clang/test/SemaCXX/constant-expression-cxx2b.cpp
index 2519839b7ac57..42b3f81cc713d 100644
--- a/clang/test/SemaCXX/constant-expression-cxx2b.cpp
+++ b/clang/test/SemaCXX/constant-expression-cxx2b.cpp
@@ -3,7 +3,7 @@
 
 struct NonLiteral { // cxx2a-note {{'NonLiteral' is not literal}} \
                     // cxx23-note 2{{'NonLiteral' is not literal}}
-  NonLiteral() {}
+  NonLiteral() {} // cxx23-note 2{{declared here}}
 };
 
 struct Constexpr{};
@@ -165,9 +165,9 @@ int test_in_lambdas() {
 
   auto non_literal = [](bool b) constexpr {
     if (!b)
-      NonLiteral n; // cxx23-note {{non-literal type 'NonLiteral' cannot be used in a constant expression}} \
-                    // cxx2a-error {{variable of non-literal type 'NonLiteral' cannot be defined in a constexpr function before C++23}} \
-                    // cxx23-warning {{definition of a variable of non-literal type in a constexpr function is incompatible with C++ standards before C++23}}
+      NonLiteral n; // cxx2a-error {{variable of non-literal type 'NonLiteral' cannot be defined in a constexpr function before C++23}} \
+                    // cxx23-warning {{definition of a variable of non-literal type in a constexpr function is incompatible with C++ standards before C++23}} \
+		    // cxx23-note {{non-constexpr constructor 'NonLiteral' cannot be used in a constant expression}}
     return 0;
   };
 
@@ -217,7 +217,7 @@ int test_lambdas_implicitly_constexpr() {
 
   auto non_literal = [](bool b) { // cxx2a-note 2{{declared here}}
     if (b)
-      NonLiteral n; // cxx23-note {{non-literal type 'NonLiteral' cannot be used in a constant expression}}
+      NonLiteral n; // cxx23-note {{non-constexpr constructor 'NonLiteral' cannot be used in a constant expression}}
     return 0;
   };
 
diff --git a/clang/test/SemaCXX/cxx23-invalid-constexpr.cpp b/clang/test/SemaCXX/cxx23-invalid-constexpr.cpp
index 4dc16c59d8058..3229a91fbcefb 100644
--- a/clang/test/SemaCXX/cxx23-invalid-constexpr.cpp
+++ b/clang/test/SemaCXX/cxx23-invalid-constexpr.cpp
@@ -25,14 +25,14 @@ constexpr int FT(T N) {
 class NonLiteral { // expected-note {{'NonLiteral' is not literal because it is not an aggregate and has no constexpr constructors}}
 public:
   NonLiteral() {}
-  ~NonLiteral() {}
+  ~NonLiteral() {} // expected-note {{declared here}}
 };
 
 constexpr NonLiteral F1() {
   return NonLiteral{};
 }
 
-constexpr int F2(NonLiteral N) {
+constexpr int F2(NonLiteral N) { // expected-note {{non-constexpr function '~NonLiteral' cannot be used in a constant expression}}
   return 8;
 }
 
@@ -46,7 +46,7 @@ class Derived1 : public NonLiteral {
 
 
 struct X {
-  X();
+  X(); // expected-note 2{{declared here}}
   X(const X&);
   X(X&&);
   X& operator=(X&);
@@ -80,7 +80,7 @@ struct WrapperNonT {
 };
 
 struct NonDefaultMembers {
-  constexpr NonDefaultMembers() {}; // expected-note {{non-literal type 'X' cannot be used in a constant expression}}
+  constexpr NonDefaultMembers() {}; // expected-note 2{{non-constexpr constructor 'X' cannot be used in a constant expression}}
   constexpr NonDefaultMembers(NonDefaultMembers const&) {};
   constexpr NonDefaultMembers(NonDefaultMembers &&) {};
   constexpr NonDefaultMembers& operator=(NonDefaultMembers &other) {this->t = other.t; return *this;}
@@ -109,7 +109,6 @@ void test() {
   F1();
   NonLiteral L;
   constexpr auto D = F2(L); // expected-error {{constexpr variable 'D' must be initialized by a constant expression}}
-                            // expected-note@-1 {{non-literal type 'NonLiteral' cannot be used in a constant expression}}
 
   constexpr auto E = FT(1); // expected-error {{constexpr variable 'E' must be initialized by a constant expression}}
                             // expected-note@-1 {{in call}}
@@ -125,8 +124,8 @@ void test() {
 
   static_assert((NonDefaultMembers(), true),""); // expected-error{{expression is not an integral constant expression}} \
                                                  // expected-note {{in call to}}
-  constexpr bool FFF = (NonDefaultMembers() == NonDefaultMembers()); // expected-error{{must be initialized by a constant expression}} \
-                                                                     // expected-note{{non-literal}}
+  constexpr bool FFF = (NonDefaultMembers() == NonDefaultMembers()); // expected-error {{must be initialized by a constant expression}} \
+								     // expected-note {{in call to 'NonDefaultMembers()'}}
 }
 
 struct A {

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

LGTM

Considering that the literal type requirement was quite literally just straight-up removed in C++23, returning early here is basically just doing what the standard did, so this change makes sense to me.

Also, one option would be to move this check up even further to be the first thing we check for in this function (don’t know how expensive the isLiteralType check is off the top of my head). I’m not sure it really matters though, so either way seems fine to me.

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

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

This seems reasonable. Slightly confusing because we so have several CheckLiteralType functions doing different things.

FYI as far as i can tell, WG21 is itching to get rid of the notion of literal types entirely so this further goes into that direction .

I find the way we print destructor/constructor names in some of these diagnostics rip for improvement. ie function '~NonLiteral' is not amazing. But it's a pre existing issue

We will need a release note though

tbaederr added 2 commits July 23, 2024 12:34
Instead of diagnosing non-literal types in C++23, allow them
and later diagnose them differently, e.g. because they have a
non-constexpr constructor, destructor, etc.
@tbaederr tbaederr merged commit dbe308c into llvm:main Jul 24, 2024
8 checks passed
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
Summary:
Instead of diagnosing non-literal types in C++23, allow them and later
diagnose them differently, e.g. because they have a non-constexpr
constructor, destructor, etc.

For this test:
```c++
struct NonLiteral {
  NonLiteral() {}
};
constexpr int foo() {
  NonLiteral L;
  return 1;
}

// static_assert(foo() == 1);
```
The current diagnostics with c++20/c++23 are:
```console
~/code/llvm-project/build » clang -c array.cpp -std=c++20
array.cpp:91:14: error: variable of non-literal type 'NonLiteral' cannot be defined in a constexpr function before C++23
   91 |   NonLiteral L;
      |              ^
array.cpp:87:8: note: 'NonLiteral' is not literal because it is not an aggregate and has no constexpr constructors other than copy or move constructors
   87 | struct NonLiteral {
      |        ^
1 error generated.
------------------------------------------------------------
~/code/llvm-project/build » clang -c array.cpp -std=c++23
(no output)
```

With the `static_assert` enabled, compiling with `-std=c++23` prints:
```console
array.cpp:95:15: error: static assertion expression is not an integral constant expression
   95 | static_assert(foo() == 1);
      |               ^~~~~~~~~~
array.cpp:91:14: note: non-literal type 'NonLiteral' cannot be used in a constant expression
   91 |   NonLiteral L;
      |              ^
array.cpp:95:15: note: in call to 'foo()'
   95 | static_assert(foo() == 1);
      |               ^~~~~
1 error generated.
```

As mentioned in #60311, this is confusing. The output with c++20
suggests that using c++23 will make the problem go away, but it's
diagnosed the same when running the function.

With this commit, the output instead diagnoses _why_ the non-literal
type can't be used:
```console
array.cpp:95:15: error: static assertion expression is not an integral constant expression
   95 | static_assert(foo() == 1);
      |               ^~~~~~~~~~
array.cpp:91:14: note: non-constexpr constructor 'NonLiteral' cannot be used in a constant expression
   91 |   NonLiteral L;
      |              ^
array.cpp:95:15: note: in call to 'foo()'
   95 | static_assert(foo() == 1);
      |               ^~~~~
array.cpp:88:3: note: declared here
   88 |   NonLiteral() {}
      |   ^
1 error generated.

``` 




Fixes #60311

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60250647
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.

Confusing error messages about non-literal types being used in constant expressions
4 participants