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