[clang] [clang][NFC] Add tests for static_cast<T&&> of a bit-field lvalue. (PR #231092)
Aditya Chaudhary via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 18 21:48:04 PDT 2026
https://github.com/aditya-chaudhary1 updated https://github.com/llvm/llvm-project/pull/231092
>From 0094c65251fce8bc9f26813dea1f20602c4418e3 Mon Sep 17 00:00:00 2001
From: Aditya Chaudhary <Aditya.Chaudhary1 at ibm.com>
Date: Mon, 12 Oct 2026 12:25:04 +0530
Subject: [PATCH 1/3] Precommit tests that record clang current handling
---
.../test/AST/ast-dump-rvalue-ref-bitfield.cpp | 29 +++++++++++++++++++
clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp | 28 ++++++++++++++++++
2 files changed, 57 insertions(+)
create mode 100644 clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp
create mode 100644 clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
diff --git a/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp b/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp
new file mode 100644
index 00000000000000..38ed402ef38982
--- /dev/null
+++ b/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp
@@ -0,0 +1,29 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++11 -fsyntax-only \
+// RUN: -ast-dump -ast-dump-filter test %s | FileCheck %s
+
+// static_cast<T&&> applied to a bit-field lvalue. A bit-field has no address,
+// so the reference has to bind to a materialized temporary.
+//
+// FIXME: Clang currently binds the reference directly to the bit-field. No
+// MaterializeTemporaryExpr or LValueToRValue conversion is created, and this
+// AST crashes CodeGen (see CodeGenCXX/rvalue-ref-bitfield.cpp).
+
+struct A {
+ int a : 4;
+ int b : 1;
+};
+
+void test(A obj) {
+ int &&r = static_cast<int &&>(obj.a);
+ const int &&cr = static_cast<const int &&>(obj.a);
+}
+
+// CHECK-LABEL: FunctionDecl {{.*}} test 'void (A)'
+// CHECK: VarDecl {{.*}} r 'int &&' cinit
+// CHECK-NEXT: `-CXXStaticCastExpr {{.*}} 'int' xvalue static_cast<int &&> <NoOp>
+// CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a
+// CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A'
+// CHECK: VarDecl {{.*}} cr 'const int &&' cinit
+// CHECK-NEXT: `-CXXStaticCastExpr {{.*}} 'const int' xvalue static_cast<const int &&> <NoOp>
+// CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a
+// CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A'
diff --git a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
new file mode 100644
index 00000000000000..8966178f3a9bad
--- /dev/null
+++ b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
@@ -0,0 +1,28 @@
+// REQUIRES: asserts
+// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \
+// RUN: -emit-llvm -o - %s -DNONCONST 2>&1 | FileCheck %s --check-prefix=CHECK-NONCONST
+// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \
+// RUN: -emit-llvm -o - %s -DCONST 2>&1 | FileCheck %s --check-prefix=CHECK-CONST
+
+// static_cast<T&&> of a bit-field lvalue should materialize a temporary.
+// FIXME: Sema binds the reference directly to the bit-field, so CodeGen
+// receives a bit-field LValue and asserts in EmitReferenceBindingToExpr.
+// Each case is guarded by its own macro because the crash aborts the whole
+// compilation.
+
+struct A {
+ int a : 4;
+};
+
+// CHECK-LABEL: define {{.*}} @_Z4test1A(
+void test(A obj) {
+ #ifdef NONCONST
+ // CHECK-NONCONST: {{Assertion.*LV.isSimple\(\)}}
+ int &&ri = static_cast<int&&>(obj.a);
+ #endif
+
+#ifdef CONST
+ // CHECK-CONST: {{Assertion.*LV.isSimple\(\)}}
+ const int &&cri = static_cast<const int &&>(obj.a);
+ #endif
+}
>From 6c1138217d1ccab7050f583fa46ea77e5255316e Mon Sep 17 00:00:00 2001
From: Aditya Chaudhary <Aditya.Chaudhary1 at ibm.com>
Date: Thu, 15 Oct 2026 00:35:10 +0530
Subject: [PATCH 2/3] moved ast test and format the test as well
---
clang/test/AST/ast-dump-expr.cpp | 22 ++++++++++++++
.../test/AST/ast-dump-rvalue-ref-bitfield.cpp | 29 -------------------
clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp | 14 +++++----
3 files changed, 30 insertions(+), 35 deletions(-)
delete mode 100644 clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp
diff --git a/clang/test/AST/ast-dump-expr.cpp b/clang/test/AST/ast-dump-expr.cpp
index e19c6d4b12451d..d48a72b4d9511f 100644
--- a/clang/test/AST/ast-dump-expr.cpp
+++ b/clang/test/AST/ast-dump-expr.cpp
@@ -596,3 +596,25 @@ void f() {
}
// CHECK: CXXTemporaryObjectExpr {{.*}} <col:5, col:11> 'S' 'void (int, int)'
}
+
+// static_cast<T&&> of a bit-field lvalue.
+// FIXME: Clang binds the reference directly to the bit-field without inserting
+// an LValueToRValue conversion or MaterializeTemporaryExpr, which crashes
+// CodeGen (see CodeGenCXX/rvalue-ref-bitfield.cpp).
+namespace BitFieldRvalueRef {
+struct BF {
+ int a : 4;
+};
+void test(BF obj) {
+ int &&r = static_cast<int &&>(obj.a);
+ // CHECK: VarDecl 0x{{[^ ]*}} <col:3, col:38> col:9 r 'int &&' cinit
+ // CHECK-NEXT: CXXStaticCastExpr 0x{{[^ ]*}} <col:13, col:38> 'int' xvalue static_cast<int &&> <NoOp>
+ // CHECK-NEXT: MemberExpr 0x{{[^ ]*}} <col:33, col:37> 'int' lvalue bitfield .a
+ // CHECK-NEXT: DeclRefExpr 0x{{[^ ]*}} <col:33> 'BF' lvalue ParmVar 0x{{[^ ]*}} 'obj' 'BF'
+ const int &&cr = static_cast<const int &&>(obj.a);
+ // CHECK: VarDecl 0x{{[^ ]*}} <col:3, col:51> col:15 cr 'const int &&' cinit
+ // CHECK-NEXT: CXXStaticCastExpr 0x{{[^ ]*}} <col:20, col:51> 'const int' xvalue static_cast<const int &&> <NoOp>
+ // CHECK-NEXT: MemberExpr 0x{{[^ ]*}} <col:46, col:50> 'int' lvalue bitfield .a
+ // CHECK-NEXT: DeclRefExpr 0x{{[^ ]*}} <col:46> 'BF' lvalue ParmVar 0x{{[^ ]*}} 'obj' 'BF'
+}
+} // namespace BitFieldRvalueRef
diff --git a/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp b/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp
deleted file mode 100644
index 38ed402ef38982..00000000000000
--- a/clang/test/AST/ast-dump-rvalue-ref-bitfield.cpp
+++ /dev/null
@@ -1,29 +0,0 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++11 -fsyntax-only \
-// RUN: -ast-dump -ast-dump-filter test %s | FileCheck %s
-
-// static_cast<T&&> applied to a bit-field lvalue. A bit-field has no address,
-// so the reference has to bind to a materialized temporary.
-//
-// FIXME: Clang currently binds the reference directly to the bit-field. No
-// MaterializeTemporaryExpr or LValueToRValue conversion is created, and this
-// AST crashes CodeGen (see CodeGenCXX/rvalue-ref-bitfield.cpp).
-
-struct A {
- int a : 4;
- int b : 1;
-};
-
-void test(A obj) {
- int &&r = static_cast<int &&>(obj.a);
- const int &&cr = static_cast<const int &&>(obj.a);
-}
-
-// CHECK-LABEL: FunctionDecl {{.*}} test 'void (A)'
-// CHECK: VarDecl {{.*}} r 'int &&' cinit
-// CHECK-NEXT: `-CXXStaticCastExpr {{.*}} 'int' xvalue static_cast<int &&> <NoOp>
-// CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a
-// CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A'
-// CHECK: VarDecl {{.*}} cr 'const int &&' cinit
-// CHECK-NEXT: `-CXXStaticCastExpr {{.*}} 'const int' xvalue static_cast<const int &&> <NoOp>
-// CHECK-NEXT: `-MemberExpr {{.*}} 'int' lvalue bitfield .a
-// CHECK-NEXT: `-DeclRefExpr {{.*}} 'A' lvalue ParmVar {{.*}} 'obj' 'A'
diff --git a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
index 8966178f3a9bad..0e7f5a87bc7fea 100644
--- a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
+++ b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
@@ -1,8 +1,10 @@
// REQUIRES: asserts
// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \
-// RUN: -emit-llvm -o - %s -DNONCONST 2>&1 | FileCheck %s --check-prefix=CHECK-NONCONST
+// RUN: -emit-llvm -o - %s -DNONCONST 2>&1 | FileCheck %s
+// --check-prefix=CHECK-NONCONST
// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \
-// RUN: -emit-llvm -o - %s -DCONST 2>&1 | FileCheck %s --check-prefix=CHECK-CONST
+// RUN: -emit-llvm -o - %s -DCONST 2>&1 | FileCheck %s
+// --check-prefix=CHECK-CONST
// static_cast<T&&> of a bit-field lvalue should materialize a temporary.
// FIXME: Sema binds the reference directly to the bit-field, so CodeGen
@@ -16,13 +18,13 @@ struct A {
// CHECK-LABEL: define {{.*}} @_Z4test1A(
void test(A obj) {
- #ifdef NONCONST
+#ifdef NONCONST
// CHECK-NONCONST: {{Assertion.*LV.isSimple\(\)}}
- int &&ri = static_cast<int&&>(obj.a);
- #endif
+ int &&ri = static_cast<int &&>(obj.a);
+#endif
#ifdef CONST
// CHECK-CONST: {{Assertion.*LV.isSimple\(\)}}
const int &&cri = static_cast<const int &&>(obj.a);
- #endif
+#endif
}
>From 38747d6439e1b3ad5222ae9e487d19cfebe545fd Mon Sep 17 00:00:00 2001
From: Aditya Chaudhary <Aditya.Chaudhary1 at ibm.com>
Date: Mon, 19 Oct 2026 10:17:23 +0530
Subject: [PATCH 3/3] add test in the SemaCXX
---
clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp | 6 +--
.../static-cast-bitfield-temporary.cpp | 46 +++++++++++++++++++
2 files changed, 48 insertions(+), 4 deletions(-)
create mode 100644 clang/test/SemaCXX/static-cast-bitfield-temporary.cpp
diff --git a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
index 0e7f5a87bc7fea..faf6d2ff433705 100644
--- a/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
+++ b/clang/test/CodeGenCXX/rvalue-ref-bitfield.cpp
@@ -1,10 +1,8 @@
// REQUIRES: asserts
// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \
-// RUN: -emit-llvm -o - %s -DNONCONST 2>&1 | FileCheck %s
-// --check-prefix=CHECK-NONCONST
+// RUN: -emit-llvm -o -disable-llvm-passes %s -DNONCONST 2>&1 | FileCheck %s --check-prefix=CHECK-NONCONST
// RUN: not --crash %clang_cc1 -std=c++11 -triple x86_64-unknown-unknown \
-// RUN: -emit-llvm -o - %s -DCONST 2>&1 | FileCheck %s
-// --check-prefix=CHECK-CONST
+// RUN: -emit-llvm -o -disable-llvm-passes %s -DCONST 2>&1 | FileCheck %s --check-prefix=CHECK-CONST
// static_cast<T&&> of a bit-field lvalue should materialize a temporary.
// FIXME: Sema binds the reference directly to the bit-field, so CodeGen
diff --git a/clang/test/SemaCXX/static-cast-bitfield-temporary.cpp b/clang/test/SemaCXX/static-cast-bitfield-temporary.cpp
new file mode 100644
index 00000000000000..5a10b20e89cfaa
--- /dev/null
+++ b/clang/test/SemaCXX/static-cast-bitfield-temporary.cpp
@@ -0,0 +1,46 @@
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++2c -triple x86_64-unknown-unknown -fsyntax-only -verify -fexperimental-new-constant-interpreter %s
+
+struct A {
+ unsigned x : 1;
+};
+
+// FIXME: static_cast<T&&> of a bit-field should bind to an independent
+// temporary; currently Sema binds directly to the bit-field so r aliases a.x.
+constexpr unsigned f1() {
+ A a{0};
+ unsigned &&r = static_cast<unsigned &&>(a.x);
+ r = 7;
+ a.x = 0;
+ return r;
+}
+static_assert(f1() == 7); // expected-error {{static assertion failed due to requirement 'f1() == 7'}}
+ // expected-note at -1 {{expression evaluates to '0 == 7'}}
+
+// FIXME: the temporary's lifetime should expire at the end of the
+// full-expression; reading through p after that point must be rejected.
+// Currently a different Sema error fires due to the direct bit-field binding.
+constexpr auto &q(auto &&a) { return a; } // expected-error {{non-const lvalue reference to type 'unsigned int' cannot bind to a temporary of type 'unsigned int'}}
+constexpr unsigned f2() {
+ A a{0};
+ unsigned *p;
+ *(p = &q(static_cast<unsigned &&>(a.x))) = 1; // expected-note {{in instantiation of function template specialization 'q<unsigned int>' requested here}}
+ // expected-note at -1 {{subexpression not valid in a constant expression}}
+ return *p;
+}
+static_assert(f2() == 1); // expected-error {{static assertion expression is not an integral constant expression}}
+ // expected-note at -1 {{in call to 'f2()'}}
+
+// FIXME: cv-qualification from static_cast<const T&&> must be preserved on the
+// materialized temporary; const_cast cannot enable modification through it.
+// f3() must never produce a constant expression. Currently no error is emitted
+// because the direct binding lets the modification silently succeed.
+// When fixed, this function should be diagnosed as never producing a constant
+// expression, with a note pointing at the modification of the const temporary.
+// The new interpreter has a separate bug and may not emit the note.
+constexpr unsigned f3() { // FIXME-expected-error {{never produces a constant expression}}
+ A a{0};
+ unsigned &&r = const_cast<unsigned &&>(static_cast<const unsigned &&>(a.x));
+ r = 0; // FIXME-expected-note {{modification of object of const-qualified type 'const unsigned int'}}
+ return a.x;
+}
More information about the cfe-commits
mailing list