Hi! We currently print std::nullptr_t or std::meta::info in diagnostics when seeing a NULLPTR_TYPE or META_TYPE, when they aren't type aliases (or when they are exactly those type aliases). I think that isn't a bad idea, the aliases is what users usually use for those. There are 2 problems with this though. We print decltype(nullptr) and decltype(nullptr) const volatile exactly the same, both as std::nullptr_t, so the qualifiers are lost. And, e.g. in case of a static assertion failure when people want to find out why some reflections aren't equal we can print note: the comparison reduces to '(^^std::meta::info == ^^std::meta:info)' and the user then has no idea what is going on. Is it because one of those is a type alias (which one), or because of cv-qual differences, or both? The following patch prints the aliases in normal %qT etc. printing only if unqualified, when qualified prints decltype(^^int) or decltype(nullptr) with the qualifications. And, when printing a reflection expression, it differentiates even between the type alias case and non-alias.
So far lightly tested, ok for trunk if it passes full bootstrap/regtest? Or do you prefer always printing decltype? 2026-07-22 Jakub Jelinek <[email protected]> PR c++/126343 * error.cc (dump_type) <case NULLPTR_TYPE>: For qualified NULLPTR_TYPE print decltype(nullptr) quals. (dump_type) <case META_TYPE>: For qualified META_TYPE print decltype(^^int) quals. (dump_expr) <case REFLECT_EXPR>: For REFLECT_EXPR on non-typedef non-qualified META_TYPE or NULLPTR_TYPE print decltype(^^int) or decltype(nullptr). * g++.dg/reflect/pr126343.C: New test. * g++.dg/reflect/diag6.C: Adjust expected diagnostic wording. * g++.dg/reflect/init2.C: Likewise. --- gcc/cp/error.cc.jj 2026-07-13 18:35:54.649463659 +0200 +++ gcc/cp/error.cc 2026-07-22 09:56:36.155416491 +0200 @@ -875,11 +875,27 @@ dump_type (cxx_pretty_printer *pp, tree break; case NULLPTR_TYPE: - pp_cxx_ws_string (pp, "std::nullptr_t"); + /* Print std::nullptr_t if it is unqualified, otherwise e.g. + decltype(nullptr) const. */ + if (TYPE_QUALS (t)) + { + pp_cxx_ws_string (pp, "decltype(nullptr)"); + pp_c_type_qualifier_list (pp, t); + } + else + pp_cxx_ws_string (pp, "std::nullptr_t"); break; case META_TYPE: - pp_cxx_ws_string (pp, "std::meta::info"); + /* Print std::meta::info if it is unqualified, otherwise e.g. + decltype(^^int) const. */ + if (TYPE_QUALS (t)) + { + pp_cxx_ws_string (pp, "decltype(^^int)"); + pp_c_type_qualifier_list (pp, t); + } + else + pp_cxx_ws_string (pp, "std::meta::info"); break; case SPLICE_SCOPE: @@ -3481,7 +3497,21 @@ dump_expr (cxx_pretty_printer *pp, tree if (DECL_P (h)) dump_decl (pp, h, flags); else if (TYPE_P (h)) - dump_type (pp, h, flags); + { + /* For reflection we care about the difference + between std::meta::info/std::nullptr_t and + decltype(^^int)/decltype(nullptr). */ + if (TREE_CODE (h) == META_TYPE + && !typedef_variant_p (h) + && !TYPE_QUALS (h)) + pp_cxx_ws_string (pp, "decltype(^^int)"); + else if (TREE_CODE (h) == NULLPTR_TYPE + && !typedef_variant_p (h) + && !TYPE_QUALS (h)) + pp_cxx_ws_string (pp, "decltype(nullptr)"); + else + dump_type (pp, h, flags); + } else dump_expr (pp, h, flags); break; --- gcc/testsuite/g++.dg/reflect/pr126343.C.jj 2026-07-22 10:15:07.629513229 +0200 +++ gcc/testsuite/g++.dg/reflect/pr126343.C 2026-07-22 10:19:29.149019109 +0200 @@ -0,0 +1,43 @@ +// PR c++/126343 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } +namespace std { + namespace meta { + using info = decltype (^^::); + } + using nullptr_t = decltype (nullptr); +} +namespace my { + using info = decltype (^^::) const; + using nullptr_t = volatile decltype (nullptr); +} +constexpr auto a = ^^decltype (^^int); +constexpr auto b = ^^decltype (nullptr); +constexpr auto c = ^^const decltype (^^int); +constexpr auto d = ^^const decltype (nullptr); +constexpr auto e = ^^decltype (^^int) const volatile; +constexpr auto f = ^^decltype (nullptr) volatile; +constexpr auto g = ^^std::meta::info; +constexpr auto h = ^^std::nullptr_t; +constexpr auto i = ^^my::info; +constexpr auto j = ^^my::nullptr_t; +static_assert (a == ^^char); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^decltype\\\(\\\^\\\^int\\\) == \\\^\\\^char\\\)'" "" { target *-*-* } .-1 } +static_assert (b == ^^short); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^decltype\\\(nullptr\\\) == \\\^\\\^short int\\\)'" "" { target *-*-* } .-1 } +static_assert (c == ^^int); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^decltype\\\(\\\^\\\^int\\\) const == \\\^\\\^int\\\)'" "" { target *-*-* } .-1 } +static_assert (d == ^^long); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^decltype\\\(nullptr\\\) const == \\\^\\\^long int\\\)'" "" { target *-*-* } .-1 } +static_assert (e == ^^long long); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^decltype\\\(\\\^\\\^int\\\) const volatile == \\\^\\\^long long int\\\)'" "" { target *-*-* } .-1 } +static_assert (f == ^^unsigned char); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^decltype\\\(nullptr\\\) volatile == \\\^\\\^unsigned char\\\)'" "" { target *-*-* } .-1 } +static_assert (g == ^^unsigned short); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^std::meta::info == \\\^\\\^short unsigned int\\\)'" "" { target *-*-* } .-1 } +static_assert (h == ^^unsigned int); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^std::nullptr_t == \\\^\\\^unsigned int\\\)'" "" { target *-*-* } .-1 } +static_assert (i == ^^unsigned long); // { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^my::info == \\\^\\\^long unsigned int\\\)'" "" { target *-*-* } .-1 } +static_assert (j == ^^unsigned long long);// { dg-error "static assertion " } + // { dg-message "note: the comparison reduces to '\\\(\\\^\\\^my::nullptr_t == \\\^\\\^long long unsigned int\\\)'" "" { target *-*-* } .-1 } --- gcc/testsuite/g++.dg/reflect/diag6.C.jj 2026-03-27 10:17:16.121298315 +0100 +++ gcc/testsuite/g++.dg/reflect/diag6.C 2026-07-22 10:21:14.401616096 +0200 @@ -5,6 +5,6 @@ void g () { - constexpr decltype(^^::) dm = ^^int; // { dg-message ".constexpr std::meta::info dm. previously declared here" } - constexpr decltype(^^::) dm = ^^int; // { dg-error "redeclaration of .constexpr std::meta::info dm." } + constexpr decltype(^^::) dm = ^^int; // { dg-message ".constexpr decltype\\\(\\\^\\\^int\\\) const dm. previously declared here" } + constexpr decltype(^^::) dm = ^^int; // { dg-error "redeclaration of .constexpr decltype\\\(\\\^\\\^int\\\) const dm." } } --- gcc/testsuite/g++.dg/reflect/init2.C.jj 2026-03-27 10:17:16.127298217 +0100 +++ gcc/testsuite/g++.dg/reflect/init2.C 2026-07-22 10:25:46.159993563 +0200 @@ -8,7 +8,7 @@ f () constexpr static auto srefl = ^^int; constexpr auto *p = &srefl; constexpr auto **q = &p; // { dg-error "unable to deduce" } - // { dg-message "types .auto\\*. and .std::meta::info\\* const." "" { target *-*-* } .-1 } + // { dg-message "types .auto\\*. and .decltype\\\(\\\^\\\^int\\\) const\\* const." "" { target *-*-* } .-1 } } void Jakub
