On Sat, 13 Dec 2025, Jason Merrill wrote:

> On 12/13/25 11:23 PM, Patrick Palka wrote:
> > On Sat, 13 Dec 2025, Jason Merrill wrote:
> > 
> > > On 12/12/25 10:39 PM, Patrick Palka wrote:
> > > > We currently have 7 different tag_types, but our TYPENAME_TYPE
> > > > representation only differentiates between four of them:
> > > > class/enum/union/typename.  This patch makes our representation and
> > > > cache of TYPENAME_TYPE remember the tag_type fully so that later
> > > > substitution/resolution of the TYPENAME_TYPE is sound.
> > > 
> > > What do you mean by "sound"?  This only makes a difference for
> > > -Wmismatched-tags, right?
> > 
> > True for record_type vs class_type which we now differentiate.
> > 
> > But I there's also scope_type tag_type which our current representation
> > of TYPENAME_TYPE can't encode, and so we effectively encode as
> > none_type.  But scope_type indicates a type-only lookup context unlike
> > none_type.
> 
> Ah, right, please mention scope_type in the commit message.  Did my other
> comments make sense?

Done.

> 
> > So building a TYPENAME_TYPE with tag_type=scope_type and then resolving
> > it (i.e. build_typename_type + make_typename_type) is different from
> > just doing the lookup immediately (i.e. just make_typename_type)
> > because we don't know the lookup is type-only anymore.
> > 
> > > 
> > > > +/* Set the tag type of the given TYPENAME_TYPE.  */
> > > > +
> > > > +inline void
> > > > +set_typename_tag_type (tree t, tag_types tag)
> > > > +{
> > > 
> > > Maybe assert that 'tag' fits in 3 bits?

Done.

> > > 
> > > > +  TYPENAME_TYPE_TAG_BIT_0 (t) = (tag >> 0) & 1;
> > > > +  TYPENAME_TYPE_TAG_BIT_1 (t) = (tag >> 1) & 1;
> > > > +  TYPENAME_TYPE_TAG_BIT_2 (t) = (tag >> 2) & 1;
> > > > +}
> > > > +
> > > >    /* The various kinds of lvalues we distinguish.  */
> > > >    enum cp_lvalue_kind_flags {
> > > >      clk_none = 0,     /* Things that are not an lvalue.  */
> > > > diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
> > > > index ae899ec9f770..1359aaab7229 100644
> > > > --- a/gcc/cp/error.cc
> > > > +++ b/gcc/cp/error.cc
> > > > @@ -824,7 +824,7 @@ dump_type (cxx_pretty_printer *pp, tree t, int
> > > > flags)
> > > >          pp_cxx_cv_qualifier_seq (pp, t);
> > > >          pp_cxx_ws_string (pp,
> > > >                          TYPENAME_IS_ENUM_P (t) ? "enum"
> > > > -                        : TYPENAME_IS_CLASS_P (t) ? "class"
> > > > +                        : TYPENAME_IS_CLASS_OR_STRUCT_P (t) ? "class"
> > > >                          : TYPENAME_IS_UNION_P (t) ? "union"
> > > >                          : "typename");
> > > 
> > > If we're going to represent struct vs class, let's also print it here.

Turns out we already have a helper for that, tag_name, which I extended
and used here instead.

-- >8 --

Subject: [PATCH] c++: losslessly encode TYPENAME_TYPE tag

We currently have 7 distinct tag_types, but our TYPENAME_TYPE
representation effectively only differentiates between 4 of them at the
expense of struct_type (which gets conflated with class_type),
none_type and scope_type (which get conflated with typename_type).

struct_type / class_type conflation is mostly harmless, but it
probably affects -Wmismatched-tags.  none_type / typename_type too
should be harmless.  But scope_type / typename_type conflation is
problematic because scope_type indicates a type-only lookup unlike
typename_type.

This patch makes our representation of TYPENAME_TYPE encode the numeric
tag type value losslessly using the first three adjacent tree flag bits.
We already use three flag bits for the tag encoding (but inefficiently)
so no additional space is needed.

gcc/cp/ChangeLog:

        * cp-tree.h (TYPENAME_TYPE_TAG_BIT_0): New.
        (TYPENAME_TYPE_TAG_BIT_1): New.
        (TYPENAME_TYPE_TAG_BIT_2): New.
        (TYPENAME_IS_ENUM_P): Use get_typename_tag.
        (TYPENAME_IS_CLASS_P): Rename to ...
        (TYPENAME_IS_CLASS_OR_STRUCT_P): ... this, and use
        get_typename_tag.
        (TYPENAME_IS_UNION_P): Use get_typename_tag.
        (TYPENAME_IS_RESOLVING_P): Use TREE_LANG_FLAG_3
        instead of _2.
        (get_typename_tag): New.
        (set_typename_tag): New.
        (tag_name): Declare.
        * decl.cc (typename_info): Replace bool fields with a single
        tag_types field.
        (typename_hasher::equal): Adjust.
        (build_typename_type): Adjust.
        (tag_name): Handle none_type and scope_type.
        * error.cc (dump_type) <case TYPENAME_TYPE>: Use tag_name.
        * module.cc (trees_out::type_node) <case TYPENAME_TYPE>: Use
        get_typename_tag.
        * pt.cc (tsubst) <case TYPENAME_TYPE>: Likewise.
---
 gcc/cp/cp-tree.h | 50 +++++++++++++++++++++++++++++++++++++++---------
 gcc/cp/decl.cc   | 25 +++++++++---------------
 gcc/cp/error.cc  |  7 ++-----
 gcc/cp/module.cc | 11 +----------
 gcc/cp/pt.cc     | 12 +++---------
 5 files changed, 56 insertions(+), 49 deletions(-)

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 6f244a8bdfd4..1f00898cfad0 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -417,7 +417,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
       EXPR_STMT_STMT_EXPR_RESULT (in EXPR_STMT)
       STMT_EXPR_NO_SCOPE (in STMT_EXPR)
       BIND_EXPR_TRY_BLOCK (in BIND_EXPR)
-      TYPENAME_IS_ENUM_P (in TYPENAME_TYPE)
+      TYPENAME_TYPE_TAG_BIT_0 (in TYPENAME_TYPE)
       OMP_FOR_GIMPLIFYING_P (in OMP_FOR, OMP_SIMD, OMP_DISTRIBUTE,
                             and OMP_TASKLOOP)
       BASELINK_QUALIFIED_P (in BASELINK)
@@ -460,7 +460,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
       DELETE_EXPR_USE_VEC (in DELETE_EXPR).
       ICS_ELLIPSIS_FLAG (in _CONV)
       DECL_INITIALIZED_P (in VAR_DECL)
-      TYPENAME_IS_CLASS_P (in TYPENAME_TYPE)
+      TYPENAME_TYPE_TAG_BIT_1 (in TYPENAME_TYPE)
       STMT_IS_FULL_EXPR_P (in _STMT)
       TARGET_EXPR_LIST_INIT_P (in TARGET_EXPR)
       DECL_FINAL_P (in FUNCTION_DECL)
@@ -480,7 +480,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
       ICS_THIS_FLAG (in _CONV)
       DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
       STATEMENT_LIST_TRY_BLOCK (in STATEMENT_LIST)
-      TYPENAME_IS_RESOLVING_P (in TYPENAME_TYPE)
+      TYPENAME_TYPE_TAG_BIT_2 (in TYPENAME_TYPE)
       TYPE_POLYMORPHIC_P (in RECORD_TYPE and UNION_TYPE)
       TARGET_EXPR_DIRECT_INIT_P (in TARGET_EXPR)
       FNDECL_USED_AUTO (in FUNCTION_DECL)
@@ -513,7 +513,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
       TARGET_EXPR_ELIDING_P (in TARGET_EXPR)
       IF_STMT_VACUOUS_INIT_P (IF_STMT)
       contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT)
-      TYPENAME_IS_UNION_P (in TYPENAME_TYPE)
+      TYPENAME_IS_RESOLVING_P (in TYPENAME_TYPE)
    4: IDENTIFIER_MARKED (IDENTIFIER_NODEs)
       TREE_HAS_CONSTRUCTOR (in INDIRECT_REF, SAVE_EXPR, CONSTRUCTOR,
          CALL_EXPR, or FIELD_DECL).
@@ -4551,21 +4551,30 @@ get_vec_init_expr (tree t)
 #define TYPENAME_TYPE_FULLNAME(NODE) \
   (TYPE_VALUES_RAW (TYPENAME_TYPE_CHECK (NODE)))
 
+/* Storage for the tag type of a TYPENAME_TYPE.  */
+#define TYPENAME_TYPE_TAG_BIT_0(NODE) \
+  (TREE_LANG_FLAG_0 (TYPENAME_TYPE_CHECK (NODE)))
+#define TYPENAME_TYPE_TAG_BIT_1(NODE) \
+  (TREE_LANG_FLAG_1 (TYPENAME_TYPE_CHECK (NODE)))
+#define TYPENAME_TYPE_TAG_BIT_2(NODE) \
+  (TREE_LANG_FLAG_2 (TYPENAME_TYPE_CHECK (NODE)))
+
 /* True if a TYPENAME_TYPE was declared as an "enum".  */
 #define TYPENAME_IS_ENUM_P(NODE) \
-  (TREE_LANG_FLAG_0 (TYPENAME_TYPE_CHECK (NODE)))
+  (get_typename_tag (NODE) == enum_type)
 
 /* 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)))
+#define TYPENAME_IS_CLASS_OR_STRUCT_P(NODE) \
+  (get_typename_tag (NODE) == class_type \
+   || get_typename_tag (NODE) == record_type)
 
 /* True if a TYPENAME_TYPE was declared as a "union".  */
 #define TYPENAME_IS_UNION_P(NODE) \
-  (TREE_LANG_FLAG_3 (TYPENAME_TYPE_CHECK (NODE)))
+  (get_typename_tag (NODE) == union_type)
 
 /* 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)))
+  (TREE_LANG_FLAG_3 (TYPENAME_TYPE_CHECK (NODE)))
 
 /* [class.virtual]
 
@@ -5791,6 +5800,28 @@ enum tag_types {
   scope_type    /* namespace or tagged type name followed by :: */
 };
 
+/* Return the tag type of the given TYPENAME_TYPE.  */
+
+inline tag_types
+get_typename_tag (tree t)
+{
+  unsigned bit0 = TYPENAME_TYPE_TAG_BIT_0 (t);
+  unsigned bit1 = TYPENAME_TYPE_TAG_BIT_1 (t);
+  unsigned bit2 = TYPENAME_TYPE_TAG_BIT_2 (t);
+  return tag_types ((bit2 << 2) | (bit1 << 1) | bit0);
+}
+
+/* Set the tag type of the given TYPENAME_TYPE.  */
+
+inline void
+set_typename_tag (tree t, tag_types tag)
+{
+  gcc_checking_assert ((tag & 7) == tag);
+  TYPENAME_TYPE_TAG_BIT_0 (t) = (tag >> 0) & 1;
+  TYPENAME_TYPE_TAG_BIT_1 (t) = (tag >> 1) & 1;
+  TYPENAME_TYPE_TAG_BIT_2 (t) = (tag >> 2) & 1;
+}
+
 /* The various kinds of lvalues we distinguish.  */
 enum cp_lvalue_kind_flags {
   clk_none = 0,     /* Things that are not an lvalue.  */
@@ -7364,6 +7395,7 @@ extern bool use_eh_spec_block                     (tree);
 extern void do_push_parm_decls                 (tree, tree, tree *);
 extern tree do_aggregate_paren_init            (tree, tree);
 extern void maybe_mark_function_versioned      (tree);
+extern const char *tag_name                    (enum tag_types);
 
 /* in decl2.cc */
 extern void record_mangling                    (tree, bool);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index 74c862ec1c7d..30f38f1e099d 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -85,7 +85,6 @@ static void check_static_variable_definition (tree, tree);
 static void record_unknown_type (tree, const char *);
 static int member_function_or_else (tree, tree, enum overload_flags);
 static tree local_variable_p_walkfn (tree *, int *, void *);
-static const char *tag_name (enum tag_types);
 static tree lookup_and_check_tag (enum tag_types, tree, TAG_how, bool);
 static void maybe_deduce_size_from_array_init (tree, tree);
 static void layout_var_decl (tree);
@@ -4843,9 +4842,7 @@ struct typename_info {
   tree scope;
   tree name;
   tree template_id;
-  bool enum_p;
-  bool class_p;
-  bool union_p;
+  tag_types tag_type;
 };
 
 struct typename_hasher : ggc_ptr_hash<tree_node>
@@ -4883,9 +4880,7 @@ struct typename_hasher : ggc_ptr_hash<tree_node>
     return (TYPE_IDENTIFIER (t1) == t2->name
            && 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_UNION_P (t1) == t2->union_p);
+           && get_typename_tag (t1) == t2->tag_type);
   }
 };
 
@@ -4908,9 +4903,7 @@ build_typename_type (tree context, tree name, tree 
fullname,
   ti.scope = FROB_CONTEXT (context);
   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);
-  ti.union_p = tag_type == union_type;
+  ti.tag_type = tag_type;
   hashval_t hash = typename_hasher::hash (&ti);
 
   /* See if we already have this type.  */
@@ -4924,9 +4917,7 @@ build_typename_type (tree context, tree name, tree 
fullname,
       t = cxx_make_type (TYPENAME_TYPE);
       TYPE_CONTEXT (t) = ti.scope;
       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;
+      set_typename_tag (t, ti.tag_type);
 
       /* Build the corresponding TYPE_DECL.  */
       tree d = build_decl (input_location, TYPE_DECL, name, t);
@@ -17941,7 +17932,7 @@ grok_op_properties (tree decl, bool complain)
 
 /* Return a string giving the keyword associate with CODE.  */
 
-static const char *
+const char *
 tag_name (enum tag_types code)
 {
   switch (code)
@@ -17956,9 +17947,11 @@ tag_name (enum tag_types code)
       return "enum";
     case typename_type:
       return "typename";
-    default:
-      gcc_unreachable ();
+    case none_type:
+    case scope_type:
+      return nullptr;
     }
+  gcc_unreachable ();
 }
 
 /* Name lookup in an elaborated-type-specifier (after the keyword
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index ae899ec9f770..4e35a4fcbc21 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -822,11 +822,8 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags)
          break;
        }
       pp_cxx_cv_qualifier_seq (pp, t);
-      pp_cxx_ws_string (pp,
-                        TYPENAME_IS_ENUM_P (t) ? "enum"
-                        : TYPENAME_IS_CLASS_P (t) ? "class"
-                        : TYPENAME_IS_UNION_P (t) ? "union"
-                        : "typename");
+      if (const char *tag = tag_name (get_typename_tag (t)))
+       pp_cxx_ws_string (pp, tag);
       dump_typename (pp, t, flags);
       break;
 
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index 7899fac8a2db..b0755e58d4bc 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -9727,16 +9727,7 @@ trees_out::type_node (tree type)
        tree_node (DECL_NAME (TYPE_NAME (type)));
        tree_node (TYPENAME_TYPE_FULLNAME (type));
        if (streaming_p ())
-         {
-           enum tag_types tag_type = none_type;
-           if (TYPENAME_IS_ENUM_P (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));
-         }
+         u (get_typename_tag (type));
        }
       break;
 
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index f13b3436eb3f..170768c03db2 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -17323,14 +17323,7 @@ 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' 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_UNION_P (t) ? union_type
-            : TYPENAME_IS_ENUM_P (t) ? enum_type
-            : typename_type);
+       enum tag_types tag_type = get_typename_tag (t);
        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);
@@ -17352,7 +17345,8 @@ tsubst (tree t, tree args, tsubst_flags_t complain, 
tree in_decl)
                else
                  return error_mark_node;
              }
-           else if (TYPENAME_IS_CLASS_P (t) && !NON_UNION_CLASS_TYPE_P (f))
+           else if (TYPENAME_IS_CLASS_OR_STRUCT_P (t)
+                    && !NON_UNION_CLASS_TYPE_P (f))
              {
                if (complain & tf_error)
                  error ("%qT resolves to %qT, which is not a non-union "
-- 
2.52.0.209.ge85ae279b0

Reply via email to