Обсуждение: Identifying function-lookup failures due to argument name mismatches

Поиск
Список
Период
Сортировка

Identifying function-lookup failures due to argument name mismatches

От
Tom Lane
Дата:
We've had various complaints about how our error reports aren't too
helpful if a function lookup failure occurs because you misspell
the name of a named argument.  The most recent is at [1], but there
have been others if memory serves.

I took a swing at improving this, as attached.  It turns out to be
about as messy as I feared, because the basic question of "did the
argument names match" turns out to be intermixed with a bunch of
random rules about default arguments and precisely how you're allowed
to mix named and positional arguments.  So, of the three existing
test cases that this patch changes the results for, the first change
is quite on-point but the second and third maybe not so much.

Perhaps this could be improved further with some refactoring, but the
function lookup logic is complicated and changing it would raise the
odds of introducing a bug quite a lot.

Another thing not to like is that it seems like this is doing violence
to several APIs in exchange for not very much improvement in the error
messages.  I feel like maybe we ought to be trying for more
specificity about additional cases, but I'm not very sure what else
to improve or what the API could look like.

Anyway, I'm not seriously proposing that this should be committed
as-is.  I'm throwing it out there in case anyone else has a good
idea or feels motivated to push on the problem some more.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/CAFCRh-_iLoUtMAtyunw_-O6sgpWo04sOmB38MUVNpuQVSkL_0Q%40mail.gmail.com

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d97d632a7ef..100c58b658a 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -1184,17 +1184,24 @@ 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.
+ * *bad_argnames is set true if there were matches to the function name
+ * but not to the argnames, or to false in all other cases.
+ *
+ * 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.)
  */
 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,
+                      bool *bad_argnames)
 {
     FuncCandidateList resultList = NULL;
     bool        any_special = false;
+    bool        found_name_match = false;
+    bool        found_argnames_match = false;
     char       *schemaname;
     char       *funcname;
     Oid            namespaceId;
@@ -1204,6 +1211,9 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
     /* check for caller error */
     Assert(nargs >= 0 || !(expand_variadic | expand_defaults));

+    /* set default output */
+    *bad_argnames = false;
+
     /* deconstruct the name list */
     DeconstructQualifiedName(names, &schemaname, &funcname);

@@ -1263,6 +1273,9 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
                 continue;        /* proc is not in search path */
         }

+        /* Okay, we have a name (and schema) match */
+        found_name_match = true;
+
         /*
          * If we are asked to match to OUT arguments, then use the
          * proallargtypes array (which includes those); otherwise use
@@ -1331,6 +1344,9 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
                                 &argnumbers))
                 continue;

+            /* We have found at least one match to the argument names */
+            found_argnames_match = true;
+
             /* Named argument matching is always "special" */
             any_special = true;
         }
@@ -1557,6 +1573,14 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,

     ReleaseSysCacheList(catlist);

+    /*
+     * Detect case where there were possible matches but they all failed to
+     * match the given argnames.
+     */
+    if (resultList == NULL && argnames != NIL &&
+        found_name_match && !found_argnames_match)
+        *bad_argnames = true;
+
     return resultList;
 }

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

         visible = false;

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

         for (; clist; clist = clist->next)
         {
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 583bbbf232f..e8383ef99d7 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -601,9 +601,30 @@ 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"
+         * rather than mentioning columns.  We have a couple of different
+         * things to say as detail or hint, though.
          */
-        if (list_length(agg_order) > 1 && !agg_within_group)
+        if (fdresult == FUNCDETAIL_BAD_ARGNAMES)
+        {
+            if (proc_call)
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                         errmsg("procedure %s does not exist",
+                                func_signature_string(funcname, nargs, argnames,
+                                                      actual_arg_types)),
+                         errdetail("No procedure matches the given argument name(s)."),
+                         parser_errposition(pstate, location)));
+            else
+                ereport(ERROR,
+                        (errcode(ERRCODE_UNDEFINED_FUNCTION),
+                         errmsg("function %s does not exist",
+                                func_signature_string(funcname, nargs, argnames,
+                                                      actual_arg_types)),
+                         errdetail("No function matches the given argument name(s)."),
+                         parser_errposition(pstate, location)));
+        }
+        else if (list_length(agg_order) > 1 && !agg_within_group)
         {
             /* It's agg(x, ORDER BY y,z) ... perhaps misplaced ORDER BY */
             ereport(ERROR,
@@ -1410,6 +1431,7 @@ func_get_detail(List *funcname,
 {
     FuncCandidateList raw_candidates;
     FuncCandidateList best_candidate;
+    bool        bad_argnames;

     /* initialize output arguments to silence compiler warnings */
     *funcid = InvalidOid;
@@ -1424,7 +1446,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,
+                                           &bad_argnames);

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

         *funcid = best_candidate->oid;
         *nvargs = best_candidate->nvargs;
@@ -1719,6 +1742,12 @@ func_get_detail(List *funcname,
         return result;
     }

+    /*
+     * For error reporting purposes, we want to distinguish the case of
+     * could-not-find-an-argnames-match from other cases.
+     */
+    if (bad_argnames)
+        return FUNCDETAIL_BAD_ARGNAMES;
     return FUNCDETAIL_NOTFOUND;
 }

@@ -2053,6 +2082,7 @@ LookupFuncNameInternal(ObjectType objtype, List *funcname,
 {
     Oid            result = InvalidOid;
     FuncCandidateList clist;
+    bool        bad_argnames;

     /* NULL argtypes allowed for nullary functions only */
     Assert(argtypes != NULL || nargs == 0);
@@ -2062,7 +2092,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,
+                                  &bad_argnames);

     /* 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..9b70305bfc6 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;
+    bool        bad_argnames;

     /* 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,
+                                  &bad_argnames);

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

             /*
              * 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,
+                                          &bad_argnames);
             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;
+    bool        bad_argnames;

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

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

     for (; clist; clist = clist->next)
     {
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 8c7ccc69a3c..9ab5fb5daf6 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -102,7 +102,8 @@ extern FuncCandidateList FuncnameGetCandidates(List *names,
                                                bool expand_variadic,
                                                bool expand_defaults,
                                                bool include_out_arguments,
-                                               bool missing_ok);
+                                               bool missing_ok,
+                                               bool *bad_argnames);
 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..29af0f58ec0 100644
--- a/src/include/parser/parse_func.h
+++ b/src/include/parser/parse_func.h
@@ -22,6 +22,7 @@
 typedef enum
 {
     FUNCDETAIL_NOTFOUND,        /* no matching function */
+    FUNCDETAIL_BAD_ARGNAMES,    /* no matching function because of arg names */
     FUNCDETAIL_MULTIPLE,        /* too many matching functions */
     FUNCDETAIL_NORMAL,            /* found a matching regular function */
     FUNCDETAIL_PROCEDURE,        /* found a matching procedure */
diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out
index 94eedfe375e..83576c739b5 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -1448,17 +1448,17 @@ 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 matches the given argument name(s).
 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:  No function matches the given argument name(s).
 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:  No function matches the given argument name(s).
 drop function dfunc(int, int, int, int);
 -- test with different parameter types
 create function dfunc(a varchar, b numeric, c date = current_date)

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

От
Tom Lane
Дата:
I wrote:
> Another thing not to like is that it seems like this is doing violence
> to several APIs in exchange for not very much improvement in the error
> messages.  I feel like maybe we ought to be trying for more
> specificity about additional cases, but I'm not very sure what else
> to improve or what the API could look like.

I couldn't quite let go of this, and after some thought I hit on the
idea of making FuncnameGetCandidates pass back a bitmask of flags
showing how far the match succeeded.  This seems to work pretty
nicely, allowing quite-detailed reports with only minimal added
overhead or code restructuring.  There's probably room for further
improvement, but it has less of a whiff of "quick single-purpose
hack".  See draft commit message for more details.

            regards, tom lane

From bd559eb9ab1dbcbd398269aa463ddcad9607b61d Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 14 Aug 2025 15:12:05 -0400
Subject: [PATCH v2] 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.

The specific hint messages I've written could perhaps do with some
bike-shedding.  Also, I've not thought hard about whether any of these
complaints are factual enough that they ought to be reported as DETAIL
not HINT.

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               |  79 ++++++++----
 src/backend/catalog/pg_aggregate.c            |   2 +
 src/backend/parser/parse_func.c               | 113 ++++++++++++++++--
 src/backend/utils/adt/regproc.c               |  13 +-
 src/backend/utils/adt/ruleutils.c             |   2 +
 src/include/catalog/namespace.h               |  18 ++-
 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/plpgsql.out         |   2 +-
 src/test/regress/expected/polymorphism.out    |  52 +++++++-
 src/test/regress/expected/temp.out            |   2 +-
 src/test/regress/sql/polymorphism.sql         |  17 +++
 17 files changed, 267 insertions(+), 47 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d97d632a7ef..3798461cad9 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
@@ -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,6 +1211,9 @@ 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);

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

+        *fgc_flags |= FGC_NAME_MATCH;    /* we found a matching function name */
+
         /*
          * If we are asked to match to OUT arguments, then use the
          * proallargtypes array (which includes those); otherwise use
@@ -1297,16 +1310,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 */
@@ -1328,9 +1331,27 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
             /* 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;
         }
@@ -1580,11 +1601,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 +1616,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 +1670,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 +1689,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 +1717,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 +1783,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..adae5beae71 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 *funcname,
+                                        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, funcname,
+                                                 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, funcname,
+                                                 argnames, proc_call),
                      parser_errposition(pstate, location)));
     }

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

+/*
+ * Interpret the fgc_flags and issue a suitable hint.
+ *
+ * Helper function to reduce code duplication while throwing a
+ * function-not-found error.
+ */
+static int
+func_lookup_failure_details(int fgc_flags, List *funcname,
+                            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 (list_length(funcname) > 1)
+            return 0;            /* schema-qualified name */
+        else if (proc_call)
+            return errhint("There is no procedure of that name in the search_path.");
+        else
+            return errhint("There is no function of that name in the search_path.");
+    }
+
+    /*
+     * 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 errhint("No procedure matches the given argument names.");
+        else
+            return errhint("No function 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 errhint("Named arguments were incorrectly combined with positional arguments.");
+
+    if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_ALL))
+        return errhint("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 +1453,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 +1486,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 +1511,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 +1682,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 +2144,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 +2154,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..ed973afc9e4 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -39,6 +39,21 @@ 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_MATCH    0x0001    /* Found the explicitly-specified schema */
+#define FGC_NAME_MATCH        0x0002    /* Found a function name match */
+/* These bits relate only to calls using named or mixed arguments: */
+#define FGC_ARGNAMES_MATCH    0x0004    /* Found a func matching all argnames */
+#define FGC_ARGNAMES_PLACED    0x0008    /* Found argnames validly placed */
+#define FGC_ARGNAMES_ALL    0x0010    /* Found a function with no missing args */
+#define FGC_ARGNAMES_VALID    0x0020    /* Found a fully-valid use of argnames */
+/* These bits are actually filled by func_get_detail: */
+#define FGC_VARIADIC_FAIL    0x0040    /* Disallowed VARIADIC with named args */
+
 /*
  * Result of checkTempNamespaceStatus
  */
@@ -102,7 +117,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..ce533e6de47 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.
+HINT:  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..4a128db6091 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.
+HINT:  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..9047a5f8841 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.
+HINT:  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..1036c6677db 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" H
"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..2753a71e91e 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.
+HINT:  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..e610597a1e5 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.
+HINT:  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/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d8ce39dba3c..6fbbebb526b 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.
+HINT:  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..7bf94dc7e59 100644
--- a/src/test/regress/expected/polymorphism.out
+++ b/src/test/regress/expected/polymorphism.out
@@ -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.
+HINT:  No function 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.
+HINT:  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.
+HINT:  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]);
+               ^
+HINT:  No function 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]);
+               ^
+HINT:  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..7910c0cc6d0 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.
+HINT:  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


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

От
Dominique Devienne
Дата:
On Thu, Aug 14, 2025 at 9:18 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> I wrote:
> > Another thing not to like is that it seems like this is doing violence
> > to several APIs in exchange for not very much improvement in the error
> > messages.  I feel like maybe we ought to be trying for more
> > specificity about additional cases, but I'm not very sure what else
> > to improve or what the API could look like.
>
> I couldn't quite let go of this, and after some thought I hit on the
> idea of making FuncnameGetCandidates pass back a bitmask of flags
> showing how far the match succeeded.  This seems to work pretty
> nicely, allowing quite-detailed reports with only minimal added
> overhead or code restructuring.  There's probably room for further
> improvement, but it has less of a whiff of "quick single-purpose
> hack".  See draft commit message for more details.

As the original "complainer", I felt compelled to have a look at this
patch.  Not to gauge its quality, mind you; I'm not qualified for
that.  But to look at the flags, and the error messages.  Below are my
notes observations:

From the use in code:
FGC_SCHEMA_MATCH; /* report that the schema is valid */
FGC_NAME_MATCH; /* we found a matching function name */
FGC_ARGNAMES_VALID; /* We found a fully-valid call using argument names */
FGC_ARGNAMES_MATCH; /* the function did match all the supplied argnames */
FGC_ARGNAMES_PLACED; /* call doesn't violate the rules for mixed notation */
FGC_ARGNAMES_ALL; /* call supplies all the required arguments */
FGC_VARIADIC_FAIL;

From the declaration:
+/*
+ * 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_MATCH 0x0001 /* Found the explicitly-specified schema */
+#define FGC_NAME_MATCH 0x0002 /* Found a function name match */
+/* These bits relate only to calls using named or mixed arguments: */
+#define FGC_ARGNAMES_MATCH 0x0004 /* Found a func matching all argnames */
+#define FGC_ARGNAMES_PLACED 0x0008 /* Found argnames validly placed */
+#define FGC_ARGNAMES_ALL 0x0010 /* Found a function with no missing args */
+#define FGC_ARGNAMES_VALID 0x0020 /* Found a fully-valid use of argnames */
+/* These bits are actually filled by func_get_detail: */
+#define FGC_VARIADIC_FAIL 0x0040 /* Disallowed VARIADIC with named args */

I like the new messages in func_lookup_failure_details().  Very much
so in fact.  BUT I still don't like the fallback "traditional"
message, because the way I read it, it fails to mention argument
*names* could be the reason for the lookup failure.  Now maybe that's
moot, because of earlier messages.  But I can't know that myself, thus
I'm still re-iterating my feeling on this.  In my reading, "given
name" applies to "function" or "procedure" before it, and not to
"argument" after it.  Thus I'd go with "and argument types or names".

One thing I find missing are flags about the actual syntax used by the
user, i.e.
is it schema-qualified?
does it use named arguments?

If some flags you've added Tom, are TRUE, then that's implied.
But is the converse true? i.e. if the flag is FALSE, can you know
whether named-args were used? (schema-qualified seems special, as
fails earlier, if I read you right). Because it could have some
bearing on the errors, no?

In any case, what you are proposing goes a LONG WAY to improve the
current situation, IMHO.  Thank you VERY MUCH for pursuing this.  And
I very much hope something like it goes through for v19 next year.
--DD



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

От
Chao Li
Дата:
Hi Tom,

On Aug 8, 2025, at 09:29, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Anyway, I'm not seriously proposing that this should be committed
as-is.  I'm throwing it out there in case anyone else has a good
idea or feels motivated to push on the problem some more.

Looks like you are looking for someone to work out a final patch. If that is true, I will be happy to work on this problem.

I couldn't quite let go of this, and after some thought I hit on the
idea of making FuncnameGetCandidates pass back a bitmask of flags
showing how far the match succeeded.  This seems to work pretty
nicely, allowing quite-detailed reports with only minimal added
overhead or code restructuring.  There's probably room for further
improvement, but it has less of a whiff of "quick single-purpose
hack".  See draft commit message for more details.

I traced this problem today, and I agree that making FuncnameGetCandidates to pass out some information should be right direction to go.

When there are multiple matches, I think we can find the best match by considering argument names/types, default values. If there are still multiple best matches, I think we can prompt all matches to client.

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/




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

От
Tom Lane
Дата:
Dominique Devienne <ddevienne@gmail.com> writes:
> I like the new messages in func_lookup_failure_details().  Very much
> so in fact.  BUT I still don't like the fallback "traditional"
> message, because the way I read it, it fails to mention argument
> *names* could be the reason for the lookup failure.  Now maybe that's
> moot, because of earlier messages.  But I can't know that myself, thus
> I'm still re-iterating my feeling on this.  In my reading, "given
> name" applies to "function" or "procedure" before it, and not to
> "argument" after it.  Thus I'd go with "and argument types or names".

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?)

Perhaps a compromise could be to use two different hint messages,
mentioning argument names only when the call actually used some.
That would limit the blast radius a good deal, I think, though
I didn't try counting affected tests.

> One thing I find missing are flags about the actual syntax used by the
> user, i.e.
> is it schema-qualified?
> does it use named arguments?

I thought that wasn't really necessary, because the caller already
knows those things, or can discover them about as easily as by
checking a flag.

> If some flags you've added Tom, are TRUE, then that's implied.
> But is the converse true? i.e. if the flag is FALSE, can you know
> whether named-args were used? (schema-qualified seems special, as
> fails earlier, if I read you right). Because it could have some
> bearing on the errors, no?

The patch's func_lookup_failure_details() code shows how I intended
to deal with those questions.  Sure, we could also do that through
returned flags, but is that better?  ParseFuncOrColumn contains
existing tests for "argnames != NIL", so checking that the same way
in this new code seemed better than doing it differently.

            regards, tom lane



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

От
Tom Lane
Дата:
Chao Li <li.evan.chao@gmail.com> writes:
> On Aug 8, 2025, at 09:29, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> I couldn't quite let go of this, and after some thought I hit on the
>> idea of making FuncnameGetCandidates pass back a bitmask of flags
>> showing how far the match succeeded.

> I traced this problem today, and I agree that making FuncnameGetCandidates to pass out some information should be
rightdirection to go. 

> When there are multiple matches, I think we can find the best match by considering argument names/types, default
values.If there are still multiple best matches, I think we can prompt all matches to client. 

I don't want to touch the existing rules about how we winnow down the
potential matches.  That has a risk of breaking applications that are
fine today.  The idea of this patch is just to give more-specific
error messages when we end up with no matches.  (In fact, one of the
points that I think could use review is checking that the small
refactoring I did have to do inside MatchNamedCall didn't change
its existing outputs.)

            regards, tom lane



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

От
Tom Lane
Дата:
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


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

От
Dominique Devienne
Дата:
On Fri, Aug 22, 2025 at 12:58 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> 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 [...] it seems maybe not *that* awful.

Great.

> 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.

Excellent. Thanks for sharing.
Maybe I'll get another undeserved medallion then :)

> it's best for ParseFuncOrColumn to uniformly use "argnames != NIL"
> for checking whether there are argnames, though.

I'm sure you're right. But given the above, an out flag for it too
would be more consistent, like the schema one. My $0.02.

> Also, I added a flag bit [...] where none of the candidate [...]
> because when that's true, we'll never get to looking at arguments

Sounds like an improvement indeed. Subtle difference I didn't
even get on my first reading of your mail. You're into it now!

One last though. Is it worth reserving a few bits to count the
candidate matches? You'll never reach 32 flags, so 8 feels like plenty.
Barring listing the candidates, a count hint might help? In my case
it was only 1, but it more complete cases where the search_path
is involved, one might get surprised with candidates coming from afar
making things ambiguous? Again, jus thinking aloud. --DD



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

От
Tom Lane
Дата:
Dominique Devienne <ddevienne@gmail.com> writes:
> One last though. Is it worth reserving a few bits to count the
> candidate matches? You'll never reach 32 flags, so 8 feels like plenty.
> Barring listing the candidates, a count hint might help? In my case
> it was only 1, but it more complete cases where the search_path
> is involved, one might get surprised with candidates coming from afar
> making things ambiguous? Again, jus thinking aloud. --DD

Candidates in what sense, that is where would you make the count?
In any case, that seems like it's about adding detail to the
"ambiguous function" case, which might be worth doing but it's
not the goal of this patch.

            regards, tom lane



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

От
Tom Lane
Дата:
Here is a v4 with some additional bike-shedding on the error texts.
In particular, I decided that it was worth expending an additional
flag bit so that we could reliably distinguish "There is no function
of that name" from "A function of that name exists, but it is not in
the search_path".  (Since FuncnameGetCandidates is already searching
the entire set of functions matching the given name, it doesn't take
any extra work to know that there's a match outside the search path.)
I rephrased a couple of the other messages too, but without any
substantive logic change.

            regards, tom lane

From ba0701a0bf2f2f20b1453f38471c9f100c989c8d Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 24 Aug 2025 13:39:54 -0400
Subject: [PATCH v4 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               |  94 +++++++++---
 src/backend/catalog/pg_aggregate.c            |   2 +
 src/backend/parser/parse_func.c               | 137 ++++++++++++++++--
 src/backend/utils/adt/regproc.c               |  13 +-
 src/backend/utils/adt/ruleutils.c             |   2 +
 src/include/catalog/namespace.h               |  21 ++-
 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, 312 insertions(+), 56 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d97d632a7ef..c54cbbd7a35 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_EXISTS;    /* report that the schema exists */
     }
     else
     {
@@ -1238,6 +1250,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
         int           *argnumbers = NULL;
         FuncCandidateList newResult;

+        *fgc_flags |= FGC_NAME_EXISTS;    /* the name is present in pg_proc */
+
         if (OidIsValid(namespaceId))
         {
             /* Consider only procs in specified namespace */
@@ -1263,6 +1277,8 @@ FuncnameGetCandidates(List *names, int nargs, List *argnames,
                 continue;        /* proc is not in search path */
         }

+        *fgc_flags |= FGC_NAME_VISIBLE; /* routine is in the right schema */
+
         /*
          * If we are asked to match to OUT arguments, then use the
          * proallargtypes array (which includes those); otherwise use
@@ -1297,16 +1313,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,11 +1331,32 @@ 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 +1399,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 +1610,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 +1625,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 +1679,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 +1698,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 have invalid mixed notation */
+    *fgc_flags |= FGC_ARGNAMES_NONDUP;
+
     /* Check for default arguments */
     if (nargs < pronargs)
     {
@@ -1683,6 +1726,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 +1792,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..8d036f52752 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,105 @@ 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_VISIBLE, we shouldn't raise the question of whether the
+     * arguments are wrong.  If the function name was not schema-qualified,
+     * it's helpful to distinguish between doesn't-exist-anywhere and
+     * not-in-search-path; 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_EXISTS here: we'd have already thrown an
+     * error if an explicitly-given schema doesn't exist.
+     */
+    if (!(fgc_flags & FGC_NAME_VISIBLE))
+    {
+        if (fgc_flags & FGC_SCHEMA_GIVEN)
+            return 0;            /* schema-qualified name */
+        else if (!(fgc_flags & FGC_NAME_EXISTS))
+        {
+            if (proc_call)
+                return errdetail("There is no procedure of that name.");
+            else
+                return errdetail("There is no function of that name.");
+        }
+        else
+        {
+            if (proc_call)
+                return errdetail("A procedure of that name exists, but it is not in the search_path.");
+            else
+                return errdetail("A function of that name exists, but it is not 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 accepts the given number of arguments.");
+        else
+            return errdetail("No function of that name accepts the given 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 accepts the given argument names.");
+        else
+            return errdetail("No function of that name accepts 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_NONDUP))
+        return errdetail("In the closest available match, "
+                         "an argument was specified both positionally and by name.");
+
+    if (argnames != NIL && !(fgc_flags & FGC_ARGNAMES_ALL))
+        return errdetail("In the closest available match, "
+                         "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 +1477,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 +1510,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 +1535,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 +1706,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 +2168,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 +2178,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..977c015f3aa 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -39,6 +39,24 @@ 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_EXISTS    0x0002    /* Found the explicitly-specified schema */
+#define FGC_NAME_EXISTS        0x0004    /* Found a routine by that name */
+#define FGC_NAME_VISIBLE    0x0008    /* Found a routine name/schema match */
+#define FGC_ARGCOUNT_MATCH    0x0010    /* Found a func with right # of args */
+/* These bits relate only to calls using named or mixed arguments: */
+#define FGC_ARGNAMES_MATCH    0x0020    /* Found a func matching all argnames */
+#define FGC_ARGNAMES_NONDUP    0x0040    /* argnames don't overlap positional args */
+#define FGC_ARGNAMES_ALL    0x0080    /* Found a func with no missing args */
+#define FGC_ARGNAMES_VALID    0x0100    /* Found a fully-valid use of argnames */
+/* These bits are actually filled by func_get_detail: */
+#define FGC_VARIADIC_FAIL    0x0200    /* Disallowed VARIADIC with named args */
+
 /*
  * Result of checkTempNamespaceStatus
  */
@@ -102,7 +120,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..6343962b81d 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.
 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..a85dd17b579 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.
 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..96bbdfb9586 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.
 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..3e5007d13b2 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." 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..fdae52d6ab2 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.
 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..3e8817a4db9 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.
 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..36164a99c83 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 accepts the given 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 accepts the given number of arguments.
 --
 -- canonicalize_path()
 --
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index d8ce39dba3c..1592252f602 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.
 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..7ebfb1f8707 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 accepts the given 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 accepts the given 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 accepts the given 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 accepts the given 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 accepts 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:  In the closest available match, an argument was specified both positionally and by name.
 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:  In the closest available match, 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 accepts 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:  In the closest available match, an argument was specified both positionally and by name.
+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..a50c7ae88a9 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.
 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 543ab56ae813375e11b632fc0c4f68ce2f8c9466 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 24 Aug 2025 13:43:35 -0400
Subject: [PATCH v4 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 8d036f52752..a772de6b76e 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)));
         }
@@ -1000,14 +1000,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 3e8817a4db9..f89042cf798 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 1592252f602..d320b4d43de 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 7ebfb1f8707..74bdb1ffdca 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 45d93d558218ddb303fe1b57297c48855dde8cf0 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 24 Aug 2025 14:07:28 -0400
Subject: [PATCH v4 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               | 24 ++++++-
 src/backend/parser/parse_oper.c               | 65 +++++++++++++++----
 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, 120 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 c54cbbd7a35..0411977a9ed 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -1931,9 +1931,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;
@@ -1944,15 +1955,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_EXISTS;    /* report that the schema exists */
     }
     else
     {
@@ -1990,6 +2006,8 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
         if (oprkind && operform->oprkind != oprkind)
             continue;

+        *fgc_flags |= FGC_NAME_EXISTS;    /* the name is present in pg_operator */
+
         if (OidIsValid(namespaceId))
         {
             /* Consider only opers in specified namespace */
@@ -2063,6 +2081,8 @@ OpernameGetCandidates(List *names, char oprkind, bool missing_schema_ok)
             }
         }

+        *fgc_flags |= FGC_NAME_VISIBLE; /* operator is in the right schema */
+
         /*
          * 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..fafdf0a80a3 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,52 @@ 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_VISIBLE, we shouldn't raise the question of whether the
+     * arguments are wrong.  If the operator name was not schema-qualified,
+     * it's helpful to distinguish between doesn't-exist-anywhere and
+     * not-in-search-path; 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_EXISTS here: we'd have already thrown an
+     * error if an explicitly-given schema doesn't exist.
+     */
+    if (!(fgc_flags & FGC_NAME_VISIBLE))
+    {
+        if (fgc_flags & FGC_SCHEMA_GIVEN)
+            return 0;            /* schema-qualified name */
+        else if (!(fgc_flags & FGC_NAME_EXISTS))
+            return errdetail("There is no operator of that name.");
+        else
+            return errdetail("An operator of that name exists, but it is not 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 977c015f3aa..f1423f28c32 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -126,7 +126,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 b33e06a0d3d..a08f115b0e5 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 d320b4d43de..474be478ce8 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 74bdb1ffdca..dd38d950d81 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 177b5d3819d4f013bc201d99c37b127f2d3484a1 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 24 Aug 2025 14:15:06 -0400
Subject: [PATCH v4 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 a772de6b76e..c43020a769d 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 fafdf0a80a3..7bd7a336fd6 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 dd38d950d81..3cd99a92aa5 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


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

От
Chao Li
Дата:

> On Aug 25, 2025, at 02:47, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> Here is a v4 with some additional bike-shedding on the error texts.
> In particular, I decided that it was worth expending an additional
> flag bit so that we could reliably distinguish "There is no function
> of that name" from "A function of that name exists, but it is not in
> the search_path".  (Since FuncnameGetCandidates is already searching
> the entire set of functions matching the given name, it doesn't take
> any extra work to know that there's a match outside the search path.)
> I rephrased a couple of the other messages too, but without any
> substantive logic change.
>

I tested various error cases, all got proper error messages. So, I think this patch has significantly improved the
situation.

I just have a tiny comment. In func_lookup_failure_details(), there are a lot of duplicate code like:

```
    if (proc_call)
        return errdetail("A procedure of that name exists, but it is not in the search_path.");
    else
        return errdetail("A function of that name exists, but it is not in the search_path.”);
```

The if-else is just to distinguish “procedure” and “function”, rest of words are duplicated.

Can we avoid the duplication in a way like:

```
static int
func_lookup_failure_details(int fgc_flags, List *argnames, bool proc_call)
{
    const char *func_kind = proc_call ? "procedure" : "function";

    /*
    if (proc_call)
        return errdetail("There is no procedure of that name.");
    else
        return errdetail("There is no function of that name.");
    */
    return errdetail("There is no %s of that name.", func_kind);
```

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/


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

От
Peter Eisentraut
Дата:
On 24.08.25 20:47, Tom Lane wrote:
> Here is a v4 with some additional bike-shedding on the error texts.
> In particular, I decided that it was worth expending an additional
> flag bit so that we could reliably distinguish "There is no function
> of that name" from "A function of that name exists, but it is not in
> the search_path".  (Since FuncnameGetCandidates is already searching
> the entire set of functions matching the given name, it doesn't take
> any extra work to know that there's a match outside the search path.)
> I rephrased a couple of the other messages too, but without any
> substantive logic change.

I only gave it a quick review right now.  I have also been wanting to 
make the function lookup error messages more specific, so I like this 
direction very much.  The wording of the messages looks good and more 
useful than before.



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

От
Peter Eisentraut
Дата:
On 25.08.25 04:43, Chao Li wrote:
> Can we avoid the duplication in a way like:
> 
> ```
> static int
> func_lookup_failure_details(int fgc_flags, List *argnames, bool proc_call)
> {
>     const char *func_kind = proc_call ? "procedure" : "function";
> 
>     /*
>     if (proc_call)
>         return errdetail("There is no procedure of that name.");
>     else
>         return errdetail("There is no function of that name.");
>     */
>     return errdetail("There is no %s of that name.", func_kind);
> ```

No, see here: 
https://www.postgresql.org/docs/devel/nls-programmer.html#NLS-GUIDELINES



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

От
Chao Li
Дата:


On Aug 27, 2025, at 23:42, Peter Eisentraut <peter@eisentraut.org> wrote:

On 25.08.25 04:43, Chao Li wrote:
Can we avoid the duplication in a way like:
```
static int
func_lookup_failure_details(int fgc_flags, List *argnames, bool proc_call)
{
const char *func_kind = proc_call ? "procedure" : "function";
/*
if (proc_call)
return errdetail("There is no procedure of that name.");
else
return errdetail("There is no function of that name.");
*/
return errdetail("There is no %s of that name.", func_kind);
```

No, see here: https://www.postgresql.org/docs/devel/nls-programmer.html#NLS-GUIDELINES

Thank you Peter very much. It is good to learn.

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/




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

От
Robert Haas
Дата:
Some review of 0001:

+ return errdetail("A procedure of that name exists, but it is not in
the search_path.");

Are you sure you want to expose this case in this way? From a security
point of view it makes me a bit nervous. If we're going to keep it, it
should have a test. Even from a non-security perspective, maybe having
the error message vary based on the contents of a completely unrelated
schema is not the best design decision. I can imagine that hosing some
user that is looking for a specific message and then somebody installs
an extension and the message changes even though there's no reason for
them to interact.

-HINT:  No function matches the given name and argument types. You
might need to add explicit type casts.

I wonder what caused this line to disappear without being replaced by
anything (test_extensions.out).

Overall, I like this. I think these changes are helpful.

Some review of 0002:

I don't mind the churn. It is perhaps not mandatory, though. Call me +0.5.

Comments above also basically apply to 0003 and 0004: not mandatory, I
tentatively think they're improvements, be careful about the
not-in-schema case, test it if we're going to expose that information.

--
Robert Haas
EDB: http://www.enterprisedb.com



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

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> Some review of 0001:
> + return errdetail("A procedure of that name exists, but it is not in
> the search_path.");

> Are you sure you want to expose this case in this way? From a security
> point of view it makes me a bit nervous.

I'm not seeing the security concern?  An attacker who can issue a
SQL command that would trigger this could presumably also issue a
"SELECT FROM pg_proc" that would reveal the same info and more.

> If we're going to keep it, it
> should have a test.

Huh, I thought I had covered the case, but you're right it's not
anywhere in the .out files.  Will fix.

> Even from a non-security perspective, maybe having
> the error message vary based on the contents of a completely unrelated
> schema is not the best design decision. I can imagine that hosing some
> user that is looking for a specific message and then somebody installs
> an extension and the message changes even though there's no reason for
> them to interact.

The primary error message is not varying, only the DETAIL/HINT, so
I find this concern pretty far-fetched.  Also, I believe that the
case that the message intends to help with is very common and so
it will save a lot of people time, more than enough to outweigh
any cases where it's perhaps un-optimal.

> -HINT:  No function matches the given name and argument types. You
> might need to add explicit type casts.

> I wonder what caused this line to disappear without being replaced by
> anything (test_extensions.out).

That is the response to

 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.

and I omitted the hint/detail because it seems to add nothing,
per this argument:

+     * If not FGC_NAME_VISIBLE, we shouldn't raise the question of whether the
+     * arguments are wrong.  If the function name was not schema-qualified,
+     * it's helpful to distinguish between doesn't-exist-anywhere and
+     * not-in-search-path; but if it was, there's really nothing to add to the
+     * basic "function/procedure %s does not exist" message.

I'm certainly willing to discuss that choice, but I wonder what you
have in mind that isn't just a restatement of "function does not
exist".  There flat out isn't a pg_proc entry of the name that
the user gave.  We could issue something like "HINT: maybe you
misspelled the function name." or "HINT: maybe there's some extension
you need to install." but that's getting way too nanny-ish for my
taste.  The odds of giving an on-point hint don't seem good.

> Overall, I like this. I think these changes are helpful.
> Some review of 0002:
> I don't mind the churn. It is perhaps not mandatory, though. Call me +0.5.
> Comments above also basically apply to 0003 and 0004: not mandatory, I
> tentatively think they're improvements, be careful about the
> not-in-schema case, test it if we're going to expose that information.

Fair enough.

            regards, tom lane



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

От
Robert Haas
Дата:
On Mon, Sep 15, 2025 at 4:01 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> The primary error message is not varying, only the DETAIL/HINT, so
> I find this concern pretty far-fetched.  Also, I believe that the
> case that the message intends to help with is very common and so
> it will save a lot of people time, more than enough to outweigh
> any cases where it's perhaps un-optimal.

I'm not entirely convinced, but you could well be right. I do like all
the other detailed diagnostics, I think, I just wasn't sure about that
one. But I'm not really here to argue, just giving my opinion.

> That is the response to
>
>  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.
>
> and I omitted the hint/detail because it seems to add nothing,

Yeah, OK, that's fair.

--
Robert Haas
EDB: http://www.enterprisedb.com



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

От
Tom Lane
Дата:
Robert Haas <robertmhaas@gmail.com> writes:
> On Mon, Sep 15, 2025 at 4:01 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> The primary error message is not varying, only the DETAIL/HINT, so
>> I find this concern pretty far-fetched.  Also, I believe that the
>> case that the message intends to help with is very common and so
>> it will save a lot of people time, more than enough to outweigh
>> any cases where it's perhaps un-optimal.

> I'm not entirely convinced, but you could well be right. I do like all
> the other detailed diagnostics, I think, I just wasn't sure about that
> one. But I'm not really here to argue, just giving my opinion.

Fair enough.  Again, how shall we proceed?  What I suggest is to
go ahead and push what I have, and if there's anything that's not
so great, hopefully we'll get feedback about it before v19 is
frozen.

            regards, tom lane



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

От
Robert Haas
Дата:
On Mon, Sep 15, 2025 at 5:12 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> Robert Haas <robertmhaas@gmail.com> writes:
> > On Mon, Sep 15, 2025 at 4:01 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
> >> The primary error message is not varying, only the DETAIL/HINT, so
> >> I find this concern pretty far-fetched.  Also, I believe that the
> >> case that the message intends to help with is very common and so
> >> it will save a lot of people time, more than enough to outweigh
> >> any cases where it's perhaps un-optimal.
>
> > I'm not entirely convinced, but you could well be right. I do like all
> > the other detailed diagnostics, I think, I just wasn't sure about that
> > one. But I'm not really here to argue, just giving my opinion.
>
> Fair enough.  Again, how shall we proceed?  What I suggest is to
> go ahead and push what I have, and if there's anything that's not
> so great, hopefully we'll get feedback about it before v19 is
> frozen.

Seems reasonable. I don't see that anyone is strongly objecting. In
fact, I think everyone who has commented has been generally in favor,
just with various minor concerns here and there. And it's certainly
better to put stuff that might need some fine-tuning into the tree
sooner rather than later.

--
Robert Haas
EDB: http://www.enterprisedb.com