https://github.com/zihan001 created https://github.com/llvm/llvm-project/pull/218225
Fixes #215501. C++20 allows a braced initializer list to bind to a const lvalue reference to an array of unknown bound. The existing initialization path handled the rvalue-reference case, but missed the corresponding conversion for const lvalue references. This could leave the call argument with the wrong array type and hit an assertion in CodeGen. Apply the same handling to const lvalue references, preserving the correct value category, and add regression tests. AI assistance: I used ChatGPT and Claude Code for codebase navigation, investigation, implementation planning, and test development. I reviewed and tested the final patch and understand the submitted changes. >From b613ece9f2f12d5191219bf3de28ff67657172a3 Mon Sep 17 00:00:00 2001 From: Zihan <[email protected]> Date: Sun, 23 Aug 2026 03:50:18 -0600 Subject: [PATCH] [clang] Fix C++20 list initialization of const references to arrays of unknown bound The existing C++20 handling for arrays of unknown bound only covered rvalue references. Const lvalue references could reach CodeGen with a mismatched argument type and hit an assertion. Handle both reference kinds and preserve the correct value category. Fixes #215501. Assisted-by: Claude Code Assisted-by: ChatGPT --- clang/docs/ReleaseNotes.md | 3 +++ clang/lib/Sema/SemaInit.cpp | 12 ++++++----- clang/test/AST/ast-dump-init.cpp | 20 ++++++++++++++++++ .../CodeGenCXX/cxx20-p0388-unbound-ary.cpp | 21 +++++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index ca0dbfa2af229..0dcf47352d052 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -533,6 +533,9 @@ features cannot lower the translation-unit ABI level; parameter that follows a parameter pack (e.g. `template <typename... T> S::S(T..., int = 10) {}`). (#GH216211) +- Fixed a crash when passing a braced initializer list to a const reference + to an array of unknown bound in C++20. (#GH215501) + #### Bug Fixes to AST Handling - Fixed a non-deterministic ordering of unused local typedefs that made diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index 9b10ac1735c81..5c438a3a36948 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -5005,8 +5005,7 @@ static void TryReferenceListInitialization(Sema &S, Sequence.AddReferenceBindingStep(cv1T1IgnoreAS, /*BindingTemporary=*/true); if (S.getLangOpts().CPlusPlus20 && - isa<IncompleteArrayType>(T1->getUnqualifiedDesugaredType()) && - DestType->isRValueReferenceType()) { + isa<IncompleteArrayType>(T1->getUnqualifiedDesugaredType())) { // C++20 [dcl.init.list]p3.10: // List-initialization of an object or reference of type T is defined as // follows: @@ -5014,9 +5013,12 @@ static void TryReferenceListInitialization(Sema &S, // case the type of the prvalue is the type of x in the declaration U // x[] H, where H is the initializer list. - // The call to AddReferenceBindingStep above converts the rvalue to an - // xvalue. Convert that xvalue to the incomplete array type. - Sequence.AddQualificationConversionStep(cv1T1, clang::VK_XValue); + // The call to AddReferenceBindingStep above materialized a temporary + // with the deduced bound. Convert it to the incomplete array type so + // that the expression type matches the referenced type. The temporary + // is an lvalue when bound to an lvalue reference, an xvalue otherwise. + Sequence.AddQualificationConversionStep( + cv1T1, DestType->isRValueReferenceType() ? VK_XValue : VK_LValue); } if (T1Quals.hasAddressSpace()) Sequence.AddQualificationConversionStep( diff --git a/clang/test/AST/ast-dump-init.cpp b/clang/test/AST/ast-dump-init.cpp index dbf95afdccb82..9eaa85160b477 100644 --- a/clang/test/AST/ast-dump-init.cpp +++ b/clang/test/AST/ast-dump-init.cpp @@ -19,3 +19,23 @@ void foo(int a) { // CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <col:5, col:7> 'int[1]' // CHECK-NEXT: `-ImplicitCastExpr 0x{{[^ ]*}} <col:6> 'int' <LValueToRValue> // CHECK-NEXT: `-DeclRefExpr 0x{{[^ ]*}} <col:6> 'int' lvalue ParmVar 0x{{[^ ]*}} 'a' 'int' + +void bar(int a) { + auto g = [](const int(&)[]) {}; + g({a}); +} + +// Same as above, but binding to a const lvalue reference: the cast to the +// incomplete array type is an lvalue rather than an xvalue. + +// CHECK: `-ExprWithCleanups 0x{{[^ ]*}}{{[^ ]*}} <line:25:3, col:8> 'void' +// CHECK-NEXT: `-CXXOperatorCallExpr 0x{{[^ ]*}} <col:3, col:8> 'void' '()' +// CHECK-NEXT: |-ImplicitCastExpr 0x{{[^ ]*}} <col:4, col:8> 'void (*)(const int (&)[]) const' <FunctionToPointerDecay> +// CHECK-NEXT: | `-DeclRefExpr 0x{{[^ ]*}} <col:4, col:8> 'void (const int (&)[]) const' lvalue CXXMethod 0x{{[^ ]*}} 'operator()' 'void (const int (&)[]) const' +// CHECK-NEXT: |-ImplicitCastExpr 0x{{[^ ]*}} <col:3> 'const (lambda at {{.*}})' lvalue <NoOp> +// CHECK-NEXT: | `-DeclRefExpr 0x{{[^ ]*}} <col:3> '(lambda at {{.*}})' lvalue Var 0x{{[^ ]*}} 'g' '(lambda at {{.*}})' +// CHECK-NEXT: `-ImplicitCastExpr 0x{{[^ ]*}} <col:5, col:7> 'const int[]' lvalue <NoOp> +// CHECK-NEXT: `-MaterializeTemporaryExpr 0x{{[^ ]*}} <col:5, col:7> 'const int[1]' lvalue +// CHECK-NEXT: `-InitListExpr 0x{{[^ ]*}} <col:5, col:7> 'const int[1]' +// CHECK-NEXT: `-ImplicitCastExpr 0x{{[^ ]*}} <col:6> 'int' <LValueToRValue> +// CHECK-NEXT: `-DeclRefExpr 0x{{[^ ]*}} <col:6> 'int' lvalue ParmVar 0x{{[^ ]*}} 'a' 'int' diff --git a/clang/test/CodeGenCXX/cxx20-p0388-unbound-ary.cpp b/clang/test/CodeGenCXX/cxx20-p0388-unbound-ary.cpp index 007b47c441b2f..119e1e32f3a32 100644 --- a/clang/test/CodeGenCXX/cxx20-p0388-unbound-ary.cpp +++ b/clang/test/CodeGenCXX/cxx20-p0388-unbound-ary.cpp @@ -48,3 +48,24 @@ void gh151716_f() { } } // namespace One + +namespace Two { + +// A braced-init-list may also bind to a const lvalue reference to an array of +// unknown bound, not just to an rvalue reference. This used to crash in +// CodeGen with "type mismatch in call argument!" (GH215501). +void unknownBoundArrayRef(const int (&)[]); + +// CHECK-LABEL: @_ZN3Two8gh215501Ev +// CHECK-NEXT: entry: +// CHECK-NEXT: %ref.tmp = alloca [3 x i32], align 4 +// CHECK: store i32 1, ptr %ref.tmp, align 4 +// CHECK: store i32 2, ptr %arrayinit.element, align 4 +// CHECK: store i32 3, ptr %arrayinit.element1, align 4 +// CHECK: call void @_ZN3Two20unknownBoundArrayRefERA_Ki(ptr noundef nonnull align 4 %ref.tmp) +// CHECK: ret void +void gh215501() { + unknownBoundArrayRef({1, 2, 3}); +} + +} // namespace Two _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
