Skip to content

[Clang] support C23 constexpr struct member access in constant expressions - #182770

Merged
a-tarasyuk merged 5 commits into
llvm:mainfrom
a-tarasyuk:feat/178349
Feb 27, 2026
Merged

[Clang] support C23 constexpr struct member access in constant expressions#182770
a-tarasyuk merged 5 commits into
llvm:mainfrom
a-tarasyuk:feat/178349

Conversation

@a-tarasyuk

Copy link
Copy Markdown
Member

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

@a-tarasyuk
a-tarasyuk marked this pull request as ready for review February 23, 2026 11:40
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Feb 23, 2026
@llvmbot

llvmbot commented Feb 23, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-clang

Author: Oleksandr Tarasiuk (a-tarasyuk)

Changes

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


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

4 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+1)
  • (modified) clang/lib/AST/ExprConstant.cpp (+20-1)
  • (modified) clang/test/C/drs/dr4xx.c (+1-1)
  • (added) clang/test/Sema/constexpr-member-access.c (+54)
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, "");
+}

@Fznamznon Fznamznon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, I spent some time looking at this and this seems like a possible solution so thank you for working on this. Not sure about compound literals change though.

Comment thread clang/lib/AST/ExprConstant.cpp Outdated
Comment thread clang/lib/AST/ExprConstant.cpp Outdated
Comment thread clang/test/Sema/constexpr-member-access.c
@github-actions

github-actions Bot commented Feb 23, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 114199 tests passed
  • 4561 tests skipped

✅ The build succeeded and all tests passed.

@github-actions

github-actions Bot commented Feb 23, 2026

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 54289 tests passed
  • 2302 tests skipped

✅ The build succeeded and all tests passed.

@a-tarasyuk
a-tarasyuk requested a review from Fznamznon February 24, 2026 11:04

@Fznamznon Fznamznon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, @AaronBallman could you please take a look too?

@AaronBallman AaronBallman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@a-tarasyuk
a-tarasyuk merged commit 6b91049 into llvm:main Feb 27, 2026
11 checks passed
sujianIBM pushed a commit to sujianIBM/llvm-project that referenced this pull request Mar 5, 2026
…sions (llvm#182770)

Fixes llvm#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
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.

(C23) Acessing a constexpr struct's member through the dot operator does not yield a constant.

4 participants