Skip to content

Commit 36824ed

Browse files
authored
Suggest different arity functions when it makes sense (#7712)
* suggest different arity fns when it makes sense * improve comments * changelog * format
1 parent fc69bf4 commit 36824ed

9 files changed

+141
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
- Better error message if platform binaries package is not found. https://github.com/rescript-lang/rescript/pull/7698
2929
- Hint in error for string constants matching expected variant/polyvariant constructor. https://github.com/rescript-lang/rescript/pull/7711
3030
- Polish arity mismatch error message a bit. https://github.com/rescript-lang/rescript/pull/7709
31+
- Suggest related functions with the expected arity in errors when it makes sense. https://github.com/rescript-lang/rescript/pull/7712
3132
- Improve error when a constructor expects an inline record. https://github.com/rescript-lang/rescript/pull/7713
3233

3334
#### :house: Internal

compiler/ml/typecore.ml

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,13 @@ type error =
8484
| Unknown_literal of string * char
8585
| Illegal_letrec_pat
8686
| Empty_record_literal
87-
| Uncurried_arity_mismatch of
88-
type_expr * int * int * Asttypes.Noloc.arg_label list
87+
| Uncurried_arity_mismatch of {
88+
function_type: type_expr;
89+
expected_arity: int;
90+
provided_arity: int;
91+
provided_args: Asttypes.Noloc.arg_label list;
92+
function_name: Longident.t option;
93+
}
8994
| Field_not_optional of string * type_expr
9095
| Type_params_not_supported of Longident.t
9196
| Field_access_on_dict_type
@@ -2230,6 +2235,11 @@ let not_function env ty =
22302235
let ls, tvar = list_labels env ty in
22312236
ls = [] && not tvar
22322237
2238+
let extract_function_name funct =
2239+
match funct.exp_desc with
2240+
| Texp_ident (path, _, _) -> Some (Longident.parse (Path.name path))
2241+
| _ -> None
2242+
22332243
type lazy_args =
22342244
(Asttypes.Noloc.arg_label * (unit -> Typedtree.expression) option) list
22352245
@@ -3522,10 +3532,13 @@ and type_application ~context total_app env funct (sargs : sargs) :
35223532
( funct.exp_loc,
35233533
env,
35243534
Uncurried_arity_mismatch
3525-
( funct.exp_type,
3526-
arity,
3527-
List.length sargs,
3528-
sargs |> List.map (fun (a, _) -> to_noloc a) ) ));
3535+
{
3536+
function_type = funct.exp_type;
3537+
expected_arity = arity;
3538+
provided_arity = List.length sargs;
3539+
provided_args = sargs |> List.map (fun (a, _) -> to_noloc a);
3540+
function_name = extract_function_name funct;
3541+
} ));
35293542
arity
35303543
| None -> max_int
35313544
in
@@ -3541,10 +3554,13 @@ and type_application ~context total_app env funct (sargs : sargs) :
35413554
( funct.exp_loc,
35423555
env,
35433556
Uncurried_arity_mismatch
3544-
( funct.exp_type,
3545-
required_args + newarity,
3546-
required_args,
3547-
sargs |> List.map (fun (a, _) -> to_noloc a) ) )));
3557+
{
3558+
function_type = funct.exp_type;
3559+
expected_arity = required_args + newarity;
3560+
provided_arity = required_args;
3561+
provided_args = sargs |> List.map (fun (a, _) -> to_noloc a);
3562+
function_name = extract_function_name funct;
3563+
} )));
35483564
let new_t =
35493565
if fully_applied then new_t
35503566
else
@@ -4247,6 +4263,40 @@ let spellcheck ppf unbound_name valid_names =
42474263
let spellcheck_idents ppf unbound valid_idents =
42484264
spellcheck ppf (Ident.name unbound) (List.map Ident.name valid_idents)
42494265
4266+
let strip_arity_suffix name =
4267+
let len = String.length name in
4268+
let rec scan_back i =
4269+
if i < 0 || name.[i] < '0' || name.[i] > '9' then i + 1
4270+
else scan_back (i - 1)
4271+
in
4272+
let start_of_digits = scan_back (len - 1) in
4273+
if start_of_digits > 0 && start_of_digits < len then
4274+
String.sub name 0 start_of_digits
4275+
else name
4276+
4277+
let find_arity_suggestion env function_name target_arity =
4278+
let base_name = strip_arity_suffix function_name in
4279+
let candidate =
4280+
if target_arity = 1 then base_name
4281+
else base_name ^ string_of_int target_arity
4282+
in
4283+
try
4284+
let path, desc = Env.lookup_value (Longident.parse candidate) env in
4285+
if Builtin_attributes.deprecated_of_attrs desc.val_attributes <> None then
4286+
None
4287+
else
4288+
let expanded_type = Ctype.expand_head env desc.val_type in
4289+
let actual_arity =
4290+
match Ctype.get_arity env expanded_type with
4291+
| Some arity -> arity
4292+
| None -> 0
4293+
in
4294+
if actual_arity = target_arity then Some (Printtyp.string_of_path path)
4295+
else None
4296+
with
4297+
| Not_found -> None
4298+
| _ -> None
4299+
42504300
open Format
42514301
let longident = Printtyp.longident
42524302
let super_report_unification_error = Printtyp.super_report_unification_error
@@ -4516,7 +4566,14 @@ let report_error env loc ppf error =
45164566
fprintf ppf
45174567
"Empty record literal {} should be type annotated or used in a record \
45184568
context."
4519-
| Uncurried_arity_mismatch (typ, arity, args, sargs) ->
4569+
| Uncurried_arity_mismatch
4570+
{
4571+
function_type = typ;
4572+
expected_arity = arity;
4573+
provided_arity = args;
4574+
provided_args = sargs;
4575+
function_name = function_name_opt;
4576+
} ->
45204577
(* We need:
45214578
- Any arg that's required but isn't passed
45224579
- Any arg that is passed but isn't in the fn definition (optional or labelled)
@@ -4625,6 +4682,26 @@ let report_error env loc ppf error =
46254682
(if args = 1 then "" else "s")
46264683
arity;
46274684
4685+
(* Add suggestions for related functions with correct arity *)
4686+
(match function_name_opt with
4687+
| Some function_name -> (
4688+
let function_name_str =
4689+
let buffer = Buffer.create 16 in
4690+
let formatter = Format.formatter_of_buffer buffer in
4691+
Printtyp.longident formatter function_name;
4692+
Format.pp_print_flush formatter ();
4693+
Buffer.contents buffer
4694+
in
4695+
let suggestion = find_arity_suggestion env function_name_str args in
4696+
match suggestion with
4697+
| None -> ()
4698+
| Some suggestion_str ->
4699+
fprintf ppf
4700+
"@,@,Hint: Try @{<info>%s@} instead (takes @{<info>%d@} argument%s)."
4701+
suggestion_str args
4702+
(if args = 1 then "" else "s"))
4703+
| None -> ());
4704+
46284705
fprintf ppf "@]"
46294706
| Field_not_optional (name, typ) ->
46304707
fprintf ppf "Field @{<info>%s@} is not optional in type %a. Use without ?"

compiler/ml/typecore.mli

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,13 @@ type error =
116116
| Unknown_literal of string * char
117117
| Illegal_letrec_pat
118118
| Empty_record_literal
119-
| Uncurried_arity_mismatch of
120-
type_expr * int * int * Asttypes.Noloc.arg_label list
119+
| Uncurried_arity_mismatch of {
120+
function_type: type_expr;
121+
expected_arity: int;
122+
provided_arity: int;
123+
provided_args: Asttypes.Noloc.arg_label list;
124+
function_name: Longident.t option;
125+
}
121126
| Field_not_optional of string * type_expr
122127
| Type_params_not_supported of Longident.t
123128
| Field_access_on_dict_type
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/suggest_existing_arity_fn_1.res:1:1-11
4+
5+
1 │ Console.log(1, 2)
6+
2 │
7+
8+
This function call is incorrect.
9+
The function has type:
10+
'a => unit
11+
12+
- The function takes just 1 unlabelled argument, but is called with 2
13+
14+
Hint: Try Console.log2 instead (takes 2 arguments).
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/suggest_existing_arity_fn_2.res:1:1-12
4+
5+
1 │ Console.log2(1)
6+
2 │
7+
8+
This function call is incorrect.
9+
The function has type:
10+
(int, 'a) => unit
11+
12+
- The function takes 2 unlabelled arguments, but is called with just 1
13+
14+
Hint: Try Console.log instead (takes 1 argument).
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/suggest_existing_arity_fn_3.res:1:1-12
4+
5+
1 │ Console.log4(1, 2)
6+
2 │
7+
8+
This function call is incorrect.
9+
The function has type:
10+
(int, int, 'a, 'b) => unit
11+
12+
- The function takes 4 unlabelled arguments, but is called with just 2
13+
14+
Hint: Try Console.log2 instead (takes 2 arguments).
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Console.log(1, 2)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Console.log2(1)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Console.log4(1, 2)

0 commit comments

Comments
 (0)