Skip to content

Commit 64dc1a2

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

Some content is hidden

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

51 files changed

+362
-153
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
{

tests/analysis_tests/tests/src/CompletionJsx.res

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ module MultiPropComp = {
6767
@react.component
6868
let make = (~name, ~age, ~time: time) => {
6969
ignore(time)
70-
name ++ age
70+
React.string(name ++ age)
7171
}
7272
}
7373

tests/analysis_tests/tests/src/CompletionPattern.res

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ let make = (~thing: result<someVariant, unit>) => {
229229
// ^com
230230
| _ => ()
231231
}
232+
React.null
232233
}
233234

234235
type results = {

tests/analysis_tests/tests/src/CreateInterface.res

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,27 +109,27 @@ module type OptT = {
109109

110110
module Opt = {
111111
@react.component
112-
let withOpt1 = (~x=3, ~y) => x + y
112+
let withOpt1 = (~x=3, ~y) => React.int(x + y)
113113

114114
module Opt2 = {
115115
@react.component
116116
let withOpt2 = (~x: option<int>=?, ~y: int) =>
117-
switch x {
117+
React.int(switch x {
118118
| None => 0
119119
| Some(x) => x
120120
} +
121-
y
121+
y)
122122
}
123123
module type Opt2 = module type of Opt2
124124

125125
module Opt3 = {
126126
@react.component
127127
let withOpt3 = (~x: option<int>, ~y: int) =>
128-
switch x {
128+
React.int(switch x {
129129
| None => 0
130130
| Some(x) => x
131131
} +
132-
y
132+
y)
133133
}
134134
module type Opt3 = module type of Opt3
135135
}

tests/analysis_tests/tests/src/expected/CodeLens.res.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
Code Lens src/CodeLens.res
22
[{
3+
"range": {"start": {"line": 9, "character": 4}, "end": {"line": 9, "character": 8}},
4+
"command": {"title": "Jsx.element", "command": ""}
5+
}, {
36
"range": {"start": {"line": 4, "character": 4}, "end": {"line": 4, "character": 6}},
47
"command": {"title": "(~opt1: int=?, ~a: int, ~b: int, unit, ~opt2: int=?, unit, ~c: int) => int", "command": ""}
58
}, {

tests/analysis_tests/tests/src/expected/CompletionPattern.res.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1178,7 +1178,8 @@ Path res
11781178
}]
11791179

11801180
Complete src/CompletionPattern.res 227:25
1181-
posCursor:[227:25] posNoWhite:[227:24] Found expr:[223:11->231:1]
1181+
posCursor:[227:25] posNoWhite:[227:24] Found expr:[223:11->232:1]
1182+
posCursor:[227:25] posNoWhite:[227:24] Found expr:[224:2->231:12]
11821183
posCursor:[227:25] posNoWhite:[227:24] Found expr:[226:4->227:28]
11831184
posCursor:[227:25] posNoWhite:[227:24] Found pattern:[227:18->227:27]
11841185
Completable: Cpattern Value[r]->recordBody
@@ -1206,8 +1207,8 @@ Path r
12061207
"documentation": {"kind": "markdown", "value": "```rescript\nnest: nestedRecord\n```\n\n```rescript\ntype someRecord = {first: int, second: (bool, option<someRecord>), optThird: option<[#first | #second(someRecord)]>, nest: nestedRecord}\n```"}
12071208
}]
12081209

1209-
Complete src/CompletionPattern.res 242:33
1210-
posCursor:[242:33] posNoWhite:[242:32] Found pattern:[242:7->242:35]
1210+
Complete src/CompletionPattern.res 243:33
1211+
posCursor:[243:33] posNoWhite:[243:32] Found pattern:[243:7->243:35]
12111212
Completable: Cpattern Value[hitsUse](Nolabel)->recordBody
12121213
Package opens Stdlib.place holder Pervasives.JsxModules.place holder
12131214
Resolved opens 1 Stdlib

tests/analysis_tests/tests/src/expected/CreateInterface.res.txt

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ Create Interface src/CreateInterface.res
22
type r = {name: string, age: int}
33
let add: (~x: int, ~y: int) => int
44
@react.component
5-
let make: (~name: string) => React.element
5+
let make: (~name: string) => Jsx.element
66
module Other: {
77
@react.component
8-
let otherComponentName: (~name: string) => React.element
8+
let otherComponentName: (~name: string) => Jsx.element
99
}
1010
module Mod: {
1111
@react.component
12-
let make: (~name: string) => React.element
12+
let make: (~name: string) => Jsx.element
1313
}
1414
module type ModTyp = {
1515
@react.component
16-
let make: (~name: string) => React.element
16+
let make: (~name: string) => Jsx.element
1717
}
1818
@module("path") external dirname: string => string = "dirname"
1919
@module("path") @variadic
@@ -49,21 +49,21 @@ module RFS: {
4949
module Functor: () =>
5050
{
5151
@react.component
52-
let make: unit => React.element
52+
let make: unit => Jsx.element
5353
}
5454
module type FT = {
5555
module Functor: (
5656
X: {
5757
let a: int
5858
@react.component
59-
let make: (~name: string) => React.element
59+
let make: (~name: string) => Jsx.element
6060
let b: int
6161
},
6262
Y: ModTyp,
6363
) =>
6464
{
6565
@react.component
66-
let make: (~name: string) => React.element
66+
let make: (~name: string) => Jsx.element
6767
}
6868
}
6969
module NormaList = List
@@ -73,34 +73,34 @@ module rec RM: ModTyp
7373
and D: ModTyp
7474
module type OptT = {
7575
@react.component
76-
let withOpt1: (~x: int=?, ~y: int) => int
76+
let withOpt1: (~x: int=?, ~y: int) => Jsx.element
7777
module type Opt2 = {
7878
@react.component
79-
let withOpt2: (~x: int=?, ~y: int) => int
79+
let withOpt2: (~x: int=?, ~y: int) => Jsx.element
8080
}
8181
module type Opt3 = {
8282
@react.component
83-
let withOpt3: (~x: option<int>, ~y: int) => int
83+
let withOpt3: (~x: option<int>, ~y: int) => Jsx.element
8484
}
8585
}
8686
module Opt: {
8787
@react.component
88-
let withOpt1: (~x: int=?, ~y: int) => int
88+
let withOpt1: (~x: int=?, ~y: int) => Jsx.element
8989
module Opt2: {
9090
@react.component
91-
let withOpt2: (~x: int=?, ~y: int) => int
91+
let withOpt2: (~x: int=?, ~y: int) => Jsx.element
9292
}
9393
module type Opt2 = {
9494
@react.component
95-
let withOpt2: (~x: int=?, ~y: int) => int
95+
let withOpt2: (~x: int=?, ~y: int) => Jsx.element
9696
}
9797
module Opt3: {
9898
@react.component
99-
let withOpt3: (~x: option<int>, ~y: int) => int
99+
let withOpt3: (~x: option<int>, ~y: int) => Jsx.element
100100
}
101101
module type Opt3 = {
102102
@react.component
103-
let withOpt3: (~x: option<int>, ~y: int) => int
103+
let withOpt3: (~x: option<int>, ~y: int) => Jsx.element
104104
}
105105
}
106106
module Opt2: OptT

tests/analysis_tests/tests/src/expected/Hover.res.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ Hover src/Hover.res 33:4
2222
{"contents": {"kind": "markdown", "value": "```rescript\nunit => int\n```\n---\nDoc comment for functionWithTypeAnnotation"}}
2323

2424
Hover src/Hover.res 37:13
25-
{"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}}
25+
{"contents": {"kind": "markdown", "value": "```rescript\nJsx.element\n```\n\n---\n\n```\n \n```\n```rescript\ntype Jsx.element\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Jsx.res%22%2C25%2C0%5D)\n"}}
2626

2727
Hover src/Hover.res 42:15
28-
{"contents": {"kind": "markdown", "value": "```rescript\nstring\n```"}}
28+
{"contents": {"kind": "markdown", "value": "```rescript\nJsx.element\n```\n\n---\n\n```\n \n```\n```rescript\ntype Jsx.element\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Jsx.res%22%2C25%2C0%5D)\n"}}
2929

3030
Hover src/Hover.res 46:10
3131
{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}

tests/analysis_tests/tests/src/expected/InlayHint.res.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ Inlay Hint src/InlayHint.res 1:34
3535
"kind": 1,
3636
"paddingLeft": true,
3737
"paddingRight": false
38+
}, {
39+
"position": {"line": 14, "character": 8},
40+
"label": ": Jsx.element",
41+
"kind": 1,
42+
"paddingLeft": true,
43+
"paddingRight": false
3844
}, {
3945
"position": {"line": 10, "character": 10},
4046
"label": ": (~xx: int) => int",

tests/analysis_tests/tests/src/expected/Jsx2.resi.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Hover src/Jsx2.resi 1:4
2-
{"contents": {"kind": "markdown", "value": "```rescript\nprops<string>\n```\n\n---\n\n```\n \n```\n```rescript\ntype props<'first> = {first: 'first}\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Jsx2.resi%22%2C0%2C0%5D)\n"}}
2+
{"contents": {"kind": "markdown", "value": "```rescript\nJsx.element\n```\n\n---\n\n```\n \n```\n```rescript\ntype Jsx.element\n```\nGo to: [Type definition](command:rescript-vscode.go_to_location?%5B%22Jsx.res%22%2C25%2C0%5D)\n"}}
33

44
Hover src/Jsx2.resi 4:4
55
{"contents": {"kind": "markdown", "value": "```rescript\nint\n```"}}

0 commit comments

Comments
 (0)