Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions compiler/syntax/src/jsx_common.ml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ let has_attr (loc, _) =
| "react.component" | "jsx.component" -> true
| _ -> false

let has_attr_with_props (loc, _) =
match loc.txt with
| "react.componentWithProps" | "jsx.componentWithProps" -> true
| _ -> false

(* Iterate over the attributes and try to find the [@react.component] attribute *)
let has_attr_on_binding {pvb_attributes} =
List.find_opt has_attr pvb_attributes <> None
let has_attr_on_binding pred {pvb_attributes} =
List.find_opt pred pvb_attributes <> None

let core_type_of_attrs attributes =
List.find_map
Expand Down
96 changes: 94 additions & 2 deletions compiler/syntax/src/jsx_v4.ml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,9 @@ let merlin_focus = ({loc = Location.none; txt = "merlin.focus"}, PStr [])
(* Helper method to filter out any attribute that isn't [@react.component] *)
let other_attrs_pure (loc, _) =
match loc.txt with
| "react.component" | "jsx.component" -> false
| "react.component" | "jsx.component" | "react.componentWithProps"
| "jsx.componentWithProps" ->
false
| _ -> true

(* Finds the name of the variable the binding is assigned to, otherwise raises Invalid_argument *)
Expand Down Expand Up @@ -941,7 +943,7 @@ let vb_match_expr named_arg_list expr =
aux (List.rev named_arg_list)

let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
if Jsx_common.has_attr_on_binding binding then (
if Jsx_common.has_attr_on_binding Jsx_common.has_attr binding then (
check_multiple_components ~config ~loc:pstr_loc;
let binding = Jsx_common.remove_arity binding in
let core_type_of_attr =
Expand Down Expand Up @@ -1189,6 +1191,96 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
Some (binding_wrapper full_expression) )
in
(Some props_record_type, binding, new_binding))
else if Jsx_common.has_attr_on_binding Jsx_common.has_attr_with_props binding
then
let modified_binding = Jsx_common.remove_arity binding in
let modified_binding =
{
modified_binding with
pvb_attributes =
modified_binding.pvb_attributes |> List.filter other_attrs_pure;
}
in
let fn_name = get_fn_name modified_binding.pvb_pat in
let internal_fn_name = fn_name ^ "$Internal" in
let full_module_name =
make_module_name file_name config.nested_modules fn_name
in

let is_async =
Ext_list.find_first modified_binding.pvb_expr.pexp_attributes
Ast_async.is_async
|> Option.is_some
in

let make_new_binding ~loc ~full_module_name binding =
let props_pattern =
match binding.pvb_expr with
| {
pexp_desc =
Pexp_fun (_, _, {ppat_desc = Ppat_constraint (_, typ)}, _, _);
} -> (
match typ with
| {ptyp_desc = Ptyp_constr ({txt = Lident "props"}, args)} ->
(* props<_> *)
if List.length args > 0 then
Pat.constraint_
(Pat.var {txt = "props"; loc})
(Typ.constr {txt = Lident "props"; loc} [Typ.any ()])
(* props *)
else
Pat.constraint_
(Pat.var {txt = "props"; loc})
(Typ.constr {txt = Lident "props"; loc} [])
| _ -> Pat.var {txt = "props"; loc})
| _ -> Pat.var {txt = "props"; loc}
in

let wrapper_expr =
Exp.fun_ ~arity:None Nolabel None props_pattern
(Jsx_common.async_component ~async:is_async
(Exp.apply
(Exp.ident
{
txt =
Lident
(match rec_flag with
| Recursive -> internal_fn_name
| Nonrecursive -> fn_name);
loc;
})
[(Nolabel, Exp.ident {txt = Lident "props"; loc})]))
in

let wrapper_expr =
Ast_uncurried.uncurried_fun ~loc:wrapper_expr.pexp_loc ~arity:1
wrapper_expr
in

let internal_expression =
Exp.let_ Nonrecursive
[Vb.mk (Pat.var {txt = full_module_name; loc}) wrapper_expr]
(Exp.ident {txt = Lident full_module_name; loc})
in

Vb.mk ~attrs:modified_binding.pvb_attributes
(Pat.var {txt = fn_name; loc})
internal_expression
in

let new_binding =
match rec_flag with
| Recursive -> None
| Nonrecursive ->
Some
(make_new_binding ~loc:empty_loc ~full_module_name modified_binding)
in
( None,
{
binding with
pvb_attributes = binding.pvb_attributes |> List.filter other_attrs_pure;
},
new_binding )
else (None, binding, None)

let transform_structure_item ~config item =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
let f = a => Js.Promise.resolve(a + a)

@@jsxConfig({version: 4, mode: "classic"})

module V4C1 = {
type props = sharedProps
let make = props => React.string(props.x ++ props.y)
let make = {
let \"SharedPropsWithProps$V4C1" = props => make(props)
\"SharedPropsWithProps$V4C1"
}
}

module V4C2 = {
type props = sharedProps
let make = (props: props) => React.string(props.x ++ props.y)
let make = {
let \"SharedPropsWithProps$V4C2" = (props: props) => make(props)
\"SharedPropsWithProps$V4C2"
}
}

module V4C3 = {
type props<'a> = sharedProps<'a>
let make = ({x, y}: props<_>) => React.string(x ++ y)
let make = {
let \"SharedPropsWithProps$V4C3" = (props: props<_>) => make(props)
\"SharedPropsWithProps$V4C3"
}
}

module V4C4 = {
type props<'a> = sharedProps<string, 'a>
let make = ({x, y}: props<_>) => React.string(x ++ y)
let make = {
let \"SharedPropsWithProps$V4C4" = (props: props<_>) => make(props)
\"SharedPropsWithProps$V4C4"
}
}

module V4C5 = {
type props<'a> = {a: 'a}
let make = async ({a}: props<_>) => {
let a = await f(a)
ReactDOM.createDOMElementVariadic("div", [{React.int(a)}])
}
let make = {
let \"SharedPropsWithProps$V4C5" = (props: props<_>) =>
JsxPPXReactSupport.asyncComponent(make(props))
\"SharedPropsWithProps$V4C5"
}
}

module V4C6 = {
type props<'status> = {status: 'status}
let make = async ({status}: props<_>) => {
switch status {
| #on => React.string("on")
| #off => React.string("off")
}
}
let make = {
let \"SharedPropsWithProps$V4C6" = (props: props<_>) =>
JsxPPXReactSupport.asyncComponent(make(props))
\"SharedPropsWithProps$V4C6"
}
}

@@jsxConfig({version: 4, mode: "automatic"})

module V4A1 = {
type props = sharedProps
let make = props => React.string(props.x ++ props.y)
let make = {
let \"SharedPropsWithProps$V4A1" = props => make(props)
\"SharedPropsWithProps$V4A1"
}
}

module V4A2 = {
type props = sharedProps
let make = (props: props) => React.string(props.x ++ props.y)
let make = {
let \"SharedPropsWithProps$V4A2" = (props: props) => make(props)
\"SharedPropsWithProps$V4A2"
}
}

module V4A3 = {
type props<'a> = sharedProps<'a>
let make = ({x, y}: props<_>) => React.string(x ++ y)
let make = {
let \"SharedPropsWithProps$V4A3" = (props: props<_>) => make(props)
\"SharedPropsWithProps$V4A3"
}
}

module V4A4 = {
type props<'a> = sharedProps<string, 'a>
let make = ({x, y}: props<_>) => React.string(x ++ y)
let make = {
let \"SharedPropsWithProps$V4A4" = (props: props<_>) => make(props)
\"SharedPropsWithProps$V4A4"
}
}

module V4A5 = {
type props<'a> = {a: 'a}
let make = async ({a}: props<_>) => {
let a = await f(a)
ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.int(a)})})
}
let make = {
let \"SharedPropsWithProps$V4A5" = (props: props<_>) =>
JsxPPXReactSupport.asyncComponent(make(props))
\"SharedPropsWithProps$V4A5"
}
}

module V4A6 = {
type props<'status> = {status: 'status}
let make = async ({status}: props<_>) => {
switch status {
| #on => React.string("on")
| #off => React.string("off")
}
}
let make = {
let \"SharedPropsWithProps$V4A6" = (props: props<_>) =>
JsxPPXReactSupport.asyncComponent(make(props))
\"SharedPropsWithProps$V4A6"
}
}
93 changes: 93 additions & 0 deletions tests/syntax_tests/data/ppx/react/sharedPropsWithProps.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
let f = a => Js.Promise.resolve(a + a)

@@jsxConfig({version:4, mode: "classic"})

module V4C1 = {
type props = sharedProps
@react.componentWithProps
let make = (props) => React.string(props.x ++ props.y)
}

module V4C2 = {
type props = sharedProps
@react.componentWithProps
let make = (props: props) => React.string(props.x ++ props.y)
}

module V4C3 = {
type props<'a> = sharedProps<'a>
@react.componentWithProps
let make = ({x, y}: props<_>) => React.string(x ++ y)
}

module V4C4 = {
type props<'a> = sharedProps<string, 'a>
@react.componentWithProps
let make = ({x, y}: props<_>) => React.string(x ++ y)
}

module V4C5 = {
type props<'a> = {a: 'a}
@react.componentWithProps
let make = async ({a}: props<_>) => {
let a = await f(a)
<div> {React.int(a)} </div>
}
}

module V4C6 = {
type props<'status> = {status: 'status}
@react.componentWithProps
let make = async ({status}: props<_>) => {
switch status {
| #on => React.string("on")
| #off => React.string("off")
}
}
}

@@jsxConfig({version:4, mode: "automatic"})

module V4A1 = {
type props = sharedProps
@react.componentWithProps
let make = (props) => React.string(props.x ++ props.y)
}

module V4A2 = {
type props = sharedProps
@react.componentWithProps
let make = (props: props) => React.string(props.x ++ props.y)
}

module V4A3 = {
type props<'a> = sharedProps<'a>
@react.componentWithProps
let make = ({x, y}: props<_>) => React.string(x ++ y)
}

module V4A4 = {
type props<'a> = sharedProps<string, 'a>
@react.componentWithProps
let make = ({x, y}: props<_>) => React.string(x ++ y)
}

module V4A5 = {
type props<'a> = {a: 'a}
@react.componentWithProps
let make = async ({a}: props<_>) => {
let a = await f(a)
<div> {React.int(a)} </div>
}
}

module V4A6 = {
type props<'status> = {status: 'status}
@react.componentWithProps
let make = async ({status}: props<_>) => {
switch status {
| #on => React.string("on")
| #off => React.string("off")
}
}
}
Loading