Skip to content

Commit 3b172de

Browse files
committed
fix signature of @jsx.component
1 parent f0ebf0d commit 3b172de

File tree

7 files changed

+202
-14
lines changed

7 files changed

+202
-14
lines changed

compiler/syntax/src/jsx_common.ml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ type jsx_config = {
77
mutable nested_modules: string list;
88
mutable has_component: bool;
99
mutable hoisted_structure_items: structure_item list;
10-
(* Nesting depth of [structure] calls while rewriting one implementation.
10+
mutable hoisted_signature_items: signature_item list;
11+
(* Nesting depth of [structure] / [signature] while rewriting one file.
1112
Used so we only clear/append hoisted items at the true file root — not for
12-
nested [Pmod_structure] reached from expression traversal (e.g. via
13-
[module_expr]), which would otherwise see [nested_modules = []] and wipe
14-
hoisted bindings added for earlier structure items. *)
13+
nested [Pmod_structure] / [Pmty_signature] reached from inner traversal,
14+
which would otherwise wipe hoists from earlier items. *)
1515
mutable structure_depth: int;
16-
(* Inside [Pmod_functor] bodies, hoisting [File$M = M.make] at the file top
17-
would reference [M] as a functor (illegal). Skip hoists when > 0. *)
16+
(* Inside [Pmod_functor] / [Pmty_functor] bodies, hoisting [File$M = M.make]
17+
at the file top would reference [M] as a functor (illegal). Skip hoists
18+
when > 0. *)
1819
mutable functor_depth: int;
1920
}
2021

compiler/syntax/src/jsx_ppx.ml

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,37 @@ let get_mapper ~config =
9898
raise e)
9999
| _ -> default_mapper.module_expr mapper me
100100
in
101+
let module_type mapper mt =
102+
match (config.version, mt.pmty_desc) with
103+
| 4, Pmty_functor _ -> (
104+
config.functor_depth <- config.functor_depth + 1;
105+
try
106+
let m = default_mapper.module_type mapper mt in
107+
config.functor_depth <- config.functor_depth - 1;
108+
m
109+
with e ->
110+
config.functor_depth <- config.functor_depth - 1;
111+
raise e)
112+
| _ -> default_mapper.module_type mapper mt
113+
in
114+
let module_declaration mapper md =
115+
match config.version with
116+
| 4 ->
117+
config.nested_modules <- md.pmd_name.txt :: config.nested_modules;
118+
let mapped =
119+
try default_mapper.module_declaration mapper md
120+
with e ->
121+
(match config.nested_modules with
122+
| _ :: rest -> config.nested_modules <- rest
123+
| [] -> ());
124+
raise e
125+
in
126+
(match config.nested_modules with
127+
| _ :: rest -> config.nested_modules <- rest
128+
| [] -> ());
129+
mapped
130+
| _ -> default_mapper.module_declaration mapper md
131+
in
101132
let save_config () =
102133
{
103134
config with
@@ -113,7 +144,10 @@ let get_mapper ~config =
113144
in
114145
let signature mapper items =
115146
let old_config = save_config () in
147+
let is_top_level = config.structure_depth = 0 in
148+
config.structure_depth <- config.structure_depth + 1;
116149
config.has_component <- false;
150+
if is_top_level then config.hoisted_signature_items <- [];
117151
let result =
118152
List.map
119153
(fun item ->
@@ -125,6 +159,12 @@ let get_mapper ~config =
125159
items
126160
|> List.flatten
127161
in
162+
let result =
163+
if config.version = 4 && is_top_level then
164+
result @ List.rev config.hoisted_signature_items
165+
else result
166+
in
167+
config.structure_depth <- config.structure_depth - 1;
128168
restore_config old_config;
129169
result
130170
in
@@ -155,7 +195,16 @@ let get_mapper ~config =
155195
result
156196
in
157197

158-
{default_mapper with expr; module_binding; module_expr; signature; structure}
198+
{
199+
default_mapper with
200+
expr;
201+
module_binding;
202+
module_expr;
203+
module_type;
204+
module_declaration;
205+
signature;
206+
structure;
207+
}
159208

160209
let rewrite_implementation ~jsx_version ~jsx_module (code : Parsetree.structure)
161210
: Parsetree.structure =
@@ -166,6 +215,7 @@ let rewrite_implementation ~jsx_version ~jsx_module (code : Parsetree.structure)
166215
nested_modules = [];
167216
has_component = false;
168217
hoisted_structure_items = [];
218+
hoisted_signature_items = [];
169219
structure_depth = 0;
170220
functor_depth = 0;
171221
}
@@ -182,6 +232,7 @@ let rewrite_signature ~jsx_version ~jsx_module (code : Parsetree.signature) :
182232
nested_modules = [];
183233
has_component = false;
184234
hoisted_structure_items = [];
235+
hoisted_signature_items = [];
185236
structure_depth = 0;
186237
functor_depth = 0;
187238
}

compiler/syntax/src/jsx_v4.ml

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ let longident_of_segments = function
8585
| head :: rest ->
8686
List.fold_left (fun acc name -> Ldot (acc, name)) (Lident head) rest
8787

88+
(* [nested_modules] is the same stack as [config.nested_modules] while inside a
89+
nested [module M: { ... }]: outermost submodule name is at the tail. *)
90+
let props_longident_for_nested_module nested_modules =
91+
match List.rev nested_modules with
92+
| [] -> Lident "props"
93+
| m :: rest ->
94+
let mod_path =
95+
List.fold_left (fun acc name -> Ldot (acc, name)) (Lident m) rest
96+
in
97+
Ldot (mod_path, "props")
98+
8899
let make_hoisted_component_binding ~empty_loc ~full_module_name nested_modules =
89100
let path =
90101
nested_modules |> List.rev |> longident_of_segments |> fun txt ->
@@ -140,6 +151,44 @@ let maybe_hoist_nested_make_component ~(config : Jsx_common.jsx_config)
140151
:: config.hoisted_structure_items
141152
| _ -> ()
142153

154+
let make_hoisted_component_signature ~empty_loc ~full_module_name
155+
(component_type : Parsetree.core_type) =
156+
let marker_name = full_module_name ^ "$jsx" in
157+
let bool_ty =
158+
Typ.constr ~loc:empty_loc {loc = empty_loc; txt = Lident "bool"} []
159+
in
160+
let full_sig =
161+
{
162+
psig_loc = empty_loc;
163+
psig_desc =
164+
Psig_value
165+
(Val.mk ~loc:empty_loc
166+
{loc = empty_loc; txt = full_module_name}
167+
component_type);
168+
}
169+
in
170+
let jsx_sig =
171+
{
172+
psig_loc = empty_loc;
173+
psig_desc =
174+
Psig_value
175+
(Val.mk ~loc:empty_loc {loc = empty_loc; txt = marker_name} bool_ty);
176+
}
177+
in
178+
(full_sig, jsx_sig)
179+
180+
let maybe_hoist_nested_make_signature ~(config : Jsx_common.jsx_config)
181+
~empty_loc ~full_module_name ~component_type fn_name =
182+
match (fn_name, config.nested_modules, config.functor_depth) with
183+
| "make", _ :: _, 0 ->
184+
let full_sig, jsx_sig =
185+
make_hoisted_component_signature ~empty_loc ~full_module_name
186+
component_type
187+
in
188+
config.hoisted_signature_items <-
189+
jsx_sig :: full_sig :: config.hoisted_signature_items
190+
| _ -> ()
191+
143192
(* Build a string representation of a module name with segments separated by $ *)
144193
let make_module_name file_name nested_modules fn_name =
145194
let file_name = unnamespace_module_name file_name in
@@ -1101,7 +1150,8 @@ let transform_signature_item ~config item =
11011150
match item with
11021151
| {
11031152
psig_loc;
1104-
psig_desc = Psig_value ({pval_attributes; pval_type} as psig_desc);
1153+
psig_desc =
1154+
Psig_value ({pval_attributes; pval_type; pval_name} as psig_desc);
11051155
} as psig -> (
11061156
match List.filter Jsx_common.has_attr pval_attributes with
11071157
| [] -> [item]
@@ -1117,15 +1167,25 @@ let transform_signature_item ~config item =
11171167
in
11181168
let prop_types = collect_prop_types [] pval_type in
11191169
let named_type_list = List.fold_left arg_to_concrete_type [] prop_types in
1170+
let props_type_args =
1171+
match core_type_of_attr with
1172+
| None -> make_props_type_params named_type_list
1173+
| Some _ -> (
1174+
match typ_vars_of_core_type with
1175+
| [] -> []
1176+
| _ -> [Typ.any ()])
1177+
in
11201178
let ret_props_type =
11211179
Typ.constr
11221180
(Location.mkloc (Lident "props") psig_loc)
1123-
(match core_type_of_attr with
1124-
| None -> make_props_type_params named_type_list
1125-
| Some _ -> (
1126-
match typ_vars_of_core_type with
1127-
| [] -> []
1128-
| _ -> [Typ.any ()]))
1181+
props_type_args
1182+
in
1183+
let ret_props_type_for_hoist =
1184+
Typ.constr
1185+
(Location.mkloc
1186+
(props_longident_for_nested_module config.nested_modules)
1187+
psig_loc)
1188+
props_type_args
11291189
in
11301190
let external_ = psig_desc.pval_prim <> [] in
11311191
let props_record_type =
@@ -1138,6 +1198,11 @@ let transform_signature_item ~config item =
11381198
( {loc = psig_loc; txt = module_access_name config "component"},
11391199
[ret_props_type] )
11401200
in
1201+
let new_external_type_for_hoist =
1202+
Ptyp_constr
1203+
( {loc = psig_loc; txt = module_access_name config "component"},
1204+
[ret_props_type_for_hoist] )
1205+
in
11411206
let new_structure =
11421207
{
11431208
psig with
@@ -1150,6 +1215,16 @@ let transform_signature_item ~config item =
11501215
};
11511216
}
11521217
in
1218+
let file_name = filename_from_loc psig_loc in
1219+
let empty_loc = Location.in_file file_name in
1220+
let full_module_name =
1221+
make_module_name file_name config.nested_modules pval_name.txt
1222+
in
1223+
let component_type =
1224+
{pval_type with ptyp_desc = new_external_type_for_hoist}
1225+
in
1226+
maybe_hoist_nested_make_signature ~config ~empty_loc ~full_module_name
1227+
~component_type pval_name.txt;
11531228
[props_record_type; new_structure]
11541229
| _ ->
11551230
Jsx_common.raise_error ~loc:psig_loc

tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,12 @@ module Select: {
2222
>,
2323
>
2424
}
25+
let \"FirstClassModules$Select": React.component<
26+
Select.props<
27+
module(T with type t = 'a and type key = 'key),
28+
option<'key>,
29+
option<'key> => unit,
30+
array<'a>,
31+
>,
32+
>
33+
let \"FirstClassModules$Select$jsx": bool

tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,35 @@ module V4AUncurried: {
8989
let make: React.component<props<React.ref<Js.Nullable.t<inputRef>>>>
9090
}
9191
}
92+
let \"ForwardRef$V4C$FancyInput": React.component<
93+
V4C.FancyInput.props<string, React.element, ReactDOM.Ref.currentDomRef>,
94+
>
95+
let \"ForwardRef$V4C$FancyInput$jsx": bool
96+
let \"ForwardRef$V4C$ForwardRef": React.component<
97+
V4C.ForwardRef.props<React.ref<Js.Nullable.t<inputRef>>>,
98+
>
99+
let \"ForwardRef$V4C$ForwardRef$jsx": bool
100+
let \"ForwardRef$V4CUncurried$FancyInput": React.component<
101+
V4CUncurried.FancyInput.props<string, React.element, ReactDOM.Ref.currentDomRef>,
102+
>
103+
let \"ForwardRef$V4CUncurried$FancyInput$jsx": bool
104+
let \"ForwardRef$V4CUncurried$ForwardRef": React.component<
105+
V4CUncurried.ForwardRef.props<React.ref<Js.Nullable.t<inputRef>>>,
106+
>
107+
let \"ForwardRef$V4CUncurried$ForwardRef$jsx": bool
108+
let \"ForwardRef$V4A$FancyInput": React.component<
109+
V4A.FancyInput.props<string, React.element, ReactDOM.Ref.currentDomRef>,
110+
>
111+
let \"ForwardRef$V4A$FancyInput$jsx": bool
112+
let \"ForwardRef$V4A$ForwardRef": React.component<
113+
V4A.ForwardRef.props<React.ref<Js.Nullable.t<inputRef>>>,
114+
>
115+
let \"ForwardRef$V4A$ForwardRef$jsx": bool
116+
let \"ForwardRef$V4AUncurried$FancyInput": React.component<
117+
V4AUncurried.FancyInput.props<string, React.element, ReactDOM.Ref.currentDomRef>,
118+
>
119+
let \"ForwardRef$V4AUncurried$FancyInput$jsx": bool
120+
let \"ForwardRef$V4AUncurried$ForwardRef": React.component<
121+
V4AUncurried.ForwardRef.props<React.ref<Js.Nullable.t<inputRef>>>,
122+
>
123+
let \"ForwardRef$V4AUncurried$ForwardRef$jsx": bool

tests/syntax_tests/data/ppx/react/expected/interface.resi.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ module NoProps: {
1212

1313
let make: React.component<props>
1414
}
15+
let \"Interface$A": React.component<A.props<string>>
16+
let \"Interface$A$jsx": bool
17+
let \"Interface$NoProps": React.component<NoProps.props>
18+
let \"Interface$NoProps$jsx": bool

tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,19 @@ module V4A4: {
4949

5050
let make: React.component<props>
5151
}
52+
let \"SharedProps$V4C1": React.component<V4C1.props>
53+
let \"SharedProps$V4C1$jsx": bool
54+
let \"SharedProps$V4C2": React.component<V4C2.props<_>>
55+
let \"SharedProps$V4C2$jsx": bool
56+
let \"SharedProps$V4C3": React.component<V4C3.props<_>>
57+
let \"SharedProps$V4C3$jsx": bool
58+
let \"SharedProps$V4C4": React.component<V4C4.props>
59+
let \"SharedProps$V4C4$jsx": bool
60+
let \"SharedProps$V4A1": React.component<V4A1.props>
61+
let \"SharedProps$V4A1$jsx": bool
62+
let \"SharedProps$V4A2": React.component<V4A2.props<_>>
63+
let \"SharedProps$V4A2$jsx": bool
64+
let \"SharedProps$V4A3": React.component<V4A3.props<_>>
65+
let \"SharedProps$V4A3$jsx": bool
66+
let \"SharedProps$V4A4": React.component<V4A4.props>
67+
let \"SharedProps$V4A4$jsx": bool

0 commit comments

Comments
 (0)