Skip to content

Improve the error message about too many arguments#11679

Merged
Octachron merged 4 commits intoocaml:trunkfrom
Julow:err_app_extra
Jan 19, 2023
Merged

Improve the error message about too many arguments#11679
Octachron merged 4 commits intoocaml:trunkfrom
Julow:err_app_extra

Conversation

@Julow
Copy link
Copy Markdown
Contributor

@Julow Julow commented Oct 28, 2022

The "applied to too many arguments" error message confuses me every time I see even though it often has a trivial to fix cause.
The message highlights the function but not the whole application, which can be useful to understand which argument is problematic and where I should add the suggested ;.

This PR change the location of the main message to span the whole application and adds two sub-messages:

  • Where to put the missing ;, for which the location extends outside the range of the line.
  • The extra argument, which helps understand whether it's really about a missing ; or if I should remove an argument.

The multi-line highlighting is not easy to read, I have a suggestion in #11678

Before:

File "./test.ml", line 4, characters 2-13:
4 |   print_lines (List.map string_of_int [ 1; 2; 3; 4; 5 ])
      ^^^^^^^^^^^
Error: This function has type string list -> unit
       It is applied to too many arguments; maybe you forgot a `;'.

After:

File "./test.ml", lines 4-5, characters 2-15:
4 | ..print_lines (List.map string_of_int [ 1; 2; 3; 4; 5 ])
5 |   print_endline......
Error: This function has type string list -> unit
       It is applied to too many arguments
File "./test.ml", line 4, characters 56-57:
 |   print_lines (List.map string_of_int [ 1; 2; 3; 4; 5 ])
                                                           ^
  Maybe you forgot a ';'.
File "./test.ml", line 5, characters 2-15:
5 |   print_endline "foo"
      ^^^^^^^^^^^^^
  This argument is not expected.

Format.fprintf ppf "%s | %s@," line_nb line;
Format.fprintf ppf "%*s " (String.length line_nb) "";
String.iteri (fun i c ->
for i = 0 to rightmost.pos_cnum do
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is weird.

I think your intent is to support locations that are outside the source line, "one past" the end. You should say so in a comment, because the code is confusing if we don't know this.

Note that the code computing lines above appears to allow "one past" positions, but I wonder if this was intentional or in fact a programming mistake (did the authors realize that their definition of end_pos allow this? I think they believed that there were only including valid line indices in the range). Could you add a comment there as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree it's complicated ! Added a comment.

I think the code above works because the end of line has an indice (there's a pos_cnum that correspond to it), so only one line can be picked if the location spans it (it won't pick the start of the next line).
I ended up rewriting it in #11678 (with more comments)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, rightmost.pos_cnum counts the number of character since the start of the file. It is far too large, the right size is rightmost.pos_cnum - line_cnum_start - 1.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right! That was a big oversight, thanks.

in
parent expr "#" meth'
| Texp_instvar (_, _, name) -> Some name.txt
| _ -> None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can do this with much less code. The problem you have decided to solve is to be able to show the function to the user in the error message, but only if "it can be named", which basically amounts to "it is a short/atomic construct". Is the expression "short" enough that I want to include it in the error message? This is the question.

But you are mixing this the actual printing logic, which is needlessly duplicated. It may also very well be wrong / imperfect; for example, do you handle the case of infix operators properly?

My suggestion: turn this into a is_short function that returns a boolean, move it to typedtree.ml, and call it from the error message printer (is funct short?). For the printing you could call Untypeast.untype_expression and then Pprintast.expression. (Or maybe you have access to the parsetree directly at error-raising point and don't need Untypeast.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't proud of this code! I've implemented it like you suggested and it's much better. I've included more cases and written down why I choose them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I don't currently see the new version in the PR.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I just pushed it now.

{ loc_start;
loc_end = { loc_start with pos_cnum = loc_start.pos_cnum + 1 };
loc_ghost = false }
in
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a corner case here: there is not necessarily a white space after the last valid argument. For instance

let f x y z = x + y + z
let t = f 0 1
    (+)2

yields

File "test.ml", line 3, characters 7-8:
3 |     (+)2
           ^
  Hint: Did you forget a ';'?
File "test.ml", line 3, characters 7-8:
3 |     (+)2
           ^
  This argument is not expected.

One option that might work would be to widen the highlighting interval to include the last character of the previous arg:

3 |     (+)2
          ^^
  Hint: Did you forget a ';'?

to make it clearer that we are proposing to insert ; in the middle of the interval.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. It's nice when there's a space too, it shows that the semicolon has to be inserted "after the argument".

in
let warned = ref false in
let rec type_args args ty_fun ty_fun0 sargs =
let rec type_args args ty_fun ty_fun0 sargs prev_arg_loc =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal nitpicking: as prev_arg_loc is a metadata parameter of the functions whereas sargs is the decreasing argument in the recursion, I would rather group it with the args argument and keep sargs as the last argument of the function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I paired the location with the argument, which makes the code clearer. However, I'm not sure it decreases the diff size.

Some (fun () -> option_none env (instance ty) Location.none)
in
let remaining_sargs, arg =
let remaining_sargs, arg, arg_loc =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal nitpicking 2: the arg argument requires some computation whereas arg_loc is straightforward to compute. It may make sense to commute those two elements of the tuple so you can decrease the diff size.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Paired with the arg now. The location needs to be passed because arg is wrapped in a thunk doing side effects.

Copy link
Copy Markdown
Member

@Octachron Octachron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, I like the new information available in the error message. It is a really good idea to point to the supernumerary arguments: it is extremely helpful for beginners and still helpful for the experimented developer.

The hint for ; could be refined to only appear when adding ; is correct in the strict mode but this is the existing behavior and improvements are better left to another PR.

Even if I have still have many small remarks, the code is in good shape and could be merged, once the missing hint tag is added.

@Julow Julow force-pushed the err_app_extra branch 2 times, most recently from cf90cd3 to bcb05bb Compare January 17, 2023 18:11
@Julow
Copy link
Copy Markdown
Contributor Author

Julow commented Jan 17, 2023

Ready for review again. I rebased and cleaned the history.

The hint for ; could be refined to only appear when adding ; is correct in the strict mode but this is the existing behavior and improvements are better left to another PR.

Added to my TODO.

of the argument. *)
let arg_end = previous_arg_loc.loc_end in
{ loc_start = cnum_offset ~-1 arg_end;
loc_end = cnum_offset ~+1 arg_end;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This use of unary + is a bit surprising (this is the only instance in the whole compiler)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's nice, isn't it ?

@Julow Julow force-pushed the err_app_extra branch 2 times, most recently from 4b6ce50 to 6b7689b Compare January 18, 2023 15:19
@Octachron
Copy link
Copy Markdown
Member

This is good to merge once the check-typo commit has been split off.

Julow added 4 commits January 19, 2023 14:47
Allow to write for example:

    File "./test.ml", line 4, characters 46-47:
     |   f (List.map string_of_int [ 1; 2; 3; 4; 5 ])
                                                     ^
      Maybe you forgot a ';'.

Iterating on the string instead of the range is not useful because range
calculation have already been done.
The location of the application, upto the problematic argument, is computed and
used as the location for the main message.
Two sub-messages are added: one for the suggested location of a potential `;`
and the other for highlighting the extra argument.

Before:

    File "./test.ml", line 4, characters 2-13:
    4 |   print_lines (List.map string_of_int [ 1; 2; 3; 4; 5 ])
          ^^^^^^^^^^^
    Error: This function has type string list -> unit
           It is applied to too many arguments; maybe you forgot a `;'.

After:

    File "./test.ml", lines 4-5, characters 2-15:
    4 | ..print_lines (List.map string_of_int [ 1; 2; 3; 4; 5 ])
    5 |   print_endline......
    Error: This function has type string list -> unit
           It is applied to too many arguments
    File "./test.ml", line 4, characters 56-57:
     |   print_lines (List.map string_of_int [ 1; 2; 3; 4; 5 ])
                                                               ^
      Maybe you forgot a ';'.
    File "./test.ml", line 5, characters 2-15:
    5 |   print_endline "foo"
          ^^^^^^^^^^^^^
      This argument is not expected.
Increase clarity of the message since the location no longer point
directly to the function.

Introduce 'Typedtree.exp_is_nominal' for testing whether an expression
can be printed as the subject of a sentence with 'Pprintast'.
Extend the location to the right to cover the end of the previous
argument as well.
This renders well when there's no space between the two arguments and
shows better that the semicolon has to be inserted (between two
position).
@Octachron Octachron merged commit 6c80a1c into ocaml:trunk Jan 19, 2023
@Octachron
Copy link
Copy Markdown
Member

Merged, thanks a lot for the great improvement to the compiler error messages!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants