https://gcc.gnu.org/g:f8152db38660061623150b346d84765676e92844

commit r16-7903-gf8152db38660061623150b346d84765676e92844
Author: Jakub Jelinek <[email protected]>
Date:   Thu Mar 5 09:19:59 2026 +0100

    c++: Fix up handling of unnamed types named by typedef for linkage purposes 
for -freflection [PR123810]
    
    As mentioned on the PR, we ICE on the following testcase and if members_of
    isn't called on a class with e.g. typedef struct { int d; } D;, we don't
    handle it correctly, e.g. we say ^^C::D is not an type alias or for
    members_of in a namespace that there aren't two entities, the struct itself
    and the type alias for it.
    This is because name_unnamed_type handles the naming of an unnamed type
    through typedef for linkage purposes (where we originally have
    a TYPE_DECL with IDENTIFIER_ANON_P DECL_NAME for the type) by replacing
    all occurrences of TYPE_NAME on the type from the old TYPE_DECL to the new
    TYPE_DECL with the user provided name.
    The ICE for members_of (^^C, uctx) is then because we see two TYPE_DECLs
    (one with IDENTIFIER_ANON_P, one with user name) with the same TREE_TYPE
    and enter the same thing twice into what we want to return and ICE in the
    comparison routine.  Anyway, for is_type_alias purposes, there is no
    is_typedef_decl and can't be because the same TYPE_DECL is used as TYPE_NAME
    of both the type proper and its alias.  Without reflection we didn't care
    about the difference.
    
    So, the following patch changes name_unnamed_type to do things differently,
    but only for -freflection, because 1) I don't want to break stuff late in
    stage4 2) without reflection we don't really need it and don't need to
    pay the extra memory cost by having another type which is the type alias.
    The change is that instead of
    TYPE_DECL .anon_NN
      | TREE_TYPE
      v
    type <----------+
      | TYPE_NAME   |
      v             |
    TYPE_DECL D     |
      | TREE_TYPE   |
      +-------------+
    where for class context both TYPE_DECLs are in TYPE_FIELDS and for
    namespace context only the latter one is (as pushdecl ignores the
    IDENTIFIER_ANON_P one) we have
    TYPE_DECL D    TYPE_DECL D --- DECL_ORIGINAL_TYPE
      | TREE_TYPE    | TREE_TYPE      |
      v              v                |
    type           variant_type       |
      ^-------------------------------+
    which is except for the same DECL_NAME on both TYPE_DECLs exactly what
    is used for typedef struct D_ { int d; } D;
    Various spots have been testing for the typedef name for linkage purposes
    cases and were using tests like:
    OVERLOAD_TYPE_P (TREE_TYPE (value))
    && value == TYPE_NAME (TYPE_MAIN_VARIANT (TREE_TYPE (value)))
    So that this can be tested, this patch introduces a new decl_flag on
    the TYPE_DECLs and marks for -freflection both of these TYPE_DECLs
    (and for -fno-reflection the one without IDENTIFIER_ANON_P name).
    It is easy to differentiate between the two, the first one is also
    DECL_IMPLICIT_TYPEDEF_P, the latter is not (and on the other side
    has DECL_ORIGINAL_TYPE non-NULL).
    For name lookup in namespaces, nothing special needs to be done,
    because the originally IDENTIFIER_ANON_P TYPE_DECL wasn't added
    to the bindings, at block scope I had to deal with it in pop_local_binding
    because it was unhappy that it got renamed.  And finally for class
    scopes, we need to arrange for the latter TYPE_DECL to be found, but
    currently it is the second one.  The patch currently skips the first one for
    name lookup in fields_linear_search and arranges for count_class_fields
    and member_vec_append_class_fields to also ignore the first one.  Wonder if
    the latter two shouldn't also ignore any other IDENTIFIER_ANON_P TYPE_FIELDS
    chain decls, or do we ever perform name lookup for the anon identifiers?
    Another option for fields_linear_search would be try to swap the order of
    the two TYPE_DECLs in TYPE_FIELDS chain somewhere in grokfield.
    
    Anyway, the changes result in minor emitted DWARF changes, say for
    g++.dg/debug/dwarf2/typedef1.C without -freflection there is
            .uleb128 0x4    # (DIE (0x46) DW_TAG_enumeration_type)
            .long   .LASF6  # DW_AT_name: "typedef foo<1>::type type"
            .byte   0x7     # DW_AT_encoding
            .byte   0x4     # DW_AT_byte_size
            .long   0x70    # DW_AT_type
            .byte   0x1     # DW_AT_decl_file (typedef1.C)
            .byte   0x18    # DW_AT_decl_line
            .byte   0x12    # DW_AT_decl_column
            .long   .LASF7  # DW_AT_MIPS_linkage_name: "N3fooILj1EE4typeE"
    ...
    and no typedef, while with -freflection there is
            .uleb128 0x3    # (DIE (0x3a) DW_TAG_enumeration_type)
            .long   .LASF5  # DW_AT_name: "type"
            .byte   0x7     # DW_AT_encoding
            .byte   0x4     # DW_AT_byte_size
            .long   0x6c    # DW_AT_type
            .byte   0x1     # DW_AT_decl_file (typedef1.C)
            .byte   0x18    # DW_AT_decl_line
            .byte   0x12    # DW_AT_decl_column
    ...
            .uleb128 0x5    # (DIE (0x57) DW_TAG_typedef)
            .long   .LASF5  # DW_AT_name: "type"
            .byte   0x1     # DW_AT_decl_file (typedef1.C)
            .byte   0x18    # DW_AT_decl_line
            .byte   0x1d    # DW_AT_decl_column
            .long   0x3a    # DW_AT_type
    so, different DW_AT_name on the DW_TAG_enumeration_type, missing
    DW_AT_MIPS_linkage_name and an extra DW_TAG_typedef.  While in theory
    I could work harder to hide that detail, I actually think it is a good
    thing to have it the latter way because it represents more exactly
    what is going on.
    Another slight change is different locations in some diagnostics
    on g++.dg/lto/odr-3 test (location of the unnamed struct vs. locations
    of the typedef name given to it without -freflection), and a module
    issue which Nathan has some WIP patch for in
    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=123810#c11
    
    In any case, none of those differences show up in normal testsuite runs
    currently (as those tests aren't compiled with -freflection), if/when
    -freflection becomes the default for -std=c++26 we can deal with the
    DWARF one as well as different locations in odr-3 and for modules I was
    hoping it could be handled incrementally.  I'm not even sure what should
    happen if one TU has struct D { int d; }; and another one has
    typedef struct { int d; } D;, shall that be some kind of error?  Though
    right now typedef struct { int d; } D; in both results in an error too
    and that definitely needs to be handled.
    
    2026-03-05  Jakub Jelinek  <[email protected]>
    
            PR c++/123810
            * cp-tree.h (TYPE_DECL_FOR_LINKAGE_PURPOSES_P): Define.
            (TYPE_DECL_WAS_UNNAMED): Likewise.
            (TYPE_WAS_UNNAMED): Also check TYPE_DECL_WAS_UNNAMED.
            * decl.cc (start_decl): Use TYPE_DECL_FOR_LINKAGE_PURPOSES_P.
            (maybe_diagnose_non_c_class_typedef_for_l): If t == type, use
            DECL_SOURCE_LOCATION (orig) instead of
            DECL_SOURCE_LOCATION (TYPE_NAME (t)).
            (name_unnamed_type): Set TYPE_DECL_FOR_LINKAGE_PURPOSES_P
            on decl.  For -freflection don't change TYPE_NAME from
            orig to decl, but instead change DECL_NAME (orig) to
            DECL_NAME (decl) and set TYPE_DECL_FOR_LINKAGE_PURPOSES_P on
            orig too.
            * decl2.cc (grokfield): Use TYPE_DECL_FOR_LINKAGE_PURPOSES_P.
            * name-lookup.cc (fields_linear_search): Ignore
            TYPE_DECL_WAS_UNNAMED decls.
            (count_class_fields): Likewise.
            (member_vec_append_class_fields): Likewise.
            (pop_local_binding): Likewise.
            * reflect.cc (namespace_members_of): For TYPE_DECL with
            TYPE_DECL_FOR_LINKAGE_PURPOSES_P set also append
            reflection of strip_typedefs (m).
            * class.cc (find_flexarrays): Handle TYPE_DECLs with
            TYPE_DECL_WAS_UNNAMED like the ones with IDENTIFIER_ANON_P
            name.
    
            * g++.dg/reflect/members_of10.C: New test.
            * g++.dg/cpp2a/typedef1.C: Expect one message on a different line.

Diff:
---
 gcc/cp/class.cc                             |  3 +-
 gcc/cp/cp-tree.h                            | 22 ++++++++++--
 gcc/cp/decl.cc                              | 26 +++++++++-----
 gcc/cp/decl2.cc                             |  3 +-
 gcc/cp/name-lookup.cc                       | 21 +++++++++--
 gcc/cp/reflect.cc                           |  8 +++++
 gcc/testsuite/g++.dg/cpp2a/typedef1.C       |  4 +--
 gcc/testsuite/g++.dg/reflect/members_of10.C | 56 +++++++++++++++++++++++++++++
 8 files changed, 126 insertions(+), 17 deletions(-)

diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index a223f71dbbe9..8beeb797a08b 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -7608,7 +7608,8 @@ find_flexarrays (tree t, flexmems_t *fmem, bool base_p,
       if (TREE_CODE (fld) == TYPE_DECL
          && DECL_IMPLICIT_TYPEDEF_P (fld)
          && CLASS_TYPE_P (TREE_TYPE (fld))
-         && IDENTIFIER_ANON_P (DECL_NAME (fld)))
+         && (IDENTIFIER_ANON_P (DECL_NAME (fld))
+             || TYPE_DECL_WAS_UNNAMED (fld)))
        {
          /* Check the nested unnamed type referenced via a typedef
             independently of FMEM (since it's not a data member of
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index b1291c269d74..7f31aca47b0a 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -591,6 +591,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
    7: DECL_THUNK_P (in a member FUNCTION_DECL)
       DECL_NORMAL_CAPTURE_P (in FIELD_DECL)
       DECL_DECLARED_CONSTINIT_P (in VAR_DECL)
+      TYPE_DECL_FOR_LINKAGE_PURPOSES_P (in TYPE_DECL)
    8: DECL_DECLARED_CONSTEXPR_P (in VAR_DECL, FUNCTION_DECL)
 
    Usage of language-independent fields in a language-dependent manner:
@@ -4000,6 +4001,20 @@ struct GTY(()) lang_decl {
    && TREE_CODE (TYPE_NAME (NODE)) == TYPE_DECL        \
    && TYPE_DECL_ALIAS_P (TYPE_NAME (NODE)))
 
+/* Nonzero for typedef name for linkage purposes.  For -freflection
+   set also on the originally unnamed TYPE_DECL.  */
+#define TYPE_DECL_FOR_LINKAGE_PURPOSES_P(NODE) \
+  DECL_LANG_FLAG_7 (TYPE_DECL_CHECK (NODE))
+
+/* Nonzero for TYPE_DECL originally with IDENTIFIER_ANON_P DECL_NAME
+   later on named by a typedef name for linkage purposes in the
+   -freflection case (otherwise the TYPE_DECL keeps IDENTIFIER_ANON_P
+   DECL_NAME).  */
+#define TYPE_DECL_WAS_UNNAMED(NODE) \
+  (TREE_CODE (NODE) == TYPE_DECL \
+   && TYPE_DECL_FOR_LINKAGE_PURPOSES_P (NODE) \
+   && DECL_IMPLICIT_TYPEDEF_P (NODE))
+
 /* If non-NULL for a VAR_DECL, FUNCTION_DECL, TYPE_DECL, TEMPLATE_DECL,
    or CONCEPT_DECL, the entity is either a template specialization (if
    DECL_USE_TEMPLATE is nonzero) or the abstract instance of the
@@ -5372,10 +5387,13 @@ get_vec_init_expr (tree t)
 
 /* True if TYPE is an unnamed structured type with a typedef for
    linkage purposes.  In that case TYPE_NAME and TYPE_STUB_DECL of the
-   MAIN-VARIANT are different.  */
+   MAIN-VARIANT are different or TYPE_DECL_WAS_UNNAMED
+   is true for the TYPE_NAME.  */
 #define TYPE_WAS_UNNAMED(NODE)                         \
   (TYPE_NAME (TYPE_MAIN_VARIANT (NODE))                        \
-   != TYPE_STUB_DECL (TYPE_MAIN_VARIANT (NODE)))
+   != TYPE_STUB_DECL (TYPE_MAIN_VARIANT (NODE))                \
+   || TYPE_DECL_WAS_UNNAMED            \
+       (TYPE_NAME (TYPE_MAIN_VARIANT (NODE))))
 
 /* C++: all of these are overloaded!  These apply only to TYPE_DECLs.  */
 
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 6b210a30b6a8..40e71bfb30a0 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -6618,8 +6618,7 @@ start_decl (const cp_declarator *declarator,
   /* If this is a typedef that names the class for linkage purposes
      (7.1.3p8), apply any attributes directly to the type.  */
   if (TREE_CODE (decl) == TYPE_DECL
-      && OVERLOAD_TYPE_P (TREE_TYPE (decl))
-      && decl == TYPE_NAME (TYPE_MAIN_VARIANT (TREE_TYPE (decl))))
+      && TYPE_DECL_FOR_LINKAGE_PURPOSES_P (decl))
     flags = ATTR_FLAG_TYPE_IN_PLACE;
   else
     flags = 0;
@@ -13731,7 +13730,8 @@ maybe_diagnose_non_c_class_typedef_for_linkage (tree 
type, tree orig, tree t)
     {
       auto_diagnostic_group d;
       if (diagnose_non_c_class_typedef_for_linkage (type, orig))
-       inform (DECL_SOURCE_LOCATION (TYPE_NAME (t)),
+       inform (type == t ? DECL_SOURCE_LOCATION (orig)
+               : DECL_SOURCE_LOCATION (TYPE_NAME (t)),
                "type is not C-compatible because it has a base class");
       return true;
     }
@@ -13797,12 +13797,22 @@ name_unnamed_type (tree type, tree decl)
   gcc_assert (TYPE_UNNAMED_P (type)
              || enum_with_enumerator_for_linkage_p (type));
 
-  /* Replace the anonymous decl with the real decl.  Be careful not to
-     rename other typedefs (such as the self-reference) of type.  */
   tree orig = TYPE_NAME (type);
-  for (tree t = TYPE_MAIN_VARIANT (type); t; t = TYPE_NEXT_VARIANT (t))
-    if (TYPE_NAME (t) == orig)
-      TYPE_NAME (t) = decl;
+  if (flag_reflection)
+    {
+      /* For -freflection for typedef struct { ... } S; ^^S needs to be
+        a reflection of a type alias.  So, TREE_TYPE (DECL) can't be
+        TYPE.  Instead of what we do below, override DECL_NAME (orig).  */
+      DECL_NAME (orig) = DECL_NAME (decl);
+      TYPE_DECL_FOR_LINKAGE_PURPOSES_P (orig) = 1;
+    }
+  else
+    /* Replace the anonymous decl with the real decl.  Be careful not to
+       rename other typedefs (such as the self-reference) of type.  */
+    for (tree t = TYPE_MAIN_VARIANT (type); t; t = TYPE_NEXT_VARIANT (t))
+      if (TYPE_NAME (t) == orig)
+       TYPE_NAME (t) = decl;
+  TYPE_DECL_FOR_LINKAGE_PURPOSES_P (decl) = 1;
 
   /* If this is a typedef within a template class, the nested
      type is a (non-primary) template.  The name for the
diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc
index 20ee662eea22..351e1c3766fa 100644
--- a/gcc/cp/decl2.cc
+++ b/gcc/cp/decl2.cc
@@ -1303,8 +1303,7 @@ grokfield (const cp_declarator *declarator,
 
          /* If this is a typedef that names the class for linkage purposes
             (7.1.3p8), apply any attributes directly to the type.  */
-         if (OVERLOAD_TYPE_P (TREE_TYPE (value))
-             && value == TYPE_NAME (TYPE_MAIN_VARIANT (TREE_TYPE (value))))
+         if (TYPE_DECL_FOR_LINKAGE_PURPOSES_P (value))
            attrflags = ATTR_FLAG_TYPE_IN_PLACE;
 
          cplus_decl_attributes (&value, attrlist, attrflags);
diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc
index b7fc43391cf2..b8d7e7ded548 100644
--- a/gcc/cp/name-lookup.cc
+++ b/gcc/cp/name-lookup.cc
@@ -1869,6 +1869,11 @@ fields_linear_search (tree klass, tree name, bool 
want_type)
            continue;
        }
 
+      if (TYPE_DECL_WAS_UNNAMED (decl))
+       /* Ignore DECL_NAME given to unnamed TYPE_DECLs named for linkage
+          purposes.  */
+       continue;
+
       if (DECL_DECLARES_FUNCTION_P (decl))
        /* Functions are found separately.  */
        continue;
@@ -2345,7 +2350,13 @@ count_class_fields (tree klass)
             && ANON_AGGR_TYPE_P (TREE_TYPE (fields)))
       n_fields += count_class_fields (TREE_TYPE (fields));
     else if (DECL_NAME (fields))
-      n_fields += 1;
+      {
+       if (TYPE_DECL_WAS_UNNAMED (fields))
+         /* Ignore DECL_NAME given to unnamed TYPE_DECLs named for linkage
+            purposes.  */
+         continue;
+       n_fields += 1;
+      }
 
   return n_fields;
 }
@@ -2369,6 +2380,10 @@ member_vec_append_class_fields (vec<tree, va_gc> 
*member_vec, tree klass)
        if (TREE_CODE (field) == USING_DECL
            && IDENTIFIER_CONV_OP_P (DECL_NAME (field)))
          field = ovl_make (conv_op_marker, field);
+       else if (TYPE_DECL_WAS_UNNAMED (field))
+         /* Ignore DECL_NAME given to unnamed TYPE_DECLs named for linkage
+            purposes.  */
+         continue;
        member_vec->quick_push (field);
       }
 }
@@ -2700,7 +2715,9 @@ pop_local_binding (tree id, tree decl)
     binding->value = NULL_TREE;
   else if (binding->type == decl)
     binding->type = NULL_TREE;
-  else
+  /* Ignore DECL_NAME given to unnamed TYPE_DECLs named for linkage
+     purposes.  */
+  else if (!TYPE_DECL_WAS_UNNAMED (decl))
     {
       /* Name-independent variable was found after at least one declaration
         with the same name.  */
diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc
index 90bac5baa021..e36548e6c386 100644
--- a/gcc/cp/reflect.cc
+++ b/gcc/cp/reflect.cc
@@ -6549,6 +6549,14 @@ namespace_members_of (location_t loc, tree ns)
             so don't bother calling it here.  */
          CONSTRUCTOR_APPEND_ELT (elts, NULL_TREE,
                                  get_reflection_raw (loc, m));
+         /* For typedef struct { ... } S; include both the S type
+            alias (added above) and dealias of that for the originally
+            unnamed type (added below).  */
+         if (TREE_CODE (b) == TYPE_DECL
+             && TYPE_DECL_FOR_LINKAGE_PURPOSES_P (b))
+           CONSTRUCTOR_APPEND_ELT (elts, NULL_TREE,
+                                   get_reflection_raw (loc,
+                                                       strip_typedefs (m)));
        }
     }
   delete seen;
diff --git a/gcc/testsuite/g++.dg/cpp2a/typedef1.C 
b/gcc/testsuite/g++.dg/cpp2a/typedef1.C
index 10a053f4f4d0..c780e1cd2b29 100644
--- a/gcc/testsuite/g++.dg/cpp2a/typedef1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/typedef1.C
@@ -33,8 +33,8 @@ typedef struct {                                      // { 
dg-message "unnamed class defined here" }
   static int a;                                                // { dg-error 
"static data member '<unnamed struct>::a' in unnamed class" }
 } B;
 typedef struct : public A {                            // { dg-error 
"anonymous non-C-compatible type given name for linkage purposes by 'typedef' 
declaration" }
-  int a;
-} C;                                                   // { dg-message "type 
is not C-compatible because it has a base class" }
+  int a;                                               // { dg-message "type 
is not C-compatible because it has a base class" "" { target *-*-* } .-1 }
+} C;
 #if __cplusplus >= 201103L
 typedef struct {                                       // { dg-error 
"anonymous non-C-compatible type given name for linkage purposes by 'typedef' 
declaration" "" { target c++11 } }
   int b = 42;                                          // { dg-message "type 
is not C-compatible because 'D::b' has default member initializer" "" { target 
c++11 } }
diff --git a/gcc/testsuite/g++.dg/reflect/members_of10.C 
b/gcc/testsuite/g++.dg/reflect/members_of10.C
new file mode 100644
index 000000000000..b17ba346d755
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/members_of10.C
@@ -0,0 +1,56 @@
+// PR c++/123810
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+namespace A {
+  typedef struct { int b; } B;
+}
+struct C {
+  typedef struct { int d; } D;
+};
+struct F {
+  typedef struct { int g; } G;
+  typedef struct { int h; } H;
+  typedef struct { int i; } I;
+  typedef struct { int j; } J;
+  typedef struct { int k; } K;
+  typedef struct { int l; } L;
+  typedef struct { int m; } M;
+  typedef struct { int n; } N;
+};
+void
+foo ()
+{
+  typedef struct { int e; } E;
+  static_assert (is_type_alias (^^E));
+}
+
+static_assert (is_type_alias (^^A::B));
+static_assert (is_type_alias (^^C::D));
+static_assert (is_type_alias (^^F::G));
+static_assert (is_type_alias (^^F::N));
+constexpr auto uctx = std::meta::access_context::unchecked ();
+static_assert (members_of (^^C, uctx)[0] == dealias (^^C::D));
+static_assert (members_of (^^C, uctx)[1] == ^^C::D);
+static_assert (members_of (^^F, uctx)[0] == dealias (^^F::G));
+static_assert (members_of (^^F, uctx)[1] == ^^F::G);
+static_assert (members_of (^^F, uctx)[2] == dealias (^^F::H));
+static_assert (members_of (^^F, uctx)[3] == ^^F::H);
+static_assert (members_of (^^F, uctx)[4] == dealias (^^F::I));
+static_assert (members_of (^^F, uctx)[5] == ^^F::I);
+static_assert (members_of (^^F, uctx)[6] == dealias (^^F::J));
+static_assert (members_of (^^F, uctx)[7] == ^^F::J);
+static_assert (members_of (^^F, uctx)[8] == dealias (^^F::K));
+static_assert (members_of (^^F, uctx)[9] == ^^F::K);
+static_assert (members_of (^^F, uctx)[10] == dealias (^^F::L));
+static_assert (members_of (^^F, uctx)[11] == ^^F::L);
+static_assert (members_of (^^F, uctx)[12] == dealias (^^F::M));
+static_assert (members_of (^^F, uctx)[13] == ^^F::M);
+static_assert (members_of (^^F, uctx)[14] == dealias (^^F::N));
+static_assert (members_of (^^F, uctx)[15] == ^^F::N);
+static_assert ((members_of (^^A, uctx)[0] == dealias (^^A::B)
+               && members_of (^^A, uctx)[1] == ^^A::B)
+              || ((members_of (^^A, uctx)[0] == ^^A::B)
+                  && members_of (^^A, uctx)[1] == dealias (^^A::B)));

Reply via email to