On 12/11/25 5:17 AM, Marek Polacek wrote:
On Fri, Dec 05, 2025 at 10:18:34PM +0530, Jason Merrill wrote:
+static tree
+cp_parser_reflection_name (cp_parser *parser)
+{
+ /* Look for the optional `::' operator. */
+ bool global_scope_p
+ = (cp_parser_global_scope_opt (parser,
+ /*current_scope_valid_p=*/false)
+ != NULL_TREE);
+ /* And the optional nested-name-specifier. */
+ bool nested_name_specifier_p
+ = (cp_parser_nested_name_specifier_opt (parser,
+ /*typename_keyword_p=*/false,
+ /*check_dependency_p=*/true,
+ /*type_p=*/false,
+ /*is_declaration=*/false,
+ /*template_keyword_p=*/false,
+ global_scope_p)
+ != NULL_TREE);
+ /* Look for the optional `template' keyword. */
+ if (cp_parser_optional_template_keyword (parser)
+ && !global_scope_p
+ && !nested_name_specifier_p)
+ /* Only "template identifier" isn't valid. */
+ cp_parser_simulate_error (parser);
This could be a cp_parser_error without a comment?
Done. Though I think we'll never see that error because here we're
always parsing tentatively.
Agreed.
+ /* Nope. Well then, maybe it's a type-id. */
+ cp_parser_parse_tentatively (parser);
+
+ /* Unfortunately we need to distinguish in
+ template <typename> struct cls_tmpl {};
+ template <typename T> using cls_tmpl_alias = const cls_tmpl <T>;
+
+ ^^cls_tmpl_alias <int> which is a type alias from
+ ^^const cls_tmpl_alias <int>
+ ^^cls_tmpl_alias <int> const
+ ^^cls_tmpl_alias <int> *
+ etc. which are just types, not type aliases. Parse tentatively
+ type specifiers and check that there is just ds_type_spec specified
+ and it is a specialization of a template alias, in that case later
+ on if cp_parser_type_id parses the same tokens don't strip typedefs. */
+ if (!talias)
+ {
+ cp_decl_specifier_seq type_specifier_seq;
+
+ /* Parse the type-specifier-seq. */
+ cp_parser_type_specifier_seq (parser, CP_PARSER_FLAGS_NONE,
+ /*is_declaration=*/false, false,
+ &type_specifier_seq);
+ if (is_typedef_decl (type_specifier_seq.type)
+ && TYPE_ALIAS_TEMPLATE_INFO (TREE_TYPE (type_specifier_seq.type)))
+ {
+ int i;
+ for (i = ds_first; i < ds_last; ++i)
+ if (i != ds_type_spec && type_specifier_seq.locations[i])
+ break;
+ if (i == ds_last)
+ {
+ talias = type_specifier_seq.type;
+ next = cp_lexer_peek_token (parser->lexer);
+ }
+ }
+ cp_parser_abort_tentative_parse (parser);
+ cp_parser_parse_tentatively (parser);
+ }
+
+ t = cp_parser_type_id (parser);
+ if (cp_parser_parse_definitely (parser))
+ {
+ if (TYPE_P (t))
+ {
+ /* With using A = int; ^^A is a type alias but
+ ^^const A or ^^A & or ^^A const is not.
+ With template <typename T> using B = C <T>;
+ ^^B <int> is a type alias though. */
+ if (talias == NULL_TREE
+ || cp_lexer_peek_token (parser->lexer) != next)
+ t = strip_typedefs (t);
+ }
+ return get_reflection (loc, t);
+ }
I think it would be simpler to handle more of this in cp_parser_type_id_1,
which already handles other special cases, rather than parsing the
type-specifiers twice?
I took a crack at this in
<https://forge.sourceware.org/marek/gcc/commit/e2a9ca89a628d54897ffe30054191b0e14dfd1c4>
and it seemed to work.
Looks good, except I think I'd rather call _type_id_1 directly rather
than add a special-case parameter to _type_id.
+ typename_token = token;
+ splice_token = cp_lexer_peek_nth_token (parser->lexer, 2);
+ }
+ else if (!processing_template_decl)
error_at (token->location,
"keyword %<typename%> not allowed outside of templates");
else
@@ -30501,10 +31381,31 @@ cp_parser_base_specifier (cp_parser* parser)
class_scope_p = (parser->scope && TYPE_P (parser->scope));
template_p = class_scope_p && cp_parser_optional_template_keyword (parser);
+ if (typename_token && cp_lexer_peek_token (parser->lexer) != splice_token)
+ {
+ /* Emit deferred diagnostics for invalid typename keyword if
+ cp_parser_nested_name_specifier_opt parsed splice-scope-specifier. */
+ if (!processing_template_decl)
+ error_at (typename_token->location,
+ "keyword %<typename%> not allowed outside of templates");
Pre-existig issue, but this is not true anymore. For instance, this is
fine:
struct A { struct B {}; };
typename A::B b;
+ else
+ error_at (typename_token->location,
+ "keyword %<typename%> not allowed in this context "
+ "(the base class is implicitly a type)");
...so let's only give this error.
Happy to, but I think it should be done outside the Reflection
patch. parse/typename6.C checks the first error.
Agreed.
@@ -17478,6 +17601,9 @@ tsubst (tree t, tree args, tsubst_flags_t complain,
tree in_decl)
static tree
tsubst_scope (tree t, tree args, tsubst_flags_t complain, tree in_decl)
{
+ /* This could be a dependent namespace. */
+ if (TREE_CODE (t) == NAMESPACE_DECL)
+ return tsubst_expr (t, args, complain | tf_qualifying_scope, in_decl);
gcc_checking_assert (TYPE_P (t));
return tsubst (t, args, complain | tf_qualifying_scope, in_decl);
}
Why doesn't tsubst handle NAMESPACE_DECL? A namespace isn't an expression,
so tsubst_expr seems wrong. More context below.
NAMESPACE_DECL used to be handled by tsubst_copy and since r14-4796 by
tsubst_expr.
Moved case NAMESPACE_DECL into tsubst_decl here:
<https://forge.sourceware.org/marek/gcc/commit/950ad2a58b10542fb41978f2c657235f292e49a2>
@@ -20943,6 +21068,38 @@ tsubst_expr (tree t, tree args, tsubst_flags_t
complain, tree in_decl)
RETURN (r);
}
+ /* Normally, we only expect a template function at this point. But
+ for [:X:]<arg>, we don't really know what [:X:] means until we
+ substitute it. */
+ if (DECL_TYPE_TEMPLATE_P (templ)
+ || DECL_TEMPLATE_TEMPLATE_PARM_P (templ))
+ {
+ tree op = TREE_OPERAND (t, 0);
+ gcc_assert (TREE_CODE (op) == SPLICE_EXPR);
+ tree r = finish_template_type (templ, targs,
+ /*entering_scope=*/false);
+ if (TREE_CODE (r) == TYPE_DECL)
+ r = TREE_TYPE (r);
+ if (SPLICE_EXPR_EXPRESSION_P (op)
+ && !check_splice_expr (input_location, UNKNOWN_LOCATION, r,
+ SPLICE_EXPR_ADDRESS_P (op),
+ SPLICE_EXPR_MEMBER_ACCESS_P (op),
+ /*complain_p=*/false))
+ {
+ if (complain & tf_error)
+ {
+ auto_diagnostic_group d;
+ error_at (cp_expr_loc_or_input_loc (t),
+ "%qE is not usable in a splice expression", r);
+ if (TYPE_P (r))
+ inform (input_location, "add %<typename%> to denote a "
+ "type outside a type-only context");
+ }
+ RETURN (error_mark_node);
+ }
+ RETURN (r);
How can we get here such that it isn't an error?
<tests>
aha, in
typename [: R :]<int> c1;
the type is SPLICE_SCOPE where SPLICE_SCOPE_EXPR is [:R:]<int>, and we pass
it to tsubst_expr.
But the comment is wrong; we do know what [:R:] means, we know it needs to
be a type because it's a SPLICE_SCOPE, so we shouldn't have needed to pass
it to tsubst_expr in the first place.
Yes: every valid usage of [:R:] is coming from tsubst_splice_scope, so we
know it needs to expand to a type/scope.
Can we instead add SPLICE_EXPR and TEMPLATE_ID_EXPR cases to tsubst for the
type cases, as well as moving the NAMESPACE_DECL case there?
Hopefully done in
<https://forge.sourceware.org/marek/gcc/commit/0726f7476b187bccd4a113a80900fb8125486313>
Looks good.
@@ -3437,9 +3444,24 @@ finish_class_member_access_expr (cp_expr object, tree
name, bool template_p,
return error_mark_node;
}
+ /* For OBJECT.[:S::fn:] the BASELINK can be inside a SCOPE_REF.
+ This happens, but, until Reflection, not for a class member access. */
+ if (TREE_CODE (name) == SCOPE_REF && BASELINK_P (TREE_OPERAND (name, 1)))
+ name = TREE_OPERAND (name, 1);
Why does this happen? Why isn't the SCOPE_REF folded into the BASELINK?
Happens in:
struct S {
void foo () {}
};
template <typename T>
void qux ()
{
S s {};
s.[:(T) ^^S::foo:] ();
}
void
corge ()
{
qux <decltype (^^int)> ();
}
In cp_parser_postfix_dot_deref_expression:
* cp_parser_splice_expression gives us a SPLICE_EXPR (~it's dependent)
Yes.
* since parser->scope is S, we create a SCOPE_REF in build_qualified_name
As discussed in _dot_deref, parser->scope shouldn't be set by the time
we get back there, so I guess fixing that should make this hunk unnecessary.
* need to tsubst, so tsubst_expr/SCOPE_REF does tsubst_scope + tsubst_name,
then build_qualified_name creates a new SCOPE_REF
* tsubst_expr calls finish_class_member_access_expr with that SCOPE_REF
Does this have to change?
+/* The DIE for C++26 'decltype(^^int)' fundamental type. */
+static GTY(()) dw_die_ref meta_type_die;
Why do we need this?
I don't think the current auto handling is a great model to copy.
I wonder about, in is_base_type and gen_type_die_with_usage, just handling
everything past END_OF_BASE_TREE_CODES like LANG_TYPE?
Resolved in
<https://forge.sourceware.org/marek/gcc/commit/06e3ccc259570c2e2c7ab4e997d38326e8afc53d>
I'd still prefer END_OF_BASE_TREE_CODES rather than the (equal)
LAST_AND_UNUSED_TREE_CODE.
Jason