https://gcc.gnu.org/g:77798637b9cdea2d2b7cca9fc42200a95def7412
commit r16-5521-g77798637b9cdea2d2b7cca9fc42200a95def7412 Author: Nathaniel Shead <[email protected]> Date: Thu Oct 23 13:57:37 2025 +1100 c++: Add detailed diagnostics for __is_layout_compatible gcc/cp/ChangeLog: * constraint.cc (diagnose_trait_expr) <case CPTK_IS_LAYOUT_COMPATIBLE>: Explain why not. * cp-tree.h (layout_compatible_type_p): Add explain parameter. * typeck.cc (layout_compatible_type_p): Add explanations when returning false. gcc/testsuite/ChangeLog: * g++.dg/cpp2a/is-layout-compatible4.C: New test. Signed-off-by: Nathaniel Shead <[email protected]> Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/constraint.cc | 3 +- gcc/cp/cp-tree.h | 2 +- gcc/cp/typeck.cc | 134 +++++++++++++++++---- gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C | 78 ++++++++++++ 4 files changed, 192 insertions(+), 25 deletions(-) diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc index ebc3b8a40200..19d2d8f82d51 100644 --- a/gcc/cp/constraint.cc +++ b/gcc/cp/constraint.cc @@ -3188,7 +3188,8 @@ diagnose_trait_expr (location_t loc, tree expr, tree args) } break; case CPTK_IS_LAYOUT_COMPATIBLE: - inform (loc, "%qT is not layout compatible with %qT", t1, t2); + inform (loc, "%qT is not layout compatible with %qT, because", t1, t2); + layout_compatible_type_p (t1, t2, /*explain=*/true); break; case CPTK_IS_LITERAL_TYPE: inform (decl_loc, "%qT is not a literal type", t1); diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 21befdb7c247..5244370f7c6c 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -8540,7 +8540,7 @@ extern bool same_type_ignoring_top_level_qualifiers_p (tree, tree); extern bool similar_type_p (tree, tree); extern bool cp_comp_parm_types (tree, tree); extern bool next_common_initial_sequence (tree &, tree &); -extern bool layout_compatible_type_p (tree, tree); +extern bool layout_compatible_type_p (tree, tree, bool = false); extern bool compparms (const_tree, const_tree); extern int comp_cv_qualification (const_tree, const_tree); extern int comp_cv_qualification (int, int); diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc index 0b8ef84bf50a..65e08eaa645c 100644 --- a/gcc/cp/typeck.cc +++ b/gcc/cp/typeck.cc @@ -1870,42 +1870,103 @@ next_common_initial_sequence (tree &memb1, tree &memb2) /* Return true if TYPE1 and TYPE2 are layout-compatible types. */ bool -layout_compatible_type_p (tree type1, tree type2) +layout_compatible_type_p (tree type1, tree type2, bool explain/*=false*/) { if (type1 == error_mark_node || type2 == error_mark_node) return false; if (type1 == type2) return true; - if (TREE_CODE (type1) != TREE_CODE (type2)) - return false; type1 = cp_build_qualified_type (type1, TYPE_UNQUALIFIED); type2 = cp_build_qualified_type (type2, TYPE_UNQUALIFIED); + if (same_type_p (type1, type2)) + return true; - if (TREE_CODE (type1) == ENUMERAL_TYPE) - return same_type_p (finish_underlying_type (type1), - finish_underlying_type (type2)); + if (TREE_CODE (type1) != TREE_CODE (type2) + || (TREE_CODE (type1) != ENUMERAL_TYPE + && !CLASS_TYPE_P (type1)) + || (TREE_CODE (type2) != ENUMERAL_TYPE + && !CLASS_TYPE_P (type2))) + { + if (explain) + inform (input_location, "%q#T and %q#T are not both the same type, " + "layout-compatible enumerations, or " + "layout-compatible standard-layout class types", + type1, type2); + return false; + } + + if (!std_layout_type_p (type1)) + { + if (explain) + inform (location_of (type1), + "%qT is not a standard-layout type", type1); + return false; + } + if (!std_layout_type_p (type2)) + { + if (explain) + inform (location_of (type2), + "%qT is not a standard-layout type", type2); + return false; + } - if (CLASS_TYPE_P (type1) - && std_layout_type_p (type1) - && std_layout_type_p (type2)) + if (TREE_CODE (type1) == ENUMERAL_TYPE) + { + tree underlying1 = finish_underlying_type (type1); + tree underlying2 = finish_underlying_type (type2); + if (!same_type_p (underlying1, underlying2)) + { + if (explain) + { + inform (location_of (type1), + "the underlying type of %qT is %qT, but", + type1, underlying1); + inform (location_of (type2), + "the underlying type of %qT is %qT", + type2, underlying2); + } + return false; + } + } + else if (TREE_CODE (type1) == RECORD_TYPE) { tree field1 = TYPE_FIELDS (type1); tree field2 = TYPE_FIELDS (type2); - if (TREE_CODE (type1) == RECORD_TYPE) + while (1) { - while (1) + if (!next_common_initial_sequence (field1, field2)) { - if (!next_common_initial_sequence (field1, field2)) - return false; - if (field1 == NULL_TREE) - return true; - field1 = DECL_CHAIN (field1); - field2 = DECL_CHAIN (field2); + if (explain) + { + if (field1 && field2) + { + inform (DECL_SOURCE_LOCATION (field1), + "%qD and %qD do not correspond", + field1, field2); + inform (DECL_SOURCE_LOCATION (field2), + "%qD declared here", field2); + } + else if (field1) + inform (DECL_SOURCE_LOCATION (field1), + "%qT has no member corresponding to %qD", + type2, field1); + else if (field2) + inform (DECL_SOURCE_LOCATION (field2), + "%qT has no member corresponding to %qD", + type1, field2); + } + return false; } + if (field1 == NULL_TREE) + break; + field1 = DECL_CHAIN (field1); + field2 = DECL_CHAIN (field2); } - /* Otherwise both types must be union types. - The standard says: + } + else if (TREE_CODE (type1) == UNION_TYPE) + { + /* The standard says: "Two standard-layout unions are layout-compatible if they have the same number of non-static data members and corresponding non-static data members (in any order) have layout-compatible @@ -1913,6 +1974,8 @@ layout_compatible_type_p (tree type1, tree type2) but the code anticipates that bitfield vs. non-bitfield, different bitfield widths or presence/absence of [[no_unique_address]] should be checked as well. */ + tree field1 = TYPE_FIELDS (type1); + tree field2 = TYPE_FIELDS (type2); auto_vec<tree, 16> vec; unsigned int count = 0; for (; field1; field1 = DECL_CHAIN (field1)) @@ -1921,11 +1984,26 @@ layout_compatible_type_p (tree type1, tree type2) for (; field2; field2 = DECL_CHAIN (field2)) if (TREE_CODE (field2) == FIELD_DECL) vec.safe_push (field2); + /* Discussions on core lean towards treating multiple union fields of the same type as the same field, so this might need changing in the future. */ if (count != vec.length ()) - return false; + { + if (explain) + { + inform_n (location_of (type1), count, + "%qT has %u field, but", + "%qT has %u fields, but", + type1, count); + inform_n (location_of (type2), vec.length (), + "%qT has %u field", + "%qT has %u fields", + type2, vec.length ()); + } + return false; + } + for (field1 = TYPE_FIELDS (type1); field1; field1 = DECL_CHAIN (field1)) { if (TREE_CODE (field1) != FIELD_DECL) @@ -1959,13 +2037,23 @@ layout_compatible_type_p (tree type1, tree type2) break; } if (j == vec.length ()) - return false; + { + if (explain) + { + inform (DECL_SOURCE_LOCATION (field1), + "%qT has no member corresponding to %qD", + type2, field1); + inform (location_of (type2), "%qT declared here", type2); + } + return false; + } vec.unordered_remove (j); } - return true; } + else + gcc_unreachable (); - return same_type_p (type1, type2); + return true; } /* Returns 1 if TYPE1 is at least as qualified as TYPE2. */ diff --git a/gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C b/gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C new file mode 100644 index 000000000000..6217fcbcff29 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C @@ -0,0 +1,78 @@ +// Test for diagnostics on failed is_layout_compatible. +// { dg-do compile { target c++20 } } + +template <typename T, typename U> +constexpr bool is_layout_compatible_v = __is_layout_compatible (T, U); + +static_assert(is_layout_compatible_v<int, unsigned>); // { dg-error "assert" } +// { dg-message "is not layout compatible" "" { target *-*-* } .-1 } +// { dg-message "same type" "" { target *-*-* } .-2 } + +struct S {}; +static_assert(is_layout_compatible_v<const S, volatile int>); // { dg-error "assert" } +// { dg-message "is not layout compatible" "" { target *-*-* } .-1 } +// { dg-message "same type" "" { target *-*-* } .-2 } + +struct A { + int a; + char b; // { dg-message "'A::b' and 'B::b' do not correspond" } +}; +struct B { + int a; + signed char b; // { dg-message "declared here" } +}; +static_assert(is_layout_compatible_v<A, B>); // { dg-error "assert" } + +struct C { + int : 1; + int c : 7; + int : 0; // { dg-message "'C::<anonymous>' and 'D::g' do not correspond" } + int : 2; +}; +struct D { + int f : 1; + int : 7; + int g : 2; // { dg-message "declared here" } +}; +static_assert(is_layout_compatible_v<C, D>); // { dg-error "assert" } + +struct E { // { dg-message "'E' is not a standard-layout type" } + int a; +private: + int b; +}; +struct F { + int a; +private: + int b; +}; +static_assert(is_layout_compatible_v<E, F>); // { dg-error "assert" } + +union G { + int a; + long long b; + signed char c; // { dg-message "'H' has no member corresponding to 'G::c'" } +}; +union H { // { dg-message "declared here" } + char x; + int y; + long long z; +}; +static_assert(is_layout_compatible_v<G, H>); // { dg-error "assert" } + +union I { // { dg-message "'I' has 2 fields, but" } + int a; + double b; +}; +union J { // { dg-message "'J' has 1 field" } + int c; +}; +static_assert(is_layout_compatible_v<I, J>); // { dg-error "assert" } + +enum K : int { // { dg-message "the underlying type of 'K' is 'int'" } + K0, K1 +}; +enum L : long int { // { dg-message "the underlying type of 'L' is 'long int'" } + L0, L1 +}; +static_assert(is_layout_compatible_v<K, L>); // { dg-error "assert" }
