On Sat, 13 Dec 2025, Patrick Palka wrote:

> 
> 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.

Oh and I opted to rename get/set_typename_type_tag to
get/set_typename_tag.  The "_type" component of the name seems
unnecessary and it sidesteps the question of where it should
appear in the name, e.g.

  get_typename_type_tag,
  get_typename_tag_type, or perhaps
  get_typename_tag_tag_type

> 
> -- >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