]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: use nesting and counts in print_candidates
authorDavid Malcolm <dmalcolm@redhat.com>
Wed, 14 Jan 2026 16:43:57 +0000 (11:43 -0500)
committerDavid Malcolm <dmalcolm@redhat.com>
Wed, 14 Jan 2026 16:44:19 +0000 (11:44 -0500)
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.

FWIW I've been experimenting with followups that
* show more nested information about the problems (e.g. the "void *"
vs "const void *" mismatch) - having the candidates be nested is a
useful step towards that
* potentially look at the "edit distance" of the type signatures to find
close matches, and perhaps reordering/highlighting them (e.g. in the
above candidate 2 is arguably a closer match than candidate 1, due to
the "const" snafu) - gathering an auto_vec might help with that.

gcc/cp/ChangeLog:
* call.cc (print_z_candidates): Move inform_n call into a new
inform_num_candidates function.
* class.cc (check_methods): Pass location to call to
print_candidates.
(resolve_address_of_overloaded_function): Likewise.
* cp-tree.h (print_candidates): Add location_t param.
(inform_num_candidates): New decl.
* decl.cc (make_typename_type): Pass location to call to
print_candidates.
(reshape_init_class): Likewise.
(lookup_and_check_tag): Likewise.
* decl2.cc (check_classfn): Likewise.
* error.cc (qualified_name_lookup_error): Likewise.
* init.cc (build_new_1): Likewise.
* name-lookup.cc (lookup_using_decl): Likewise.
(set_decl_namespace): Likewise.
(push_namespace): Likewise.
* parser.cc (cp_parser_nested_name_specifier_opt): Likewise.
(cp_parser_lookup_name): Likewise.
* pt.cc  (print_candidates_1): Drop, converting the looping part
into...
(flatten_candidates): ...this new function.
(inform_num_candidates): 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.  Add "error_loc" param and pass it to
inform_num_candidates to show a heading, and add nesting levels
for it and for the candidate notes.
(determine_specialization): Pass location to calls to
print_candidates.
* search.cc (lookup_member): Likewise.
* semantics.cc (finish_id_expression_1): Likewise.

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.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
20 files changed:
gcc/cp/call.cc
gcc/cp/class.cc
gcc/cp/cp-tree.h
gcc/cp/decl.cc
gcc/cp/decl2.cc
gcc/cp/error.cc
gcc/cp/init.cc
gcc/cp/name-lookup.cc
gcc/cp/parser.cc
gcc/cp/pt.cc
gcc/cp/search.cc
gcc/cp/semantics.cc
gcc/testsuite/g++.dg/cpp0x/inline-ns2.C
gcc/testsuite/g++.dg/cpp23/explicit-obj-lambda11.C
gcc/testsuite/g++.dg/diagnostic/bad-fndef-1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/lookup/decl1.C
gcc/testsuite/g++.dg/lookup/using17.C
gcc/testsuite/g++.dg/parse/non-dependent2.C
gcc/testsuite/g++.old-deja/g++.other/lineno2.C
gcc/testsuite/g++.old-deja/g++.pt/t37.C

index 34d23e998490b8c0f20e9a80e5ce9ee74183dfb9..d0386eaebcc8c93282836cf3c959d5dbea0f12e1 100644 (file)
@@ -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;
index adffd35123c646282cc2b0f985260e09a61d2446..2a65ffb1c0094d5e7974616ac1aa3ba531612baf 100644 (file)
@@ -5138,7 +5138,7 @@ check_methods (tree t)
            error_at (location_of (t), "no viable destructor for %qT", t);
          else
            error_at (location_of (t), "destructor for %qT is ambiguous", t);
-         print_candidates (dtor);
+         print_candidates (location_of (t), dtor);
 
          /* Arbitrarily prune the overload set to a single function for
             sake of error recovery.  */
@@ -9115,7 +9115,7 @@ resolve_address_of_overloaded_function (tree target_type,
          error ("no matches converting function %qD to type %q#T",
                 OVL_NAME (overload), target_type);
 
-         print_candidates (overload);
+         print_candidates (input_location, overload);
        }
       return error_mark_node;
     }
@@ -9147,7 +9147,7 @@ resolve_address_of_overloaded_function (tree target_type,
              for (match = matches; match; match = TREE_CHAIN (match))
                TREE_VALUE (match) = TREE_PURPOSE (match);
 
-             print_candidates (matches);
+             print_candidates (input_location, matches);
            }
 
          return error_mark_node;
index 6bd5b1696c4fad0d8282c10fa0cf2590bb12dd3c..0fdcb537708c40da2ce6e8ee89804411efc53725 100644 (file)
@@ -7947,7 +7947,8 @@ 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 print_candidates                   (tree);
+extern void inform_num_candidates              (location_t, int);
+extern void print_candidates                   (location_t, tree);
 extern void instantiate_pending_templates      (int);
 extern tree tsubst_default_argument            (tree, int, tree, tree,
                                                 tsubst_flags_t);
index 567aa7abe42a99216731bcb2232030fd7cf75208..46a17a596edce9bdc5e363cd7c99297d00b54216 100644 (file)
@@ -5066,7 +5066,7 @@ make_typename_type (tree context, tree name, enum tag_types tag_type,
        {
          auto_diagnostic_group d;
          error ("lookup of %qT in %qT is ambiguous", name, context);
-         print_candidates (t);
+         print_candidates (input_location, t);
        }
       return error_mark_node;
     }
@@ -7775,7 +7775,7 @@ reshape_init_class (tree type, reshape_iter *d, bool first_initializer_p,
                      auto_diagnostic_group g;
                      error ("request for member %qD is ambiguous",
                             d->cur->index);
-                     print_candidates (field);
+                     print_candidates (input_location, field);
                    }
                  else
                    error ("%qT has no non-static data member named %qD", type,
@@ -18105,7 +18105,7 @@ lookup_and_check_tag (enum tag_types tag_code, tree name,
     {
       auto_diagnostic_group d;
       error ("reference to %qD is ambiguous", name);
-      print_candidates (decl);
+      print_candidates (input_location, decl);
       return error_mark_node;
     }
 
index af2b921268d6698a1b13a286f963c38ee23e0c53..69551e5f3741da129efb02ae0fa1109bd988c85b 100644 (file)
@@ -917,7 +917,7 @@ check_classfn (tree ctype, tree function, tree template_parms)
          error_at (DECL_SOURCE_LOCATION (function),
                    "no declaration matches %q#D", function);
          if (fns)
-           print_candidates (fns);
+           print_candidates (DECL_SOURCE_LOCATION (function), fns);
          else if (DECL_CONV_FN_P (function))
            inform (DECL_SOURCE_LOCATION (function),
                    "no conversion operators declared");
index e23ea9aa48ce881960336ec5d2448c9e570743ae..c51725555a9dfefa070e2b22ca0a4bb91f64818b 100644 (file)
@@ -4988,7 +4988,7 @@ qualified_name_lookup_error (tree scope, tree name,
          auto_diagnostic_group d;
          error_at (location, "reference to %<%T::%D%> is ambiguous",
                    scope, name);
-         print_candidates (decl);
+         print_candidates (location, decl);
        }
       else
        {
index 79672e98de43eb83d521d7c89f76a9f1fef7660f..01e3d007a49193eeaef359182b4172acab74057f 100644 (file)
@@ -3430,7 +3430,7 @@ build_new_1 (vec<tree, va_gc> **placement, tree type, tree nelts,
            {
              auto_diagnostic_group d;
              error ("request for member %qD is ambiguous", fnname);
-             print_candidates (fns);
+             print_candidates (input_location, fns);
            }
          return error_mark_node;
        }
index eb6a18a18ad5c2c8e0a113056f16408b3db212b8..8847acc693c1c8e4cbfd888f9d740126d3b0c08d 100644 (file)
@@ -6336,7 +6336,8 @@ lookup_using_decl (tree scope, name_lookup &lookup)
     {
       auto_diagnostic_group d;
       error ("reference to %qD is ambiguous", lookup.name);
-      print_candidates (TREE_CODE (lookup.value) == TREE_LIST
+      print_candidates (input_location,
+                       TREE_CODE (lookup.value) == TREE_LIST
                        ? lookup.value : lookup.type);
       return NULL_TREE;
     }
@@ -6469,7 +6470,7 @@ set_decl_namespace (tree decl, tree scope, bool friendp)
       auto_diagnostic_group d;
       DECL_CONTEXT (decl) = FROB_CONTEXT (scope);
       error ("reference to %qD is ambiguous", decl);
-      print_candidates (old);
+      print_candidates (input_location, old);
       return;
     }
 
@@ -9367,7 +9368,7 @@ push_namespace (tree name, bool make_inline)
        if (TREE_CHAIN (lookup.value))
          {
            error ("%<namespace %E%> is ambiguous", name);
-           print_candidates (lookup.value);
+           print_candidates (input_location, lookup.value);
          }
       }
     else if (TREE_CODE (lookup.value) == NAMESPACE_DECL)
index c02bd1a3fce044b72beebc5dc0a34960fe948e02..2dc4863ec5285d0517edf778ae438c9f359a0eb2 100644 (file)
@@ -7607,7 +7607,8 @@ cp_parser_nested_name_specifier_opt (cp_parser *parser,
                              error_at (token->location,
                                        "reference to %qD is ambiguous",
                                        token->u.value);
-                             print_candidates (ambiguous_decls);
+                             print_candidates (token->location,
+                                               ambiguous_decls);
                            }
                          decl = error_mark_node;
                        }
@@ -34347,7 +34348,7 @@ cp_parser_lookup_name (cp_parser *parser, tree name,
          auto_diagnostic_group d;
          error_at (name_location, "reference to %qD is ambiguous",
                    name);
-         print_candidates (decl);
+         print_candidates (name_location, decl);
        }
       return error_mark_node;
     }
index bc735cfcf60210ad68895c6d2d8e3b8347fa5b01..49db5f17a545f38c2e1be8a8adb2bacdfe75aaa8 100644 (file)
@@ -2038,50 +2038,59 @@ 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 with the overload set 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 (tree cand : lkp_range (fns))
+      out.safe_push (cand);
+}
 
-       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
    be a TREE_LIST of non-functions in the case of an ambiguous lookup.  */
 
 void
-print_candidates (tree fns)
+print_candidates (location_t error_loc, 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 (error_loc, 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 (tree cand : candidates)
+       inform (DECL_SOURCE_LOCATION (cand), "candidate %i: %#qD",
+               ++idx, cand);
+    }
 }
 
 /* Get a (possibly) constrained template declaration for the
@@ -2505,7 +2514,7 @@ determine_specialization (tree template_id,
                "saw %d %<template<>%>, need %d for "
                "specializing a member function template",
                header_count, template_count + 1);
-      print_candidates (orig_fns);
+      print_candidates (DECL_SOURCE_LOCATION (decl), orig_fns);
       return error_mark_node;
     }
   else if ((templates && TREE_CHAIN (templates))
@@ -2516,7 +2525,7 @@ determine_specialization (tree template_id,
       error ("ambiguous template specialization %qD for %q+D",
             template_id, decl);
       candidates = chainon (candidates, templates);
-      print_candidates (candidates);
+      print_candidates (input_location, candidates);
       return error_mark_node;
     }
 
index 0ded4780729863c76a75ef943ee4db1b7e801cd0..db7c2d7702a572bd87e24d6a9b896fb06a83147d 100644 (file)
@@ -1243,7 +1243,7 @@ lookup_member (tree xbasetype, tree name, int protect, bool want_type,
            {
              auto_diagnostic_group d;
              error ("request for member %qD is ambiguous", name);
-             print_candidates (lfi.ambiguous);
+             print_candidates (input_location, lfi.ambiguous);
            }
          return error_mark_node;
        }
index 3e9da8d24396648900731b336bf764655297c100..78d717cade3bcca3f83021f78265291ddeace846 100644 (file)
@@ -4934,7 +4934,7 @@ finish_id_expression_1 (tree id_expression,
          auto_diagnostic_group d;
          error ("request for member %qD is ambiguous in "
                 "multiple inheritance lattice", id_expression);
-         print_candidates (decl);
+         print_candidates (input_location, decl);
          return error_mark_node;
        }
 
index 6ad9d65a6fbbbc8d948982b5a6ccfdd073447831..1dc8efeb329ffad54309418046e3e5e6d7a6a014 100644 (file)
@@ -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'" }
index 7957ad3e194306bbef9b458b06cfad16321eea4d..731caf16effa5744c8c05a273f7180253657c1e3 100644 (file)
@@ -40,8 +40,8 @@ void test2()
 
   int (*fp0)(decltype(f)&) = &decltype(f)::operator();
   int (*fp1)(int&) = &decltype(f)::operator(); // { dg-error {no matches converting function} }
+  // { dg-note "there is 1 candidate" "" { target *-*-* } .-1 }
 }
 
 // { 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 }
-
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 (file)
index 0000000..1156072
--- /dev/null
@@ -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 }
index 205ffcff1d73d10397cd5afe9e28f968d1b36b94..38319ba923de0f6dd51fa7efe3271f0e0a5d6248 100644 (file)
@@ -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" }
index 55875fe9af978894ba2f9c40b9e00c61351b1575..374dcfa8f25a3831bdd263fd7fbdb3363cf00bbb 100644 (file)
@@ -3,11 +3,11 @@
 // { dg-do compile }
 
 namespace M {
-  struct S {}; // { dg-message "candidates are: .struct M::S." "candidate 1" }
+  struct S {}; // { dg-message "candidate 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;
 
index c22497044e90ce13c6b9134b8ec2ac91de665042..eb83390206c5278bbe750d1bbb7af60427f33df0 100644 (file)
@@ -15,7 +15,7 @@ struct Foo {
 struct Baz 
 {
   int j;
-  int k; // { dg-message "candidates" }
+  int k; // { dg-message "candidate" }
   
 };
 
index d6aca8b9cfd084525eed7c74df7de7fdcea48cb5..6434f9ac45ce441b867ae299113f926a80cdf3d4 100644 (file)
@@ -2,14 +2,14 @@
 // Submitted by Nathan Sidwell <nathan@acm.org>
 // 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" }
 }
index dbf1f4403b31cdbac292e7887bc280ceea1fd6dd..ffef5e58a438568f57371f58b108c41e05054d66 100644 (file)
@@ -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) {