[clang] [Clang] support C23 constexpr struct member access in constant expressions (PR #182770)
Oleksandr Tarasiuk via cfe-commits
cfe-commits at lists.llvm.org
Sun Feb 22 12:44:09 PST 2026
https://github.com/a-tarasyuk created https://github.com/llvm/llvm-project/pull/182770
Fixes #178349
---
This patch resolves an issue where accessing C23 `constexpr` struct members using the dot operator was not recognized as a constant expression.
According to C23 spec:
> 6.6p7:
>
> An identifier that is:
> — an enumeration constant,
> — a predefined constant, or
> — declared with storage-class specifier constexpr and has an object type,
> is a named constant, as is a postfix expression that applies the . member access operator to a named
> constant of structure or union type, even recursively. For enumeration and predefined constants,
> their value and type are defined in the respective clauses; for constexpr objects, such a named
> constant is a constant expression with the type and value of the declared object.
>
> § 6.6p13:
>
> A structure or union constant is a named constant or compound literal constant with structure or
> union type, respectively
>
> § 6.6p15:
>
> Starting from a structure or union constant, the member-access . operator may be used to form a
> named constant or compound literal constant as described previously in this subclause
>From 5b51c2654d2bb8f859ff3fa61efb9ce2a00563a7 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Sun, 22 Feb 2026 18:23:55 +0200
Subject: [PATCH] [Clang] support C23 constexpr struct member access in
constant expressions
---
clang/docs/ReleaseNotes.rst | 1 +
clang/lib/AST/ExprConstant.cpp | 21 ++++++++-
clang/test/C/drs/dr4xx.c | 2 +-
clang/test/Sema/constexpr-member-access.c | 54 +++++++++++++++++++++++
4 files changed, 76 insertions(+), 2 deletions(-)
create mode 100644 clang/test/Sema/constexpr-member-access.c
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 6e9e5baea2921..ed555627977ee 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -130,6 +130,7 @@ C2y Feature Support
C23 Feature Support
^^^^^^^^^^^^^^^^^^^
+- Clang now allows C23 ``constexpr`` struct member access through the dot operator in constant expressions. (#GH178349)
Non-comprehensive list of changes in this release
-------------------------------------------------
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 2c13befec02f2..c249624ff4cda 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -21009,7 +21009,6 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
case Expr::ArraySectionExprClass:
case Expr::OMPArrayShapingExprClass:
case Expr::OMPIteratorExprClass:
- case Expr::MemberExprClass:
case Expr::CompoundAssignOperatorClass:
case Expr::CompoundLiteralExprClass:
case Expr::ExtVectorElementExprClass:
@@ -21087,6 +21086,26 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
case Expr::HLSLOutArgExprClass:
return ICEDiag(IK_NotICE, E->getBeginLoc());
+ case Expr::MemberExprClass: {
+ if (Ctx.getLangOpts().C23) {
+ const Expr *ME = E->IgnoreParenImpCasts();
+ while (const auto *M = dyn_cast<MemberExpr>(ME)) {
+ if (M->isArrow())
+ return ICEDiag(IK_NotICE, E->getBeginLoc());
+ ME = M->getBase()->IgnoreParenImpCasts();
+ }
+ const auto *DRE = dyn_cast<DeclRefExpr>(ME);
+ if (DRE) {
+ const auto *VD = dyn_cast<VarDecl>(DRE->getDecl());
+ if (VD && VD->isConstexpr())
+ return CheckEvalInICE(E, Ctx);
+ }
+ if (isa<CompoundLiteralExpr>(ME))
+ return CheckEvalInICE(E, Ctx);
+ }
+ return ICEDiag(IK_NotICE, E->getBeginLoc());
+ }
+
case Expr::InitListExprClass: {
// C++03 [dcl.init]p13: If T is a scalar type, then a declaration of the
// form "T x = { a };" is equivalent to "T x = a;".
diff --git a/clang/test/C/drs/dr4xx.c b/clang/test/C/drs/dr4xx.c
index 83d7b94cd6795..fda5e437b8759 100644
--- a/clang/test/C/drs/dr4xx.c
+++ b/clang/test/C/drs/dr4xx.c
@@ -106,7 +106,7 @@ void dr413(void) {
* not 0.
*/
_Static_assert((S){ /* c89only-warning {{compound literals are a C99-specific feature}}
- expected-warning {{expression is not an integer constant expression; folding it to a constant is a GNU extension}}
+ pre-c23-warning {{expression is not an integer constant expression; folding it to a constant is a GNU extension}}
*/
1,
.t = { /* c89only-warning {{designated initializers are a C99 feature}} */
diff --git a/clang/test/Sema/constexpr-member-access.c b/clang/test/Sema/constexpr-member-access.c
new file mode 100644
index 0000000000000..a2cf3cb56f5d5
--- /dev/null
+++ b/clang/test/Sema/constexpr-member-access.c
@@ -0,0 +1,54 @@
+// RUN: %clang_cc1 -std=c23 -verify -triple x86_64 -pedantic -Wno-conversion -Wno-constant-conversion -Wno-div-by-zero %s
+// RUN: %clang_cc1 -std=c23 -verify -triple x86_64 -pedantic -Wno-conversion -Wno-constant-conversion -Wno-div-by-zero -fexperimental-new-constant-interpreter %s
+
+#define comptime_if(predicate, on_true, on_false) \
+ _Generic((char (*)[1 + !!(predicate)]){0}, \
+ char (*)[2]: (on_true), \
+ char (*)[1]: (on_false))
+
+enum E { E_A = 0, E_B = 1 };
+union U {
+ int a;
+ char b;
+};
+struct S1 {
+ int a;
+};
+struct S2 {
+ int a;
+ int b;
+ _Bool c;
+ char d;
+ enum E e;
+ struct S1 f;
+ double g;
+ int h;
+};
+
+constexpr struct S2 V1 = {0, 1, 1, 'c', E_B, {3}, 1.0, -1};
+constexpr union U V2 = {5};
+constexpr int V3 = V1.f.a;
+constexpr int V4 = ((struct S2){0, 4, 0, 0, E_A, {6}, 0.0, 0}).f.a;
+
+void gh178349() {
+ int a[V1.b] = {};
+ int b[V1.g] = {}; // expected-error {{size of array has non-integer type 'double'}}
+ int c[V1.h] = {}; // expected-error {{'c' declared as an array with a negative size}}
+
+ const struct S2 *P1 = &V1;
+ _Static_assert(P1->b, ""); // expected-error {{static assertion expression is not an integral constant expression}}
+ _Static_assert(V1.b, "");
+
+ _Static_assert(comptime_if(V1.a, 1, 0) == 0, "");
+ _Static_assert(comptime_if(V1.a, 0, 1) == 1, "");
+ _Static_assert(comptime_if(V2.a, 1, 0) == 1, "");
+
+ _Static_assert(V1.c, "");
+ _Static_assert(V1.d == 'c', "");
+ _Static_assert(V1.e == E_B, "");
+ _Static_assert(V1.f.a == 3, "");
+
+ _Static_assert(V2.a == 5, "");
+ _Static_assert(V3 == 3, "");
+ _Static_assert(V4 == 6, "");
+}
More information about the cfe-commits
mailing list