Skip to content

Commit b2efaef

Browse files
committed
Check @react.component function return type
1 parent 118294a commit b2efaef

File tree

8 files changed

+85
-17
lines changed

8 files changed

+85
-17
lines changed

compiler/syntax/src/jsx_v4.ml

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,16 @@ let vb_match_expr named_arg_list expr =
528528
in
529529
aux (List.rev named_arg_list)
530530

531+
(* https://github.com/rescript-lang/rescript/issues/7722 *)
532+
let add_jsx_element_return_constraint config expression =
533+
Exp.constraint_ expression
534+
(Typ.constr
535+
{txt = module_access_name config "element"; loc = expression.pexp_loc}
536+
[])
537+
531538
let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
532539
if Jsx_common.has_attr_on_binding Jsx_common.has_attr binding then (
540+
(* @react.component *)
533541
check_multiple_components ~config ~loc:pstr_loc;
534542
let core_type_of_attr =
535543
Jsx_common.core_type_of_attrs binding.pvb_attributes
@@ -732,6 +740,7 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
732740
| [] -> Pat.any ()
733741
| _ -> Pat.record (List.rev patterns_with_label) Open
734742
in
743+
let expression = add_jsx_element_return_constraint config expression in
735744
let expression =
736745
Exp.fun_ ~arity:(Some 1) ~async:is_async Nolabel None
737746
(Pat.constraint_ record_pattern
@@ -779,6 +788,7 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
779788
(Some props_record_type, binding, new_binding))
780789
else if Jsx_common.has_attr_on_binding Jsx_common.has_attr_with_props binding
781790
then
791+
(* @react.componentWithProps *)
782792
let modified_binding =
783793
{
784794
binding with
@@ -835,21 +845,24 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding =
835845
| _ -> Pat.var {txt = "props"; loc}
836846
in
837847

848+
let expression =
849+
Jsx_common.async_component ~async:is_async
850+
(Exp.apply
851+
(Exp.ident
852+
{
853+
txt =
854+
Lident
855+
(match rec_flag with
856+
| Recursive -> internal_fn_name
857+
| Nonrecursive -> fn_name);
858+
loc;
859+
})
860+
[(Nolabel, Exp.ident {txt = Lident "props"; loc})])
861+
in
862+
let expression = add_jsx_element_return_constraint config expression in
838863
let wrapper_expr =
839864
Exp.fun_ ~arity:None Nolabel None props_pattern
840-
~attrs:binding.pvb_expr.pexp_attributes
841-
(Jsx_common.async_component ~async:is_async
842-
(Exp.apply
843-
(Exp.ident
844-
{
845-
txt =
846-
Lident
847-
(match rec_flag with
848-
| Recursive -> internal_fn_name
849-
| Nonrecursive -> fn_name);
850-
loc;
851-
})
852-
[(Nolabel, Exp.ident {txt = Lident "props"; loc})]))
865+
~attrs:binding.pvb_expr.pexp_attributes expression
853866
in
854867

855868
let wrapper_expr = Ast_uncurried.uncurried_fun ~arity:1 wrapper_expr in
@@ -1282,7 +1295,7 @@ let mk_react_jsx (config : Jsx_common.jsx_config) mapper loc attrs
12821295
let args = [(nolabel, elementTag); (nolabel, props_record)] @ key_and_unit in
12831296
Exp.apply ~loc ~attrs ~transformed_jsx:true jsx_expr args
12841297

1285-
(* In most situations, the component name is the make function from a module.
1298+
(* In most situations, the component name is the make function from a module.
12861299
However, if the name contains a lowercase letter, it means it probably an external component.
12871300
In this case, we use the name as is.
12881301
See tests/syntax_tests/data/ppx/react/externalWithCustomName.res
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/jsx_invalid_return_type.res:14:5-6
4+
5+
12 │ @react.component
6+
13 │ let make = () => {
7+
14 │ 42 // This should be an error - returning int instead of React.elem
8+
│ ent
9+
15 │ }
10+
16 │ }
11+
12+
This has type: int
13+
But it's expected to have type: React.element (defined as Jsx.element)
14+
15+
In JSX, all content must be JSX elements. You can convert int to a JSX element with React.int.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/jsx_invalid_return_type_component_with_props.res
4+
5+
This has type: int
6+
But it's expected to have type: React.element (defined as Jsx.element)
7+
8+
In JSX, all content must be JSX elements. You can convert int to a JSX element with React.int.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@@config({
2+
flags: ["-bs-jsx", "4"],
3+
})
4+
5+
module React = {
6+
type element = Jsx.element
7+
type componentLike<'props, 'return> = 'props => 'return
8+
type component<'props> = Jsx.component<'props>
9+
}
10+
11+
module BadComponent = {
12+
@react.component
13+
let make = () => {
14+
42 // This should be an error - returning int instead of React.element
15+
}
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@@config({
2+
flags: ["-bs-jsx", "4"],
3+
})
4+
5+
module React = {
6+
type element = Jsx.element
7+
type componentLike<'props, 'return> = 'props => 'return
8+
type component<'props> = Jsx.component<'props>
9+
}
10+
11+
module BadComponent = {
12+
@react.componentWithProps
13+
let make = _props => {
14+
42 // This should be an error - returning int instead of React.element
15+
}
16+
}

tests/tests/src/alias_default_value_test.res

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ module C6 = {
4141
}
4242

4343
@react.component
44-
let make = (~comp as module(Comp: Comp), ~x as (a, b)) => Comp.xx
44+
let make = (~comp as module(Comp: Comp), ~x as (a, b)) => Comp.xx->React.int
4545
}
4646

4747
module C7 = {

tests/tests/src/jsxv4_newtype.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ let V4A2 = {
2626
};
2727

2828
function Jsxv4_newtype$V4A3(props) {
29-
return props.foo;
29+
return null;
3030
}
3131

3232
let V4A3 = {

tests/tests/src/jsxv4_newtype.res

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ module V4A3 = {
2828
@react.component
2929
let make = (type a, ~foo) => {
3030
module T = unpack(foo: T with type t = a)
31-
foo
31+
React.null
3232
}
3333
}

0 commit comments

Comments
 (0)