From 3251cbc77e8c6c9fa28dfbfa8168aabd6987b282 Mon Sep 17 00:00:00 2001 From: Yihan Wang Date: Mon, 5 Oct 2026 10:50:33 -0700 Subject: [PATCH 1/2] Consider explicit conversions before aggregate fallback [] Allow explicit conversion functions when matching the implicit copy constructor during single-argument direct-initialization, preventing premature C++20 aggregate fallback. Extend EDGcpfe24743 to cover explicit conversion functions. Signed-off-by: Yihan Wang --- src/expr.c | 6 +++++- src/overload.c | 4 +++- tests/tests/changes/EDGcpfe24743.sft.cpp | 6 ++++++ .../SemaCXX/.cxx2a-explicit-bool.rto/default.2.1.txt | 10 +--------- .../clang/cpp/SemaCXX/.explicit.rto/default.2.1.txt | 10 +--------- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/expr.c b/src/expr.c index fe8a6d57cd..adcc10de30 100644 --- a/src/expr.c +++ b/src/expr.c @@ -30702,11 +30702,15 @@ parentheses do not themselves correspond to aggregate initialization). an_arg_match_summary arg_match; an_operand opnd; clear_arg_match_summary(&arg_match); + /* When matching the first reference parameter of a copy constructor + called with one argument in a direct-initialization context, explicit + conversion functions are considered ([over.match.copy]). */ prep_list_initializer(arg_list, dest_type, /*is_direct_init=*/TRUE, /*check_narrowing=*/FALSE, /*warning_on_narrowing=*/FALSE, CCO_CAST | CCO_FUNC_NOTATION_CAST | - CCO_EXPLICIT_CAST | CCO_DIRECT_INITIALIZATION, + CCO_EXPLICIT_CAST | CCO_DIRECT_INITIALIZATION | + CCO_ALLOW_EXPLICIT_CONV_FUNCTIONS, /*fill_in_dtor=*/TRUE, /*force_temp=*/FALSE, /*make_lvalue_temp=*/FALSE, &opnd, (an_init_state*)NULL, &arg_match); diff --git a/src/overload.c b/src/overload.c index 4eafe2f719..6592201c34 100644 --- a/src/overload.c +++ b/src/overload.c @@ -27542,7 +27542,9 @@ will be an lvalue instead of the usual prvalue. (a_param_type_ptr)NULL, /*param_type_is_deduced=*/FALSE, try_user_conversions_in_ovl_res, - /*allow_expl_conv_funcs=*/FALSE, + /*allow_expl_conv_funcs=*/ + (conv_context & + CCO_ALLOW_EXPLICIT_CONV_FUNCTIONS) != 0, arg_match); } /* if */ } else { diff --git a/tests/tests/changes/EDGcpfe24743.sft.cpp b/tests/tests/changes/EDGcpfe24743.sft.cpp index ea0022a590..232e6aeba6 100644 --- a/tests/tests/changes/EDGcpfe24743.sft.cpp +++ b/tests/tests/changes/EDGcpfe24743.sft.cpp @@ -15,3 +15,9 @@ struct S { operator D() const; }; void g(S p) { D d(p); // Previously an error. Now okay. } + +struct ExplicitS { explicit operator D() const; }; +D h(ExplicitS p) { + D d(p); // Previously an error in C++20 mode. Now okay. + return D(p); // Likewise. +} diff --git a/tests/tests/imported/clang/cpp/SemaCXX/.cxx2a-explicit-bool.rto/default.2.1.txt b/tests/tests/imported/clang/cpp/SemaCXX/.cxx2a-explicit-bool.rto/default.2.1.txt index 61b48306c1..e3487904a6 100644 --- a/tests/tests/imported/clang/cpp/SemaCXX/.cxx2a-explicit-bool.rto/default.2.1.txt +++ b/tests/tests/imported/clang/cpp/SemaCXX/.cxx2a-explicit-bool.rto/default.2.1.txt @@ -476,14 +476,6 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers Y y2 = z; // expected-error {{no viable conversion}} ^ -"Test_name.c", line 530: error: too many initializer values - Y y2b(z); - ^ - -"Test_name.c", line 532: error: too many initializer values - Y y4 = Y(z); - ^ - "Test_name.c", line 540: error: no suitable user-defined conversion from "conversion3::Z" to "const conversion3::Y" exists const Y& y6 = z; // expected-error {{no viable conversion}} ^ @@ -683,4 +675,4 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers explicit(ptr) S(long); // expected-error {{conversion from 'const int *' to 'bool' is not allowed in a converted constant expression}} ^ -121 errors detected in the compilation of "Test_name.c". +119 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/imported/clang/cpp/SemaCXX/.explicit.rto/default.2.1.txt b/tests/tests/imported/clang/cpp/SemaCXX/.explicit.rto/default.2.1.txt index ededac4caa..0428590258 100644 --- a/tests/tests/imported/clang/cpp/SemaCXX/.explicit.rto/default.2.1.txt +++ b/tests/tests/imported/clang/cpp/SemaCXX/.explicit.rto/default.2.1.txt @@ -23,14 +23,6 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers Y y2 = z; // expected-error {{no viable conversion from 'Z' to 'Y'}} ^ -"Test_name.c", line 90: error: too many initializer values - Y y2b(z); - ^ - -"Test_name.c", line 92: error: too many initializer values - Y y4 = Y(z); - ^ - "Test_name.c", line 100: error: no suitable user-defined conversion from "Z" to "const Y" exists const Y& y6 = z; // expected-error {{no viable conversion from 'Z' to 'const Y'}} ^ @@ -171,4 +163,4 @@ fe_only -DTEST_NUMBER=2 --clang -tused -e 200 --no_wrap_diagnostics --clang_vers switch(EC){} ^ -31 errors detected in the compilation of "Test_name.c". +29 errors detected in the compilation of "Test_name.c". From 3fc9bca9dfedf590eaf4422a091172a709473164 Mon Sep 17 00:00:00 2001 From: Yihan Wang Date: Mon, 12 Oct 2026 08:12:18 -0700 Subject: [PATCH 2/2] Address review comments and add more tests [GH #281] --- src/Changes | 19 ++++++++++++++++ src/expr.c | 3 ++- .../tests/changes/.GH281.rto/default.1.1.txt | 1 + .../tests/changes/.GH281.rto/default.2.1.txt | 1 + .../tests/changes/.GH281.rto/default.3.1.txt | 1 + .../tests/changes/.GH281.rto/default.4.1.txt | 1 + .../tests/changes/.GH281.rto/default.5.1.txt | 1 + .../tests/changes/.GH281.rto/default.6.1.txt | 1 + .../tests/changes/.GH281.rto/default.7.1.txt | 1 + tests/tests/changes/EDGcpfe24743.sft.cpp | 6 ----- tests/tests/changes/GH281.sft.cpp | 20 +++++++++++++++++ tests/tests/cwg/.899.rto/default.1.1.txt | 10 +++++++++ tests/tests/cwg/.899.rto/default.2.1.txt | 10 +++++++++ tests/tests/cwg/.899.rto/default.3.1.txt | 10 +++++++++ tests/tests/cwg/.899.rto/default.4.1.txt | 10 +++++++++ tests/tests/cwg/.899.rto/default.5.1.txt | 10 +++++++++ tests/tests/cwg/.899.rto/default.6.1.txt | 10 +++++++++ tests/tests/cwg/.899.rto/default.7.1.txt | 10 +++++++++ tests/tests/cwg/899.sft.cpp | 22 +++++++++++++++++++ 19 files changed, 140 insertions(+), 7 deletions(-) create mode 100644 tests/tests/changes/.GH281.rto/default.1.1.txt create mode 100644 tests/tests/changes/.GH281.rto/default.2.1.txt create mode 100644 tests/tests/changes/.GH281.rto/default.3.1.txt create mode 100644 tests/tests/changes/.GH281.rto/default.4.1.txt create mode 100644 tests/tests/changes/.GH281.rto/default.5.1.txt create mode 100644 tests/tests/changes/.GH281.rto/default.6.1.txt create mode 100644 tests/tests/changes/.GH281.rto/default.7.1.txt create mode 100644 tests/tests/changes/GH281.sft.cpp create mode 100644 tests/tests/cwg/.899.rto/default.1.1.txt create mode 100644 tests/tests/cwg/.899.rto/default.2.1.txt create mode 100644 tests/tests/cwg/.899.rto/default.3.1.txt create mode 100644 tests/tests/cwg/.899.rto/default.4.1.txt create mode 100644 tests/tests/cwg/.899.rto/default.5.1.txt create mode 100644 tests/tests/cwg/.899.rto/default.6.1.txt create mode 100644 tests/tests/cwg/.899.rto/default.7.1.txt create mode 100644 tests/tests/cwg/899.sft.cpp diff --git a/src/Changes b/src/Changes index 79bcd59a6c..1bd4845ddf 100644 --- a/src/Changes +++ b/src/Changes @@ -1,3 +1,22 @@ +10/12/26 [GH #281] +Explicit conversion functions before parenthesized aggregate initialization + +The front end could incorrectly attempt parenthesized aggregate initialization +instead of considering an explicit conversion function when directly +initializing an aggregate in C++20 mode. For example: + + struct D { float x, y; }; + struct S { explicit operator D() const; }; + D f(S s) { + D d(s); // Previously an error in C++20 mode. Now okay. + return D(s); // Likewise. + } + +That is now fixed. The rule allowing explicit conversion functions in this +context comes from Core issue 899 and applies in C++11 and later modes. The +failure occurred when C++20 parenthesized aggregate initialization was enabled. + + 10/1/26 [GH #197] Deducing noexcept-specifiers diff --git a/src/expr.c b/src/expr.c index adcc10de30..fbe143f994 100644 --- a/src/expr.c +++ b/src/expr.c @@ -30704,7 +30704,8 @@ parentheses do not themselves correspond to aggregate initialization). clear_arg_match_summary(&arg_match); /* When matching the first reference parameter of a copy constructor called with one argument in a direct-initialization context, explicit - conversion functions are considered ([over.match.copy]). */ + conversion functions are considered (N3092 [over.match.copy]/1, + Core issue 899). */ prep_list_initializer(arg_list, dest_type, /*is_direct_init=*/TRUE, /*check_narrowing=*/FALSE, /*warning_on_narrowing=*/FALSE, diff --git a/tests/tests/changes/.GH281.rto/default.1.1.txt b/tests/tests/changes/.GH281.rto/default.1.1.txt new file mode 100644 index 0000000000..fcf5b546e4 --- /dev/null +++ b/tests/tests/changes/.GH281.rto/default.1.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=1 -tused --c++11 Test_name.c diff --git a/tests/tests/changes/.GH281.rto/default.2.1.txt b/tests/tests/changes/.GH281.rto/default.2.1.txt new file mode 100644 index 0000000000..480ced1860 --- /dev/null +++ b/tests/tests/changes/.GH281.rto/default.2.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=2 -tused --c++11 --gnu_version 160200 Test_name.c diff --git a/tests/tests/changes/.GH281.rto/default.3.1.txt b/tests/tests/changes/.GH281.rto/default.3.1.txt new file mode 100644 index 0000000000..fcf16ce89d --- /dev/null +++ b/tests/tests/changes/.GH281.rto/default.3.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=3 -tused --c++11 --clang_version 230100 Test_name.c diff --git a/tests/tests/changes/.GH281.rto/default.4.1.txt b/tests/tests/changes/.GH281.rto/default.4.1.txt new file mode 100644 index 0000000000..7f68c48fcc --- /dev/null +++ b/tests/tests/changes/.GH281.rto/default.4.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=4 -tused --c++20 Test_name.c diff --git a/tests/tests/changes/.GH281.rto/default.5.1.txt b/tests/tests/changes/.GH281.rto/default.5.1.txt new file mode 100644 index 0000000000..5411ee9e60 --- /dev/null +++ b/tests/tests/changes/.GH281.rto/default.5.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=5 -tused --c++20 --gnu_version 160200 Test_name.c diff --git a/tests/tests/changes/.GH281.rto/default.6.1.txt b/tests/tests/changes/.GH281.rto/default.6.1.txt new file mode 100644 index 0000000000..87a47aca25 --- /dev/null +++ b/tests/tests/changes/.GH281.rto/default.6.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=6 -tused --c++20 --clang_version 230100 Test_name.c diff --git a/tests/tests/changes/.GH281.rto/default.7.1.txt b/tests/tests/changes/.GH281.rto/default.7.1.txt new file mode 100644 index 0000000000..ab7537ac29 --- /dev/null +++ b/tests/tests/changes/.GH281.rto/default.7.1.txt @@ -0,0 +1 @@ +fe_only -DTEST_NUMBER=7 -tused --ms_c++20 --microsoft_version 1950 Test_name.c diff --git a/tests/tests/changes/EDGcpfe24743.sft.cpp b/tests/tests/changes/EDGcpfe24743.sft.cpp index 232e6aeba6..ea0022a590 100644 --- a/tests/tests/changes/EDGcpfe24743.sft.cpp +++ b/tests/tests/changes/EDGcpfe24743.sft.cpp @@ -15,9 +15,3 @@ struct S { operator D() const; }; void g(S p) { D d(p); // Previously an error. Now okay. } - -struct ExplicitS { explicit operator D() const; }; -D h(ExplicitS p) { - D d(p); // Previously an error in C++20 mode. Now okay. - return D(p); // Likewise. -} diff --git a/tests/tests/changes/GH281.sft.cpp b/tests/tests/changes/GH281.sft.cpp new file mode 100644 index 0000000000..4b5569e7bb --- /dev/null +++ b/tests/tests/changes/GH281.sft.cpp @@ -0,0 +1,20 @@ +//type:fp +//options:--c++11:--c++11 --gnu_version 160200:--c++11 --clang_version 230100:--c++20:--c++20 --gnu_version 160200:--c++20 --clang_version 230100:--ms_c++20 --microsoft_version 1950 +//options_all:-tused +//remark:Explicit conversion functions before aggregate fallback [GH #281] +// Core issue 899 permits these conversions in C++11 and later. Previously, +// C++20 parenthesized aggregate initialization caused spurious errors. + +struct D { float x, y; }; +struct S { + float x, y; + explicit constexpr operator D() const { return D{x, y}; } +}; + +D f(S s) { + D d(s); // Previously an error in C++20 mode. Now okay. + return D(s); // Likewise. +} + +constexpr S s = {1.0f, 2.0f}; +static_assert(D(s).x == 1.0f && D(s).y == 2.0f, "functional cast"); diff --git a/tests/tests/cwg/.899.rto/default.1.1.txt b/tests/tests/cwg/.899.rto/default.1.1.txt new file mode 100644 index 0000000000..04bdacfb33 --- /dev/null +++ b/tests/tests/cwg/.899.rto/default.1.1.txt @@ -0,0 +1,10 @@ +fe_only -DTEST_NUMBER=1 -tused --c++11 Test_name.c +"Test_name.c", line 18: error: no suitable conversion function from "A" to "int" exists + int i = a; // Error: copy-initialization. + ^ + +"Test_name.c", line 20: error: no suitable user-defined conversion from "A" to "C" exists + C c = a; // Error: copy-initialization. + ^ + +2 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/cwg/.899.rto/default.2.1.txt b/tests/tests/cwg/.899.rto/default.2.1.txt new file mode 100644 index 0000000000..22089b6dc8 --- /dev/null +++ b/tests/tests/cwg/.899.rto/default.2.1.txt @@ -0,0 +1,10 @@ +fe_only -DTEST_NUMBER=2 -tused --c++11 --gnu_version 160200 Test_name.c +"Test_name.c", line 18: error: no suitable conversion function from "A" to "int" exists + int i = a; // Error: copy-initialization. + ^ + +"Test_name.c", line 20: error: no suitable user-defined conversion from "A" to "C" exists + C c = a; // Error: copy-initialization. + ^ + +2 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/cwg/.899.rto/default.3.1.txt b/tests/tests/cwg/.899.rto/default.3.1.txt new file mode 100644 index 0000000000..7dc696587f --- /dev/null +++ b/tests/tests/cwg/.899.rto/default.3.1.txt @@ -0,0 +1,10 @@ +fe_only -DTEST_NUMBER=3 -tused --c++11 --clang_version 230100 Test_name.c +"Test_name.c", line 18: error: no suitable conversion function from "A" to "int" exists + int i = a; // Error: copy-initialization. + ^ + +"Test_name.c", line 20: error: no suitable user-defined conversion from "A" to "C" exists + C c = a; // Error: copy-initialization. + ^ + +2 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/cwg/.899.rto/default.4.1.txt b/tests/tests/cwg/.899.rto/default.4.1.txt new file mode 100644 index 0000000000..c88edeae40 --- /dev/null +++ b/tests/tests/cwg/.899.rto/default.4.1.txt @@ -0,0 +1,10 @@ +fe_only -DTEST_NUMBER=4 -tused --c++20 Test_name.c +"Test_name.c", line 18: error: no suitable conversion function from "A" to "int" exists + int i = a; // Error: copy-initialization. + ^ + +"Test_name.c", line 20: error: no suitable user-defined conversion from "A" to "C" exists + C c = a; // Error: copy-initialization. + ^ + +2 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/cwg/.899.rto/default.5.1.txt b/tests/tests/cwg/.899.rto/default.5.1.txt new file mode 100644 index 0000000000..cdaea3d1d1 --- /dev/null +++ b/tests/tests/cwg/.899.rto/default.5.1.txt @@ -0,0 +1,10 @@ +fe_only -DTEST_NUMBER=5 -tused --c++20 --gnu_version 160200 Test_name.c +"Test_name.c", line 18: error: no suitable conversion function from "A" to "int" exists + int i = a; // Error: copy-initialization. + ^ + +"Test_name.c", line 20: error: no suitable user-defined conversion from "A" to "C" exists + C c = a; // Error: copy-initialization. + ^ + +2 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/cwg/.899.rto/default.6.1.txt b/tests/tests/cwg/.899.rto/default.6.1.txt new file mode 100644 index 0000000000..cb2aec38b8 --- /dev/null +++ b/tests/tests/cwg/.899.rto/default.6.1.txt @@ -0,0 +1,10 @@ +fe_only -DTEST_NUMBER=6 -tused --c++20 --clang_version 230100 Test_name.c +"Test_name.c", line 18: error: no suitable conversion function from "A" to "int" exists + int i = a; // Error: copy-initialization. + ^ + +"Test_name.c", line 20: error: no suitable user-defined conversion from "A" to "C" exists + C c = a; // Error: copy-initialization. + ^ + +2 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/cwg/.899.rto/default.7.1.txt b/tests/tests/cwg/.899.rto/default.7.1.txt new file mode 100644 index 0000000000..09e46c8a0e --- /dev/null +++ b/tests/tests/cwg/.899.rto/default.7.1.txt @@ -0,0 +1,10 @@ +fe_only -DTEST_NUMBER=7 -tused --ms_c++20 --microsoft_version 1950 Test_name.c +"Test_name.c", line 18: error: no suitable conversion function from "A" to "int" exists + int i = a; // Error: copy-initialization. + ^ + +"Test_name.c", line 20: error: no suitable user-defined conversion from "A" to "C" exists + C c = a; // Error: copy-initialization. + ^ + +2 errors detected in the compilation of "Test_name.c". diff --git a/tests/tests/cwg/899.sft.cpp b/tests/tests/cwg/899.sft.cpp new file mode 100644 index 0000000000..9790365b97 --- /dev/null +++ b/tests/tests/cwg/899.sft.cpp @@ -0,0 +1,22 @@ +//type:fn +//options:--c++11:--c++11 --gnu_version 160200:--c++11 --clang_version 230100:--c++20:--c++20 --gnu_version 160200:--c++20 --clang_version 230100:--ms_c++20 --microsoft_version 1950 +//options_all:-tused +//remark:Core issue 899: Explicit conversion functions in direct class initialization + +struct C { }; +struct A { + explicit operator int() const; + explicit operator C() const; +}; + +struct B { + int i; + B(const A& a): i(a) { } // Okay. +}; + +void f(A a) { + int i = a; // Error: copy-initialization. + int j(a); // Okay: direct-initialization. + C c = a; // Error: copy-initialization. + C c2(a); // Okay: direct-initialization (Core issue 899). +}