Skip to content

Commit 01ee097

Browse files
authored
Hint about coercion in error msg (#7505)
* print type coercion hint when appropriate * refactor * changelog * do not suggest coercion for constant expressions * tighten wording
1 parent 6f799bc commit 01ee097

File tree

7 files changed

+112
-6
lines changed

7 files changed

+112
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
- Complete from `RegExp` stdlib module for regexes. https://github.com/rescript-lang/rescript/pull/7425
2929
- Allow oneliner formatting when including module with single type alias. https://github.com/rescript-lang/rescript/pull/7502
3030
- Improve error messages for JSX type mismatches, passing objects where record is expected, passing array literal where tuple is expected, and more. https://github.com/rescript-lang/rescript/pull/7500
31+
- Show in error messages when coercion can be used to fix a type mismatch. https://github.com/rescript-lang/rescript/pull/7505
3132

3233
# 12.0.0-alpha.13
3334

compiler/ml/error_message_utils.ml

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ let with_configured_jsx_module s =
1111
module Parser : sig
1212
type comment
1313

14+
val extract_text_at_loc : Location.t -> string
15+
1416
val parse_source : (string -> Parsetree.structure * comment list) ref
1517

1618
val reprint_source : (Parsetree.structure -> comment list -> string) ref
@@ -38,10 +40,13 @@ end = struct
3840
let end_offset = end_pos.pos_cnum in
3941
String.sub src start_offset (end_offset - start_offset)
4042

41-
let parse_expr_at_loc loc =
43+
let extract_text_at_loc loc =
4244
(* TODO: Maybe cache later on *)
4345
let src = Ext_io.load_file loc.Location.loc_start.pos_fname in
44-
let sub_src = extract_location_string ~src loc in
46+
extract_location_string ~src loc
47+
48+
let parse_expr_at_loc loc =
49+
let sub_src = extract_text_at_loc loc in
4550
let parsed, comments = !parse_source sub_src in
4651
match parsed with
4752
| [{Parsetree.pstr_desc = Pstr_eval (exp, _)}] -> Some (exp, comments)
@@ -59,6 +64,11 @@ end = struct
5964
| None -> None
6065
end
6166
67+
let type_expr ppf typ =
68+
(* print a type and avoid infinite loops *)
69+
Printtyp.reset_and_mark_loops typ;
70+
Printtyp.type_expr ppf typ
71+
6272
type type_clash_statement = FunctionCall
6373
type type_clash_context =
6474
| SetRecordField
@@ -345,6 +355,43 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
345355
single JSX element.@,"
346356
(with_configured_jsx_module "array")
347357
| _ -> ())
358+
| _, Some (t1, t2) ->
359+
let is_subtype =
360+
try
361+
Ctype.subtype env t1 t2 ();
362+
true
363+
with _ -> false
364+
in
365+
let target_type_string = Format.asprintf "%a" type_expr t2 in
366+
let target_expr_text = Parser.extract_text_at_loc loc in
367+
let suggested_rewrite =
368+
match
369+
!Parser.parse_source
370+
(Printf.sprintf "(%s :> %s)" target_expr_text target_type_string)
371+
with
372+
| [], _ -> None
373+
| structure, comments -> Some (!Parser.reprint_source structure comments)
374+
in
375+
376+
(* Suggesting coercion only makes sense for non-constant values. *)
377+
let is_constant =
378+
match !Parser.parse_source target_expr_text with
379+
| ( [{Parsetree.pstr_desc = Pstr_eval ({pexp_desc = Pexp_constant _}, _)}],
380+
_ ) ->
381+
true
382+
| _ -> false
383+
in
384+
385+
if is_subtype && not is_constant then (
386+
fprintf ppf
387+
"@,\
388+
@,\
389+
Possible solutions: @,\
390+
- These types are compatible at runtime. You can use the coercion \
391+
operator to convert to the expected type";
392+
match suggested_rewrite with
393+
| Some rewrite -> fprintf ppf ": @{<info>%s@}@," rewrite
394+
| None -> fprintf ppf ": @{<info>:>@}@,")
348395
| _ -> ()
349396
350397
let type_clash_context_from_function sexp sfunct =

compiler/ml/typecore.ml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4168,10 +4168,8 @@ let longident = Printtyp.longident
41684168
let super_report_unification_error = Printtyp.super_report_unification_error
41694169
let report_ambiguous_type_error = Printtyp.report_ambiguous_type_error
41704170
let report_subtyping_error = Printtyp.report_subtyping_error
4171-
let type_expr ppf typ =
4172-
(* print a type and avoid infinite loops *)
4173-
Printtyp.reset_and_mark_loops typ;
4174-
Printtyp.type_expr ppf typ
4171+
4172+
let type_expr = Error_message_utils.type_expr
41754173
41764174
let report_error env loc ppf error =
41774175
match error with
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/subtype_record.res:23:23
4+
5+
21 │ }
6+
22 │
7+
23 │ let x = takesSomeType(v)
8+
24 │
9+
10+
This has type: someOtherType
11+
But this function argument is expecting: SomeModule.someType
12+
13+
Possible solutions:
14+
- These types are compatible at runtime. You can use the coercion operator to convert to the expected type: (v :> SomeModule.someType)
15+

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/subtype_string.res:7:18-20
4+
5+
5 │ }
6+
6 │
7+
7 │ let x = takesStr(One)
8+
8 │
9+
10+
This has type: variant
11+
But it's expected to have type: string
12+
13+
Possible solutions:
14+
- These types are compatible at runtime. You can use the coercion operator to convert to the expected type: (One :> string)
15+

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
module SomeModule = {
2+
type someType = {
3+
one: bool,
4+
two: int,
5+
}
6+
}
7+
8+
type someOtherType = {
9+
...SomeModule.someType,
10+
three: string,
11+
}
12+
13+
let v = {
14+
one: true,
15+
two: 1,
16+
three: "hello",
17+
}
18+
19+
let takesSomeType = (s: SomeModule.someType) => {
20+
s.one
21+
}
22+
23+
let x = takesSomeType(v)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type variant = One | Two
2+
3+
let takesStr = (s: string) => {
4+
s ++ "hello"
5+
}
6+
7+
let x = takesStr(One)

0 commit comments

Comments
 (0)