Re: Identifying function-lookup failures due to argument name mismatches

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: Identifying function-lookup failures due to argument name mismatches
Дата
Msg-id 1775065.1755817133@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: Identifying function-lookup failures due to argument name mismatches  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: Identifying function-lookup failures due to argument name mismatches
Список pgsql-hackers
I wrote:
> Dunno, I think the new messages already cover all the interesting
> cases of argument name mismatch.  I'm hesitant to touch the
> longstanding hint, and if I did I'd probably change it more than that,
> to something like

> ERROR:  function foo(integer) does not exist
> DETAIL:  No function of that name matches the given argument types.
> HINT:  You might need to add explicit type casts.

> because whoever wrote it originally had a poor grasp of our
> error message style guide.  But that'd result in very widespread
> changes in our regression test outputs, probably likewise break
> the regression tests of extensions and other downstream code,
> and generally cause a lot more pain than I think it's worth.
> (Maybe others think differently?)

I decided to investigate just how bad changing this would be, and it
seems maybe not *that* awful.  v3-0001 attached is much like v2-0001,
and then 0002 shows the effects of changing the wording of this hint,
and 0003 and 0004 explore cleaning up some related messages.  I count
the following numbers of changed messages in each patch:

$ grep '^-HINT' v3-0001-Provide-more-specific-error-hints-for-function-lo.patch | wc
     17     306    1803
$ grep '^-HINT' v3-0002-Change-the-wording-of-our-traditional-function-no.patch | wc
     40     715    4214
$ grep '^-HINT' v3-0003-Improve-the-messages-for-operator-not-found-too.patch | wc
     19     342    2014
$ grep '^-HINT' v3-0004-Mop-up-a-few-other-error-message-style-violations.patch | wc
      7     109     644

So doing all of this is certainly a little bit invasive, but it's not
out of the question IMO.  On the other hand it could certainly be
argued that 0002-0004 are just style nannyism.

0001 makes a couple of changes compared to v2.  I adopted your thought
of passing back a flag bit about a schema name being given after all.
I concluded that was a bit cleaner than the other way.  I still think
it's best for ParseFuncOrColumn to uniformly use "argnames != NIL"
for checking whether there are argnames, though.  Also, I added a
flag bit and error message for the case where none of the candidate
functions have the right number of arguments, because when that's
true, we'll never get to looking at argument names or types.  And
I switched some of the messages from HINT to DETAIL.

            regards, tom lane

From d843d6508dac46a4fe3df85100593baf4187837a Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 21 Aug 2025 16:43:50 -0400
Subject: [PATCH v3 1/4] Provide more-specific error hints for function lookup
 failures.

Up to now we've contented ourselves with a one-size-fits-all error
hint when we fail to find any match to a function or procedure call.
That was mostly okay in the beginning, but in the presence of named
arguments it's really not great.  We at least ought to distinguish
"function name doesn't exist" from "function name exists, but not with
those argument names".  And the rules for named-argument matching are
arcane enough that some more detail seems warranted if we match the
argument names but the call still doesn't work.

This patch proposes a framework for dealing with these problems:
FuncnameGetCandidates and related code should pass back a bitmask of
flags showing how far the match succeeded.  This allows a considerable
amount of granularity in the reports.  The set-bits-in-a-bitmask
approach means that when there are multiple candidate functions, the
report will reflect the match(es) that got the furthest, which seems
correct.  Also, we can avoid mentioning "maybe add casts" unless
failure to match argument types is actually the issue.

The specific messages I've written could perhaps do with more
bike-shedding.

Reported-by: Dominique Devienne <ddevienne@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1756041.1754616558@sss.pgh.pa.us
---
 src/backend/catalog/namespace.c               |  92 +++++++++----
 src/backend/catalog/pg_aggregate.c            |   2 +
 src/backend/parser/parse_func.c               | 125 ++++++++++++++++--
 src/backend/utils/adt/regproc.c               |  13 +-
 src/backend/utils/adt/ruleutils.c             |   2 +
 src/include/catalog/namespace.h               |  20 ++-
 src/include/parser/parse_func.h               |   1 +
 src/pl/plperl/expected/plperl_elog.out        |   2 +-
 src/pl/plperl/expected/plperl_elog_1.out      |   2 +-
 src/pl/plpython/expected/plpython_error.out   |   2 +-
 .../traces/pipeline_abort.trace               |   2 +-
 .../expected/test_extensions.out              |   3 +-
 .../regress/expected/create_procedure.out     |   2 +-
 src/test/regress/expected/misc_functions.out  |   4 +-
 src/test/regress/expected/plpgsql.out         |   2 +-
 src/test/regress/expected/polymorphism.out    |  60 ++++++++-
 src/test/regress/expected/temp.out            |   2 +-
 src/test/regress/sql/polymorphism.sql         |  17 +++
 18 files changed, 297 insertions(+), 56 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d97d632a7ef..524f325be03 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -233,7 +233,7 @@ static void RemoveTempRelationsCallback(int code, Datum arg);
 static void InvalidationCallback(Datum arg, int cacheid, uint32 hashvalue);
 static bool MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
                            bool include_out_arguments, int pronargs,
-                           int **argnumbers);
+                           int **argnumbers, int *fgc_flags);

 /*
  * Recomputing the namespace path can be costly when done frequently, such as
@@ -1118,15 +1118,15 @@ TypeIsVisibleExt(Oid typid, bool *is_missing)

 /*
  * FuncnameGetCandidates
- *        Given a possibly-qualified function name and argument count,
+ *        Given a possibly-qualified routine name, argument count, and arg names,
  *        retrieve a list of the possible matches.
  *
- * If nargs is -1, we return all functions matching the given name,
+ * If nargs is -1, we return all routines matching the given name,
  * regardless of argument count.  (argnames must be NIL, and expand_variadic
  * and expand_defaults must be false, in this case.)
  *
  * If argnames isn't NIL, we are considering a named- or mixed-notation call,
- * and only functions having all the listed argument names will be returned.
+ * and only routines having all the listed argument names will be returned.
  * (We assume that length(argnames) <= nargs and all the passed-in names are
  * distinct.)  The returned structs will include an argnumbers array showing
  * the actual argument index for each logical argument position.
@@ -1184,14 +1184,21 @@ TypeIsVisibleExt(Oid typid, bool *is_missing)
  * The caller might end up discarding such an entry anyway, but if it selects
  * such an entry it should react as though the call were ambiguous.
  *
- * If missing_ok is true, an empty list (NULL) is returned if the name was
- * schema-qualified with a schema that does not exist.  Likewise if no
- * candidate is found for other reasons.
+ * We return an empty list (NULL) if no suitable matches can be found.
+ * If the function name was schema-qualified with a schema that does not
+ * exist, then we return an empty list if missing_ok is true and otherwise
+ * throw an error.  (missing_ok does not affect the behavior otherwise.)
+ *
+ * The output argument *fgc_flags is filled with a bitmask indicating how
+ * far we were able to match the supplied information.  This is not of much
+ * interest if any candidates were found, but if not, it can help callers
+ * produce an on-point error message.
  */
 FuncCandidateList
 FuncnameGetCandidates(List *names, int nargs, List *argnames,
                       bool expand_variadic, bool expand_defaults,
-                      bool include_out_arguments, bool missing_ok)
+                      bool include_out_arguments, bool missing_ok,
+                      int *fgc_flags)
 {
     FuncCandidateList resultList = NULL;
     bool        any_special = false;
@@ -1204,15 +1211,20 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
     /* check for caller error */
     Assert(nargs >= 0 || !(expand_variadic | expand_defaults));

+    /* initialize output fgc_flags to empty */
+    *fgc_flags = 0;
+
     /* deconstruct the name list */
     DeconstructQualifiedName(names, &schemaname, &funcname);

     if (schemaname)
     {
         /* use exact schema given */
+        *fgc_flags |= FGC_SCHEMA_GIVEN; /* report that a schema is given */
         namespaceId = LookupExplicitNamespace(schemaname, missing_ok);
         if (!OidIsValid(namespaceId))
             return NULL;
+        *fgc_flags |= FGC_SCHEMA_MATCH; /* report that the schema is valid */
     }
     else
     {
@@ -1263,6 +1275,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
                 continue;        /* proc is not in search path */
         }

+        *fgc_flags |= FGC_NAME_MATCH;    /* we found a matching routine name */
+
         /*
          * If we are asked to match to OUT arguments, then use the
          * proallargtypes array (which includes those); otherwise use
@@ -1297,16 +1311,6 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
             /*
              * Call uses named or mixed notation
              *
-             * Named or mixed notation can match a variadic function only if
-             * expand_variadic is off; otherwise there is no way to match the
-             * presumed-nameless parameters expanded from the variadic array.
-             */
-            if (OidIsValid(procform->provariadic) && expand_variadic)
-                continue;
-            va_elem_type = InvalidOid;
-            variadic = false;
-
-            /*
              * Check argument count.
              */
             Assert(nargs >= 0); /* -1 not supported with argnames */
@@ -1325,12 +1329,33 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
             if (pronargs != nargs && !use_defaults)
                 continue;

+            /* We found a routine with a suitable number of arguments */
+            *fgc_flags |= FGC_ARGCOUNT_MATCH;
+
             /* Check for argument name match, generate positional mapping */
             if (!MatchNamedCall(proctup, nargs, argnames,
                                 include_out_arguments, pronargs,
-                                &argnumbers))
+                                &argnumbers, fgc_flags))
                 continue;

+            /*
+             * Named or mixed notation can match a variadic function only if
+             * expand_variadic is off; otherwise there is no way to match the
+             * presumed-nameless parameters expanded from the variadic array.
+             * However, we postpone the check until here because we want to
+             * perform argument name matching anyway (using the variadic array
+             * argument's name).  This allows us to give an on-point error
+             * message if the user forgets to say VARIADIC in what would have
+             * been a valid call with it.
+             */
+            if (OidIsValid(procform->provariadic) && expand_variadic)
+                continue;
+            va_elem_type = InvalidOid;
+            variadic = false;
+
+            /* We found a fully-valid call using argument names */
+            *fgc_flags |= FGC_ARGNAMES_VALID;
+
             /* Named argument matching is always "special" */
             any_special = true;
         }
@@ -1372,6 +1397,9 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
             /* Ignore if it doesn't match requested argument count */
             if (nargs >= 0 && pronargs != nargs && !variadic && !use_defaults)
                 continue;
+
+            /* We found a routine with a suitable number of arguments */
+            *fgc_flags |= FGC_ARGCOUNT_MATCH;
         }

         /*
@@ -1580,11 +1608,13 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
  * the mapping from call argument positions to actual function argument
  * numbers.  Defaulted arguments are included in this map, at positions
  * after the last supplied argument.
+ *
+ * We also add flag bits to *fgc_flags reporting on how far the match got.
  */
 static bool
 MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
                bool include_out_arguments, int pronargs,
-               int **argnumbers)
+               int **argnumbers, int *fgc_flags)
 {
     Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup);
     int            numposargs = nargs - list_length(argnames);
@@ -1593,6 +1623,7 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
     char      **p_argnames;
     char       *p_argmodes;
     bool        arggiven[FUNC_MAX_ARGS];
+    bool        arg_filled_twice = false;
     bool        isnull;
     int            ap;                /* call args position */
     int            pp;                /* proargs position */
@@ -1646,9 +1677,9 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,
                 continue;
             if (p_argnames[i] && strcmp(p_argnames[i], argname) == 0)
             {
-                /* fail if argname matches a positional argument */
+                /* note if argname matches a positional argument */
                 if (arggiven[pp])
-                    return false;
+                    arg_filled_twice = true;
                 arggiven[pp] = true;
                 (*argnumbers)[ap] = pp;
                 found = true;
@@ -1665,6 +1696,16 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,

     Assert(ap == nargs);        /* processed all actual parameters */

+    /* If we get here, the function did match all the supplied argnames */
+    *fgc_flags |= FGC_ARGNAMES_MATCH;
+
+    /* ... however, some of them might have been placed wrong */
+    if (arg_filled_twice)
+        return false;            /* some argname matched a positional argument */
+
+    /* If we get here, the call doesn't violate the rules for mixed notation */
+    *fgc_flags |= FGC_ARGNAMES_PLACED;
+
     /* Check for default arguments */
     if (nargs < pronargs)
     {
@@ -1683,6 +1724,9 @@ MatchNamedCall(HeapTuple proctup, int nargs, List *argnames,

     Assert(ap == pronargs);        /* processed all function parameters */

+    /* If we get here, the call supplies all the required arguments */
+    *fgc_flags |= FGC_ARGNAMES_ALL;
+
     return true;
 }

@@ -1746,11 +1790,13 @@ FunctionIsVisibleExt(Oid funcid, bool *is_missing)
         char       *proname = NameStr(procform->proname);
         int            nargs = procform->pronargs;
         FuncCandidateList clist;
+        int            fgc_flags;

         visible = false;

         clist = FuncnameGetCandidates(list_make1(makeString(proname)),
-                                      nargs, NIL, false, false, false, false);
+                                      nargs, NIL, false, false, false, false,
+                                      &fgc_flags);

         for (; clist; clist = clist->next)
         {
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index c62e8acd413..a1cb5719a0c 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -836,6 +836,7 @@ lookup_agg_function(List *fnName,
     Oid            vatype;
     Oid           *true_oid_array;
     FuncDetailCode fdresult;
+    int            fgc_flags;
     AclResult    aclresult;
     int            i;

@@ -848,6 +849,7 @@ lookup_agg_function(List *fnName,
      */
     fdresult = func_get_detail(fnName, NIL, NIL,
                                nargs, input_types, false, false, false,
+                               &fgc_flags,
                                &fnOid, rettype, &retset,
                                &nvargs, &vatype,
                                &true_oid_array, NULL);
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 583bbbf232f..febccb74843 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -42,6 +42,8 @@ typedef enum
     FUNCLOOKUP_AMBIGUOUS,
 } FuncLookupError;

+static int    func_lookup_failure_details(int fgc_flags, List *argnames,
+                                        bool proc_call);
 static void unify_hypothetical_args(ParseState *pstate,
                                     List *fargs, int numAggregatedArgs,
                                     Oid *actual_arg_types, Oid *declared_arg_types);
@@ -115,6 +117,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
     int            nvargs;
     Oid            vatype;
     FuncDetailCode fdresult;
+    int            fgc_flags;
     char        aggkind = 0;
     ParseCallbackState pcbstate;

@@ -266,6 +269,7 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
     fdresult = func_get_detail(funcname, fargs, argnames, nargs,
                                actual_arg_types,
                                !func_variadic, true, proc_call,
+                               &fgc_flags,
                                &funcid, &rettype, &retset,
                                &nvargs, &vatype,
                                &declared_arg_types, &argdefaults);
@@ -601,7 +605,9 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,

         /*
          * No function, and no column either.  Since we're dealing with
-         * function notation, report "function does not exist".
+         * function notation, report "function/procedure does not exist".
+         * Depending on what was returned in fgc_flags, we can add some color
+         * to that with detail or hint messages.
          */
         if (list_length(agg_order) > 1 && !agg_within_group)
         {
@@ -622,8 +628,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
                      errmsg("procedure %s does not exist",
                             func_signature_string(funcname, nargs, argnames,
                                                   actual_arg_types)),
-                     errhint("No procedure matches the given name and argument types. "
-                             "You might need to add explicit type casts."),
+                     func_lookup_failure_details(fgc_flags, argnames,
+                                                 proc_call),
                      parser_errposition(pstate, location)));
         else
             ereport(ERROR,
@@ -631,8 +637,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
                      errmsg("function %s does not exist",
                             func_signature_string(funcname, nargs, argnames,
                                                   actual_arg_types)),
-                     errhint("No function matches the given name and argument types. "
-                             "You might need to add explicit type casts."),
+                     func_lookup_failure_details(fgc_flags, argnames,
+                                                 proc_call),
                      parser_errposition(pstate, location)));
     }

@@ -905,6 +911,93 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
     return retval;
 }

+/*
+ * Interpret the fgc_flags and issue a suitable detail or hint message.
+ *
+ * Helper function to reduce code duplication while throwing a
+ * function-not-found error.
+ */
+static int
+func_lookup_failure_details(int fgc_flags, List *argnames, bool proc_call)
+{
+    /*
+     * If not FGC_NAME_MATCH, we shouldn't raise the question of whether the
+     * arguments are wrong.  It does seem worth calling the search_path to the
+     * user's mind if the function name was not schema-qualified; but if it
+     * was, there's really nothing to add to the basic "function/procedure %s
+     * does not exist" message.
+     *
+     * Note: we passed missing_ok = false to FuncnameGetCandidates, so there's
+     * no need to consider FGC_SCHEMA_MATCH here: we'd have already thrown an
+     * error if an explicitly-given schema doesn't exist.
+     */
+    if (!(fgc_flags & FGC_NAME_MATCH))
+    {
+        if (fgc_flags & FGC_SCHEMA_GIVEN)
+            return 0;            /* schema-qualified name */
+        else if (proc_call)
+            return errdetail("There is no procedure of that name in the search_path.");
+        else
+            return errdetail("There is no function of that name in the search_path.");
+    }
+
+    /*
+     * Next, complain if nothing had the right number of arguments.  (This
+     * takes precedence over wrong-argnames cases because we won't even look
+     * at the argnames unless there's a workable number of arguments.)
+     */
+    if (!(fgc_flags & FGC_ARGCOUNT_MATCH))
+    {
+        if (proc_call)
+            return errdetail("No procedure of that name has the right number of arguments.");
+        else
+            return errdetail("No function of that name has the right number of arguments.");
+    }
+
+    /*
+     * If there are argnames, and we failed to match them, again we should
+     * mention that and not bring up the argument types.
+     */
+    if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_MATCH))
+    {
+        if (proc_call)
+            return errdetail("No procedure of that name matches the given argument names.");
+        else
+            return errdetail("No function of that name matches the given argument names.");
+    }
+
+    /*
+     * We could have matched all the given argnames and still not have had a
+     * valid call, either because of improper use of mixed notation, or
+     * because of missing arguments, or because the user misused VARIADIC. The
+     * rules about named-argument matching are finicky enough that it's worth
+     * trying to be specific about the problem.  (The messages here are chosen
+     * with full knowledge of the steps that namespace.c uses while checking a
+     * potential match.)
+     */
+    if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_PLACED))
+        return errdetail("Named arguments were incorrectly combined with positional arguments.");
+
+    if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_ALL))
+        return errdetail("Not all required arguments were supplied.");
+
+    if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_VALID))
+        return errhint("This call would be correct if the variadic array were labeled VARIADIC and placed last.");
+
+    if (fgc_flags & FGC_VARIADIC_FAIL)
+        return errhint("The VARIADIC parameter must be placed last, even when using argument names.");
+
+    /*
+     * Otherwise, give our traditional hint about argument types and casting.
+     */
+    if (proc_call)
+        return errhint("No procedure matches the given name and argument types. "
+                       "You might need to add explicit type casts.");
+    else
+        return errhint("No function matches the given name and argument types. "
+                       "You might need to add explicit type casts.");
+}
+

 /* func_match_argtypes()
  *
@@ -1372,9 +1465,14 @@ func_select_candidate(int nargs,
  *    1) check for possible interpretation as a type coercion request
  *    2) apply the ambiguous-function resolution rules
  *
- * Return values *funcid through *true_typeids receive info about the function.
- * If argdefaults isn't NULL, *argdefaults receives a list of any default
- * argument expressions that need to be added to the given arguments.
+ * If there is no match at all, we return FUNCDETAIL_NOTFOUND, and *fgc_flags
+ * is filled with some flags that may be useful for issuing an on-point error
+ * message (see FuncnameGetCandidates).
+ *
+ * On success, return values *funcid through *true_typeids receive info about
+ * the function.  If argdefaults isn't NULL, *argdefaults receives a list of
+ * any default argument expressions that need to be added to the given
+ * arguments.
  *
  * When processing a named- or mixed-notation call (ie, fargnames isn't NIL),
  * the returned true_typeids and argdefaults are ordered according to the
@@ -1400,6 +1498,7 @@ func_get_detail(List *funcname,
                 bool expand_variadic,
                 bool expand_defaults,
                 bool include_out_arguments,
+                int *fgc_flags, /* return value */
                 Oid *funcid,    /* return value */
                 Oid *rettype,    /* return value */
                 bool *retset,    /* return value */
@@ -1424,7 +1523,8 @@ func_get_detail(List *funcname,
     /* Get list of possible candidates from namespace search */
     raw_candidates = FuncnameGetCandidates(funcname, nargs, fargnames,
                                            expand_variadic, expand_defaults,
-                                           include_out_arguments, false);
+                                           include_out_arguments, false,
+                                           fgc_flags);

     /*
      * Quickly check if there is an exact match to the input datatypes (there
@@ -1594,7 +1694,10 @@ func_get_detail(List *funcname,
          */
         if (fargnames != NIL && !expand_variadic && nargs > 0 &&
             best_candidate->argnumbers[nargs - 1] != nargs - 1)
+        {
+            *fgc_flags |= FGC_VARIADIC_FAIL;
             return FUNCDETAIL_NOTFOUND;
+        }

         *funcid = best_candidate->oid;
         *nvargs = best_candidate->nvargs;
@@ -2053,6 +2156,7 @@ LookupFuncNameInternal(ObjectType objtype, List *funcname,
 {
     Oid            result = InvalidOid;
     FuncCandidateList clist;
+    int            fgc_flags;

     /* NULL argtypes allowed for nullary functions only */
     Assert(argtypes != NULL || nargs == 0);
@@ -2062,7 +2166,8 @@ LookupFuncNameInternal(ObjectType objtype, List *funcname,

     /* Get list of candidate objects */
     clist = FuncnameGetCandidates(funcname, nargs, NIL, false, false,
-                                  include_out_arguments, missing_ok);
+                                  include_out_arguments, missing_ok,
+                                  &fgc_flags);

     /* Scan list for a match to the arg types (if specified) and the objtype */
     for (; clist != NULL; clist = clist->next)
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index b8bbe95e82e..0c5dec025d7 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -71,6 +71,7 @@ regprocin(PG_FUNCTION_ARGS)
     RegProcedure result;
     List       *names;
     FuncCandidateList clist;
+    int            fgc_flags;

     /* Handle "-" or numeric OID */
     if (parseDashOrOid(pro_name_or_oid, &result, escontext))
@@ -93,7 +94,8 @@ regprocin(PG_FUNCTION_ARGS)
     if (names == NIL)
         PG_RETURN_NULL();

-    clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true);
+    clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true,
+                                  &fgc_flags);

     if (clist == NULL)
         ereturn(escontext, (Datum) 0,
@@ -164,13 +166,15 @@ regprocout(PG_FUNCTION_ARGS)
         {
             char       *nspname;
             FuncCandidateList clist;
+            int            fgc_flags;

             /*
              * Would this proc be found (uniquely!) by regprocin? If not,
              * qualify it.
              */
             clist = FuncnameGetCandidates(list_make1(makeString(proname)),
-                                          -1, NIL, false, false, false, false);
+                                          -1, NIL, false, false, false, false,
+                                          &fgc_flags);
             if (clist != NULL && clist->next == NULL &&
                 clist->oid == proid)
                 nspname = NULL;
@@ -231,6 +235,7 @@ regprocedurein(PG_FUNCTION_ARGS)
     int            nargs;
     Oid            argtypes[FUNC_MAX_ARGS];
     FuncCandidateList clist;
+    int            fgc_flags;

     /* Handle "-" or numeric OID */
     if (parseDashOrOid(pro_name_or_oid, &result, escontext))
@@ -251,8 +256,8 @@ regprocedurein(PG_FUNCTION_ARGS)
                               escontext))
         PG_RETURN_NULL();

-    clist = FuncnameGetCandidates(names, nargs, NIL, false, false,
-                                  false, true);
+    clist = FuncnameGetCandidates(names, nargs, NIL, false, false, false, true,
+                                  &fgc_flags);

     for (; clist; clist = clist->next)
     {
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3d6e6bdbfd2..0408a95941d 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13265,6 +13265,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
     bool        use_variadic;
     char       *nspname;
     FuncDetailCode p_result;
+    int            fgc_flags;
     Oid            p_funcid;
     Oid            p_rettype;
     bool        p_retset;
@@ -13323,6 +13324,7 @@ generate_function_name(Oid funcid, int nargs, List *argnames, Oid *argtypes,
         p_result = func_get_detail(list_make1(makeString(proname)),
                                    NIL, argnames, nargs, argtypes,
                                    !use_variadic, true, false,
+                                   &fgc_flags,
                                    &p_funcid, &p_rettype,
                                    &p_retset, &p_nvargs, &p_vatype,
                                    &p_true_typeids, NULL);
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 8c7ccc69a3c..a7bc24ead86 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -39,6 +39,23 @@ typedef struct _FuncCandidateList
     Oid            args[FLEXIBLE_ARRAY_MEMBER];    /* arg types */
 }           *FuncCandidateList;

+/*
+ * FuncnameGetCandidates also returns a bitmask containing these flags,
+ * which report on what it found or didn't find.  They can help callers
+ * produce better error reports after a function lookup failure.
+ */
+#define FGC_SCHEMA_GIVEN    0x0001    /* Func name includes a schema */
+#define FGC_SCHEMA_MATCH    0x0002    /* Found the explicitly-specified schema */
+#define FGC_NAME_MATCH        0x0004    /* Found a routine name match */
+#define FGC_ARGCOUNT_MATCH    0x0008    /* Found a func with right # of args */
+/* These bits relate only to calls using named or mixed arguments: */
+#define FGC_ARGNAMES_MATCH    0x0010    /* Found a func matching all argnames */
+#define FGC_ARGNAMES_PLACED    0x0020    /* Found argnames validly placed */
+#define FGC_ARGNAMES_ALL    0x0040    /* Found a func with no missing args */
+#define FGC_ARGNAMES_VALID    0x0080    /* Found a fully-valid use of argnames */
+/* These bits are actually filled by func_get_detail: */
+#define FGC_VARIADIC_FAIL    0x0100    /* Disallowed VARIADIC with named args */
+
 /*
  * Result of checkTempNamespaceStatus
  */
@@ -102,7 +119,8 @@ extern FuncCandidateList FuncnameGetCandidates(List *names,
                                                bool expand_variadic,
                                                bool expand_defaults,
                                                bool include_out_arguments,
-                                               bool missing_ok);
+                                               bool missing_ok,
+                                               int *fgc_flags);
 extern bool FunctionIsVisible(Oid funcid);

 extern Oid    OpernameGetOprid(List *names, Oid oprleft, Oid oprright);
diff --git a/src/include/parser/parse_func.h b/src/include/parser/parse_func.h
index a6f24b83d84..218bb14c5d6 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -40,6 +40,7 @@ extern FuncDetailCode func_get_detail(List *funcname,
                                       int nargs, Oid *argtypes,
                                       bool expand_variadic, bool expand_defaults,
                                       bool include_out_arguments,
+                                      int *fgc_flags,
                                       Oid *funcid, Oid *rettype,
                                       bool *retset, int *nvargs, Oid *vatype,
                                       Oid **true_typeids, List **argdefaults);
diff --git a/src/pl/plperl/expected/plperl_elog.out b/src/pl/plperl/expected/plperl_elog.out
index a6d35cb79c4..df5a3fa23aa 100644
--- a/src/pl/plperl/expected/plperl_elog.out
+++ b/src/pl/plperl/expected/plperl_elog.out
@@ -41,7 +41,7 @@ select uses_global();
 ERROR:  function uses_global() does not exist
 LINE 1: select uses_global();
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  There is no function of that name in the search_path.
 SET plperl.use_strict = false;
 create or replace function uses_global() returns text language plperl as $$

diff --git a/src/pl/plperl/expected/plperl_elog_1.out b/src/pl/plperl/expected/plperl_elog_1.out
index 85aa460ec4c..2592d987f40 100644
--- a/src/pl/plperl/expected/plperl_elog_1.out
+++ b/src/pl/plperl/expected/plperl_elog_1.out
@@ -41,7 +41,7 @@ select uses_global();
 ERROR:  function uses_global() does not exist
 LINE 1: select uses_global();
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  There is no function of that name in the search_path.
 SET plperl.use_strict = false;
 create or replace function uses_global() returns text language plperl as $$

diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
index fd9cd73be74..290cf8d25e6 100644
--- a/src/pl/plpython/expected/plpython_error.out
+++ b/src/pl/plpython/expected/plpython_error.out
@@ -63,7 +63,7 @@ SELECT exception_index_invalid_nested();
 ERROR:  spiexceptions.UndefinedFunction: function test5(unknown) does not exist
 LINE 1: SELECT test5('foo')
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  There is no function of that name in the search_path.
 QUERY:  SELECT test5('foo')
 CONTEXT:  Traceback (most recent call last):
   PL/Python function "exception_index_invalid_nested", line 1, in <module>
diff --git a/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
b/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
index cf6ccec6b9d..3f82eee8e9c 100644
--- a/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
+++ b/src/test/modules/libpq_pipeline/traces/pipeline_abort.trace
@@ -27,7 +27,7 @@ B    4    ParseComplete
 B    4    BindComplete
 B    4    NoData
 B    15    CommandComplete     "INSERT 0 1"
-B    NN    ErrorResponse     S "ERROR" V "ERROR" C "42883" M "function no_such_function(integer) does not exist" H "No
functionmatches the given name and argument types. You might need to add explicit type casts." P "8" F "SSSS" L "SSSS"
R"SSSS" \x00 
+B    NN    ErrorResponse     S "ERROR" V "ERROR" C "42883" M "function no_such_function(integer) does not exist" D
"Thereis no function of that name in the search_path." P "8" F "SSSS" L "SSSS" R "SSSS" \x00 
 B    5    ReadyForQuery     I
 B    4    ParseComplete
 B    4    BindComplete
diff --git a/src/test/modules/test_extensions/expected/test_extensions.out
b/src/test/modules/test_extensions/expected/test_extensions.out
index 72bae1bf254..be8f51d6be2 100644
--- a/src/test/modules/test_extensions/expected/test_extensions.out
+++ b/src/test/modules/test_extensions/expected/test_extensions.out
@@ -333,7 +333,7 @@ SELECT ext_cor_func();
 ERROR:  function ext_cor_func() does not exist
 LINE 1: SELECT ext_cor_func();
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  There is no function of that name in the search_path.
 SELECT * FROM ext_cor_view;
           col
 ------------------------
@@ -649,7 +649,6 @@ SELECT dep_req3b();  -- fails
 ERROR:  function public.dep_req2() does not exist
 LINE 1:  SELECT public.dep_req2() || ' req3b'
                 ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
 QUERY:   SELECT public.dep_req2() || ' req3b'
 CONTEXT:  SQL function "dep_req3b" statement 1
 DROP EXTENSION test_ext_req_schema3;
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index 45b402e25e7..cd8a77c1ade 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -2,7 +2,7 @@ CALL nonexistent();  -- error
 ERROR:  procedure nonexistent() does not exist
 LINE 1: CALL nonexistent();
              ^
-HINT:  No procedure matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  There is no procedure of that name in the search_path.
 CALL random();  -- error
 ERROR:  random() is not a procedure
 LINE 1: CALL random();
diff --git a/src/test/regress/expected/misc_functions.out b/src/test/regress/expected/misc_functions.out
index c3b2b9d8603..4f8e6892102 100644
--- a/src/test/regress/expected/misc_functions.out
+++ b/src/test/regress/expected/misc_functions.out
@@ -171,12 +171,12 @@ SELECT num_nonnulls();
 ERROR:  function num_nonnulls() does not exist
 LINE 1: SELECT num_nonnulls();
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name has the right number of arguments.
 SELECT num_nulls();
 ERROR:  function num_nulls() does not exist
 LINE 1: SELECT num_nulls();
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name has the right number of arguments.
 --
 -- canonicalize_path()
 --
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d8ce39dba3c..7fd0481710c 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -3072,7 +3072,7 @@ select shadowtest(1);
 ERROR:  function shadowtest(integer) does not exist
 LINE 1: select shadowtest(1);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  There is no function of that name in the search_path.
 reset plpgsql.extra_errors;
 reset plpgsql.extra_warnings;
 create or replace function shadowtest(f1 int)
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 94eedfe375e..b6f5fb126fa 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -990,7 +990,7 @@ select myleast(); -- fail
 ERROR:  function myleast() does not exist
 LINE 1: select myleast();
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name has the right number of arguments.
 -- test with variadic call parameter
 select myleast(variadic array[1,2,3,4,-1]);
  myleast
@@ -1154,7 +1154,7 @@ select dfunc(10, 20, 30);  -- fail
 ERROR:  function dfunc(integer, integer, integer) does not exist
 LINE 1: select dfunc(10, 20, 30);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name has the right number of arguments.
 drop function dfunc();  -- fail
 ERROR:  function dfunc() does not exist
 drop function dfunc(int);  -- fail
@@ -1310,7 +1310,7 @@ select dfunc();  -- fail
 ERROR:  function dfunc() does not exist
 LINE 1: select dfunc();
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name has the right number of arguments.
 select dfunc(10);
  dfunc
 -------
@@ -1417,7 +1417,7 @@ select * from dfunc(0);  -- fail
 ERROR:  function dfunc(integer) does not exist
 LINE 1: select * from dfunc(0);
                       ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name has the right number of arguments.
 select * from dfunc(1,2);
  a | b | c | d
 ---+---+---+---
@@ -1448,18 +1448,64 @@ select * from dfunc(x := 10, b := 20, c := 30);  -- fail, unknown param
 ERROR:  function dfunc(x => integer, b => integer, c => integer) does not exist
 LINE 1: select * from dfunc(x := 10, b := 20, c := 30);
                       ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name matches the given argument names.
 select * from dfunc(10, 10, a := 20);  -- fail, a overlaps positional parameter
 ERROR:  function dfunc(integer, integer, a => integer) does not exist
 LINE 1: select * from dfunc(10, 10, a := 20);
                       ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  Named arguments were incorrectly combined with positional arguments.
 select * from dfunc(1,c := 2,d := 3); -- fail, no value for b
 ERROR:  function dfunc(integer, c => integer, d => integer) does not exist
 LINE 1: select * from dfunc(1,c := 2,d := 3);
                       ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  Not all required arguments were supplied.
 drop function dfunc(int, int, int, int);
+create function xleast(x numeric, variadic arr numeric[])
+  returns numeric as $$
+  select least(x, min(arr[i])) from generate_subscripts(arr, 1) g(i);
+$$ language sql;
+select xleast(x => 1, variadic arr => array[2,3]);
+ xleast
+--------
+      1
+(1 row)
+
+select xleast(1, variadic arr => array[2,3]);
+ xleast
+--------
+      1
+(1 row)
+
+select xleast(foo => 1, variadic arr => array[2,3]);  -- wrong argument name
+ERROR:  function xleast(foo => integer, arr => integer[]) does not exist
+LINE 1: select xleast(foo => 1, variadic arr => array[2,3]);
+               ^
+DETAIL:  No function of that name matches the given argument names.
+select xleast(x => 1, variadic array[2,3]);  -- misuse of mixed notation
+ERROR:  positional argument cannot follow named argument
+LINE 1: select xleast(x => 1, variadic array[2,3]);
+                                       ^
+select xleast(1, variadic x => array[2,3]);  -- misuse of mixed notation
+ERROR:  function xleast(integer, x => integer[]) does not exist
+LINE 1: select xleast(1, variadic x => array[2,3]);
+               ^
+DETAIL:  Named arguments were incorrectly combined with positional arguments.
+select xleast(arr => array[1], variadic x => 3);  -- wrong arg is VARIADIC
+ERROR:  function xleast(arr => integer[], x => integer) does not exist
+LINE 1: select xleast(arr => array[1], variadic x => 3);
+               ^
+HINT:  The VARIADIC parameter must be placed last, even when using argument names.
+select xleast(arr => array[1], x => 3);  -- failed to use VARIADIC
+ERROR:  function xleast(arr => integer[], x => integer) does not exist
+LINE 1: select xleast(arr => array[1], x => 3);
+               ^
+HINT:  This call would be correct if the variadic array were labeled VARIADIC and placed last.
+select xleast(arr => 1, variadic x => array[2,3]);  -- mixed-up args
+ERROR:  function xleast(arr => integer, x => integer[]) does not exist
+LINE 1: select xleast(arr => 1, variadic x => array[2,3]);
+               ^
+HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+drop function xleast(x numeric, variadic arr numeric[]);
 -- test with different parameter types
 create function dfunc(a varchar, b numeric, c date = current_date)
   returns table (a varchar, b numeric, c date) as $$
diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out
index 370361543b3..c20e70d26a2 100644
--- a/src/test/regress/expected/temp.out
+++ b/src/test/regress/expected/temp.out
@@ -229,7 +229,7 @@ select nonempty('');
 ERROR:  function nonempty(unknown) does not exist
 LINE 1: select nonempty('');
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  There is no function of that name in the search_path.
 select pg_temp.nonempty('');
 ERROR:  value for domain nonempty violates check constraint "nonempty_check"
 -- other syntax matches rules for tables
diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql
index fa57db6559c..023d67751ea 100644
--- a/src/test/regress/sql/polymorphism.sql
+++ b/src/test/regress/sql/polymorphism.sql
@@ -873,6 +873,23 @@ select * from dfunc(1,c := 2,d := 3); -- fail, no value for b

 drop function dfunc(int, int, int, int);

+create function xleast(x numeric, variadic arr numeric[])
+  returns numeric as $$
+  select least(x, min(arr[i])) from generate_subscripts(arr, 1) g(i);
+$$ language sql;
+
+select xleast(x => 1, variadic arr => array[2,3]);
+select xleast(1, variadic arr => array[2,3]);
+
+select xleast(foo => 1, variadic arr => array[2,3]);  -- wrong argument name
+select xleast(x => 1, variadic array[2,3]);  -- misuse of mixed notation
+select xleast(1, variadic x => array[2,3]);  -- misuse of mixed notation
+select xleast(arr => array[1], variadic x => 3);  -- wrong arg is VARIADIC
+select xleast(arr => array[1], x => 3);  -- failed to use VARIADIC
+select xleast(arr => 1, variadic x => array[2,3]);  -- mixed-up args
+
+drop function xleast(x numeric, variadic arr numeric[]);
+
 -- test with different parameter types
 create function dfunc(a varchar, b numeric, c date = current_date)
   returns table (a varchar, b numeric, c date) as $$
--
2.43.7

From 86bb7e9aa135cb4dbcf8ffe4cfbe473c1a003c3a Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 21 Aug 2025 17:22:06 -0400
Subject: [PATCH v3 2/4] Change the wording of our traditional
 function-not-found hint.

With the previous patch's addition of specific messages for a lot
of other error cases, it's possible to be pretty sure that the
problem is mismatched argument types rather than something else,
so we can be more specific about that.

I think this wording is clearer and follows our message style
guidelines better (by separating factual detail from hint).
However, the change causes a lot of churn in our regression test
results, and probably will do the same to extensions and other
downstream consumers.  Is it worth it?

Reported-by: Dominique Devienne <ddevienne@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1756041.1754616558@sss.pgh.pa.us
---
 doc/src/sgml/typeconv.sgml                    |  4 +-
 src/backend/parser/parse_func.c               | 13 ++--
 src/pl/plpgsql/src/expected/plpgsql_call.out  |  3 +-
 .../plpgsql/src/expected/plpgsql_record.out   |  3 +-
 src/test/regress/expected/arrays.out          |  3 +-
 src/test/regress/expected/create_cast.out     |  6 +-
 .../regress/expected/create_procedure.out     |  3 +-
 src/test/regress/expected/multirangetypes.out | 15 +++--
 src/test/regress/expected/plpgsql.out         |  6 +-
 src/test/regress/expected/polymorphism.out    | 60 ++++++++++++-------
 src/test/regress/expected/rangetypes.out      |  9 ++-
 src/test/regress/expected/rowtypes.out        |  6 +-
 src/test/regress/expected/text.out            |  3 +-
 13 files changed, 86 insertions(+), 48 deletions(-)

diff --git a/doc/src/sgml/typeconv.sgml b/doc/src/sgml/typeconv.sgml
index 28748742486..44aaf284da4 100644
--- a/doc/src/sgml/typeconv.sgml
+++ b/doc/src/sgml/typeconv.sgml
@@ -901,8 +901,8 @@ the parser will try to convert that to <type>text</type>:
 <screen>
 SELECT substr(1234, 3);
 ERROR:  function substr(integer, integer) does not exist
-HINT:  No function matches the given name and argument types. You might need
-to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 </screen>

 This does not work because <type>integer</type> does not have an implicit cast
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index febccb74843..f666a64999c 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -617,8 +617,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
                      errmsg("function %s does not exist",
                             func_signature_string(funcname, nargs, argnames,
                                                   actual_arg_types)),
-                     errhint("No aggregate function matches the given name and argument types. "
-                             "Perhaps you misplaced ORDER BY; ORDER BY must appear "
+                     errdetail("No aggregate function matches the given name and argument types."),
+                     errhint("Perhaps you misplaced ORDER BY; ORDER BY must appear "
                              "after all regular arguments of the aggregate."),
                      parser_errposition(pstate, location)));
         }
@@ -988,14 +988,13 @@ func_lookup_failure_details(int fgc_flags, List *argnames, bool proc_call)
         return errhint("The VARIADIC parameter must be placed last, even when using argument names.");

     /*
-     * Otherwise, give our traditional hint about argument types and casting.
+     * Otherwise, the problem must be incorrect argument types.
      */
     if (proc_call)
-        return errhint("No procedure matches the given name and argument types. "
-                       "You might need to add explicit type casts.");
+        (void) errdetail("No procedure of that name accepts the given argument types.");
     else
-        return errhint("No function matches the given name and argument types. "
-                       "You might need to add explicit type casts.");
+        (void) errdetail("No function of that name accepts the given argument types.");
+    return errhint("You might need to add explicit type casts.");
 }


diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out
index ea7107dca0d..3d0b117f236 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_call.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_call.out
@@ -440,7 +440,8 @@ $$;
 ERROR:  procedure test_proc12(integer, integer, text[]) does not exist
 LINE 1: CALL test_proc12(_a, _b, _c)
              ^
-HINT:  No procedure matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No procedure of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 QUERY:  CALL test_proc12(_a, _b, _c)
 CONTEXT:  PL/pgSQL function inline_code_block line 5 at CALL
 -- transition variable assignment
diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out
index e5de7143606..511f9e03c85 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_record.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_record.out
@@ -466,7 +466,8 @@ select getf1(1);
 ERROR:  function getf1(integer) does not exist
 LINE 1: select getf1(1);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select getf1(row(1,2));
  getf1
 -------
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index b815473f414..69ea2cf5ad8 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -2747,7 +2747,8 @@ SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]);
 ERROR:  function width_bucket(text, integer[]) does not exist
 LINE 1: SELECT width_bucket('5'::text, ARRAY[3, 4]::integer[]);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 SELECT width_bucket(5, ARRAY[3, 4, NULL]);
 ERROR:  thresholds array must not contain NULLs
 SELECT width_bucket(5, ARRAY[ARRAY[1, 2], ARRAY[3, 4]]);
diff --git a/src/test/regress/expected/create_cast.out b/src/test/regress/expected/create_cast.out
index fd4871d94db..0e69644bca2 100644
--- a/src/test/regress/expected/create_cast.out
+++ b/src/test/regress/expected/create_cast.out
@@ -28,14 +28,16 @@ SELECT casttestfunc('foo'::text); -- fails, as there's no cast
 ERROR:  function casttestfunc(text) does not exist
 LINE 1: SELECT casttestfunc('foo'::text);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 -- Try binary coercion cast
 CREATE CAST (text AS casttesttype) WITHOUT FUNCTION;
 SELECT casttestfunc('foo'::text); -- doesn't work, as the cast is explicit
 ERROR:  function casttestfunc(text) does not exist
 LINE 1: SELECT casttestfunc('foo'::text);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 SELECT casttestfunc('foo'::text::casttesttype); -- should work
  casttestfunc
 --------------
diff --git a/src/test/regress/expected/create_procedure.out b/src/test/regress/expected/create_procedure.out
index cd8a77c1ade..3090a57790d 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -299,7 +299,8 @@ CALL ptest9(1./0.);  -- error
 ERROR:  procedure ptest9(numeric) does not exist
 LINE 1: CALL ptest9(1./0.);
              ^
-HINT:  No procedure matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No procedure of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 -- check named-parameter matching
 CREATE PROCEDURE ptest10(OUT a int, IN b int, IN c int)
 LANGUAGE SQL AS $$ SELECT b - c $$;
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index c6363ebeb24..63de4d09b15 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -3096,7 +3096,8 @@ select multirange_of_text(textrange2('a','Z'));  -- should fail
 ERROR:  function multirange_of_text(textrange2) does not exist
 LINE 1: select multirange_of_text(textrange2('a','Z'));
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select multirange_of_text(textrange1('a','Z')) @> 'b'::text;
 ERROR:  range lower bound must be less than or equal to range upper bound
 select unnest(multirange_of_text(textrange1('a','b'), textrange1('d','e')));
@@ -3160,7 +3161,8 @@ select anyarray_anymultirange_func(ARRAY[1,2], nummultirange(numrange(10,20)));
 ERROR:  function anyarray_anymultirange_func(integer[], nummultirange) does not exist
 LINE 1: select anyarray_anymultirange_func(ARRAY[1,2], nummultirange...
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anyarray_anymultirange_func(anyarray, anymultirange);
 -- should fail
 create function bogus_func(anyelement)
@@ -3199,7 +3201,8 @@ select multirangetypes_sql(nummultirange(numrange(1,10)), ARRAY[2,20]);  -- matc
 ERROR:  function multirangetypes_sql(nummultirange, integer[]) does not exist
 LINE 1: select multirangetypes_sql(nummultirange(numrange(1,10)), AR...
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 create function anycompatiblearray_anycompatiblemultirange_func(a anycompatiblearray, mr anycompatiblemultirange)
   returns anycompatible as 'select $1[1] + lower($2);' language sql;
 select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1,2], multirange(int4range(10,20)));
@@ -3219,7 +3222,8 @@ select anycompatiblearray_anycompatiblemultirange_func(ARRAY[1.1,2], multirange(
 ERROR:  function anycompatiblearray_anycompatiblemultirange_func(numeric[], int4multirange) does not exist
 LINE 1: select anycompatiblearray_anycompatiblemultirange_func(ARRAY...
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anycompatiblearray_anycompatiblemultirange_func(anycompatiblearray, anycompatiblemultirange);
 create function anycompatiblerange_anycompatiblemultirange_func(r anycompatiblerange, mr anycompatiblemultirange)
   returns anycompatible as 'select lower($1) + lower($2);' language sql;
@@ -3234,7 +3238,8 @@ select anycompatiblerange_anycompatiblemultirange_func(numrange(1,2), multirange
 ERROR:  function anycompatiblerange_anycompatiblemultirange_func(numrange, int4multirange) does not exist
 LINE 1: select anycompatiblerange_anycompatiblemultirange_func(numra...
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anycompatiblerange_anycompatiblemultirange_func(anycompatiblerange, anycompatiblemultirange);
 -- should fail
 create function bogus_func(anycompatible)
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 7fd0481710c..b2aaaf10b9c 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1848,7 +1848,8 @@ select f1(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 ERROR:  function f1(int4range, integer, numeric) does not exist
 LINE 1: select f1(int4range(42, 49), 11, 4.5) as fail;
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function f1(x anycompatiblerange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function f1(x anycompatible) returns anycompatiblerange as $$
@@ -1902,7 +1903,8 @@ select x, pg_typeof(x), y, pg_typeof(y)
 ERROR:  function f1(integer, numeric[], integer, numeric) does not exist
 LINE 2:   from f1(11, array[1, 2.2], 42, 34.5);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function f1(a anyelement, b anyarray,
                  c anycompatible, d anycompatible);
 --
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index b6f5fb126fa..05b2300a46e 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -95,7 +95,8 @@ select polyf(int4range(42, 49), 11, 4.5) as fail;  -- range type doesn't fit
 ERROR:  function polyf(int4range, integer, numeric) does not exist
 LINE 1: select polyf(int4range(42, 49), 11, 4.5) as fail;
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function polyf(x anycompatiblerange, y anycompatible, z anycompatible);
 create function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible) returns anycompatiblearray as $$
   select array[lower(x), upper(x), y, z]
@@ -110,7 +111,8 @@ select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail;  -- range type doe
 ERROR:  function polyf(int4multirange, integer, numeric) does not exist
 LINE 1: select polyf(multirange(int4range(42, 49)), 11, 4.5) as fail...
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function polyf(x anycompatiblemultirange, y anycompatible, z anycompatible);
 -- fail, can't infer type:
 create function polyf(x anycompatible) returns anycompatiblerange as $$
@@ -176,7 +178,8 @@ select x, pg_typeof(x), y, pg_typeof(y)
 ERROR:  function polyf(integer, numeric[], integer, numeric) does not exist
 LINE 2:   from polyf(11, array[1, 2.2], 42, 34.5);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function polyf(a anyelement, b anyarray,
                     c anycompatible, d anycompatible);
 create function polyf(anyrange) returns anymultirange
@@ -1060,17 +1063,20 @@ select formarray(1.1, array[1.2,55.5]); -- fail without variadic
 ERROR:  function formarray(numeric, numeric[]) does not exist
 LINE 1: select formarray(1.1, array[1.2,55.5]);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select formarray(1, 'x'::text); -- fail, type mismatch
 ERROR:  function formarray(integer, text) does not exist
 LINE 1: select formarray(1, 'x'::text);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select formarray(1, variadic array['x'::text]); -- fail, type mismatch
 ERROR:  function formarray(integer, text[]) does not exist
 LINE 1: select formarray(1, variadic array['x'::text]);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function formarray(anyelement, variadic anyarray);
 -- test pg_typeof() function
 select pg_typeof(null);           -- unknown
@@ -1504,7 +1510,8 @@ select xleast(arr => 1, variadic x => array[2,3]);  -- mixed-up args
 ERROR:  function xleast(arr => integer, x => integer[]) does not exist
 LINE 1: select xleast(arr => 1, variadic x => array[2,3]);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function xleast(x numeric, variadic arr numeric[]);
 -- test with different parameter types
 create function dfunc(a varchar, b numeric, c date = current_date)
@@ -1545,7 +1552,8 @@ select * from dfunc('Hello World', c := 20, b := '2009-07-25'::date);  -- fail
 ERROR:  function dfunc(unknown, c => integer, b => date) does not exist
 LINE 1: select * from dfunc('Hello World', c := 20, b := '2009-07-25...
                       ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function dfunc(varchar, numeric, date);
 -- test out parameters with named params
 create function dfunc(a varchar = 'def a', out _a varchar, c numeric = NULL, out _c numeric)
@@ -1890,7 +1898,8 @@ select x, pg_typeof(x) from anyctest(11, point(1,2)) x;  -- fail
 ERROR:  function anyctest(integer, point) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11, point(1,2)) x;
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select x, pg_typeof(x) from anyctest('11', '12.3') x;  -- defaults to text
   x   | pg_typeof
 ------+-----------
@@ -1918,7 +1927,8 @@ select x, pg_typeof(x) from anyctest(11, array[1,2]) x;  -- fail
 ERROR:  function anyctest(integer, integer[]) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11, array[1,2]) x;
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anyctest(anycompatible, anycompatible);
 create function anyctest(anycompatible, anycompatiblearray)
 returns anycompatiblearray as $$
@@ -1952,12 +1962,14 @@ select x, pg_typeof(x) from anyctest(11, array[point(1,2)]) x;  -- fail
 ERROR:  function anyctest(integer, point[]) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11, array[point(1,2)]) ...
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
 ERROR:  function anyctest(integer, integer) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anyctest(anycompatible, anycompatiblearray);
 create function anyctest(anycompatible, anycompatiblerange)
 returns anycompatiblerange as $$
@@ -1979,12 +1991,14 @@ select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
 ERROR:  function anyctest(integer, integer) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select x, pg_typeof(x) from anyctest(11.2, int4range(4,7)) x;  -- fail
 ERROR:  function anyctest(numeric, int4range) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11.2, int4range(4,7)) x...
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select x, pg_typeof(x) from anyctest(11.2, '[4,7)') x;  -- fail
 ERROR:  could not determine polymorphic type anycompatiblerange because input has type unknown
 drop function anyctest(anycompatible, anycompatiblerange);
@@ -2002,7 +2016,8 @@ select x, pg_typeof(x) from anyctest(int4range(11,12), numrange(4,7)) x; -- fail
 ERROR:  function anyctest(int4range, numrange) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(int4range(11,12), numra...
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anyctest(anycompatiblerange, anycompatiblerange);
 -- fail, can't infer result type:
 create function anyctest(anycompatible)
@@ -2031,12 +2046,14 @@ select x, pg_typeof(x) from anyctest(11, 12) x;  -- fail
 ERROR:  function anyctest(integer, integer) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11, 12) x;
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select x, pg_typeof(x) from anyctest(11.2, multirange(int4range(4,7))) x;  -- fail
 ERROR:  function anyctest(numeric, int4multirange) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11.2, multirange(int4ra...
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select x, pg_typeof(x) from anyctest(11.2, '{[4,7)}') x;  -- fail
 ERROR:  could not determine polymorphic type anycompatiblemultirange because input has type unknown
 drop function anyctest(anycompatible, anycompatiblemultirange);
@@ -2054,7 +2071,8 @@ select x, pg_typeof(x) from anyctest(multirange(int4range(11,12)), multirange(nu
 ERROR:  function anyctest(int4multirange, nummultirange) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(multirange(int4range(11...
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anyctest(anycompatiblemultirange, anycompatiblemultirange);
 -- fail, can't infer result type:
 create function anyctest(anycompatible)
@@ -2083,7 +2101,8 @@ select x, pg_typeof(x) from anyctest(array[11], array[1,2]) x;  -- fail
 ERROR:  function anyctest(integer[], integer[]) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(array[11], array[1,2]) ...
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anyctest(anycompatiblenonarray, anycompatiblenonarray);
 create function anyctest(a anyelement, b anyarray,
                          c anycompatible, d anycompatible)
@@ -2112,7 +2131,8 @@ select x, pg_typeof(x) from anyctest(11, array[1, 2.2], 42, 34.5) x;  -- fail
 ERROR:  function anyctest(integer, numeric[], integer, numeric) does not exist
 LINE 1: select x, pg_typeof(x) from anyctest(11, array[1, 2.2], 42, ...
                                     ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anyctest(a anyelement, b anyarray,
                        c anycompatible, d anycompatible);
 create function anyctest(variadic anycompatiblearray)
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index a7cc220bf0d..cdd95799cd5 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -1630,7 +1630,8 @@ select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20));
 ERROR:  function anyarray_anyrange_func(integer[], numrange) does not exist
 LINE 1: select anyarray_anyrange_func(ARRAY[1,2], numrange(10,20));
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anyarray_anyrange_func(anyarray, anyrange);
 -- should fail
 create function bogus_func(anyelement)
@@ -1669,7 +1670,8 @@ select rangetypes_sql(numrange(1,10), ARRAY[2,20]);  -- match failure
 ERROR:  function rangetypes_sql(numrange, integer[]) does not exist
 LINE 1: select rangetypes_sql(numrange(1,10), ARRAY[2,20]);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 create function anycompatiblearray_anycompatiblerange_func(a anycompatiblearray, r anycompatiblerange)
   returns anycompatible as 'select $1[1] + lower($2);' language sql;
 select anycompatiblearray_anycompatiblerange_func(ARRAY[1,2], int4range(10,20));
@@ -1689,7 +1691,8 @@ select anycompatiblearray_anycompatiblerange_func(ARRAY[1.1,2], int4range(10,20)
 ERROR:  function anycompatiblearray_anycompatiblerange_func(numeric[], int4range) does not exist
 LINE 1: select anycompatiblearray_anycompatiblerange_func(ARRAY[1.1,...
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 drop function anycompatiblearray_anycompatiblerange_func(anycompatiblearray, anycompatiblerange);
 -- should fail
 create function bogus_func(anycompatible)
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index 9168979a620..d84122881af 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -965,7 +965,8 @@ select text(fullname) from fullname;  -- error
 ERROR:  function text(fullname) does not exist
 LINE 1: select text(fullname) from fullname;
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select fullname.text from fullname;  -- error
 ERROR:  column fullname.text does not exist
 LINE 1: select fullname.text from fullname;
@@ -987,7 +988,8 @@ select text(row('Jim', 'Beam'));  -- error
 ERROR:  function text(record) does not exist
 LINE 1: select text(row('Jim', 'Beam'));
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select (row('Jim', 'Beam')).text;  -- error
 ERROR:  could not identify column "text" in record data type
 LINE 1: select (row('Jim', 'Beam')).text;
diff --git a/src/test/regress/expected/text.out b/src/test/regress/expected/text.out
index 4c65b238e76..ced71e903c6 100644
--- a/src/test/regress/expected/text.out
+++ b/src/test/regress/expected/text.out
@@ -27,7 +27,8 @@ select length(42);
 ERROR:  function length(integer) does not exist
 LINE 1: select length(42);
                ^
-HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No function of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 -- But as a special exception for usability's sake, we still allow implicit
 -- casting to text in concatenations, so long as the other input is text or
 -- an unknown literal.  So these work:
--
2.43.7

From 839deadf749d1e483789b882e3f5cc404a7bb6c9 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 21 Aug 2025 18:20:42 -0400
Subject: [PATCH v3 3/4] Improve the messages for operator-not-found, too.

Extend the same return-a-bitmask approach to OpernameGetCandidates.
The issues around argument names don't apply to operator syntax,
but it still seems worth distinguishing between "there is no
operator of that name" and "we couldn't match the argument types".

Also follow the previous patch's improvement of style by
separating errdetail from errhint.

Reported-by: Dominique Devienne <ddevienne@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1756041.1754616558@sss.pgh.pa.us
---
 .../postgres_fdw/expected/postgres_fdw.out    |  6 +-
 src/backend/catalog/namespace.c               | 22 ++++++-
 src/backend/parser/parse_oper.c               | 63 +++++++++++++++----
 src/backend/utils/adt/regproc.c               |  6 +-
 src/include/catalog/namespace.h               |  3 +-
 src/test/regress/expected/alter_table.out     |  3 +-
 .../regress/expected/create_function_sql.out  |  3 +-
 src/test/regress/expected/create_view.out     |  3 +-
 src/test/regress/expected/domain.out          |  3 +-
 src/test/regress/expected/expressions.out     |  3 +-
 src/test/regress/expected/geometry.out        |  3 +-
 src/test/regress/expected/horology.out        |  3 +-
 src/test/regress/expected/plpgsql.out         |  3 +-
 src/test/regress/expected/polymorphism.out    |  3 +-
 src/test/regress/expected/subselect.out       |  3 +-
 src/test/regress/expected/text.out            |  3 +-
 src/test/regress/expected/timetz.out          |  3 +-
 src/test/regress/expected/with.out            |  3 +-
 src/test/regress/expected/xid.out             | 12 ++--
 19 files changed, 116 insertions(+), 35 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index d3323b04676..35c619226a6 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -4621,11 +4621,13 @@ SELECT * FROM ft1 WHERE 'foo' = c8 LIMIT 1;
 -- with that remote type
 SELECT * FROM ft1 WHERE c8 LIKE 'foo' LIMIT 1; -- ERROR
 ERROR:  operator does not exist: public.user_enum ~~ unknown
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 CONTEXT:  remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT
1::bigint
 SELECT * FROM ft1 WHERE c8::text LIKE 'foo' LIMIT 1; -- ERROR; cast not pushed down
 ERROR:  operator does not exist: public.user_enum ~~ unknown
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 CONTEXT:  remote SQL command: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((c8 ~~ 'foo')) LIMIT
1::bigint
 ALTER FOREIGN TABLE ft1 ALTER COLUMN c8 TYPE user_enum;
 -- ===================================================================
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 524f325be03..0c18a8466d2 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -1929,9 +1929,20 @@ OpernameGetOprid(List *names, Oid oprleft, Oid oprright)
  *
  * The returned items always have two args[] entries --- the first will be
  * InvalidOid for a prefix oprkind.  nargs is always 2, too.
+ *
+ * We return an empty list (NULL) if no suitable matches can be found.  If the
+ * operator name was schema-qualified with a schema that does not exist, then
+ * we return an empty list if missing_schema_ok is true and otherwise throw an
+ * error.  (missing_schema_ok does not affect the behavior otherwise.)
+ *
+ * The output argument *fgc_flags is filled with a bitmask indicating how
+ * far we were able to match the supplied information.  This is not of much
+ * interest if any candidates were found, but if not, it can help callers
+ * produce an on-point error message.
  */
 FuncCandidateList
-OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
+OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok,
+                      int *fgc_flags)
 {
     FuncCandidateList resultList = NULL;
     char       *resultSpace = NULL;
@@ -1942,15 +1953,20 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
     CatCList   *catlist;
     int            i;

+    /* initialize output fgc_flags to empty */
+    *fgc_flags = 0;
+
     /* deconstruct the name list */
     DeconstructQualifiedName(names, &schemaname, &opername);

     if (schemaname)
     {
         /* use exact schema given */
+        *fgc_flags |= FGC_SCHEMA_GIVEN; /* report that a schema is given */
         namespaceId = LookupExplicitNamespace(schemaname, missing_schema_ok);
-        if (missing_schema_ok && !OidIsValid(namespaceId))
+        if (!OidIsValid(namespaceId))
             return NULL;
+        *fgc_flags |= FGC_SCHEMA_MATCH; /* report that the schema is valid */
     }
     else
     {
@@ -2061,6 +2077,8 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
             }
         }

+        *fgc_flags |= FGC_NAME_MATCH;    /* we found a matching operator name */
+
         /*
          * Okay to add it to result list
          */
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index 0c4337563cf..f9e18270489 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -72,7 +72,8 @@ static FuncDetailCode oper_select_candidate(int nargs,
                                             Oid *operOid);
 static void op_error(ParseState *pstate, List *op,
                      Oid arg1, Oid arg2,
-                     FuncDetailCode fdresult, int location);
+                     FuncDetailCode fdresult, int fgc_flags, int location);
+static int    oper_lookup_failure_details(int fgc_flags, bool is_unary_op);
 static bool make_oper_cache_key(ParseState *pstate, OprCacheKey *key,
                                 List *opname, Oid ltypeId, Oid rtypeId,
                                 int location);
@@ -373,6 +374,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
     Oid            operOid;
     OprCacheKey key;
     bool        key_ok;
+    int            fgc_flags = 0;
     FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
     HeapTuple    tup = NULL;

@@ -404,7 +406,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
         FuncCandidateList clist;

         /* Get binary operators of given name */
-        clist = OpernameGetCandidates(opname, 'b', false);
+        clist = OpernameGetCandidates(opname, 'b', false, &fgc_flags);

         /* No operators found? Then fail... */
         if (clist != NULL)
@@ -434,7 +436,8 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
             make_oper_cache_entry(&key, operOid);
     }
     else if (!noError)
-        op_error(pstate, opname, ltypeId, rtypeId, fdresult, location);
+        op_error(pstate, opname, ltypeId, rtypeId,
+                 fdresult, fgc_flags, location);

     return (Operator) tup;
 }
@@ -520,6 +523,7 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
     Oid            operOid;
     OprCacheKey key;
     bool        key_ok;
+    int            fgc_flags = 0;
     FuncDetailCode fdresult = FUNCDETAIL_NOTFOUND;
     HeapTuple    tup = NULL;

@@ -551,7 +555,7 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
         FuncCandidateList clist;

         /* Get prefix operators of given name */
-        clist = OpernameGetCandidates(op, 'l', false);
+        clist = OpernameGetCandidates(op, 'l', false, &fgc_flags);

         /* No operators found? Then fail... */
         if (clist != NULL)
@@ -585,7 +589,8 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
             make_oper_cache_entry(&key, operOid);
     }
     else if (!noError)
-        op_error(pstate, op, InvalidOid, arg, fdresult, location);
+        op_error(pstate, op, InvalidOid, arg,
+                 fdresult, fgc_flags, location);

     return (Operator) tup;
 }
@@ -621,7 +626,7 @@ op_signature_string(List *op, Oid arg1, Oid arg2)
 static void
 op_error(ParseState *pstate, List *op,
          Oid arg1, Oid arg2,
-         FuncDetailCode fdresult, int location)
+         FuncDetailCode fdresult, int fgc_flags, int location)
 {
     if (fdresult == FUNCDETAIL_MULTIPLE)
         ereport(ERROR,
@@ -636,14 +641,50 @@ op_error(ParseState *pstate, List *op,
                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
                  errmsg("operator does not exist: %s",
                         op_signature_string(op, arg1, arg2)),
-                 (!arg1 || !arg2) ?
-                 errhint("No operator matches the given name and argument type. "
-                         "You might need to add an explicit type cast.") :
-                 errhint("No operator matches the given name and argument types. "
-                         "You might need to add explicit type casts."),
+                 oper_lookup_failure_details(fgc_flags, (!arg1 || !arg2)),
                  parser_errposition(pstate, location)));
 }

+/*
+ * Interpret the fgc_flags and issue a suitable detail or hint message.
+ */
+static int
+oper_lookup_failure_details(int fgc_flags, bool is_unary_op)
+{
+    /*
+     * If not FGC_NAME_MATCH, we shouldn't raise the question of whether the
+     * arguments are wrong.  It does seem worth calling the search_path to the
+     * user's mind if the operator name was not schema-qualified; but if it
+     * was, there's really nothing to add to the basic "operator does not
+     * exist" message.
+     *
+     * Note: we passed missing_ok = false to OpernameGetCandidates, so there's
+     * no need to consider FGC_SCHEMA_MATCH here: we'd have already thrown an
+     * error if an explicitly-given schema doesn't exist.
+     */
+    if (!(fgc_flags & FGC_NAME_MATCH))
+    {
+        if (fgc_flags & FGC_SCHEMA_GIVEN)
+            return 0;            /* schema-qualified name */
+        else
+            return errdetail("There is no operator of that name in the search_path.");
+    }
+
+    /*
+     * Otherwise, the problem must be incorrect argument type(s).
+     */
+    if (is_unary_op)
+    {
+        (void) errdetail("No operator of that name accepts the given argument type.");
+        return errhint("You might need to add an explicit type cast.");
+    }
+    else
+    {
+        (void) errdetail("No operator of that name accepts the given argument types.");
+        return errhint("You might need to add explicit type casts.");
+    }
+}
+
 /*
  * make_op()
  *        Operator expression construction.
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 0c5dec025d7..2709024a4ad 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -488,6 +488,7 @@ regoperin(PG_FUNCTION_ARGS)
     Oid            result;
     List       *names;
     FuncCandidateList clist;
+    int            fgc_flags;

     /* Handle "0" or numeric OID */
     if (parseNumericOid(opr_name_or_oid, &result, escontext))
@@ -507,7 +508,7 @@ regoperin(PG_FUNCTION_ARGS)
     if (names == NIL)
         PG_RETURN_NULL();

-    clist = OpernameGetCandidates(names, '\0', true);
+    clist = OpernameGetCandidates(names, '\0', true, &fgc_flags);

     if (clist == NULL)
         ereturn(escontext, (Datum) 0,
@@ -577,13 +578,14 @@ regoperout(PG_FUNCTION_ARGS)
         else
         {
             FuncCandidateList clist;
+            int            fgc_flags;

             /*
              * Would this oper be found (uniquely!) by regoperin? If not,
              * qualify it.
              */
             clist = OpernameGetCandidates(list_make1(makeString(oprname)),
-                                          '\0', false);
+                                          '\0', false, &fgc_flags);
             if (clist != NULL && clist->next == NULL &&
                 clist->oid == oprid)
                 result = pstrdup(oprname);
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index a7bc24ead86..a3a194620f4 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -125,7 +125,8 @@ extern bool FunctionIsVisible(Oid funcid);

 extern Oid    OpernameGetOprid(List *names, Oid oprleft, Oid oprright);
 extern FuncCandidateList OpernameGetCandidates(List *names, char oprkind,
-                                               bool missing_schema_ok);
+                                               bool missing_schema_ok,
+                                               int *fgc_flags);
 extern bool OperatorIsVisible(Oid oprid);

 extern Oid    OpclassnameGetOpcid(Oid amid, const char *opcname);
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 08984dd98f1..18dd093ca79 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -2041,7 +2041,8 @@ alter table anothertab alter column atcol1 drop default;
 alter table anothertab alter column atcol1 type boolean
         using case when atcol1 % 2 = 0 then true else false end; -- fails
 ERROR:  operator does not exist: boolean <= integer
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 alter table anothertab drop constraint anothertab_chk;
 alter table anothertab drop constraint anothertab_chk; -- fails
 ERROR:  constraint "anothertab_chk" of relation "anothertab" does not exist
diff --git a/src/test/regress/expected/create_function_sql.out b/src/test/regress/expected/create_function_sql.out
index da112608d66..73c6730d459 100644
--- a/src/test/regress/expected/create_function_sql.out
+++ b/src/test/regress/expected/create_function_sql.out
@@ -304,7 +304,8 @@ CREATE FUNCTION functest_S_xx(x date) RETURNS boolean
 ERROR:  operator does not exist: date > integer
 LINE 3:     RETURN x > 1;
                      ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 -- tricky parsing
 CREATE FUNCTION functest_S_15(x int) RETURNS boolean
 LANGUAGE SQL
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index f551624afb3..49dd13c345c 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -1924,7 +1924,8 @@ select 'foo'::text = any((select array['abc','def','foo']::text[]));  -- fail
 ERROR:  operator does not exist: text = text[]
 LINE 1: select 'foo'::text = any((select array['abc','def','foo']::t...
                            ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select 'foo'::text = any((select array['abc','def','foo']::text[])::text[]);
  ?column?
 ----------
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index b5ea707df31..62a48a523a2 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -415,7 +415,8 @@ select row(0,1)::dcomptype;  -- fail
 ERROR:  value for domain dcomptype violates check constraint "c1"
 alter type comptype alter attribute r type varchar;  -- fail
 ERROR:  operator does not exist: character varying > double precision
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 alter type comptype alter attribute r type bigint;
 alter type comptype drop attribute r;  -- fail
 ERROR:  cannot drop column r of composite type comptype because other objects depend on it
diff --git a/src/test/regress/expected/expressions.out b/src/test/regress/expected/expressions.out
index 21c54fc1989..9a3c97b15a3 100644
--- a/src/test/regress/expected/expressions.out
+++ b/src/test/regress/expected/expressions.out
@@ -218,7 +218,8 @@ select '(0,0)'::point in ('(0,0,0,0)'::box, point(0,0));
 ERROR:  operator does not exist: point = box
 LINE 1: select '(0,0)'::point in ('(0,0,0,0)'::box, point(0,0));
                               ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 --
 -- Tests for ScalarArrayOpExpr with a hashfn
 --
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 8be694f46be..1d168b21cbc 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -1777,7 +1777,8 @@ SELECT p.f1, l.s, l.s # p.f1 AS intersection
 ERROR:  operator does not exist: lseg # point
 LINE 1: SELECT p.f1, l.s, l.s # p.f1 AS intersection
                               ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 -- Length
 SELECT s, @-@ s FROM LSEG_TBL;
                s               |   ?column?
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 5ae93d8e8a5..32cf62b6741 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -605,7 +605,8 @@ SELECT date '1991-02-03' - time with time zone '04:05:06 UTC' AS "Subtract Time
 ERROR:  operator does not exist: date - time with time zone
 LINE 1: SELECT date '1991-02-03' - time with time zone '04:05:06 UTC...
                                  ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 --
 -- timestamp, interval arithmetic
 --
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index b2aaaf10b9c..b7b009295ff 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -1763,7 +1763,8 @@ select f1(point(3,4));  -- fail for lack of + operator
 ERROR:  operator does not exist: point + integer
 LINE 1: x + 1
           ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 QUERY:  x + 1
 CONTEXT:  PL/pgSQL function f1(anyelement) line 3 at RETURN
 drop function f1(x anyelement);
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 05b2300a46e..6ac00488103 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -15,7 +15,8 @@ select polyf(point(3,4));  -- fail for lack of + operator
 ERROR:  operator does not exist: point + integer
 LINE 2:   select x + 1
                    ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 QUERY:
   select x + 1

diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index 0563d0cd5a1..637b55f1414 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -1156,7 +1156,8 @@ select * from int8_tbl where q1 in (select c1 from inner_text);
 ERROR:  operator does not exist: bigint = text
 LINE 1: select * from int8_tbl where q1 in (select c1 from inner_tex...
                                         ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 begin;
 -- make an operator to allow it to succeed
 create function bogus_int8_text_eq(int8, text) returns boolean
diff --git a/src/test/regress/expected/text.out b/src/test/regress/expected/text.out
index ced71e903c6..3f9982388ba 100644
--- a/src/test/regress/expected/text.out
+++ b/src/test/regress/expected/text.out
@@ -49,7 +49,8 @@ select 3 || 4.0;
 ERROR:  operator does not exist: integer || numeric
 LINE 1: select 3 || 4.0;
                  ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 /*
  * various string functions
  */
diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out
index cbab6cfe5d7..324b1a740e8 100644
--- a/src/test/regress/expected/timetz.out
+++ b/src/test/regress/expected/timetz.out
@@ -174,7 +174,8 @@ SELECT f1 + time with time zone '00:01' AS "Illegal" FROM TIMETZ_TBL;
 ERROR:  operator does not exist: time with time zone + time with time zone
 LINE 1: SELECT f1 + time with time zone '00:01' AS "Illegal" FROM TI...
                   ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 --
 -- test EXTRACT
 --
diff --git a/src/test/regress/expected/with.out b/src/test/regress/expected/with.out
index 26c88505140..f015e997276 100644
--- a/src/test/regress/expected/with.out
+++ b/src/test/regress/expected/with.out
@@ -175,7 +175,8 @@ SELECT n, pg_typeof(n) FROM t;
 ERROR:  operator does not exist: text + integer
 LINE 4:     SELECT n+1 FROM t WHERE n < 10
                     ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 -- Deeply nested WITH caused a list-munging problem in v13
 -- Detection of cross-references and self-references
 WITH RECURSIVE w1(c1) AS
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
index 835077e9d57..1ce7826cf90 100644
--- a/src/test/regress/expected/xid.out
+++ b/src/test/regress/expected/xid.out
@@ -110,22 +110,26 @@ select '1'::xid < '2'::xid;
 ERROR:  operator does not exist: xid < xid
 LINE 1: select '1'::xid < '2'::xid;
                         ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select '1'::xid <= '2'::xid;
 ERROR:  operator does not exist: xid <= xid
 LINE 1: select '1'::xid <= '2'::xid;
                         ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select '1'::xid > '2'::xid;
 ERROR:  operator does not exist: xid > xid
 LINE 1: select '1'::xid > '2'::xid;
                         ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 select '1'::xid >= '2'::xid;
 ERROR:  operator does not exist: xid >= xid
 LINE 1: select '1'::xid >= '2'::xid;
                         ^
-HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.
+DETAIL:  No operator of that name accepts the given argument types.
+HINT:  You might need to add explicit type casts.
 -- we want them for xid8 though
 select '1'::xid8 < '2'::xid8, '2'::xid8 < '2'::xid8, '2'::xid8 < '1'::xid8;
  ?column? | ?column? | ?column?
--
2.43.7

From 3c9e74f36c5cb8ab3e237d80cb21b9496d1a9b5c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 21 Aug 2025 18:37:21 -0400
Subject: [PATCH v3 4/4] Mop up a few other error message style violations.

Fix a few related messages that likewise were failing to
separate errdetail from errhint.

Reported-by: Dominique Devienne <ddevienne@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1756041.1754616558@sss.pgh.pa.us
---
 doc/src/sgml/sources.sgml                  |  7 ++++---
 doc/src/sgml/typeconv.sgml                 |  6 +++---
 src/backend/parser/parse_func.c            |  8 ++++----
 src/backend/parser/parse_oper.c            |  4 ++--
 src/test/regress/expected/polymorphism.out | 15 ++++++++++-----
 src/test/regress/expected/time.out         |  3 ++-
 6 files changed, 25 insertions(+), 18 deletions(-)

diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index fa68d4d024a..5759c6f7426 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -153,11 +153,12 @@ ereport(ERROR,
         errmsg("function %s is not unique",
                func_signature_string(funcname, nargs,
                                      NIL, actual_arg_types)),
-        errhint("Unable to choose a best candidate function. "
-                "You might need to add explicit typecasts."));
+        errdetail("Could not choose a best candidate function."),
+        errhint("You might need to add explicit type casts."));
 </programlisting>
     This illustrates the use of format codes to embed run-time values into
-    a message text.  Also, an optional <quote>hint</quote> message is provided.
+    a message text.  Also, optional <quote>detail</quote>
+    and <quote>hint</quote> messages are provided.
     The auxiliary function calls can be written in any order, but
     conventionally <function>errcode</function>
     and <function>errmsg</function> appear first.
diff --git a/doc/src/sgml/typeconv.sgml b/doc/src/sgml/typeconv.sgml
index 44aaf284da4..1707bd884dc 100644
--- a/doc/src/sgml/typeconv.sgml
+++ b/doc/src/sgml/typeconv.sgml
@@ -465,9 +465,9 @@ try a similar case with <literal>~</literal>, we get:
 <screen>
 SELECT ~ '20' AS "negation";

-ERROR:  operator is not unique: ~ "unknown"
-HINT:  Could not choose a best candidate operator. You might need to add
-explicit type casts.
+ERROR:  operator is not unique: ~ unknown
+DETAIL:  Could not choose a best candidate operator.
+HINT:  You might need to add explicit type casts.
 </screen>
 This happens because the system cannot decide which of the several
 possible <literal>~</literal> operators should be preferred.  We can help
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index f666a64999c..f293a5e3f3f 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -567,8 +567,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
                      errmsg("procedure %s is not unique",
                             func_signature_string(funcname, nargs, argnames,
                                                   actual_arg_types)),
-                     errhint("Could not choose a best candidate procedure. "
-                             "You might need to add explicit type casts."),
+                     errdetail("Could not choose a best candidate procedure."),
+                     errhint("You might need to add explicit type casts."),
                      parser_errposition(pstate, location)));
         else
             ereport(ERROR,
@@ -576,8 +576,8 @@ ParseFuncOrColumn(ParseState *pstate, List *funcname, List *fargs,
                      errmsg("function %s is not unique",
                             func_signature_string(funcname, nargs, argnames,
                                                   actual_arg_types)),
-                     errhint("Could not choose a best candidate function. "
-                             "You might need to add explicit type casts."),
+                     errdetail("Could not choose a best candidate function."),
+                     errhint("You might need to add explicit type casts."),
                      parser_errposition(pstate, location)));
     }
     else
diff --git a/src/backend/parser/parse_oper.c b/src/backend/parser/parse_oper.c
index f9e18270489..a480dc05760 100644
--- a/src/backend/parser/parse_oper.c
+++ b/src/backend/parser/parse_oper.c
@@ -633,8 +633,8 @@ op_error(ParseState *pstate, List *op,
                 (errcode(ERRCODE_AMBIGUOUS_FUNCTION),
                  errmsg("operator is not unique: %s",
                         op_signature_string(op, arg1, arg2)),
-                 errhint("Could not choose a best candidate operator. "
-                         "You might need to add explicit type casts."),
+                 errdetail("Could not choose a best candidate operator."),
+                 errhint("You might need to add explicit type casts."),
                  parser_errposition(pstate, location)));
     else
         ereport(ERROR,
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 6ac00488103..758f75b3d89 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1210,7 +1210,8 @@ select dfunc();  -- fail: which dfunc should be called? int or text
 ERROR:  function dfunc() is not unique
 LINE 1: select dfunc();
                ^
-HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
+DETAIL:  Could not choose a best candidate function.
+HINT:  You might need to add explicit type casts.
 select dfunc('Hi');  -- ok
    dfunc
 -----------
@@ -1249,17 +1250,20 @@ select dfunc();  -- fail
 ERROR:  function dfunc() is not unique
 LINE 1: select dfunc();
                ^
-HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
+DETAIL:  Could not choose a best candidate function.
+HINT:  You might need to add explicit type casts.
 select dfunc(1);  -- fail
 ERROR:  function dfunc(integer) is not unique
 LINE 1: select dfunc(1);
                ^
-HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
+DETAIL:  Could not choose a best candidate function.
+HINT:  You might need to add explicit type casts.
 select dfunc(1, 2);  -- fail
 ERROR:  function dfunc(integer, integer) is not unique
 LINE 1: select dfunc(1, 2);
                ^
-HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
+DETAIL:  Could not choose a best candidate function.
+HINT:  You might need to add explicit type casts.
 select dfunc(1, 2, 3);  -- ok
  dfunc
 -------
@@ -1378,7 +1382,8 @@ select dfunc(1);  -- fail
 ERROR:  function dfunc(integer) is not unique
 LINE 1: select dfunc(1);
                ^
-HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
+DETAIL:  Could not choose a best candidate function.
+HINT:  You might need to add explicit type casts.
 -- but this works since the ambiguous functions aren't preferred anyway
 select dfunc('Hi');
  dfunc
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index 4247fae9412..765adeb6e51 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -157,7 +157,8 @@ SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
 ERROR:  operator is not unique: time without time zone + time without time zone
 LINE 1: SELECT f1 + time '00:01' AS "Illegal" FROM TIME_TBL;
                   ^
-HINT:  Could not choose a best candidate operator. You might need to add explicit type casts.
+DETAIL:  Could not choose a best candidate operator.
+HINT:  You might need to add explicit type casts.
 --
 -- test EXTRACT
 --
--
2.43.7


В списке pgsql-hackers по дате отправления: