Skip to content

Commit 9afac86

Browse files
authored
Add support for functions in untagged variants. (#6279)
* Add support for functions in untagged variants. This was done at speed: need to double check that there are no corner cases missing. Fixes #6278 * test gentype * Add parens as required for TS case with function type. * Update CHANGELOG.md
1 parent 22e69d2 commit 9afac86

16 files changed

+92
-15
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#### :rocket: New Feature
1616

1717
- Introduced a new `%ffi` extension that provides a more robust mechanism for JavaScript function interoperation by considering function arity in type constraints. This enhancement improves safety when dealing with JavaScript functions by enforcing type constraints based on the arity of the function. [PR #6251](https://github.com/rescript-lang/rescript-compiler/pull/6251)
18+
- Extended untagged variants with function types https://github.com/rescript-lang/rescript-compiler/pull/6279
1819

1920
#### :bug: Bug Fix
2021

jscomp/core/js_exp_make.ml

+1
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,7 @@ let tag_type = function
769769
| Undefined -> undefined
770770
| Untagged IntType -> str "number"
771771
| Untagged FloatType -> str "number"
772+
| Untagged FunctionType -> str "function"
772773
| Untagged StringType -> str "string"
773774
| Untagged ArrayType -> str "Array" ~delim:DNoQuotes
774775
| Untagged ObjectType -> str "object"

jscomp/frontend/ast_core_type.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ let get_uncurry_arity (ty : t) =
125125
| _ -> None
126126

127127
let get_curry_arity (ty : t) =
128-
if Ast_uncurried.typeIsUncurriedFun ty then
128+
if Ast_uncurried.coreTypeIsUncurriedFun ty then
129129
let arity, _ = Ast_uncurried.typeExtractUncurriedFun ty in
130130
arity
131131
else get_uncurry_arity_aux ty 0

jscomp/frontend/ast_external_process.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ let spec_of_ptyp (nolabel : bool) (ptyp : Parsetree.core_type) :
6868
| _ -> Bs_syntaxerr.err ptyp.ptyp_loc Invalid_bs_unwrap_type)
6969
| `Uncurry opt_arity -> (
7070
let real_arity =
71-
if Ast_uncurried.typeIsUncurriedFun ptyp then
71+
if Ast_uncurried.coreTypeIsUncurriedFun ptyp then
7272
let arity, _ = Ast_uncurried.typeExtractUncurriedFun ptyp in
7373
Some arity
7474
else Ast_core_type.get_uncurry_arity ptyp

jscomp/gentype/EmitType.ml

+8-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,14 @@ let rec renderType ~(config : Config.t) ?(indent = None) ~typeNameIsInterface
184184
|> field ~name:(Runtime.jsVariantTag ~polymorphic:false)
185185
in
186186
match (unboxed, type_) with
187-
| true, type_ -> type_ |> render
187+
| true, type_ ->
188+
let needParens =
189+
match type_ with
190+
| Function _ -> true
191+
| _ -> false
192+
in
193+
let t = type_ |> render in
194+
if needParens then EmitText.parens [t] else t
188195
| false, type_ when polymorphic ->
189196
(* poly variant *)
190197
[

jscomp/gentype_tests/typescript-react-example/bsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"subdirs": true
2525
}
2626
],
27+
"uncurried": false,
2728
"package-specs": {
2829
"module": "es6",
2930
"in-source": true

jscomp/gentype_tests/typescript-react-example/package-lock.json

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jscomp/gentype_tests/typescript-react-example/src/Unboxed.gen.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ export type r1 = number;
1818
// tslint:disable-next-line:interface-over-type-literal
1919
export type r2 = string;
2020

21+
// tslint:disable-next-line:interface-over-type-literal
22+
export type t = number[] | number | ((_1:number) => number);
23+
2124
export const testV1: (x:v1) => v1 = UnboxedBS.testV1;
2225

2326
export const r2Test: (x:r2) => r2 = UnboxedBS.r2Test;

jscomp/gentype_tests/typescript-react-example/src/Unboxed.res

+3
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ type r1 = {x: int}
1313
type r2 = B({g: string})
1414

1515
@genType let r2Test = (x: r2) => x
16+
17+
@genType @unboxed
18+
type t = Array(array<int>) | Record({x:int}) | Function((. int) => int)

jscomp/ml/ast_uncurried.ml

+8-1
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,19 @@ let exprExtractUncurriedFun (expr : Parsetree.expression) =
6363
| Pexp_construct ({ txt = Lident "Function$" }, Some e) -> e
6464
| _ -> assert false
6565

66-
let typeIsUncurriedFun (typ : Parsetree.core_type) =
66+
let coreTypeIsUncurriedFun (typ : Parsetree.core_type) =
6767
match typ.ptyp_desc with
6868
| Ptyp_constr ({txt = Lident "function$"}, [{ptyp_desc = Ptyp_arrow _}; _]) ->
6969
true
7070
| _ -> false
7171

72+
let typeIsUncurriedFun (typ : Types.type_expr) =
73+
match typ.desc with
74+
| Tconstr (Pident {name = "function$"}, [{desc = Tarrow _}; _], _) ->
75+
true
76+
| _ -> false
77+
78+
7279
let typeExtractUncurriedFun (typ : Parsetree.core_type) =
7380
match typ.ptyp_desc with
7481
| Ptyp_constr ({txt = Lident "function$"}, [tArg; tArity]) ->

jscomp/ml/ast_untagged_variants.ml

+19-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type untaggedError = OnlyOneUnknown | AtMostOneObject | AtMostOneArray | AtMostOneString | AtMostOneNumber | DuplicateLiteral of string
1+
type untaggedError = OnlyOneUnknown | AtMostOneObject | AtMostOneArray | AtMostOneFunction | AtMostOneString | AtMostOneNumber | DuplicateLiteral of string
22
type error =
33
| InvalidVariantAsAnnotation
44
| Duplicated_bs_as
@@ -22,14 +22,15 @@ let report_error ppf =
2222
| OnlyOneUnknown -> "An unknown case must be the only case with payloads."
2323
| AtMostOneObject -> "At most one case can be an object type."
2424
| AtMostOneArray -> "At most one case can be an array type."
25+
| AtMostOneFunction -> "At most one case can be a function type."
2526
| AtMostOneString -> "At most one case can be a string type."
2627
| AtMostOneNumber -> "At most one case can be a number type (int or float)."
2728
| DuplicateLiteral s -> "Duplicate literal " ^ s ^ "."
2829
)
2930

3031
(* Type of the runtime representation of an untagged block (case with payoad) *)
3132
type block_type =
32-
| IntType | StringType | FloatType | ArrayType | ObjectType | UnknownType
33+
| IntType | StringType | FloatType | ArrayType | FunctionType | ObjectType | UnknownType
3334

3435
(*
3536
Type of the runtime representation of a tag.
@@ -116,6 +117,10 @@ let get_block_type ~env (cstr: Types.constructor_declaration) : block_type optio
116117
Some FloatType
117118
| true, Cstr_tuple [{desc = Tconstr (path, _, _)}] when Path.same path Predef.path_array ->
118119
Some ArrayType
120+
| true, Cstr_tuple [{desc = Tconstr _} as t] when Ast_uncurried.typeIsUncurriedFun t ->
121+
Some FunctionType
122+
| true, Cstr_tuple [{desc = Tarrow _} ] ->
123+
Some FunctionType
119124
| true, Cstr_tuple [{desc = Tconstr (path, _, _)}] when Path. same path Predef.path_string ->
120125
Some StringType
121126
| true, Cstr_tuple [{desc = Tconstr _} as t] when type_is_builtin_object t ->
@@ -162,6 +167,7 @@ let checkInvariant ~isUntaggedDef ~(consts : (Location.t * tag) list) ~(blocks :
162167
let string_literals = ref StringSet.empty in
163168
let nonstring_literals = ref StringSet.empty in
164169
let arrayTypes = ref 0 in
170+
let functionTypes = ref 0 in
165171
let objectTypes = ref 0 in
166172
let stringTypes = ref 0 in
167173
let numberTypes = ref 0 in
@@ -181,6 +187,8 @@ let checkInvariant ~isUntaggedDef ~(consts : (Location.t * tag) list) ~(blocks :
181187
then raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneObject));
182188
if !arrayTypes > 1
183189
then raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneArray));
190+
if !functionTypes > 1
191+
then raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneFunction));
184192
if !stringTypes > 1
185193
then raise (Error (loc, InvalidUntaggedVariantDefinition AtMostOneString));
186194
if !numberTypes > 1
@@ -214,6 +222,9 @@ let checkInvariant ~isUntaggedDef ~(consts : (Location.t * tag) list) ~(blocks :
214222
| Some ArrayType ->
215223
incr arrayTypes;
216224
invariant loc
225+
| Some FunctionType ->
226+
incr functionTypes;
227+
invariant loc
217228
| Some (IntType | FloatType) ->
218229
incr numberTypes;
219230
invariant loc
@@ -266,6 +277,8 @@ module DynamicChecks = struct
266277
let nil = Null |> tag_type
267278
let undefined = Undefined |> tag_type
268279
let object_ = Untagged ObjectType |> tag_type
280+
281+
let function_ = Untagged FunctionType |> tag_type
269282
let string = Untagged StringType |> tag_type
270283
let number = Untagged IntType |> tag_type
271284

@@ -298,6 +311,8 @@ module DynamicChecks = struct
298311
typeof e != number
299312
| ArrayType ->
300313
not (is_array e)
314+
| FunctionType ->
315+
typeof e != function_
301316
| ObjectType when literals_overlaps_with_object () = false ->
302317
typeof e != object_
303318
| ObjectType (* overlap *) ->
@@ -341,9 +356,8 @@ module DynamicChecks = struct
341356
let add_runtime_type_check ~tag_type ~(block_cases: block_type list) x y =
342357
let has_array() = Ext_list.exists block_cases (fun t -> t = ArrayType) in
343358
match tag_type with
344-
| Untagged IntType
345-
| Untagged StringType
346-
| Untagged FloatType -> typeof y == x
359+
| Untagged (IntType | StringType | FloatType | FunctionType) ->
360+
typeof y == x
347361
| Untagged ObjectType ->
348362
if has_array() then
349363
typeof y == x &&& not (is_array y)

jscomp/syntax/src/react_jsx_common.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ let raiseErrorMultipleReactComponent ~loc =
4545
let optionalAttr = ({txt = "res.optional"; loc = Location.none}, PStr [])
4646

4747
let extractUncurried typ =
48-
if Ast_uncurried.typeIsUncurriedFun typ then
48+
if Ast_uncurried.coreTypeIsUncurriedFun typ then
4949
let _arity, t = Ast_uncurried.typeExtractUncurriedFun typ in
5050
t
5151
else typ

jscomp/syntax/src/res_parens.ml

+1-1
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ let includeModExpr modExpr =
442442
let arrowReturnTypExpr typExpr =
443443
match typExpr.Parsetree.ptyp_desc with
444444
| Parsetree.Ptyp_arrow _ -> true
445-
| _ when Ast_uncurried.typeIsUncurriedFun typExpr -> true
445+
| _ when Ast_uncurried.coreTypeIsUncurriedFun typExpr -> true
446446
| _ -> false
447447

448448
let patternRecordRowRhs (pattern : Parsetree.pattern) =

jscomp/syntax/src/res_printer.ml

+3-3
Original file line numberDiff line numberDiff line change
@@ -1591,7 +1591,7 @@ and printTypExpr ~(state : State.t) (typExpr : Parsetree.core_type) cmtTbl =
15911591
let doc = printTypExpr ~state n cmtTbl in
15921592
match n.ptyp_desc with
15931593
| Ptyp_arrow _ | Ptyp_tuple _ | Ptyp_alias _ -> addParens doc
1594-
| _ when Ast_uncurried.typeIsUncurriedFun n -> addParens doc
1594+
| _ when Ast_uncurried.coreTypeIsUncurriedFun n -> addParens doc
15951595
| _ -> doc
15961596
in
15971597
Doc.group
@@ -1652,7 +1652,7 @@ and printTypExpr ~(state : State.t) (typExpr : Parsetree.core_type) cmtTbl =
16521652
let needsParens =
16531653
match typ.ptyp_desc with
16541654
| Ptyp_arrow _ -> true
1655-
| _ when Ast_uncurried.typeIsUncurriedFun typ -> true
1655+
| _ when Ast_uncurried.coreTypeIsUncurriedFun typ -> true
16561656
| _ -> false
16571657
in
16581658
let doc = printTypExpr ~state typ cmtTbl in
@@ -1664,7 +1664,7 @@ and printTypExpr ~(state : State.t) (typExpr : Parsetree.core_type) cmtTbl =
16641664
| Ptyp_object (fields, openFlag) ->
16651665
printObject ~state ~inline:false fields openFlag cmtTbl
16661666
| Ptyp_arrow _ -> printArrow ~uncurried:false typExpr
1667-
| Ptyp_constr _ when Ast_uncurried.typeIsUncurriedFun typExpr ->
1667+
| Ptyp_constr _ when Ast_uncurried.coreTypeIsUncurriedFun typExpr ->
16681668
let arity, tArg = Ast_uncurried.typeExtractUncurriedFun typExpr in
16691669
printArrow ~uncurried:true ~arity tArg
16701670
| Ptyp_constr (longidentLoc, [{ptyp_desc = Ptyp_object (fields, openFlag)}])

jscomp/test/UntaggedVariants.js

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jscomp/test/UntaggedVariants.res

+14
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,17 @@ module OptionUnboxingHeuristic = {
293293
type untaggedInlineMultinaryOption = A | B({x: option<int>, y?: string})
294294
let untaggedInlineMultinaryOption = (x: untaggedInlineMultinaryOption) => Some(x)
295295
}
296+
297+
module TestFunctionCase = {
298+
@unboxed
299+
type t = Array(array<int>) | Record({x:int}) | Function((. int) => int)
300+
301+
let classify = v =>
302+
switch v {
303+
| Record({x}) => x
304+
| Array(a) => a[0]
305+
| Function(f) => f(. 3)
306+
}
307+
308+
let ff = Function((. x) => x+1)
309+
}

0 commit comments

Comments
 (0)