Skip to content

Commit 8e32074

Browse files
committed
suggest different arity fns when it makes sense
1 parent 724232e commit 8e32074

8 files changed

+137
-13
lines changed

compiler/ml/typecore.ml

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,13 @@ type error =
7979
| Unknown_literal of string * char
8080
| Illegal_letrec_pat
8181
| Empty_record_literal
82-
| Uncurried_arity_mismatch of
83-
type_expr * int * int * Asttypes.Noloc.arg_label list
82+
| Uncurried_arity_mismatch of {
83+
function_type: type_expr;
84+
expected_arity: int;
85+
provided_arity: int;
86+
provided_args: Asttypes.Noloc.arg_label list;
87+
function_name: Longident.t option;
88+
}
8489
| Field_not_optional of string * type_expr
8590
| Type_params_not_supported of Longident.t
8691
| Field_access_on_dict_type
@@ -2220,6 +2225,11 @@ let not_function env ty =
22202225
let ls, tvar = list_labels env ty in
22212226
ls = [] && not tvar
22222227
2228+
let extract_function_name funct =
2229+
match funct.exp_desc with
2230+
| Texp_ident (path, _, _) -> Some (Longident.parse (Path.name path))
2231+
| _ -> None
2232+
22232233
type lazy_args =
22242234
(Asttypes.Noloc.arg_label * (unit -> Typedtree.expression) option) list
22252235
@@ -3512,10 +3522,13 @@ and type_application ~context total_app env funct (sargs : sargs) :
35123522
( funct.exp_loc,
35133523
env,
35143524
Uncurried_arity_mismatch
3515-
( funct.exp_type,
3516-
arity,
3517-
List.length sargs,
3518-
sargs |> List.map (fun (a, _) -> to_noloc a) ) ));
3525+
{
3526+
function_type = funct.exp_type;
3527+
expected_arity = arity;
3528+
provided_arity = List.length sargs;
3529+
provided_args = sargs |> List.map (fun (a, _) -> to_noloc a);
3530+
function_name = extract_function_name funct;
3531+
} ));
35193532
arity
35203533
| None -> max_int
35213534
in
@@ -3531,10 +3544,13 @@ and type_application ~context total_app env funct (sargs : sargs) :
35313544
( funct.exp_loc,
35323545
env,
35333546
Uncurried_arity_mismatch
3534-
( funct.exp_type,
3535-
required_args + newarity,
3536-
required_args,
3537-
sargs |> List.map (fun (a, _) -> to_noloc a) ) )));
3547+
{
3548+
function_type = funct.exp_type;
3549+
expected_arity = required_args + newarity;
3550+
provided_arity = required_args;
3551+
provided_args = sargs |> List.map (fun (a, _) -> to_noloc a);
3552+
function_name = extract_function_name funct;
3553+
} )));
35383554
let new_t =
35393555
if fully_applied then new_t
35403556
else
@@ -4232,6 +4248,40 @@ let spellcheck ppf unbound_name valid_names =
42324248
let spellcheck_idents ppf unbound valid_idents =
42334249
spellcheck ppf (Ident.name unbound) (List.map Ident.name valid_idents)
42344250
4251+
let strip_arity_suffix name =
4252+
let len = String.length name in
4253+
let rec scan_back i =
4254+
if i < 0 || name.[i] < '0' || name.[i] > '9' then i + 1
4255+
else scan_back (i - 1)
4256+
in
4257+
let start_of_digits = scan_back (len - 1) in
4258+
if start_of_digits > 0 && start_of_digits < len then
4259+
String.sub name 0 start_of_digits
4260+
else name
4261+
4262+
let find_arity_suggestion env function_name target_arity =
4263+
let base_name = strip_arity_suffix function_name in
4264+
let candidate =
4265+
if target_arity = 1 then base_name
4266+
else base_name ^ string_of_int target_arity
4267+
in
4268+
try
4269+
let path, desc = Env.lookup_value (Longident.parse candidate) env in
4270+
if Builtin_attributes.deprecated_of_attrs desc.val_attributes <> None then
4271+
None
4272+
else
4273+
let expanded_type = Ctype.expand_head env desc.val_type in
4274+
let actual_arity =
4275+
match Ctype.get_arity env expanded_type with
4276+
| Some arity -> arity
4277+
| None -> 0
4278+
in
4279+
if actual_arity = target_arity then Some (Printtyp.string_of_path path)
4280+
else None
4281+
with
4282+
| Not_found -> None
4283+
| _ -> None
4284+
42354285
open Format
42364286
let longident = Printtyp.longident
42374287
let super_report_unification_error = Printtyp.super_report_unification_error
@@ -4491,7 +4541,14 @@ let report_error env loc ppf error =
44914541
fprintf ppf
44924542
"Empty record literal {} should be type annotated or used in a record \
44934543
context."
4494-
| Uncurried_arity_mismatch (typ, arity, args, sargs) ->
4544+
| Uncurried_arity_mismatch
4545+
{
4546+
function_type = typ;
4547+
expected_arity = arity;
4548+
provided_arity = args;
4549+
provided_args = sargs;
4550+
function_name = function_name_opt;
4551+
} ->
44954552
(* We need:
44964553
- Any arg that's required but isn't passed
44974554
- Any arg that is passed but isn't in the fn definition (optional or labelled)
@@ -4600,6 +4657,26 @@ let report_error env loc ppf error =
46004657
(if args = 1 then "" else "s")
46014658
arity;
46024659
4660+
(* Add suggestions for functions with correct arity *)
4661+
(match function_name_opt with
4662+
| Some function_name -> (
4663+
let function_name_str =
4664+
let buffer = Buffer.create 16 in
4665+
let formatter = Format.formatter_of_buffer buffer in
4666+
Printtyp.longident formatter function_name;
4667+
Format.pp_print_flush formatter ();
4668+
Buffer.contents buffer
4669+
in
4670+
let suggestion = find_arity_suggestion env function_name_str args in
4671+
match suggestion with
4672+
| None -> () (* No suggestion found *)
4673+
| Some suggestion_str ->
4674+
fprintf ppf
4675+
"@,@,Hint: Try @{<info>%s@} instead (takes @{<info>%d@} argument%s)."
4676+
suggestion_str args
4677+
(if args = 1 then "" else "s"))
4678+
| None -> () (* Function name not available *));
4679+
46034680
fprintf ppf "@]"
46044681
| Field_not_optional (name, typ) ->
46054682
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
@@ -111,8 +111,13 @@ type error =
111111
| Unknown_literal of string * char
112112
| Illegal_letrec_pat
113113
| Empty_record_literal
114-
| Uncurried_arity_mismatch of
115-
type_expr * int * int * Asttypes.Noloc.arg_label list
114+
| Uncurried_arity_mismatch of {
115+
function_type: type_expr;
116+
expected_arity: int;
117+
provided_arity: int;
118+
provided_args: Asttypes.Noloc.arg_label list;
119+
function_name: Longident.t option;
120+
}
116121
| Field_not_optional of string * type_expr
117122
| Type_params_not_supported of Longident.t
118123
| Field_access_on_dict_type
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
7+
This function call is incorrect.
8+
The function has type:
9+
'a => unit
10+
11+
- The function takes just 1 unlabelled argument, but is called with 2
12+
13+
Hint: Try Console.log2 instead (takes 2 arguments).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
7+
This function call is incorrect.
8+
The function has type:
9+
(int, 'a) => unit
10+
11+
- The function takes 2 unlabelled arguments, but is called with just 1
12+
13+
Hint: Try Console.log instead (takes 1 argument).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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+
7+
This function call is incorrect.
8+
The function has type:
9+
(int, int, 'a, 'b) => unit
10+
11+
- The function takes 4 unlabelled arguments, but is called with just 2
12+
13+
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)