Skip to content

Commit 26a098d

Browse files
committed
Process uncurried application in the type checker.
- Do the processing of uncurried application in the type checker. - Add support for default arguments in uncurried functions. - Add custom error messages for uncurried application. - Uncurried pipe processing does not require special handling. The current encoding is based on arity. To support default arguments one cannot just look at the number of supplied arguments, but to know whether the required arguments are supplied one needs to inspect the function type.
1 parent 9fa2b3c commit 26a098d

16 files changed

+406
-340
lines changed

jscomp/build_tests/super_errors/expected/arity_mismatch.res.expected

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
2 │ let makeVariables = makeVar(.~f=f => f)
77
3 │
88

9-
This function expected 2 arguments, but got 1
9+
This uncurried function has type (. ~f: 'a => 'a, unit) => int
10+
It is applied with 1 arguments but it requires 2.

jscomp/build_tests/super_errors/expected/arity_mismatch2.res.expected

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
2 │ let makeVariables = makeVar(. 1, 2, 3)
77
3 │
88

9-
This function expected 2 arguments, but got 3
9+
This uncurried function has type (. 'a, unit) => int
10+
It is applied with 3 arguments but it requires 2.

jscomp/build_tests/super_errors/expected/method_arity_mismatch.res.expected

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@
88
4 │ }
99
5 │
1010

11-
This function expected 2 arguments, but got 1
11+
This uncurried function has type (. int, int) => unit
12+
It is applied with 1 arguments but it requires 2.

jscomp/build_tests/super_errors/expected/warnings4.res.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
14 │
1111

1212
You forgot to handle a possible case here, for example:
13-
#second(_) | #fourth | #third
13+
#second(_) | #fourth | #third

jscomp/build_tests/super_errors/expected/warnings5.res.expected

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,4 @@ Either bind these labels explicitly or add ', _' to the pattern.
187187
60 │
188188

189189
You forgot to handle a possible case here, for example:
190-
(_, true)
190+
(_, true)

jscomp/frontend/ast_attributes.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,7 @@ let locg = Location.none
341341
let is_bs (attr : attr) =
342342
match attr with { Location.txt = "bs"; _ }, _ -> true | _ -> false
343343

344-
let is_res_uapp (attr : attr) =
345-
match attr with { Location.txt = "res.uapp"; _ }, _ -> true | _ -> false
344+
let res_uapp : attr = ({ txt = "res.uapp"; loc = locg }, Ast_payload.empty)
346345

347346
let bs_get : attr = ({ txt = "bs.get"; loc = locg }, Ast_payload.empty)
348347

jscomp/frontend/ast_attributes.mli

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ val is_bs : attr -> bool
7373
val is_bs_as : attr -> bool *)
7474

7575
(* Attribute for uncurried application coming from the ReScript parser *)
76-
val is_res_uapp : attr -> bool
76+
val res_uapp : attr
7777

7878
val bs_get : attr
7979

jscomp/frontend/ast_exp_apply.ml

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ let app_exp_mapper (e : exp) (self : Bs_ast_mapper.mapper) (fn : exp)
124124
{
125125
pexp_desc = Pexp_apply (fn1, (Nolabel, a) :: args);
126126
pexp_loc = e.pexp_loc;
127-
pexp_attributes = e.pexp_attributes;
127+
pexp_attributes = e.pexp_attributes @ f.pexp_attributes;
128128
}
129129
| Pexp_tuple xs ->
130130
bound a (fun bounded_obj_arg ->
@@ -158,38 +158,16 @@ let app_exp_mapper (e : exp) (self : Bs_ast_mapper.mapper) (fn : exp)
158158
})
159159
| _ -> (
160160
match
161-
( Ext_list.exclude_with_val f_.pexp_attributes (fun a ->
162-
Ast_attributes.is_bs a
163-
|| Ast_attributes.is_res_uapp a),
161+
( Ext_list.exclude_with_val f_.pexp_attributes
162+
Ast_attributes.is_bs,
164163
f_.pexp_desc )
165164
with
166-
| Some other_attributes, Pexp_apply (fn1, args) ->
167-
(* a |. f b c [@bs]
168-
Cannot process uncurried application early as the arity is wip *)
169-
let fn1 = self.expr self fn1 in
170-
let args =
171-
args |> List.map (fun (l, e) -> (l, self.expr self e))
172-
in
173-
Bs_ast_invariant.warn_discarded_unused_attributes
174-
fn1.pexp_attributes;
175-
{
176-
pexp_desc =
177-
Ast_uncurry_apply.uncurry_fn_apply ~arity0:(op = "|.")
178-
e.pexp_loc self fn1 ((Nolabel, a) :: args);
179-
pexp_loc = e.pexp_loc;
180-
pexp_attributes = e.pexp_attributes @ other_attributes;
181-
}
182165
| _ when op = "|.u" ->
183166
(* a |.u f
184167
Uncurried unary application *)
185-
{
186-
pexp_desc =
187-
Ast_uncurry_apply.uncurry_fn_apply ~arity0:false
188-
e.pexp_loc self f
189-
[ (Nolabel, a) ];
190-
pexp_loc = e.pexp_loc;
191-
pexp_attributes = e.pexp_attributes;
192-
}
168+
Ast_compatible.app1 ~loc
169+
~attrs:(Ast_attributes.res_uapp :: e.pexp_attributes)
170+
f a
193171
| _ -> Ast_compatible.app1 ~loc ~attrs:e.pexp_attributes f a))
194172
| Some { op = "##"; loc; args = [ obj; rest ] } -> (
195173
(* - obj##property
@@ -289,21 +267,7 @@ let app_exp_mapper (e : exp) (self : Bs_ast_mapper.mapper) (fn : exp)
289267
{
290268
e with
291269
pexp_desc =
292-
Ast_uncurry_apply.uncurry_fn_apply ~arity0:true e.pexp_loc
293-
self fn args;
270+
Ast_uncurry_apply.uncurry_fn_apply e.pexp_loc self fn args;
294271
pexp_attributes;
295272
}
296-
| None -> (
297-
match
298-
Ext_list.exclude_with_val e.pexp_attributes
299-
Ast_attributes.is_res_uapp
300-
with
301-
| Some pexp_attributes ->
302-
{
303-
e with
304-
pexp_desc =
305-
Ast_uncurry_apply.uncurry_fn_apply ~arity0:false
306-
e.pexp_loc self fn args;
307-
pexp_attributes;
308-
}
309-
| None -> default_expr_mapper self e)))
273+
| None -> default_expr_mapper self e))

jscomp/frontend/ast_uncurry_apply.ml

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ let opaque_full_apply ~loc (e : exp) : Parsetree.expression_desc =
4444
[ (Nolabel, e) ],
4545
Typ.any ~loc () )
4646

47-
let generic_apply ~arity0 loc (self : Bs_ast_mapper.mapper)
48-
(obj : Parsetree.expression) (args : Ast_compatible.args)
49-
(cb : loc -> exp -> exp) =
47+
let generic_apply loc (self : Bs_ast_mapper.mapper) (obj : Parsetree.expression)
48+
(args : Ast_compatible.args) (cb : loc -> exp -> exp) =
5049
let obj = self.expr self obj in
5150
let args =
5251
Ext_list.map args (fun (lbl, e) ->
@@ -58,8 +57,7 @@ let generic_apply ~arity0 loc (self : Bs_ast_mapper.mapper)
5857
match args with
5958
| [
6059
(Nolabel, { pexp_desc = Pexp_construct ({ txt = Lident "()" }, None) });
61-
]
62-
when arity0 ->
60+
] ->
6361
[]
6462
| _ -> args
6563
in
@@ -130,9 +128,9 @@ let method_apply loc (self : Bs_ast_mapper.mapper) (obj : Parsetree.expression)
130128
])
131129
args)
132130

133-
let uncurry_fn_apply ~arity0 loc self fn args =
134-
generic_apply ~arity0 loc self fn args (fun _ obj -> obj)
131+
let uncurry_fn_apply loc self fn args =
132+
generic_apply loc self fn args (fun _ obj -> obj)
135133

136134
let property_apply loc self obj name args =
137-
generic_apply ~arity0:true loc self obj args (fun loc obj ->
135+
generic_apply loc self obj args (fun loc obj ->
138136
Exp.send ~loc obj { txt = name; loc })

jscomp/frontend/ast_uncurry_apply.mli

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
(* TODO: the interface is not reusable, it depends on too much context *)
2626

2727
val uncurry_fn_apply :
28-
arity0:bool ->
2928
Location.t ->
3029
Bs_ast_mapper.mapper ->
3130
Parsetree.expression ->

jscomp/ml/typecore.ml

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ type error =
7373
| Illegal_letrec_pat
7474
| Labels_omitted of string list
7575
| Empty_record_literal
76+
| Uncurried_arity_mismatch of type_expr * int * int * bool
7677
exception Error of Location.t * Env.t * error
7778
exception Error_forward of Location.error
7879

@@ -2015,15 +2016,35 @@ and type_expect_ ?in_function ?(recarg=Rejected) env sexp ty_expected =
20152016
end_def ();
20162017
wrap_trace_gadt_instances env (lower_args env []) ty;
20172018
begin_def ();
2018-
let (args, ty_res) = type_application env funct sargs in
2019+
let uncurried = Ext_list.exists sexp.pexp_attributes (fun ({txt },_) -> txt = "res.uapp") in
2020+
let (args, ty_res, fully_applied) = type_application uncurried env funct sargs in
20192021
end_def ();
20202022
unify_var env (newvar()) funct.exp_type;
2021-
rue {
2022-
exp_desc = Texp_apply(funct, args);
2023-
exp_loc = loc; exp_extra = [];
2024-
exp_type = ty_res;
2025-
exp_attributes = sexp.pexp_attributes;
2026-
exp_env = env }
2023+
2024+
let mk_exp exp_desc exp_type =
2025+
{ exp_desc;
2026+
exp_loc = Location.none; exp_extra = [];
2027+
exp_type;
2028+
exp_attributes = [];
2029+
exp_env = env } in
2030+
let apply_internal name e =
2031+
let lid:Longident.t = Ldot (Ldot (Lident "Js", "Internal"), name) in
2032+
let (path, desc) = Env.lookup_value lid env in
2033+
let id = mk_exp (Texp_ident(path, {txt=lid; loc=Location.none}, desc)) desc.val_type in
2034+
mk_exp (Texp_apply(id, [(Nolabel, Some e)])) e.exp_type in
2035+
2036+
let mk_apply funct args =
2037+
rue {
2038+
exp_desc = Texp_apply(funct, args);
2039+
exp_loc = loc; exp_extra = [];
2040+
exp_type = ty_res;
2041+
exp_attributes = sexp.pexp_attributes;
2042+
exp_env = env } in
2043+
2044+
if fully_applied then
2045+
rue (apply_internal "opaqueFullApply" (mk_apply (apply_internal "opaque" funct) args))
2046+
else
2047+
rue (mk_apply funct args)
20272048
| Pexp_match(sarg, caselist) ->
20282049
begin_def ();
20292050
let arg = type_exp env sarg in
@@ -2939,7 +2960,7 @@ and type_argument ?recarg env sarg ty_expected' ty_expected =
29392960
unify_exp env texp ty_expected;
29402961
texp
29412962

2942-
and type_application env funct (sargs : sargs) : targs * Types.type_expr =
2963+
and type_application uncurried env funct (sargs : sargs) : targs * Types.type_expr * bool =
29432964
(* funct.exp_type may be generic *)
29442965
let result_type omitted ty_fun =
29452966
List.fold_left
@@ -2951,25 +2972,49 @@ and type_application env funct (sargs : sargs) : targs * Types.type_expr =
29512972
tvar || List.mem l ls
29522973
in
29532974
let ignored = ref [] in
2954-
let extract_uncurried_type t =
2975+
let mk_js_fn arity t =
2976+
let a = "arity" ^ string_of_int arity in
2977+
let lid:Longident.t = Ldot (Ldot (Lident "Js", "Fn"), a) in
2978+
let path = Env.lookup_type lid env in
2979+
newconstr path [t] in
2980+
let has_uncurried_type t =
29552981
match (expand_head env t).desc with
2956-
| Tconstr (Pdot (Pdot(Pident {name = "Js"},"Fn",_),_,_),[t],_) -> t
2957-
| _ -> t in
2958-
let lower_uncurried_arity ~nargs t newT =
2959-
match (expand_head env t).desc with
2960-
| Tconstr (Pdot (Pdot(Pident {name = "Js"},"Fn",_),a,_),[_],_) ->
2982+
| Tconstr (Pdot (Pdot(Pident {name = "Js"},"Fn",_),a,_),[t],_) ->
29612983
let arity =
29622984
if String.sub a 0 5 = "arity"
29632985
then int_of_string (String.sub a 5 (String.length a - 5))
29642986
else 0 in
2987+
Some (arity, t)
2988+
| _ -> None in
2989+
let force_uncurried_type funct =
2990+
match has_uncurried_type funct.exp_type with
2991+
| None ->
2992+
let arity = List.length sargs in
2993+
let js_fn = mk_js_fn arity (newvar()) in
2994+
unify_exp env funct js_fn
2995+
| Some _ -> () in
2996+
let extract_uncurried_type t =
2997+
match has_uncurried_type t with
2998+
| Some (arity, t1) ->
2999+
if List.length sargs > arity then
3000+
raise(Error(funct.exp_loc, env,
3001+
Uncurried_arity_mismatch (t, arity, List.length sargs, false)));
3002+
t1
3003+
| None -> t in
3004+
let update_uncurried_arity ~nargs t newT =
3005+
match has_uncurried_type t with
3006+
| Some (arity, _) ->
29653007
let newarity = arity - nargs in
2966-
if newarity > 0 then
2967-
let a = "arity" ^ string_of_int newarity in
2968-
let lid:Longident.t = Ldot (Ldot (Lident "Js", "Fn"), a) in
2969-
let path = Env.lookup_type lid env in
2970-
newconstr path [newT]
2971-
else newT
2972-
| _ -> newT
3008+
if newarity < 0 then
3009+
raise(Error(funct.exp_loc, env,
3010+
Uncurried_arity_mismatch (t, arity, List.length sargs, true)));
3011+
let fully_applied = newarity = 0 in
3012+
if uncurried && not fully_applied then
3013+
raise(Error(funct.exp_loc, env,
3014+
Uncurried_arity_mismatch (t, arity, List.length sargs, false)));
3015+
let newT = if fully_applied then newT else mk_js_fn newarity newT in
3016+
(fully_applied, newT)
3017+
| _ -> (false, newT)
29733018
in
29743019
let rec type_unknown_args (args : lazy_args) omitted ty_fun (syntax_args : sargs)
29753020
: targs * _ =
@@ -3078,14 +3123,14 @@ and type_application env funct (sargs : sargs) : targs * Types.type_expr =
30783123
Delayed_checks.add_delayed_check (fun () -> check_application_result env false exp)
30793124
| _ -> ()
30803125
end;
3081-
([Nolabel, Some exp], ty_res)
3126+
([Nolabel, Some exp], ty_res, false)
30823127
| _ ->
3128+
if uncurried then force_uncurried_type funct;
30833129
let ty = extract_uncurried_type funct.exp_type in
30843130
let targs, ret_t = type_args [] [] ~ty_fun:ty (instance env ty) ~sargs in
3085-
let ret_t =
3086-
if funct.exp_type == ty then ret_t
3087-
else lower_uncurried_arity funct.exp_type ~nargs:(List.length !ignored + List.length sargs) ret_t in
3088-
targs, ret_t
3131+
let fully_applied, ret_t =
3132+
update_uncurried_arity funct.exp_type ~nargs:(List.length !ignored + List.length sargs) ret_t in
3133+
targs, ret_t, fully_applied
30893134

30903135
and type_construct env loc lid sarg ty_expected attrs =
30913136
let opath =
@@ -3821,6 +3866,16 @@ let report_error env ppf = function
38213866
(String.concat ", " labels)
38223867
| Empty_record_literal ->
38233868
fprintf ppf "Empty record literal {} should be type annotated or used in a record context."
3869+
| Uncurried_arity_mismatch (typ, arity, args, false (* no partial application *)) ->
3870+
fprintf ppf "@[<v>@[<2>This uncurried function has type@ %a@]"
3871+
type_expr typ;
3872+
fprintf ppf "@ @[It is applied with @{<error>%d@} argument%s but it requires @{<info>%d@}.@]@]"
3873+
args (if args = 0 then "" else "s") arity
3874+
| Uncurried_arity_mismatch (typ, _, _, true (* partial application *)) ->
3875+
fprintf ppf "@[<v>@[<2>This uncurried function has type@ %a@]"
3876+
type_expr typ;
3877+
fprintf ppf "@ @[It is partially applied with too many arguments.@]@]"
3878+
38243879
38253880
let super_report_error_no_wrap_printing_env = report_error
38263881

jscomp/ml/typecore.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ type error =
109109
| Illegal_letrec_pat
110110
| Labels_omitted of string list
111111
| Empty_record_literal
112+
| Uncurried_arity_mismatch of type_expr * int * int * bool
112113
exception Error of Location.t * Env.t * error
113114
exception Error_forward of Location.error
114115

jscomp/test/uncurried_cast.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ var Uncurried = {
2222
var E = /* @__PURE__ */Caml_exceptions.create("Uncurried_cast.E");
2323

2424
function testRaise(param) {
25-
throw {
26-
RE_EXN_ID: E,
27-
Error: new Error()
28-
};
25+
return raise({
26+
RE_EXN_ID: E
27+
});
2928
}
3029

3130
var l = map({
@@ -50,9 +49,9 @@ function partial(param) {
5049
return map(partial_arg, param);
5150
}
5251

53-
var ll = Curry._1(partial, (function (x) {
54-
return x + 1 | 0;
55-
}));
52+
var ll = partial(function (x) {
53+
return x + 1 | 0;
54+
});
5655

5756
function withOpts(xOpt, y, zOpt, w) {
5857
var x = xOpt !== undefined ? xOpt : 3;

0 commit comments

Comments
 (0)