Skip to content

Commit 5bf383c

Browse files
committed
JSX PPX: add Jsx.element return constraint
1 parent a58ff10 commit 5bf383c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+321
-123
lines changed

compiler/syntax/src/jsx_v4.ml

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,38 @@ let ref_type loc =
4646
{loc; txt = Ldot (Ldot (Lident "Js", "Nullable"), "t")}
4747
[ref_type_var loc]
4848

49+
let jsx_element_type ~loc =
50+
Typ.constr ~loc {loc; txt = Ldot (Lident "Jsx", "element")} []
51+
52+
let jsx_element_constraint ~loc expr =
53+
Exp.constraint_ ~loc expr (jsx_element_type ~loc)
54+
55+
let rec constrain_jsx_return ~loc expr =
56+
match expr.pexp_desc with
57+
| Pexp_fun ({rhs} as desc) ->
58+
{
59+
expr with
60+
pexp_desc = Pexp_fun {desc with rhs = constrain_jsx_return ~loc rhs};
61+
}
62+
| Pexp_newtype (param, inner) ->
63+
{
64+
expr with
65+
pexp_desc = Pexp_newtype (param, constrain_jsx_return ~loc inner);
66+
}
67+
| Pexp_constraint (inner, _) ->
68+
jsx_element_constraint ~loc (constrain_jsx_return ~loc inner)
69+
| Pexp_let (rec_flag, bindings, body) ->
70+
{
71+
expr with
72+
pexp_desc = Pexp_let (rec_flag, bindings, constrain_jsx_return ~loc body);
73+
}
74+
| Pexp_sequence (first, second) ->
75+
{
76+
expr with
77+
pexp_desc = Pexp_sequence (first, constrain_jsx_return ~loc second);
78+
}
79+
| _ -> jsx_element_constraint ~loc expr
80+
4981
let merlin_focus = ({loc = Location.none; txt = "merlin.focus"}, PStr [])
5082

5183
(* Helper method to filter out any attribute that isn't [@react.component] *)
@@ -713,6 +745,10 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
713745
vb_match_expr named_arg_list expression
714746
else expression
715747
in
748+
let expression =
749+
Exp.constraint_ ~loc:binding_loc expression
750+
(jsx_element_type ~loc:binding_loc)
751+
in
716752
(* (ref) => expr *)
717753
let expression =
718754
List.fold_left
@@ -784,6 +820,7 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
784820
(Some props_record_type, binding, new_binding))
785821
else if Jsx_common.has_attr_on_binding Jsx_common.has_attr_with_props binding
786822
then
823+
let binding_loc = binding.pvb_loc in
787824
let modified_binding =
788825
{
789826
binding with
@@ -839,21 +876,28 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
839876
| _ -> Pat.var {txt = "props"; loc}
840877
in
841878

879+
let applied_expression =
880+
Exp.apply
881+
(Exp.ident
882+
{
883+
txt =
884+
Lident
885+
(match rec_flag with
886+
| Recursive -> internal_fn_name
887+
| Nonrecursive -> fn_name);
888+
loc;
889+
})
890+
[(Nolabel, Exp.ident {txt = Lident "props"; loc})]
891+
in
892+
let applied_expression =
893+
Jsx_common.async_component ~async:is_async applied_expression
894+
in
895+
let applied_expression =
896+
Exp.constraint_ ~loc applied_expression (jsx_element_type ~loc)
897+
in
842898
let wrapper_expr =
843899
Exp.fun_ ~arity:None Nolabel None props_pattern
844-
~attrs:binding.pvb_expr.pexp_attributes
845-
(Jsx_common.async_component ~async:is_async
846-
(Exp.apply
847-
(Exp.ident
848-
{
849-
txt =
850-
Lident
851-
(match rec_flag with
852-
| Recursive -> internal_fn_name
853-
| Nonrecursive -> fn_name);
854-
loc;
855-
})
856-
[(Nolabel, Exp.ident {txt = Lident "props"; loc})]))
900+
~attrs:binding.pvb_expr.pexp_attributes applied_expression
857901
in
858902

859903
let wrapper_expr = Ast_uncurried.uncurried_fun ~arity:1 wrapper_expr in
@@ -874,18 +918,20 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
874918
| Recursive -> None
875919
| Nonrecursive ->
876920
Some
877-
(make_new_binding ~loc:empty_loc ~full_module_name modified_binding)
921+
(make_new_binding ~loc:binding_loc ~full_module_name modified_binding)
878922
in
879923
( None,
880924
{
881925
binding with
882926
pvb_attributes = binding.pvb_attributes |> List.filter other_attrs_pure;
883927
pvb_expr =
884-
{
885-
binding.pvb_expr with
886-
(* moved to wrapper_expr *)
887-
pexp_attributes = [];
888-
};
928+
( binding.pvb_expr |> fun expr ->
929+
{
930+
expr with
931+
(* moved to wrapper_expr *)
932+
pexp_attributes = [];
933+
}
934+
|> constrain_jsx_return ~loc:binding_loc );
889935
},
890936
new_binding )
891937
else (None, binding, None)
@@ -934,7 +980,7 @@ let transform_structure_item ~config item =
934980
(arg.lbl, arg.attrs, return_value.ptyp_loc, arg.typ) :: types )
935981
| _ -> (full_type, types)
936982
in
937-
let inner_type, prop_types = get_prop_types [] pval_type in
983+
let _, prop_types = get_prop_types [] pval_type in
938984
let named_type_list = List.fold_left arg_to_concrete_type [] prop_types in
939985
let ret_props_type =
940986
Typ.constr ~loc:pstr_loc
@@ -955,7 +1001,7 @@ let transform_structure_item ~config item =
9551001
let new_external_type =
9561002
Ptyp_constr
9571003
( {loc = pstr_loc; txt = module_access_name config "componentLike"},
958-
[ret_props_type; inner_type] )
1004+
[ret_props_type; jsx_element_type ~loc:pstr_loc] )
9591005
in
9601006
let new_structure =
9611007
{
@@ -1046,7 +1092,7 @@ let transform_signature_item ~config item =
10461092
(arg.lbl, arg.attrs, return_value.ptyp_loc, arg.typ) :: types )
10471093
| _ -> (full_type, types)
10481094
in
1049-
let inner_type, prop_types = get_prop_types [] pval_type in
1095+
let _, prop_types = get_prop_types [] pval_type in
10501096
let named_type_list = List.fold_left arg_to_concrete_type [] prop_types in
10511097
let ret_props_type =
10521098
Typ.constr
@@ -1067,7 +1113,7 @@ let transform_signature_item ~config item =
10671113
let new_external_type =
10681114
Ptyp_constr
10691115
( {loc = psig_loc; txt = module_access_name config "componentLike"},
1070-
[ret_props_type; inner_type] )
1116+
[ret_props_type; jsx_element_type ~loc:psig_loc] )
10711117
in
10721118
let new_structure =
10731119
{
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/react_async_component_missing_jsx_element.res:4:24
4+
5+
2 │
6+
3 │ @react.component
7+
4 │ let make = async () => 1
8+
5 │
9+
10+
This has type: int
11+
But it's expected to have type: Jsx.element
12+
13+
In JSX, all content must be JSX elements. You can convert int to a JSX element with React.int.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/react_componentWithProps_missing_jsx_element.res:6:35-45
4+
5+
4 │
6+
5 │ @react.componentWithProps
7+
6 │ let make = (props: props<int>) => props.value
8+
7 │
9+
10+
This has type: int
11+
But it's expected to have type: Jsx.element
12+
13+
In JSX, all content must be JSX elements. You can convert int to a JSX element with React.int.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/react_component_missing_jsx_element.res:4:18
4+
5+
2 │
6+
3 │ @react.component
7+
4 │ let make = () => 1
8+
5 │
9+
10+
This has type: int
11+
But it's expected to have type: Jsx.element
12+
13+
In JSX, all content must be JSX elements. You can convert int to a JSX element with React.int.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/react_forwardRef_missing_jsx_element.res:4:42
4+
5+
2 │
6+
3 │ @react.component
7+
4 │ let make = React.forwardRef((_, _ref) => 1)
8+
5 │
9+
10+
This has type: int
11+
But it's expected to have type: Jsx.element
12+
13+
In JSX, all content must be JSX elements. You can convert int to a JSX element with React.int.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@@jsxConfig({version: 4})
2+
3+
@react.component
4+
let make = async () => 1
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@@jsxConfig({version: 4})
2+
3+
type props<'a> = {value: 'a}
4+
5+
@react.componentWithProps
6+
let make = (props: props<int>) => props.value
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@@jsxConfig({version: 4})
2+
3+
@react.component
4+
let make = () => 1
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@@jsxConfig({version: 4})
2+
3+
@react.component
4+
let make = React.forwardRef((_, _ref) => 1)

tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module C0 = {
44
@res.jsxComponentProps
55
type props<'priority, 'text> = {priority: 'priority, text?: 'text}
66

7-
let make = ({priority: _, text: ?__text, _}: props<_, _>) => {
7+
let make = ({priority: _, text: ?__text, _}: props<_, _>): Jsx.element => {
88
let text = switch __text {
99
| Some(text) => text
1010
| None => "Test"
@@ -23,7 +23,7 @@ module C1 = {
2323
@res.jsxComponentProps
2424
type props<'priority, 'text> = {priority: 'priority, text?: 'text}
2525

26-
let make = ({priority: p, text: ?__text, _}: props<_, _>) => {
26+
let make = ({priority: p, text: ?__text, _}: props<_, _>): Jsx.element => {
2727
let text = switch __text {
2828
| Some(text) => text
2929
| None => "Test"
@@ -42,7 +42,7 @@ module C2 = {
4242
@res.jsxComponentProps
4343
type props<'foo> = {foo?: 'foo}
4444

45-
let make = ({foo: ?__bar, _}: props<_>) => {
45+
let make = ({foo: ?__bar, _}: props<_>): Jsx.element => {
4646
let bar = switch __bar {
4747
| Some(foo) => foo
4848
| None => ""
@@ -61,7 +61,7 @@ module C3 = {
6161
@res.jsxComponentProps
6262
type props<'foo, 'a, 'b> = {foo?: 'foo, a?: 'a, b: 'b}
6363

64-
let make = ({foo: ?__bar, a: ?__a, b, _}: props<_, _, _>) => {
64+
let make = ({foo: ?__bar, a: ?__a, b, _}: props<_, _, _>): Jsx.element => {
6565
let bar = switch __bar {
6666
| Some(foo) => foo
6767
| None => ""
@@ -86,7 +86,7 @@ module C4 = {
8686
@res.jsxComponentProps
8787
type props<'a, 'x> = {a: 'a, x?: 'x}
8888

89-
let make = ({a: b, x: ?__x, _}: props<_, _>) => {
89+
let make = ({a: b, x: ?__x, _}: props<_, _>): Jsx.element => {
9090
let x = switch __x {
9191
| Some(x) => x
9292
| None => true
@@ -105,7 +105,7 @@ module C5 = {
105105
@res.jsxComponentProps
106106
type props<'a, 'z> = {a: 'a, z?: 'z}
107107

108-
let make = ({a: (x, y), z: ?__z, _}: props<_, _>) => {
108+
let make = ({a: (x, y), z: ?__z, _}: props<_, _>): Jsx.element => {
109109
let z = switch __z {
110110
| Some(z) => z
111111
| None => 3
@@ -125,12 +125,13 @@ module C6 = {
125125
@res.jsxComponentProps
126126
type props = {}
127127

128-
let make: React.componentLike<props, React.element>
128+
let make: React.componentLike<props, Jsx.element>
129129
}
130130
@res.jsxComponentProps
131131
type props<'comp, 'x> = {comp: 'comp, x: 'x}
132132

133-
let make = ({comp: module(Comp: Comp), x: (a, b), _}: props<_, _>) => React.jsx(Comp.make, {})
133+
let make = ({comp: module(Comp: Comp), x: (a, b), _}: props<_, _>): Jsx.element =>
134+
React.jsx(Comp.make, {})
134135
let make = {
135136
let \"AliasProps$C6" = (props: props<_>) => make(props)
136137

0 commit comments

Comments
 (0)