Skip to content

Commit a9aaa40

Browse files
committed
print type coercion hint when appropriate
1 parent 2193869 commit a9aaa40

File tree

8 files changed

+127
-2
lines changed

8 files changed

+127
-2
lines changed

compiler/ml/error_message_utils.ml

Lines changed: 47 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,12 @@ end = struct
5964
| None -> None
6065
end
6166
67+
(* TODO: Move this to appropriate place where it can be shared with typecore.ml *)
68+
let type_expr ppf typ =
69+
(* print a type and avoid infinite loops *)
70+
Printtyp.reset_and_mark_loops typ;
71+
Printtyp.type_expr ppf typ
72+
6273
type type_clash_statement = FunctionCall
6374
type type_clash_context =
6475
| SetRecordField
@@ -345,6 +356,40 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
345356
single JSX element.@,"
346357
(with_configured_jsx_module "array")
347358
| _ -> ())
359+
| _, Some (t1, t2) ->
360+
let is_subtype =
361+
try
362+
Ctype.subtype env t1 t2 ();
363+
true
364+
with _ -> false
365+
in
366+
let target_type_string = Format.asprintf "%a" type_expr t2 in
367+
let target_expr_text = Parser.extract_text_at_loc loc in
368+
let suggested_rewrite =
369+
match
370+
!Parser.parse_source
371+
(Printf.sprintf "(%s :> %s)" target_expr_text target_type_string)
372+
with
373+
| [], _ -> None
374+
| structure, comments -> Some (!Parser.reprint_source structure comments)
375+
in
376+
377+
if is_subtype then (
378+
fprintf ppf
379+
"@,\
380+
@,\
381+
Possible solutions: @,\
382+
- These types are compatible at runtime. You can use the coercion \
383+
operator @{<info>:>@} to convert to the expected type @{<info>%s@}."
384+
target_type_string;
385+
match suggested_rewrite with
386+
| Some rewrite ->
387+
fprintf ppf
388+
"@,\
389+
\ If you want to use coercion, rewrite the highlighted code to: \
390+
@{<info>%s@}@,"
391+
rewrite
392+
| None -> ())
348393
| _ -> ()
349394
350395
let type_clash_context_from_function sexp sfunct =

tests/build_tests/super_errors/expected/primitives1.res.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,11 @@
99
This has type: int
1010
But it's expected to have type: float
1111

12+
Possible solutions:
13+
- These types are compatible at runtime. You can use the coercion operator :> to convert to the expected type float.
14+
If you want to use coercion, rewrite the highlighted code to: (2 :> float)
15+

16+
17+
1218
You can convert int to float with Int.toFloat.
1319
If this is a literal, try a number with a trailing dot (e.g. 20.).

tests/build_tests/super_errors/expected/primitives6.res.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,11 @@
1010
This has type: int
1111
But it's expected to have type: float
1212

13+
Possible solutions:
14+
- These types are compatible at runtime. You can use the coercion operator :> to convert to the expected type float.
15+
If you want to use coercion, rewrite the highlighted code to: (10 :> float)
16+

17+
18+
1319
You can convert int to float with Int.toFloat.
1420
If this is a literal, try a number with a trailing dot (e.g. 20.).
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 SomeModule.someType.
15+
If you want to use coercion, rewrite the highlighted code to: (v :> SomeModule.someType)
16+

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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 string.
15+
If you want to use coercion, rewrite the highlighted code to: (One :> string)
16+


tests/build_tests/super_errors/expected/type1.res.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,11 @@
88
This has type: int
99
But it's expected to have type: float
1010

11+
Possible solutions:
12+
- These types are compatible at runtime. You can use the coercion operator :> to convert to the expected type float.
13+
If you want to use coercion, rewrite the highlighted code to: (2 :> float)
14+

15+
16+
1117
You can convert int to float with Int.toFloat.
1218
If this is a literal, try a number with a trailing dot (e.g. 20.).
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)