On Thu, Dec 19, 2024 at 06:40:19PM -0500, David Malcolm wrote:
> Consider this case of a bad call to a callback function (perhaps
> due to C23 changing the meaning of () in function decls):
> 
> struct p {
>         int (*bar)();
> };
> 
> void baz() {
>     struct p q;
>     q.bar(1);
> }
> 
> Before this patch the C frontend emits:
> 
> t.c: In function 'baz':
> t.c:7:5: error: too many arguments to function 'q.bar'
>     7 |     q.bar(1);
>       |     ^
> 
> and the C++ frontend emits:
> 
> t.c: In function 'void baz()':
> t.c:7:10: error: too many arguments to function
>     7 |     q.bar(1);
>       |     ~~~~~^~~
> 
> neither of which give the user much help in terms of knowing what
> was expected, and where the relevant declaration is.
> 
> With this patch the C frontend emits:
> 
> t.c: In function 'baz':
> t.c:7:5: error: too many arguments to function 'q.bar'; expected 0, have 1
>     7 |     q.bar(1);
>       |     ^     ~
> t.c:2:15: note: declared here
>     2 |         int (*bar)();
>       |               ^~~
> 
> (showing the expected vs actual counts, the pertinent field decl, and
> underlining the first extraneous argument at the callsite)
> 
> and the C++ frontend emits:
> 
> t.c: In function 'void baz()':
> t.c:7:10: error: too many arguments to function; expected 0, have 1
>     7 |     q.bar(1);
>       |     ~~~~~^~~
> 
> (showing the expected vs actual counts; the other data was not accessible
> without a more invasive patch)
> 
> Similarly, the patch also updates the "too few arguments" case to also
> show expected vs actual counts.  Doing so requires a tweak to the
> wording to say "at least" for the case of variadic fns, and for C++ fns
> with default args, where e.g. previously the C FE emitted:
> 
> s.c: In function 'test':
> s.c:5:3: error: too few arguments to function 'callee'
>     5 |   callee ();
>       |   ^~~~~~
> s.c:1:6: note: declared here
>     1 | void callee (const char *, ...);
>       |      ^~~~~~
> 
> with this patch it emits:
> 
> s.c: In function 'test':
> s.c:5:3: error: too few arguments to function 'callee'; expected at least 1, 
> have 0
>     5 |   callee ();
>       |   ^~~~~~
> s.c:1:6: note: declared here
>     1 | void callee (const char *, ...);
>       |      ^~~~~~
> 
> Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
> OK for trunk?
> 
> gcc/c/ChangeLog:
>       PR c/118112
>       * c-typeck.cc (inform_declaration): Add "function_expr" param and
>       use it for cases where we couldn't show the function decl to show
>       field decls for callbacks.
>       (build_function_call_vec): Add missing auto_diagnostic_group.
>       Update for new param of inform_declaration.
>       (convert_arguments): Likewise.  For the "too many arguments" case
>       add the expected vs actual counts to the message, and if we have
>       it, add the location_t of the first surplus param as a secondary
>       location within the diagnostic.  For the "too few arguments" case,
>       determine the minimum number of arguments required and add the
>       expected vs actual counts to the message, tweaking it to "at least"
>       for variadic functions.
> 
> gcc/cp/ChangeLog:
>       PR c/118112
>       * typeck.cc (error_args_num): Add params "expected_num",
>       "actual_num", and "at_least_p".  Compute "too_many_p" from these
>       rather than have it be a param.  Add expected vs actual counts to
>       the messages and tweak them for the "at least" case.
>       (convert_arguments): Update calls to error_args_num to pass in
>       expected vs actual number, and the "at_least_p", determining this
>       for the "too few" case.
> 
> gcc/testsuite/ChangeLog:
>       PR c/118112
>       * c-c++-common/too-few-arguments.c: New test.
>       * c-c++-common/too-many-arguments.c: New test.
>       * g++.dg/cpp0x/variadic169.C: Verify the reported expected vs
>       actual argument counts.
>       * g++.dg/modules/macloc-1_c.C: Update regexp for addition of param
>       counts to error message.
>       * g++.dg/modules/macloc-1_d.C: Likewise.
> 
> Signed-off-by: David Malcolm <dmalc...@redhat.com>

[...]

> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index 964e549a6122..2966931ca8c1 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -59,7 +59,7 @@ static tree get_delta_difference (tree, tree, bool, bool, 
> tsubst_flags_t);
>  static void casts_away_constness_r (tree *, tree *, tsubst_flags_t);
>  static bool casts_away_constness (tree, tree, tsubst_flags_t);
>  static bool maybe_warn_about_returning_address_of_local (tree, location_t = 
> UNKNOWN_LOCATION);
> -static void error_args_num (location_t, tree, bool);
> +static void error_args_num (location_t, tree, int, int, bool);
>  static int convert_arguments (tree, vec<tree, va_gc> **, tree, int,
>                                tsubst_flags_t);
>  static bool is_std_move_p (tree);
> @@ -4533,11 +4533,16 @@ cp_build_function_call_vec (tree function, vec<tree, 
> va_gc> **params,
>  }
>  
>  /* Subroutine of convert_arguments.
> -   Print an error message about a wrong number of arguments.  */
> +   Print an error message about a wrong number of arguments.
> +   If AT_LEAST_P is true, then EXPECTED_NUM is the minimum number
> +   of expected arguments.  */

Should probably mention ACTUAL_NUM in the comment too.

>  
>  static void
> -error_args_num (location_t loc, tree fndecl, bool too_many_p)
> +error_args_num (location_t loc, tree fndecl, int expected_num, int 
> actual_num,
> +             bool at_least_p)
>  {
> +  gcc_assert (expected_num != actual_num);
> +  const bool too_many_p = actual_num > expected_num;
>    if (fndecl)
>      {
>        auto_diagnostic_group d;
> @@ -4548,22 +4553,28 @@ error_args_num (location_t loc, tree fndecl, bool 
> too_many_p)
>                 == DECL_NAME (TYPE_NAME (DECL_CONTEXT (fndecl)))))
>           error_at (loc,
>                     too_many_p
> -                   ? G_("too many arguments to constructor %q#D")
> -                   : G_("too few arguments to constructor %q#D"),
> -                   fndecl);
> +                   ? G_("too many arguments to constructor %q#D; expected 
> %i, have %i")
> +                   : (at_least_p
> +                      ? G_("too few arguments to constructor %q#D; expected 
> at least %i, have %i")
> +                      : G_("too few arguments to constructor %q#D; expected 
> %i, have %i")),
> +                   fndecl, expected_num, actual_num);
>         else
>           error_at (loc,
>                     too_many_p
> -                   ? G_("too many arguments to member function %q#D")
> -                   : G_("too few arguments to member function %q#D"),
> -                   fndecl);
> +                   ? G_("too many arguments to member function %q#D; 
> expected %i, have %i")
> +                   : (at_least_p
> +                      ? G_("too few arguments to member function %q#D; 
> expected at least %i, have %i")
> +                      : G_("too few arguments to member function %q#D; 
> expected %i, have %i")),
> +                   fndecl, expected_num, actual_num);
>       }

Looks like the METHOD_TYPE block is not exercised in our testsuite :(.

>        else
>       error_at (loc,
>                 too_many_p
> -               ? G_("too many arguments to function %q#D")
> -               : G_("too few arguments to function %q#D"),
> -               fndecl);
> +               ? G_("too many arguments to function %q#D; expected %i, have 
> %i")
> +               : (at_least_p
> +                  ? G_("too few arguments to function %q#D; expected at 
> least %i, have %i")
> +                  : G_("too few arguments to function %q#D; expected %i, 
> have %i")),
> +               fndecl, expected_num, actual_num);
>        if (!DECL_IS_UNDECLARED_BUILTIN (fndecl))
>       inform (DECL_SOURCE_LOCATION (fndecl), "declared here");
>      }
> @@ -4572,12 +4583,19 @@ error_args_num (location_t loc, tree fndecl, bool 
> too_many_p)
>        if (c_dialect_objc ()  &&  objc_message_selector ())
>       error_at (loc,
>                 too_many_p
> -               ? G_("too many arguments to method %q#D")
> -               : G_("too few arguments to method %q#D"),
> -               objc_message_selector ());
> +               ? G_("too many arguments to method %q#D; expected %i, have 
> %i")
> +               : (at_least_p
> +                  ? G_("too few arguments to method %q#D; expected at least 
> %i, have %i")
> +                  : G_("too few arguments to method %q#D; expected %i, have 
> %i")),
> +               objc_message_selector (), expected_num, actual_num);
>        else
> -     error_at (loc, too_many_p ? G_("too many arguments to function")
> -                               : G_("too few arguments to function"));
> +     error_at (loc,
> +               too_many_p
> +               ? G_("too many arguments to function; expected %i, have %i")
> +               : (at_least_p
> +                  ? G_("too few arguments to function; expected at least %i, 
> have %i")
> +                  : G_("too few arguments to function; expected %i, have 
> %i")),
> +               expected_num, actual_num);
>      }
>  }
>  
> @@ -4607,6 +4625,10 @@ convert_arguments (tree typelist, vec<tree, va_gc> 
> **values, tree fndecl,
>    /* Argument passing is always copy-initialization.  */
>    flags |= LOOKUP_ONLYCONVERTING;
>  
> +  /* Preserve actual number of arguments passed (without counting default
> +     args), in case we need to complain about too many/few.  */
> +  int actual_num = vec_safe_length (*values);

If we're computing the length here, we don't need to compute it again...

> +
>    for (i = 0, typetail = typelist;
>         i < vec_safe_length (*values);

...here.

>         i++)
> @@ -4621,7 +4643,10 @@ convert_arguments (tree typelist, vec<tree, va_gc> 
> **values, tree fndecl,
>       {
>            if (complain & tf_error)
>              {
> -           error_args_num (input_location, fndecl, /*too_many_p=*/true);
> +           /* Too many args.  */
> +           int expected_num = i;

Why not just pass i?  error_args_num doesn't modify its args.

> +           error_args_num (input_location, fndecl, expected_num, actual_num,
> +                           false);

Nit, but I'd like to have it as /*at_least_p=*/false.

>                return i;
>              }
>            else
> @@ -4743,7 +4768,38 @@ convert_arguments (tree typelist, vec<tree, va_gc> 
> **values, tree fndecl,
>        if (typetail && typetail != void_list_node)
>       {
>         if (complain & tf_error)
> -         error_args_num (input_location, fndecl, /*too_many_p=*/false);
> +         {
> +           /* Not enough args.
> +              Determine minimum number of arguments required.  */
> +           int min_expected_num = 0;
> +           bool at_least_p = false;
> +           tree iter = typelist;
> +           while (true)
> +             {
> +               if (!iter)
> +                 {
> +                   /* Variadic arguments; stop iterating.  */
> +                   at_least_p = true;
> +                   break;
> +                 }
> +               if (iter == void_list_node)
> +                 /* End of arguments; stop iterating.  */
> +                 break;
> +               if (fndecl && TREE_PURPOSE (iter)
> +                   && TREE_CODE (TREE_PURPOSE (iter)) != DEFERRED_PARSE)
> +                 {
> +                   /* Found a default argument; skip this one when
> +                      counting minimum required.  */
> +                   at_least_p = true;
> +                   iter = TREE_CHAIN (iter);
> +                   continue;
> +                 }
> +               ++min_expected_num;
> +               iter = TREE_CHAIN (iter);
> +             }
> +           error_args_num (input_location, fndecl,
> +                           min_expected_num, actual_num, at_least_p);
> +         }
>         return -1;
>       }
>      }

I haven't found any issues otherwise.

Marek

Reply via email to