Improve the type clash error message#12182
Conversation
|
This is request for comment. I don't mind dropping the third commit or giving up the entire idea. |
|
Interesting, thanks! I think that I disagree with some of the category names. For example "The method |
|
A more correct denomination might be "method call", "record field access". I don't think length is a problem at this point. Perhaps the message could print "The method 'm' has type" and remove the |
| Line 3, characters 18-22: | ||
| 3 | let _ = print_int Cons | ||
| ^^^^ | ||
| Error: This expression has type v but an expression was expected of type int |
| - #12182: Improve the type clash error message. | ||
| For example, this message: | ||
| This expression has type ... | ||
| is changed into: | ||
| The constant '42' has type ... | ||
| (Jules Aguillon, review by Gabriel Scherer) |
There was a problem hiding this comment.
The change entry is perhaps too long ?
There was a problem hiding this comment.
Fine with me. I like descriptive change entries.
|
I believe that using type ('a,'b,'c) t = { x:'a; y:'b; z:'c }
let f r = let a = r.z (1 + r.x) ("y" ^ r.y) in [None; r.z]It would be confusing to state that Error: The field 'z' has type int -> string -> 'a
but an expression was expected of type 'b optionwhen there is no such constraints on the field |
|
Do we want to revisit this or close it? |
|
Overall, I like the change; and if @Julow has the time I would be happy to revisit it. |
|
Hi, I would be happy to revisit it. I also don't mind if this is closed even after I put some time into it. |
To anchor the error message in the context, we could say "the method 'x#m'" or "the field 'r.f'" ? |
|
Possibly, but then we have the issue that to (which also has the advantage of being localisable) |
b127ccc to
c78123e
Compare
|
With the last commits, a denomination is used for multi-keyword expressions like What do you think of applying this change to the other messages starting in "This expression" ? I worry that adding an intro to the message makes reading harder as it's of irregular length. What do you think of these: More correct: Perhaps more uses of the context could be found ? |
| Line 1, characters 14-36: | ||
| 1 | let _ : int = while false do () done | ||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||
| Error: This while expression has type "unit" |
There was a problem hiding this comment.
We want inline code quote here is since while is not part of the grammatical structure of the sentence.
34e668d to
f9dd42d
Compare
|
Using P.S: Note that those messages are not localizable, the issue is that composing error messages by fragments like |
| (** [sexp_for_hint] is used by error messages to report literals in their | ||
| original formatting *) | ||
| let unify_pat ?sdesc_for_hint env pat expected_ty = | ||
| let unify_pat ?sexp_for_hint env pat expected_ty = |
There was a problem hiding this comment.
This PR is still only using the description for hints in the pattern case. I propose to keep the code unchanged.
| (** [sexp_for_hint] is used by error messages to report literals in their | ||
| original formatting *) | ||
| let unify_exp ?sdesc_for_hint env exp expected_ty = | ||
| let unify_exp ?sexp_for_hint env exp expected_ty = |
There was a problem hiding this comment.
The sexp_for_hint is used for more than hints now. I think it would be better to rename it to sexp and make it non-optional. That will fix the missing constructors case.
There was a problem hiding this comment.
Thanks, this fixed many cases indeed.
| | Pexp_constant { pconst_desc = Pconst_string (s, _, None); _ } -> | ||
| (* Avoid adding quotes around a quoted string constant but make sure to | ||
| use consistent inline code style. *) | ||
| Format.pp_print_string ppf (String.escaped s) |
There was a problem hiding this comment.
This is only an issue with dumb terminal and in the testsuite, isn't it? Moreover using String.escaped does not mesh well with non-ascii contents. I would propose to skip this step.
There was a problem hiding this comment.
You are right, making the strings nominal is problematic. I removed it.
|
|
||
| let report_expr_type_clash_hints exp diff = | ||
| match exp with | ||
| | Some (Pexp_constant const) -> report_literal_type_constraint const diff |
| let d = match denom with Some d -> d | None -> "expression" in | ||
| fprintf ppf "%s" d | ||
| in | ||
| report_general_this_exp denom ppf |
There was a problem hiding this comment.
It seems to me that the two report_this_exp function could be merged without loss of readability.
There was a problem hiding this comment.
Do you mean change report_this_pexp to take an option pexp ?
There was a problem hiding this comment.
Yes, I was thinking of making the choice of denomination in one place:
let report_this_pexp_opt denom ppf exp =
let denom ppf =
match denom, exp with
| Some d, _ -> fprintf ppf "%s" d
| None, Some exp -> pp_pexp_denom ppf exp
| None, None -> fprintf ppf "expression"
in
let nexp = Option.bind exp Pprintast.Doc.(nominal_exp_doc longident) in
match nexp with
| Some nexp -> fprintf ppf "The %t %a" denom pp_doc nexp
| _ -> fprintf ppf "This %t" denomThere was a problem hiding this comment.
Looking at the call sites, we could also make the sentence more readable by adding the has type suffix.
There was a problem hiding this comment.
You removed the wrapping of nexp with Style.as_inline_code in your snippet. Is that intended ?
There was a problem hiding this comment.
Thanks, I've changed the code.
f9dd42d to
d478de5
Compare
Julow
left a comment
There was a problem hiding this comment.
Rebased and ready for an other review. Thanks for waiting!
| ^^^^ | ||
| Error: This expression has type "bool" but an expression was expected of type | ||
| "int" | ||
| Error: The constructor "\#true" has type "bool" |
There was a problem hiding this comment.
This could be improved.
There was a problem hiding this comment.
I can have a look at this issue later.
|
|
||
| let report_expr_type_clash_hints exp diff = | ||
| match exp with | ||
| | Some (Pexp_constant const) -> report_literal_type_constraint const diff |
| let d = match denom with Some d -> d | None -> "expression" in | ||
| fprintf ppf "%s" d | ||
| in | ||
| report_general_this_exp denom ppf |
There was a problem hiding this comment.
Do you mean change report_this_pexp to take an option pexp ?
| (** [sexp_for_hint] is used by error messages to report literals in their | ||
| original formatting *) | ||
| let unify_pat ?sdesc_for_hint env pat expected_ty = | ||
| let unify_pat ?sexp_for_hint env pat expected_ty = |
| (** [sexp_for_hint] is used by error messages to report literals in their | ||
| original formatting *) | ||
| let unify_exp ?sdesc_for_hint env exp expected_ty = | ||
| let unify_exp ?sexp_for_hint env exp expected_ty = |
There was a problem hiding this comment.
Thanks, this fixed many cases indeed.
| | Pexp_constant { pconst_desc = Pconst_string (s, _, None); _ } -> | ||
| (* Avoid adding quotes around a quoted string constant but make sure to | ||
| use consistent inline code style. *) | ||
| Format.pp_print_string ppf (String.escaped s) |
There was a problem hiding this comment.
You are right, making the strings nominal is problematic. I removed it.
| - Do not contain spaces when printed. | ||
| - Is a constant that is short enough. | ||
| *) | ||
| let nominal_exp_doc lid t = |
There was a problem hiding this comment.
I have the impression that we don't need to delay the dependency on longident anymore?
Also since the function is in the Doc submodule, the _doc suffix seems redundant.
Octachron
left a comment
There was a problem hiding this comment.
The PR looks good to me: directly quoting the conflicting part of the source code should help readers to locate the source of the type error.
This should be a noticeable improvement, which only requires a small and localized change in Typecore.
|
@Julow , do you wish to clean the history in order to preserve the individual message commits? Otherwise, I would squash the PR and merge the message commits. |
|
Thanks :) Yes, I can clean up the history. |
This function can be used to improve the type clash error message, which uses the parsetree instead of the typedtree to avoid loosing some information from the source code (eg. how constants are printed). This function is used for the "this function ..." message, which can be easily changed to use untyped expression.
Re-use the mechanism implemented for the "This function" message in
type clash errors to print the original expression if possible.
Type error messages now look like this:
Error: The expression '43' has type int
but an expression was expected of type int32
The argument 'unify_exp ~sexp' is made non-optional as this isn't too
intrusive and ensures the new error is implemented in more cases.
Clarify type clash error messages by printing the kind of the reported
expression.
Currently implemented for expression that are likely 'nominal' but not
necessarily.
Example:
The constant "42" has type ...
This field has type ...
This is similar to the `Apply_non_function` error:
The function "foo" has type ...
This function has type ...
Add denominations for expressions like `for`, `while`, `if`, `match`, etc.. that are often spanning several lines. It acts as a confirmation that the error is about the containing expression rather than a nested one as the printed code is sometimes harder to read. There's no test for some of them (`try`, `if`) but they'll become easily reachable if the denomination is used in other messages.
9162094 to
bc88b1b
Compare
Related: #12152
The second commit re-use the mechanism implemented in #11679 for type clash errors:
The third commit change the denomination for some expressions instead of the generic "expression".
For example:
This is similar to the previous PR (#11679):
This could be extended to most expressions (eg.
object,unnamed function,tuple,match expression, etc..).