Skip to content

Commit 01eb614

Browse files
authored
Support optional named arguments without a final unit in uncurried functions. (#5907)
* Explore default arguments in uncurried functions without a final unit argument Functions such as this one ```res let foo1 = (~x=3, ~y) => x+y ``` normally require a final unit unlabeled argument to indicate that the optional argument `x` is not passed. In this example, writing ```res let r1 = foo1(~y=11) ``` makes `r1` a function that is still expecting the `x` argument. Requiring a final unit argument, and passing it as `foo1(~y=11, ())` is the only way to omit `x` in practice. With uncurried functions, there's the opportunity to treat `foo1(~y=11)` in a special way, meaning that argument `x` is omitted. TODO: - Figure out what to do with the warning ""This optional parameter in final position will, in practice, not be optional" - Figure out the case of all optional arguments. There's no way to pass zero arguments. One could interpret `foo()`, which actually passes `()` as single unlabelled argument, in a special way when `foo` does not accept unlabelled arguments, and use it to mean zero arguments passed. * Disable Unerasable_optional_argument for uncurried functions via the type checker. In reality this disables also the check for the body, but this check is on the way out so this should be reasonable in practice without over-complicating the error logic. * Treat `foo(. )` as empty application if all arguments are optional. When all the processed arguments to the function are ignored (hence optional), and no arguments are ignored (so no mandatory labelled), it means the uncurried function only has optional arguments. - If the uncurried type of the function had an unlabelled arg, then it would be caught by the unit application, so it would be a non-ignored argument. But this is not possible as all the argiments are ignored. - If it had a labelled mandatory argument, then either it would be non-ignored, or omitted, but both cases are excluded. - It follows that the type only has optional arguments, and that the unit argument was the only argument supplied. The new mechanism does not kick in when some legit argument is passed alongside the unit. And noes not interfere with cases where the function expects a legitimate unit argument. * Update CHANGELOG.md
1 parent a11ceea commit 01eb614

File tree

4 files changed

+116
-16
lines changed

4 files changed

+116
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ subset of the arguments, and return a curried type with the remaining ones https
2424
- Add support for default arguments in uncurried functions https://github.com/rescript-lang/rescript-compiler/pull/5835
2525
- Inline uncurried application when it is safe https://github.com/rescript-lang/rescript-compiler/pull/5847
2626
- Add support for toplevel `await` https://github.com/rescript-lang/rescript-compiler/pull/5940
27+
- Support optional named arguments without a final unit in uncurried functions https://github.com/rescript-lang/rescript-compiler/pull/5907
2728

2829
#### :boom: Breaking Change
2930

jscomp/ml/typecore.ml

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,13 +2103,18 @@ and type_expect_ ?in_function ?(recarg=Rejected) env sexp ty_expected =
21032103
exp_type = newty (Ttuple (List.map (fun e -> e.exp_type) expl));
21042104
exp_attributes = sexp.pexp_attributes;
21052105
exp_env = env }
2106+
| Pexp_construct({txt = Lident "Function$"} as lid, sarg) ->
2107+
let state = Warnings.backup () in
2108+
let arity = Ast_uncurried.attributes_to_arity sexp.pexp_attributes in
2109+
let uncurried_typ = Ast_uncurried.make_uncurried_type ~env ~arity (newvar()) in
2110+
unify_exp_types loc env ty_expected uncurried_typ;
2111+
(* Disable Unerasable_optional_argument for uncurried functions *)
2112+
let unerasable_optional_argument = Warnings.number Unerasable_optional_argument in
2113+
Warnings.parse_options false ("-" ^ string_of_int unerasable_optional_argument);
2114+
let exp = type_construct env loc lid sarg ty_expected sexp.pexp_attributes in
2115+
Warnings.restore state;
2116+
exp
21062117
| Pexp_construct(lid, sarg) ->
2107-
(match lid.txt with
2108-
| Lident "Function$" ->
2109-
let arity = Ast_uncurried.attributes_to_arity sexp.pexp_attributes in
2110-
let uncurried_typ = Ast_uncurried.make_uncurried_type ~env ~arity (newvar()) in
2111-
unify_exp_types loc env ty_expected uncurried_typ
2112-
| _ -> ());
21132118
type_construct env loc lid sarg ty_expected sexp.pexp_attributes
21142119
| Pexp_variant(l, sarg) ->
21152120
(* Keep sharing *)
@@ -3014,7 +3019,7 @@ and type_application uncurried env funct (sargs : sargs) : targs * Types.type_ex
30143019
match has_uncurried_type t with
30153020
| Some (arity, _) ->
30163021
let newarity = arity - nargs in
3017-
let fully_applied = newarity = 0 in
3022+
let fully_applied = newarity <= 0 in
30183023
if uncurried && not fully_applied then
30193024
raise(Error(funct.exp_loc, env,
30203025
Uncurried_arity_mismatch (t, arity, List.length sargs)));
@@ -3024,13 +3029,27 @@ and type_application uncurried env funct (sargs : sargs) : targs * Types.type_ex
30243029
in
30253030
let rec type_unknown_args max_arity (args : lazy_args) omitted ty_fun (syntax_args : sargs)
30263031
: targs * _ =
3027-
match syntax_args with
3028-
| [] ->
3029-
(List.map
3030-
(function l, None -> l, None
3031-
| l, Some f -> l, Some (f ()))
3032-
(List.rev args),
3033-
instance env (result_type omitted ty_fun))
3032+
match syntax_args with
3033+
| [] ->
3034+
let collect_args () =
3035+
(List.map
3036+
(function l, None -> l, None
3037+
| l, Some f -> l, Some (f ()))
3038+
(List.rev args),
3039+
instance env (result_type omitted ty_fun)) in
3040+
if List.length args < max_arity && uncurried then
3041+
(match (expand_head env ty_fun).desc with
3042+
| Tarrow (Optional l,t1,t2,_) ->
3043+
ignored := (Optional l,t1,ty_fun.level) :: !ignored;
3044+
let arg = Optional l, Some (fun () -> option_none (instance env t1) Location.none) in
3045+
type_unknown_args max_arity (arg::args) omitted t2 []
3046+
| _ -> collect_args ())
3047+
else
3048+
collect_args ()
3049+
| [(Nolabel, {pexp_desc = Pexp_construct ({txt = Lident "()"}, None)})]
3050+
when uncurried && omitted = [] && List.length args = List.length !ignored ->
3051+
(* foo(. ) treated as empty application if all args are optional (hence ignored) *)
3052+
type_unknown_args max_arity args omitted ty_fun []
30343053
| (l1, sarg1) :: sargl ->
30353054
let (ty1, ty2) =
30363055
let ty_fun = expand_head env ty_fun in
@@ -3082,7 +3101,7 @@ and type_application uncurried env funct (sargs : sargs) : targs * Types.type_ex
30823101
let sargs, omitted, arg =
30833102
match extract_label name sargs with
30843103
| None ->
3085-
if optional && label_assoc Nolabel sargs
3104+
if optional && (uncurried || label_assoc Nolabel sargs)
30863105
then begin
30873106
ignored := (l,ty,lv) :: !ignored;
30883107
sargs, omitted , Some (fun () -> option_none (instance env ty) Location.none)

jscomp/test/uncurried_default.args.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,42 @@ var partial = Curry._1((function (param) {
2020

2121
var total = withOpt(10, 3)(4, 11);
2222

23+
function foo1(xOpt, y) {
24+
var x = xOpt !== undefined ? xOpt : 3;
25+
return x + y | 0;
26+
}
27+
28+
var x = 3;
29+
30+
var r1 = x + 11 | 0;
31+
32+
function foo2(y, xOpt, zOpt) {
33+
var x = xOpt !== undefined ? xOpt : 3;
34+
var z = zOpt !== undefined ? zOpt : 4;
35+
return (x + y | 0) + z | 0;
36+
}
37+
38+
var r2 = foo2(11, undefined, undefined);
39+
40+
function foo3(xOpt, yOpt) {
41+
var x = xOpt !== undefined ? xOpt : 3;
42+
var y = yOpt !== undefined ? yOpt : 4;
43+
return x + y | 0;
44+
}
45+
46+
var r3 = foo3(undefined, undefined);
47+
2348
var StandardNotation = {
2449
withOpt: withOpt,
2550
testWithOpt: testWithOpt,
2651
partial: partial,
27-
total: total
52+
total: total,
53+
foo1: foo1,
54+
r1: r1,
55+
foo2: foo2,
56+
r2: r2,
57+
foo3: foo3,
58+
r3: r3
2859
};
2960

3061
function withOpt$1(xOpt, y) {
@@ -45,9 +76,40 @@ var partial$1 = Curry._1((function (param) {
4576

4677
var total$1 = withOpt$1(10, 3)(4, 11);
4778

79+
function foo1$1(xOpt, y) {
80+
var x = xOpt !== undefined ? xOpt : 3;
81+
return x + y | 0;
82+
}
83+
84+
var x$1 = 3;
85+
86+
var r1$1 = x$1 + 11 | 0;
87+
88+
function foo2$1(y, xOpt, zOpt) {
89+
var x = xOpt !== undefined ? xOpt : 3;
90+
var z = zOpt !== undefined ? zOpt : 4;
91+
return (x + y | 0) + z | 0;
92+
}
93+
94+
var r2$1 = foo2$1(11, undefined, undefined);
95+
96+
function foo3$1(xOpt, yOpt) {
97+
var x = xOpt !== undefined ? xOpt : 3;
98+
var y = yOpt !== undefined ? yOpt : 4;
99+
return x + y | 0;
100+
}
101+
102+
var r3$1 = foo3$1(undefined, undefined);
103+
48104
exports.StandardNotation = StandardNotation;
49105
exports.withOpt = withOpt$1;
50106
exports.testWithOpt = testWithOpt$1;
51107
exports.partial = partial$1;
52108
exports.total = total$1;
109+
exports.foo1 = foo1$1;
110+
exports.r1 = r1$1;
111+
exports.foo2 = foo2$1;
112+
exports.r2 = r2$1;
113+
exports.foo3 = foo3$1;
114+
exports.r3 = r3$1;
53115
/* testWithOpt Not a pure module */

jscomp/test/uncurried_default.args.res

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ module StandardNotation = {
33
let testWithOpt = withOpt(. 3)(. 4)
44
let partial = withOpt(~x=10)(3)(~z=4)(11)
55
let total = withOpt(. ~x=10, 3)(. ~z=4, 11)
6+
7+
let foo1 = (. ~x=3, ~y) => x+y
8+
let r1 = foo1(. ~y=11)
9+
10+
let foo2 = (. ~y, ~x=3, ~z=4) => x+y+z
11+
let r2 = foo2(. ~y=11)
12+
13+
let foo3 = (. ~x=3, ~y=4) => x+y
14+
let r3 = foo3(. )
615
}
716

817
@@uncurried
@@ -13,3 +22,12 @@ let withOpt = (~x=1, y) => (~z=1, w) => x+y+z+w
1322
let testWithOpt = withOpt(3)(4)
1423
let partial = withOpt(. ~x=10)(. 3)(. ~z=4)(. 11)
1524
let total = withOpt(~x=10, 3)(~z=4, 11)
25+
26+
let foo1 = (~x=3, ~y) => x+y
27+
let r1 = foo1(~y=11)
28+
29+
let foo2 = (~y, ~x=3, ~z=4) => x+y+z
30+
let r2 = foo2(~y=11)
31+
32+
let foo3 = (~x=3, ~y=4) => x+y
33+
let r3 = foo3()

0 commit comments

Comments
 (0)