I'd like to ping these two C++ frontend patches for review:
* [PATCH 1/2] c++: use nesting and counts in print_candidates * https://gcc.gnu.org/pipermail/gcc-patches/2025-November/700801.html * [PATCH 2/2] c++: UX improvements for close matches in print_candidates * https://gcc.gnu.org/pipermail/gcc-patches/2025-November/700802.html Thanks Dave On Sat, 2025-11-15 at 18:14 -0500, David Malcolm wrote: > In r15-6116-gd3dd24acd74605 I updated print_z_candidates to print a > count of the number of candidates, and to show the number of each > candidate in the list if there is more than one. > > The following patch updates print_candidates to work in a similar > way, showing counts, numbering, and using nesting. > > Consider this test case for which we print 2 candidates: > > class foo > { > public: > void test (int i, int j, void *ptr, int k); > void test (int i, int j, int k); > }; > > // Wrong "const"-ness of a param, for one of the overloads (param 3). > void foo::test (int i, int j, const void *ptr, int k) > { > } > > The output before the patch is: > > test.cc:9:6: error: no declaration matches ‘void foo::test(int, int, > const void*, int)’ > 9 | void foo::test (int i, int j, const void *ptr, int k) > | ^~~ > test.cc:5:8: note: candidates are: ‘void foo::test(int, int, int)’ > 5 | void test (int i, int j, int k); > | ^~~~ > test.cc:4:8: note: ‘void foo::test(int, int, void*, > int)’ > 4 | void test (int i, int j, void *ptr, int k); > | ^~~~ > test.cc:1:7: note: ‘class foo’ defined here > 1 | class foo > | ^~~ > > With the patch, the output looks like: > > test.cc:9:6: error: no declaration matches ‘void foo::test(int, int, > const void*, int)’ > 9 | void foo::test (int i, int j, const void *ptr, int k) > | ^~~ > • there are 2 candidates > • candidate 1: ‘void foo::test(int, int, int)’ > test.cc:5:8: > 5 | void test (int i, int j, int k); > | ^~~~ > • candidate 2: ‘void foo::test(int, int, void*, int)’ > test.cc:4:8: > 4 | void test (int i, int j, void *ptr, int k); > | ^~~~ > test.cc:1:7: note: ‘class foo’ defined here > 1 | class foo > | ^~~ > > which I believe is much more readable. > > I dabbled with removing the "there is 1 candidate" line for the case > of > a single candidate, but I think I prefer it to be present. > > Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu. > > OK for trunk? > Dave > > gcc/cp/ChangeLog: > * call.cc (print_z_candidates): Move inform_n call into a > new > inform_num_candidates function. > * cp-tree.h (inform_num_candidates): New decl. > * pt.cc (print_candidates_1): Drop, converting the looping > part > into... > (flatten_candidates): ...this new function. > (print_candidates): Use flatten_candidates to build an > auto_vec > of candidates, and use this to print them here, rather than > in > print_candidates_1. Eliminate the dynamic allocation of > spaces > for a prefix in favor of printing "candidate %i" when there > is > more than one candidate. Use inform_num_candidates to show > a > heading, and add nesting levels for it and for the candidate > notes. > > gcc/testsuite/ChangeLog: > * g++.dg/cpp0x/inline-ns2.C: Make dg-message directives non- > empty. > * g++.dg/cpp23/explicit-obj-lambda11.C: Prune the extra > note. > * g++.dg/diagnostic/bad-fndef-1.C: New test. > * g++.dg/lookup/decl1.C: Give the dg-message directives > different > messages. > * g++.dg/lookup/using17.C: Update expected output. > * g++.dg/parse/non-dependent2.C: Likewise. > * g++.old-deja/g++.other/lineno2.C: Give the dg-message > directives > different messages. > * g++.old-deja/g++.pt/t37.C: Likewise. > --- > gcc/cp/call.cc | 5 +- > gcc/cp/cp-tree.h | 1 + > gcc/cp/pt.cc | 67 +++++++++++------ > -- > gcc/testsuite/g++.dg/cpp0x/inline-ns2.C | 18 ++--- > .../g++.dg/cpp23/explicit-obj-lambda11.C | 1 + > gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C | 15 +++++ > gcc/testsuite/g++.dg/lookup/decl1.C | 8 +-- > gcc/testsuite/g++.dg/lookup/using17.C | 4 +- > gcc/testsuite/g++.dg/parse/non-dependent2.C | 2 +- > .../g++.old-deja/g++.other/lineno2.C | 8 +-- > gcc/testsuite/g++.old-deja/g++.pt/t37.C | 10 +-- > 11 files changed, 82 insertions(+), 57 deletions(-) > create mode 100644 gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C > > diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc > index f80d597b33944..fc4e6da1f9a20 100644 > --- a/gcc/cp/call.cc > +++ b/gcc/cp/call.cc > @@ -4261,9 +4261,8 @@ print_z_candidates (location_t loc, struct > z_candidate *candidates, > ++num_candidates; > } > > - inform_n (loc, > - num_candidates, "there is %i candidate", "there are %i > candidates", > - num_candidates); > + inform_num_candidates (loc, num_candidates); > + > auto_diagnostic_nesting_level sentinel2; > > int candidate_idx = 0; > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h > index 5e8d1c9644c5b..d0d057c927017 100644 > --- a/gcc/cp/cp-tree.h > +++ b/gcc/cp/cp-tree.h > @@ -7890,6 +7890,7 @@ extern tree > maybe_process_partial_specialization (tree); > extern tree most_specialized_instantiation (tree); > extern tree most_specialized_partial_spec (tree, > tsubst_flags_t, bool = false); > extern tree most_constrained_function (tree); > +extern void inform_num_candidates (location_t, int); > extern void print_candidates (tree); > extern void instantiate_pending_templates (int); > extern tree tsubst_default_argument (tree, int, tree, > tree, > diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc > index bbbf49363e8f0..59e9585aac94d 100644 > --- a/gcc/cp/pt.cc > +++ b/gcc/cp/pt.cc > @@ -2033,39 +2033,30 @@ explicit_class_specialization_p (tree type) > return !uses_template_parms (CLASSTYPE_TI_ARGS (type)); > } > > -/* Print the list of functions at FNS, going through all the > overloads > - for each element of the list. Alternatively, FNS cannot be a > - TREE_LIST, in which case it will be printed together with all the > - overloads. > - > - MORE and *STR should respectively be FALSE and NULL when the > function > - is called from the outside. They are used internally on > recursive > - calls. print_candidates manages the two parameters and leaves > NULL > - in *STR when it ends. */ > +/* Populate OUT the list of functions at FNS, going through all the > + overloads for each element of the list. Alternatively, FNS can > be a > + TREE_LIST, in which case it will be added together with all the > + overloads. */ > > static void > -print_candidates_1 (tree fns, char **str, bool more = false) > +flatten_candidates (tree fns, auto_vec<tree> &out) > { > if (TREE_CODE (fns) == TREE_LIST) > for (; fns; fns = TREE_CHAIN (fns)) > - print_candidates_1 (TREE_VALUE (fns), str, more || TREE_CHAIN > (fns)); > + flatten_candidates (TREE_VALUE (fns), out); > else > - for (lkp_iterator iter (fns); iter;) > - { > - tree cand = *iter; > - ++iter; > + for (lkp_iterator iter (fns); iter; ++iter) > + out.safe_push (*iter); > +} > > - const char *pfx = *str; > - if (!pfx) > - { > - if (more || iter) > - pfx = _("candidates are:"); > - else > - pfx = _("candidate is:"); > - *str = get_spaces (pfx); > - } > - inform (DECL_SOURCE_LOCATION (cand), "%s %#qD", pfx, cand); > - } > +/* Print a note announcing a list of candidates. */ > + > +void > +inform_num_candidates (location_t loc, int num_candidates) > +{ > + inform_n (loc, > + num_candidates, "there is %i candidate", "there are %i > candidates", > + num_candidates); > } > > /* Print the list of candidate FNS in an error message. FNS can > also > @@ -2074,9 +2065,27 @@ print_candidates_1 (tree fns, char **str, bool > more = false) > void > print_candidates (tree fns) > { > - char *str = NULL; > - print_candidates_1 (fns, &str); > - free (str); > + auto_vec<tree> candidates; > + flatten_candidates (fns, candidates); > + > + auto_diagnostic_nesting_level sentinel; > + > + inform_num_candidates (UNKNOWN_LOCATION, candidates.length ()); > + > + auto_diagnostic_nesting_level sentinel2; > + > + if (candidates.length () == 1) > + { > + tree cand = candidates[0]; > + inform (DECL_SOURCE_LOCATION (cand), "candidate is: %#qD", > cand); > + } > + else > + { > + int idx = 0; > + for (auto cand : candidates) > + inform (DECL_SOURCE_LOCATION (cand), "candidate %i: %#qD", > + ++idx, cand); > + } > } > > /* Get a (possibly) constrained template declaration for the > diff --git a/gcc/testsuite/g++.dg/cpp0x/inline-ns2.C > b/gcc/testsuite/g++.dg/cpp0x/inline-ns2.C > index 6ad9d65a6fbbb..1dc8efeb329ff 100644 > --- a/gcc/testsuite/g++.dg/cpp0x/inline-ns2.C > +++ b/gcc/testsuite/g++.dg/cpp0x/inline-ns2.C > @@ -2,17 +2,17 @@ > > namespace Q { > inline namespace V1 { > - extern int i; // { dg-message "" } > - extern int j; // { dg-message "" } > - void f(); // { dg-message "" } > - void g(); // { dg-message "" } > + extern int i; // { dg-message "candidate" } > + extern int j; // { dg-message "candidate" } > + void f(); // { dg-message "candidate" } > + void g(); // { dg-message "candidate" } > } > inline namespace V2 { > - extern int j; // { dg-message "" } > - void g(); // { dg-message "" } > + extern int j; // { dg-message "candidate" } > + void g(); // { dg-message "candidate" } > } > - extern int i; // { dg-message "" } > - void f(); // { dg-message "" } > + extern int i; // { dg-message "candidate" > } > + void f(); // { dg-message "candidate" } > void h(); > } > namespace R { > @@ -22,4 +22,4 @@ int Q::i = 1; // { dg-error > "ambiguous" } > int Q::j = 1; // { dg-error "ambiguous" } > void Q::f() { } // { dg-error "ambiguous" } > void Q::g() { } // { dg-error "ambiguous" } > -void R::h() { } // { dg-error "" } > +void R::h() { } // { dg-error "should have > been declared inside 'R'" } > diff --git a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C > b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C > index 7957ad3e19430..1fc67d624714a 100644 > --- a/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C > +++ b/gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C > @@ -45,3 +45,4 @@ void test2() > // { dg-error "a lambda with captures may not have an explicit > object parameter of an unrelated type" {depends on PR112874} { xfail > *-*-* } t2_f } > // { dg-note "candidate is" "" { target *-*-* } t2_f } > > +// { dg-prune-output "cc1plus: note: there is 1 candidate" } > diff --git a/gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C > b/gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C > new file mode 100644 > index 0000000000000..1156072b11f16 > --- /dev/null > +++ b/gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C > @@ -0,0 +1,15 @@ > +class foo // { dg-message "'class foo' defined here" } > +{ > +public: > + void test (int i, int j, void *ptr, int k); // { dg-line > close_decl } > + void test (int i, int j, int k); // { dg-line > other_decl } > +}; > + > +// Wrong "const"-ness of a param, for one of the overloads (param > 3). > +void foo::test (int i, int j, const void *ptr, int k) // { dg-line > defn } > +{ > +} > + > +// { dg-error "6: no declaration matches" "error" { target *-*-* } > defn } > +// { dg-message "8: candidate 1: " "candidate 1" { target *-*-* } > other_decl } > +// { dg-message "8: candidate 2: " "candidate 2" { target *-*-* } > close_decl } > diff --git a/gcc/testsuite/g++.dg/lookup/decl1.C > b/gcc/testsuite/g++.dg/lookup/decl1.C > index 205ffcff1d73d..38319ba923de0 100644 > --- a/gcc/testsuite/g++.dg/lookup/decl1.C > +++ b/gcc/testsuite/g++.dg/lookup/decl1.C > @@ -20,10 +20,10 @@ C2<X>::operator C1<Y>() > } > > struct A { // { dg-message "defined here" } > - operator int (); // { dg-message "operator" } > - operator float (); // { dg-message "operator" } > - operator float () const; // { dg-message "operator" } > - template <typename T> operator T * (); // { dg-message "operator" > } > + operator int (); // { dg-message "operator > int" } > + operator float (); // { dg-message "operator > float" } > + operator float () const; // { dg-message "operator > float" } > + template <typename T> operator T * (); // { dg-message "operator > T" } > }; > > A::operator short () { // { dg-error "no declaration matches" } > diff --git a/gcc/testsuite/g++.dg/lookup/using17.C > b/gcc/testsuite/g++.dg/lookup/using17.C > index 55875fe9af978..1e69dbe36376d 100644 > --- a/gcc/testsuite/g++.dg/lookup/using17.C > +++ b/gcc/testsuite/g++.dg/lookup/using17.C > @@ -3,11 +3,11 @@ > // { dg-do compile } > > namespace M { > - struct S {}; // { dg-message "candidates are: .struct M::S." > "candidate 1" } > + struct S {}; // { dg-message "candidates 1: 'struct M::S'"} > } > > int S; > -struct S {}; // { dg-message ".struct S." "candidate 2" } > +struct S {}; // { dg-message "candidate 2: 'struct S'" } > > using namespace M; > > diff --git a/gcc/testsuite/g++.dg/parse/non-dependent2.C > b/gcc/testsuite/g++.dg/parse/non-dependent2.C > index c22497044e90c..eb83390206c52 100644 > --- a/gcc/testsuite/g++.dg/parse/non-dependent2.C > +++ b/gcc/testsuite/g++.dg/parse/non-dependent2.C > @@ -15,7 +15,7 @@ struct Foo { > struct Baz > { > int j; > - int k; // { dg-message "candidates" } > + int k; // { dg-message "candidate" } > > }; > > diff --git a/gcc/testsuite/g++.old-deja/g++.other/lineno2.C > b/gcc/testsuite/g++.old-deja/g++.other/lineno2.C > index d6aca8b9cfd08..6434f9ac45ce4 100644 > --- a/gcc/testsuite/g++.old-deja/g++.other/lineno2.C > +++ b/gcc/testsuite/g++.old-deja/g++.other/lineno2.C > @@ -2,14 +2,14 @@ > // Submitted by Nathan Sidwell <[email protected]> > // Bug: g++ wasn't listing candidates for a failed conversion. > > -void f(int, double); // { dg-message "" } candidate > -void f(double, int); // { dg-message "" } candidate > -void f(int); // { dg-message "" } candidate > +void f(int, double); // { dg-message "candidate" } > +void f(double, int); // { dg-message "candidate" } > +void f(int); // { dg-message "candidate" } > > int > main () > { > void (*ptr)(int, int); > > - ptr = &f; // { dg-error "" } no match > + ptr = &f; // { dg-error "no matches" } > } > diff --git a/gcc/testsuite/g++.old-deja/g++.pt/t37.C > b/gcc/testsuite/g++.old-deja/g++.pt/t37.C > index dbf1f4403b31c..ffef5e58a4385 100644 > --- a/gcc/testsuite/g++.old-deja/g++.pt/t37.C > +++ b/gcc/testsuite/g++.old-deja/g++.pt/t37.C > @@ -1,14 +1,14 @@ > // { dg-do compile } > > -class A { // { dg-message "A::A" } synthesized copy ctor > - // { dg-message "defined here" "note" { target *-*-* } .-1 } > +class A { // { dg-message "A::A\\\(const A&\\\)" } synthesized copy > ctor > + // { dg-message "'class A' defined here" "note" { target *-*-* } > .-1 } > public: > - A(int); // { dg-message "A::A" } > - A(float); // { dg-message "A::A" } > + A(int); // { dg-message "A::A\\\(int\\\)" } > + A(float); // { dg-message "A::A\\\(float\\\)" > } > ~A(); > }; > > -A::A() { // { dg-error "" } > +A::A() { // { dg-error "no declaration matches" } > } > > A::A(int) {
