Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

Commit 900ac0d

Browse files
glennslcristianoc
authored andcommitted
jsx: allow locally abstract types and type constraints on @react.component
* jsx: allow locally abstract types in @react.component definitions * jsx: allow type constraints on @react.componenet definitions
1 parent ddf2640 commit 900ac0d

File tree

5 files changed

+103
-13
lines changed

5 files changed

+103
-13
lines changed

src/reactjs_jsx_ppx_v3.ml

Lines changed: 77 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ let otherAttrsPure (loc, _) = loc.txt <> "react.component"
9393
let hasAttrOnBinding { pvb_attributes } = find_opt hasAttr pvb_attributes <> None
9494

9595
(* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *)
96-
let getFnName binding =
96+
let rec getFnName binding =
9797
match binding with
98-
| { pvb_pat = { ppat_desc = Ppat_var { txt } } } -> txt
98+
| { ppat_desc = Ppat_var { txt } } -> txt
99+
| { ppat_desc = Ppat_constraint (pat, _) } -> getFnName pat
99100
| _ -> raise (Invalid_argument "react.component calls cannot be destructured.")
100101
[@@raises Invalid_argument]
101102

@@ -232,6 +233,44 @@ let makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList =
232233
(makePropsType ~loc namedTypeList)
233234
[@@raises Invalid_argument]
234235

236+
let rec newtypeToVar newtype typ =
237+
let traverse = newtypeToVar newtype in
238+
{ typ with ptyp_desc =
239+
match typ.ptyp_desc with
240+
| Ptyp_constr ({ txt = Lident name }, _) when name = newtype -> Ptyp_var newtype
241+
| Ptyp_constr (ident, args) -> Ptyp_constr (ident, List.map traverse args)
242+
| Ptyp_arrow (label, typ, rest) -> Ptyp_arrow (label, traverse typ, traverse rest)
243+
| Ptyp_tuple (types) -> Ptyp_tuple (List.map traverse types)
244+
| Ptyp_class (ident, args) -> Ptyp_class (ident, List.map traverse args)
245+
| Ptyp_alias (typ, alias) -> Ptyp_alias (traverse typ, alias)
246+
| Ptyp_poly (vars, rest) -> Ptyp_poly (vars, traverse rest)
247+
| Ptyp_variant (fields, flag, labels) ->
248+
let fields =
249+
List.map (function
250+
| Rtag (label, attrs, flag, args) -> Rtag (label, attrs, flag, List.map traverse args)
251+
| Rinherit typ -> Rinherit (traverse typ))
252+
fields
253+
in
254+
Ptyp_variant (fields, flag, labels)
255+
| Ptyp_object (fields, flag) ->
256+
let fields =
257+
List.map (function
258+
| Otag (label, attrs, typ) -> Otag (label, attrs, traverse typ)
259+
| Oinherit typ -> Oinherit (traverse typ))
260+
fields
261+
in
262+
Ptyp_object (fields, flag)
263+
| Ptyp_package (ident, substitutions) ->
264+
let substitutions =
265+
List.map
266+
(fun (ident, typ) -> (ident, traverse typ))
267+
substitutions
268+
in
269+
Ptyp_package (ident, substitutions)
270+
| Ptyp_extension _ -> Location.raise_errorf "extensions are not allowed in @react.component definitions"
271+
| (Ptyp_any | Ptyp_var _) as typ -> typ
272+
}
273+
235274
(* TODO: some line number might still be wrong *)
236275
let jsxMapper () =
237276
let jsxVersion = ref None in
@@ -334,7 +373,7 @@ let jsxMapper () =
334373
[@@raises Invalid_argument]
335374
in
336375

337-
let rec recursivelyTransformNamedArgsForMake mapper expr list =
376+
let rec recursivelyTransformNamedArgsForMake mapper expr args newtypes =
338377
let expr = mapper.expr mapper expr in
339378
match expr.pexp_desc with
340379
(* TODO: make this show up with a loc. *)
@@ -375,19 +414,23 @@ let jsxMapper () =
375414
let type_ = match pattern with { ppat_desc = Ppat_constraint (_, type_) } -> Some type_ | _ -> None in
376415

377416
recursivelyTransformNamedArgsForMake mapper expression
378-
((arg, default, pattern, alias, pattern.ppat_loc, type_) :: list)
417+
((arg, default, pattern, alias, pattern.ppat_loc, type_) :: args) newtypes
379418
| Pexp_fun (Nolabel, _, { ppat_desc = Ppat_construct ({ txt = Lident "()" }, _) | Ppat_any }, _expression) ->
380-
(list, None)
419+
(args, newtypes, None)
381420
| Pexp_fun
382421
( Nolabel,
383422
_,
384423
{ ppat_desc = Ppat_var { txt } | Ppat_constraint ({ ppat_desc = Ppat_var { txt } }, _) },
385424
_expression ) ->
386-
(list, Some txt)
425+
(args, newtypes, Some txt)
387426
| Pexp_fun (Nolabel, _, pattern, _expression) ->
388427
Location.raise_errorf ~loc:pattern.ppat_loc
389428
"React: react.component refs only support plain arguments and type annotations."
390-
| _ -> (list, None)
429+
| Pexp_newtype (label, expression) ->
430+
recursivelyTransformNamedArgsForMake mapper expression args (label :: newtypes)
431+
| Pexp_constraint (expression, _typ) ->
432+
recursivelyTransformNamedArgsForMake mapper expression args newtypes
433+
| _ -> (args, newtypes, None)
391434
[@@raises Invalid_argument]
392435
in
393436

@@ -487,7 +530,7 @@ let jsxMapper () =
487530
let bindingLoc = binding.pvb_loc in
488531
let bindingPatLoc = binding.pvb_pat.ppat_loc in
489532
let binding = { binding with pvb_pat = { binding.pvb_pat with ppat_loc = emptyLoc }; pvb_loc = emptyLoc } in
490-
let fnName = getFnName binding in
533+
let fnName = getFnName binding.pvb_pat in
491534
let internalFnName = fnName ^ "$Internal" in
492535
let fullModuleName = makeModuleName fileName !nestedModules fnName in
493536
let modifiedBindingOld binding =
@@ -496,7 +539,8 @@ let jsxMapper () =
496539
let rec spelunkForFunExpression expression =
497540
match expression with
498541
(* let make = (~prop) => ... *)
499-
| { pexp_desc = Pexp_fun _ } -> expression
542+
| { pexp_desc = Pexp_fun _ }
543+
| { pexp_desc = Pexp_newtype _ } -> expression
500544
(* let make = {let foo = bar in (~prop) => ...} *)
501545
| { pexp_desc = Pexp_let (_recursive, _vbs, returnExpression) } ->
502546
(* here's where we spelunk! *)
@@ -506,6 +550,8 @@ let jsxMapper () =
506550
spelunkForFunExpression innerFunctionExpression
507551
| { pexp_desc = Pexp_sequence (_wrapperExpression, innerFunctionExpression) } ->
508552
spelunkForFunExpression innerFunctionExpression
553+
| { pexp_desc = Pexp_constraint (innerFunctionExpression, _typ) } ->
554+
spelunkForFunExpression innerFunctionExpression
509555
| _ ->
510556
raise
511557
(Invalid_argument
@@ -594,8 +640,8 @@ let jsxMapper () =
594640
in
595641
let props = getPropsAttr payload in
596642
(* do stuff here! *)
597-
let namedArgList, forwardRef =
598-
recursivelyTransformNamedArgsForMake mapper (modifiedBindingOld binding) []
643+
let namedArgList, newtypes, forwardRef =
644+
recursivelyTransformNamedArgsForMake mapper (modifiedBindingOld binding) [] []
599645
in
600646
let namedArgListWithKeyAndRef =
601647
(optional "key", None, Pat.var { txt = "key"; loc = emptyLoc }, "key", emptyLoc, Some (keyType emptyLoc))
@@ -630,7 +676,25 @@ let jsxMapper () =
630676
in
631677
let namedTypeList = List.fold_left argToType [] namedArgList in
632678
let loc = emptyLoc in
633-
let externalDecl = makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList in
679+
let externalArgs = (* translate newtypes to type variables *)
680+
List.fold_left
681+
(fun args newtype ->
682+
List.map (fun (a, b, c, d, e, maybeTyp) ->
683+
match maybeTyp with
684+
| Some typ -> (a, b, c, d, e, Some (newtypeToVar newtype.txt typ))
685+
| None -> (a, b, c, d, e, None))
686+
args)
687+
namedArgListWithKeyAndRef
688+
newtypes
689+
in
690+
let externalTypes = (* translate newtypes to type variables *)
691+
List.fold_left
692+
(fun args newtype ->
693+
List.map (fun (a, b, typ) -> (a, b, newtypeToVar newtype.txt typ)) args)
694+
namedTypeList
695+
newtypes
696+
in
697+
let externalDecl = makeExternalDecl fnName loc externalArgs externalTypes in
634698
let innerExpressionArgs =
635699
List.map pluckArg namedArgListWithKeyAndRefForNew
636700
@ if hasUnit then [ (Nolabel, Exp.construct { loc; txt = Lident "()" } None) ] else []
@@ -660,7 +724,7 @@ let jsxMapper () =
660724
{
661725
ppat_desc =
662726
Ppat_constraint
663-
(makePropsName ~loc:emptyLoc props.propsName, makePropsType ~loc:emptyLoc namedTypeList);
727+
(makePropsName ~loc:emptyLoc props.propsName, makePropsType ~loc:emptyLoc externalTypes);
664728
ppat_loc = emptyLoc;
665729
ppat_attributes = [];
666730
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@obj
2+
external makeProps: (
3+
~a: 'a,
4+
~b: array<option<[#Foo('a)]>>,
5+
~key: string=?,
6+
unit,
7+
) => {"a": 'a, "b": array<option<[#Foo('a)]>>} = ""
8+
let make = (type a, ~a: a, ~b: array<option<[#Foo(a)]>>, _) =>
9+
ReactDOMRe.createDOMElementVariadic("div", [])
10+
let make = {
11+
let \"Newtype" = (\"Props": {"a": 'a, "b": array<option<[#Foo('a)]>>}) =>
12+
make(~b=\"Props"["b"], ~a=\"Props"["a"])
13+
\"Newtype"
14+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@obj external makeProps: (~a: 'a, ~b: 'b, ~key: string=?, unit) => {"a": 'a, "b": 'b} = ""
2+
let make:
3+
type a. (~a: a, ~b: a, a) => React.element =
4+
(~a, ~b, _) => ReactDOMRe.createDOMElementVariadic("div", [])
5+
let make = {
6+
let \"TypeConstraint" = (\"Props": {"a": 'a, "b": 'b}) => make(~b=\"Props"["b"], ~a=\"Props"["a"])
7+
\"TypeConstraint"
8+
}

tests/ppx/react/newtype.res

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@react.component
2+
let make = (type a, ~a: a, ~b: array<option<[#Foo(a)]>>, _) => <div />

tests/ppx/react/typeConstraint.res

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@react.component
2+
let make: type a. (~a: a, ~b: a, a) => React.element = (~a, ~b, _) => <div />

0 commit comments

Comments
 (0)