https://gcc.gnu.org/g:7d11ae1dd95a0296eeb5c14bfe3a5d4ec8873e3b

commit r16-2111-g7d11ae1dd95a0296eeb5c14bfe3a5d4ec8873e3b
Author: Marek Polacek <pola...@redhat.com>
Date:   Tue Jul 8 10:09:36 2025 -0400

    c++: bogus error with union in qualified name [PR83469]
    
    While working on Reflection I noticed that we reject:
    
      union U { int i; };
      constexpr auto r = ^^typename ::U;
    
    which is due to PR83469.  Andrew P. posted a patch in 2021:
    https://gcc.gnu.org/pipermail/gcc-patches/2021-December/586344.html
    for which I had some comments but an updated patch never came.
    
    ~~
    There are a few issues here with typenames and unions (and even struct
    keywords with unions). First in cp_parser_check_class_key,
    we need to allow typenames to name union types and union key
    to be able to use with typenames.
    
    The next issue is we need to record if we had a union key,
    right now we just record it was a struct/class/typename one
    which is wrong.
    ~~
    
    This patch is an updated and cleaned up version; I've also addressed
    a missing bit in pt.cc.
    
            PR c++/83469
            PR c++/93809
    
    gcc/cp/ChangeLog:
    
            * cp-tree.h (UNION_TYPE_P): Define.
            (TYPENAME_IS_UNION_P): Define.
            * decl.cc (struct typename_info): Add union_p field.
            (struct typename_hasher::equal): Compare union_p field.
            (build_typename_type): Use ti.union_p for union_type.  Set
            TYPENAME_IS_UNION_P.
            * error.cc (dump_type) <case TYPENAME_TYPE>: Handle
            TYPENAME_IS_UNION_P.
            * module.cc (trees_out::type_node): Likewise.
            * parser.cc (cp_parser_check_class_key): Allow typename key for 
union
            types and allow union keyword for typename types.
            * pt.cc (tsubst) <case TYPENAME_TYPE>: Don't conflate unions with
            class_type.  For TYPENAME_IS_CLASS_P, check NON_UNION_CLASS_TYPE_P
            rather than CLASS_TYPE_P.  Add TYPENAME_IS_UNION_P handling.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/template/error45.C: Adjust dg-error.
            * g++.dg/warn/Wredundant-tags-3.C: Remove xfail.
            * g++.dg/parse/union1.C: New test.
            * g++.dg/parse/union2.C: New test.
            * g++.dg/parse/union3.C: New test.
            * g++.dg/parse/union4.C: New test.
            * g++.dg/parse/union5.C: New test.
            * g++.dg/parse/union6.C: New test.
    
    Co-authored-by: Andrew Pinski <quic_apin...@quicinc.com>
    Reviewed-by: Jason Merrill <ja...@redhat.com>

Diff:
---
 gcc/cp/cp-tree.h                              | 12 ++++++++++--
 gcc/cp/decl.cc                                | 10 ++++++----
 gcc/cp/error.cc                               |  1 +
 gcc/cp/module.cc                              |  2 ++
 gcc/cp/parser.cc                              |  4 +++-
 gcc/cp/pt.cc                                  | 25 +++++++++++++++++--------
 gcc/testsuite/g++.dg/parse/union1.C           | 19 +++++++++++++++++++
 gcc/testsuite/g++.dg/parse/union2.C           | 19 +++++++++++++++++++
 gcc/testsuite/g++.dg/parse/union3.C           | 19 +++++++++++++++++++
 gcc/testsuite/g++.dg/parse/union4.C           | 12 ++++++++++++
 gcc/testsuite/g++.dg/parse/union5.C           |  5 +++++
 gcc/testsuite/g++.dg/parse/union6.C           |  5 +++++
 gcc/testsuite/g++.dg/template/error45.C       |  2 +-
 gcc/testsuite/g++.dg/warn/Wredundant-tags-3.C |  2 +-
 14 files changed, 120 insertions(+), 17 deletions(-)

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 1b893e23543d..3b92d9af6e1c 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -506,6 +506,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
       LAMBDA_EXPR_STATIC_P (in LAMBDA_EXPR)
       TARGET_EXPR_ELIDING_P (in TARGET_EXPR)
       contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT)
+      TYPENAME_IS_UNION_P (in TYPENAME_TYPE)
    4: IDENTIFIER_MARKED (IDENTIFIER_NODEs)
       TREE_HAS_CONSTRUCTOR (in INDIRECT_REF, SAVE_EXPR, CONSTRUCTOR,
          CALL_EXPR, or FIELD_DECL).
@@ -2354,6 +2355,10 @@ enum languages { lang_c, lang_cplusplus };
 #define NON_UNION_CLASS_TYPE_P(T) \
   (TREE_CODE (T) == RECORD_TYPE && TYPE_LANG_FLAG_5 (T))
 
+/* Nonzero if T is a class type and is a union.  */
+#define UNION_TYPE_P(T) \
+  (TREE_CODE (T) == UNION_TYPE && TYPE_LANG_FLAG_5 (T))
+
 /* Keep these checks in ascending code order.  */
 #define RECORD_OR_UNION_CODE_P(T)      \
   ((T) == RECORD_TYPE || (T) == UNION_TYPE)
@@ -4485,11 +4490,14 @@ get_vec_init_expr (tree t)
 #define TYPENAME_IS_ENUM_P(NODE) \
   (TREE_LANG_FLAG_0 (TYPENAME_TYPE_CHECK (NODE)))
 
-/* True if a TYPENAME_TYPE was declared as a "class", "struct", or
-   "union".  */
+/* True if a TYPENAME_TYPE was declared as a "class" or "struct".  */
 #define TYPENAME_IS_CLASS_P(NODE) \
   (TREE_LANG_FLAG_1 (TYPENAME_TYPE_CHECK (NODE)))
 
+/* True if a TYPENAME_TYPE was declared as a "union".  */
+#define TYPENAME_IS_UNION_P(NODE) \
+  (TREE_LANG_FLAG_3 (TYPENAME_TYPE_CHECK (NODE)))
+
 /* True if a TYPENAME_TYPE is in the process of being resolved.  */
 #define TYPENAME_IS_RESOLVING_P(NODE) \
   (TREE_LANG_FLAG_2 (TYPENAME_TYPE_CHECK (NODE)))
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 99b9854210f7..0e6afbe56527 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -4370,6 +4370,7 @@ struct typename_info {
   tree template_id;
   bool enum_p;
   bool class_p;
+  bool union_p;
 };
 
 struct typename_hasher : ggc_ptr_hash<tree_node>
@@ -4408,7 +4409,8 @@ struct typename_hasher : ggc_ptr_hash<tree_node>
            && TYPE_CONTEXT (t1) == t2->scope
            && TYPENAME_TYPE_FULLNAME (t1) == t2->template_id
            && TYPENAME_IS_ENUM_P (t1) == t2->enum_p
-           && TYPENAME_IS_CLASS_P (t1) == t2->class_p);
+           && TYPENAME_IS_CLASS_P (t1) == t2->class_p
+           && TYPENAME_IS_UNION_P (t1) == t2->union_p);
   }
 };
 
@@ -4432,9 +4434,8 @@ build_typename_type (tree context, tree name, tree 
fullname,
   ti.name = name;
   ti.template_id = fullname;
   ti.enum_p = tag_type == enum_type;
-  ti.class_p = (tag_type == class_type
-               || tag_type == record_type
-               || tag_type == union_type);
+  ti.class_p = (tag_type == class_type || tag_type == record_type);
+  ti.union_p = tag_type == union_type;
   hashval_t hash = typename_hasher::hash (&ti);
 
   /* See if we already have this type.  */
@@ -4450,6 +4451,7 @@ build_typename_type (tree context, tree name, tree 
fullname,
       TYPENAME_TYPE_FULLNAME (t) = ti.template_id;
       TYPENAME_IS_ENUM_P (t) = ti.enum_p;
       TYPENAME_IS_CLASS_P (t) = ti.class_p;
+      TYPENAME_IS_UNION_P (t) = ti.union_p;
 
       /* Build the corresponding TYPE_DECL.  */
       tree d = build_decl (input_location, TYPE_DECL, name, t);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index abeb0285eec6..eb2ff33ac308 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -810,6 +810,7 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags)
       pp_cxx_ws_string (pp,
                         TYPENAME_IS_ENUM_P (t) ? "enum"
                         : TYPENAME_IS_CLASS_P (t) ? "class"
+                        : TYPENAME_IS_UNION_P (t) ? "union"
                         : "typename");
       dump_typename (pp, t, flags);
       break;
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index c8e79f33af41..6b5a60a3f3ec 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -9619,6 +9619,8 @@ trees_out::type_node (tree type)
              tag_type = enum_type;
            else if (TYPENAME_IS_CLASS_P (type))
              tag_type = class_type;
+           else if (TYPENAME_IS_UNION_P (type))
+             tag_type = union_type;
            u (int (tag_type));
          }
        }
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 32c6a42b31de..8148495f9887 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -35928,7 +35928,9 @@ cp_parser_check_class_key (cp_parser *parser, 
location_t key_loc,
     return;
 
   bool seen_as_union = TREE_CODE (type) == UNION_TYPE;
-  if (seen_as_union != (class_key == union_type))
+  if (class_key != typename_type
+      && TREE_CODE (type) != TYPENAME_TYPE
+      && seen_as_union != (class_key == union_type))
     {
       auto_diagnostic_group d;
       if (permerror (input_location, "%qs tag used in naming %q#T",
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 3362a6f8f9ca..40ce987a6e81 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -17250,13 +17250,14 @@ tsubst (tree t, tree args, tsubst_flags_t complain, 
tree in_decl)
              return error_mark_node;
          }
 
-       /* FIXME: TYPENAME_IS_CLASS_P conflates 'class' vs 'struct' vs 'union'
-          tags.  TYPENAME_TYPE should probably remember the exact tag that
-          was written.  */
+       /* FIXME: TYPENAME_IS_CLASS_P conflates 'class' vs 'struct' tags.
+          TYPENAME_TYPE should probably remember the exact tag that
+          was written for -Wmismatched-tags.  */
        enum tag_types tag_type
-         = TYPENAME_IS_CLASS_P (t) ? class_type
-         : TYPENAME_IS_ENUM_P (t) ? enum_type
-         : typename_type;
+         = (TYPENAME_IS_CLASS_P (t) ? class_type
+            : TYPENAME_IS_UNION_P (t) ? union_type
+            : TYPENAME_IS_ENUM_P (t) ? enum_type
+            : typename_type);
        tsubst_flags_t tcomplain = complain | tf_keep_type_decl;
        tcomplain |= tst_ok_flag | qualifying_scope_flag;
        f = make_typename_type (ctx, f, tag_type, tcomplain);
@@ -17278,10 +17279,18 @@ tsubst (tree t, tree args, tsubst_flags_t complain, 
tree in_decl)
                else
                  return error_mark_node;
              }
-           else if (TYPENAME_IS_CLASS_P (t) && !CLASS_TYPE_P (f))
+           else if (TYPENAME_IS_CLASS_P (t) && !NON_UNION_CLASS_TYPE_P (f))
              {
                if (complain & tf_error)
-                 error ("%qT resolves to %qT, which is not a class type",
+                 error ("%qT resolves to %qT, which is not a non-union "
+                        "class type", t, f);
+               else
+                 return error_mark_node;
+             }
+           else if (TYPENAME_IS_UNION_P (t) && !UNION_TYPE_P (f))
+             {
+               if (complain & tf_error)
+                 error ("%qT resolves to %qT, which is not a union type",
                         t, f);
                else
                  return error_mark_node;
diff --git a/gcc/testsuite/g++.dg/parse/union1.C 
b/gcc/testsuite/g++.dg/parse/union1.C
new file mode 100644
index 000000000000..d567ea3cab03
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/union1.C
@@ -0,0 +1,19 @@
+// PR c++/83469
+// { dg-do compile }
+
+struct S {
+  union U { int m; };
+};
+
+template <typename T>
+void
+f ()
+{
+  union T::U u;
+}
+
+int
+main()
+{
+  f<S>();
+}
diff --git a/gcc/testsuite/g++.dg/parse/union2.C 
b/gcc/testsuite/g++.dg/parse/union2.C
new file mode 100644
index 000000000000..cdb1392b3e77
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/union2.C
@@ -0,0 +1,19 @@
+// PR c++/83469
+// { dg-do compile }
+
+struct S {
+  union U { int m; };
+};
+
+template <typename T>
+void
+f ()
+{
+  struct T::U u;  // { dg-error "not a non-union class type" }
+}
+
+int
+main()
+{
+  f<S>();
+}
diff --git a/gcc/testsuite/g++.dg/parse/union3.C 
b/gcc/testsuite/g++.dg/parse/union3.C
new file mode 100644
index 000000000000..61552a426efb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/union3.C
@@ -0,0 +1,19 @@
+// PR c++/83469
+// { dg-do compile }
+
+struct S {
+  struct C { int m; };
+};
+
+template <typename T>
+void
+f ()
+{
+  union T::C u; // { dg-error "not a union type" }
+}
+
+int
+main()
+{
+  f<S>();
+}
diff --git a/gcc/testsuite/g++.dg/parse/union4.C 
b/gcc/testsuite/g++.dg/parse/union4.C
new file mode 100644
index 000000000000..709f6a48974a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/union4.C
@@ -0,0 +1,12 @@
+// PR c++/93809
+// { dg-do compile }
+
+class C { };
+enum E { };
+struct S { };
+union U { };
+
+typedef typename ::C C2;
+typedef typename ::E E2;
+typedef typename ::S S2;
+typedef typename ::U U2;
diff --git a/gcc/testsuite/g++.dg/parse/union5.C 
b/gcc/testsuite/g++.dg/parse/union5.C
new file mode 100644
index 000000000000..18238dd4afbb
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/union5.C
@@ -0,0 +1,5 @@
+// PR c++/93809
+// { dg-do compile { target c++11 } }
+
+union U {};
+auto var = new (typename ::U);
diff --git a/gcc/testsuite/g++.dg/parse/union6.C 
b/gcc/testsuite/g++.dg/parse/union6.C
new file mode 100644
index 000000000000..61b9568b9520
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/union6.C
@@ -0,0 +1,5 @@
+// PR c++/93809
+// { dg-do compile }
+
+typedef union{} U;
+typename ::U foo () { return U();  }
diff --git a/gcc/testsuite/g++.dg/template/error45.C 
b/gcc/testsuite/g++.dg/template/error45.C
index 064554dfe71f..f4c65608c340 100644
--- a/gcc/testsuite/g++.dg/template/error45.C
+++ b/gcc/testsuite/g++.dg/template/error45.C
@@ -11,7 +11,7 @@ struct enable_if< true, T >
 
 template < typename T >
 struct enable_if< true, T >::type
-f( T x );                      // { dg-error "not a class type" }
+f( T x );                      // { dg-error "not a non-union class type" }
 
 void
 g( void )
diff --git a/gcc/testsuite/g++.dg/warn/Wredundant-tags-3.C 
b/gcc/testsuite/g++.dg/warn/Wredundant-tags-3.C
index 0eeee34dae75..dcccdcad22d0 100644
--- a/gcc/testsuite/g++.dg/warn/Wredundant-tags-3.C
+++ b/gcc/testsuite/g++.dg/warn/Wredundant-tags-3.C
@@ -28,7 +28,7 @@ struct N::S s3;       // { dg-warning "-Wredundant-tags" }
 
 N::U u1;
 typename N::U u2;     // { dg-bogus "-Wredundant-tags" }
-                      // { dg-bogus "'class' tag used in naming 'union N::U" 
"pr93809" { xfail *-*-*} .-1 }
+                      // { dg-bogus "'class' tag used in naming 'union N::U" 
"pr93809" { target *-*-*} .-1 }
 union N::U u3;        // { dg-warning "-Wredundant-tags" }

Reply via email to