@@ -191,31 +191,108 @@ let rec recursivelyMakeNamedArgsForExternal list args =
191
191
| [] -> args
192
192
[@@ raises Invalid_argument ]
193
193
194
- (* Build an AST node for the [@bs.obj] representing props for a component *)
195
- let makePropsValue fnName loc namedArgListWithKeyAndRef propsType =
196
- let propsName = fnName ^ " Props" in
197
- {
198
- pval_name = { txt = propsName; loc };
199
- pval_type =
200
- recursivelyMakeNamedArgsForExternal namedArgListWithKeyAndRef
201
- (Typ. arrow nolabel
202
- { ptyp_desc = Ptyp_constr ({ txt = Lident " unit" ; loc }, [] ); ptyp_loc = loc; ptyp_attributes = [] }
203
- propsType);
204
- pval_prim = [ " " ];
205
- pval_attributes = [ ({ txt = " bs.obj" ; loc }, PStr [] ) ];
206
- pval_loc = loc;
207
- }
208
- [@@ raises Invalid_argument ]
209
-
210
- (* Build an AST node representing an `external` with the definition of the [@bs.obj] *)
211
- let makePropsExternal fnName loc namedArgListWithKeyAndRef propsType =
212
- { pstr_loc = loc; pstr_desc = Pstr_primitive (makePropsValue fnName loc namedArgListWithKeyAndRef propsType) }
213
- [@@ raises Invalid_argument ]
214
-
215
- (* Build an AST node for the signature of the `external` definition *)
216
- let makePropsExternalSig fnName loc namedArgListWithKeyAndRef propsType =
217
- { psig_loc = loc; psig_desc = Psig_value (makePropsValue fnName loc namedArgListWithKeyAndRef propsType) }
218
- [@@ raises Invalid_argument ]
194
+ (* List.filter_map in 4.08.0 *)
195
+ let filterMap f =
196
+ let rec aux accu = function
197
+ | [] -> List. rev accu
198
+ | x :: l -> (
199
+ match f x with None -> aux accu l | Some v -> aux (v :: accu) l )
200
+ in
201
+ aux []
202
+
203
+ (* make record from props and spread props if exists *)
204
+ let recordFromProps { pexp_loc } callArguments =
205
+ let rec removeLastPositionUnitAux props acc =
206
+ match props with
207
+ | [] -> acc
208
+ | [ (Nolabel , { pexp_desc = Pexp_construct ({ txt = Lident " ()" }, None ) }) ] -> acc
209
+ | (Nolabel, _ ) :: _rest -> raise (Invalid_argument " JSX: found non-labelled argument before the last position" )
210
+ | prop :: rest -> removeLastPositionUnitAux rest (prop :: acc)
211
+ in
212
+ let props, propsToSpread = removeLastPositionUnitAux callArguments []
213
+ |> List. rev
214
+ |> List. partition (fun (label , _ ) -> label <> labelled " spreadProps" ) in
215
+ let fields = props |> List. map (fun (arg_label , ({ pexp_loc } as expr ) ) ->
216
+ ({ txt = (Longident. parse (getLabel arg_label)); loc = pexp_loc} , expr))
217
+ in
218
+ let spreadFields = propsToSpread |> List. map (fun (_ , expression ) -> expression) in
219
+ match spreadFields with
220
+ | [] -> { pexp_desc= Pexp_record (fields, None ); pexp_loc; pexp_attributes= [] }
221
+ | [ spreadProps] -> { pexp_desc= Pexp_record (fields, Some spreadProps); pexp_loc; pexp_attributes= [] }
222
+ | spreadProps :: _ -> { pexp_desc= Pexp_record (fields, Some spreadProps); pexp_loc; pexp_attributes= [] }
223
+
224
+ (* @obj type props = { id: option<string>, key: ... } *)
225
+ let makePropsRecordType fnName loc namedTypeList =
226
+ let labelDeclList =
227
+ namedTypeList
228
+ |> List. map (fun (label , _ , interiorType ) ->
229
+ Type. field ~loc { txt = label; loc } interiorType)
230
+ in
231
+ (* 'id, 'className, ... *)
232
+ let params =
233
+ namedTypeList
234
+ |> filterMap (fun (_ , _ , interiorType ) ->
235
+ match interiorType with
236
+ | { ptyp_desc = Ptyp_var _ } as param -> Some (param, Invariant )
237
+ | _ -> None )
238
+ in
239
+ Str. type_ Nonrecursive
240
+ [
241
+ Type. mk ~loc
242
+ ~attrs: [ ({ txt = " bs.obj" ; loc }, PStr [] ) ]
243
+ ~params
244
+ { txt = fnName ^ " Props" ; loc }
245
+ ~kind: (Ptype_record labelDeclList);
246
+ ]
247
+
248
+ (* let makeProps = (props: makeProps) => props *)
249
+ let makePropsFn fnName loc =
250
+ Str. value Nonrecursive
251
+ [
252
+ Vb. mk
253
+ (Pat. var { txt = fnName ^ " Props" ; loc })
254
+ (Exp. fun_ Nolabel None
255
+ (Pat. constraint_
256
+ (Pat. var { txt = " props" ; loc })
257
+ (Typ. constr ~loc
258
+ { txt = Longident. parse @@ fnName ^ " Props" ; loc }
259
+ [] ))
260
+ (Exp. ident { txt = Longident. parse " props" ; loc }));
261
+ ]
262
+
263
+ (* @obj type props = { id: option<string>, key: ... } *)
264
+ let makePropsRecordTypeSig fnName loc namedTypeList =
265
+ let labelDeclList =
266
+ namedTypeList
267
+ |> List. map (fun (label , _ , interiorType ) ->
268
+ Type. field ~loc { txt = label; loc } interiorType)
269
+ in
270
+ let params =
271
+ namedTypeList
272
+ |> filterMap (fun (_ , _ , interiorType ) ->
273
+ match interiorType with
274
+ | { ptyp_desc = Ptyp_var _ } as param -> Some (param, Invariant )
275
+ | _ -> None )
276
+ in
277
+ Sig. type_ Nonrecursive
278
+ [
279
+ Type. mk ~loc
280
+ ~attrs: [ ({ txt = " bs.obj" ; loc }, PStr [] ) ]
281
+ ~params
282
+ { txt = fnName ^ " Props" ; loc }
283
+ ~kind: (Ptype_record labelDeclList);
284
+ ]
285
+
286
+ (* let makeProps: makeProps => props *)
287
+ let makePropsFnSig fnName loc =
288
+ Sig. value ~loc
289
+ (Val. mk
290
+ { txt = fnName ^ " Props" ; loc }
291
+ (Typ. arrow Nolabel
292
+ (Typ. constr
293
+ (Location. mkloc (Longident. parse (fnName ^ " Props" )) loc)
294
+ [] )
295
+ (Typ. constr (Location. mkloc (Longident. parse @@ fnName ^ " Props" ) loc) [] )))
219
296
220
297
(* Build an AST node for the props name when converted to an object inside the function signature *)
221
298
let makePropsName ~loc name = { ppat_desc = Ppat_var { txt = name; loc }; ppat_loc = loc; ppat_attributes = [] }
@@ -226,13 +303,6 @@ let makeObjectField loc (str, attrs, type_) = Otag ({ loc; txt = str }, attrs, t
226
303
let makePropsType ~loc namedTypeList =
227
304
Typ. mk ~loc (Ptyp_object (List. map (makeObjectField loc) namedTypeList, Closed ))
228
305
229
- (* Builds an AST node for the entire `external` definition of props *)
230
- let makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList =
231
- makePropsExternal fnName loc
232
- (List. map pluckLabelDefaultLocType namedArgListWithKeyAndRef)
233
- (makePropsType ~loc namedTypeList)
234
- [@@ raises Invalid_argument ]
235
-
236
306
let newtypeToVar newtype type_ =
237
307
let var_desc = Ptyp_var (" type-" ^ newtype) in
238
308
let typ (mapper : Ast_mapper.mapper ) typ =
@@ -248,7 +318,7 @@ let newtypeToVar newtype type_ =
248
318
let jsxMapper () =
249
319
let jsxVersion = ref None in
250
320
251
- let transformUppercaseCall3 modulePath mapper loc attrs _ callArguments =
321
+ let transformUppercaseCall3 modulePath mapper loc attrs callExpression callArguments =
252
322
let children, argsWithLabels = extractChildren ~loc ~remove LastPositionUnit:true callArguments in
253
323
let argsForMake = argsWithLabels in
254
324
let childrenExpr = transformChildrenIfListUpper ~loc ~mapper children in
@@ -265,8 +335,8 @@ let jsxMapper () =
265
335
(* this is a hack to support react components that introspect into their children *)
266
336
childrenArg := Some expression;
267
337
[ (labelled " children" , Exp. ident ~loc { loc; txt = Ldot (Lident " React" , " null" ) }) ] )
268
- @ [ (nolabel, Exp. construct ~loc { loc; txt = Lident " ()" } None ) ]
269
338
in
339
+ let record = recordFromProps callExpression args in
270
340
let isCap str =
271
341
let first = String. sub str 0 1 [@@ raises Invalid_argument ] in
272
342
let capped = String. uppercase_ascii first in
@@ -285,7 +355,7 @@ let jsxMapper () =
285
355
| Ldot (ident , path ) -> Ldot (ident, path ^ " Props" )
286
356
| _ -> raise (Invalid_argument " JSX name can't be the result of function applications" )
287
357
in
288
- let props = Exp. apply ~attrs ~loc (Exp. ident ~loc { loc; txt = propsIdent }) args in
358
+ let props = Exp. apply ~attrs ~loc (Exp. ident ~loc { loc; txt = propsIdent }) [ (nolabel, record) ] in
289
359
(* handle key, ref, children *)
290
360
(* React.createElement(Component.make, props, ...children) *)
291
361
match ! childrenArg with
@@ -300,8 +370,9 @@ let jsxMapper () =
300
370
[@@ raises Invalid_argument ]
301
371
in
302
372
303
- let transformLowercaseCall3 mapper loc attrs callArguments id =
373
+ let transformLowercaseCall3 mapper loc attrs callExpression callArguments id =
304
374
let children, nonChildrenProps = extractChildren ~loc callArguments in
375
+ let record = recordFromProps callExpression nonChildrenProps in
305
376
let componentNameExpr = constantString ~loc id in
306
377
let childrenExpr = transformChildrenIfList ~loc ~mapper children in
307
378
let createElementCall =
@@ -324,17 +395,12 @@ let jsxMapper () =
324
395
match nonChildrenProps with
325
396
| [ _justTheUnitArgumentAtEnd ] ->
326
397
[ (* "div" *) (nolabel, componentNameExpr); (* [|moreCreateElementCallsHere|] *) (nolabel, childrenExpr) ]
327
- | nonEmptyProps ->
328
- let propsCall =
329
- Exp. apply ~loc
330
- (Exp. ident ~loc { loc; txt = Ldot (Lident " ReactDOMRe" , " domProps" ) })
331
- (nonEmptyProps |> List. map (fun (label , expression ) -> (label, mapper.expr mapper expression)))
332
- in
398
+ | _nonEmptyProps ->
333
399
[
334
400
(* "div" *)
335
401
(nolabel, componentNameExpr);
336
- (* ReactDOMRe.props(~ className=blabla, ~ foo=bar, () ) *)
337
- (labelled " props" , propsCall );
402
+ (* ReactDOMRe.props({ className=blabla, foo=bar} ) *)
403
+ (labelled " props" , record );
338
404
(* [|moreCreateElementCallsHere|] *)
339
405
(nolabel, childrenExpr);
340
406
]
@@ -469,12 +535,15 @@ let jsxMapper () =
469
535
in
470
536
let innerType, propTypes = getPropTypes [] pval_type in
471
537
let namedTypeList = List. fold_left argToConcreteType [] propTypes in
472
- let pluckLabelAndLoc (label , loc , type_ ) = (label, None (* default *) , loc, Some type_) in
473
- let retPropsType = makePropsType ~loc: pstr_loc namedTypeList in
474
- let externalPropsDecl =
475
- makePropsExternal fnName pstr_loc
476
- ((optional " key" , None , pstr_loc, Some (keyType pstr_loc)) :: List. map pluckLabelAndLoc propTypes)
477
- retPropsType
538
+ let retPropsType = (Typ. constr ~loc: pstr_loc (Location. mkloc (Longident. parse (fnName ^ " Props" )) pstr_loc) [] ) in
539
+ (* @obj type makeProps = { ... } *)
540
+ let propsRecordType =
541
+ makePropsRecordType fnName pstr_loc
542
+ ((" key" , [] , keyType pstr_loc) :: namedTypeList)
543
+ in
544
+ (* let makeProps = (props: makeProps) => props *)
545
+ let propsFn =
546
+ makePropsFn fnName pstr_loc
478
547
in
479
548
(* can't be an arrow because it will defensively uncurry *)
480
549
let newExternalType =
@@ -492,7 +561,7 @@ let jsxMapper () =
492
561
};
493
562
}
494
563
in
495
- externalPropsDecl :: newStructure :: returnStructures
564
+ propsRecordType :: propsFn :: newStructure :: returnStructures
496
565
| _ -> raise (Invalid_argument " Only one react.component call can exist on a component at one time" ) )
497
566
(* let component = ... *)
498
567
| { pstr_loc; pstr_desc = Pstr_value (recFlag , valueBindings ) } ->
@@ -616,17 +685,6 @@ let jsxMapper () =
616
685
let namedArgList, newtypes, forwardRef =
617
686
recursivelyTransformNamedArgsForMake mapper (modifiedBindingOld binding) [] []
618
687
in
619
- let namedArgListWithKeyAndRef =
620
- (optional " key" , None , Pat. var { txt = " key" ; loc = emptyLoc }, " key" , emptyLoc, Some (keyType emptyLoc))
621
- :: namedArgList
622
- in
623
- let namedArgListWithKeyAndRef =
624
- match forwardRef with
625
- | Some _ ->
626
- (optional " ref" , None , Pat. var { txt = " key" ; loc = emptyLoc }, " ref" , emptyLoc, None )
627
- :: namedArgListWithKeyAndRef
628
- | None -> namedArgListWithKeyAndRef
629
- in
630
688
let namedArgListWithKeyAndRefForNew =
631
689
match forwardRef with
632
690
| Some txt -> namedArgList @ [ (nolabel, None , Pat. var { txt; loc = emptyLoc }, txt, emptyLoc, None ) ]
@@ -649,25 +707,22 @@ let jsxMapper () =
649
707
in
650
708
let namedTypeList = List. fold_left argToType [] namedArgList in
651
709
let loc = emptyLoc in
652
- let externalArgs = (* translate newtypes to type variables *)
653
- List. fold_left
654
- (fun args newtype ->
655
- List. map (fun (a , b , c , d , e , maybeTyp ) ->
656
- match maybeTyp with
657
- | Some typ -> (a, b, c, d, e, Some (newtypeToVar newtype.txt typ))
658
- | None -> (a, b, c, d, e, None ))
659
- args)
660
- namedArgListWithKeyAndRef
661
- newtypes
662
- in
663
710
let externalTypes = (* translate newtypes to type variables *)
664
711
List. fold_left
665
712
(fun args newtype ->
666
713
List. map (fun (a , b , typ ) -> (a, b, newtypeToVar newtype.txt typ)) args)
667
714
namedTypeList
668
715
newtypes
669
716
in
670
- let externalDecl = makeExternalDecl fnName loc externalArgs externalTypes in
717
+ (* @obj type makeProps = { ... } *)
718
+ let propsRecordType =
719
+ makePropsRecordType fnName pstr_loc
720
+ ((" key" , [] , keyType pstr_loc) :: namedTypeList)
721
+ in
722
+ (* let makeProps = (props: makeProps) => props *)
723
+ let propsFn =
724
+ makePropsFn fnName pstr_loc
725
+ in
671
726
let innerExpressionArgs =
672
727
List. map pluckArg namedArgListWithKeyAndRefForNew
673
728
@ if hasUnit then [ (Nolabel , Exp. construct { loc; txt = Lident " ()" } None ) ] else []
@@ -727,20 +782,22 @@ let jsxMapper () =
727
782
| Nonrecursive ->
728
783
([ { binding with pvb_expr = expression; pvb_attributes = [] } ], Some (bindingWrapper fullExpression))
729
784
in
730
- (Some externalDecl , bindings, newBinding)
731
- else (None , [ binding ], None )
785
+ (Some propsRecordType, Some propsFn , bindings, newBinding)
786
+ else (None , None , [ binding ], None )
732
787
[@@ raises Invalid_argument ]
733
788
in
734
789
let structuresAndBinding = List. map mapBinding valueBindings in
735
- let otherStructures (extern , binding , newBinding ) (externs , bindings , newBindings ) =
736
- let externs = match extern with Some extern -> extern :: externs | None -> externs in
790
+ let otherStructures (type_ , propsFn , binding , newBinding ) (types , propsFns , bindings , newBindings ) =
791
+ let types = match type_ with Some type_ -> type_ :: types | None -> types in
792
+ let propsFns = match propsFn with Some propsFn -> propsFn :: propsFns | None -> propsFns in
737
793
let newBindings =
738
794
match newBinding with Some newBinding -> newBinding :: newBindings | None -> newBindings
739
795
in
740
- (externs , binding @ bindings, newBindings)
796
+ (types, propsFns , binding @ bindings, newBindings)
741
797
in
742
- let externs, bindings, newBindings = List. fold_right otherStructures structuresAndBinding ([] , [] , [] ) in
743
- externs
798
+ let types, propsFns, bindings, newBindings = List. fold_right otherStructures structuresAndBinding ([] , [] , [] , [] ) in
799
+ types
800
+ @ propsFns
744
801
@ [ { pstr_loc; pstr_desc = Pstr_value (recFlag, bindings) } ]
745
802
@ ( match newBindings with
746
803
| [] -> []
@@ -774,12 +831,13 @@ let jsxMapper () =
774
831
in
775
832
let innerType, propTypes = getPropTypes [] pval_type in
776
833
let namedTypeList = List. fold_left argToConcreteType [] propTypes in
777
- let pluckLabelAndLoc (label , loc , type_ ) = (label, None , loc, Some type_) in
778
- let retPropsType = makePropsType ~loc: psig_loc namedTypeList in
779
- let externalPropsDecl =
780
- makePropsExternalSig fnName psig_loc
781
- ((optional " key" , None , psig_loc, Some (keyType psig_loc)) :: List. map pluckLabelAndLoc propTypes)
782
- retPropsType
834
+ let retPropsType = (Typ. constr (Location. mkloc (Longident. parse (fnName ^ " Props" )) psig_loc) [] ) in
835
+ let propsRecordType =
836
+ makePropsRecordTypeSig fnName psig_loc
837
+ ((" key" , [] , keyType psig_loc) :: namedTypeList)
838
+ in
839
+ let propsFnSig =
840
+ makePropsFnSig fnName psig_loc
783
841
in
784
842
(* can't be an arrow because it will defensively uncurry *)
785
843
let newExternalType =
@@ -797,7 +855,7 @@ let jsxMapper () =
797
855
};
798
856
}
799
857
in
800
- externalPropsDecl :: newStructure :: returnSignatures
858
+ propsRecordType :: propsFnSig :: newStructure :: returnSignatures
801
859
| _ -> raise (Invalid_argument " Only one react.component call can exist on a component at one time" ) )
802
860
| signature -> signature :: returnSignatures
803
861
[@@ raises Invalid_argument ]
@@ -824,7 +882,7 @@ let jsxMapper () =
824
882
ReactDOMRe.createElement(~props=ReactDOMRe.props(~props1=foo, ~props2=bar, ()), [|bla|]) *)
825
883
| { loc; txt = Lident id } -> (
826
884
match ! jsxVersion with
827
- | None | Some 3 -> transformLowercaseCall3 mapper loc attrs callArguments id
885
+ | None | Some 3 -> transformLowercaseCall3 mapper loc attrs callExpression callArguments id
828
886
| Some _ -> raise (Invalid_argument " JSX: the JSX version must be 3" ) )
829
887
| { txt = Ldot (_ , anythingNotCreateElementOrMake ) } ->
830
888
raise
0 commit comments