[gcc r16-9472] c++: Improve diagnostics for nullptr_t/info [PR126343]

Jakub Jelinek via Gcc-cvs <[email protected]> Fri, 31 Jul 2026 07:44:04 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:c68a5d17726fe3b57bcea127d21bf71ff53794a3

commit r16-9472-gc68a5d17726fe3b57bcea127d21bf71ff53794a3
Author: Jakub Jelinek <[email protected]>
Date:   Fri Jul 31 08:37:18 2026 +0200

    c++: Improve diagnostics for nullptr_t/info [PR126343]
    
    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,
    if cv qualified prints qualifications after them (so e.g.
    'std::nullptr_t const' or 'std::meta::info volatile').
    And, when printing a reflection expression, it differentiates even
    between the type alias case and non-alias, so for non-aliases prints
    'decltype(nullptr)' or 'decltype(^^int) const volatile' etc.
    
    2026-07-31  Jakub Jelinek  <[email protected]>
    
            PR c++/126343
            * error.cc (dump_type) <case NULLPTR_TYPE>: Call
            pp_c_type_qualifier_list.
            (dump_type) <case META_TYPE>: Likewise.
            (dump_expr) <case REFLECT_EXPR>: For REFLECT_EXPR on
            non-typedef 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.
            * g++.dg/cpp0x/pr124489.C: Likewise.
    
    Reviewed-by: Jason Merrill <[email protected]>
    (cherry picked from commit 525494c18742ef917ef046584af6dd78f713a449)

Diff:
---
 gcc/cp/error.cc                         | 21 +++++++++++++++-
 gcc/testsuite/g++.dg/cpp0x/pr124489.C   |  4 +--
 gcc/testsuite/g++.dg/reflect/diag6.C    |  4 +--
 gcc/testsuite/g++.dg/reflect/init2.C    |  2 +-
 gcc/testsuite/g++.dg/reflect/pr126343.C | 43 +++++++++++++++++++++++++++++++++
 5 files changed, 68 insertions(+), 6 deletions(-)

diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index a80b6e668922..55edd51ee01e 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -876,10 +876,12 @@ dump_type (cxx_pretty_printer *pp, tree t, int flags)
 
     case NULLPTR_TYPE:
       pp_cxx_ws_string (pp, "std::nullptr_t");
+      pp_c_type_qualifier_list (pp, t);
       break;
 
     case META_TYPE:
       pp_cxx_ws_string (pp, "std::meta::info");
+      pp_c_type_qualifier_list (pp, t);
       break;
 
     case SPLICE_SCOPE:
@@ -3476,7 +3478,24 @@ dump_expr (cxx_pretty_printer *pp, tree t, int flags)
 	    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))
+		  {
+		    pp_cxx_ws_string (pp, "decltype(^^int)");
+		    pp_c_type_qualifier_list (pp, h);
+		  }
+		else if (TREE_CODE (h) == NULLPTR_TYPE
+			 && !typedef_variant_p (h))
+		  {
+		    pp_cxx_ws_string (pp, "decltype(nullptr)");
+		    pp_c_type_qualifier_list (pp, h);
+		  }
+		else
+		  dump_type (pp, h, flags);
+	      }
 	    else
 	      dump_expr (pp, h, flags);
 	    break;
diff --git a/gcc/testsuite/g++.dg/cpp0x/pr124489.C b/gcc/testsuite/g++.dg/cpp0x/pr124489.C
index 8421270ff268..b4f4cb89f248 100644
--- a/gcc/testsuite/g++.dg/cpp0x/pr124489.C
+++ b/gcc/testsuite/g++.dg/cpp0x/pr124489.C
@@ -4,6 +4,6 @@
 void
 g ()
 {
-  constexpr decltype(nullptr) dm = nullptr; // { dg-message ".constexpr std::nullptr_t dm. previously declared here" }
-  constexpr decltype(nullptr) dm = nullptr; // { dg-error "redeclaration of .constexpr std::nullptr_t dm." }
+  constexpr decltype(nullptr) dm = nullptr; // { dg-message ".constexpr std::nullptr_t const dm. previously declared here" }
+  constexpr decltype(nullptr) dm = nullptr; // { dg-error "redeclaration of .constexpr std::nullptr_t const dm." }
 }
diff --git a/gcc/testsuite/g++.dg/reflect/diag6.C b/gcc/testsuite/g++.dg/reflect/diag6.C
index 61caa082551d..2eed4d0b6336 100644
--- a/gcc/testsuite/g++.dg/reflect/diag6.C
+++ b/gcc/testsuite/g++.dg/reflect/diag6.C
@@ -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 std::meta::info const dm. previously declared here" }
+  constexpr decltype(^^::) dm = ^^int; // { dg-error "redeclaration of .constexpr std::meta::info const dm." }
 }
diff --git a/gcc/testsuite/g++.dg/reflect/init2.C b/gcc/testsuite/g++.dg/reflect/init2.C
index 4c248fd9821f..c4f2292f21d5 100644
--- a/gcc/testsuite/g++.dg/reflect/init2.C
+++ b/gcc/testsuite/g++.dg/reflect/init2.C
@@ -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 .std::meta::info const\\* const." "" { target *-*-* } .-1 }
 }
 
 void
diff --git a/gcc/testsuite/g++.dg/reflect/pr126343.C b/gcc/testsuite/g++.dg/reflect/pr126343.C
new file mode 100644
index 000000000000..beabf8661a5d
--- /dev/null
+++ b/gcc/testsuite/g++.dg/reflect/pr126343.C
@@ -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 }