On 10/26/25 7:33 AM, Nathaniel Shead wrote:
OK for trunk?

-- >8 --

This reorders some checks in layout_compatible_type_p to promote more
useful diagnostics as well and try to avoid duplicate code.

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]>
---
  gcc/cp/constraint.cc                          |   3 +-
  gcc/cp/cp-tree.h                              |   2 +-
  gcc/cp/typeck.cc                              | 146 +++++++++++++++---
  .../g++.dg/cpp2a/is-layout-compatible4.C      |  86 +++++++++++
  4 files changed, 211 insertions(+), 26 deletions(-)
  create mode 100644 gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C

diff --git a/gcc/cp/constraint.cc b/gcc/cp/constraint.cc
index 1ab5a2902d3..f55cae37007 100644
--- a/gcc/cp/constraint.cc
+++ b/gcc/cp/constraint.cc
@@ -3186,7 +3186,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 844dc3a577e..339ea062cd0 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -8550,7 +8550,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 dbadeb77085..97e96d0b045 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -1870,44 +1870,106 @@ 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) != 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 (TREE_CODE (type1) == ENUMERAL_TYPE)
-    return (tree_int_cst_equal (TYPE_SIZE (type1), TYPE_SIZE (type2))
-           && same_type_p (finish_underlying_type (type1),
-                           finish_underlying_type (type2)));
+    {
+      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
+    gcc_checking_assert (CLASS_TYPE_P (type1) && CLASS_TYPE_P (type2));
- if (CLASS_TYPE_P (type1)
-      && std_layout_type_p (type1)
-      && std_layout_type_p (type2)
-      && tree_int_cst_equal (TYPE_SIZE (type1), TYPE_SIZE (type2)))
+  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 (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
@@ -1915,6 +1977,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))
@@ -1923,11 +1987,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)
@@ -1961,13 +2040,32 @@ 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;
      }
- return same_type_p (type1, type2);
+  if (!tree_int_cst_equal (TYPE_SIZE (type1), TYPE_SIZE (type2)))
+    {
+      if (explain)
+       {
+         inform (location_of (type1), "%qT and %qT have different sizes",
+                 type1, type2);
+         inform (location_of (type2), "%qT declared here", type2);
+       }
+      return false;
+    }
+
+  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 00000000000..7ee5cbd6d07
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/is-layout-compatible4.C
@@ -0,0 +1,86 @@
+// 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;

Hmm, I'm surprised that we consider named and unnamed bit-fields as corresponding, but I guess the spec doesn't say otherwise.

+  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" }
+
+struct M {  // { dg-message "different sizes" }

I think it would be clearer to refer to the alignment requirements.

Jason

Reply via email to