From: Marek Polacek Date: Tue, 10 Feb 2026 18:24:44 +0000 (-0500) Subject: c++/reflection: various diagnostic tweaks X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=68957163b8853996b09b86fee700359681e78c70;p=thirdparty%2Fgcc.git c++/reflection: various diagnostic tweaks This patch improves various reflection diagnostics as discussed in . In particular: - reword "not usable" diagnostics to say what kind of reflections we expected, - use the new inform_tree_category to better describe what kind of reflection we actually got, - when we see a missing 'template' keyword, emit a -Wmissing-template-keyword warning instead of giving a hard error (this should only happen when the code would be valid with the 'template' added. gcc/cp/ChangeLog: * cp-tree.h (inform_tree_category): Declare. * error.cc (inform_tree_category): New. * parser.cc (cp_parser_splice_specifier): Use OVL_FIRST when checking for TEMPLATE_DECL. (cp_parser_splice_type_specifier): Reword an error message. Call inform_tree_category. (cp_parser_splice_expression): Check check_splice_expr earlier. Reword error messages. Call inform_tree_category. Turn an error into an assert. Use missing_template_diag instead of giving an error about a missing 'template' keyword. (cp_parser_splice_scope_specifier): Reword an error message. Call inform_tree_category. (missing_template_diag): Forward declare. Drop "enum" in a parameter. * reflect.cc (check_splice_expr): Reword error messages. Call inform_tree_category. gcc/testsuite/ChangeLog: * g++.dg/reflect/crash10.C: Adjust expected diagnostics. * g++.dg/reflect/crash2.C: Likewise. * g++.dg/reflect/crash3.C: Likewise. * g++.dg/reflect/crash7.C: Likewise. * g++.dg/reflect/crash9.C: Likewise. * g++.dg/reflect/dep5.C: Likewise. * g++.dg/reflect/diag1.C: Likewise. * g++.dg/reflect/error10.C: Likewise. * g++.dg/reflect/error12.C: Likewise. * g++.dg/reflect/error5.C: Likewise. * g++.dg/reflect/expr3.C: Likewise. * g++.dg/reflect/member1.C: Likewise. * g++.dg/reflect/ns2.C: Likewise. Test more cases. * g++.dg/reflect/p2996-12.C: Likewise. * g++.dg/reflect/splice5.C: Likewise. * g++.dg/reflect/diag1a.C: New test. * g++.dg/reflect/diag1b.C: New test. Reviewed-by: Jason Merrill --- diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 98411317064..a0683110eb3 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7688,6 +7688,7 @@ extern bool pedwarn_cxx98 (location_t, extern location_t location_of (tree); extern void qualified_name_lookup_error (tree, tree, tree, location_t); +void inform_tree_category (tree); struct decl_location_traits : simple_cache_map_traits { }; diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc index c184846066e..e64da242152 100644 --- a/gcc/cp/error.cc +++ b/gcc/cp/error.cc @@ -3976,6 +3976,42 @@ function_category (tree fn) return G_("In function %qD"); } +/* We expected some kind of tree but instead got T and emitted a diagnostic. + Print the category of T (type, expression, ...) if possible. */ + +void +inform_tree_category (tree t) +{ + const location_t loc = location_of (t); + + t = maybe_get_first_fn (t); + if (TREE_CODE (t) == TYPE_DECL) + t = TREE_TYPE (t); + + if (TYPE_P (t)) + inform (loc, "but %qE is a type", t); + else if (EXPR_P (t)) + inform (loc, "but %qE is an expression", t); + else if (DECL_DECOMPOSITION_P (t) && !DECL_DECOMP_IS_BASE (t)) + inform (loc, "but %qE is a structured binding", t); + else if (VAR_P (t)) + inform (loc, "but %qE is a variable", t); + else if (TREE_CODE (t) == PARM_DECL) + inform (loc, "but %qE is a parameter", t); + else if (TREE_CODE (t) == FUNCTION_DECL) + inform (loc, "but %qE is a function", t); + else if (TREE_CODE (t) == FIELD_DECL) + inform (loc, "but %qE is a data member", t); + else if (DECL_FUNCTION_TEMPLATE_P (t)) + inform (loc, "but %qE is a function template", t); + else if (DECL_CLASS_TEMPLATE_P (t)) + inform (loc, "but %qE is a class template", t); + else if (TREE_CODE (t) == NAMESPACE_DECL) + inform (loc, "but %qE is a namespace", t); + else if (TREE_CODE (t) == CONST_DECL && !DECL_TEMPLATE_PARM_P (t)) + inform (loc, "but %qE is an enumerator", t); +} + /* Disable warnings about missing quoting in GCC diagnostics for the pp_verbatim calls. Their format strings deliberately don't follow GCC diagnostic conventions. */ diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 97f2ddd62b3..a40e913893a 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -279,6 +279,8 @@ static tree omp_start_variant_function static int omp_finish_variant_function (cp_parser *, tree, tree, tree, bool, bool); static void maybe_start_implicit_template (cp_parser *parser); +static void missing_template_diag + (location_t, diagnostics::kind = diagnostics::kind::warning); /* Manifest constants. */ #define CP_LEXER_BUFFER_SIZE ((256 * 1024) / sizeof (cp_token)) @@ -6183,7 +6185,9 @@ cp_parser_splice_specifier (cp_parser *parser, bool template_p = false, /* As a courtesy to the user, if there is a < after a template name, parse the construct as an s-s-s and warn about the missing 'template'; it can't be anything else. */ - && (template_p || typename_p || TREE_CODE (expr) == TEMPLATE_DECL)) + && (template_p + || typename_p + || TREE_CODE (OVL_FIRST (expr)) == TEMPLATE_DECL)) { /* For member access splice-specialization-specifier, try to wrap non-dependent splice for function template into a BASELINK so @@ -6252,7 +6256,11 @@ cp_parser_splice_type_specifier (cp_parser *parser) if (!valid_splice_type_p (type)) { if (!cp_parser_simulate_error (parser)) - error_at (loc, "reflection %qE not usable in a splice type", type); + { + auto_diagnostic_group d; + error_at (loc, "expected a reflection of a type"); + inform_tree_category (type); + } type = NULL_TREE; } @@ -6313,63 +6321,42 @@ cp_parser_splice_expression (cp_parser *parser, bool template_p, return error_mark_node; } + /* Make sure this splice-expression produces an expression. */ + if (!check_splice_expr (loc, expr.get_start (), t, address_p, + member_access_p, /*complain=*/true)) + return error_mark_node; + if (template_p) { /* [expr.prim.splice] For a splice-expression of the form template splice-specifier, the splice-specifier shall designate a function template. */ - if (!targs_p) + if (!targs_p + && !really_overloaded_fn (t) + && !dependent_splice_p (t)) { - if (!really_overloaded_fn (t) && !dependent_splice_p (t)) - { - auto_diagnostic_group d; - error_at (loc, "reflection %qE not usable in a template splice", - t); - inform (loc, "only function templates are allowed here"); - return error_mark_node; - } + auto_diagnostic_group d; + error_at (loc, "expected a reflection of a function template"); + inform_tree_category (t); + return error_mark_node; } /* [expr.prim.splice] For a splice-expression of the form template splice-specialization-specifier, the splice-specifier of the - splice-specialization-specifier shall designate a template. */ - else - { - if (really_overloaded_fn (t) - || get_template_info (t) - || TREE_CODE (t) == TEMPLATE_ID_EXPR) - /* OK */; - else - { - auto_diagnostic_group d; - error_at (loc, "reflection %qE not usable in a template splice", - t); - inform (loc, "only templates are allowed here"); - return error_mark_node; - } - } + splice-specialization-specifier shall designate a template. Since + we would have already complained, just check that we have a template. */ + gcc_checking_assert (really_overloaded_fn (t) + || get_template_info (t) + || TREE_CODE (t) == TEMPLATE_ID_EXPR + || dependent_splice_p (t)); } else if (/* No 'template' but there were template arguments? */ - targs_p - /* No 'template' but the splice-specifier designates a template? */ - || really_overloaded_fn (t)) - { - auto_diagnostic_group d; - if (targs_p) - error_at (loc, "reflection %qE not usable in a splice expression with " - "template arguments", t); - else - error_at (loc, "reflection %qE not usable in a splice expression", t); - location_t sloc = expr.get_start (); - rich_location richloc (line_table, sloc); - richloc.add_fixit_insert_before (sloc, "template "); - inform (&richloc, "add % to denote a template"); - return error_mark_node; - } - - /* Make sure this splice-expression produces an expression. */ - if (!check_splice_expr (loc, expr.get_start (), t, address_p, - member_access_p, /*complain=*/true)) - return error_mark_node; + (targs_p + /* No 'template' but the splice-specifier designates a function + template? */ + || really_overloaded_fn (t)) + && warning_enabled_at (loc, OPT_Wmissing_template_keyword)) + /* Were 'template' present, this would be valid code, so keep going. */ + missing_template_diag (loc, diagnostics::kind::pedwarn); /* When doing foo.[: bar :], cp_parser_postfix_dot_deref_expression wants to see an identifier or a TEMPLATE_ID_EXPR, if we have something like @@ -6471,13 +6458,9 @@ cp_parser_splice_scope_specifier (cp_parser *parser, bool typename_p, if (!valid_splice_scope_p (scope)) { auto_diagnostic_group d; - error_at (loc, "reflection not usable in a splice scope"); - if (TYPE_P (scope)) - inform (loc, "%qT is not a class, namespace, or enumeration", - tree (scope)); - else - inform (loc, "%qE is not a class, namespace, or enumeration", - tree (scope)); + error_at (loc, "expected a reflection of a class, namespace, or " + "enumeration"); + inform_tree_category (tree (scope)); scope = error_mark_node; } @@ -7235,7 +7218,7 @@ cp_parser_primary_expression (cp_parser *parser, static void missing_template_diag (location_t loc, - enum diagnostics::kind diag_kind = diagnostics::kind::warning) + diagnostics::kind diag_kind/*=kind::warning*/) { if (warning_suppressed_at (loc, OPT_Wmissing_template_keyword)) return; diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc index 825f68e179f..367905f5c0b 100644 --- a/gcc/cp/reflect.cc +++ b/gcc/cp/reflect.cc @@ -8338,8 +8338,8 @@ check_splice_expr (location_t loc, location_t start_loc, tree t, if (TYPE_P (t)) { auto_diagnostic_group d; - error_at (loc, "expected a reflection of an expression instead " - "of type %qT", t); + error_at (loc, "expected a reflection of an expression"); + inform_tree_category (t); if (start_loc != UNKNOWN_LOCATION) { rich_location richloc (line_table, start_loc); @@ -8352,8 +8352,11 @@ check_splice_expr (location_t loc, location_t start_loc, tree t, "a type-only context"); } else - error_at (loc, "expected a reflection of an expression instead " - "of %qD", t); + { + auto_diagnostic_group d; + error_at (loc, "expected a reflection of an expression"); + inform_tree_category (t); + } } return false; } diff --git a/gcc/testsuite/g++.dg/reflect/crash10.C b/gcc/testsuite/g++.dg/reflect/crash10.C index 05845846e23..dddf1e62544 100644 --- a/gcc/testsuite/g++.dg/reflect/crash10.C +++ b/gcc/testsuite/g++.dg/reflect/crash10.C @@ -4,8 +4,9 @@ struct S { }; using X = S; -template constexpr int e = [:V:]; // { dg-error "expected a reflection of an expression instead of type .X." } -template constexpr int e2 = [:V:]; // { dg-error "expected a reflection of an expression instead of type .X." } +template constexpr int e = [:V:]; // { dg-error "expected a reflection of an expression" } +// { dg-message "but .X. is a type" "" { target *-*-* } 4 } +template constexpr int e2 = [:V:]; // { dg-error "expected a reflection of an expression" } constexpr auto h = ^^X; constexpr auto i = e<([:^^h:])>; constexpr auto j = e2<^^X>; diff --git a/gcc/testsuite/g++.dg/reflect/crash2.C b/gcc/testsuite/g++.dg/reflect/crash2.C index 83927fbdc7c..879c2145b97 100644 --- a/gcc/testsuite/g++.dg/reflect/crash2.C +++ b/gcc/testsuite/g++.dg/reflect/crash2.C @@ -13,8 +13,9 @@ void f () { // Parsed as "S::tfn < S". - [: ^^T :]::tfn<[: ^^T :]>(); // { dg-error "expected primary-expression|expected a reflection of an expression instead of type .S." } + [: ^^T :]::tfn<[: ^^T :]>(); // { dg-error "expected primary-expression|expected a reflection of an expression" } // { dg-warning "expected .template. keyword" "" { target *-*-* } .-1 } +// { dg-message "but .S. is a type" "" { target *-*-* } 4 } int i = [: ^^T :]::var; // { dg-error "missing|expected" } } diff --git a/gcc/testsuite/g++.dg/reflect/crash3.C b/gcc/testsuite/g++.dg/reflect/crash3.C index 51cf3d8b727..f2934fe98fc 100644 --- a/gcc/testsuite/g++.dg/reflect/crash3.C +++ b/gcc/testsuite/g++.dg/reflect/crash3.C @@ -1,8 +1,10 @@ // { dg-do compile { target c++26 } } // { dg-additional-options "-freflection" } -template constexpr int e = [:V:]; // { dg-error "expected a reflection of an expression instead of type .int." } -template constexpr int e2 = [:V:]; // { dg-error "expected a reflection of an expression instead of type .int." } +template constexpr int e = [:V:]; // { dg-error "expected a reflection of an expression" } +// { dg-message "but .int. is a type" "" { target *-*-* } .-1 } +template constexpr int e2 = [:V:]; // { dg-error "expected a reflection of an expression" } +// { dg-message "but .int. is a type" "" { target *-*-* } .-1 } constexpr auto h = ^^int; constexpr auto i = e<([:^^h:])>; constexpr auto j = e2<^^int>; diff --git a/gcc/testsuite/g++.dg/reflect/crash7.C b/gcc/testsuite/g++.dg/reflect/crash7.C index 50905acc7a0..c122ba2066c 100644 --- a/gcc/testsuite/g++.dg/reflect/crash7.C +++ b/gcc/testsuite/g++.dg/reflect/crash7.C @@ -12,7 +12,8 @@ template void f () { - int j = s.template [:R:]; // { dg-error "expected a reflection of an expression instead of type .S." } + int j = s.template [:R:]; // { dg-error "expected a reflection of an expression" } + // { dg-message "but .S. is a type" "" { target *-*-* } 6 } } void diff --git a/gcc/testsuite/g++.dg/reflect/crash9.C b/gcc/testsuite/g++.dg/reflect/crash9.C index 1f8aff6a715..eaf3214ec1e 100644 --- a/gcc/testsuite/g++.dg/reflect/crash9.C +++ b/gcc/testsuite/g++.dg/reflect/crash9.C @@ -3,8 +3,9 @@ namespace N { } -template constexpr int e = [:V:]; // { dg-error "expected a reflection of an expression instead of .N." } -template constexpr int e2 = [:V:]; // { dg-error "expected a reflection of an expression instead of .N." } +template constexpr int e = [:V:]; // { dg-error "expected a reflection of an expression" } +// { dg-message "but .N. is a namespace" "" { target *-*-* } 4 } +template constexpr int e2 = [:V:]; // { dg-error "expected a reflection of an expression" } constexpr auto h = ^^N; constexpr auto i = e<([:^^h:])>; constexpr auto j = e2<^^N>; diff --git a/gcc/testsuite/g++.dg/reflect/dep5.C b/gcc/testsuite/g++.dg/reflect/dep5.C index 4a118de1b70..3a04cbd58dd 100644 --- a/gcc/testsuite/g++.dg/reflect/dep5.C +++ b/gcc/testsuite/g++.dg/reflect/dep5.C @@ -18,8 +18,9 @@ f () static_assert ([: ^^T :]::x == 42); typename [: ^^T :]::type a = 42; [: ^^T :]::fn (42); - [: ^^T :]::template tfn<([: ^^T :])>(); // { dg-error "expected a reflection of an expression instead of type .S." } - auto x = [: ^^T :]::template var<([: ^^T :])>; // { dg-error "expected a reflection of an expression instead of type .S." } + [: ^^T :]::template tfn<([: ^^T :])>(); // { dg-error "expected a reflection of an expression" } + // { dg-message "but .S. is a type" "" { target *-*-* } 4 } + auto x = [: ^^T :]::template var<([: ^^T :])>; // { dg-error "expected a reflection of an expression" } } void diff --git a/gcc/testsuite/g++.dg/reflect/diag1.C b/gcc/testsuite/g++.dg/reflect/diag1.C index 2d34623a8ac..537929decb7 100644 --- a/gcc/testsuite/g++.dg/reflect/diag1.C +++ b/gcc/testsuite/g++.dg/reflect/diag1.C @@ -14,12 +14,10 @@ void f () { S s; - s.[: ^^S::tfn :](42); // { dg-error "reflection .S::tfn. not usable in a splice expression" } -// { dg-message "add .template. to denote a template" "" { target *-*-* } .-1 } + s.[: ^^S::tfn :](42); // { dg-error "expected 'template' keyword before dependent template name" } s.template [: ^^S::tfn :](42); constexpr auto r = ^^fortytwo; - constexpr int i1 = [:r:]; // { dg-error "reflection .fortytwo. not usable in a splice expression with template arguments" } -// { dg-message "add .template. to denote a template" "" { target *-*-* } .-1 } + constexpr int i1 = [:r:]; // { dg-error "expected 'template' keyword before dependent template name" } constexpr int i2 = template [:r:]; } diff --git a/gcc/testsuite/g++.dg/reflect/diag1a.C b/gcc/testsuite/g++.dg/reflect/diag1a.C new file mode 100644 index 00000000000..d276a7d22f4 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/diag1a.C @@ -0,0 +1,24 @@ +// { dg-do compile { target c++26 } } +// { dg-options "" } +// { dg-additional-options "-freflection" } +// Test that we offer some helpful diagnostic. + +struct S { + template + void tfn (T) { } +}; + +template +constexpr T fortytwo = 42; + +void +f () +{ + S s; + s.[: ^^S::tfn :](42); // { dg-warning "expected 'template' keyword before dependent template name" } + s.template [: ^^S::tfn :](42); + + constexpr auto r = ^^fortytwo; + constexpr int i1 = [:r:]; // { dg-warning "expected 'template' keyword before dependent template name" } + constexpr int i2 = template [:r:]; +} diff --git a/gcc/testsuite/g++.dg/reflect/diag1b.C b/gcc/testsuite/g++.dg/reflect/diag1b.C new file mode 100644 index 00000000000..b2c9bade331 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/diag1b.C @@ -0,0 +1,23 @@ +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection -Wno-missing-template-keyword" } +// Test that we offer some helpful diagnostic. + +struct S { + template + void tfn (T) { } +}; + +template +constexpr T fortytwo = 42; + +void +f () +{ + S s; + s.[: ^^S::tfn :](42); // { dg-bogus "expected 'template' keyword before dependent template name" } + s.template [: ^^S::tfn :](42); + + constexpr auto r = ^^fortytwo; + constexpr int i1 = [:r:]; // { dg-bogus "expected 'template' keyword before dependent template name" } + constexpr int i2 = template [:r:]; +} diff --git a/gcc/testsuite/g++.dg/reflect/error10.C b/gcc/testsuite/g++.dg/reflect/error10.C index 2ca2b18a1fa..891a643c1a0 100644 --- a/gcc/testsuite/g++.dg/reflect/error10.C +++ b/gcc/testsuite/g++.dg/reflect/error10.C @@ -4,29 +4,34 @@ int g; void -fn0 (int, typename [: ^^:: :] i) // { dg-error "reflection .::. not usable in a splice type|declared" } +fn0 (int, typename [: ^^:: :] i) // { dg-error "expected a reflection of a type|declared" } +// { dg-message "but .::. is a namespace" "" { target *-*-* } 0 } { } void -fn1 (int, typename [: ^^g :] i) // { dg-error "reflection .g. not usable in a splice type|declared" } +fn1 (int, typename [: ^^g :] i) // { dg-error "expected a reflection of a type|declared" } +// { dg-message "but .g. is a variable" "" { target *-*-* } 4 } { } void -fn2 (int, typename [: ^^fn1 :] i) // { dg-error "reflection .fn1. not usable in a splice type|declared" } +fn2 (int, typename [: ^^fn1 :] i) // { dg-error "expected a reflection of a type|declared" } +// { dg-message "but .fn1. is a function" "" { target *-*-* } 13 } { } void -fn3 (int p, typename [: ^^p :] i) // { dg-error "reflection .p. not usable in a splice type|declared" } +fn3 (int p, typename [: ^^p :] i) // { dg-error "expected a reflection of a type|declared" } +// { dg-message "but .p. is a parameter" "" { target *-*-* } 25 } { } enum Harold { Budd }; void -fn4 (int, typename [: ^^Budd :] i) // { dg-error "reflection .Budd. not usable in a splice type|declared" } +fn4 (int, typename [: ^^Budd :] i) // { dg-error "expected a reflection of a type|declared" } +// { dg-message "but .Budd. is an enumerator" "" { target *-*-* } 30 } { } @@ -34,6 +39,16 @@ template struct S {}; void -fn5 (int, typename [: ^^S :] i) // { dg-error "reflection .S. not usable in a splice type|declared" } +fn5 (int, typename [: ^^S :] i) // { dg-error "expected a reflection of a type|declared" } +// { dg-message "but .S. is a class template" "" { target *-*-* } 39 } +{ +} + +template +void bar (); + +void +fn6 (int, typename [: ^^bar :] i) // { dg-error "expected a reflection of a type|declared" } +// { dg-message "but .bar. is a function template" "" { target *-*-* } 48 } { } diff --git a/gcc/testsuite/g++.dg/reflect/error12.C b/gcc/testsuite/g++.dg/reflect/error12.C index 7eb787f010d..8f39b7f74a7 100644 --- a/gcc/testsuite/g++.dg/reflect/error12.C +++ b/gcc/testsuite/g++.dg/reflect/error12.C @@ -7,16 +7,17 @@ constexpr auto r = ^^i; constexpr bool b0 = template [:r:] < 0 > 0; // { dg-error "not a template" } constexpr bool b1 = template [:r:] < 0 < 0; // { dg-error "not a template|expected" } constexpr bool b2 = template [:r:] < 43; // { dg-error "not a template|expected" } -constexpr bool b3 = template [:r:] <= 43; // { dg-error "template splice" } -constexpr bool b4 = template [:r:] > 41; // { dg-error "template splice" } -constexpr bool b5 = template [:r:] >= 41; // { dg-error "template splice" } -constexpr bool b6 = template [:r:] == 42; // { dg-error "template splice" } -constexpr bool b7 = template [:r:] != 41; // { dg-error "template splice" } +constexpr bool b3 = template [:r:] <= 43; // { dg-error "expected a reflection of a function template" } +// { dg-message "but .i. is a variable" "" { target *-*-* } 5 } +constexpr bool b4 = template [:r:] > 41; // { dg-error "expected a reflection of a function template" } +constexpr bool b5 = template [:r:] >= 41; // { dg-error "expected a reflection of a function template" } +constexpr bool b6 = template [:r:] == 42; // { dg-error "expected a reflection of a function template" } +constexpr bool b7 = template [:r:] != 41; // { dg-error "expected a reflection of a function template" } template struct S { }; S