From add21c27780ece2868bbd5387603115a0528ba85 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 12 Mar 2026 18:46:42 +0100 Subject: [PATCH 01/56] Rewrite nested JSX component paths to direct hoisted exports --- compiler/core/js_of_lam_block.ml | 2 +- compiler/core/lam.ml | 6 +- compiler/core/lam_analysis.ml | 7 +- compiler/core/lam_arity_analysis.ml | 4 +- compiler/core/lam_compat.ml | 2 +- compiler/core/lam_compat.mli | 2 +- compiler/core/lam_compile.ml | 122 ++++++++++++++++++ compiler/core/lam_pass_remove_alias.ml | 3 +- compiler/core/lam_print.ml | 2 +- compiler/ml/lambda.ml | 26 +++- compiler/ml/lambda.mli | 5 +- compiler/ml/printlambda.ml | 2 +- compiler/ml/translcore.ml | 9 +- compiler/ml/translmod.ml | 15 ++- compiler/syntax/src/jsx_common.ml | 1 + compiler/syntax/src/jsx_ppx.ml | 9 ++ compiler/syntax/src/jsx_v4.ml | 41 +++++- .../rsc_nested_jsx_members/input.js | 38 ++++++ .../rsc_nested_jsx_members/rescript.json | 16 +++ .../rsc_nested_jsx_members/src/MainLayout.res | 4 + .../rsc_nested_jsx_members/src/React.res | 30 +++++ .../rsc_nested_jsx_members/src/Sidebar.res | 13 ++ tests/tests/src/alias_default_value_test.mjs | 28 ++++ tests/tests/src/async_jsx.mjs | 8 ++ tests/tests/src/jsx_optional_props_test.mjs | 4 + tests/tests/src/jsxv4_newtype.mjs | 16 +++ 26 files changed, 396 insertions(+), 19 deletions(-) create mode 100644 tests/build_tests/rsc_nested_jsx_members/input.js create mode 100644 tests/build_tests/rsc_nested_jsx_members/rescript.json create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/MainLayout.res create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/React.res create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/Sidebar.res diff --git a/compiler/core/js_of_lam_block.ml b/compiler/core/js_of_lam_block.ml index 850e8ad2f94..bfc943d99f5 100644 --- a/compiler/core/js_of_lam_block.ml +++ b/compiler/core/js_of_lam_block.ml @@ -43,7 +43,7 @@ let field (field_info : Lam_compat.field_dbg_info) e (i : int32) = | Fld_cons -> E.cons_access e i | Fld_record_inline {name} -> E.inline_record_access e name i | Fld_record {name} -> E.record_access e name i - | Fld_module {name} -> E.module_access e name i + | Fld_module {name; jsx_component = _} -> E.module_access e name i let field_by_exp e i = E.array_index e i diff --git a/compiler/core/lam.ml b/compiler/core/lam.ml index 51b8bb3e382..3eb1b227845 100644 --- a/compiler/core/lam.ml +++ b/compiler/core/lam.ml @@ -555,7 +555,8 @@ let prim ~primitive:(prim : Lam_primitive.t) ~args loc : t = | ( f :: fields, Lprim { - primitive = Pfield (pos, Fld_module {name = f1}); + primitive = + Pfield (pos, Fld_module {name = f1; jsx_component = false}); args = [(Lglobal_module (v1, _) | Lvar v1)]; } :: args ) -> @@ -566,7 +567,8 @@ let prim ~primitive:(prim : Lam_primitive.t) ~args loc : t = | ( field1 :: rest, Lprim { - primitive = Pfield (pos, Fld_module {name = f1}); + primitive = + Pfield (pos, Fld_module {name = f1; jsx_component = false}); args = [((Lglobal_module (v1, _) | Lvar v1) as lam)]; } :: args1 ) -> diff --git a/compiler/core/lam_analysis.ml b/compiler/core/lam_analysis.ml index a4b78bea0eb..90f4c6c4cf9 100644 --- a/compiler/core/lam_analysis.ml +++ b/compiler/core/lam_analysis.ml @@ -123,7 +123,12 @@ let rec no_side_effects (lam : Lam.t) : bool = (* | Lsend _ -> false *) | Lapply { - ap_func = Lprim {primitive = Pfield (_, Fld_module {name = "from_fun"})}; + ap_func = + Lprim + { + primitive = + Pfield (_, Fld_module {name = "from_fun"; jsx_component = _}); + }; ap_args = [arg]; } -> no_side_effects arg diff --git a/compiler/core/lam_arity_analysis.ml b/compiler/core/lam_arity_analysis.ml index ee3d91f1541..c3608365049 100644 --- a/compiler/core/lam_arity_analysis.ml +++ b/compiler/core/lam_arity_analysis.ml @@ -42,7 +42,7 @@ let rec get_arity (meta : Lam_stats.t) (lam : Lam.t) : Lam_arity.t = | Llet (_, _, _, l) -> get_arity meta l | Lprim { - primitive = Pfield (_, Fld_module {name}); + primitive = Pfield (_, Fld_module {name; jsx_component = _}); args = [Lglobal_module (id, dynamic_import)]; _; } -> ( @@ -58,7 +58,7 @@ let rec get_arity (meta : Lam_stats.t) (lam : Lam.t) : Lam_arity.t = [ Lprim { - primitive = Pfield (_, Fld_module {name}); + primitive = Pfield (_, Fld_module {name; jsx_component = _}); args = [Lglobal_module (id, dynamic_import)]; }; ]; diff --git a/compiler/core/lam_compat.ml b/compiler/core/lam_compat.ml index a652d74ca43..70c3486c5e9 100644 --- a/compiler/core/lam_compat.ml +++ b/compiler/core/lam_compat.ml @@ -64,7 +64,7 @@ type let_kind = Lambda.let_kind = Strict | Alias | StrictOpt | Variable type field_dbg_info = Lambda.field_dbg_info = | Fld_record of {name: string; mutable_flag: Asttypes.mutable_flag} - | Fld_module of {name: string} + | Fld_module of {name: string; jsx_component: bool} | Fld_record_inline of {name: string} | Fld_record_extension of {name: string} | Fld_tuple diff --git a/compiler/core/lam_compat.mli b/compiler/core/lam_compat.mli index 4d67d95242e..cbea5cd5ea8 100644 --- a/compiler/core/lam_compat.mli +++ b/compiler/core/lam_compat.mli @@ -28,7 +28,7 @@ type let_kind = Lambda.let_kind = Strict | Alias | StrictOpt | Variable type field_dbg_info = Lambda.field_dbg_info = | Fld_record of {name: string; mutable_flag: Asttypes.mutable_flag} - | Fld_module of {name: string} + | Fld_module of {name: string; jsx_component: bool} | Fld_record_inline of {name: string} | Fld_record_extension of {name: string} | Fld_tuple diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 42844bc99c0..966fd29e59c 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -232,6 +232,64 @@ type initialization = J.block *) let compile output_prefix = + let root_module_name (id : Ident.t) = + match String.index_opt id.name '$' with + | Some index -> String.sub id.name 0 index + | None -> id.name + in + let rec extract_nested_external_component_segments segments + ((lam : Lam.t), (make_dynamic_import : bool option ref)) : + (Ident.t * bool * string list) option = + match lam with + | Lprim + { + primitive = Pfield (_, Fld_module {name; jsx_component = _}); + args = [arg]; + _; + } -> + extract_nested_external_component_segments (name :: segments) + (arg, make_dynamic_import) + | Lvar id -> + make_dynamic_import := Some false; + Some (id, false, List.rev segments) + | Lglobal_module (id, dynamic_import) -> + make_dynamic_import := Some dynamic_import; + Some (id, dynamic_import, List.rev segments) + | _ -> None + in + let extract_nested_external_component_field (lam : Lam.t) : + (Ident.t * bool * string) option = + match lam with + | Lprim + { + primitive = Pfield (_, Fld_module {name = "make"; jsx_component = _}); + args = [arg]; + _; + } -> ( + let dynamic_import = ref None in + match + extract_nested_external_component_segments [] (arg, dynamic_import) + with + | Some (id, dynamic_import, segments) -> ( + let segments = + match segments with + | head :: rest + when head = id.name + || head = root_module_name id + || Ext_string.starts_with head (root_module_name id ^ "$") -> + rest + | _ -> segments + in + match segments with + | [] -> None + | _ -> + Some + ( id, + dynamic_import, + String.concat "$" (root_module_name id :: segments) )) + | None -> None) + | _ -> None + in let rec compile_external_field (* Like [List.empty]*) ?(dynamic_import = false) (lamba_cxt : Lam_compile_context.t) (id : Ident.t) name : Js_output.t = @@ -300,6 +358,17 @@ let compile output_prefix = (Ext_list.append block args_code, b :: args) | _ -> assert false) in + let args = + if appinfo.ap_transformed_jsx then + match (appinfo.ap_args, args) with + | jsx_tag :: _, _ :: rest_args -> ( + match extract_nested_external_component_field jsx_tag with + | Some (id, dynamic_import, hidden_name) -> + E.ml_var_dot ~dynamic_import id hidden_name :: rest_args + | None -> args) + | _ -> args + else args + in let fn = E.ml_var_dot ~dynamic_import module_id ident_info.name in let expression = @@ -1524,6 +1593,17 @@ let compile output_prefix = (Ext_list.append block args_code, b :: fn_code) | {value = None} -> assert false) in + let args = + if appinfo.ap_transformed_jsx then + match (appinfo.ap_args, args) with + | jsx_tag :: _, _ :: rest_args -> ( + match extract_nested_external_component_field jsx_tag with + | Some (id, dynamic_import, hidden_name) -> + E.ml_var_dot ~dynamic_import id hidden_name :: rest_args + | None -> args) + | _ -> args + else args + in match (ap_func, lambda_cxt.continuation) with | ( Lvar fn_id, ( EffectCall (Maybe_tail_is_return (Tail_with_name {label = Some ret})) @@ -1583,6 +1663,48 @@ let compile output_prefix = and compile_prim (prim_info : Lam.prim_info) (lambda_cxt : Lam_compile_context.t) = match prim_info with + | { + primitive = + Pjs_call + { + prim_name = "jsx" | "jsxs" | "jsxKeyed" | "jsxsKeyed"; + transformed_jsx = true; + _; + }; + args = jsx_tag :: rest_args; + loc; + } -> + let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in + let tag_block, tag_expr = + match extract_nested_external_component_field jsx_tag with + | Some (id, dynamic_import, hidden_name) -> ( + match + Lam_compile_env.query_external_id_info ~dynamic_import id + (hidden_name ^ "$jsx") + with + | exception Not_found -> ( + match compile_lambda new_cxt jsx_tag with + | {block; value = Some b} -> (block, b) + | {value = None} -> assert false) + | _ -> ([], E.ml_var_dot ~dynamic_import id hidden_name)) + | None -> ( + match compile_lambda new_cxt jsx_tag with + | {block; value = Some b} -> (block, b) + | {value = None} -> assert false) + in + let rest_blocks, rest_exprs = + Ext_list.split_map rest_args (fun x -> + match compile_lambda new_cxt x with + | {block; value = Some b} -> (block, b) + | {value = None} -> assert false) + in + let args_code : J.block = List.concat (tag_block :: rest_blocks) in + let exp = + Lam_compile_primitive.translate output_prefix loc lambda_cxt + prim_info.primitive (tag_expr :: rest_exprs) + in + Js_output.output_of_block_and_expression lambda_cxt.continuation args_code + exp | { primitive = Pfield (_, fld_info); args = [Lglobal_module (id, dynamic_import)]; diff --git a/compiler/core/lam_pass_remove_alias.ml b/compiler/core/lam_pass_remove_alias.ml index 1dad7d3865c..cd72556a775 100644 --- a/compiler/core/lam_pass_remove_alias.ml +++ b/compiler/core/lam_pass_remove_alias.ml @@ -133,7 +133,8 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = ap_func = Lprim { - primitive = Pfield (_, Fld_module {name = fld_name}); + primitive = + Pfield (_, Fld_module {name = fld_name; jsx_component = _}); args = [Lglobal_module (ident, dynamic_import)]; _; } as l1; diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index 172e219abb7..0607724e232 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -323,7 +323,7 @@ let lambda ppf v = fprintf ppf ")@ %a)@]" lam body | Lprim { - primitive = Pfield (n, Fld_module {name = s}); + primitive = Pfield (n, Fld_module {name = s; jsx_component = _}); args = [Lglobal_module (id, dynamic_import)]; _; } -> diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index db810d4f912..540b9204c0a 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -113,7 +113,7 @@ let ref_tag_info : tag_info = type field_dbg_info = | Fld_record of {name: string; mutable_flag: Asttypes.mutable_flag} - | Fld_module of {name: string} + | Fld_module of {name: string; jsx_component: bool} | Fld_record_inline of {name: string} | Fld_record_extension of {name: string} | Fld_tuple @@ -134,6 +134,8 @@ let fld_record_extension (lbl : label) = Fld_record_extension {name = Ext_list.find_def lbl.lbl_attributes find_name lbl.lbl_name} +let fld_module ~name ~jsx_component = Fld_module {name; jsx_component} + let ref_field_info : field_dbg_info = Fld_record {name = "contents"; mutable_flag = Mutable} @@ -620,11 +622,28 @@ let rec transl_normal_path = function else Lvar id | Pdot (p, s, pos) -> Lprim - ( Pfield (pos, Fld_module {name = s}), + ( Pfield (pos, Fld_module {name = s; jsx_component = false}), [transl_normal_path p], Location.none ) | Papply _ -> assert false +let transl_jsx_path path = + let rec aux ~is_final = function + | Path.Pident id -> + if Ident.global id then Lprim (Pgetglobal id, [], Location.none) + else Lvar id + | Pdot (p, s, pos) -> + Lprim + ( Pfield + ( pos, + Fld_module + {name = s; jsx_component = is_final && String.equal s "make"} ), + [aux ~is_final:false p], + Location.none ) + | Papply _ -> assert false + in + aux ~is_final:true path + (* Translation of identifiers *) let transl_module_path ?(loc = Location.none) env path = @@ -633,6 +652,9 @@ let transl_module_path ?(loc = Location.none) env path = let transl_value_path ?(loc = Location.none) env path = transl_normal_path (Env.normalize_path_prefix (Some loc) env path) +let transl_jsx_value_path ?(loc = Location.none) env path = + transl_jsx_path (Env.normalize_path_prefix (Some loc) env path) + let transl_extension_path = transl_value_path (* Apply a substitution to a lambda-term. diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index d8eaf57be6f..3658437d902 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -84,7 +84,7 @@ val ref_tag_info : tag_info type field_dbg_info = | Fld_record of {name: string; mutable_flag: Asttypes.mutable_flag} - | Fld_module of {name: string} + | Fld_module of {name: string; jsx_component: bool} | Fld_record_inline of {name: string} | Fld_record_extension of {name: string} | Fld_tuple @@ -100,6 +100,8 @@ val fld_record_inline : Types.label_description -> field_dbg_info val fld_record_extension : Types.label_description -> field_dbg_info +val fld_module : name:string -> jsx_component:bool -> field_dbg_info + val ref_field_info : field_dbg_info type set_field_dbg_info = @@ -393,6 +395,7 @@ val transl_normal_path : Path.t -> lambda (* Path.t is already normal *) val transl_module_path : ?loc:Location.t -> Env.t -> Path.t -> lambda val transl_value_path : ?loc:Location.t -> Env.t -> Path.t -> lambda +val transl_jsx_value_path : ?loc:Location.t -> Env.t -> Path.t -> lambda val transl_extension_path : ?loc:Location.t -> Env.t -> Path.t -> lambda val subst_lambda : lambda Ident.tbl -> lambda -> lambda diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index 0282f6e113c..ecafa38cd6e 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -72,7 +72,7 @@ let string_of_loc_kind = function let str_of_field_info (fld_info : Lambda.field_dbg_info) = match fld_info with - | Fld_module {name} + | Fld_module {name; jsx_component = _} | Fld_record {name} | Fld_record_inline {name} | Fld_record_extension {name} -> diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 078cbf133a0..670a91a8a82 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -652,6 +652,11 @@ let extract_directive_for_fn exp = if txt = "directive" then Ast_payload.is_single_string payload else None) +let has_jsx_component_path_attr (exp : Typedtree.expression) = + List.exists + (fun ({txt; _}, _) -> String.equal txt "res.jsxComponentPath") + exp.exp_attributes + let rec transl_exp e = List.iter (Translattribute.check_attribute e) e.exp_attributes; transl_exp0 e @@ -661,7 +666,9 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = | Texp_ident (_, _, {val_kind = Val_prim p}) -> transl_primitive e.exp_loc p e.exp_env e.exp_type | Texp_ident (path, _, {val_kind = Val_reg}) -> - transl_value_path ~loc:e.exp_loc e.exp_env path + if has_jsx_component_path_attr e then + transl_jsx_value_path ~loc:e.exp_loc e.exp_env path + else transl_value_path ~loc:e.exp_loc e.exp_env path | Texp_constant cst -> Lconst (Const_base cst) | Texp_let (rec_flag, pat_expr_list, body) -> transl_let rec_flag pat_expr_list (transl_exp body) diff --git a/compiler/ml/translmod.ml b/compiler/ml/translmod.ml index 87471ac26bf..caf2b47dde6 100644 --- a/compiler/ml/translmod.ml +++ b/compiler/ml/translmod.ml @@ -64,14 +64,20 @@ let rec apply_coercion loc strict (restr : Typedtree.module_coercion) arg = | Tcoerce_structure (pos_cc_list, id_pos_list, runtime_fields) -> Lambda.name_lambda strict arg (fun id -> let get_field_name name pos = - Lambda.Lprim (Pfield (pos, Fld_module {name}), [Lvar id], loc) + Lambda.Lprim + ( Pfield (pos, Fld_module {name; jsx_component = false}), + [Lvar id], + loc ) in let lam = Lambda.Lprim ( Pmakeblock (Blk_module runtime_fields), Ext_list.map2 pos_cc_list runtime_fields (fun (pos, cc) name -> apply_coercion loc Alias cc - (Lprim (Pfield (pos, Fld_module {name}), [Lvar id], loc))), + (Lprim + ( Pfield (pos, Fld_module {name; jsx_component = false}), + [Lvar id], + loc ))), loc ) in wrap_id_pos_list loc id_pos_list get_field_name lam) @@ -432,7 +438,10 @@ and transl_structure loc fields cc rootpath final_env = function Pgenval, id, Lprim - ( Pfield (pos, Fld_module {name = Ident.name id}), + ( Pfield + ( pos, + Fld_module {name = Ident.name id; jsx_component = false} + ), [Lvar mid], incl.incl_loc ), body ), diff --git a/compiler/syntax/src/jsx_common.ml b/compiler/syntax/src/jsx_common.ml index a48cf11bc97..e7b8698c701 100644 --- a/compiler/syntax/src/jsx_common.ml +++ b/compiler/syntax/src/jsx_common.ml @@ -6,6 +6,7 @@ type jsx_config = { mutable module_: string; mutable nested_modules: string list; mutable has_component: bool; + mutable hoisted_structure_items: structure_item list; } (* Helper method to look up the [@react.component] attribute *) diff --git a/compiler/syntax/src/jsx_ppx.ml b/compiler/syntax/src/jsx_ppx.ml index 4b05e1995d9..078d812eca9 100644 --- a/compiler/syntax/src/jsx_ppx.ml +++ b/compiler/syntax/src/jsx_ppx.ml @@ -117,7 +117,9 @@ let get_mapper ~config = in let structure mapper items = let old_config = save_config () in + let is_top_level = config.nested_modules = [] in config.has_component <- false; + if is_top_level then config.hoisted_structure_items <- []; let result = List.map (fun item -> @@ -129,6 +131,11 @@ let get_mapper ~config = items |> List.flatten in + let result = + if config.version = 4 && is_top_level then + result @ List.rev config.hoisted_structure_items + else result + in restore_config old_config; result in @@ -143,6 +150,7 @@ let rewrite_implementation ~jsx_version ~jsx_module (code : Parsetree.structure) module_ = jsx_module; nested_modules = []; has_component = false; + hoisted_structure_items = []; } in let mapper = get_mapper ~config in @@ -156,6 +164,7 @@ let rewrite_signature ~jsx_version ~jsx_module (code : Parsetree.signature) : module_ = jsx_module; nested_modules = []; has_component = false; + hoisted_structure_items = []; } in let mapper = get_mapper ~config in diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 83bbb0dcdc4..8f8f0cd4216 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -80,6 +80,34 @@ let make_new_binding binding expression new_name = Jsx_common.raise_error ~loc:pvb_loc "JSX component calls cannot be destructured." +let longident_of_segments = function + | [] -> assert false + | head :: rest -> + List.fold_left (fun acc name -> Ldot (acc, name)) (Lident head) rest + +let make_hoisted_component_binding ~empty_loc ~full_module_name nested_modules = + let path = + nested_modules |> List.rev |> longident_of_segments |> fun txt -> + {loc = empty_loc; txt = Ldot (txt, "make")} + in + let marker_name = full_module_name ^ "$jsx" in + { + pstr_loc = empty_loc; + pstr_desc = + Pstr_value + ( Nonrecursive, + [ + Vb.mk ~loc:empty_loc + (Pat.var ~loc:empty_loc {loc = empty_loc; txt = full_module_name}) + (Exp.ident ~loc:empty_loc path); + Vb.mk ~loc:empty_loc + (Pat.var ~loc:empty_loc {loc = empty_loc; txt = marker_name}) + (Exp.construct ~loc:empty_loc + {loc = empty_loc; txt = Lident "true"} + None); + ] ); + } + (* Lookup the filename from the location information on the AST node and turn it into a valid module identifier *) let filename_from_loc (pstr_loc : Location.t) = let file_name = @@ -216,6 +244,8 @@ let make_type_decls_with_core_type props_name loc core_type typ_vars = let live_attr = ({txt = "live"; loc = Location.none}, PStr []) let jsx_component_props_attr = ({txt = "res.jsxComponentProps"; loc = Location.none}, PStr []) +let jsx_component_path_attr = + ({txt = "res.jsxComponentPath"; loc = Location.none}, PStr []) (* type props<'x, 'y, ...> = { x: 'x, y?: 'y, ... } *) let make_props_record_type ~core_type_of_attr ~external_ ~typ_vars_of_core_type @@ -803,6 +833,15 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding = }, Some (binding_wrapper full_expression) ) in + let () = + match (fn_name, config.nested_modules) with + | "make", _ :: _ -> + config.hoisted_structure_items <- + make_hoisted_component_binding ~empty_loc ~full_module_name + config.nested_modules + :: config.hoisted_structure_items + | _ -> () + in (Some props_record_type, binding, new_binding)) else if Jsx_common.has_attr_on_binding Jsx_common.has_attr_with_props binding then @@ -1286,7 +1325,7 @@ let mk_uppercase_tag_name_expr tag_name = | JsxUpperTag path -> Longident.Ldot (path, "make") in let loc = tag_name.loc in - Exp.ident ~loc {txt = tag_identifier; loc} + Exp.ident ~loc ~attrs:[jsx_component_path_attr] {txt = tag_identifier; loc} let expr ~(config : Jsx_common.jsx_config) mapper expression = match expression with diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js new file mode 100644 index 00000000000..ddec0f4b716 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -0,0 +1,38 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const outputPath = path.join(import.meta.dirname, "src", "MainLayout.res.js"); +const output = await fs.readFile(outputPath, "utf8"); +const sidebarOutputPath = path.join( + import.meta.dirname, + "src", + "Sidebar.res.js", +); +const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); + +assert.match( + output, + /import \* as Sidebar\$RscNestedJsxMembers from "\.\/Sidebar\.res\.js";/, +); +assert.match( + output, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar(?:\$RscNestedJsxMembers)?\$Provider,/, +); +assert.doesNotMatch(output, /\.Provider\.make,/); +assert.match( + sidebarOutput, + /export \{[\s\S]*Provider,[\s\S]*Inset,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset,[\s\S]*\}/s, +); +assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.match(sidebarOutput, /Sidebar\$Inset\$jsx/); + +await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members/rescript.json b/tests/build_tests/rsc_nested_jsx_members/rescript.json new file mode 100644 index 00000000000..b83ca40838d --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-nested-jsx-members", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + }, + "namespace": true +} diff --git a/tests/build_tests/rsc_nested_jsx_members/src/MainLayout.res b/tests/build_tests/rsc_nested_jsx_members/src/MainLayout.res new file mode 100644 index 00000000000..e2c753eeb17 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/MainLayout.res @@ -0,0 +1,4 @@ +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/build_tests/rsc_nested_jsx_members/src/React.res b/tests/build_tests/rsc_nested_jsx_members/src/React.res new file mode 100644 index 00000000000..0a2d41f0096 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/React.res @@ -0,0 +1,30 @@ +type element + +@val external null: element = "null" + +external string: string => element = "%identity" + +external array: array => element = "%identity" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react") +external createElement: (component<'props>, 'props) => element = "createElement" + +@variadic @module("react") +external createElementVariadic: (component<'props>, 'props, array) => element = + "createElement" + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" + +@module("react/jsx-runtime") +external jsxKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsx" + +@module("react/jsx-runtime") +external jsxs: (component<'props>, 'props) => element = "jsxs" + +@module("react/jsx-runtime") +external jsxsKeyed: (component<'props>, 'props, ~key: string=?, @ignore unit) => element = "jsxs" diff --git a/tests/build_tests/rsc_nested_jsx_members/src/Sidebar.res b/tests/build_tests/rsc_nested_jsx_members/src/Sidebar.res new file mode 100644 index 00000000000..a88c10803e2 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/Sidebar.res @@ -0,0 +1,13 @@ +module Provider = { + @react.component + let make = (~children) => { + children + } +} + +module Inset = { + @react.component + let make = (~children) => { + children + } +} diff --git a/tests/tests/src/alias_default_value_test.mjs b/tests/tests/src/alias_default_value_test.mjs index 714b76ab98a..49f6103cef0 100644 --- a/tests/tests/src/alias_default_value_test.mjs +++ b/tests/tests/src/alias_default_value_test.mjs @@ -91,6 +91,20 @@ let C8 = { make: Alias_default_value_test$C8 }; +let Alias_default_value_test$C0$jsx = true; + +let Alias_default_value_test$C1$jsx = true; + +let Alias_default_value_test$C2$jsx = true; + +let Alias_default_value_test$C3$jsx = true; + +let Alias_default_value_test$C4$jsx = true; + +let Alias_default_value_test$C6$jsx = true; + +let Alias_default_value_test$C7$jsx = true; + export { C0, C1, @@ -100,5 +114,19 @@ export { C6, C7, C8, + Alias_default_value_test$C0, + Alias_default_value_test$C0$jsx, + Alias_default_value_test$C1, + Alias_default_value_test$C1$jsx, + Alias_default_value_test$C2, + Alias_default_value_test$C2$jsx, + Alias_default_value_test$C3, + Alias_default_value_test$C3$jsx, + Alias_default_value_test$C4, + Alias_default_value_test$C4$jsx, + Alias_default_value_test$C6, + Alias_default_value_test$C6$jsx, + Alias_default_value_test$C7, + Alias_default_value_test$C7$jsx, } /* No side effect */ diff --git a/tests/tests/src/async_jsx.mjs b/tests/tests/src/async_jsx.mjs index f02ec3cf205..9feec016fcb 100644 --- a/tests/tests/src/async_jsx.mjs +++ b/tests/tests/src/async_jsx.mjs @@ -33,9 +33,17 @@ let Bar = { make: Async_jsx$Bar }; +let Async_jsx$Foo$jsx = true; + +let Async_jsx$Bar$jsx = true; + export { getNow, Foo, Bar, + Async_jsx$Foo, + Async_jsx$Foo$jsx, + Async_jsx$Bar, + Async_jsx$Bar$jsx, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/tests/src/jsx_optional_props_test.mjs b/tests/tests/src/jsx_optional_props_test.mjs index ef3a0d326f1..b6d2b8bc792 100644 --- a/tests/tests/src/jsx_optional_props_test.mjs +++ b/tests/tests/src/jsx_optional_props_test.mjs @@ -16,8 +16,12 @@ let _element = JsxRuntime.jsx(Jsx_optional_props_test$ComponentWithOptionalProps element: JsxRuntime.jsx("div", {}) }); +let Jsx_optional_props_test$ComponentWithOptionalProps$jsx = true; + export { ComponentWithOptionalProps, _element, + Jsx_optional_props_test$ComponentWithOptionalProps, + Jsx_optional_props_test$ComponentWithOptionalProps$jsx, } /* _element Not a pure module */ diff --git a/tests/tests/src/jsxv4_newtype.mjs b/tests/tests/src/jsxv4_newtype.mjs index a6e2b5a2dc7..138850c6d73 100644 --- a/tests/tests/src/jsxv4_newtype.mjs +++ b/tests/tests/src/jsxv4_newtype.mjs @@ -33,10 +33,26 @@ let V4A3 = { make: Jsxv4_newtype$V4A3 }; +let Jsxv4_newtype$V4A$jsx = true; + +let Jsxv4_newtype$V4A1$jsx = true; + +let Jsxv4_newtype$V4A2$jsx = true; + +let Jsxv4_newtype$V4A3$jsx = true; + export { V4A, V4A1, V4A2, V4A3, + Jsxv4_newtype$V4A, + Jsxv4_newtype$V4A$jsx, + Jsxv4_newtype$V4A1, + Jsxv4_newtype$V4A1$jsx, + Jsxv4_newtype$V4A2, + Jsxv4_newtype$V4A2$jsx, + Jsxv4_newtype$V4A3, + Jsxv4_newtype$V4A3$jsx, } /* No side effect */ From 97bafbbab2e59b2d0cb007e6eee78054074e1a58 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 12 Mar 2026 19:03:38 +0100 Subject: [PATCH 02/56] Fix syntax snapshots --- compiler/syntax/src/jsx_v4.ml | 11 +++++++++++ .../data/ppx/react/expected/aliasProps.res.txt | 9 ++++++++- .../data/ppx/react/expected/asyncAwait.res.txt | 2 ++ .../ppx/react/expected/defaultValueProp.res.txt | 4 ++++ .../react/expected/externalWithCustomName.res.txt | 2 +- .../ppx/react/expected/fileLevelConfig.res.txt | 1 + .../data/ppx/react/expected/forwardRef.res.txt | 15 +++++++++++++-- .../data/ppx/react/expected/fragment.res.txt | 8 ++++---- .../data/ppx/react/expected/interface.res.txt | 2 ++ .../data/ppx/react/expected/mangleKeyword.res.txt | 5 +++-- .../data/ppx/react/expected/nested.res.txt | 8 +++++--- .../data/ppx/react/expected/newtype.res.txt | 1 + .../ppx/react/expected/noPropsWithKey.res.txt | 6 ++++-- .../react/expected/optimizeAutomaticMode.res.txt | 1 + .../ppx/react/expected/optionalKeyType.res.txt | 6 +++--- .../ppx/react/expected/returnConstraint.res.txt | 3 +++ .../data/ppx/react/expected/sharedProps.res.txt | 4 ++++ .../data/ppx/react/expected/spreadProps.res.txt | 8 ++++---- .../data/ppx/react/expected/topLevel.res.txt | 1 + .../ppx/react/expected/typeConstraint.res.txt | 1 + .../ppx/react/expected/uncurriedProps.res.txt | 5 ++++- .../data/ppx/react/expected/v4.res.txt | 3 +++ 22 files changed, 83 insertions(+), 23 deletions(-) diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 8f8f0cd4216..c254433ee0f 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -1329,6 +1329,17 @@ let mk_uppercase_tag_name_expr tag_name = let expr ~(config : Jsx_common.jsx_config) mapper expression = match expression with + | {pexp_desc = Pexp_letmodule (name, module_expr, body); pexp_loc = loc; pexp_attributes = attrs} + -> + config.nested_modules <- name.txt :: config.nested_modules; + let mapped_module_expr = default_mapper.module_expr mapper module_expr in + let mapped_body = mapper.expr mapper body in + let () = + match config.nested_modules with + | _ :: rest -> config.nested_modules <- rest + | [] -> () + in + Exp.letmodule ~loc ~attrs name mapped_module_expr mapped_body | { pexp_desc = Pexp_jsx_element jsx_element; pexp_loc = loc; diff --git a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt index f2ae63a276f..106e33b8ff5 100644 --- a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt @@ -155,10 +155,17 @@ module C6 = { } let make = ({comp: module(Comp: Comp), x: (a, b), _}: props<_, _>): React.element => - React.jsx(Comp.make, {}) + React.jsx(@res.jsxComponentPath Comp.make, {}) let make = { let \"AliasProps$C6" = (props: props<_>) => make(props) \"AliasProps$C6" } } +let \"AliasProps$C0" = C0.make and \"AliasProps$C0$jsx" = true +let \"AliasProps$C1" = C1.make and \"AliasProps$C1$jsx" = true +let \"AliasProps$C2" = C2.make and \"AliasProps$C2$jsx" = true +let \"AliasProps$C3" = C3.make and \"AliasProps$C3$jsx" = true +let \"AliasProps$C4" = C4.make and \"AliasProps$C4$jsx" = true +let \"AliasProps$C5" = C5.make and \"AliasProps$C5$jsx" = true +let \"AliasProps$C6" = C6.make and \"AliasProps$C6$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt index d77b11decd3..dea1b17712c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt @@ -35,3 +35,5 @@ module C1 = { \"AsyncAwait$C1" } } +let \"AsyncAwait$C0" = C0.make and \"AsyncAwait$C0$jsx" = true +let \"AsyncAwait$C1" = C1.make and \"AsyncAwait$C1$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt index c8a7759839e..fa928be4406 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt @@ -91,3 +91,7 @@ module C3 = { \"DefaultValueProp$C3" } } +let \"DefaultValueProp$C0" = C0.make and \"DefaultValueProp$C0$jsx" = true +let \"DefaultValueProp$C1" = C1.make and \"DefaultValueProp$C1$jsx" = true +let \"DefaultValueProp$C2" = C2.make and \"DefaultValueProp$C2$jsx" = true +let \"DefaultValueProp$C3" = C3.make and \"DefaultValueProp$C3$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt index dd98fc72ff3..a7b96145c72 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithCustomName.res.txt @@ -11,4 +11,4 @@ module Foo = { external component: React.component> = "component" } -let t = React.jsx(Foo.component, {a: 1, b: {"1"}}) +let t = React.jsx(@res.jsxComponentPath Foo.component, {a: 1, b: {"1"}}) diff --git a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt index 77cde0caea1..2a3dae60b75 100644 --- a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt @@ -15,3 +15,4 @@ module V4A = { \"FileLevelConfig$V4A" } } +let \"FileLevelConfig$V4A" = V4A.make and \"FileLevelConfig$V4A$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt index 8cd3bcff8ce..abe2a0b7e9e 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt @@ -45,7 +45,10 @@ module V4A = { "div", { children: ?ReactDOM.someElement( - React.jsx(FancyInput.make, {ref: input, children: {React.string("Click to focus")}}), + React.jsx( + @res.jsxComponentPath FancyInput.make, + {ref: input, children: {React.string("Click to focus")}}, + ), ), }, ): React.element @@ -103,7 +106,10 @@ module V4AUncurried = { "div", { children: ?ReactDOM.someElement( - React.jsx(FancyInput.make, {ref: input, children: {React.string("Click to focus")}}), + React.jsx( + @res.jsxComponentPath FancyInput.make, + {ref: input, children: {React.string("Click to focus")}}, + ), ), }, ): React.element @@ -115,3 +121,8 @@ module V4AUncurried = { \"ForwardRef$V4AUncurried" } } +let \"ForwardRef$V4A$FancyInput" = V4A.FancyInput.make and \"ForwardRef$V4A$FancyInput$jsx" = true +let \"ForwardRef$V4A" = V4A.make and \"ForwardRef$V4A$jsx" = true +let \"ForwardRef$V4AUncurried$FancyInput" = V4AUncurried.FancyInput.make +and \"ForwardRef$V4AUncurried$FancyInput$jsx" = true +let \"ForwardRef$V4AUncurried" = V4AUncurried.make and \"ForwardRef$V4AUncurried$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/fragment.res.txt b/tests/syntax_tests/data/ppx/react/expected/fragment.res.txt index 80c3bb1d868..3af2218962c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/fragment.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/fragment.res.txt @@ -7,11 +7,11 @@ let _ = React.jsxs( {children: React.array([ReactDOM.jsx("div", {}), ReactDOM.jsx("div", {})])}, ) let _ = React.jsx(React.jsxFragment, {children: React.jsx(React.jsxFragment, {})}) -let _ = React.jsx(Z.make, {}) -let _ = React.jsx(Z.make, {children: ReactDOM.jsx("div", {})}) -let _ = React.jsx(Z.make, {a: "a", children: ReactDOM.jsx("div", {})}) +let _ = React.jsx(@res.jsxComponentPath Z.make, {}) +let _ = React.jsx(@res.jsxComponentPath Z.make, {children: ReactDOM.jsx("div", {})}) +let _ = React.jsx(@res.jsxComponentPath Z.make, {a: "a", children: ReactDOM.jsx("div", {})}) let _ = React.jsxs( - Z.make, + @res.jsxComponentPath Z.make, {children: React.array([ReactDOM.jsx("div", {}), ReactDOM.jsx("div", {})])}, ) let _ = ReactDOM.jsx("div", {}) diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt index df0f7137fb5..363f9439a0a 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt @@ -21,3 +21,5 @@ module NoProps = { \"Interface$NoProps" } } +let \"Interface$A" = A.make and \"Interface$A$jsx" = true +let \"Interface$NoProps" = NoProps.make and \"Interface$NoProps$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt index f1e40360ec5..85a8e1a0302 100644 --- a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt @@ -25,5 +25,6 @@ module C4A1 = { external make: React.component> = "default" } -let c4a0 = React.jsx(C4A0.make, {_open: "x", _type: "t"}) -let c4a1 = React.jsx(C4A1.make, {_open: "x", _type: "t"}) +let c4a0 = React.jsx(@res.jsxComponentPath C4A0.make, {_open: "x", _type: "t"}) +let c4a1 = React.jsx(@res.jsxComponentPath C4A1.make, {_open: "x", _type: "t"}) +let \"MangleKeyword$C4A0" = C4A0.make and \"MangleKeyword$C4A0$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt index 70f8efde0e3..6cb92900d5e 100644 --- a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt @@ -8,16 +8,18 @@ module Outer = { let make = (_: props): React.element => ReactDOM.jsx("div", {}) let make = { - let \"Nested$Outer" = props => make(props) + let \"Nested$Outer$Inner" = props => make(props) - \"Nested$Outer" + \"Nested$Outer$Inner" } } - React.jsx(Inner.make, {}) + React.jsx(@res.jsxComponentPath Inner.make, {}) } let make = { let \"Nested$Outer" = props => make(props) \"Nested$Outer" } } +let \"Nested$Outer$Inner" = Outer.Inner.make and \"Nested$Outer$Inner$jsx" = true +let \"Nested$Outer" = Outer.make and \"Nested$Outer$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt index 2fdecb86740..838fd0daa57 100644 --- a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt @@ -99,3 +99,4 @@ module Uncurried = { \"Newtype$Uncurried" } } +let \"Newtype$Uncurried" = Uncurried.make and \"Newtype$Uncurried$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt index aa8dc64f353..f6efd90c3b4 100644 --- a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt @@ -29,8 +29,8 @@ module V4C = { React.jsxFragment, { children: React.array([ - React.jsxKeyed(V4CA.make, {}, ~key="k", ()), - React.jsxKeyed(V4CB.make, {}, ~key="k", ()), + React.jsxKeyed(@res.jsxComponentPath V4CA.make, {}, ~key="k", ()), + React.jsxKeyed(@res.jsxComponentPath V4CB.make, {}, ~key="k", ()), ]), }, ) @@ -40,3 +40,5 @@ module V4C = { \"NoPropsWithKey$V4C" } } +let \"NoPropsWithKey$V4CA" = V4CA.make and \"NoPropsWithKey$V4CA$jsx" = true +let \"NoPropsWithKey$V4C" = V4C.make and \"NoPropsWithKey$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt index dfb3506590e..7797329571a 100644 --- a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt @@ -18,3 +18,4 @@ module User = { \"OptimizeAutomaticMode$User" } } +let \"OptimizeAutomaticMode$User" = User.make and \"OptimizeAutomaticMode$User$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/optionalKeyType.res.txt b/tests/syntax_tests/data/ppx/react/expected/optionalKeyType.res.txt index 0420bce343e..24d5b15c217 100644 --- a/tests/syntax_tests/data/ppx/react/expected/optionalKeyType.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/optionalKeyType.res.txt @@ -2,9 +2,9 @@ let key = None @@jsxConfig({version: 4}) -let _ = React.jsxKeyed(C.make, {}, ~key="k", ()) -let _ = React.jsxKeyed(C.make, {}, ~key=?Some("k"), ()) -let _ = React.jsxKeyed(C.make, {}, ~key?, ()) +let _ = React.jsxKeyed(@res.jsxComponentPath C.make, {}, ~key="k", ()) +let _ = React.jsxKeyed(@res.jsxComponentPath C.make, {}, ~key=?Some("k"), ()) +let _ = React.jsxKeyed(@res.jsxComponentPath C.make, {}, ~key?, ()) let _ = ReactDOM.jsxKeyed("div", {}, ~key="k", ()) let _ = ReactDOM.jsxKeyed("div", {}, ~key=?Some("k"), ()) let _ = ReactDOM.jsxKeyed("div", {}, ~key?, ()) diff --git a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt index 7d017b6d9b7..84cd1b66fd8 100644 --- a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt @@ -47,3 +47,6 @@ module Async = { \"ReturnConstraint$Async" } } +let \"ReturnConstraint$Standard" = Standard.make and \"ReturnConstraint$Standard$jsx" = true +let \"ReturnConstraint$ForwardRef" = ForwardRef.make and \"ReturnConstraint$ForwardRef$jsx" = true +let \"ReturnConstraint$Async" = Async.make and \"ReturnConstraint$Async$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt index 83c7a5fb041..63c7f492103 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt @@ -67,3 +67,7 @@ module V4A8 = { external make: React.component = "default" } +let \"SharedProps$V4A1" = V4A1.make and \"SharedProps$V4A1$jsx" = true +let \"SharedProps$V4A2" = V4A2.make and \"SharedProps$V4A2$jsx" = true +let \"SharedProps$V4A3" = V4A3.make and \"SharedProps$V4A3$jsx" = true +let \"SharedProps$V4A4" = V4A4.make and \"SharedProps$V4A4$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/spreadProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/spreadProps.res.txt index db116ee8d81..acc79ec0fee 100644 --- a/tests/syntax_tests/data/ppx/react/expected/spreadProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/spreadProps.res.txt @@ -6,10 +6,10 @@ // let c0 = // only spread props -let c1 = React.jsx(A.make, p) +let c1 = React.jsx(@res.jsxComponentPath A.make, p) // reversed order -let c2 = React.jsx(A.make, {...p, x: "x"}) +let c2 = React.jsx(@res.jsxComponentPath A.make, {...p, x: "x"}) let c3 = ReactDOM.jsx("div", p) @@ -23,5 +23,5 @@ let c5 = ReactDOM.jsxsKeyed( ) // both need to be parsed -let c6 = React.jsx(A.make, params->Obj.magic) -let c7 = React.jsx(A.make, params->Obj.magic) +let c6 = React.jsx(@res.jsxComponentPath A.make, params->Obj.magic) +let c7 = React.jsx(@res.jsxComponentPath A.make, params->Obj.magic) diff --git a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt index 9330346a961..cc329602999 100644 --- a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt @@ -17,3 +17,4 @@ module V4A = { \"TopLevel$V4A" } } +let \"TopLevel$V4A" = V4A.make and \"TopLevel$V4A$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt index 68a3b0278b3..92432c4be8c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt @@ -14,3 +14,4 @@ module V4A = { \"TypeConstraint$V4A" } } +let \"TypeConstraint$V4A" = V4A.make and \"TypeConstraint$V4A$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt index 9d81169bd1e..77a78d4f6ff 100644 --- a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt @@ -57,10 +57,13 @@ module Bar = { @res.jsxComponentProps type props = {} - let make = (_: props): React.element => React.jsx(Foo.make, {callback: {(_, _, _) => ()}}) + let make = (_: props): React.element => + React.jsx(@res.jsxComponentPath Foo.make, {callback: {(_, _, _) => ()}}) let make = { let \"UncurriedProps$Bar" = props => make(props) \"UncurriedProps$Bar" } } +let \"UncurriedProps$Foo" = Foo.make and \"UncurriedProps$Foo$jsx" = true +let \"UncurriedProps$Bar" = Bar.make and \"UncurriedProps$Bar$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt index a6f05b0e17f..2a8c1348d8f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt @@ -116,3 +116,6 @@ module Rec2 = { } and mm = x => make(x) } +let \"V4$Rec" = Rec.make and \"V4$Rec$jsx" = true +let \"V4$Rec1" = Rec1.make and \"V4$Rec1$jsx" = true +let \"V4$Rec2" = Rec2.make and \"V4$Rec2$jsx" = true From fb1a22faea4b8936a41f2c0e77c44e0c900b37df Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 12 Mar 2026 19:08:00 +0100 Subject: [PATCH 03/56] Format --- compiler/syntax/src/jsx_v4.ml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index c254433ee0f..6ecb8aa444e 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -1329,8 +1329,11 @@ let mk_uppercase_tag_name_expr tag_name = let expr ~(config : Jsx_common.jsx_config) mapper expression = match expression with - | {pexp_desc = Pexp_letmodule (name, module_expr, body); pexp_loc = loc; pexp_attributes = attrs} - -> + | { + pexp_desc = Pexp_letmodule (name, module_expr, body); + pexp_loc = loc; + pexp_attributes = attrs; + } -> config.nested_modules <- name.txt :: config.nested_modules; let mapped_module_expr = default_mapper.module_expr mapper module_expr in let mapped_body = mapper.expr mapper body in From 27bd27cb6555cbf49f2b07b1d2d104ffafe53d6d Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 12 Mar 2026 19:15:08 +0100 Subject: [PATCH 04/56] Fix gentype tests --- .../typescript-react-example/src/Hooks.res.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js index 8669c8eafed..7eafb7e5f95 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js @@ -204,6 +204,10 @@ let make$1 = Hooks; let $$default = Hooks; +let Hooks$RenderPropRequiresConversion$jsx = true; + +let Hooks$DD$jsx = true; + export { make$1 as make, $$default as default, @@ -219,5 +223,9 @@ export { RenderPropRequiresConversion, WithChildren, DD, + Hooks$RenderPropRequiresConversion, + Hooks$RenderPropRequiresConversion$jsx, + Hooks$DD, + Hooks$DD$jsx, } /* make Not a pure module */ From e20ac3b136078ecf8f910ef775d0f9d1e03d9d40 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 12 Mar 2026 22:48:01 +0100 Subject: [PATCH 05/56] Fix rewatch test --- rewatch/tests/watch/06-watch-missing-source-folder.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rewatch/tests/watch/06-watch-missing-source-folder.sh b/rewatch/tests/watch/06-watch-missing-source-folder.sh index 08436c4fe19..5240152d6b3 100755 --- a/rewatch/tests/watch/06-watch-missing-source-folder.sh +++ b/rewatch/tests/watch/06-watch-missing-source-folder.sh @@ -54,7 +54,11 @@ fi # where the config change triggers a full rebuild that runs concurrently # with the subsequent `rewatch build`. exit_watcher -sleep 1 +if ! wait_for_file_gone "lib/rescript.lock" 20; then + error "Watcher did not stop in time" + git checkout "$DEP01_CONFIG" + exit 1 +fi # Restore dep01's rescript.json git checkout "$DEP01_CONFIG" From f78664b431ef2380eb6ceb8d6014600553279540 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 12 Mar 2026 22:59:13 +0100 Subject: [PATCH 06/56] Another fixed snapshot --- .../tests/src/expected/CreateInterface.res.txt | 4 ++++ tests/analysis_tests/tests/src/expected/JsxV4.res.txt | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt index 998459a8a1c..e71ea422ce1 100644 --- a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt +++ b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt @@ -132,4 +132,8 @@ module OrderedSet: { } let make: (~id: module(Belt.Id.Comparable with type identity = 'a and type t = 'b)) => t<'b, 'a> } +let CreateInterface$ComponentWithPolyProp: ComponentWithPolyProp.props< + [< #large | #small], +> => React.element +let CreateInterface$ComponentWithPolyProp$jsx: bool diff --git a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt index ea1e781cb4e..45682c0a53c 100644 --- a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt +++ b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt @@ -32,4 +32,10 @@ module Other: { @react.component let make: (~name: string) => React.element } +let JsxV4$M4: M4.props => React.element +let JsxV4$M4$jsx: bool +let JsxV4$MM: MM.props => React.element +let JsxV4$MM$jsx: bool +let JsxV4$Other: Other.props => React.element +let JsxV4$Other$jsx: bool From 2bdb0e517f28f8c10f56794408c683c1f47556b9 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 13 Mar 2026 10:35:52 +0100 Subject: [PATCH 07/56] Fix namespace handling --- compiler/core/lam_compile.ml | 124 ++++++++++++++---- compiler/syntax/src/jsx_v4.ml | 9 ++ .../rsc_nested_jsx_members/input.js | 6 +- 3 files changed, 109 insertions(+), 30 deletions(-) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 966fd29e59c..e4b84587962 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -233,9 +233,12 @@ type initialization = J.block let compile output_prefix = let root_module_name (id : Ident.t) = - match String.index_opt id.name '$' with - | Some index -> String.sub id.name 0 index - | None -> id.name + match Ext_namespace.try_split_module_name id.name with + | Some (_namespace, module_name) -> module_name + | None -> ( + match String.index_opt id.name '$' with + | Some index -> String.sub id.name 0 index + | None -> id.name) in let rec extract_nested_external_component_segments segments ((lam : Lam.t), (make_dynamic_import : bool option ref)) : @@ -271,6 +274,16 @@ let compile output_prefix = extract_nested_external_component_segments [] (arg, dynamic_import) with | Some (id, dynamic_import, segments) -> ( + let denamespace_segment segment = + let root_name = root_module_name id in + let namespaced_prefix = root_name ^ "$" in + if Ext_string.starts_with segment namespaced_prefix then + match String.split_on_char '$' segment with + | root :: _namespace :: rest when rest <> [] -> + String.concat "$" (root :: rest) + | _ -> segment + else segment + in let segments = match segments with | head :: rest @@ -280,6 +293,11 @@ let compile output_prefix = rest | _ -> segments in + let segments = + match segments with + | head :: rest -> denamespace_segment head :: rest + | [] -> [] + in match segments with | [] -> None | _ -> @@ -290,6 +308,72 @@ let compile output_prefix = | None -> None) | _ -> None in + let normalize_hidden_component_name (id : Ident.t) (hidden_name : string) = + let root_name = root_module_name id in + let id_parts = String.split_on_char '$' id.name in + let namespace_parts = + match id_parts with + | _root :: rest -> rest + | [] -> [] + in + let hidden_parts = String.split_on_char '$' hidden_name in + let hidden_parts_without_root = + match hidden_parts with + | first :: rest when String.equal first root_name -> rest + | _ -> hidden_parts + in + let rec drop_prefix prefix parts = + match (prefix, parts) with + | [], _ -> parts + | x :: xs, y :: ys when String.equal x y -> drop_prefix xs ys + | _ -> parts + in + let tail = drop_prefix namespace_parts hidden_parts_without_root in + match tail with + | [] -> hidden_name + | _ -> String.concat "$" (root_name :: tail) + in + let hidden_component_name_candidates (id : Ident.t) (hidden_name : string) = + let candidates = ref [] in + let push candidate = + if not (List.mem candidate !candidates) then + candidates := candidate :: !candidates + in + (match String.split_on_char '$' hidden_name with + | root :: _namespace :: rest when rest <> [] -> + push (String.concat "$" (root :: rest)) + | _ -> ()); + push (normalize_hidden_component_name id hidden_name); + push hidden_name; + List.rev !candidates + in + let rewrite_nested_jsx_component_expr (jsx_tag : Lam.t) + (compiled_expr : J.expression) : J.expression = + let rec extract_module_id (expr : J.expression) = + match expr.expression_desc with + | Var (Qualified (module_id, _)) -> Some module_id + | Static_index (inner, _, _) -> extract_module_id inner + | _ -> None + in + match extract_nested_external_component_field jsx_tag with + | Some (id, dynamic_import, hidden_name) -> ( + let hidden_name_candidates = + hidden_component_name_candidates id hidden_name + in + let resolved_hidden_name = + match hidden_name_candidates with + | candidate :: _ -> Some candidate + | [] -> None + in + match (resolved_hidden_name, extract_module_id compiled_expr) with + | Some hidden_name, Some module_id -> + { + compiled_expr with + expression_desc = Var (Qualified (module_id, Some hidden_name)); + } + | _ -> compiled_expr) + | None -> compiled_expr + in let rec compile_external_field (* Like [List.empty]*) ?(dynamic_import = false) (lamba_cxt : Lam_compile_context.t) (id : Ident.t) name : Js_output.t = @@ -361,15 +445,11 @@ let compile output_prefix = let args = if appinfo.ap_transformed_jsx then match (appinfo.ap_args, args) with - | jsx_tag :: _, _ :: rest_args -> ( - match extract_nested_external_component_field jsx_tag with - | Some (id, dynamic_import, hidden_name) -> - E.ml_var_dot ~dynamic_import id hidden_name :: rest_args - | None -> args) + | jsx_tag :: _, jsx_expr :: rest_args -> + rewrite_nested_jsx_component_expr jsx_tag jsx_expr :: rest_args | _ -> args else args in - let fn = E.ml_var_dot ~dynamic_import module_id ident_info.name in let expression = match appinfo.ap_info.ap_status with @@ -1596,11 +1676,8 @@ let compile output_prefix = let args = if appinfo.ap_transformed_jsx then match (appinfo.ap_args, args) with - | jsx_tag :: _, _ :: rest_args -> ( - match extract_nested_external_component_field jsx_tag with - | Some (id, dynamic_import, hidden_name) -> - E.ml_var_dot ~dynamic_import id hidden_name :: rest_args - | None -> args) + | jsx_tag :: _, jsx_expr :: rest_args -> + rewrite_nested_jsx_component_expr jsx_tag jsx_expr :: rest_args | _ -> args else args in @@ -1676,21 +1753,10 @@ let compile output_prefix = } -> let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in let tag_block, tag_expr = - match extract_nested_external_component_field jsx_tag with - | Some (id, dynamic_import, hidden_name) -> ( - match - Lam_compile_env.query_external_id_info ~dynamic_import id - (hidden_name ^ "$jsx") - with - | exception Not_found -> ( - match compile_lambda new_cxt jsx_tag with - | {block; value = Some b} -> (block, b) - | {value = None} -> assert false) - | _ -> ([], E.ml_var_dot ~dynamic_import id hidden_name)) - | None -> ( - match compile_lambda new_cxt jsx_tag with - | {block; value = Some b} -> (block, b) - | {value = None} -> assert false) + match compile_lambda new_cxt jsx_tag with + | {block; value = Some b} -> + (block, rewrite_nested_jsx_component_expr jsx_tag b) + | {value = None} -> assert false in let rest_blocks, rest_exprs = Ext_list.split_map rest_args (fun x -> diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 6ecb8aa444e..b52909acb04 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -122,8 +122,17 @@ let filename_from_loc (pstr_loc : Location.t) = let file_name = String.capitalize_ascii file_name in file_name +let unnamespace_module_name file_name = + match String.index_opt file_name '$' with + | Some index -> String.sub file_name 0 index + | None -> ( + match Ext_namespace.try_split_module_name file_name with + | Some (module_name, _namespace) -> module_name + | None -> file_name) + (* Build a string representation of a module name with segments separated by $ *) let make_module_name file_name nested_modules fn_name = + let file_name = unnamespace_module_name file_name in let full_module_name = match (file_name, nested_modules, fn_name) with (* TODO: is this even reachable? It seems like the fileName always exists *) diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index ddec0f4b716..8992279572b 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -25,7 +25,11 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar(?:\$RscNestedJsxMembers)?\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider,/, +); +assert.doesNotMatch( + output, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$RscNestedJsxMembers\$Provider,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); assert.match( From 0b45ebb5e57f51c2b17ded2e4c939554ca1b13ac Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 13 Mar 2026 10:54:02 +0100 Subject: [PATCH 08/56] Fix possible race condition in rewatch test --- .../watch/06-watch-missing-source-folder.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/rewatch/tests/watch/06-watch-missing-source-folder.sh b/rewatch/tests/watch/06-watch-missing-source-folder.sh index 5240152d6b3..11d4336c4ca 100755 --- a/rewatch/tests/watch/06-watch-missing-source-folder.sh +++ b/rewatch/tests/watch/06-watch-missing-source-folder.sh @@ -65,7 +65,23 @@ git checkout "$DEP01_CONFIG" # Rebuild to regenerate any artifacts that were removed by `rewatch clean` # but not rebuilt due to the modified config (e.g. Dep01.mjs). -rewatch build > /dev/null 2>&1 +if ! rewatch build > /dev/null 2>&1; then + error "Rebuild after restoring config failed" + rm -f rewatch.log + exit 1 +fi + +# Slow CI runners can still be catching up on file restoration when the build +# command returns. Wait until git no longer sees tracked deletions before doing +# the final cleanliness check. +timeout=20 +while [ "$timeout" -gt 0 ]; do + if [ -z "$(git diff --name-only --diff-filter=D .)" ]; then + break + fi + sleep 1 + timeout=$((timeout - 1)) +done rm -f rewatch.log if git diff --exit-code . > /dev/null 2>&1 && [ -z "$(git ls-files --others --exclude-standard .)" ]; From 22003b4d30f35c2e860dfaa5e702ad25738e1fd8 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 13 Mar 2026 13:00:02 +0100 Subject: [PATCH 09/56] Copilot comment fixes --- compiler/core/lam_compile.ml | 43 +++++++++++++------ compiler/syntax/src/jsx_v4.ml | 33 ++++++++++---- .../rsc_nested_jsx_members/input.js | 27 ++++++++++++ .../src/MainLayoutExternal.res | 4 ++ .../src/SidebarExternal.res | 4 ++ .../src/SidebarExternalImpl.js | 3 ++ tests/tests/src/ExternalArity.mjs | 6 +++ 7 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/MainLayoutExternal.res create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/SidebarExternal.res create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/SidebarExternalImpl.js diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index e4b84587962..b3c7da6e1ec 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -347,6 +347,23 @@ let compile output_prefix = push hidden_name; List.rev !candidates in + let exported_hidden_component_name (module_id : J.module_id) + (hidden_name_candidates : string list) = + let rec loop = function + | [] -> None + | candidate :: rest -> ( + match + Lam_compile_env.query_external_id_info + ~dynamic_import:module_id.dynamic_import module_id.id + (candidate ^ "$jsx") + with + | _ -> Some candidate + | exception Not_found -> loop rest) + in + match module_id.kind with + | Ml -> loop hidden_name_candidates + | _ -> None + in let rewrite_nested_jsx_component_expr (jsx_tag : Lam.t) (compiled_expr : J.expression) : J.expression = let rec extract_module_id (expr : J.expression) = @@ -356,22 +373,22 @@ let compile output_prefix = | _ -> None in match extract_nested_external_component_field jsx_tag with - | Some (id, dynamic_import, hidden_name) -> ( + | Some (id, _dynamic_import, hidden_name) -> ( let hidden_name_candidates = hidden_component_name_candidates id hidden_name in - let resolved_hidden_name = - match hidden_name_candidates with - | candidate :: _ -> Some candidate - | [] -> None - in - match (resolved_hidden_name, extract_module_id compiled_expr) with - | Some hidden_name, Some module_id -> - { - compiled_expr with - expression_desc = Var (Qualified (module_id, Some hidden_name)); - } - | _ -> compiled_expr) + match extract_module_id compiled_expr with + | Some module_id -> ( + match + exported_hidden_component_name module_id hidden_name_candidates + with + | Some hidden_name -> + { + compiled_expr with + expression_desc = Var (Qualified (module_id, Some hidden_name)); + } + | None -> compiled_expr) + | None -> compiled_expr) | None -> compiled_expr in let rec compile_external_field (* Like [List.empty]*) diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index b52909acb04..9c5a88feaaa 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -127,9 +127,19 @@ let unnamespace_module_name file_name = | Some index -> String.sub file_name 0 index | None -> ( match Ext_namespace.try_split_module_name file_name with - | Some (module_name, _namespace) -> module_name + | Some (_namespace, module_name) -> module_name | None -> file_name) +let maybe_hoist_nested_make_component ~(config : Jsx_common.jsx_config) + ~empty_loc ~full_module_name fn_name = + match (fn_name, config.nested_modules) with + | "make", _ :: _ -> + config.hoisted_structure_items <- + make_hoisted_component_binding ~empty_loc ~full_module_name + config.nested_modules + :: config.hoisted_structure_items + | _ -> () + (* Build a string representation of a module name with segments separated by $ *) let make_module_name file_name nested_modules fn_name = let file_name = unnamespace_module_name file_name in @@ -843,13 +853,8 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding = Some (binding_wrapper full_expression) ) in let () = - match (fn_name, config.nested_modules) with - | "make", _ :: _ -> - config.hoisted_structure_items <- - make_hoisted_component_binding ~empty_loc ~full_module_name - config.nested_modules - :: config.hoisted_structure_items - | _ -> () + maybe_hoist_nested_make_component ~config ~empty_loc ~full_module_name + fn_name in (Some props_record_type, binding, new_binding)) else if Jsx_common.has_attr_on_binding Jsx_common.has_attr_with_props binding @@ -981,7 +986,8 @@ let transform_structure_item ~config item = | { pstr_loc; pstr_desc = - Pstr_primitive ({pval_attributes; pval_type} as value_description); + Pstr_primitive + ({pval_attributes; pval_type; pval_name} as value_description); } as pstr -> ( match ( List.filter Jsx_common.has_attr pval_attributes, @@ -1042,6 +1048,15 @@ let transform_structure_item ~config item = }; } in + let file_name = filename_from_loc pstr_loc in + let empty_loc = Location.in_file file_name in + let full_module_name = + make_module_name file_name config.nested_modules pval_name.txt + in + let () = + maybe_hoist_nested_make_component ~config ~empty_loc ~full_module_name + pval_name.txt + in [props_record_type; new_structure] | _ -> Jsx_common.raise_error ~loc:pstr_loc diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 8992279572b..dda8ebdcd09 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -18,6 +18,21 @@ const sidebarOutputPath = path.join( "Sidebar.res.js", ); const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); +const externalOutputPath = path.join( + import.meta.dirname, + "src", + "MainLayoutExternal.res.js", +); +const externalOutput = await fs.readFile(externalOutputPath, "utf8"); +const externalSidebarOutputPath = path.join( + import.meta.dirname, + "src", + "SidebarExternal.res.js", +); +const externalSidebarOutput = await fs.readFile( + externalSidebarOutputPath, + "utf8", +); assert.match( output, @@ -27,10 +42,18 @@ assert.match( output, /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider,/, ); +assert.match( + externalOutput, + /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.SidebarExternal\$Provider,/, +); assert.doesNotMatch( output, /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$RscNestedJsxMembers\$Provider,/, ); +assert.doesNotMatch( + externalOutput, + /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.Provider\.make,/, +); assert.doesNotMatch(output, /\.Provider\.make,/); assert.match( sidebarOutput, @@ -38,5 +61,9 @@ assert.match( ); assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); assert.match(sidebarOutput, /Sidebar\$Inset\$jsx/); +assert.match( + externalSidebarOutput, + /export \{[\s\S]*Provider,[\s\S]*SidebarExternal\$Provider,[\s\S]*SidebarExternal\$Provider\$jsx[\s\S]*\}/s, +); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members/src/MainLayoutExternal.res b/tests/build_tests/rsc_nested_jsx_members/src/MainLayoutExternal.res new file mode 100644 index 00000000000..1a25cad0b9a --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/MainLayoutExternal.res @@ -0,0 +1,4 @@ +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/build_tests/rsc_nested_jsx_members/src/SidebarExternal.res b/tests/build_tests/rsc_nested_jsx_members/src/SidebarExternal.res new file mode 100644 index 00000000000..f0015e98896 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/SidebarExternal.res @@ -0,0 +1,4 @@ +module Provider = { + @react.component @module("./SidebarExternalImpl.js") + external make: (~children: React.element=?) => React.element = "default" +} diff --git a/tests/build_tests/rsc_nested_jsx_members/src/SidebarExternalImpl.js b/tests/build_tests/rsc_nested_jsx_members/src/SidebarExternalImpl.js new file mode 100644 index 00000000000..f94e84e1aa9 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/SidebarExternalImpl.js @@ -0,0 +1,3 @@ +export default function SidebarExternalImpl(props) { + return props.children ?? null; +} diff --git a/tests/tests/src/ExternalArity.mjs b/tests/tests/src/ExternalArity.mjs index 1de3ab38f2d..9f669373f66 100644 --- a/tests/tests/src/ExternalArity.mjs +++ b/tests/tests/src/ExternalArity.mjs @@ -37,10 +37,16 @@ let ReactTest = { FormattedMessage: FormattedMessage }; +let ExternalArity$ReactTest$FormattedMessage = ReactIntl.FormattedMessage; + +let ExternalArity$ReactTest$FormattedMessage$jsx = true; + export { f1, f2, FromTypeConstructor, ReactTest, + ExternalArity$ReactTest$FormattedMessage, + ExternalArity$ReactTest$FormattedMessage$jsx, } /* test1 Not a pure module */ From 01e5f438ec7d3a761628af592a9404fadadd26bb Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 13 Mar 2026 13:25:17 +0100 Subject: [PATCH 10/56] Update syntax tests --- .../data/ppx/react/expected/externalWithRef.res.txt | 1 + .../data/ppx/react/expected/externalWithTypeVariables.res.txt | 1 + .../data/ppx/react/expected/mangleKeyword.res.txt | 1 + .../data/ppx/react/expected/noPropsWithKey.res.txt | 1 + .../syntax_tests/data/ppx/react/expected/sharedProps.res.txt | 4 ++++ tests/syntax_tests/data/ppx/react/expected/v4.res.txt | 2 ++ 6 files changed, 10 insertions(+) diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt index 44a3420fd87..f981aad592c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt @@ -10,3 +10,4 @@ module V4C = { @module("componentForwardRef") external make: React.component> = "component" } +let \"ExternalWithRef$V4C" = V4C.make and \"ExternalWithRef$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt index cbdee832996..e271934ecbb 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt @@ -10,3 +10,4 @@ module V4C = { @module("c") external make: React.component, React.element>> = "component" } +let \"ExternalWithTypeVariables$V4C" = V4C.make and \"ExternalWithTypeVariables$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt index 85a8e1a0302..8b9bd814834 100644 --- a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt @@ -28,3 +28,4 @@ module C4A1 = { let c4a0 = React.jsx(@res.jsxComponentPath C4A0.make, {_open: "x", _type: "t"}) let c4a1 = React.jsx(@res.jsxComponentPath C4A1.make, {_open: "x", _type: "t"}) let \"MangleKeyword$C4A0" = C4A0.make and \"MangleKeyword$C4A0$jsx" = true +let \"MangleKeyword$C4A1" = C4A1.make and \"MangleKeyword$C4A1$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt index f6efd90c3b4..bd40f4c83c7 100644 --- a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt @@ -41,4 +41,5 @@ module V4C = { } } let \"NoPropsWithKey$V4CA" = V4CA.make and \"NoPropsWithKey$V4CA$jsx" = true +let \"NoPropsWithKey$V4CB" = V4CB.make and \"NoPropsWithKey$V4CB$jsx" = true let \"NoPropsWithKey$V4C" = V4C.make and \"NoPropsWithKey$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt index 63c7f492103..70cb0dee1dc 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt @@ -71,3 +71,7 @@ let \"SharedProps$V4A1" = V4A1.make and \"SharedProps$V4A1$jsx" = true let \"SharedProps$V4A2" = V4A2.make and \"SharedProps$V4A2$jsx" = true let \"SharedProps$V4A3" = V4A3.make and \"SharedProps$V4A3$jsx" = true let \"SharedProps$V4A4" = V4A4.make and \"SharedProps$V4A4$jsx" = true +let \"SharedProps$V4A5" = V4A5.make and \"SharedProps$V4A5$jsx" = true +let \"SharedProps$V4A6" = V4A6.make and \"SharedProps$V4A6$jsx" = true +let \"SharedProps$V4A7" = V4A7.make and \"SharedProps$V4A7$jsx" = true +let \"SharedProps$V4A8" = V4A8.make and \"SharedProps$V4A8$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt index 2a8c1348d8f..40159d7edbc 100644 --- a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt @@ -116,6 +116,8 @@ module Rec2 = { } and mm = x => make(x) } +let \"V4$E" = E.make and \"V4$E$jsx" = true +let \"V4$EUncurried" = EUncurried.make and \"V4$EUncurried$jsx" = true let \"V4$Rec" = Rec.make and \"V4$Rec$jsx" = true let \"V4$Rec1" = Rec1.make and \"V4$Rec1$jsx" = true let \"V4$Rec2" = Rec2.make and \"V4$Rec2$jsx" = true From f0c3ccdd8a938870573b5ae874f3d9104de5a82d Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 13 Mar 2026 14:20:31 +0100 Subject: [PATCH 11/56] Update tests --- .../react_ppx/src/recursive_component_test.res.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/build_tests/react_ppx/src/recursive_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_component_test.res.js index 4e3fd9951f4..f3aa4d5fc91 100644 --- a/tests/build_tests/react_ppx/src/recursive_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_component_test.res.js @@ -18,7 +18,13 @@ let Rec = { mm: mm }; +let Recursive_component_test$Rec = make; + +let Recursive_component_test$Rec$jsx = true; + export { Rec, + Recursive_component_test$Rec, + Recursive_component_test$Rec$jsx, } /* No side effect */ From 15ded2db24eff77e93104b8f535bb59cb9f82719 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 13 Mar 2026 17:57:25 +0100 Subject: [PATCH 12/56] Add more regression tests --- compiler/core/lam_compile.ml | 45 ++++++++++--------- compiler/syntax/src/jsx_v4.ml | 4 ++ .../rsc_component_with_props_nested/input.js | 33 ++++++++++++++ .../rescript.json | 16 +++++++ .../src/MainLayout.res | 4 ++ .../src/React.res | 12 +++++ .../src/Sidebar.res | 6 +++ .../rsc_dynamic_import_nested_jsx/input.js | 30 +++++++++++++ .../rescript.json | 16 +++++++ .../src/MainLayout.res | 8 ++++ .../src/React.res | 12 +++++ .../src/Sidebar.res | 6 +++ .../rsc_mixed_runtime_import/input.js | 34 ++++++++++++++ .../rsc_mixed_runtime_import/rescript.json | 16 +++++++ .../src/MainLayout.res | 8 ++++ .../rsc_mixed_runtime_import/src/React.res | 10 +++++ .../rsc_mixed_runtime_import/src/Sidebar.res | 4 ++ .../build_tests/rsc_nested_jsx_deep/input.js | 33 ++++++++++++++ .../rsc_nested_jsx_deep/rescript.json | 16 +++++++ .../rsc_nested_jsx_deep/src/MainLayout.res | 4 ++ .../rsc_nested_jsx_deep/src/React.res | 10 +++++ .../rsc_nested_jsx_deep/src/Sidebar.res | 6 +++ .../rsc_nested_jsx_members/input.js | 15 +++++++ .../src/PlainAccess.res | 3 ++ .../input.js | 29 ++++++++++++ .../rescript.json | 16 +++++++ .../src/MainLayout.res | 4 ++ .../src/React.res | 10 +++++ .../src/Sidebar.res | 6 +++ .../rsc_suffix_runtime_import/input.js | 34 ++++++++++++++ .../rsc_suffix_runtime_import/rescript.json | 16 +++++++ .../src/MainLayout.res | 5 +++ .../rsc_suffix_runtime_import/src/React.res | 12 +++++ .../rsc_suffix_runtime_import/src/Sidebar.res | 4 ++ tests/tests/src/alias_default_value_test.mjs | 4 ++ 35 files changed, 471 insertions(+), 20 deletions(-) create mode 100644 tests/build_tests/rsc_component_with_props_nested/input.js create mode 100644 tests/build_tests/rsc_component_with_props_nested/rescript.json create mode 100644 tests/build_tests/rsc_component_with_props_nested/src/MainLayout.res create mode 100644 tests/build_tests/rsc_component_with_props_nested/src/React.res create mode 100644 tests/build_tests/rsc_component_with_props_nested/src/Sidebar.res create mode 100644 tests/build_tests/rsc_dynamic_import_nested_jsx/input.js create mode 100644 tests/build_tests/rsc_dynamic_import_nested_jsx/rescript.json create mode 100644 tests/build_tests/rsc_dynamic_import_nested_jsx/src/MainLayout.res create mode 100644 tests/build_tests/rsc_dynamic_import_nested_jsx/src/React.res create mode 100644 tests/build_tests/rsc_dynamic_import_nested_jsx/src/Sidebar.res create mode 100644 tests/build_tests/rsc_mixed_runtime_import/input.js create mode 100644 tests/build_tests/rsc_mixed_runtime_import/rescript.json create mode 100644 tests/build_tests/rsc_mixed_runtime_import/src/MainLayout.res create mode 100644 tests/build_tests/rsc_mixed_runtime_import/src/React.res create mode 100644 tests/build_tests/rsc_mixed_runtime_import/src/Sidebar.res create mode 100644 tests/build_tests/rsc_nested_jsx_deep/input.js create mode 100644 tests/build_tests/rsc_nested_jsx_deep/rescript.json create mode 100644 tests/build_tests/rsc_nested_jsx_deep/src/MainLayout.res create mode 100644 tests/build_tests/rsc_nested_jsx_deep/src/React.res create mode 100644 tests/build_tests/rsc_nested_jsx_deep/src/Sidebar.res create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/PlainAccess.res create mode 100644 tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js create mode 100644 tests/build_tests/rsc_nested_jsx_members_no_namespace/rescript.json create mode 100644 tests/build_tests/rsc_nested_jsx_members_no_namespace/src/MainLayout.res create mode 100644 tests/build_tests/rsc_nested_jsx_members_no_namespace/src/React.res create mode 100644 tests/build_tests/rsc_nested_jsx_members_no_namespace/src/Sidebar.res create mode 100644 tests/build_tests/rsc_suffix_runtime_import/input.js create mode 100644 tests/build_tests/rsc_suffix_runtime_import/rescript.json create mode 100644 tests/build_tests/rsc_suffix_runtime_import/src/MainLayout.res create mode 100644 tests/build_tests/rsc_suffix_runtime_import/src/React.res create mode 100644 tests/build_tests/rsc_suffix_runtime_import/src/Sidebar.res diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index b3c7da6e1ec..d72440c85d1 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -347,46 +347,51 @@ let compile output_prefix = push hidden_name; List.rev !candidates in - let exported_hidden_component_name (module_id : J.module_id) + let exported_hidden_component_name ~(id : Ident.t) ~(dynamic_import : bool) (hidden_name_candidates : string list) = let rec loop = function | [] -> None | candidate :: rest -> ( match - Lam_compile_env.query_external_id_info - ~dynamic_import:module_id.dynamic_import module_id.id + Lam_compile_env.query_external_id_info ~dynamic_import id (candidate ^ "$jsx") with | _ -> Some candidate | exception Not_found -> loop rest) in - match module_id.kind with - | Ml -> loop hidden_name_candidates - | _ -> None + loop hidden_name_candidates in let rewrite_nested_jsx_component_expr (jsx_tag : Lam.t) (compiled_expr : J.expression) : J.expression = - let rec extract_module_id (expr : J.expression) = + let rec extract_root_expr (expr : J.expression) = match expr.expression_desc with - | Var (Qualified (module_id, _)) -> Some module_id - | Static_index (inner, _, _) -> extract_module_id inner + | Var (Qualified (module_id, Some _)) -> + Some {expr with expression_desc = Var (Qualified (module_id, None))} + | Static_index (inner, _, _) -> extract_root_expr inner + | Var _ -> Some expr | _ -> None in + let hidden_component_access (root_expr : J.expression) hidden_name = + match root_expr.expression_desc with + | Var (Qualified (module_id, None)) -> + { + root_expr with + expression_desc = Var (Qualified (module_id, Some hidden_name)); + } + | _ -> E.dot root_expr hidden_name + in match extract_nested_external_component_field jsx_tag with - | Some (id, _dynamic_import, hidden_name) -> ( + | Some (id, dynamic_import, hidden_name) -> ( let hidden_name_candidates = hidden_component_name_candidates id hidden_name in - match extract_module_id compiled_expr with - | Some module_id -> ( + match extract_root_expr compiled_expr with + | Some root_expr -> ( match - exported_hidden_component_name module_id hidden_name_candidates + exported_hidden_component_name ~id ~dynamic_import + hidden_name_candidates with - | Some hidden_name -> - { - compiled_expr with - expression_desc = Var (Qualified (module_id, Some hidden_name)); - } + | Some hidden_name -> hidden_component_access root_expr hidden_name | None -> compiled_expr) | None -> compiled_expr) | None -> compiled_expr @@ -1671,7 +1676,7 @@ let compile output_prefix = }; } -> ( match fld_info with - | Fld_module {name} -> + | Fld_module {name; jsx_component = _} -> compile_external_field_apply ~dynamic_import appinfo id name lambda_cxt | _ -> assert false) | _ -> ( @@ -1795,7 +1800,7 @@ let compile output_prefix = } -> ( (* should be before Lglobal_global *) match fld_info with - | Fld_module {name = field} -> + | Fld_module {name = field; jsx_component = _} -> compile_external_field ~dynamic_import lambda_cxt id field | _ -> assert false) | {primitive = Praise; args = [e]; _} -> ( diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 9c5a88feaaa..566f790cd5f 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -953,6 +953,10 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name ~rec_flag binding = Some (make_new_binding ~loc:empty_loc ~full_module_name modified_binding) in + let () = + maybe_hoist_nested_make_component ~config ~empty_loc ~full_module_name + fn_name + in let binding_expr = { binding.pvb_expr with diff --git a/tests/build_tests/rsc_component_with_props_nested/input.js b/tests/build_tests/rsc_component_with_props_nested/input.js new file mode 100644 index 00000000000..ac0939c3778 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_nested/input.js @@ -0,0 +1,33 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const outputPath = path.join(import.meta.dirname, "src", "MainLayout.res.js"); +const output = await fs.readFile(outputPath, "utf8"); +const sidebarOutputPath = path.join( + import.meta.dirname, + "src", + "Sidebar.res.js", +); +const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); + +assert.match( + output, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsNested\.Sidebar\$Provider,/, +); +assert.doesNotMatch(output, /\.Provider\.make,/); +assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.match( + sidebarOutput, + /export \{[\s\S]*Provider,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Provider\$jsx[\s\S]*\}/s, +); + +await execClean(); diff --git a/tests/build_tests/rsc_component_with_props_nested/rescript.json b/tests/build_tests/rsc_component_with_props_nested/rescript.json new file mode 100644 index 00000000000..537397ae493 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_nested/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-component-with-props-nested", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + }, + "namespace": true +} diff --git a/tests/build_tests/rsc_component_with_props_nested/src/MainLayout.res b/tests/build_tests/rsc_component_with_props_nested/src/MainLayout.res new file mode 100644 index 00000000000..e2c753eeb17 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_nested/src/MainLayout.res @@ -0,0 +1,4 @@ +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/build_tests/rsc_component_with_props_nested/src/React.res b/tests/build_tests/rsc_component_with_props_nested/src/React.res new file mode 100644 index 00000000000..75e3b2c2d78 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_nested/src/React.res @@ -0,0 +1,12 @@ +type element + +@val external null: element = "null" + +external string: string => element = "%identity" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_component_with_props_nested/src/Sidebar.res b/tests/build_tests/rsc_component_with_props_nested/src/Sidebar.res new file mode 100644 index 00000000000..b667cc58fe6 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_nested/src/Sidebar.res @@ -0,0 +1,6 @@ +module Provider = { + type props = {children: React.element} + + @react.componentWithProps + let make = props => props.children +} diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js new file mode 100644 index 00000000000..13ae1728e8f --- /dev/null +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js @@ -0,0 +1,30 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const outputPath = path.join(import.meta.dirname, "src", "MainLayout.res.mjs"); +const output = await fs.readFile(outputPath, "utf8"); + +assert.match( + output, + /let DynamicSidebar = await import\("\.\/Sidebar\.res\.mjs"\);/, +); +assert.match(output, /let dynamicProvider = DynamicSidebar\.Provider\.make;/); +assert.match( + output, + /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider,/, +); +assert.doesNotMatch( + output, + /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/, +); + +await execClean(); diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/rescript.json b/tests/build_tests/rsc_dynamic_import_nested_jsx/rescript.json new file mode 100644 index 00000000000..f4772ad950b --- /dev/null +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-dynamic-import-nested-jsx", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.mjs" + }, + "namespace": true +} diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/src/MainLayout.res b/tests/build_tests/rsc_dynamic_import_nested_jsx/src/MainLayout.res new file mode 100644 index 00000000000..42ebacc8096 --- /dev/null +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/src/MainLayout.res @@ -0,0 +1,8 @@ +module DynamicSidebar = await Sidebar + +let dynamicProvider = DynamicSidebar.Provider.make + +@react.component +let make = (~children) => { + {Option.getOr(children, React.null)} +} diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/src/React.res b/tests/build_tests/rsc_dynamic_import_nested_jsx/src/React.res new file mode 100644 index 00000000000..75e3b2c2d78 --- /dev/null +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/src/React.res @@ -0,0 +1,12 @@ +type element + +@val external null: element = "null" + +external string: string => element = "%identity" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/src/Sidebar.res b/tests/build_tests/rsc_dynamic_import_nested_jsx/src/Sidebar.res new file mode 100644 index 00000000000..ad06cf9dc20 --- /dev/null +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/src/Sidebar.res @@ -0,0 +1,6 @@ +module Provider = { + @react.component + let make = (~children) => { + children + } +} diff --git a/tests/build_tests/rsc_mixed_runtime_import/input.js b/tests/build_tests/rsc_mixed_runtime_import/input.js new file mode 100644 index 00000000000..1e4f73b8bd2 --- /dev/null +++ b/tests/build_tests/rsc_mixed_runtime_import/input.js @@ -0,0 +1,34 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const outputPath = path.join(import.meta.dirname, "src", "MainLayout.res.mjs"); +const output = await fs.readFile(outputPath, "utf8"); + +assert.match( + output, + /import \* as Sidebar\$RscMixedRuntimeImport from "\.\/Sidebar\.res\.mjs";/, +); +assert.match( + output, + /import \* as Stdlib_OptionJs from "@rescript\/runtime\/lib\/es6\/Stdlib_Option\.js";/, +); +assert.match( + output, + /JsxRuntime\.jsx\(Sidebar\$RscMixedRuntimeImport\.Sidebar\$Provider,/, +); +assert.doesNotMatch(output, /@rescript\/runtime\/lib\/es6\/Stdlib_Option\.mjs/); +assert.equal( + output.match(/@rescript\/runtime\/lib\/es6\/Stdlib_Option\.js/g)?.length ?? 0, + 1, +); + +await execClean(); diff --git a/tests/build_tests/rsc_mixed_runtime_import/rescript.json b/tests/build_tests/rsc_mixed_runtime_import/rescript.json new file mode 100644 index 00000000000..c9a0312aa05 --- /dev/null +++ b/tests/build_tests/rsc_mixed_runtime_import/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-mixed-runtime-import", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.mjs" + }, + "namespace": true +} diff --git a/tests/build_tests/rsc_mixed_runtime_import/src/MainLayout.res b/tests/build_tests/rsc_mixed_runtime_import/src/MainLayout.res new file mode 100644 index 00000000000..4a2dfa741f1 --- /dev/null +++ b/tests/build_tests/rsc_mixed_runtime_import/src/MainLayout.res @@ -0,0 +1,8 @@ +@module("@rescript/runtime/lib/es6/Stdlib_Option.js") +external getOr: (option<'a>, 'a) => 'a = "getOr" + +@react.component +let make = (~children) => { + let child = getOr(children, React.null) + {child} +} diff --git a/tests/build_tests/rsc_mixed_runtime_import/src/React.res b/tests/build_tests/rsc_mixed_runtime_import/src/React.res new file mode 100644 index 00000000000..733b8ee02f0 --- /dev/null +++ b/tests/build_tests/rsc_mixed_runtime_import/src/React.res @@ -0,0 +1,10 @@ +type element + +@val external null: element = "null" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_mixed_runtime_import/src/Sidebar.res b/tests/build_tests/rsc_mixed_runtime_import/src/Sidebar.res new file mode 100644 index 00000000000..25e1db24bca --- /dev/null +++ b/tests/build_tests/rsc_mixed_runtime_import/src/Sidebar.res @@ -0,0 +1,4 @@ +module Provider = { + @react.component + let make = (~children) => children +} diff --git a/tests/build_tests/rsc_nested_jsx_deep/input.js b/tests/build_tests/rsc_nested_jsx_deep/input.js new file mode 100644 index 00000000000..e9128fe30ed --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_deep/input.js @@ -0,0 +1,33 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const outputPath = path.join(import.meta.dirname, "src", "MainLayout.res.js"); +const output = await fs.readFile(outputPath, "utf8"); +const sidebarOutputPath = path.join( + import.meta.dirname, + "src", + "Sidebar.res.js", +); +const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); + +assert.match( + output, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxDeep\.Sidebar\$Group,/, +); +assert.doesNotMatch(output, /\.Group\.make,/); +assert.match(sidebarOutput, /Sidebar\$Group\$jsx/); +assert.match( + sidebarOutput, + /export \{[\s\S]*Group,[\s\S]*Sidebar\$Group,[\s\S]*Sidebar\$Group\$jsx[\s\S]*\}/s, +); + +await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_deep/rescript.json b/tests/build_tests/rsc_nested_jsx_deep/rescript.json new file mode 100644 index 00000000000..7f67937417b --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_deep/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-nested-jsx-deep", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + }, + "namespace": true +} diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/MainLayout.res b/tests/build_tests/rsc_nested_jsx_deep/src/MainLayout.res new file mode 100644 index 00000000000..3e31dfdfcbc --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_deep/src/MainLayout.res @@ -0,0 +1,4 @@ +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/React.res b/tests/build_tests/rsc_nested_jsx_deep/src/React.res new file mode 100644 index 00000000000..733b8ee02f0 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_deep/src/React.res @@ -0,0 +1,10 @@ +type element + +@val external null: element = "null" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/Sidebar.res b/tests/build_tests/rsc_nested_jsx_deep/src/Sidebar.res new file mode 100644 index 00000000000..a7feecaa77a --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_deep/src/Sidebar.res @@ -0,0 +1,6 @@ +module Group = { + @react.component + let make = (~children) => { + children + } +} diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index dda8ebdcd09..2814bdf5a4e 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -33,6 +33,12 @@ const externalSidebarOutput = await fs.readFile( externalSidebarOutputPath, "utf8", ); +const plainAccessOutputPath = path.join( + import.meta.dirname, + "src", + "PlainAccess.res.js", +); +const plainAccessOutput = await fs.readFile(plainAccessOutputPath, "utf8"); assert.match( output, @@ -65,5 +71,14 @@ assert.match( externalSidebarOutput, /export \{[\s\S]*Provider,[\s\S]*SidebarExternal\$Provider,[\s\S]*SidebarExternal\$Provider\$jsx[\s\S]*\}/s, ); +assert.match( + plainAccessOutput, + /let provider = Sidebar\$RscNestedJsxMembers\.Provider\.make;/, +); +assert.match( + plainAccessOutput, + /let callProvider = Sidebar\$RscNestedJsxMembers\.Provider\.make\(\{/, +); +assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members/src/PlainAccess.res b/tests/build_tests/rsc_nested_jsx_members/src/PlainAccess.res new file mode 100644 index 00000000000..50ad67b4bb5 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/PlainAccess.res @@ -0,0 +1,3 @@ +let provider = Sidebar.Provider.make + +let callProvider = Sidebar.Provider.make({children: React.null}) diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js new file mode 100644 index 00000000000..0a672353d49 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js @@ -0,0 +1,29 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const outputPath = path.join(import.meta.dirname, "src", "MainLayout.res.js"); +const output = await fs.readFile(outputPath, "utf8"); +const sidebarOutputPath = path.join( + import.meta.dirname, + "src", + "Sidebar.res.js", +); +const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); + +assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); +assert.doesNotMatch(output, /Sidebar\.Provider\.make/); +assert.match( + sidebarOutput, + /export \{[\s\S]*Provider,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Provider\$jsx[\s\S]*\}/s, +); + +await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/rescript.json b/tests/build_tests/rsc_nested_jsx_members_no_namespace/rescript.json new file mode 100644 index 00000000000..dba3aa44e5a --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-nested-jsx-members-no-namespace", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + }, + "namespace": false +} diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/MainLayout.res b/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/MainLayout.res new file mode 100644 index 00000000000..e2c753eeb17 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/MainLayout.res @@ -0,0 +1,4 @@ +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/React.res b/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/React.res new file mode 100644 index 00000000000..733b8ee02f0 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/React.res @@ -0,0 +1,10 @@ +type element + +@val external null: element = "null" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/Sidebar.res b/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/Sidebar.res new file mode 100644 index 00000000000..ad06cf9dc20 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/Sidebar.res @@ -0,0 +1,6 @@ +module Provider = { + @react.component + let make = (~children) => { + children + } +} diff --git a/tests/build_tests/rsc_suffix_runtime_import/input.js b/tests/build_tests/rsc_suffix_runtime_import/input.js new file mode 100644 index 00000000000..ee1dd122963 --- /dev/null +++ b/tests/build_tests/rsc_suffix_runtime_import/input.js @@ -0,0 +1,34 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const outputPath = path.join(import.meta.dirname, "src", "MainLayout.res.mjs"); +const output = await fs.readFile(outputPath, "utf8"); + +assert.match( + output, + /import \* as Stdlib_Option from "@rescript\/runtime\/lib\/es6\/Stdlib_Option\.mjs";/, +); +assert.match( + output, + /import \* as Sidebar\$RscSuffixRuntimeImport from "\.\/Sidebar\.res\.mjs";/, +); +assert.match( + output, + /JsxRuntime\.jsx\(Sidebar\$RscSuffixRuntimeImport\.Sidebar\$Provider,/, +); +assert.equal(output.match(/Stdlib_Option\.(js|mjs)/g)?.length ?? 0, 1); +assert.doesNotMatch( + output, + /@rescript\/runtime\/lib\/es6\/Stdlib_Option\.(js|mjs)";[\s\S]*@rescript\/runtime\/lib\/es6\/Stdlib_Option\.(js|mjs)";/s, +); + +await execClean(); diff --git a/tests/build_tests/rsc_suffix_runtime_import/rescript.json b/tests/build_tests/rsc_suffix_runtime_import/rescript.json new file mode 100644 index 00000000000..177e276e8f0 --- /dev/null +++ b/tests/build_tests/rsc_suffix_runtime_import/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-suffix-runtime-import", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.mjs" + }, + "namespace": true +} diff --git a/tests/build_tests/rsc_suffix_runtime_import/src/MainLayout.res b/tests/build_tests/rsc_suffix_runtime_import/src/MainLayout.res new file mode 100644 index 00000000000..a45377b4f3d --- /dev/null +++ b/tests/build_tests/rsc_suffix_runtime_import/src/MainLayout.res @@ -0,0 +1,5 @@ +@react.component +let make = (~children) => { + let child = Option.getOr(children, React.null) + {child} +} diff --git a/tests/build_tests/rsc_suffix_runtime_import/src/React.res b/tests/build_tests/rsc_suffix_runtime_import/src/React.res new file mode 100644 index 00000000000..75e3b2c2d78 --- /dev/null +++ b/tests/build_tests/rsc_suffix_runtime_import/src/React.res @@ -0,0 +1,12 @@ +type element + +@val external null: element = "null" + +external string: string => element = "%identity" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_suffix_runtime_import/src/Sidebar.res b/tests/build_tests/rsc_suffix_runtime_import/src/Sidebar.res new file mode 100644 index 00000000000..25e1db24bca --- /dev/null +++ b/tests/build_tests/rsc_suffix_runtime_import/src/Sidebar.res @@ -0,0 +1,4 @@ +module Provider = { + @react.component + let make = (~children) => children +} diff --git a/tests/tests/src/alias_default_value_test.mjs b/tests/tests/src/alias_default_value_test.mjs index 49f6103cef0..22e6ada439e 100644 --- a/tests/tests/src/alias_default_value_test.mjs +++ b/tests/tests/src/alias_default_value_test.mjs @@ -105,6 +105,8 @@ let Alias_default_value_test$C6$jsx = true; let Alias_default_value_test$C7$jsx = true; +let Alias_default_value_test$C8$jsx = true; + export { C0, C1, @@ -128,5 +130,7 @@ export { Alias_default_value_test$C6$jsx, Alias_default_value_test$C7, Alias_default_value_test$C7$jsx, + Alias_default_value_test$C8, + Alias_default_value_test$C8$jsx, } /* No side effect */ From a6a5dd1de1291d6f40ac0c2da6cbd44f11c2fd17 Mon Sep 17 00:00:00 2001 From: tsnobip Date: Sun, 22 Mar 2026 18:43:53 +0100 Subject: [PATCH 13/56] fix syntax tests --- .../data/ppx/react/expected/defaultPatternProp.res.txt | 4 +++- .../data/ppx/react/expected/returnConstraint.res.txt | 1 + .../data/ppx/react/expected/sharedPropsWithProps.res.txt | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt index 97ed9f45567..4f7291dacc9 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt @@ -25,7 +25,7 @@ module C0 = { | None => module(M: S) } let module(C: S) = __component_value - (React.jsx(C.make, {}): React.element) + (React.jsx(@res.jsxComponentPath C.make, {}): React.element) } let make = { let \"DefaultPatternProp$C0" = (props: props<_>) => make(props) @@ -33,3 +33,5 @@ module C0 = { \"DefaultPatternProp$C0" } } +let \"DefaultPatternProp$C0$M" = C0.M.make and \"DefaultPatternProp$C0$M$jsx" = true +let \"DefaultPatternProp$C0" = C0.make and \"DefaultPatternProp$C0$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt index 84cd1b66fd8..eff4facc7bd 100644 --- a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt @@ -49,4 +49,5 @@ module Async = { } let \"ReturnConstraint$Standard" = Standard.make and \"ReturnConstraint$Standard$jsx" = true let \"ReturnConstraint$ForwardRef" = ForwardRef.make and \"ReturnConstraint$ForwardRef$jsx" = true +let \"ReturnConstraint$WithProps" = WithProps.make and \"ReturnConstraint$WithProps$jsx" = true let \"ReturnConstraint$Async" = Async.make and \"ReturnConstraint$Async$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt index 8abf4452b0f..f03c5e7248f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt @@ -76,3 +76,10 @@ module V4A7 = { \"SharedPropsWithProps$V4A7" } } +let \"SharedPropsWithProps$V4A1" = V4A1.make and \"SharedPropsWithProps$V4A1$jsx" = true +let \"SharedPropsWithProps$V4A2" = V4A2.make and \"SharedPropsWithProps$V4A2$jsx" = true +let \"SharedPropsWithProps$V4A3" = V4A3.make and \"SharedPropsWithProps$V4A3$jsx" = true +let \"SharedPropsWithProps$V4A4" = V4A4.make and \"SharedPropsWithProps$V4A4$jsx" = true +let \"SharedPropsWithProps$V4A5" = V4A5.make and \"SharedPropsWithProps$V4A5$jsx" = true +let \"SharedPropsWithProps$V4A6" = V4A6.make and \"SharedPropsWithProps$V4A6$jsx" = true +let \"SharedPropsWithProps$V4A7" = V4A7.make and \"SharedPropsWithProps$V4A7$jsx" = true From 18c14c75acd09f55e58d3004eaf4eff4192dad75 Mon Sep 17 00:00:00 2001 From: tsnobip Date: Mon, 23 Mar 2026 15:50:20 +0100 Subject: [PATCH 14/56] add repro where direct component function doesn't get exported --- tests/build_tests/rsc_nested_jsx_deep/input.js | 3 +++ .../rsc_nested_jsx_deep/src/BrandIcons.res | 12 ++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 tests/build_tests/rsc_nested_jsx_deep/src/BrandIcons.res diff --git a/tests/build_tests/rsc_nested_jsx_deep/input.js b/tests/build_tests/rsc_nested_jsx_deep/input.js index e9128fe30ed..eca3b4b5334 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/input.js +++ b/tests/build_tests/rsc_nested_jsx_deep/input.js @@ -30,4 +30,7 @@ assert.match( /export \{[\s\S]*Group,[\s\S]*Sidebar\$Group,[\s\S]*Sidebar\$Group\$jsx[\s\S]*\}/s, ); +const brandIcons = await import("./src/BrandIcons.res.js"); +assert.match(Object.keys(brandIcons).join(", "), /ReScript, BrandIcons\$ReScript, BrandIcons\$ReScript\$jsx, getIconForLanguageExtension/); + await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/BrandIcons.res b/tests/build_tests/rsc_nested_jsx_deep/src/BrandIcons.res new file mode 100644 index 00000000000..3f435fab92c --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_deep/src/BrandIcons.res @@ -0,0 +1,12 @@ +module ReScript = { + @react.component + let make = () => React.null +} + + +let getIconForLanguageExtension = (language: string) => { + switch language { + | "res" | "rescript" => + | _ => React.null + } +} From e3ad8b00ab568428ed446a63ffe4851a0735a5fc Mon Sep 17 00:00:00 2001 From: tsnobip Date: Mon, 23 Mar 2026 16:30:49 +0100 Subject: [PATCH 15/56] fix export of JSX components inside module with regular functions --- compiler/syntax/src/jsx_common.ml | 6 ++++ compiler/syntax/src/jsx_ppx.ml | 6 +++- .../react_ppx/src/gpr_3695_test.res.js | 8 +++++- .../react_ppx/src/gpr_3987_test.res.js | 8 ++++++ .../react_ppx/src/issue_7917_test.res.js | 4 +++ .../build_tests/rsc_nested_jsx_deep/input.js | 23 ++++++++++++++- .../rsc_nested_jsx_deep/src/BrandIcons.res | 3 +- .../src/MultipleNested.res | 13 +++++++++ .../data/ppx/react/expected/newtype.res.txt | 4 +++ .../data/ppx/react/expected/v4.res.txt | 1 + tests/tests/src/jsx_preserve_test.mjs | 28 +++++++++++++++++++ 11 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.res diff --git a/compiler/syntax/src/jsx_common.ml b/compiler/syntax/src/jsx_common.ml index e7b8698c701..259d9496113 100644 --- a/compiler/syntax/src/jsx_common.ml +++ b/compiler/syntax/src/jsx_common.ml @@ -7,6 +7,12 @@ type jsx_config = { mutable nested_modules: string list; mutable has_component: bool; mutable hoisted_structure_items: structure_item list; + (* Nesting depth of [structure] calls while rewriting one implementation. + Used so we only clear/append hoisted items at the true file root — not for + nested [Pmod_structure] reached from expression traversal (e.g. via + [module_expr]), which would otherwise see [nested_modules = []] and wipe + hoisted bindings added for earlier structure items. *) + mutable structure_depth: int; } (* Helper method to look up the [@react.component] attribute *) diff --git a/compiler/syntax/src/jsx_ppx.ml b/compiler/syntax/src/jsx_ppx.ml index 078d812eca9..89d170a4bb7 100644 --- a/compiler/syntax/src/jsx_ppx.ml +++ b/compiler/syntax/src/jsx_ppx.ml @@ -117,7 +117,8 @@ let get_mapper ~config = in let structure mapper items = let old_config = save_config () in - let is_top_level = config.nested_modules = [] in + let is_top_level = config.structure_depth = 0 in + config.structure_depth <- config.structure_depth + 1; config.has_component <- false; if is_top_level then config.hoisted_structure_items <- []; let result = @@ -136,6 +137,7 @@ let get_mapper ~config = result @ List.rev config.hoisted_structure_items else result in + config.structure_depth <- config.structure_depth - 1; restore_config old_config; result in @@ -151,6 +153,7 @@ let rewrite_implementation ~jsx_version ~jsx_module (code : Parsetree.structure) nested_modules = []; has_component = false; hoisted_structure_items = []; + structure_depth = 0; } in let mapper = get_mapper ~config in @@ -165,6 +168,7 @@ let rewrite_signature ~jsx_version ~jsx_module (code : Parsetree.signature) : nested_modules = []; has_component = false; hoisted_structure_items = []; + structure_depth = 0; } in let mapper = get_mapper ~config in diff --git a/tests/build_tests/react_ppx/src/gpr_3695_test.res.js b/tests/build_tests/react_ppx/src/gpr_3695_test.res.js index ebaf897020a..11c0cca5c42 100644 --- a/tests/build_tests/react_ppx/src/gpr_3695_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3695_test.res.js @@ -10,9 +10,15 @@ function test(className) { return Foo; } +let Gpr_3695_test$Test = Foo; + +let Gpr_3695_test$Test$jsx = true; + export { React, Test, test, + Gpr_3695_test$Test, + Gpr_3695_test$Test$jsx, } -/* Foo Not a pure module */ +/* Gpr_3695_test$Test Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js index 378f9156caf..b2b8ed47b8a 100644 --- a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js @@ -55,10 +55,18 @@ JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError, { onChange: (param, param$1) => {} }); +let Gpr_3987_test$Gpr3987ReproOk2$jsx = true; + +let Gpr_3987_test$Gpr3987ReproError$jsx = true; + export { makeContainer, Gpr3987ReproOk, Gpr3987ReproOk2, Gpr3987ReproError, + Gpr_3987_test$Gpr3987ReproOk2, + Gpr_3987_test$Gpr3987ReproOk2$jsx, + Gpr_3987_test$Gpr3987ReproError, + Gpr_3987_test$Gpr3987ReproError$jsx, } /* Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/issue_7917_test.res.js b/tests/build_tests/react_ppx/src/issue_7917_test.res.js index 9df85100b95..14a6b09fba1 100644 --- a/tests/build_tests/react_ppx/src/issue_7917_test.res.js +++ b/tests/build_tests/react_ppx/src/issue_7917_test.res.js @@ -18,8 +18,12 @@ function Issue_7917_test(props) { let make = Issue_7917_test; +let Issue_7917_test$M$jsx = true; + export { M, make, + Issue_7917_test$M, + Issue_7917_test$M$jsx, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/build_tests/rsc_nested_jsx_deep/input.js b/tests/build_tests/rsc_nested_jsx_deep/input.js index eca3b4b5334..9e9ad8fa159 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/input.js +++ b/tests/build_tests/rsc_nested_jsx_deep/input.js @@ -31,6 +31,27 @@ assert.match( ); const brandIcons = await import("./src/BrandIcons.res.js"); -assert.match(Object.keys(brandIcons).join(", "), /ReScript, BrandIcons\$ReScript, BrandIcons\$ReScript\$jsx, getIconForLanguageExtension/); +assert.deepStrictEqual( + new Set(Object.keys(brandIcons)), + new Set([ + "ReScript", + "BrandIcons$ReScript", + "BrandIcons$ReScript$jsx", + "getIconForLanguageExtension", + ]), +); + +const multipleNested = await import("./src/MultipleNested.res.js"); +assert.deepStrictEqual( + new Set(Object.keys(multipleNested)), + new Set([ + "Group", + "MultipleNested$Group", + "MultipleNested$Group$jsx", + "Other", + "MultipleNested$Other", + "MultipleNested$Other$jsx", + ]), +); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/BrandIcons.res b/tests/build_tests/rsc_nested_jsx_deep/src/BrandIcons.res index 3f435fab92c..05ec1396656 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/src/BrandIcons.res +++ b/tests/build_tests/rsc_nested_jsx_deep/src/BrandIcons.res @@ -3,10 +3,9 @@ module ReScript = { let make = () => React.null } - let getIconForLanguageExtension = (language: string) => { switch language { - | "res" | "rescript" => + | "res" | "rescript" => | _ => React.null } } diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.res b/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.res new file mode 100644 index 00000000000..4e6780c8eee --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.res @@ -0,0 +1,13 @@ +module Group = { + @react.component + let make = (~children) => { + children + } +} + +module Other = { + @react.component + let make = (~children) => { + children + } +} diff --git a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt index 838fd0daa57..8296472d4d1 100644 --- a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt @@ -99,4 +99,8 @@ module Uncurried = { \"Newtype$Uncurried" } } +let \"Newtype$V4A" = V4A.make and \"Newtype$V4A$jsx" = true +let \"Newtype$V4A1" = V4A1.make and \"Newtype$V4A1$jsx" = true +let \"Newtype$V4A2" = V4A2.make and \"Newtype$V4A2$jsx" = true +let \"Newtype$V4A3" = V4A3.make and \"Newtype$V4A3$jsx" = true let \"Newtype$Uncurried" = Uncurried.make and \"Newtype$Uncurried$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt index 40159d7edbc..c4de276ae23 100644 --- a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt @@ -116,6 +116,7 @@ module Rec2 = { } and mm = x => make(x) } +let \"V4$Uncurried" = Uncurried.make and \"V4$Uncurried$jsx" = true let \"V4$E" = E.make and \"V4$E$jsx" = true let \"V4$EUncurried" = EUncurried.make and \"V4$EUncurried$jsx" = true let \"V4$Rec" = Rec.make and \"V4$Rec$jsx" = true diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs index 570ca4607cf..5d074e9aa84 100644 --- a/tests/tests/src/jsx_preserve_test.mjs +++ b/tests/tests/src/jsx_preserve_test.mjs @@ -247,8 +247,24 @@ function Jsx_preserve_test(props) { ; } +let Jsx_preserve_test$A = QueryClientProvider; + let make$3 = Jsx_preserve_test; +let Jsx_preserve_test$Icon$jsx = true; + +let Jsx_preserve_test$A$jsx = true; + +let Jsx_preserve_test$B$jsx = true; + +let Jsx_preserve_test$MyWeirdComponent$jsx = true; + +let Jsx_preserve_test$ComponentWithOptionalProps$jsx = true; + +let Jsx_preserve_test$Y$1 = make$1; + +let Jsx_preserve_test$Y$jsx = true; + export { Icon, _single_element_child, @@ -281,5 +297,17 @@ export { context, ContextProvider, make$3 as make, + Jsx_preserve_test$Icon, + Jsx_preserve_test$Icon$jsx, + Jsx_preserve_test$A, + Jsx_preserve_test$A$jsx, + Jsx_preserve_test$B, + Jsx_preserve_test$B$jsx, + Jsx_preserve_test$MyWeirdComponent, + Jsx_preserve_test$MyWeirdComponent$jsx, + Jsx_preserve_test$ComponentWithOptionalProps, + Jsx_preserve_test$ComponentWithOptionalProps$jsx, + Jsx_preserve_test$Y$1 as Jsx_preserve_test$Y, + Jsx_preserve_test$Y$jsx, } /* _single_element_child Not a pure module */ From 04a584623a1284efff857b1bd32c6e6b3ee3cce7 Mon Sep 17 00:00:00 2001 From: tsnobip Date: Tue, 24 Mar 2026 12:46:10 +0100 Subject: [PATCH 16/56] update analysis/gentype tests --- .../tests/src/expected/CreateInterface.res.txt | 4 ++++ .../typescript-react-example/src/Hooks.res.js | 18 ++++++++++++++++++ .../typescript-react-example/src/JSXV4.res.js | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt index e71ea422ce1..6a135ff1b04 100644 --- a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt +++ b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt @@ -132,6 +132,10 @@ module OrderedSet: { } let make: (~id: module(Belt.Id.Comparable with type identity = 'a and type t = 'b)) => t<'b, 'a> } +let CreateInterface$Mod: Mod.props => React.element +let CreateInterface$Mod$jsx: bool +let CreateInterface$Memo: React.component> +let CreateInterface$Memo$jsx: bool let CreateInterface$ComponentWithPolyProp: ComponentWithPolyProp.props< [< #large | #small], > => React.element diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js index 7eafb7e5f95..874f987d625 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js @@ -204,6 +204,16 @@ let make$1 = Hooks; let $$default = Hooks; +let Hooks$Inner$jsx = true; + +let Hooks$Inner$Inner2$jsx = true; + +let Hooks$NoProps$jsx = true; + +let Hooks$WithRef = make; + +let Hooks$WithRef$jsx = true; + let Hooks$RenderPropRequiresConversion$jsx = true; let Hooks$DD$jsx = true; @@ -223,6 +233,14 @@ export { RenderPropRequiresConversion, WithChildren, DD, + Hooks$Inner, + Hooks$Inner$jsx, + Hooks$Inner$Inner2, + Hooks$Inner$Inner2$jsx, + Hooks$NoProps, + Hooks$NoProps$jsx, + Hooks$WithRef, + Hooks$WithRef$jsx, Hooks$RenderPropRequiresConversion, Hooks$RenderPropRequiresConversion$jsx, Hooks$DD, diff --git a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js index 016dda6790c..4a12f2143f3 100644 --- a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js +++ b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js @@ -12,8 +12,12 @@ let CompV4 = { let make = JSXV4Gen.make; +let JSXV4$CompV4$jsx = true; + export { CompV4, make, + JSXV4$CompV4, + JSXV4$CompV4$jsx, } /* make Not a pure module */ From 573dd37e275cdf8c4d381780a3ecf065ca4cfc5a Mon Sep 17 00:00:00 2001 From: tsnobip Date: Tue, 24 Mar 2026 12:46:20 +0100 Subject: [PATCH 17/56] fix functors --- compiler/syntax/src/jsx_common.ml | 3 +++ compiler/syntax/src/jsx_ppx.ml | 17 ++++++++++++++++- compiler/syntax/src/jsx_v4.ml | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/compiler/syntax/src/jsx_common.ml b/compiler/syntax/src/jsx_common.ml index 259d9496113..d07e3338dcc 100644 --- a/compiler/syntax/src/jsx_common.ml +++ b/compiler/syntax/src/jsx_common.ml @@ -13,6 +13,9 @@ type jsx_config = { [module_expr]), which would otherwise see [nested_modules = []] and wipe hoisted bindings added for earlier structure items. *) mutable structure_depth: int; + (* Inside [Pmod_functor] bodies, hoisting [File$M = M.make] at the file top + would reference [M] as a functor (illegal). Skip hoists when > 0. *) + mutable functor_depth: int; } (* Helper method to look up the [@react.component] attribute *) diff --git a/compiler/syntax/src/jsx_ppx.ml b/compiler/syntax/src/jsx_ppx.ml index 89d170a4bb7..0b4ab0f2def 100644 --- a/compiler/syntax/src/jsx_ppx.ml +++ b/compiler/syntax/src/jsx_ppx.ml @@ -85,6 +85,19 @@ let get_mapper ~config = | 4 -> module_binding4 mapper mb | _ -> default_mapper.module_binding mapper mb in + let module_expr mapper me = + match (config.version, me.pmod_desc) with + | 4, Pmod_functor _ -> ( + config.functor_depth <- config.functor_depth + 1; + try + let m = default_mapper.module_expr mapper me in + config.functor_depth <- config.functor_depth - 1; + m + with e -> + config.functor_depth <- config.functor_depth - 1; + raise e) + | _ -> default_mapper.module_expr mapper me + in let save_config () = { config with @@ -142,7 +155,7 @@ let get_mapper ~config = result in - {default_mapper with expr; module_binding; signature; structure} + {default_mapper with expr; module_binding; module_expr; signature; structure} let rewrite_implementation ~jsx_version ~jsx_module (code : Parsetree.structure) : Parsetree.structure = @@ -154,6 +167,7 @@ let rewrite_implementation ~jsx_version ~jsx_module (code : Parsetree.structure) has_component = false; hoisted_structure_items = []; structure_depth = 0; + functor_depth = 0; } in let mapper = get_mapper ~config in @@ -169,6 +183,7 @@ let rewrite_signature ~jsx_version ~jsx_module (code : Parsetree.signature) : has_component = false; hoisted_structure_items = []; structure_depth = 0; + functor_depth = 0; } in let mapper = get_mapper ~config in diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 566f790cd5f..5eb52ece30b 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -132,8 +132,8 @@ let unnamespace_module_name file_name = let maybe_hoist_nested_make_component ~(config : Jsx_common.jsx_config) ~empty_loc ~full_module_name fn_name = - match (fn_name, config.nested_modules) with - | "make", _ :: _ -> + match (fn_name, config.nested_modules, config.functor_depth) with + | "make", _ :: _, 0 -> config.hoisted_structure_items <- make_hoisted_component_binding ~empty_loc ~full_module_name config.nested_modules From f0ebf0d1491758b945b7c0e32ac2483bca4c507b Mon Sep 17 00:00:00 2001 From: tsnobip Date: Tue, 24 Mar 2026 16:34:14 +0100 Subject: [PATCH 18/56] add failing test with interface file --- .../rsc_nested_jsx_deep/src/MultipleNested.resi | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.resi diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.resi b/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.resi new file mode 100644 index 00000000000..961a38e9aa2 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.resi @@ -0,0 +1,9 @@ +module Group: { + @react.component + let make: (~children: React.element) => React.element +} + +module Other: { + @react.component + let make: (~children: React.element) => React.element +} From 26cf5e255ea8914c0c40f5860ce3d389bb0ee0fa Mon Sep 17 00:00:00 2001 From: tsnobip Date: Tue, 24 Mar 2026 16:46:42 +0100 Subject: [PATCH 19/56] fix signature of @jsx.component --- compiler/syntax/src/jsx_common.ml | 13 +-- compiler/syntax/src/jsx_ppx.ml | 53 ++++++++++- compiler/syntax/src/jsx_v4.ml | 89 +++++++++++++++++-- .../react/expected/firstClassModules.resi.txt | 9 ++ .../ppx/react/expected/forwardRef.resi.txt | 32 +++++++ .../ppx/react/expected/interface.resi.txt | 4 + .../ppx/react/expected/sharedProps.resi.txt | 16 ++++ 7 files changed, 201 insertions(+), 15 deletions(-) diff --git a/compiler/syntax/src/jsx_common.ml b/compiler/syntax/src/jsx_common.ml index d07e3338dcc..84667be8124 100644 --- a/compiler/syntax/src/jsx_common.ml +++ b/compiler/syntax/src/jsx_common.ml @@ -7,14 +7,15 @@ type jsx_config = { mutable nested_modules: string list; mutable has_component: bool; mutable hoisted_structure_items: structure_item list; - (* Nesting depth of [structure] calls while rewriting one implementation. + mutable hoisted_signature_items: signature_item list; + (* Nesting depth of [structure] / [signature] while rewriting one file. Used so we only clear/append hoisted items at the true file root — not for - nested [Pmod_structure] reached from expression traversal (e.g. via - [module_expr]), which would otherwise see [nested_modules = []] and wipe - hoisted bindings added for earlier structure items. *) + nested [Pmod_structure] / [Pmty_signature] reached from inner traversal, + which would otherwise wipe hoists from earlier items. *) mutable structure_depth: int; - (* Inside [Pmod_functor] bodies, hoisting [File$M = M.make] at the file top - would reference [M] as a functor (illegal). Skip hoists when > 0. *) + (* Inside [Pmod_functor] / [Pmty_functor] bodies, hoisting [File$M = M.make] + at the file top would reference [M] as a functor (illegal). Skip hoists + when > 0. *) mutable functor_depth: int; } diff --git a/compiler/syntax/src/jsx_ppx.ml b/compiler/syntax/src/jsx_ppx.ml index 0b4ab0f2def..e3b22f0fca4 100644 --- a/compiler/syntax/src/jsx_ppx.ml +++ b/compiler/syntax/src/jsx_ppx.ml @@ -98,6 +98,37 @@ let get_mapper ~config = raise e) | _ -> default_mapper.module_expr mapper me in + let module_type mapper mt = + match (config.version, mt.pmty_desc) with + | 4, Pmty_functor _ -> ( + config.functor_depth <- config.functor_depth + 1; + try + let m = default_mapper.module_type mapper mt in + config.functor_depth <- config.functor_depth - 1; + m + with e -> + config.functor_depth <- config.functor_depth - 1; + raise e) + | _ -> default_mapper.module_type mapper mt + in + let module_declaration mapper md = + match config.version with + | 4 -> + config.nested_modules <- md.pmd_name.txt :: config.nested_modules; + let mapped = + try default_mapper.module_declaration mapper md + with e -> + (match config.nested_modules with + | _ :: rest -> config.nested_modules <- rest + | [] -> ()); + raise e + in + (match config.nested_modules with + | _ :: rest -> config.nested_modules <- rest + | [] -> ()); + mapped + | _ -> default_mapper.module_declaration mapper md + in let save_config () = { config with @@ -113,7 +144,10 @@ let get_mapper ~config = in let signature mapper items = let old_config = save_config () in + let is_top_level = config.structure_depth = 0 in + config.structure_depth <- config.structure_depth + 1; config.has_component <- false; + if is_top_level then config.hoisted_signature_items <- []; let result = List.map (fun item -> @@ -125,6 +159,12 @@ let get_mapper ~config = items |> List.flatten in + let result = + if config.version = 4 && is_top_level then + result @ List.rev config.hoisted_signature_items + else result + in + config.structure_depth <- config.structure_depth - 1; restore_config old_config; result in @@ -155,7 +195,16 @@ let get_mapper ~config = result in - {default_mapper with expr; module_binding; module_expr; signature; structure} + { + default_mapper with + expr; + module_binding; + module_expr; + module_type; + module_declaration; + signature; + structure; + } let rewrite_implementation ~jsx_version ~jsx_module (code : Parsetree.structure) : Parsetree.structure = @@ -166,6 +215,7 @@ let rewrite_implementation ~jsx_version ~jsx_module (code : Parsetree.structure) nested_modules = []; has_component = false; hoisted_structure_items = []; + hoisted_signature_items = []; structure_depth = 0; functor_depth = 0; } @@ -182,6 +232,7 @@ let rewrite_signature ~jsx_version ~jsx_module (code : Parsetree.signature) : nested_modules = []; has_component = false; hoisted_structure_items = []; + hoisted_signature_items = []; structure_depth = 0; functor_depth = 0; } diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 5eb52ece30b..17803f6be80 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -85,6 +85,17 @@ let longident_of_segments = function | head :: rest -> List.fold_left (fun acc name -> Ldot (acc, name)) (Lident head) rest +(* [nested_modules] is the same stack as [config.nested_modules] while inside a + nested [module M: { ... }]: outermost submodule name is at the tail. *) +let props_longident_for_nested_module nested_modules = + match List.rev nested_modules with + | [] -> Lident "props" + | m :: rest -> + let mod_path = + List.fold_left (fun acc name -> Ldot (acc, name)) (Lident m) rest + in + Ldot (mod_path, "props") + let make_hoisted_component_binding ~empty_loc ~full_module_name nested_modules = let path = nested_modules |> List.rev |> longident_of_segments |> fun txt -> @@ -140,6 +151,44 @@ let maybe_hoist_nested_make_component ~(config : Jsx_common.jsx_config) :: config.hoisted_structure_items | _ -> () +let make_hoisted_component_signature ~empty_loc ~full_module_name + (component_type : Parsetree.core_type) = + let marker_name = full_module_name ^ "$jsx" in + let bool_ty = + Typ.constr ~loc:empty_loc {loc = empty_loc; txt = Lident "bool"} [] + in + let full_sig = + { + psig_loc = empty_loc; + psig_desc = + Psig_value + (Val.mk ~loc:empty_loc + {loc = empty_loc; txt = full_module_name} + component_type); + } + in + let jsx_sig = + { + psig_loc = empty_loc; + psig_desc = + Psig_value + (Val.mk ~loc:empty_loc {loc = empty_loc; txt = marker_name} bool_ty); + } + in + (full_sig, jsx_sig) + +let maybe_hoist_nested_make_signature ~(config : Jsx_common.jsx_config) + ~empty_loc ~full_module_name ~component_type fn_name = + match (fn_name, config.nested_modules, config.functor_depth) with + | "make", _ :: _, 0 -> + let full_sig, jsx_sig = + make_hoisted_component_signature ~empty_loc ~full_module_name + component_type + in + config.hoisted_signature_items <- + jsx_sig :: full_sig :: config.hoisted_signature_items + | _ -> () + (* Build a string representation of a module name with segments separated by $ *) let make_module_name file_name nested_modules fn_name = let file_name = unnamespace_module_name file_name in @@ -1101,7 +1150,8 @@ let transform_signature_item ~config item = match item with | { psig_loc; - psig_desc = Psig_value ({pval_attributes; pval_type} as psig_desc); + psig_desc = + Psig_value ({pval_attributes; pval_type; pval_name} as psig_desc); } as psig -> ( match List.filter Jsx_common.has_attr pval_attributes with | [] -> [item] @@ -1117,15 +1167,23 @@ let transform_signature_item ~config item = in let prop_types = collect_prop_types [] pval_type in let named_type_list = List.fold_left arg_to_concrete_type [] prop_types in + let props_type_args = + match core_type_of_attr with + | None -> make_props_type_params named_type_list + | Some _ -> ( + match typ_vars_of_core_type with + | [] -> [] + | _ -> [Typ.any ()]) + in let ret_props_type = + Typ.constr (Location.mkloc (Lident "props") psig_loc) props_type_args + in + let ret_props_type_for_hoist = Typ.constr - (Location.mkloc (Lident "props") psig_loc) - (match core_type_of_attr with - | None -> make_props_type_params named_type_list - | Some _ -> ( - match typ_vars_of_core_type with - | [] -> [] - | _ -> [Typ.any ()])) + (Location.mkloc + (props_longident_for_nested_module config.nested_modules) + psig_loc) + props_type_args in let external_ = psig_desc.pval_prim <> [] in let props_record_type = @@ -1138,6 +1196,11 @@ let transform_signature_item ~config item = ( {loc = psig_loc; txt = module_access_name config "component"}, [ret_props_type] ) in + let new_external_type_for_hoist = + Ptyp_constr + ( {loc = psig_loc; txt = module_access_name config "component"}, + [ret_props_type_for_hoist] ) + in let new_structure = { psig with @@ -1150,6 +1213,16 @@ let transform_signature_item ~config item = }; } in + let file_name = filename_from_loc psig_loc in + let empty_loc = Location.in_file file_name in + let full_module_name = + make_module_name file_name config.nested_modules pval_name.txt + in + let component_type = + {pval_type with ptyp_desc = new_external_type_for_hoist} + in + maybe_hoist_nested_make_signature ~config ~empty_loc ~full_module_name + ~component_type pval_name.txt; [props_record_type; new_structure] | _ -> Jsx_common.raise_error ~loc:psig_loc diff --git a/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt b/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt index feabbdaf5f4..9a3cb5536f3 100644 --- a/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt @@ -22,3 +22,12 @@ module Select: { >, > } +let \"FirstClassModules$Select": React.component< + Select.props< + module(T with type t = 'a and type key = 'key), + option<'key>, + option<'key> => unit, + array<'a>, + >, +> +let \"FirstClassModules$Select$jsx": bool diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt index 47cd6f10105..89055b25426 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt @@ -89,3 +89,35 @@ module V4AUncurried: { let make: React.component>>> } } +let \"ForwardRef$V4C$FancyInput": React.component< + V4C.FancyInput.props, +> +let \"ForwardRef$V4C$FancyInput$jsx": bool +let \"ForwardRef$V4C$ForwardRef": React.component< + V4C.ForwardRef.props>>, +> +let \"ForwardRef$V4C$ForwardRef$jsx": bool +let \"ForwardRef$V4CUncurried$FancyInput": React.component< + V4CUncurried.FancyInput.props, +> +let \"ForwardRef$V4CUncurried$FancyInput$jsx": bool +let \"ForwardRef$V4CUncurried$ForwardRef": React.component< + V4CUncurried.ForwardRef.props>>, +> +let \"ForwardRef$V4CUncurried$ForwardRef$jsx": bool +let \"ForwardRef$V4A$FancyInput": React.component< + V4A.FancyInput.props, +> +let \"ForwardRef$V4A$FancyInput$jsx": bool +let \"ForwardRef$V4A$ForwardRef": React.component< + V4A.ForwardRef.props>>, +> +let \"ForwardRef$V4A$ForwardRef$jsx": bool +let \"ForwardRef$V4AUncurried$FancyInput": React.component< + V4AUncurried.FancyInput.props, +> +let \"ForwardRef$V4AUncurried$FancyInput$jsx": bool +let \"ForwardRef$V4AUncurried$ForwardRef": React.component< + V4AUncurried.ForwardRef.props>>, +> +let \"ForwardRef$V4AUncurried$ForwardRef$jsx": bool diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt b/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt index d0cc914abf7..6a21c4d722c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt @@ -12,3 +12,7 @@ module NoProps: { let make: React.component } +let \"Interface$A": React.component> +let \"Interface$A$jsx": bool +let \"Interface$NoProps": React.component +let \"Interface$NoProps$jsx": bool diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt index 3deabcfbe3e..7674ec9fc3a 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt @@ -49,3 +49,19 @@ module V4A4: { let make: React.component } +let \"SharedProps$V4C1": React.component +let \"SharedProps$V4C1$jsx": bool +let \"SharedProps$V4C2": React.component> +let \"SharedProps$V4C2$jsx": bool +let \"SharedProps$V4C3": React.component> +let \"SharedProps$V4C3$jsx": bool +let \"SharedProps$V4C4": React.component +let \"SharedProps$V4C4$jsx": bool +let \"SharedProps$V4A1": React.component +let \"SharedProps$V4A1$jsx": bool +let \"SharedProps$V4A2": React.component> +let \"SharedProps$V4A2$jsx": bool +let \"SharedProps$V4A3": React.component> +let \"SharedProps$V4A3$jsx": bool +let \"SharedProps$V4A4": React.component +let \"SharedProps$V4A4$jsx": bool From 69603a7c86c3b50e8885a8209a5ad3e4cdc16479 Mon Sep 17 00:00:00 2001 From: tsnobip Date: Tue, 24 Mar 2026 19:07:39 +0100 Subject: [PATCH 20/56] repro: private JSX components raise warning 32 --- tests/build_tests/rsc_nested_jsx_deep/rescript.json | 7 +++++-- .../rsc_nested_jsx_deep/src/MultipleNested.res | 9 ++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/build_tests/rsc_nested_jsx_deep/rescript.json b/tests/build_tests/rsc_nested_jsx_deep/rescript.json index 7f67937417b..07fe7f0788c 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/rescript.json +++ b/tests/build_tests/rsc_nested_jsx_deep/rescript.json @@ -12,5 +12,8 @@ "in-source": true, "suffix": ".res.js" }, - "namespace": true -} + "namespace": true, + "warnings": { + "error": "+A" + } +} \ No newline at end of file diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.res b/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.res index 4e6780c8eee..d3a41698541 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.res +++ b/tests/build_tests/rsc_nested_jsx_deep/src/MultipleNested.res @@ -1,10 +1,17 @@ -module Group = { +module Internal = { @react.component let make = (~children) => { children } } +module Group = { + @react.component + let make = (~children) => { + {children} + } +} + module Other = { @react.component let make = (~children) => { From 88e45fa69297ff6f60f2bcf39aa6e104ca75e1d0 Mon Sep 17 00:00:00 2001 From: tsnobip Date: Tue, 24 Mar 2026 19:37:28 +0100 Subject: [PATCH 21/56] remove warning -32 when emitting the namespaced function and jsx status --- compiler/syntax/src/jsx_v4.ml | 13 ++++++++-- .../rsc_nested_jsx_deep/rescript.json | 2 +- .../ppx/react/expected/aliasProps.res.txt | 14 +++++------ .../ppx/react/expected/asyncAwait.res.txt | 4 ++-- .../react/expected/defaultPatternProp.res.txt | 6 +++-- .../react/expected/defaultValueProp.res.txt | 12 ++++++---- .../react/expected/externalWithRef.res.txt | 3 ++- .../externalWithTypeVariables.res.txt | 3 ++- .../react/expected/fileLevelConfig.res.txt | 3 ++- .../ppx/react/expected/forwardRef.res.txt | 12 ++++++---- .../data/ppx/react/expected/interface.res.txt | 5 ++-- .../ppx/react/expected/mangleKeyword.res.txt | 6 +++-- .../data/ppx/react/expected/nested.res.txt | 5 ++-- .../data/ppx/react/expected/newtype.res.txt | 11 +++++---- .../ppx/react/expected/noPropsWithKey.res.txt | 9 ++++--- .../expected/optimizeAutomaticMode.res.txt | 3 ++- .../react/expected/returnConstraint.res.txt | 12 ++++++---- .../ppx/react/expected/sharedProps.res.txt | 24 ++++++++++++------- .../expected/sharedPropsWithProps.res.txt | 21 ++++++++++------ .../data/ppx/react/expected/topLevel.res.txt | 2 +- .../ppx/react/expected/typeConstraint.res.txt | 3 ++- .../ppx/react/expected/uncurriedProps.res.txt | 6 +++-- .../data/ppx/react/expected/v4.res.txt | 13 +++++----- 23 files changed, 122 insertions(+), 70 deletions(-) diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 17803f6be80..9412449a604 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -96,6 +96,15 @@ let props_longident_for_nested_module nested_modules = in Ldot (mod_path, "props") +(* Hoisted File$Nested / File$Nested$jsx exist for JS/RSC exports; they are not + always referenced from ReScript when a nested module is absent from the + [.resi], so suppress unused-value (32) on these bindings only. *) +let jsx_hoisted_binding_warning_attrs = + [ + ( Location.mknoloc "warning", + PStr [Str.eval (Exp.constant (Pconst_string ("-32", None)))] ); + ] + let make_hoisted_component_binding ~empty_loc ~full_module_name nested_modules = let path = nested_modules |> List.rev |> longident_of_segments |> fun txt -> @@ -108,10 +117,10 @@ let make_hoisted_component_binding ~empty_loc ~full_module_name nested_modules = Pstr_value ( Nonrecursive, [ - Vb.mk ~loc:empty_loc + Vb.mk ~loc:empty_loc ~attrs:jsx_hoisted_binding_warning_attrs (Pat.var ~loc:empty_loc {loc = empty_loc; txt = full_module_name}) (Exp.ident ~loc:empty_loc path); - Vb.mk ~loc:empty_loc + Vb.mk ~loc:empty_loc ~attrs:jsx_hoisted_binding_warning_attrs (Pat.var ~loc:empty_loc {loc = empty_loc; txt = marker_name}) (Exp.construct ~loc:empty_loc {loc = empty_loc; txt = Lident "true"} diff --git a/tests/build_tests/rsc_nested_jsx_deep/rescript.json b/tests/build_tests/rsc_nested_jsx_deep/rescript.json index 07fe7f0788c..4b94b2840cb 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/rescript.json +++ b/tests/build_tests/rsc_nested_jsx_deep/rescript.json @@ -16,4 +16,4 @@ "warnings": { "error": "+A" } -} \ No newline at end of file +} diff --git a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt index 106e33b8ff5..ec718c8b55d 100644 --- a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt @@ -162,10 +162,10 @@ module C6 = { \"AliasProps$C6" } } -let \"AliasProps$C0" = C0.make and \"AliasProps$C0$jsx" = true -let \"AliasProps$C1" = C1.make and \"AliasProps$C1$jsx" = true -let \"AliasProps$C2" = C2.make and \"AliasProps$C2$jsx" = true -let \"AliasProps$C3" = C3.make and \"AliasProps$C3$jsx" = true -let \"AliasProps$C4" = C4.make and \"AliasProps$C4$jsx" = true -let \"AliasProps$C5" = C5.make and \"AliasProps$C5$jsx" = true -let \"AliasProps$C6" = C6.make and \"AliasProps$C6$jsx" = true +@warning("-32") let \"AliasProps$C0" = C0.make @warning("-32") and \"AliasProps$C0$jsx" = true +@warning("-32") let \"AliasProps$C1" = C1.make @warning("-32") and \"AliasProps$C1$jsx" = true +@warning("-32") let \"AliasProps$C2" = C2.make @warning("-32") and \"AliasProps$C2$jsx" = true +@warning("-32") let \"AliasProps$C3" = C3.make @warning("-32") and \"AliasProps$C3$jsx" = true +@warning("-32") let \"AliasProps$C4" = C4.make @warning("-32") and \"AliasProps$C4$jsx" = true +@warning("-32") let \"AliasProps$C5" = C5.make @warning("-32") and \"AliasProps$C5$jsx" = true +@warning("-32") let \"AliasProps$C6" = C6.make @warning("-32") and \"AliasProps$C6$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt index dea1b17712c..4a2ccb3500c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt @@ -35,5 +35,5 @@ module C1 = { \"AsyncAwait$C1" } } -let \"AsyncAwait$C0" = C0.make and \"AsyncAwait$C0$jsx" = true -let \"AsyncAwait$C1" = C1.make and \"AsyncAwait$C1$jsx" = true +@warning("-32") let \"AsyncAwait$C0" = C0.make @warning("-32") and \"AsyncAwait$C0$jsx" = true +@warning("-32") let \"AsyncAwait$C1" = C1.make @warning("-32") and \"AsyncAwait$C1$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt index 4f7291dacc9..e4a96d16e0c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt @@ -33,5 +33,7 @@ module C0 = { \"DefaultPatternProp$C0" } } -let \"DefaultPatternProp$C0$M" = C0.M.make and \"DefaultPatternProp$C0$M$jsx" = true -let \"DefaultPatternProp$C0" = C0.make and \"DefaultPatternProp$C0$jsx" = true +@warning("-32") let \"DefaultPatternProp$C0$M" = C0.M.make +@warning("-32") and \"DefaultPatternProp$C0$M$jsx" = true +@warning("-32") let \"DefaultPatternProp$C0" = C0.make +@warning("-32") and \"DefaultPatternProp$C0$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt index fa928be4406..7b24b91d0b4 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt @@ -91,7 +91,11 @@ module C3 = { \"DefaultValueProp$C3" } } -let \"DefaultValueProp$C0" = C0.make and \"DefaultValueProp$C0$jsx" = true -let \"DefaultValueProp$C1" = C1.make and \"DefaultValueProp$C1$jsx" = true -let \"DefaultValueProp$C2" = C2.make and \"DefaultValueProp$C2$jsx" = true -let \"DefaultValueProp$C3" = C3.make and \"DefaultValueProp$C3$jsx" = true +@warning("-32") let \"DefaultValueProp$C0" = C0.make +@warning("-32") and \"DefaultValueProp$C0$jsx" = true +@warning("-32") let \"DefaultValueProp$C1" = C1.make +@warning("-32") and \"DefaultValueProp$C1$jsx" = true +@warning("-32") let \"DefaultValueProp$C2" = C2.make +@warning("-32") and \"DefaultValueProp$C2$jsx" = true +@warning("-32") let \"DefaultValueProp$C3" = C3.make +@warning("-32") and \"DefaultValueProp$C3$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt index f981aad592c..13e42c9dae9 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt @@ -10,4 +10,5 @@ module V4C = { @module("componentForwardRef") external make: React.component> = "component" } -let \"ExternalWithRef$V4C" = V4C.make and \"ExternalWithRef$V4C$jsx" = true +@warning("-32") let \"ExternalWithRef$V4C" = V4C.make +@warning("-32") and \"ExternalWithRef$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt index e271934ecbb..72c71617ae4 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt @@ -10,4 +10,5 @@ module V4C = { @module("c") external make: React.component, React.element>> = "component" } -let \"ExternalWithTypeVariables$V4C" = V4C.make and \"ExternalWithTypeVariables$V4C$jsx" = true +@warning("-32") let \"ExternalWithTypeVariables$V4C" = V4C.make +@warning("-32") and \"ExternalWithTypeVariables$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt index 2a3dae60b75..98b0b1dd63a 100644 --- a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt @@ -15,4 +15,5 @@ module V4A = { \"FileLevelConfig$V4A" } } -let \"FileLevelConfig$V4A" = V4A.make and \"FileLevelConfig$V4A$jsx" = true +@warning("-32") let \"FileLevelConfig$V4A" = V4A.make +@warning("-32") and \"FileLevelConfig$V4A$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt index abe2a0b7e9e..ee69fc32062 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt @@ -121,8 +121,10 @@ module V4AUncurried = { \"ForwardRef$V4AUncurried" } } -let \"ForwardRef$V4A$FancyInput" = V4A.FancyInput.make and \"ForwardRef$V4A$FancyInput$jsx" = true -let \"ForwardRef$V4A" = V4A.make and \"ForwardRef$V4A$jsx" = true -let \"ForwardRef$V4AUncurried$FancyInput" = V4AUncurried.FancyInput.make -and \"ForwardRef$V4AUncurried$FancyInput$jsx" = true -let \"ForwardRef$V4AUncurried" = V4AUncurried.make and \"ForwardRef$V4AUncurried$jsx" = true +@warning("-32") let \"ForwardRef$V4A$FancyInput" = V4A.FancyInput.make +@warning("-32") and \"ForwardRef$V4A$FancyInput$jsx" = true +@warning("-32") let \"ForwardRef$V4A" = V4A.make @warning("-32") and \"ForwardRef$V4A$jsx" = true +@warning("-32") let \"ForwardRef$V4AUncurried$FancyInput" = V4AUncurried.FancyInput.make +@warning("-32") and \"ForwardRef$V4AUncurried$FancyInput$jsx" = true +@warning("-32") let \"ForwardRef$V4AUncurried" = V4AUncurried.make +@warning("-32") and \"ForwardRef$V4AUncurried$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt index 363f9439a0a..674cfc85dcf 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt @@ -21,5 +21,6 @@ module NoProps = { \"Interface$NoProps" } } -let \"Interface$A" = A.make and \"Interface$A$jsx" = true -let \"Interface$NoProps" = NoProps.make and \"Interface$NoProps$jsx" = true +@warning("-32") let \"Interface$A" = A.make @warning("-32") and \"Interface$A$jsx" = true +@warning("-32") let \"Interface$NoProps" = NoProps.make +@warning("-32") and \"Interface$NoProps$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt index 8b9bd814834..1d79a25571c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt @@ -27,5 +27,7 @@ module C4A1 = { let c4a0 = React.jsx(@res.jsxComponentPath C4A0.make, {_open: "x", _type: "t"}) let c4a1 = React.jsx(@res.jsxComponentPath C4A1.make, {_open: "x", _type: "t"}) -let \"MangleKeyword$C4A0" = C4A0.make and \"MangleKeyword$C4A0$jsx" = true -let \"MangleKeyword$C4A1" = C4A1.make and \"MangleKeyword$C4A1$jsx" = true +@warning("-32") let \"MangleKeyword$C4A0" = C4A0.make +@warning("-32") and \"MangleKeyword$C4A0$jsx" = true +@warning("-32") let \"MangleKeyword$C4A1" = C4A1.make +@warning("-32") and \"MangleKeyword$C4A1$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt index 6cb92900d5e..76e62072ce8 100644 --- a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt @@ -21,5 +21,6 @@ module Outer = { \"Nested$Outer" } } -let \"Nested$Outer$Inner" = Outer.Inner.make and \"Nested$Outer$Inner$jsx" = true -let \"Nested$Outer" = Outer.make and \"Nested$Outer$jsx" = true +@warning("-32") let \"Nested$Outer$Inner" = Outer.Inner.make +@warning("-32") and \"Nested$Outer$Inner$jsx" = true +@warning("-32") let \"Nested$Outer" = Outer.make @warning("-32") and \"Nested$Outer$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt index 8296472d4d1..fdfc5714bfa 100644 --- a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt @@ -99,8 +99,9 @@ module Uncurried = { \"Newtype$Uncurried" } } -let \"Newtype$V4A" = V4A.make and \"Newtype$V4A$jsx" = true -let \"Newtype$V4A1" = V4A1.make and \"Newtype$V4A1$jsx" = true -let \"Newtype$V4A2" = V4A2.make and \"Newtype$V4A2$jsx" = true -let \"Newtype$V4A3" = V4A3.make and \"Newtype$V4A3$jsx" = true -let \"Newtype$Uncurried" = Uncurried.make and \"Newtype$Uncurried$jsx" = true +@warning("-32") let \"Newtype$V4A" = V4A.make @warning("-32") and \"Newtype$V4A$jsx" = true +@warning("-32") let \"Newtype$V4A1" = V4A1.make @warning("-32") and \"Newtype$V4A1$jsx" = true +@warning("-32") let \"Newtype$V4A2" = V4A2.make @warning("-32") and \"Newtype$V4A2$jsx" = true +@warning("-32") let \"Newtype$V4A3" = V4A3.make @warning("-32") and \"Newtype$V4A3$jsx" = true +@warning("-32") let \"Newtype$Uncurried" = Uncurried.make +@warning("-32") and \"Newtype$Uncurried$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt index bd40f4c83c7..7b4e9bd7ea3 100644 --- a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt @@ -40,6 +40,9 @@ module V4C = { \"NoPropsWithKey$V4C" } } -let \"NoPropsWithKey$V4CA" = V4CA.make and \"NoPropsWithKey$V4CA$jsx" = true -let \"NoPropsWithKey$V4CB" = V4CB.make and \"NoPropsWithKey$V4CB$jsx" = true -let \"NoPropsWithKey$V4C" = V4C.make and \"NoPropsWithKey$V4C$jsx" = true +@warning("-32") let \"NoPropsWithKey$V4CA" = V4CA.make +@warning("-32") and \"NoPropsWithKey$V4CA$jsx" = true +@warning("-32") let \"NoPropsWithKey$V4CB" = V4CB.make +@warning("-32") and \"NoPropsWithKey$V4CB$jsx" = true +@warning("-32") let \"NoPropsWithKey$V4C" = V4C.make +@warning("-32") and \"NoPropsWithKey$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt index 7797329571a..b0b191ffde8 100644 --- a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt @@ -18,4 +18,5 @@ module User = { \"OptimizeAutomaticMode$User" } } -let \"OptimizeAutomaticMode$User" = User.make and \"OptimizeAutomaticMode$User$jsx" = true +@warning("-32") let \"OptimizeAutomaticMode$User" = User.make +@warning("-32") and \"OptimizeAutomaticMode$User$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt index eff4facc7bd..3320905230f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt @@ -47,7 +47,11 @@ module Async = { \"ReturnConstraint$Async" } } -let \"ReturnConstraint$Standard" = Standard.make and \"ReturnConstraint$Standard$jsx" = true -let \"ReturnConstraint$ForwardRef" = ForwardRef.make and \"ReturnConstraint$ForwardRef$jsx" = true -let \"ReturnConstraint$WithProps" = WithProps.make and \"ReturnConstraint$WithProps$jsx" = true -let \"ReturnConstraint$Async" = Async.make and \"ReturnConstraint$Async$jsx" = true +@warning("-32") let \"ReturnConstraint$Standard" = Standard.make +@warning("-32") and \"ReturnConstraint$Standard$jsx" = true +@warning("-32") let \"ReturnConstraint$ForwardRef" = ForwardRef.make +@warning("-32") and \"ReturnConstraint$ForwardRef$jsx" = true +@warning("-32") let \"ReturnConstraint$WithProps" = WithProps.make +@warning("-32") and \"ReturnConstraint$WithProps$jsx" = true +@warning("-32") let \"ReturnConstraint$Async" = Async.make +@warning("-32") and \"ReturnConstraint$Async$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt index 70cb0dee1dc..777b9cff874 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt @@ -67,11 +67,19 @@ module V4A8 = { external make: React.component = "default" } -let \"SharedProps$V4A1" = V4A1.make and \"SharedProps$V4A1$jsx" = true -let \"SharedProps$V4A2" = V4A2.make and \"SharedProps$V4A2$jsx" = true -let \"SharedProps$V4A3" = V4A3.make and \"SharedProps$V4A3$jsx" = true -let \"SharedProps$V4A4" = V4A4.make and \"SharedProps$V4A4$jsx" = true -let \"SharedProps$V4A5" = V4A5.make and \"SharedProps$V4A5$jsx" = true -let \"SharedProps$V4A6" = V4A6.make and \"SharedProps$V4A6$jsx" = true -let \"SharedProps$V4A7" = V4A7.make and \"SharedProps$V4A7$jsx" = true -let \"SharedProps$V4A8" = V4A8.make and \"SharedProps$V4A8$jsx" = true +@warning("-32") let \"SharedProps$V4A1" = V4A1.make +@warning("-32") and \"SharedProps$V4A1$jsx" = true +@warning("-32") let \"SharedProps$V4A2" = V4A2.make +@warning("-32") and \"SharedProps$V4A2$jsx" = true +@warning("-32") let \"SharedProps$V4A3" = V4A3.make +@warning("-32") and \"SharedProps$V4A3$jsx" = true +@warning("-32") let \"SharedProps$V4A4" = V4A4.make +@warning("-32") and \"SharedProps$V4A4$jsx" = true +@warning("-32") let \"SharedProps$V4A5" = V4A5.make +@warning("-32") and \"SharedProps$V4A5$jsx" = true +@warning("-32") let \"SharedProps$V4A6" = V4A6.make +@warning("-32") and \"SharedProps$V4A6$jsx" = true +@warning("-32") let \"SharedProps$V4A7" = V4A7.make +@warning("-32") and \"SharedProps$V4A7$jsx" = true +@warning("-32") let \"SharedProps$V4A8" = V4A8.make +@warning("-32") and \"SharedProps$V4A8$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt index f03c5e7248f..98ea0b39a8f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt @@ -76,10 +76,17 @@ module V4A7 = { \"SharedPropsWithProps$V4A7" } } -let \"SharedPropsWithProps$V4A1" = V4A1.make and \"SharedPropsWithProps$V4A1$jsx" = true -let \"SharedPropsWithProps$V4A2" = V4A2.make and \"SharedPropsWithProps$V4A2$jsx" = true -let \"SharedPropsWithProps$V4A3" = V4A3.make and \"SharedPropsWithProps$V4A3$jsx" = true -let \"SharedPropsWithProps$V4A4" = V4A4.make and \"SharedPropsWithProps$V4A4$jsx" = true -let \"SharedPropsWithProps$V4A5" = V4A5.make and \"SharedPropsWithProps$V4A5$jsx" = true -let \"SharedPropsWithProps$V4A6" = V4A6.make and \"SharedPropsWithProps$V4A6$jsx" = true -let \"SharedPropsWithProps$V4A7" = V4A7.make and \"SharedPropsWithProps$V4A7$jsx" = true +@warning("-32") let \"SharedPropsWithProps$V4A1" = V4A1.make +@warning("-32") and \"SharedPropsWithProps$V4A1$jsx" = true +@warning("-32") let \"SharedPropsWithProps$V4A2" = V4A2.make +@warning("-32") and \"SharedPropsWithProps$V4A2$jsx" = true +@warning("-32") let \"SharedPropsWithProps$V4A3" = V4A3.make +@warning("-32") and \"SharedPropsWithProps$V4A3$jsx" = true +@warning("-32") let \"SharedPropsWithProps$V4A4" = V4A4.make +@warning("-32") and \"SharedPropsWithProps$V4A4$jsx" = true +@warning("-32") let \"SharedPropsWithProps$V4A5" = V4A5.make +@warning("-32") and \"SharedPropsWithProps$V4A5$jsx" = true +@warning("-32") let \"SharedPropsWithProps$V4A6" = V4A6.make +@warning("-32") and \"SharedPropsWithProps$V4A6$jsx" = true +@warning("-32") let \"SharedPropsWithProps$V4A7" = V4A7.make +@warning("-32") and \"SharedPropsWithProps$V4A7$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt index cc329602999..36a14fdaf62 100644 --- a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt @@ -17,4 +17,4 @@ module V4A = { \"TopLevel$V4A" } } -let \"TopLevel$V4A" = V4A.make and \"TopLevel$V4A$jsx" = true +@warning("-32") let \"TopLevel$V4A" = V4A.make @warning("-32") and \"TopLevel$V4A$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt index 92432c4be8c..30f6be7931d 100644 --- a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt @@ -14,4 +14,5 @@ module V4A = { \"TypeConstraint$V4A" } } -let \"TypeConstraint$V4A" = V4A.make and \"TypeConstraint$V4A$jsx" = true +@warning("-32") let \"TypeConstraint$V4A" = V4A.make +@warning("-32") and \"TypeConstraint$V4A$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt index 77a78d4f6ff..8cb02b596d9 100644 --- a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt @@ -65,5 +65,7 @@ module Bar = { \"UncurriedProps$Bar" } } -let \"UncurriedProps$Foo" = Foo.make and \"UncurriedProps$Foo$jsx" = true -let \"UncurriedProps$Bar" = Bar.make and \"UncurriedProps$Bar$jsx" = true +@warning("-32") let \"UncurriedProps$Foo" = Foo.make +@warning("-32") and \"UncurriedProps$Foo$jsx" = true +@warning("-32") let \"UncurriedProps$Bar" = Bar.make +@warning("-32") and \"UncurriedProps$Bar$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt index c4de276ae23..1bd7275a719 100644 --- a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt @@ -116,9 +116,10 @@ module Rec2 = { } and mm = x => make(x) } -let \"V4$Uncurried" = Uncurried.make and \"V4$Uncurried$jsx" = true -let \"V4$E" = E.make and \"V4$E$jsx" = true -let \"V4$EUncurried" = EUncurried.make and \"V4$EUncurried$jsx" = true -let \"V4$Rec" = Rec.make and \"V4$Rec$jsx" = true -let \"V4$Rec1" = Rec1.make and \"V4$Rec1$jsx" = true -let \"V4$Rec2" = Rec2.make and \"V4$Rec2$jsx" = true +@warning("-32") let \"V4$Uncurried" = Uncurried.make @warning("-32") and \"V4$Uncurried$jsx" = true +@warning("-32") let \"V4$E" = E.make @warning("-32") and \"V4$E$jsx" = true +@warning("-32") let \"V4$EUncurried" = EUncurried.make +@warning("-32") and \"V4$EUncurried$jsx" = true +@warning("-32") let \"V4$Rec" = Rec.make @warning("-32") and \"V4$Rec$jsx" = true +@warning("-32") let \"V4$Rec1" = Rec1.make @warning("-32") and \"V4$Rec1$jsx" = true +@warning("-32") let \"V4$Rec2" = Rec2.make @warning("-32") and \"V4$Rec2$jsx" = true From 5d4b296bca950e51da7129a3b43736ec8ac573fb Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Tue, 31 Mar 2026 23:05:06 +0200 Subject: [PATCH 22/56] Even more regression tests --- .../rsc_nested_jsx_alias_chain/input.js | 37 +++++++++++++++++++ .../rsc_nested_jsx_alias_chain/rescript.json | 16 ++++++++ .../src/MainLayout.res | 7 ++++ .../src/PlainAccess.res | 6 +++ .../rsc_nested_jsx_alias_chain/src/React.res | 10 +++++ .../src/Sidebar.res | 4 ++ .../rsc_nested_jsx_local_hidden_resi/input.js | 31 ++++++++++++++++ .../rescript.json | 15 ++++++++ .../src/Consumer.res | 2 + .../src/LocalOnly.res | 9 +++++ .../src/LocalOnly.resi | 2 + .../src/React.res | 10 +++++ .../rsc_nested_jsx_warn_error/input.js | 19 ++++++++++ .../rsc_nested_jsx_warn_error/rescript.json | 19 ++++++++++ .../src/MainLayout.res | 4 ++ .../rsc_nested_jsx_warn_error/src/React.res | 10 +++++ .../rsc_nested_jsx_warn_error/src/Sidebar.res | 4 ++ 17 files changed, 205 insertions(+) create mode 100644 tests/build_tests/rsc_nested_jsx_alias_chain/input.js create mode 100644 tests/build_tests/rsc_nested_jsx_alias_chain/rescript.json create mode 100644 tests/build_tests/rsc_nested_jsx_alias_chain/src/MainLayout.res create mode 100644 tests/build_tests/rsc_nested_jsx_alias_chain/src/PlainAccess.res create mode 100644 tests/build_tests/rsc_nested_jsx_alias_chain/src/React.res create mode 100644 tests/build_tests/rsc_nested_jsx_alias_chain/src/Sidebar.res create mode 100644 tests/build_tests/rsc_nested_jsx_local_hidden_resi/input.js create mode 100644 tests/build_tests/rsc_nested_jsx_local_hidden_resi/rescript.json create mode 100644 tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/Consumer.res create mode 100644 tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/LocalOnly.res create mode 100644 tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/LocalOnly.resi create mode 100644 tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/React.res create mode 100644 tests/build_tests/rsc_nested_jsx_warn_error/input.js create mode 100644 tests/build_tests/rsc_nested_jsx_warn_error/rescript.json create mode 100644 tests/build_tests/rsc_nested_jsx_warn_error/src/MainLayout.res create mode 100644 tests/build_tests/rsc_nested_jsx_warn_error/src/React.res create mode 100644 tests/build_tests/rsc_nested_jsx_warn_error/src/Sidebar.res diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js new file mode 100644 index 00000000000..5c2c03b2048 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -0,0 +1,37 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const layoutOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "MainLayout.res.js"), + "utf8", +); +const plainAccessOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "PlainAccess.res.js"), + "utf8", +); + +assert.match( + layoutOutput, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider,/, +); +assert.doesNotMatch(layoutOutput, /\.Provider\.make,/); +assert.match( + plainAccessOutput, + /let provider = Sidebar\$RscNestedJsxAliasChain\.Provider\.make;/, +); +assert.match( + plainAccessOutput, + /let callProvider = Sidebar\$RscNestedJsxAliasChain\.Provider\.make\(\{/, +); +assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); + +await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/rescript.json b/tests/build_tests/rsc_nested_jsx_alias_chain/rescript.json new file mode 100644 index 00000000000..a9eae8680c0 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-nested-jsx-alias-chain", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + }, + "namespace": true +} diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/src/MainLayout.res b/tests/build_tests/rsc_nested_jsx_alias_chain/src/MainLayout.res new file mode 100644 index 00000000000..9fcca339704 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/src/MainLayout.res @@ -0,0 +1,7 @@ +module SidebarAlias = Sidebar +module ProviderAlias = SidebarAlias.Provider + +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/src/PlainAccess.res b/tests/build_tests/rsc_nested_jsx_alias_chain/src/PlainAccess.res new file mode 100644 index 00000000000..73b07478a5c --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/src/PlainAccess.res @@ -0,0 +1,6 @@ +module SidebarAlias = Sidebar +module ProviderAlias = SidebarAlias.Provider + +let provider = ProviderAlias.make + +let callProvider = ProviderAlias.make({children: React.null}) diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/src/React.res b/tests/build_tests/rsc_nested_jsx_alias_chain/src/React.res new file mode 100644 index 00000000000..733b8ee02f0 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/src/React.res @@ -0,0 +1,10 @@ +type element + +@val external null: element = "null" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/src/Sidebar.res b/tests/build_tests/rsc_nested_jsx_alias_chain/src/Sidebar.res new file mode 100644 index 00000000000..25e1db24bca --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/src/Sidebar.res @@ -0,0 +1,4 @@ +module Provider = { + @react.component + let make = (~children) => children +} diff --git a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/input.js b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/input.js new file mode 100644 index 00000000000..2017210dc4f --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/input.js @@ -0,0 +1,31 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +const buildResult = await execBuild(); + +if (buildResult.status !== 0) { + assert.fail(buildResult.stdout + buildResult.stderr); +} + +const localOnlyOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "LocalOnly.res.js"), + "utf8", +); +const consumerOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "Consumer.res.js"), + "utf8", +); + +assert.match(localOnlyOutput, /JsxRuntime\.jsx\(LocalOnly\$Hidden,/); +assert.doesNotMatch(localOnlyOutput, /export \{[\s\S]*Hidden[\s\S]*\}/s); +assert.doesNotMatch(localOnlyOutput, /LocalOnly\$Hidden\$jsx/); +assert.match(consumerOutput, /JsxRuntime\.jsx\(LocalOnly\.make,/); + +await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/rescript.json b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/rescript.json new file mode 100644 index 00000000000..f80ec5668d8 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/rescript.json @@ -0,0 +1,15 @@ +{ + "name": "rsc-nested-jsx-local-hidden-resi", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + } +} diff --git a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/Consumer.res b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/Consumer.res new file mode 100644 index 00000000000..a52fa6eb5cd --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/Consumer.res @@ -0,0 +1,2 @@ +@react.component +let make = () => diff --git a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/LocalOnly.res b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/LocalOnly.res new file mode 100644 index 00000000000..1d2de50211e --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/LocalOnly.res @@ -0,0 +1,9 @@ +module Hidden = { + @react.component + let make = () => React.null +} + +@react.component +let make = () => { + +} diff --git a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/LocalOnly.resi b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/LocalOnly.resi new file mode 100644 index 00000000000..1ca44ce2609 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/LocalOnly.resi @@ -0,0 +1,2 @@ +@react.component +let make: unit => React.element diff --git a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/React.res b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/React.res new file mode 100644 index 00000000000..733b8ee02f0 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/React.res @@ -0,0 +1,10 @@ +type element + +@val external null: element = "null" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_warn_error/input.js b/tests/build_tests/rsc_nested_jsx_warn_error/input.js new file mode 100644 index 00000000000..94e48dc3352 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_warn_error/input.js @@ -0,0 +1,19 @@ +// @ts-check + +import * as assert from "node:assert"; +import { stripVTControlCharacters } from "node:util"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +const result = await execBuild(); + +if (result.status !== 0) { + assert.fail(result.stdout + result.stderr); +} + +const stderr = stripVTControlCharacters(result.stderr); +assert.doesNotMatch(stderr, /Warning number 32/); + +await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_warn_error/rescript.json b/tests/build_tests/rsc_nested_jsx_warn_error/rescript.json new file mode 100644 index 00000000000..62ea5431fa6 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_warn_error/rescript.json @@ -0,0 +1,19 @@ +{ + "name": "rsc-nested-jsx-warn-error", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + }, + "namespace": true, + "warnings": { + "error": "+A" + } +} diff --git a/tests/build_tests/rsc_nested_jsx_warn_error/src/MainLayout.res b/tests/build_tests/rsc_nested_jsx_warn_error/src/MainLayout.res new file mode 100644 index 00000000000..e2c753eeb17 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_warn_error/src/MainLayout.res @@ -0,0 +1,4 @@ +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/build_tests/rsc_nested_jsx_warn_error/src/React.res b/tests/build_tests/rsc_nested_jsx_warn_error/src/React.res new file mode 100644 index 00000000000..733b8ee02f0 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_warn_error/src/React.res @@ -0,0 +1,10 @@ +type element + +@val external null: element = "null" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_warn_error/src/Sidebar.res b/tests/build_tests/rsc_nested_jsx_warn_error/src/Sidebar.res new file mode 100644 index 00000000000..25e1db24bca --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_warn_error/src/Sidebar.res @@ -0,0 +1,4 @@ +module Provider = { + @react.component + let make = (~children) => children +} From b8ba01bb64274176e71fdcf4e24f52e968b61f10 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 1 Apr 2026 15:56:11 +0200 Subject: [PATCH 23/56] @react.componentWithProps regression tests --- .../rsc_component_with_props_members/input.js | 53 +++++++++++++++++++ .../rescript.json | 16 ++++++ .../src/MainLayout.res | 6 +++ .../src/PlainAccess.res | 3 ++ .../src/React.res | 10 ++++ .../src/Sidebar.res | 13 +++++ .../input.js | 31 +++++++++++ .../rescript.json | 16 ++++++ .../src/MainLayout.res | 4 ++ .../src/React.res | 10 ++++ .../src/Sidebar.res | 6 +++ 11 files changed, 168 insertions(+) create mode 100644 tests/build_tests/rsc_component_with_props_members/input.js create mode 100644 tests/build_tests/rsc_component_with_props_members/rescript.json create mode 100644 tests/build_tests/rsc_component_with_props_members/src/MainLayout.res create mode 100644 tests/build_tests/rsc_component_with_props_members/src/PlainAccess.res create mode 100644 tests/build_tests/rsc_component_with_props_members/src/React.res create mode 100644 tests/build_tests/rsc_component_with_props_members/src/Sidebar.res create mode 100644 tests/build_tests/rsc_component_with_props_members_no_namespace/input.js create mode 100644 tests/build_tests/rsc_component_with_props_members_no_namespace/rescript.json create mode 100644 tests/build_tests/rsc_component_with_props_members_no_namespace/src/MainLayout.res create mode 100644 tests/build_tests/rsc_component_with_props_members_no_namespace/src/React.res create mode 100644 tests/build_tests/rsc_component_with_props_members_no_namespace/src/Sidebar.res diff --git a/tests/build_tests/rsc_component_with_props_members/input.js b/tests/build_tests/rsc_component_with_props_members/input.js new file mode 100644 index 00000000000..c0eca840066 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members/input.js @@ -0,0 +1,53 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const output = await fs.readFile( + path.join(import.meta.dirname, "src", "MainLayout.res.js"), + "utf8", +); +const sidebarOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "Sidebar.res.js"), + "utf8", +); +const plainAccessOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "PlainAccess.res.js"), + "utf8", +); + +assert.match( + output, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider,/, +); +assert.match( + output, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset,/, +); +assert.doesNotMatch(output, /\.Provider\.make,/); +assert.doesNotMatch(output, /\.Inset\.make,/); +assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.match(sidebarOutput, /Sidebar\$Inset\$jsx/); +assert.match( + sidebarOutput, + /export \{[\s\S]*Provider,[\s\S]*Inset,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset,[\s\S]*\}/s, +); +assert.match( + plainAccessOutput, + /let provider = Sidebar\$RscComponentWithPropsMembers\.Provider\.make;/, +); +assert.match( + plainAccessOutput, + /let inset = Sidebar\$RscComponentWithPropsMembers\.Inset\.make;/, +); +assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); +assert.doesNotMatch(plainAccessOutput, /Sidebar\$Inset/); + +await execClean(); diff --git a/tests/build_tests/rsc_component_with_props_members/rescript.json b/tests/build_tests/rsc_component_with_props_members/rescript.json new file mode 100644 index 00000000000..27274bf511a --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-component-with-props-members", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + }, + "namespace": true +} diff --git a/tests/build_tests/rsc_component_with_props_members/src/MainLayout.res b/tests/build_tests/rsc_component_with_props_members/src/MainLayout.res new file mode 100644 index 00000000000..51d4feac18d --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members/src/MainLayout.res @@ -0,0 +1,6 @@ +@react.component +let make = (~children) => { + + {children} + +} diff --git a/tests/build_tests/rsc_component_with_props_members/src/PlainAccess.res b/tests/build_tests/rsc_component_with_props_members/src/PlainAccess.res new file mode 100644 index 00000000000..7941e529d79 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members/src/PlainAccess.res @@ -0,0 +1,3 @@ +let provider = Sidebar.Provider.make + +let inset = Sidebar.Inset.make diff --git a/tests/build_tests/rsc_component_with_props_members/src/React.res b/tests/build_tests/rsc_component_with_props_members/src/React.res new file mode 100644 index 00000000000..733b8ee02f0 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members/src/React.res @@ -0,0 +1,10 @@ +type element + +@val external null: element = "null" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_component_with_props_members/src/Sidebar.res b/tests/build_tests/rsc_component_with_props_members/src/Sidebar.res new file mode 100644 index 00000000000..42553c69e48 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members/src/Sidebar.res @@ -0,0 +1,13 @@ +module Provider = { + type props = {children: React.element} + + @react.componentWithProps + let make = props => props.children +} + +module Inset = { + type props = {children: React.element} + + @react.componentWithProps + let make = props => props.children +} diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js new file mode 100644 index 00000000000..6fcbf397308 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js @@ -0,0 +1,31 @@ +// @ts-check + +import * as assert from "node:assert"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { setup } from "#dev/process"; + +const { execBuild, execClean } = setup(import.meta.dirname); + +await execClean(); +await execBuild(); + +const output = await fs.readFile( + path.join(import.meta.dirname, "src", "MainLayout.res.js"), + "utf8", +); +const sidebarOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "Sidebar.res.js"), + "utf8", +); + +assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); +assert.doesNotMatch(output, /Sidebar\.Provider\.make/); +assert.doesNotMatch(output, /Sidebar\.Sidebar\$Sidebar\$Provider/); +assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.match( + sidebarOutput, + /export \{[\s\S]*Provider,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Provider\$jsx[\s\S]*\}/s, +); + +await execClean(); diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/rescript.json b/tests/build_tests/rsc_component_with_props_members_no_namespace/rescript.json new file mode 100644 index 00000000000..a456f72c057 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/rescript.json @@ -0,0 +1,16 @@ +{ + "name": "rsc-component-with-props-members-no-namespace", + "jsx": { + "version": 4 + }, + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "esmodule", + "in-source": true, + "suffix": ".res.js" + }, + "namespace": false +} diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/src/MainLayout.res b/tests/build_tests/rsc_component_with_props_members_no_namespace/src/MainLayout.res new file mode 100644 index 00000000000..e2c753eeb17 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/src/MainLayout.res @@ -0,0 +1,4 @@ +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/src/React.res b/tests/build_tests/rsc_component_with_props_members_no_namespace/src/React.res new file mode 100644 index 00000000000..733b8ee02f0 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/src/React.res @@ -0,0 +1,10 @@ +type element + +@val external null: element = "null" + +type componentLike<'props, 'return> = 'props => 'return + +type component<'props> = componentLike<'props, element> + +@module("react/jsx-runtime") +external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/src/Sidebar.res b/tests/build_tests/rsc_component_with_props_members_no_namespace/src/Sidebar.res new file mode 100644 index 00000000000..b667cc58fe6 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/src/Sidebar.res @@ -0,0 +1,6 @@ +module Provider = { + type props = {children: React.element} + + @react.componentWithProps + let make = props => props.children +} From 12875425384e642ddd586fb64f3d5dc2914598c6 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 9 Apr 2026 09:58:24 +0200 Subject: [PATCH 24/56] Make this a breaking change and only export the modules alone --- .../core/js_pass_nested_component_exports.ml | 146 ++++++++++ .../core/js_pass_nested_component_exports.mli | 28 ++ compiler/core/lam_compile.ml | 263 ++++++++++-------- compiler/core/lam_compile_main.ml | 3 + .../react_ppx/src/gpr_3987_test.res.js | 16 +- .../react_ppx/src/issue_7917_test.res.js | 8 +- .../rsc_component_with_props_members/input.js | 24 +- .../input.js | 11 +- .../rsc_component_with_props_nested/input.js | 9 +- .../rsc_dynamic_import_nested_jsx/input.js | 8 +- .../rsc_mixed_runtime_import/input.js | 2 +- .../rsc_nested_jsx_alias_chain/input.js | 7 +- .../build_tests/rsc_nested_jsx_deep/input.js | 15 +- .../rsc_nested_jsx_members/input.js | 28 +- .../input.js | 9 +- .../rsc_suffix_runtime_import/input.js | 2 +- 16 files changed, 391 insertions(+), 188 deletions(-) create mode 100644 compiler/core/js_pass_nested_component_exports.ml create mode 100644 compiler/core/js_pass_nested_component_exports.mli diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml new file mode 100644 index 00000000000..1778c5f2173 --- /dev/null +++ b/compiler/core/js_pass_nested_component_exports.ml @@ -0,0 +1,146 @@ +(* Copyright (C) 2026 - Authors of ReScript + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition to the permissions granted to you by the LGPL, you may combine + * or link a "work that uses the Library" with a publicly distributed version + * of this file to produce a combined library or application, then distribute + * that combined work under the terms of your choosing, with no requirement + * to comply with the obligations normally placed on you by section 4 of the + * LGPL version 3 (or the corresponding section of a later version of the LGPL + * should you choose to use a later version). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) + +module E = Js_exp_make + +module StringSet = Set.Make (String) + +type candidate = { + module_ident: Ident.t; + component_ident: Ident.t; + hidden_export_name: string; +} + +let marker_name hidden_export_name = hidden_export_name ^ "$jsx" + +let hidden_component_suffix (module_ident : Ident.t) = + "$" ^ Ident.name module_ident + +let is_hidden_component_name_for module_ident ident = + Ext_string.ends_with (Ident.name ident) (hidden_component_suffix module_ident) + +let find_hidden_alias_by_value block module_ident value_ident = + List.find_map (fun (st : J.statement) -> + match st.statement_desc with + | Variable + { + ident; + value = Some {expression_desc = Var (Id target); _}; + property = _; + ident_info = _; + } + when Ident.same target value_ident + && is_hidden_component_name_for module_ident ident -> + Some ident + | _ -> None) + block + +let has_export_name exports name = + List.exists (fun (ident : Ident.t) -> String.equal (Ident.name ident) name) exports + +let candidate_of_statement block exports (st : J.statement) = + match st.statement_desc with + | Variable + { + ident = module_ident; + value = + Some + { + expression_desc = + Caml_block + ([{expression_desc = Var (Id value_ident); _}], Immutable, _, Blk_module ["make"]); + _; + }; + property = _; + ident_info = _; + } -> ( + let hidden_ident = + if is_hidden_component_name_for module_ident value_ident then + Some value_ident + else find_hidden_alias_by_value block module_ident value_ident + in + match hidden_ident with + | Some hidden_ident + when has_export_name exports hidden_ident.name -> + Some + { + module_ident; + component_ident = value_ident; + hidden_export_name = hidden_ident.name; + } + | Some _ | None -> None) + | _ -> None + +let collect_candidates block exports = + Ext_list.filter_map block (candidate_of_statement block exports) + +let hidden_export_names_to_remove candidates = + Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> + acc |> StringSet.add candidate.hidden_export_name + |> StringSet.add (marker_name candidate.hidden_export_name)) + +let marker_names_to_remove_from_block candidates = + Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> + StringSet.add (marker_name candidate.hidden_export_name) acc) + +let candidate_by_module_ident candidates module_ident = + List.find_map (fun candidate -> + if Ident.same candidate.module_ident module_ident then Some candidate + else None) + candidates + +let rewrite_block block candidates removed_marker_names = + List.concat_map (fun (st : J.statement) -> + match st.statement_desc with + | Variable {ident; value; property; ident_info} -> ( + match candidate_by_module_ident candidates ident with + | Some candidate -> + let module_value = E.var candidate.component_ident in + [ + { + st with + statement_desc = + Variable {ident; value = Some module_value; property; ident_info}; + }; + ] + | None -> + if StringSet.mem (Ident.name ident) removed_marker_names then [] + else [st]) + | _ -> [st]) + block + +let program (js : J.program) : J.program = + let candidates = collect_candidates js.block js.exports in + match candidates with + | [] -> js + | _ -> + let removed_export_names = hidden_export_names_to_remove candidates in + let removed_marker_names = marker_names_to_remove_from_block candidates in + let exports = + Ext_list.filter js.exports (fun (ident : Ident.t) -> + not (StringSet.mem (Ident.name ident) removed_export_names)) + in + let export_set = Set_ident.of_list exports in + let block = rewrite_block js.block candidates removed_marker_names in + {J.block; exports; export_set} diff --git a/compiler/core/js_pass_nested_component_exports.mli b/compiler/core/js_pass_nested_component_exports.mli new file mode 100644 index 00000000000..624543fc9a8 --- /dev/null +++ b/compiler/core/js_pass_nested_component_exports.mli @@ -0,0 +1,28 @@ +(* Copyright (C) 2026 - Authors of ReScript + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition to the permissions granted to you by the LGPL, you may combine + * or link a "work that uses the Library" with a publicly distributed version + * of this file to produce a combined library or application, then distribute + * that combined work under the terms of your choosing, with no requirement + * to comply with the obligations normally placed on you by section 4 of the + * LGPL version 3 (or the corresponding section of a later version of the LGPL + * should you choose to use a later version). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) + +(* Rewrite nested React component module exports from `{make: fn}` wrappers to + direct component exports, while keeping `.make` as a self-reference for + compatibility with existing generated call sites. *) +val program : J.program -> J.program diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index d72440c85d1..014665b1573 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -232,6 +232,7 @@ type initialization = J.block *) let compile output_prefix = + let local_module_aliases : Lam.t Map_ident.t ref = ref Map_ident.empty in let root_module_name (id : Ident.t) = match Ext_namespace.try_split_module_name id.name with | Some (_namespace, module_name) -> module_name @@ -252,14 +253,83 @@ let compile output_prefix = } -> extract_nested_external_component_segments (name :: segments) (arg, make_dynamic_import) - | Lvar id -> - make_dynamic_import := Some false; - Some (id, false, List.rev segments) + | Lprim {primitive = Pawait; args = [arg]; _} -> + extract_nested_external_component_segments segments + (arg, make_dynamic_import) + | Lvar id -> ( + match Map_ident.find_opt !local_module_aliases id with + | Some alias_lam -> + extract_nested_external_component_segments segments + (alias_lam, make_dynamic_import) + | None -> + make_dynamic_import := Some false; + Some (id, false, List.rev segments)) | Lglobal_module (id, dynamic_import) -> make_dynamic_import := Some dynamic_import; Some (id, dynamic_import, List.rev segments) | _ -> None in + let rec extract_static_nested_external_component_segments segments + (lam : Lam.t) : (Ident.t * bool * string list) option = + match lam with + | Lprim + { + primitive = Pfield (_, Fld_module {name; jsx_component = _}); + args = [arg]; + _; + } -> + extract_static_nested_external_component_segments (name :: segments) arg + | Lprim {primitive = Pawait; args = [arg]; _} -> + extract_static_nested_external_component_segments segments arg + | Lprim {primitive = Pimport; args = [arg]; _} -> + extract_static_nested_external_component_segments segments arg + | Lvar id -> ( + match Map_ident.find_opt !local_module_aliases id with + | Some alias_lam -> + extract_static_nested_external_component_segments segments alias_lam + | None -> None) + | Lglobal_module (id, dynamic_import) -> + Some (id, dynamic_import, List.rev segments) + | _ -> None + in + let extract_nested_external_component_path (lam : Lam.t) : + (Ident.t * bool * string) option = + let dynamic_import = ref None in + match extract_nested_external_component_segments [] (lam, dynamic_import) with + | Some (id, dynamic_import, segments) -> ( + let denamespace_segment segment = + let root_name = root_module_name id in + let namespaced_prefix = root_name ^ "$" in + if Ext_string.starts_with segment namespaced_prefix then + match String.split_on_char '$' segment with + | root :: _namespace :: rest when rest <> [] -> + String.concat "$" (root :: rest) + | _ -> segment + else segment + in + let segments = + match segments with + | head :: rest + when head = id.name + || head = root_module_name id + || Ext_string.starts_with head (root_module_name id ^ "$") -> + rest + | _ -> segments + in + let segments = + match segments with + | head :: rest -> denamespace_segment head :: rest + | [] -> [] + in + match segments with + | [] -> None + | _ -> + Some + ( id, + dynamic_import, + String.concat "$" (root_module_name id :: segments) )) + | None -> None + in let extract_nested_external_component_field (lam : Lam.t) : (Ident.t * bool * string) option = match lam with @@ -269,131 +339,62 @@ let compile output_prefix = args = [arg]; _; } -> ( - let dynamic_import = ref None in - match - extract_nested_external_component_segments [] (arg, dynamic_import) - with - | Some (id, dynamic_import, segments) -> ( - let denamespace_segment segment = - let root_name = root_module_name id in - let namespaced_prefix = root_name ^ "$" in - if Ext_string.starts_with segment namespaced_prefix then - match String.split_on_char '$' segment with - | root :: _namespace :: rest when rest <> [] -> - String.concat "$" (root :: rest) - | _ -> segment - else segment - in - let segments = - match segments with - | head :: rest - when head = id.name - || head = root_module_name id - || Ext_string.starts_with head (root_module_name id ^ "$") -> - rest - | _ -> segments - in - let segments = - match segments with - | head :: rest -> denamespace_segment head :: rest - | [] -> [] - in - match segments with - | [] -> None - | _ -> - Some - ( id, - dynamic_import, - String.concat "$" (root_module_name id :: segments) )) - | None -> None) + extract_nested_external_component_path arg) | _ -> None in - let normalize_hidden_component_name (id : Ident.t) (hidden_name : string) = - let root_name = root_module_name id in - let id_parts = String.split_on_char '$' id.name in - let namespace_parts = - match id_parts with - | _root :: rest -> rest - | [] -> [] - in - let hidden_parts = String.split_on_char '$' hidden_name in - let hidden_parts_without_root = - match hidden_parts with - | first :: rest when String.equal first root_name -> rest - | _ -> hidden_parts - in - let rec drop_prefix prefix parts = - match (prefix, parts) with - | [], _ -> parts - | x :: xs, y :: ys when String.equal x y -> drop_prefix xs ys - | _ -> parts - in - let tail = drop_prefix namespace_parts hidden_parts_without_root in - match tail with - | [] -> hidden_name - | _ -> String.concat "$" (root_name :: tail) - in - let hidden_component_name_candidates (id : Ident.t) (hidden_name : string) = - let candidates = ref [] in - let push candidate = - if not (List.mem candidate !candidates) then - candidates := candidate :: !candidates - in - (match String.split_on_char '$' hidden_name with - | root :: _namespace :: rest when rest <> [] -> - push (String.concat "$" (root :: rest)) - | _ -> ()); - push (normalize_hidden_component_name id hidden_name); - push hidden_name; - List.rev !candidates - in - let exported_hidden_component_name ~(id : Ident.t) ~(dynamic_import : bool) - (hidden_name_candidates : string list) = - let rec loop = function + let extract_static_nested_external_component_path (lam : Lam.t) : + (Ident.t * bool * string) option = + match extract_static_nested_external_component_segments [] lam with + | Some (id, dynamic_import, segments) -> ( + let denamespace_segment segment = + let root_name = root_module_name id in + let namespaced_prefix = root_name ^ "$" in + if Ext_string.starts_with segment namespaced_prefix then + match String.split_on_char '$' segment with + | root :: _namespace :: rest when rest <> [] -> + String.concat "$" (root :: rest) + | _ -> segment + else segment + in + let segments = + match segments with + | head :: rest + when head = id.name + || head = root_module_name id + || Ext_string.starts_with head (root_module_name id ^ "$") -> + rest + | _ -> segments + in + let segments = + match segments with + | head :: rest -> denamespace_segment head :: rest + | [] -> [] + in + match segments with | [] -> None - | candidate :: rest -> ( - match - Lam_compile_env.query_external_id_info ~dynamic_import id - (candidate ^ "$jsx") - with - | _ -> Some candidate - | exception Not_found -> loop rest) - in - loop hidden_name_candidates + | _ -> + Some + ( id, + dynamic_import, + String.concat "$" (root_module_name id :: segments) )) + | None -> None + in + let rewrite_component_make_access (compiled_expr : J.expression) : + J.expression = + match compiled_expr.expression_desc with + | Static_index (inner, "make", _) -> inner + | _ -> compiled_expr in let rewrite_nested_jsx_component_expr (jsx_tag : Lam.t) (compiled_expr : J.expression) : J.expression = - let rec extract_root_expr (expr : J.expression) = - match expr.expression_desc with - | Var (Qualified (module_id, Some _)) -> - Some {expr with expression_desc = Var (Qualified (module_id, None))} - | Static_index (inner, _, _) -> extract_root_expr inner - | Var _ -> Some expr - | _ -> None - in - let hidden_component_access (root_expr : J.expression) hidden_name = - match root_expr.expression_desc with - | Var (Qualified (module_id, None)) -> - { - root_expr with - expression_desc = Var (Qualified (module_id, Some hidden_name)); - } - | _ -> E.dot root_expr hidden_name - in match extract_nested_external_component_field jsx_tag with - | Some (id, dynamic_import, hidden_name) -> ( - let hidden_name_candidates = - hidden_component_name_candidates id hidden_name - in - match extract_root_expr compiled_expr with - | Some root_expr -> ( - match - exported_hidden_component_name ~id ~dynamic_import - hidden_name_candidates - with - | Some hidden_name -> hidden_component_access root_expr hidden_name - | None -> compiled_expr) - | None -> compiled_expr) + | Some _ -> rewrite_component_make_access compiled_expr + | None -> compiled_expr + in + let rewrite_nested_component_make_expr (lam : Lam.t) + (compiled_expr : J.expression) : J.expression = + match extract_static_nested_external_component_path lam with + | Some _ -> rewrite_component_make_access compiled_expr | None -> compiled_expr in let rec compile_external_field (* Like [List.empty]*) @@ -1793,6 +1794,17 @@ let compile output_prefix = in Js_output.output_of_block_and_expression lambda_cxt.continuation args_code exp + | {primitive = Pfield (_, (Fld_module {name = "make"; jsx_component = _} as fld_info)); + args = [arg]; + _} -> + let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in + (match compile_lambda new_cxt arg with + | {block; value = Some compiled_arg} -> + let compiled_expr = Js_of_lam_block.field fld_info compiled_arg 0l in + let compiled_expr = rewrite_nested_component_make_expr arg compiled_expr in + Js_output.output_of_block_and_expression lambda_cxt.continuation block + compiled_expr + | {value = None} -> assert false) | { primitive = Pfield (_, fld_info); args = [Lglobal_module (id, dynamic_import)]; @@ -2019,7 +2031,14 @@ let compile output_prefix = {lambda_cxt with continuation = Declare (let_kind, id)} arg in - Js_output.append_output args_code (compile_lambda lambda_cxt body) + let previous_aliases = !local_module_aliases in + local_module_aliases := Map_ident.add previous_aliases id arg; + let body_output = + Fun.protect + ~finally:(fun () -> local_module_aliases := previous_aliases) + (fun () -> compile_lambda lambda_cxt body) + in + Js_output.append_output args_code body_output | Lletrec (id_args, body) -> (* There is a bug in our current design, it requires compile args first (register that some objects are jsidentifiers) diff --git a/compiler/core/lam_compile_main.ml b/compiler/core/lam_compile_main.ml index 5871d643ebd..1eb49c0d9b3 100644 --- a/compiler/core/lam_compile_main.ml +++ b/compiler/core/lam_compile_main.ml @@ -254,6 +254,9 @@ js |> (fun js -> ignore @@ Js_pass_scope.program js ; js ) |> Js_shake.shake_program |> _j "shake" +|> (fun js -> + if !Js_config.cmj_only then js + else Js_pass_nested_component_exports.program js) |> ( fun (program: J.program) -> let external_module_ids : Lam_module_ident.t list = if !Js_config.all_module_aliases then [] diff --git a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js index b2b8ed47b8a..2783cb339da 100644 --- a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js @@ -33,9 +33,7 @@ function Gpr_3987_test$Gpr3987ReproOk2(props) { return null; } -let Gpr3987ReproOk2 = { - make: Gpr_3987_test$Gpr3987ReproOk2 -}; +let Gpr3987ReproOk2 = Gpr_3987_test$Gpr3987ReproOk2; JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproOk2, { value: "test", @@ -46,27 +44,17 @@ function Gpr_3987_test$Gpr3987ReproError(props) { return null; } -let Gpr3987ReproError = { - make: Gpr_3987_test$Gpr3987ReproError -}; +let Gpr3987ReproError = Gpr_3987_test$Gpr3987ReproError; JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError, { value: "test", onChange: (param, param$1) => {} }); -let Gpr_3987_test$Gpr3987ReproOk2$jsx = true; - -let Gpr_3987_test$Gpr3987ReproError$jsx = true; - export { makeContainer, Gpr3987ReproOk, Gpr3987ReproOk2, Gpr3987ReproError, - Gpr_3987_test$Gpr3987ReproOk2, - Gpr_3987_test$Gpr3987ReproOk2$jsx, - Gpr_3987_test$Gpr3987ReproError, - Gpr_3987_test$Gpr3987ReproError$jsx, } /* Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/issue_7917_test.res.js b/tests/build_tests/react_ppx/src/issue_7917_test.res.js index 14a6b09fba1..d11b31d3d03 100644 --- a/tests/build_tests/react_ppx/src/issue_7917_test.res.js +++ b/tests/build_tests/react_ppx/src/issue_7917_test.res.js @@ -6,9 +6,7 @@ function Issue_7917_test$M(props) { return null; } -let M = { - make: Issue_7917_test$M -}; +let M = Issue_7917_test$M; function Issue_7917_test(props) { let __component = props.component; @@ -18,12 +16,8 @@ function Issue_7917_test(props) { let make = Issue_7917_test; -let Issue_7917_test$M$jsx = true; - export { M, make, - Issue_7917_test$M, - Issue_7917_test$M$jsx, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/build_tests/rsc_component_with_props_members/input.js b/tests/build_tests/rsc_component_with_props_members/input.js index c0eca840066..d1bda8dc32d 100644 --- a/tests/build_tests/rsc_component_with_props_members/input.js +++ b/tests/build_tests/rsc_component_with_props_members/input.js @@ -25,28 +25,36 @@ const plainAccessOutput = await fs.readFile( assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Provider,/, ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Inset,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); assert.doesNotMatch(output, /\.Inset\.make,/); -assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); -assert.match(sidebarOutput, /Sidebar\$Inset\$jsx/); +assert.match(sidebarOutput, /let Provider = Sidebar\$Provider;/); +assert.match(sidebarOutput, /let Inset = Sidebar\$Inset;/); assert.match( sidebarOutput, - /export \{[\s\S]*Provider,[\s\S]*Inset,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset,[\s\S]*\}/s, -); + /export \{[\s\S]*Provider,[\s\S]*Inset[\s\S]*\}/s, +); +assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); +assert.doesNotMatch(sidebarOutput, /Inset\.make = Inset;/); +assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.doesNotMatch(sidebarOutput, /Sidebar\$Inset\$jsx/); +assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); +assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Inset[\s\S]*\}/s); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscComponentWithPropsMembers\.Provider\.make;/, + /let provider = Sidebar\$RscComponentWithPropsMembers\.Provider;/, ); assert.match( plainAccessOutput, - /let inset = Sidebar\$RscComponentWithPropsMembers\.Inset\.make;/, + /let inset = Sidebar\$RscComponentWithPropsMembers\.Inset;/, ); +assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); +assert.doesNotMatch(plainAccessOutput, /\.Inset\.make/); assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); assert.doesNotMatch(plainAccessOutput, /Sidebar\$Inset/); diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js index 6fcbf397308..608f934bc9d 100644 --- a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js @@ -19,13 +19,16 @@ const sidebarOutput = await fs.readFile( "utf8", ); -assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); +assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); assert.doesNotMatch(output, /Sidebar\.Provider\.make/); -assert.doesNotMatch(output, /Sidebar\.Sidebar\$Sidebar\$Provider/); -assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.doesNotMatch(output, /Sidebar\.Sidebar\$Provider/); +assert.match(sidebarOutput, /let Provider = Sidebar\$Provider;/); assert.match( sidebarOutput, - /export \{[\s\S]*Provider,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Provider\$jsx[\s\S]*\}/s, + /export \{[\s\S]*Provider[\s\S]*\}/s, ); +assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); +assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); await execClean(); diff --git a/tests/build_tests/rsc_component_with_props_nested/input.js b/tests/build_tests/rsc_component_with_props_nested/input.js index ac0939c3778..674ea323a04 100644 --- a/tests/build_tests/rsc_component_with_props_nested/input.js +++ b/tests/build_tests/rsc_component_with_props_nested/input.js @@ -21,13 +21,16 @@ const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsNested\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsNested\.Provider,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); -assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.match(sidebarOutput, /let Provider = Sidebar\$Provider;/); assert.match( sidebarOutput, - /export \{[\s\S]*Provider,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Provider\$jsx[\s\S]*\}/s, + /export \{[\s\S]*Provider[\s\S]*\}/s, ); +assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); +assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); await execClean(); diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js index 13ae1728e8f..ac09d5965dc 100644 --- a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js @@ -20,11 +20,9 @@ assert.match( assert.match(output, /let dynamicProvider = DynamicSidebar\.Provider\.make;/); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider,/, -); -assert.doesNotMatch( - output, - /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/, + /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Provider,/, ); +assert.doesNotMatch(output, /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/); +assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider,/); await execClean(); diff --git a/tests/build_tests/rsc_mixed_runtime_import/input.js b/tests/build_tests/rsc_mixed_runtime_import/input.js index 1e4f73b8bd2..bbad952cf16 100644 --- a/tests/build_tests/rsc_mixed_runtime_import/input.js +++ b/tests/build_tests/rsc_mixed_runtime_import/input.js @@ -23,7 +23,7 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscMixedRuntimeImport\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscMixedRuntimeImport\.Provider,/, ); assert.doesNotMatch(output, /@rescript\/runtime\/lib\/es6\/Stdlib_Option\.mjs/); assert.equal( diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js index 5c2c03b2048..e52a94d89cb 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -21,17 +21,18 @@ const plainAccessOutput = await fs.readFile( assert.match( layoutOutput, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxAliasChain\.Provider,/, ); assert.doesNotMatch(layoutOutput, /\.Provider\.make,/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxAliasChain\.Provider\.make;/, + /let provider = Sidebar\$RscNestedJsxAliasChain\.Provider;/, ); assert.match( plainAccessOutput, - /let callProvider = Sidebar\$RscNestedJsxAliasChain\.Provider\.make\(\{/, + /let callProvider = Sidebar\$RscNestedJsxAliasChain\.Provider\(\{/, ); +assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_deep/input.js b/tests/build_tests/rsc_nested_jsx_deep/input.js index 9e9ad8fa159..128a703ac1b 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/input.js +++ b/tests/build_tests/rsc_nested_jsx_deep/input.js @@ -21,22 +21,23 @@ const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxDeep\.Sidebar\$Group,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxDeep\.Group,/, ); assert.doesNotMatch(output, /\.Group\.make,/); -assert.match(sidebarOutput, /Sidebar\$Group\$jsx/); +assert.match(sidebarOutput, /let Group = Sidebar\$Group;/); assert.match( sidebarOutput, - /export \{[\s\S]*Group,[\s\S]*Sidebar\$Group,[\s\S]*Sidebar\$Group\$jsx[\s\S]*\}/s, + /export \{[\s\S]*Group[\s\S]*\}/s, ); +assert.doesNotMatch(sidebarOutput, /Group\.make = Group;/); +assert.doesNotMatch(sidebarOutput, /Sidebar\$Group\$jsx/); +assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Group[\s\S]*\}/s); const brandIcons = await import("./src/BrandIcons.res.js"); assert.deepStrictEqual( new Set(Object.keys(brandIcons)), new Set([ "ReScript", - "BrandIcons$ReScript", - "BrandIcons$ReScript$jsx", "getIconForLanguageExtension", ]), ); @@ -46,11 +47,7 @@ assert.deepStrictEqual( new Set(Object.keys(multipleNested)), new Set([ "Group", - "MultipleNested$Group", - "MultipleNested$Group$jsx", "Other", - "MultipleNested$Other", - "MultipleNested$Other$jsx", ]), ); diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 2814bdf5a4e..d823df0d20e 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -46,15 +46,15 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Provider,/, ); assert.match( externalOutput, - /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.SidebarExternal\$Provider,/, + /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.Provider,/, ); assert.doesNotMatch( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$RscNestedJsxMembers\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider,/, ); assert.doesNotMatch( externalOutput, @@ -63,22 +63,32 @@ assert.doesNotMatch( assert.doesNotMatch(output, /\.Provider\.make,/); assert.match( sidebarOutput, - /export \{[\s\S]*Provider,[\s\S]*Inset,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset,[\s\S]*\}/s, + /let Provider = Sidebar\$Provider;/, ); -assert.match(sidebarOutput, /Sidebar\$Provider\$jsx/); -assert.match(sidebarOutput, /Sidebar\$Inset\$jsx/); +assert.match(sidebarOutput, /let Inset = Sidebar\$Inset;/); +assert.match(sidebarOutput, /export \{[\s\S]*Provider,[\s\S]*Inset[\s\S]*\}/s); +assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); +assert.doesNotMatch(sidebarOutput, /Inset\.make = Inset;/); +assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.doesNotMatch(sidebarOutput, /Sidebar\$Inset\$jsx/); +assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); assert.match( externalSidebarOutput, - /export \{[\s\S]*Provider,[\s\S]*SidebarExternal\$Provider,[\s\S]*SidebarExternal\$Provider\$jsx[\s\S]*\}/s, + /let Provider = make;/, ); +assert.match(externalSidebarOutput, /export \{[\s\S]*Provider[\s\S]*\}/s); +assert.doesNotMatch(externalSidebarOutput, /Provider\.make = Provider;/); +assert.doesNotMatch(externalSidebarOutput, /SidebarExternal\$Provider\$jsx/); +assert.doesNotMatch(externalSidebarOutput, /SidebarExternal\$Provider,/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxMembers\.Provider\.make;/, + /let provider = Sidebar\$RscNestedJsxMembers\.Provider;/, ); assert.match( plainAccessOutput, - /let callProvider = Sidebar\$RscNestedJsxMembers\.Provider\.make\(\{/, + /let callProvider = Sidebar\$RscNestedJsxMembers\.Provider\(\{/, ); +assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js index 0a672353d49..d2e766f276c 100644 --- a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js @@ -19,11 +19,16 @@ const sidebarOutputPath = path.join( ); const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); -assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); +assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); assert.doesNotMatch(output, /Sidebar\.Provider\.make/); +assert.doesNotMatch(output, /Sidebar\.Sidebar\$Provider/); assert.match( sidebarOutput, - /export \{[\s\S]*Provider,[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Provider\$jsx[\s\S]*\}/s, + /let Provider = Sidebar\$Provider;/, ); +assert.match(sidebarOutput, /export \{[\s\S]*Provider[\s\S]*\}/s); +assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); +assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); +assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); await execClean(); diff --git a/tests/build_tests/rsc_suffix_runtime_import/input.js b/tests/build_tests/rsc_suffix_runtime_import/input.js index ee1dd122963..2121de7f3ae 100644 --- a/tests/build_tests/rsc_suffix_runtime_import/input.js +++ b/tests/build_tests/rsc_suffix_runtime_import/input.js @@ -23,7 +23,7 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscSuffixRuntimeImport\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscSuffixRuntimeImport\.Provider,/, ); assert.equal(output.match(/Stdlib_Option\.(js|mjs)/g)?.length ?? 0, 1); assert.doesNotMatch( From 97d37f119e7c20c51a1087c354d08872774da653 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 9 Apr 2026 10:52:37 +0200 Subject: [PATCH 25/56] Only export namespaced component --- .../core/js_pass_nested_component_exports.ml | 114 ++++++++++++++---- compiler/core/lam_compile.ml | 99 +++++++++++++-- .../react_ppx/src/gpr_3987_test.res.js | 12 +- .../react_ppx/src/issue_7917_test.res.js | 6 +- .../rsc_component_with_props_members/input.js | 20 ++- .../input.js | 9 +- .../rsc_component_with_props_nested/input.js | 7 +- .../rsc_dynamic_import_nested_jsx/input.js | 8 +- .../rsc_mixed_runtime_import/input.js | 2 +- .../rsc_nested_jsx_alias_chain/input.js | 8 +- .../build_tests/rsc_nested_jsx_deep/input.js | 13 +- .../rsc_nested_jsx_members/input.js | 33 ++--- .../input.js | 12 +- .../rsc_suffix_runtime_import/input.js | 2 +- 14 files changed, 247 insertions(+), 98 deletions(-) diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml index 1778c5f2173..9b232b164ed 100644 --- a/compiler/core/js_pass_nested_component_exports.ml +++ b/compiler/core/js_pass_nested_component_exports.ml @@ -28,10 +28,34 @@ module StringSet = Set.Make (String) type candidate = { module_ident: Ident.t; - component_ident: Ident.t; hidden_export_name: string; } +let dynamic_import_module_root (expr : J.expression) = + match expr.expression_desc with + | Await + { + expression_desc = + Call + ({expression_desc = Var (Id import_ident); _}, [arg], _); + _; + } + when String.equal import_ident.name "import" -> ( + match arg.expression_desc with + | Str {txt; _} -> + let basename = Filename.basename txt in + let suffixes = [".res.mjs"; ".res.js"; ".mjs"; ".js"] in + let rec strip_suffix = function + | [] -> basename + | suffix :: rest -> + if Filename.check_suffix basename suffix then + Filename.chop_suffix basename suffix + else strip_suffix rest + in + Some (strip_suffix suffixes) + | _ -> None) + | _ -> None + let marker_name hidden_export_name = hidden_export_name ^ "$jsx" let hidden_component_suffix (module_ident : Ident.t) = @@ -86,7 +110,6 @@ let candidate_of_statement block exports (st : J.statement) = Some { module_ident; - component_ident = value_ident; hidden_export_name = hidden_ident.name; } | Some _ | None -> None) @@ -97,7 +120,7 @@ let collect_candidates block exports = let hidden_export_names_to_remove candidates = Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> - acc |> StringSet.add candidate.hidden_export_name + acc |> StringSet.add (Ident.name candidate.module_ident) |> StringSet.add (marker_name candidate.hidden_export_name)) let marker_names_to_remove_from_block candidates = @@ -115,32 +138,75 @@ let rewrite_block block candidates removed_marker_names = match st.statement_desc with | Variable {ident; value; property; ident_info} -> ( match candidate_by_module_ident candidates ident with - | Some candidate -> - let module_value = E.var candidate.component_ident in - [ - { - st with - statement_desc = - Variable {ident; value = Some module_value; property; ident_info}; - }; - ] + | Some _ -> [st] | None -> if StringSet.mem (Ident.name ident) removed_marker_names then [] else [st]) | _ -> [st]) block +let dynamic_import_aliases block = + List.fold_left + (fun aliases (st : J.statement) -> + match st.statement_desc with + | Variable {ident; value = Some expr; _} -> ( + match dynamic_import_module_root expr with + | Some module_root -> Map_ident.add aliases ident module_root + | None -> aliases) + | _ -> aliases) + Map_ident.empty block + +let rewrite_dynamic_import_component_access aliases (expr : J.expression) = + let rec collect_segments segments (expr : J.expression) = + match expr.expression_desc with + | Static_index (inner, field, _) -> collect_segments (field :: segments) inner + | Var (Id id) -> ( + match Map_ident.find_opt aliases id with + | Some module_root when segments <> [] -> Some (id, module_root, segments) + | Some _ | None -> None) + | _ -> None + in + match expr.expression_desc with + | Static_index (inner, "make", _) -> ( + match collect_segments [] inner with + | Some (id, module_root, segments) -> + let segments = List.rev segments in + let hidden_name = + match segments with + | first :: _ when Ext_string.starts_with first (module_root ^ "$") -> + String.concat "$" segments + | _ -> String.concat "$" (module_root :: segments) + in + {expr with expression_desc = Static_index (E.var id, hidden_name, None)} + | None -> expr) + | _ -> expr + +let rewrite_dynamic_import_block block = + let aliases = dynamic_import_aliases block in + if Map_ident.is_empty aliases then block + else + let mapper = + { + Js_record_map.super with + expression = + (fun self expr -> + let expr = Js_record_map.super.expression self expr in + rewrite_dynamic_import_component_access aliases expr); + } + in + mapper.block mapper block + let program (js : J.program) : J.program = let candidates = collect_candidates js.block js.exports in - match candidates with - | [] -> js - | _ -> - let removed_export_names = hidden_export_names_to_remove candidates in - let removed_marker_names = marker_names_to_remove_from_block candidates in - let exports = - Ext_list.filter js.exports (fun (ident : Ident.t) -> - not (StringSet.mem (Ident.name ident) removed_export_names)) - in - let export_set = Set_ident.of_list exports in - let block = rewrite_block js.block candidates removed_marker_names in - {J.block; exports; export_set} + let removed_export_names = hidden_export_names_to_remove candidates in + let removed_marker_names = marker_names_to_remove_from_block candidates in + let exports = + Ext_list.filter js.exports (fun (ident : Ident.t) -> + not (StringSet.mem (Ident.name ident) removed_export_names)) + in + let export_set = Set_ident.of_list exports in + let block = + rewrite_dynamic_import_block + (rewrite_block js.block candidates removed_marker_names) + in + {J.block; exports; export_set} diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 014665b1573..f09a896138f 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -379,22 +379,107 @@ let compile output_prefix = String.concat "$" (root_module_name id :: segments) )) | None -> None in - let rewrite_component_make_access (compiled_expr : J.expression) : - J.expression = - match compiled_expr.expression_desc with - | Static_index (inner, "make", _) -> inner - | _ -> compiled_expr + let normalize_hidden_component_name (id : Ident.t) (hidden_name : string) = + let root_name = root_module_name id in + let id_parts = String.split_on_char '$' id.name in + let namespace_parts = + match id_parts with + | _root :: rest -> rest + | [] -> [] + in + let hidden_parts = String.split_on_char '$' hidden_name in + let hidden_parts_without_root = + match hidden_parts with + | first :: rest when String.equal first root_name -> rest + | _ -> hidden_parts + in + let rec drop_prefix prefix parts = + match (prefix, parts) with + | [], _ -> parts + | x :: xs, y :: ys when String.equal x y -> drop_prefix xs ys + | _ -> parts + in + let tail = drop_prefix namespace_parts hidden_parts_without_root in + match tail with + | [] -> hidden_name + | _ -> String.concat "$" (root_name :: tail) + in + let hidden_component_name_candidates (id : Ident.t) (hidden_name : string) = + let candidates = ref [] in + let push candidate = + if not (List.mem candidate !candidates) then + candidates := candidate :: !candidates + in + (match String.split_on_char '$' hidden_name with + | root :: _namespace :: rest when rest <> [] -> + push (String.concat "$" (root :: rest)) + | _ -> ()); + push (normalize_hidden_component_name id hidden_name); + push hidden_name; + List.rev !candidates + in + let exported_hidden_component_name ~(id : Ident.t) ~(dynamic_import : bool) + (hidden_name_candidates : string list) = + let rec loop = function + | [] -> None + | candidate :: rest -> ( + match Lam_compile_env.query_external_id_info ~dynamic_import id candidate with + | _ -> Some candidate + | exception Not_found -> loop rest) + in + loop hidden_name_candidates + in + let rec extract_root_expr (expr : J.expression) = + match expr.expression_desc with + | Var (Qualified (module_id, Some _)) -> + Some {expr with expression_desc = Var (Qualified (module_id, None))} + | Static_index (inner, _, _) -> extract_root_expr inner + | Var _ -> Some expr + | _ -> None + in + let hidden_component_access (root_expr : J.expression) hidden_name = + match root_expr.expression_desc with + | Var (Qualified (module_id, None)) -> + { + root_expr with + expression_desc = Var (Qualified (module_id, Some hidden_name)); + } + | _ -> E.dot root_expr hidden_name in let rewrite_nested_jsx_component_expr (jsx_tag : Lam.t) (compiled_expr : J.expression) : J.expression = match extract_nested_external_component_field jsx_tag with - | Some _ -> rewrite_component_make_access compiled_expr + | Some (id, dynamic_import, hidden_name) -> ( + let hidden_name_candidates = + hidden_component_name_candidates id hidden_name + in + match extract_root_expr compiled_expr with + | Some root_expr -> ( + match + exported_hidden_component_name ~id ~dynamic_import + hidden_name_candidates + with + | Some hidden_name -> hidden_component_access root_expr hidden_name + | None -> compiled_expr) + | None -> compiled_expr) | None -> compiled_expr in let rewrite_nested_component_make_expr (lam : Lam.t) (compiled_expr : J.expression) : J.expression = match extract_static_nested_external_component_path lam with - | Some _ -> rewrite_component_make_access compiled_expr + | Some (id, dynamic_import, hidden_name) -> ( + let hidden_name_candidates = + hidden_component_name_candidates id hidden_name + in + match extract_root_expr compiled_expr with + | Some root_expr -> ( + match + exported_hidden_component_name ~id ~dynamic_import + hidden_name_candidates + with + | Some hidden_name -> hidden_component_access root_expr hidden_name + | None -> compiled_expr) + | None -> compiled_expr) | None -> compiled_expr in let rec compile_external_field (* Like [List.empty]*) diff --git a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js index 2783cb339da..1730f19420e 100644 --- a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js @@ -33,7 +33,9 @@ function Gpr_3987_test$Gpr3987ReproOk2(props) { return null; } -let Gpr3987ReproOk2 = Gpr_3987_test$Gpr3987ReproOk2; +let Gpr3987ReproOk2 = { + make: Gpr_3987_test$Gpr3987ReproOk2 +}; JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproOk2, { value: "test", @@ -44,7 +46,9 @@ function Gpr_3987_test$Gpr3987ReproError(props) { return null; } -let Gpr3987ReproError = Gpr_3987_test$Gpr3987ReproError; +let Gpr3987ReproError = { + make: Gpr_3987_test$Gpr3987ReproError +}; JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError, { value: "test", @@ -54,7 +58,7 @@ JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError, { export { makeContainer, Gpr3987ReproOk, - Gpr3987ReproOk2, - Gpr3987ReproError, + Gpr_3987_test$Gpr3987ReproOk2, + Gpr_3987_test$Gpr3987ReproError, } /* Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/issue_7917_test.res.js b/tests/build_tests/react_ppx/src/issue_7917_test.res.js index d11b31d3d03..169c1dee854 100644 --- a/tests/build_tests/react_ppx/src/issue_7917_test.res.js +++ b/tests/build_tests/react_ppx/src/issue_7917_test.res.js @@ -6,7 +6,9 @@ function Issue_7917_test$M(props) { return null; } -let M = Issue_7917_test$M; +let M = { + make: Issue_7917_test$M +}; function Issue_7917_test(props) { let __component = props.component; @@ -17,7 +19,7 @@ function Issue_7917_test(props) { let make = Issue_7917_test; export { - M, make, + Issue_7917_test$M, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/build_tests/rsc_component_with_props_members/input.js b/tests/build_tests/rsc_component_with_props_members/input.js index d1bda8dc32d..ee082569e72 100644 --- a/tests/build_tests/rsc_component_with_props_members/input.js +++ b/tests/build_tests/rsc_component_with_props_members/input.js @@ -25,37 +25,35 @@ const plainAccessOutput = await fs.readFile( assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider,/, ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Inset,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); assert.doesNotMatch(output, /\.Inset\.make,/); -assert.match(sidebarOutput, /let Provider = Sidebar\$Provider;/); -assert.match(sidebarOutput, /let Inset = Sidebar\$Inset;/); +assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); +assert.match(sidebarOutput, /let Inset = \{[\s\S]*make: Sidebar\$Inset[\s\S]*\};/s); assert.match( sidebarOutput, - /export \{[\s\S]*Provider,[\s\S]*Inset[\s\S]*\}/s, + /export \{[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset[\s\S]*\}/s, ); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Inset\.make = Inset;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Inset\$jsx/); -assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); -assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Inset[\s\S]*\}/s); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscComponentWithPropsMembers\.Provider;/, + /let provider = Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider;/, ); assert.match( plainAccessOutput, - /let inset = Sidebar\$RscComponentWithPropsMembers\.Inset;/, + /let inset = Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset;/, ); assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); assert.doesNotMatch(plainAccessOutput, /\.Inset\.make/); -assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); -assert.doesNotMatch(plainAccessOutput, /Sidebar\$Inset/); +assert.doesNotMatch(plainAccessOutput, /Sidebar\$RscComponentWithPropsMembers\.Provider/); +assert.doesNotMatch(plainAccessOutput, /Sidebar\$RscComponentWithPropsMembers\.Inset/); await execClean(); diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js index 608f934bc9d..816d6b29520 100644 --- a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js @@ -19,16 +19,15 @@ const sidebarOutput = await fs.readFile( "utf8", ); -assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); +assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); assert.doesNotMatch(output, /Sidebar\.Provider\.make/); -assert.doesNotMatch(output, /Sidebar\.Sidebar\$Provider/); -assert.match(sidebarOutput, /let Provider = Sidebar\$Provider;/); +assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); +assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); assert.match( sidebarOutput, - /export \{[\s\S]*Provider[\s\S]*\}/s, + /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s, ); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); -assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); await execClean(); diff --git a/tests/build_tests/rsc_component_with_props_nested/input.js b/tests/build_tests/rsc_component_with_props_nested/input.js index 674ea323a04..2f96733cbff 100644 --- a/tests/build_tests/rsc_component_with_props_nested/input.js +++ b/tests/build_tests/rsc_component_with_props_nested/input.js @@ -21,16 +21,15 @@ const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsNested\.Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsNested\.Sidebar\$Provider,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); -assert.match(sidebarOutput, /let Provider = Sidebar\$Provider;/); +assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); assert.match( sidebarOutput, - /export \{[\s\S]*Provider[\s\S]*\}/s, + /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s, ); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); -assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); await execClean(); diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js index ac09d5965dc..ea81458d900 100644 --- a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js @@ -17,12 +17,12 @@ assert.match( output, /let DynamicSidebar = await import\("\.\/Sidebar\.res\.mjs"\);/, ); -assert.match(output, /let dynamicProvider = DynamicSidebar\.Provider\.make;/); +assert.match(output, /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider,/, ); -assert.doesNotMatch(output, /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/); -assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider,/); +assert.doesNotMatch(output, /let dynamicProvider = DynamicSidebar\.Provider\.make;/); +assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Provider,/); await execClean(); diff --git a/tests/build_tests/rsc_mixed_runtime_import/input.js b/tests/build_tests/rsc_mixed_runtime_import/input.js index bbad952cf16..1e4f73b8bd2 100644 --- a/tests/build_tests/rsc_mixed_runtime_import/input.js +++ b/tests/build_tests/rsc_mixed_runtime_import/input.js @@ -23,7 +23,7 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscMixedRuntimeImport\.Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscMixedRuntimeImport\.Sidebar\$Provider,/, ); assert.doesNotMatch(output, /@rescript\/runtime\/lib\/es6\/Stdlib_Option\.mjs/); assert.equal( diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js index e52a94d89cb..eb7bde285dc 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -21,18 +21,18 @@ const plainAccessOutput = await fs.readFile( assert.match( layoutOutput, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxAliasChain\.Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider,/, ); assert.doesNotMatch(layoutOutput, /\.Provider\.make,/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxAliasChain\.Provider;/, + /let provider = Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider;/, ); assert.match( plainAccessOutput, - /let callProvider = Sidebar\$RscNestedJsxAliasChain\.Provider\(\{/, + /let callProvider = Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider\(\{/, ); assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); -assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); +assert.doesNotMatch(plainAccessOutput, /Sidebar\$RscNestedJsxAliasChain\.Provider/); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_deep/input.js b/tests/build_tests/rsc_nested_jsx_deep/input.js index 128a703ac1b..cc29b6a5df4 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/input.js +++ b/tests/build_tests/rsc_nested_jsx_deep/input.js @@ -21,23 +21,22 @@ const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxDeep\.Group,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxDeep\.Sidebar\$Group,/, ); assert.doesNotMatch(output, /\.Group\.make,/); -assert.match(sidebarOutput, /let Group = Sidebar\$Group;/); +assert.match(sidebarOutput, /let Group = \{[\s\S]*make: Sidebar\$Group[\s\S]*\};/s); assert.match( sidebarOutput, - /export \{[\s\S]*Group[\s\S]*\}/s, + /export \{[\s\S]*Sidebar\$Group[\s\S]*\}/s, ); assert.doesNotMatch(sidebarOutput, /Group\.make = Group;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Group\$jsx/); -assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Group[\s\S]*\}/s); const brandIcons = await import("./src/BrandIcons.res.js"); assert.deepStrictEqual( new Set(Object.keys(brandIcons)), new Set([ - "ReScript", + "BrandIcons$ReScript", "getIconForLanguageExtension", ]), ); @@ -46,8 +45,8 @@ const multipleNested = await import("./src/MultipleNested.res.js"); assert.deepStrictEqual( new Set(Object.keys(multipleNested)), new Set([ - "Group", - "Other", + "MultipleNested$Group", + "MultipleNested$Other", ]), ); diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index d823df0d20e..7b1ada9d688 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -46,49 +46,50 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider,/, ); assert.match( externalOutput, - /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.Provider,/, + /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.SidebarExternal\$Provider,/, ); assert.doesNotMatch( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Provider,/, ); assert.doesNotMatch( externalOutput, /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.Provider\.make,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); -assert.match( - sidebarOutput, - /let Provider = Sidebar\$Provider;/, -); -assert.match(sidebarOutput, /let Inset = Sidebar\$Inset;/); -assert.match(sidebarOutput, /export \{[\s\S]*Provider,[\s\S]*Inset[\s\S]*\}/s); +assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); +assert.match(sidebarOutput, /let Inset = \{[\s\S]*make: Sidebar\$Inset[\s\S]*\};/s); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Inset\.make = Inset;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Inset\$jsx/); -assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); +assert.match( + sidebarOutput, + /export \{[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset[\s\S]*\}/s, +); assert.match( externalSidebarOutput, - /let Provider = make;/, + /let Provider = \{[\s\S]*make: make[\s\S]*\};/s, ); -assert.match(externalSidebarOutput, /export \{[\s\S]*Provider[\s\S]*\}/s); assert.doesNotMatch(externalSidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(externalSidebarOutput, /SidebarExternal\$Provider\$jsx/); -assert.doesNotMatch(externalSidebarOutput, /SidebarExternal\$Provider,/); +assert.match( + externalSidebarOutput, + /export \{[\s\S]*SidebarExternal\$Provider[\s\S]*\}/s, +); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxMembers\.Provider;/, + /let provider = Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider;/, ); assert.match( plainAccessOutput, - /let callProvider = Sidebar\$RscNestedJsxMembers\.Provider\(\{/, + /let callProvider = Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider\(\{/, ); assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); -assert.doesNotMatch(plainAccessOutput, /Sidebar\$Provider/); +assert.doesNotMatch(plainAccessOutput, /Sidebar\$RscNestedJsxMembers\.Provider/); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js index d2e766f276c..a3f4e530bb4 100644 --- a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js @@ -19,16 +19,12 @@ const sidebarOutputPath = path.join( ); const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); -assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); +assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); assert.doesNotMatch(output, /Sidebar\.Provider\.make/); -assert.doesNotMatch(output, /Sidebar\.Sidebar\$Provider/); -assert.match( - sidebarOutput, - /let Provider = Sidebar\$Provider;/, -); -assert.match(sidebarOutput, /export \{[\s\S]*Provider[\s\S]*\}/s); +assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); +assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); +assert.match(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); -assert.doesNotMatch(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); await execClean(); diff --git a/tests/build_tests/rsc_suffix_runtime_import/input.js b/tests/build_tests/rsc_suffix_runtime_import/input.js index 2121de7f3ae..ee1dd122963 100644 --- a/tests/build_tests/rsc_suffix_runtime_import/input.js +++ b/tests/build_tests/rsc_suffix_runtime_import/input.js @@ -23,7 +23,7 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscSuffixRuntimeImport\.Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscSuffixRuntimeImport\.Sidebar\$Provider,/, ); assert.equal(output.match(/Stdlib_Option\.(js|mjs)/g)?.length ?? 0, 1); assert.doesNotMatch( From e2d06ee014b02fb328d974218ae16085ca146c51 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 9 Apr 2026 15:29:09 +0200 Subject: [PATCH 26/56] Format --- .../core/js_pass_nested_component_exports.ml | 81 ++++++++++--------- compiler/core/lam_compile.ml | 27 ++++--- .../rsc_component_with_props_members/input.js | 20 ++++- .../input.js | 4 +- .../rsc_component_with_props_nested/input.js | 4 +- .../rsc_dynamic_import_nested_jsx/input.js | 15 +++- .../rsc_nested_jsx_alias_chain/input.js | 5 +- .../build_tests/rsc_nested_jsx_deep/input.js | 14 +--- .../rsc_nested_jsx_members/input.js | 15 +++- .../input.js | 5 +- .../typescript-react-example/src/Hooks.res.js | 16 ---- .../typescript-react-example/src/JSXV4.res.js | 4 - tests/tests/src/alias_default_value_test.mjs | 32 -------- tests/tests/src/async_jsx.mjs | 8 -- tests/tests/src/exception_alias.mjs | 4 +- tests/tests/src/jsx_optional_props_test.mjs | 4 - tests/tests/src/jsx_preserve_test.mjs | 18 +---- tests/tests/src/jsxv4_newtype.mjs | 16 ---- tests/tests/src/module_missing_conversion.mjs | 4 +- tests/tests/src/test_pervasive.mjs | 4 +- tests/tests/src/test_pervasives3.mjs | 4 +- 21 files changed, 128 insertions(+), 176 deletions(-) diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml index 9b232b164ed..b5d5bb4618f 100644 --- a/compiler/core/js_pass_nested_component_exports.ml +++ b/compiler/core/js_pass_nested_component_exports.ml @@ -26,34 +26,30 @@ module E = Js_exp_make module StringSet = Set.Make (String) -type candidate = { - module_ident: Ident.t; - hidden_export_name: string; -} +type candidate = {module_ident: Ident.t; hidden_export_name: string} let dynamic_import_module_root (expr : J.expression) = match expr.expression_desc with | Await { expression_desc = - Call - ({expression_desc = Var (Id import_ident); _}, [arg], _); + Call ({expression_desc = Var (Id import_ident); _}, [arg], _); _; } when String.equal import_ident.name "import" -> ( - match arg.expression_desc with - | Str {txt; _} -> - let basename = Filename.basename txt in - let suffixes = [".res.mjs"; ".res.js"; ".mjs"; ".js"] in - let rec strip_suffix = function - | [] -> basename - | suffix :: rest -> - if Filename.check_suffix basename suffix then - Filename.chop_suffix basename suffix - else strip_suffix rest - in - Some (strip_suffix suffixes) - | _ -> None) + match arg.expression_desc with + | Str {txt; _} -> + let basename = Filename.basename txt in + let suffixes = [".res.mjs"; ".res.js"; ".mjs"; ".js"] in + let rec strip_suffix = function + | [] -> basename + | suffix :: rest -> + if Filename.check_suffix basename suffix then + Filename.chop_suffix basename suffix + else strip_suffix rest + in + Some (strip_suffix suffixes) + | _ -> None) | _ -> None let marker_name hidden_export_name = hidden_export_name ^ "$jsx" @@ -65,7 +61,8 @@ let is_hidden_component_name_for module_ident ident = Ext_string.ends_with (Ident.name ident) (hidden_component_suffix module_ident) let find_hidden_alias_by_value block module_ident value_ident = - List.find_map (fun (st : J.statement) -> + List.find_map + (fun (st : J.statement) -> match st.statement_desc with | Variable { @@ -81,7 +78,9 @@ let find_hidden_alias_by_value block module_ident value_ident = block let has_export_name exports name = - List.exists (fun (ident : Ident.t) -> String.equal (Ident.name ident) name) exports + List.exists + (fun (ident : Ident.t) -> String.equal (Ident.name ident) name) + exports let candidate_of_statement block exports (st : J.statement) = match st.statement_desc with @@ -93,26 +92,24 @@ let candidate_of_statement block exports (st : J.statement) = { expression_desc = Caml_block - ([{expression_desc = Var (Id value_ident); _}], Immutable, _, Blk_module ["make"]); + ( [{expression_desc = Var (Id value_ident); _}], + Immutable, + _, + Blk_module ["make"] ); _; }; property = _; ident_info = _; } -> ( - let hidden_ident = - if is_hidden_component_name_for module_ident value_ident then - Some value_ident - else find_hidden_alias_by_value block module_ident value_ident - in - match hidden_ident with - | Some hidden_ident - when has_export_name exports hidden_ident.name -> - Some - { - module_ident; - hidden_export_name = hidden_ident.name; - } - | Some _ | None -> None) + let hidden_ident = + if is_hidden_component_name_for module_ident value_ident then + Some value_ident + else find_hidden_alias_by_value block module_ident value_ident + in + match hidden_ident with + | Some hidden_ident when has_export_name exports hidden_ident.name -> + Some {module_ident; hidden_export_name = hidden_ident.name} + | Some _ | None -> None) | _ -> None let collect_candidates block exports = @@ -120,7 +117,8 @@ let collect_candidates block exports = let hidden_export_names_to_remove candidates = Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> - acc |> StringSet.add (Ident.name candidate.module_ident) + acc + |> StringSet.add (Ident.name candidate.module_ident) |> StringSet.add (marker_name candidate.hidden_export_name)) let marker_names_to_remove_from_block candidates = @@ -128,13 +126,15 @@ let marker_names_to_remove_from_block candidates = StringSet.add (marker_name candidate.hidden_export_name) acc) let candidate_by_module_ident candidates module_ident = - List.find_map (fun candidate -> + List.find_map + (fun candidate -> if Ident.same candidate.module_ident module_ident then Some candidate else None) candidates let rewrite_block block candidates removed_marker_names = - List.concat_map (fun (st : J.statement) -> + List.concat_map + (fun (st : J.statement) -> match st.statement_desc with | Variable {ident; value; property; ident_info} -> ( match candidate_by_module_ident candidates ident with @@ -159,7 +159,8 @@ let dynamic_import_aliases block = let rewrite_dynamic_import_component_access aliases (expr : J.expression) = let rec collect_segments segments (expr : J.expression) = match expr.expression_desc with - | Static_index (inner, field, _) -> collect_segments (field :: segments) inner + | Static_index (inner, field, _) -> + collect_segments (field :: segments) inner | Var (Id id) -> ( match Map_ident.find_opt aliases id with | Some module_root when segments <> [] -> Some (id, module_root, segments) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index f09a896138f..21c0d040296 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -295,7 +295,9 @@ let compile output_prefix = let extract_nested_external_component_path (lam : Lam.t) : (Ident.t * bool * string) option = let dynamic_import = ref None in - match extract_nested_external_component_segments [] (lam, dynamic_import) with + match + extract_nested_external_component_segments [] (lam, dynamic_import) + with | Some (id, dynamic_import, segments) -> ( let denamespace_segment segment = let root_name = root_module_name id in @@ -338,8 +340,8 @@ let compile output_prefix = primitive = Pfield (_, Fld_module {name = "make"; jsx_component = _}); args = [arg]; _; - } -> ( - extract_nested_external_component_path arg) + } -> + extract_nested_external_component_path arg | _ -> None in let extract_static_nested_external_component_path (lam : Lam.t) : @@ -423,7 +425,9 @@ let compile output_prefix = let rec loop = function | [] -> None | candidate :: rest -> ( - match Lam_compile_env.query_external_id_info ~dynamic_import id candidate with + match + Lam_compile_env.query_external_id_info ~dynamic_import id candidate + with | _ -> Some candidate | exception Not_found -> loop rest) in @@ -1879,14 +1883,19 @@ let compile output_prefix = in Js_output.output_of_block_and_expression lambda_cxt.continuation args_code exp - | {primitive = Pfield (_, (Fld_module {name = "make"; jsx_component = _} as fld_info)); - args = [arg]; - _} -> + | { + primitive = + Pfield (_, (Fld_module {name = "make"; jsx_component = _} as fld_info)); + args = [arg]; + _; + } -> ( let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in - (match compile_lambda new_cxt arg with + match compile_lambda new_cxt arg with | {block; value = Some compiled_arg} -> let compiled_expr = Js_of_lam_block.field fld_info compiled_arg 0l in - let compiled_expr = rewrite_nested_component_make_expr arg compiled_expr in + let compiled_expr = + rewrite_nested_component_make_expr arg compiled_expr + in Js_output.output_of_block_and_expression lambda_cxt.continuation block compiled_expr | {value = None} -> assert false) diff --git a/tests/build_tests/rsc_component_with_props_members/input.js b/tests/build_tests/rsc_component_with_props_members/input.js index ee082569e72..74e28a26632 100644 --- a/tests/build_tests/rsc_component_with_props_members/input.js +++ b/tests/build_tests/rsc_component_with_props_members/input.js @@ -33,8 +33,14 @@ assert.match( ); assert.doesNotMatch(output, /\.Provider\.make,/); assert.doesNotMatch(output, /\.Inset\.make,/); -assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); -assert.match(sidebarOutput, /let Inset = \{[\s\S]*make: Sidebar\$Inset[\s\S]*\};/s); +assert.match( + sidebarOutput, + /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, +); +assert.match( + sidebarOutput, + /let Inset = \{[\s\S]*make: Sidebar\$Inset[\s\S]*\};/s, +); assert.match( sidebarOutput, /export \{[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset[\s\S]*\}/s, @@ -53,7 +59,13 @@ assert.match( ); assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); assert.doesNotMatch(plainAccessOutput, /\.Inset\.make/); -assert.doesNotMatch(plainAccessOutput, /Sidebar\$RscComponentWithPropsMembers\.Provider/); -assert.doesNotMatch(plainAccessOutput, /Sidebar\$RscComponentWithPropsMembers\.Inset/); +assert.doesNotMatch( + plainAccessOutput, + /Sidebar\$RscComponentWithPropsMembers\.Provider/, +); +assert.doesNotMatch( + plainAccessOutput, + /Sidebar\$RscComponentWithPropsMembers\.Inset/, +); await execClean(); diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js index 816d6b29520..f762ee4f172 100644 --- a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js @@ -22,11 +22,11 @@ const sidebarOutput = await fs.readFile( assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); assert.doesNotMatch(output, /Sidebar\.Provider\.make/); assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); -assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); assert.match( sidebarOutput, - /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s, + /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, ); +assert.match(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); diff --git a/tests/build_tests/rsc_component_with_props_nested/input.js b/tests/build_tests/rsc_component_with_props_nested/input.js index 2f96733cbff..7b00e9a0ecc 100644 --- a/tests/build_tests/rsc_component_with_props_nested/input.js +++ b/tests/build_tests/rsc_component_with_props_nested/input.js @@ -24,11 +24,11 @@ assert.match( /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsNested\.Sidebar\$Provider,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); -assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); assert.match( sidebarOutput, - /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s, + /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, ); +assert.match(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js index ea81458d900..1d629929c9d 100644 --- a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js @@ -17,12 +17,21 @@ assert.match( output, /let DynamicSidebar = await import\("\.\/Sidebar\.res\.mjs"\);/, ); -assert.match(output, /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/); +assert.match( + output, + /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/, +); assert.match( output, /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider,/, ); -assert.doesNotMatch(output, /let dynamicProvider = DynamicSidebar\.Provider\.make;/); -assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Provider,/); +assert.doesNotMatch( + output, + /let dynamicProvider = DynamicSidebar\.Provider\.make;/, +); +assert.doesNotMatch( + output, + /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Provider,/, +); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js index eb7bde285dc..504abfd4c8b 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -33,6 +33,9 @@ assert.match( /let callProvider = Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider\(\{/, ); assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); -assert.doesNotMatch(plainAccessOutput, /Sidebar\$RscNestedJsxAliasChain\.Provider/); +assert.doesNotMatch( + plainAccessOutput, + /Sidebar\$RscNestedJsxAliasChain\.Provider/, +); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_deep/input.js b/tests/build_tests/rsc_nested_jsx_deep/input.js index cc29b6a5df4..bb5b0bb1eca 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/input.js +++ b/tests/build_tests/rsc_nested_jsx_deep/input.js @@ -24,30 +24,24 @@ assert.match( /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxDeep\.Sidebar\$Group,/, ); assert.doesNotMatch(output, /\.Group\.make,/); -assert.match(sidebarOutput, /let Group = \{[\s\S]*make: Sidebar\$Group[\s\S]*\};/s); assert.match( sidebarOutput, - /export \{[\s\S]*Sidebar\$Group[\s\S]*\}/s, + /let Group = \{[\s\S]*make: Sidebar\$Group[\s\S]*\};/s, ); +assert.match(sidebarOutput, /export \{[\s\S]*Sidebar\$Group[\s\S]*\}/s); assert.doesNotMatch(sidebarOutput, /Group\.make = Group;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Group\$jsx/); const brandIcons = await import("./src/BrandIcons.res.js"); assert.deepStrictEqual( new Set(Object.keys(brandIcons)), - new Set([ - "BrandIcons$ReScript", - "getIconForLanguageExtension", - ]), + new Set(["BrandIcons$ReScript", "getIconForLanguageExtension"]), ); const multipleNested = await import("./src/MultipleNested.res.js"); assert.deepStrictEqual( new Set(Object.keys(multipleNested)), - new Set([ - "MultipleNested$Group", - "MultipleNested$Other", - ]), + new Set(["MultipleNested$Group", "MultipleNested$Other"]), ); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 7b1ada9d688..33960dfcb18 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -61,8 +61,14 @@ assert.doesNotMatch( /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.Provider\.make,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); -assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); -assert.match(sidebarOutput, /let Inset = \{[\s\S]*make: Sidebar\$Inset[\s\S]*\};/s); +assert.match( + sidebarOutput, + /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, +); +assert.match( + sidebarOutput, + /let Inset = \{[\s\S]*make: Sidebar\$Inset[\s\S]*\};/s, +); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Inset\.make = Inset;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); @@ -90,6 +96,9 @@ assert.match( /let callProvider = Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider\(\{/, ); assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); -assert.doesNotMatch(plainAccessOutput, /Sidebar\$RscNestedJsxMembers\.Provider/); +assert.doesNotMatch( + plainAccessOutput, + /Sidebar\$RscNestedJsxMembers\.Provider/, +); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js index a3f4e530bb4..1b3ff51f092 100644 --- a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js @@ -22,7 +22,10 @@ const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); assert.doesNotMatch(output, /Sidebar\.Provider\.make/); assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); -assert.match(sidebarOutput, /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s); +assert.match( + sidebarOutput, + /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, +); assert.match(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js index 874f987d625..2c1ef8d56b1 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js @@ -208,42 +208,26 @@ let Hooks$Inner$jsx = true; let Hooks$Inner$Inner2$jsx = true; -let Hooks$NoProps$jsx = true; - let Hooks$WithRef = make; -let Hooks$WithRef$jsx = true; - -let Hooks$RenderPropRequiresConversion$jsx = true; - -let Hooks$DD$jsx = true; - export { make$1 as make, $$default as default, Another, Inner, - NoProps, functionWithRenamedArgs, WithRename, - WithRef, ForwardRef, Poly, Fun, - RenderPropRequiresConversion, WithChildren, - DD, Hooks$Inner, Hooks$Inner$jsx, Hooks$Inner$Inner2, Hooks$Inner$Inner2$jsx, Hooks$NoProps, - Hooks$NoProps$jsx, Hooks$WithRef, - Hooks$WithRef$jsx, Hooks$RenderPropRequiresConversion, - Hooks$RenderPropRequiresConversion$jsx, Hooks$DD, - Hooks$DD$jsx, } /* make Not a pure module */ diff --git a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js index 4a12f2143f3..79505cae9bb 100644 --- a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js +++ b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js @@ -12,12 +12,8 @@ let CompV4 = { let make = JSXV4Gen.make; -let JSXV4$CompV4$jsx = true; - export { - CompV4, make, JSXV4$CompV4, - JSXV4$CompV4$jsx, } /* make Not a pure module */ diff --git a/tests/tests/src/alias_default_value_test.mjs b/tests/tests/src/alias_default_value_test.mjs index 22e6ada439e..40dd576932b 100644 --- a/tests/tests/src/alias_default_value_test.mjs +++ b/tests/tests/src/alias_default_value_test.mjs @@ -91,46 +91,14 @@ let C8 = { make: Alias_default_value_test$C8 }; -let Alias_default_value_test$C0$jsx = true; - -let Alias_default_value_test$C1$jsx = true; - -let Alias_default_value_test$C2$jsx = true; - -let Alias_default_value_test$C3$jsx = true; - -let Alias_default_value_test$C4$jsx = true; - -let Alias_default_value_test$C6$jsx = true; - -let Alias_default_value_test$C7$jsx = true; - -let Alias_default_value_test$C8$jsx = true; - export { - C0, - C1, - C2, - C3, - C4, - C6, - C7, - C8, Alias_default_value_test$C0, - Alias_default_value_test$C0$jsx, Alias_default_value_test$C1, - Alias_default_value_test$C1$jsx, Alias_default_value_test$C2, - Alias_default_value_test$C2$jsx, Alias_default_value_test$C3, - Alias_default_value_test$C3$jsx, Alias_default_value_test$C4, - Alias_default_value_test$C4$jsx, Alias_default_value_test$C6, - Alias_default_value_test$C6$jsx, Alias_default_value_test$C7, - Alias_default_value_test$C7$jsx, Alias_default_value_test$C8, - Alias_default_value_test$C8$jsx, } /* No side effect */ diff --git a/tests/tests/src/async_jsx.mjs b/tests/tests/src/async_jsx.mjs index 9feec016fcb..536673c09dc 100644 --- a/tests/tests/src/async_jsx.mjs +++ b/tests/tests/src/async_jsx.mjs @@ -33,17 +33,9 @@ let Bar = { make: Async_jsx$Bar }; -let Async_jsx$Foo$jsx = true; - -let Async_jsx$Bar$jsx = true; - export { getNow, - Foo, - Bar, Async_jsx$Foo, - Async_jsx$Foo$jsx, Async_jsx$Bar, - Async_jsx$Bar$jsx, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/tests/src/exception_alias.mjs b/tests/tests/src/exception_alias.mjs index bd3ce69ff67..5f43db8be12 100644 --- a/tests/tests/src/exception_alias.mjs +++ b/tests/tests/src/exception_alias.mjs @@ -14,6 +14,8 @@ let b = Belt_List.length({ } }); +let List_make = Belt_List.make; + let List = { size: Belt_List.size, head: Belt_List.head, @@ -26,7 +28,7 @@ let List = { get: Belt_List.get, getExn: Belt_List.getExn, getOrThrow: Belt_List.getOrThrow, - make: Belt_List.make, + make: List_make, makeByU: Belt_List.makeByU, makeBy: Belt_List.makeBy, shuffle: Belt_List.shuffle, diff --git a/tests/tests/src/jsx_optional_props_test.mjs b/tests/tests/src/jsx_optional_props_test.mjs index b6d2b8bc792..851759f336c 100644 --- a/tests/tests/src/jsx_optional_props_test.mjs +++ b/tests/tests/src/jsx_optional_props_test.mjs @@ -16,12 +16,8 @@ let _element = JsxRuntime.jsx(Jsx_optional_props_test$ComponentWithOptionalProps element: JsxRuntime.jsx("div", {}) }); -let Jsx_optional_props_test$ComponentWithOptionalProps$jsx = true; - export { - ComponentWithOptionalProps, _element, Jsx_optional_props_test$ComponentWithOptionalProps, - Jsx_optional_props_test$ComponentWithOptionalProps$jsx, } /* _element Not a pure module */ diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs index 5d074e9aa84..519fde3c5f3 100644 --- a/tests/tests/src/jsx_preserve_test.mjs +++ b/tests/tests/src/jsx_preserve_test.mjs @@ -229,7 +229,7 @@ let Y = { make: make$1 }; -; +<42 />; let context = React.createContext(0); @@ -251,22 +251,13 @@ let Jsx_preserve_test$A = QueryClientProvider; let make$3 = Jsx_preserve_test; -let Jsx_preserve_test$Icon$jsx = true; - let Jsx_preserve_test$A$jsx = true; -let Jsx_preserve_test$B$jsx = true; - -let Jsx_preserve_test$MyWeirdComponent$jsx = true; - -let Jsx_preserve_test$ComponentWithOptionalProps$jsx = true; - let Jsx_preserve_test$Y$1 = make$1; let Jsx_preserve_test$Y$jsx = true; export { - Icon, _single_element_child, _multiple_element_children, _single_element_fragment, @@ -281,12 +272,9 @@ export { _container_with_spread_props_keyed, _unary_element_with_only_spread_props, A, - B, _external_component_with_children, - MyWeirdComponent, _escaped_jsx_prop, _large_component, - ComponentWithOptionalProps, _optional_props, _props_with_hyphen, _empty_fragment, @@ -298,15 +286,11 @@ export { ContextProvider, make$3 as make, Jsx_preserve_test$Icon, - Jsx_preserve_test$Icon$jsx, Jsx_preserve_test$A, Jsx_preserve_test$A$jsx, Jsx_preserve_test$B, - Jsx_preserve_test$B$jsx, Jsx_preserve_test$MyWeirdComponent, - Jsx_preserve_test$MyWeirdComponent$jsx, Jsx_preserve_test$ComponentWithOptionalProps, - Jsx_preserve_test$ComponentWithOptionalProps$jsx, Jsx_preserve_test$Y$1 as Jsx_preserve_test$Y, Jsx_preserve_test$Y$jsx, } diff --git a/tests/tests/src/jsxv4_newtype.mjs b/tests/tests/src/jsxv4_newtype.mjs index 138850c6d73..bf5594f4efc 100644 --- a/tests/tests/src/jsxv4_newtype.mjs +++ b/tests/tests/src/jsxv4_newtype.mjs @@ -33,26 +33,10 @@ let V4A3 = { make: Jsxv4_newtype$V4A3 }; -let Jsxv4_newtype$V4A$jsx = true; - -let Jsxv4_newtype$V4A1$jsx = true; - -let Jsxv4_newtype$V4A2$jsx = true; - -let Jsxv4_newtype$V4A3$jsx = true; - export { - V4A, - V4A1, - V4A2, - V4A3, Jsxv4_newtype$V4A, - Jsxv4_newtype$V4A$jsx, Jsxv4_newtype$V4A1, - Jsxv4_newtype$V4A1$jsx, Jsxv4_newtype$V4A2, - Jsxv4_newtype$V4A2$jsx, Jsxv4_newtype$V4A3, - Jsxv4_newtype$V4A3$jsx, } /* No side effect */ diff --git a/tests/tests/src/module_missing_conversion.mjs b/tests/tests/src/module_missing_conversion.mjs index 6b3ff81d7e3..76aa14e0152 100644 --- a/tests/tests/src/module_missing_conversion.mjs +++ b/tests/tests/src/module_missing_conversion.mjs @@ -7,6 +7,8 @@ function f(x) { return x; } +let XX_make = Belt_Array.make; + let XX = { get: Belt_Array.get, getExn: Belt_Array.getExn, @@ -18,7 +20,7 @@ let XX = { shuffle: Belt_Array.shuffle, reverseInPlace: Belt_Array.reverseInPlace, reverse: Belt_Array.reverse, - make: Belt_Array.make, + make: XX_make, range: Belt_Array.range, rangeBy: Belt_Array.rangeBy, makeByU: Belt_Array.makeByU, diff --git a/tests/tests/src/test_pervasive.mjs b/tests/tests/src/test_pervasive.mjs index f0f026c6c01..c86acf7624c 100644 --- a/tests/tests/src/test_pervasive.mjs +++ b/tests/tests/src/test_pervasive.mjs @@ -3,6 +3,8 @@ import * as Belt_List from "@rescript/runtime/lib/es6/Belt_List.mjs"; import * as Pervasives from "@rescript/runtime/lib/es6/Pervasives.mjs"; +let Pervasives_make = Belt_List.make; + let Pervasives$1 = { length: Belt_List.length, size: Belt_List.size, @@ -16,7 +18,7 @@ let Pervasives$1 = { get: Belt_List.get, getExn: Belt_List.getExn, getOrThrow: Belt_List.getOrThrow, - make: Belt_List.make, + make: Pervasives_make, makeByU: Belt_List.makeByU, makeBy: Belt_List.makeBy, shuffle: Belt_List.shuffle, diff --git a/tests/tests/src/test_pervasives3.mjs b/tests/tests/src/test_pervasives3.mjs index 9a926f607e1..8fa165626d2 100644 --- a/tests/tests/src/test_pervasives3.mjs +++ b/tests/tests/src/test_pervasives3.mjs @@ -3,6 +3,8 @@ import * as Belt_List from "@rescript/runtime/lib/es6/Belt_List.mjs"; import * as Pervasives from "@rescript/runtime/lib/es6/Pervasives.mjs"; +let Pervasives_make = Belt_List.make; + let Pervasives$1 = { failwith: Pervasives.failwith, invalid_arg: Pervasives.invalid_arg, @@ -34,7 +36,7 @@ let Pervasives$1 = { get: Belt_List.get, getExn: Belt_List.getExn, getOrThrow: Belt_List.getOrThrow, - make: Belt_List.make, + make: Pervasives_make, makeByU: Belt_List.makeByU, makeBy: Belt_List.makeBy, shuffle: Belt_List.shuffle, From 8c00e4cf484daa516aa63345969cee8a1a2b9f20 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 9 Apr 2026 22:11:47 +0200 Subject: [PATCH 27/56] Gentype fix --- compiler/gentype/EmitJs.ml | 39 ++++++++++- compiler/gentype/ExportModule.ml | 68 +++++++++++++++---- compiler/gentype/Runtime.ml | 1 + compiler/gentype/Runtime.mli | 2 +- .../src/Hooks.gen.tsx | 20 +++--- .../src/JSXV4.gen.tsx | 4 +- 6 files changed, 105 insertions(+), 29 deletions(-) diff --git a/compiler/gentype/EmitJs.ml b/compiler/gentype/EmitJs.ml index 7a72efa2e17..ef3684f2681 100644 --- a/compiler/gentype/EmitJs.ml +++ b/compiler/gentype/EmitJs.ml @@ -139,6 +139,27 @@ let emit_export_from_type_declarations ~config ~emitters ~env let emit_code_item ~config ~emitters ~module_items_emitter ~env ~file_name ~output_file_relative ~resolver ~inline_one_level ~type_name_is_interface code_item = + let nested_make_hidden_export_access ~file_name module_access_path = + let rec flatten = function + | Runtime.Root s -> [s] + | Runtime.Dot (path, module_item) -> + flatten path @ [module_item |> Runtime.module_item_to_string] + in + match flatten module_access_path with + | [] -> None + | _module_path :: _ as path -> ( + match List.rev path with + | "make" :: module_path_rev -> ( + match List.rev module_path_rev with + | [] -> None + | module_path -> + Some + ((file_name |> ModuleName.for_js_file |> ModuleName.to_string) + ^ "." + ^ (file_name |> ModuleName.to_string) + ^ "$" ^ String.concat "$" module_path)) + | _ -> None) + in if !Debug.code_items then Log_.item "Code Item: %s\n" (code_item |> code_item_to_string ~config ~type_name_is_interface); @@ -350,6 +371,12 @@ let emit_code_item ~config ~emitters ~module_items_emitter ~env ~file_name (comp_type, None) | _ -> (type_, None) in + let is_react_component_export = + match type_ with + | Function ({arg_types = [{a_type = Object (_, fields)}]; ret_type; _}) -> + ret_type |> EmitType.is_type_function_component ~fields + | _ -> false + in resolved_name |> ExportModule.extend_export_modules ~doc_string ~module_items_emitter @@ -375,9 +402,15 @@ let emit_code_item ~config ~emitters ~module_items_emitter ~env ~file_name | _ -> emitters in let emitters = - (file_name_js |> ModuleName.to_string) - ^ "." - ^ (module_access_path |> Runtime.emit_module_access_path ~config) + (match + ( original_name = make && is_react_component_export, + nested_make_hidden_export_access ~file_name module_access_path ) + with + | true, Some hidden_access -> hidden_access + | _ -> + (file_name_js |> ModuleName.to_string) + ^ "." + ^ (module_access_path |> Runtime.emit_module_access_path ~config)) |> EmitType.emit_export_const ~config ~doc_string ~early:false ~emitters ~name ~type_ ~type_name_is_interface in diff --git a/compiler/gentype/ExportModule.ml b/compiler/gentype/ExportModule.ml index eb3ce62954e..4725b41b75f 100644 --- a/compiler/gentype/ExportModule.ml +++ b/compiler/gentype/ExportModule.ml @@ -3,7 +3,12 @@ open GenTypeCommon type export_module_item = (string, export_module_value) Hashtbl.t and export_module_value = - | S of {name: string; type_: type_; doc_string: DocString.t} + | S of { + name: string; + path: string list; + type_: type_; + doc_string: DocString.t; + } | M of {export_module_item: export_module_item} type export_module_items = (string, export_module_item) Hashtbl.t @@ -18,7 +23,7 @@ type field_info = {field_for_value: field; field_for_type: field} let rec export_module_value_to_type ~config export_module_value = match export_module_value with - | S {name; type_; doc_string} -> + | S {name; type_; doc_string; _} -> {type_for_value = ident name; type_for_type = type_; doc_string} | M {export_module_item} -> let fields_info = @@ -57,13 +62,13 @@ and export_module_item_to_fields = export_module_item [] : config:Config.t -> export_module_item -> field_info list) -let rec extend_export_module_item ~doc_string x +let rec extend_export_module_item ~doc_string ~resolved_name x ~(export_module_item : export_module_item) ~type_ ~value_name = match x with | [] -> () | [field_name] -> Hashtbl.replace export_module_item field_name - (S {name = value_name; type_; doc_string}) + (S {name = value_name; path = resolved_name; type_; doc_string}) | field_name :: rest -> let inner_export_module_item = match Hashtbl.find export_module_item field_name with @@ -77,7 +82,7 @@ let rec extend_export_module_item ~doc_string x inner_export_module_item in rest - |> extend_export_module_item ~doc_string + |> extend_export_module_item ~doc_string ~resolved_name ~export_module_item:inner_export_module_item ~value_name ~type_ let extend_export_module_items x ~doc_string @@ -95,8 +100,8 @@ let extend_export_module_items x ~doc_string export_module_item in rest - |> extend_export_module_item ~doc_string ~export_module_item ~type_ - ~value_name + |> extend_export_module_item ~doc_string ~resolved_name:x ~export_module_item + ~type_ ~value_name let create_module_items_emitter = (fun () -> Hashtbl.create 1 : unit -> export_module_items) @@ -107,16 +112,53 @@ let rev_fold f tbl base = let emit_all_module_items ~config ~emitters ~file_name (export_module_items : export_module_items) = + let is_react_component_type type_ = + match type_ with + | Function ({arg_types = [{a_type = Object (_, fields)}]; ret_type; _}) -> + ret_type |> EmitType.is_type_function_component ~fields + | _ -> false + in + let hidden_export_access resolved_name = + match List.rev resolved_name with + | "make" :: module_path_rev -> ( + match List.rev module_path_rev with + | [] -> None + | module_path -> + Some + ((file_name |> ModuleName.for_js_file |> ModuleName.to_string) + ^ "." + ^ (file_name |> ModuleName.to_string) + ^ "$" ^ String.concat "$" module_path)) + | _ -> None + in + let single_make_component_export export_module_item = + match Hashtbl.length export_module_item with + | 1 -> ( + match Hashtbl.find_opt export_module_item "make" with + | Some (S {path; type_; doc_string; _}) when is_react_component_type type_ -> ( + match hidden_export_access path with + | Some access -> Some (access, type_, doc_string) + | None -> None) + | Some (S _) | Some (M _) | None -> None) + | _ -> None + in emitters |> rev_fold (fun module_name export_module_item emitters -> - let {type_for_type; doc_string} = - M {export_module_item} |> export_module_value_to_type ~config - in if !Debug.code_items then Log_.item "EmitModule %s @." module_name; - let emitted_module_item = - ModuleName.for_inner_module ~file_name ~inner_module_name:module_name - |> ModuleName.to_string + let emitted_module_item, type_for_type, doc_string = + match single_make_component_export export_module_item with + | Some (component_access, type_, doc_string) -> + (component_access, type_, doc_string) + | None -> + let {type_for_type; doc_string} = + M {export_module_item} |> export_module_value_to_type ~config + in + ( ModuleName.for_inner_module ~file_name + ~inner_module_name:module_name + |> ModuleName.to_string, + type_for_type, + doc_string ) in emitted_module_item |> EmitType.emit_export_const ~doc_string ~early:false ~config diff --git a/compiler/gentype/Runtime.ml b/compiler/gentype/Runtime.ml index e8b14e21e9d..6ceef0a6c86 100644 --- a/compiler/gentype/Runtime.ml +++ b/compiler/gentype/Runtime.ml @@ -4,6 +4,7 @@ type module_access_path = | Dot of module_access_path * module_item let new_module_item ~name = name +let module_item_to_string module_item = module_item let rec emit_module_access_path ~config module_access_path = match module_access_path with diff --git a/compiler/gentype/Runtime.mli b/compiler/gentype/Runtime.mli index d220a0a7b51..84ce6009da7 100644 --- a/compiler/gentype/Runtime.mli +++ b/compiler/gentype/Runtime.mli @@ -8,8 +8,8 @@ type module_access_path = val check_mutable_object_field : previous_name:string -> name:string -> bool val default : string val emit_module_access_path : config:Config.t -> module_access_path -> string - val is_mutable_object_field : string -> bool +val module_item_to_string : module_item -> string val new_module_item : name:string -> module_item val js_variant_tag : polymorphic:bool -> tag:string option -> string val js_variant_payload_tag : n:int -> string diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.gen.tsx b/tests/gentype_tests/typescript-react-example/src/Hooks.gen.tsx index 3ebfd588035..fd481eff2ad 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.gen.tsx +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.gen.tsx @@ -61,15 +61,15 @@ export default $$default; export const Another_anotherComponent: React.ComponentType<{ readonly vehicle: vehicle; readonly callback: () => void }> = HooksJS.Another.anotherComponent as any; -export const Inner_make: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Inner.make as any; +export const Inner_make: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Hooks$Inner as any; export const Inner_Another_anotherComponent: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Inner.Another.anotherComponent as any; -export const Inner_Inner2_make: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Inner.Inner2.make as any; +export const Inner_Inner2_make: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Hooks$Inner$Inner2 as any; export const Inner_Inner2_Another_anotherComponent: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Inner.Inner2.Another.anotherComponent as any; -export const NoProps_make: React.ComponentType<{}> = HooksJS.NoProps.make as any; +export const NoProps_make: React.ComponentType<{}> = HooksJS.Hooks$NoProps as any; export const functionWithRenamedArgs: (_to:vehicle, _Type:vehicle, cb:cb) => string = HooksJS.functionWithRenamedArgs as any; @@ -79,7 +79,7 @@ export const WithRename_componentWithRenamedArgs: React.ComponentType<{ readonly cb: cb }> = HooksJS.WithRename.componentWithRenamedArgs as any; -export const WithRef_make: React.ComponentType<{ readonly vehicle: vehicle; readonly ref?: any }> = HooksJS.WithRef.make as any; +export const WithRef_make: React.ComponentType<{ readonly vehicle: vehicle; readonly ref?: any }> = HooksJS.Hooks$WithRef as any; export const ForwardRef_input: (_1:r) => JSX.Element = HooksJS.ForwardRef.input as any; @@ -87,13 +87,13 @@ export const Poly_polymorphicComponent: React.ComponentType<{ readonly p: [vehic export const Fun_functionReturningReactElement: React.ComponentType<{ readonly name: string }> = HooksJS.Fun.functionReturningReactElement as any; -export const RenderPropRequiresConversion_make: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> = HooksJS.RenderPropRequiresConversion.make as any; +export const RenderPropRequiresConversion_make: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> = HooksJS.Hooks$RenderPropRequiresConversion as any; export const WithChildren_aComponentWithChildren: React.ComponentType<{ readonly vehicle: vehicle; readonly children: React.ReactNode }> = HooksJS.WithChildren.aComponentWithChildren as any; -export const DD_make: React.ComponentType<{ readonly array: Js_TypedArray2_Uint8Array_t; readonly name: string }> = HooksJS.DD.make as any; +export const DD_make: React.ComponentType<{ readonly array: Js_TypedArray2_Uint8Array_t; readonly name: string }> = HooksJS.Hooks$DD as any; -export const NoProps: { make: React.ComponentType<{}> } = HooksJS.NoProps as any; +export const NoProps: React.ComponentType<{}> = HooksJS.Hooks$NoProps as any; export const Inner: { Inner2: { @@ -116,7 +116,7 @@ export const Inner: { }> } = HooksJS.Inner as any; -export const RenderPropRequiresConversion: { make: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> } = HooksJS.RenderPropRequiresConversion as any; +export const RenderPropRequiresConversion: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> = HooksJS.Hooks$RenderPropRequiresConversion as any; export const WithRename: { componentWithRenamedArgs: React.ComponentType<{ readonly _to: vehicle; @@ -128,11 +128,11 @@ export const ForwardRef: { input: (_1:r) => JSX.Element } = HooksJS.ForwardRef a export const Fun: { functionReturningReactElement: React.ComponentType<{ readonly name: string }> } = HooksJS.Fun as any; -export const WithRef: { make: React.ComponentType<{ readonly vehicle: vehicle; readonly ref?: any }> } = HooksJS.WithRef as any; +export const WithRef: React.ComponentType<{ readonly vehicle: vehicle; readonly ref?: any }> = HooksJS.Hooks$WithRef as any; export const WithChildren: { aComponentWithChildren: React.ComponentType<{ readonly vehicle: vehicle; readonly children: React.ReactNode }> } = HooksJS.WithChildren as any; -export const DD: { make: React.ComponentType<{ readonly array: Js_TypedArray2_Uint8Array_t; readonly name: string }> } = HooksJS.DD as any; +export const DD: React.ComponentType<{ readonly array: Js_TypedArray2_Uint8Array_t; readonly name: string }> = HooksJS.Hooks$DD as any; export const Another: { anotherComponent: React.ComponentType<{ readonly vehicle: vehicle; readonly callback: () => void }> } = HooksJS.Another as any; diff --git a/tests/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx b/tests/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx index 5d138887bf3..77509ddeaae 100644 --- a/tests/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx +++ b/tests/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx @@ -38,6 +38,6 @@ export type props = { readonly renderMe: renderMe }; -export const CompV4_make: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4JS.CompV4.make as any; +export const CompV4_make: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4JS.JSXV4$CompV4 as any; -export const CompV4: { make: React.ComponentType<{ readonly x: string; readonly y: string }> } = JSXV4JS.CompV4 as any; +export const CompV4: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4JS.JSXV4$CompV4 as any; From a9bbed85313e48f77c29deb99eccf50c3457ec92 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 9 Apr 2026 22:18:42 +0200 Subject: [PATCH 28/56] Fix jsx props regression --- compiler/core/lam_compile.ml | 24 +++++++++++-------- compiler/gentype/EmitJs.ml | 5 ++-- compiler/gentype/ExportModule.ml | 12 ++++++---- .../src/expected/CompletionAttributes.res.txt | 18 ++++++++++++++ 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 21c0d040296..4b180f34c08 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -1889,16 +1889,20 @@ let compile output_prefix = args = [arg]; _; } -> ( - let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in - match compile_lambda new_cxt arg with - | {block; value = Some compiled_arg} -> - let compiled_expr = Js_of_lam_block.field fld_info compiled_arg 0l in - let compiled_expr = - rewrite_nested_component_make_expr arg compiled_expr - in - Js_output.output_of_block_and_expression lambda_cxt.continuation block - compiled_expr - | {value = None} -> assert false) + match arg with + | Lglobal_module (id, dynamic_import) -> + compile_external_field ~dynamic_import lambda_cxt id "make" + | _ -> + let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in + match compile_lambda new_cxt arg with + | {block; value = Some compiled_arg} -> + let compiled_expr = Js_of_lam_block.field fld_info compiled_arg 0l in + let compiled_expr = + rewrite_nested_component_make_expr arg compiled_expr + in + Js_output.output_of_block_and_expression lambda_cxt.continuation block + compiled_expr + | {value = None} -> assert false) | { primitive = Pfield (_, fld_info); args = [Lglobal_module (id, dynamic_import)]; diff --git a/compiler/gentype/EmitJs.ml b/compiler/gentype/EmitJs.ml index ef3684f2681..964b34a2928 100644 --- a/compiler/gentype/EmitJs.ml +++ b/compiler/gentype/EmitJs.ml @@ -157,7 +157,8 @@ let emit_code_item ~config ~emitters ~module_items_emitter ~env ~file_name ((file_name |> ModuleName.for_js_file |> ModuleName.to_string) ^ "." ^ (file_name |> ModuleName.to_string) - ^ "$" ^ String.concat "$" module_path)) + ^ "$" + ^ String.concat "$" module_path)) | _ -> None) in if !Debug.code_items then @@ -373,7 +374,7 @@ let emit_code_item ~config ~emitters ~module_items_emitter ~env ~file_name in let is_react_component_export = match type_ with - | Function ({arg_types = [{a_type = Object (_, fields)}]; ret_type; _}) -> + | Function {arg_types = [{a_type = Object (_, fields)}]; ret_type; _} -> ret_type |> EmitType.is_type_function_component ~fields | _ -> false in diff --git a/compiler/gentype/ExportModule.ml b/compiler/gentype/ExportModule.ml index 4725b41b75f..54b913b2801 100644 --- a/compiler/gentype/ExportModule.ml +++ b/compiler/gentype/ExportModule.ml @@ -100,8 +100,8 @@ let extend_export_module_items x ~doc_string export_module_item in rest - |> extend_export_module_item ~doc_string ~resolved_name:x ~export_module_item - ~type_ ~value_name + |> extend_export_module_item ~doc_string ~resolved_name:x + ~export_module_item ~type_ ~value_name let create_module_items_emitter = (fun () -> Hashtbl.create 1 : unit -> export_module_items) @@ -114,7 +114,7 @@ let emit_all_module_items ~config ~emitters ~file_name (export_module_items : export_module_items) = let is_react_component_type type_ = match type_ with - | Function ({arg_types = [{a_type = Object (_, fields)}]; ret_type; _}) -> + | Function {arg_types = [{a_type = Object (_, fields)}]; ret_type; _} -> ret_type |> EmitType.is_type_function_component ~fields | _ -> false in @@ -128,14 +128,16 @@ let emit_all_module_items ~config ~emitters ~file_name ((file_name |> ModuleName.for_js_file |> ModuleName.to_string) ^ "." ^ (file_name |> ModuleName.to_string) - ^ "$" ^ String.concat "$" module_path)) + ^ "$" + ^ String.concat "$" module_path)) | _ -> None in let single_make_component_export export_module_item = match Hashtbl.length export_module_item with | 1 -> ( match Hashtbl.find_opt export_module_item "make" with - | Some (S {path; type_; doc_string; _}) when is_react_component_type type_ -> ( + | Some (S {path; type_; doc_string; _}) when is_react_component_type type_ + -> ( match hidden_export_access path with | Some access -> Some (access, type_, doc_string) | None -> None) diff --git a/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt b/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt index 1c7c3fb9df5..0145d56593c 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt @@ -24,6 +24,12 @@ Resolved opens 1 Stdlib "tags": [], "detail": "Package", "documentation": null + }, { + "label": "./CompletionJsxProps.cmt", + "kind": 4, + "tags": [], + "detail": "Local file", + "documentation": null }, { "label": "./test.json", "kind": 4, @@ -191,6 +197,12 @@ Resolved opens 1 Stdlib "tags": [], "detail": "Package", "documentation": null + }, { + "label": "./CompletionJsxProps.cmt", + "kind": 4, + "tags": [], + "detail": "Local file", + "documentation": null }, { "label": "./test.json", "kind": 4, @@ -216,6 +228,12 @@ Resolved opens 1 Stdlib "tags": [], "detail": "Package", "documentation": null + }, { + "label": "./CompletionJsxProps.cmt", + "kind": 4, + "tags": [], + "detail": "Local file", + "documentation": null }, { "label": "./test.json", "kind": 4, From b630257d997d062df16e8a1703655544bbc22d1f Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 9 Apr 2026 22:23:39 +0200 Subject: [PATCH 29/56] Cleanup --- compiler/core/lam_compile.ml | 4 ++-- tests/tests/src/exception_alias.mjs | 4 +--- tests/tests/src/module_missing_conversion.mjs | 4 +--- tests/tests/src/test_pervasive.mjs | 4 +--- tests/tests/src/test_pervasives3.mjs | 4 +--- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 4b180f34c08..4810011f735 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -1892,7 +1892,7 @@ let compile output_prefix = match arg with | Lglobal_module (id, dynamic_import) -> compile_external_field ~dynamic_import lambda_cxt id "make" - | _ -> + | _ -> ( let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in match compile_lambda new_cxt arg with | {block; value = Some compiled_arg} -> @@ -1902,7 +1902,7 @@ let compile output_prefix = in Js_output.output_of_block_and_expression lambda_cxt.continuation block compiled_expr - | {value = None} -> assert false) + | {value = None} -> assert false)) | { primitive = Pfield (_, fld_info); args = [Lglobal_module (id, dynamic_import)]; diff --git a/tests/tests/src/exception_alias.mjs b/tests/tests/src/exception_alias.mjs index 5f43db8be12..bd3ce69ff67 100644 --- a/tests/tests/src/exception_alias.mjs +++ b/tests/tests/src/exception_alias.mjs @@ -14,8 +14,6 @@ let b = Belt_List.length({ } }); -let List_make = Belt_List.make; - let List = { size: Belt_List.size, head: Belt_List.head, @@ -28,7 +26,7 @@ let List = { get: Belt_List.get, getExn: Belt_List.getExn, getOrThrow: Belt_List.getOrThrow, - make: List_make, + make: Belt_List.make, makeByU: Belt_List.makeByU, makeBy: Belt_List.makeBy, shuffle: Belt_List.shuffle, diff --git a/tests/tests/src/module_missing_conversion.mjs b/tests/tests/src/module_missing_conversion.mjs index 76aa14e0152..6b3ff81d7e3 100644 --- a/tests/tests/src/module_missing_conversion.mjs +++ b/tests/tests/src/module_missing_conversion.mjs @@ -7,8 +7,6 @@ function f(x) { return x; } -let XX_make = Belt_Array.make; - let XX = { get: Belt_Array.get, getExn: Belt_Array.getExn, @@ -20,7 +18,7 @@ let XX = { shuffle: Belt_Array.shuffle, reverseInPlace: Belt_Array.reverseInPlace, reverse: Belt_Array.reverse, - make: XX_make, + make: Belt_Array.make, range: Belt_Array.range, rangeBy: Belt_Array.rangeBy, makeByU: Belt_Array.makeByU, diff --git a/tests/tests/src/test_pervasive.mjs b/tests/tests/src/test_pervasive.mjs index c86acf7624c..f0f026c6c01 100644 --- a/tests/tests/src/test_pervasive.mjs +++ b/tests/tests/src/test_pervasive.mjs @@ -3,8 +3,6 @@ import * as Belt_List from "@rescript/runtime/lib/es6/Belt_List.mjs"; import * as Pervasives from "@rescript/runtime/lib/es6/Pervasives.mjs"; -let Pervasives_make = Belt_List.make; - let Pervasives$1 = { length: Belt_List.length, size: Belt_List.size, @@ -18,7 +16,7 @@ let Pervasives$1 = { get: Belt_List.get, getExn: Belt_List.getExn, getOrThrow: Belt_List.getOrThrow, - make: Pervasives_make, + make: Belt_List.make, makeByU: Belt_List.makeByU, makeBy: Belt_List.makeBy, shuffle: Belt_List.shuffle, diff --git a/tests/tests/src/test_pervasives3.mjs b/tests/tests/src/test_pervasives3.mjs index 8fa165626d2..9a926f607e1 100644 --- a/tests/tests/src/test_pervasives3.mjs +++ b/tests/tests/src/test_pervasives3.mjs @@ -3,8 +3,6 @@ import * as Belt_List from "@rescript/runtime/lib/es6/Belt_List.mjs"; import * as Pervasives from "@rescript/runtime/lib/es6/Pervasives.mjs"; -let Pervasives_make = Belt_List.make; - let Pervasives$1 = { failwith: Pervasives.failwith, invalid_arg: Pervasives.invalid_arg, @@ -36,7 +34,7 @@ let Pervasives$1 = { get: Belt_List.get, getExn: Belt_List.getExn, getOrThrow: Belt_List.getOrThrow, - make: Pervasives_make, + make: Belt_List.make, makeByU: Belt_List.makeByU, makeBy: Belt_List.makeBy, shuffle: Belt_List.shuffle, From 137e8fc716ff77d04e24f5add993576428cd3054 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 9 Apr 2026 22:27:03 +0200 Subject: [PATCH 30/56] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9fa571287a..a278cfe2539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ #### :boom: Breaking Change - Change `Intl.Collator.compare` return type from `int` to `Ordering.t` (`float`). https://github.com/rescript-lang/rescript/pull/8289 +- Nested React component modules now export only the namespaced component value instead of a public `make` wrapper, and GenType mirrors that new boundary shape. https://github.com/rescript-lang/rescript/pull/8293 #### :eyeglasses: Spec Compliance From 8484a7faf413583d4838b595806ec6ede0974ce1 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 24 Apr 2026 22:11:20 +0200 Subject: [PATCH 31/56] Remove bool marker again --- .../core/js_pass_nested_component_exports.ml | 26 +++------- compiler/syntax/src/jsx_v4.ml | 48 +++++-------------- .../react_ppx/src/gpr_3695_test.res.js | 3 -- .../src/recursive_component_test.res.js | 3 -- .../ppx/react/expected/aliasProps.res.txt | 14 +++--- .../ppx/react/expected/asyncAwait.res.txt | 4 +- .../react/expected/defaultPatternProp.res.txt | 2 - .../react/expected/defaultValueProp.res.txt | 4 -- .../react/expected/externalWithRef.res.txt | 1 - .../externalWithTypeVariables.res.txt | 1 - .../react/expected/fileLevelConfig.res.txt | 1 - .../react/expected/firstClassModules.resi.txt | 1 - .../ppx/react/expected/forwardRef.res.txt | 5 +- .../ppx/react/expected/forwardRef.resi.txt | 8 ---- .../data/ppx/react/expected/interface.res.txt | 3 +- .../ppx/react/expected/interface.resi.txt | 2 - .../ppx/react/expected/mangleKeyword.res.txt | 2 - .../data/ppx/react/expected/nested.res.txt | 3 +- .../data/ppx/react/expected/newtype.res.txt | 9 ++-- .../ppx/react/expected/noPropsWithKey.res.txt | 3 -- .../expected/optimizeAutomaticMode.res.txt | 1 - .../react/expected/returnConstraint.res.txt | 4 -- .../ppx/react/expected/sharedProps.res.txt | 8 ---- .../ppx/react/expected/sharedProps.resi.txt | 8 ---- .../expected/sharedPropsWithProps.res.txt | 7 --- .../data/ppx/react/expected/topLevel.res.txt | 2 +- .../ppx/react/expected/typeConstraint.res.txt | 1 - .../ppx/react/expected/uncurriedProps.res.txt | 2 - .../data/ppx/react/expected/v4.res.txt | 11 ++--- 29 files changed, 42 insertions(+), 145 deletions(-) diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml index b5d5bb4618f..760bc8ed695 100644 --- a/compiler/core/js_pass_nested_component_exports.ml +++ b/compiler/core/js_pass_nested_component_exports.ml @@ -26,7 +26,7 @@ module E = Js_exp_make module StringSet = Set.Make (String) -type candidate = {module_ident: Ident.t; hidden_export_name: string} +type candidate = {module_ident: Ident.t} let dynamic_import_module_root (expr : J.expression) = match expr.expression_desc with @@ -52,8 +52,6 @@ let dynamic_import_module_root (expr : J.expression) = | _ -> None) | _ -> None -let marker_name hidden_export_name = hidden_export_name ^ "$jsx" - let hidden_component_suffix (module_ident : Ident.t) = "$" ^ Ident.name module_ident @@ -108,7 +106,7 @@ let candidate_of_statement block exports (st : J.statement) = in match hidden_ident with | Some hidden_ident when has_export_name exports hidden_ident.name -> - Some {module_ident; hidden_export_name = hidden_ident.name} + Some {module_ident} | Some _ | None -> None) | _ -> None @@ -117,13 +115,7 @@ let collect_candidates block exports = let hidden_export_names_to_remove candidates = Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> - acc - |> StringSet.add (Ident.name candidate.module_ident) - |> StringSet.add (marker_name candidate.hidden_export_name)) - -let marker_names_to_remove_from_block candidates = - Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> - StringSet.add (marker_name candidate.hidden_export_name) acc) + StringSet.add (Ident.name candidate.module_ident) acc) let candidate_by_module_ident candidates module_ident = List.find_map @@ -132,16 +124,14 @@ let candidate_by_module_ident candidates module_ident = else None) candidates -let rewrite_block block candidates removed_marker_names = +let rewrite_block block candidates = List.concat_map (fun (st : J.statement) -> match st.statement_desc with - | Variable {ident; value; property; ident_info} -> ( + | Variable {ident; _} -> ( match candidate_by_module_ident candidates ident with | Some _ -> [st] - | None -> - if StringSet.mem (Ident.name ident) removed_marker_names then [] - else [st]) + | None -> [st]) | _ -> [st]) block @@ -200,14 +190,12 @@ let rewrite_dynamic_import_block block = let program (js : J.program) : J.program = let candidates = collect_candidates js.block js.exports in let removed_export_names = hidden_export_names_to_remove candidates in - let removed_marker_names = marker_names_to_remove_from_block candidates in let exports = Ext_list.filter js.exports (fun (ident : Ident.t) -> not (StringSet.mem (Ident.name ident) removed_export_names)) in let export_set = Set_ident.of_list exports in let block = - rewrite_dynamic_import_block - (rewrite_block js.block candidates removed_marker_names) + rewrite_dynamic_import_block (rewrite_block js.block candidates) in {J.block; exports; export_set} diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 9412449a604..6b82820b26c 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -96,9 +96,9 @@ let props_longident_for_nested_module nested_modules = in Ldot (mod_path, "props") -(* Hoisted File$Nested / File$Nested$jsx exist for JS/RSC exports; they are not - always referenced from ReScript when a nested module is absent from the - [.resi], so suppress unused-value (32) on these bindings only. *) +(* Hoisted File$Nested aliases exist for JS/RSC exports; they are not always + referenced from ReScript when a nested module is absent from the [.resi], so + suppress unused-value (32) on these bindings only. *) let jsx_hoisted_binding_warning_attrs = [ ( Location.mknoloc "warning", @@ -110,7 +110,6 @@ let make_hoisted_component_binding ~empty_loc ~full_module_name nested_modules = nested_modules |> List.rev |> longident_of_segments |> fun txt -> {loc = empty_loc; txt = Ldot (txt, "make")} in - let marker_name = full_module_name ^ "$jsx" in { pstr_loc = empty_loc; pstr_desc = @@ -120,11 +119,6 @@ let make_hoisted_component_binding ~empty_loc ~full_module_name nested_modules = Vb.mk ~loc:empty_loc ~attrs:jsx_hoisted_binding_warning_attrs (Pat.var ~loc:empty_loc {loc = empty_loc; txt = full_module_name}) (Exp.ident ~loc:empty_loc path); - Vb.mk ~loc:empty_loc ~attrs:jsx_hoisted_binding_warning_attrs - (Pat.var ~loc:empty_loc {loc = empty_loc; txt = marker_name}) - (Exp.construct ~loc:empty_loc - {loc = empty_loc; txt = Lident "true"} - None); ] ); } @@ -162,40 +156,24 @@ let maybe_hoist_nested_make_component ~(config : Jsx_common.jsx_config) let make_hoisted_component_signature ~empty_loc ~full_module_name (component_type : Parsetree.core_type) = - let marker_name = full_module_name ^ "$jsx" in - let bool_ty = - Typ.constr ~loc:empty_loc {loc = empty_loc; txt = Lident "bool"} [] - in - let full_sig = - { - psig_loc = empty_loc; - psig_desc = - Psig_value - (Val.mk ~loc:empty_loc - {loc = empty_loc; txt = full_module_name} - component_type); - } - in - let jsx_sig = - { - psig_loc = empty_loc; - psig_desc = - Psig_value - (Val.mk ~loc:empty_loc {loc = empty_loc; txt = marker_name} bool_ty); - } - in - (full_sig, jsx_sig) + { + psig_loc = empty_loc; + psig_desc = + Psig_value + (Val.mk ~loc:empty_loc + {loc = empty_loc; txt = full_module_name} + component_type); + } let maybe_hoist_nested_make_signature ~(config : Jsx_common.jsx_config) ~empty_loc ~full_module_name ~component_type fn_name = match (fn_name, config.nested_modules, config.functor_depth) with | "make", _ :: _, 0 -> - let full_sig, jsx_sig = + let full_sig = make_hoisted_component_signature ~empty_loc ~full_module_name component_type in - config.hoisted_signature_items <- - jsx_sig :: full_sig :: config.hoisted_signature_items + config.hoisted_signature_items <- full_sig :: config.hoisted_signature_items | _ -> () (* Build a string representation of a module name with segments separated by $ *) diff --git a/tests/build_tests/react_ppx/src/gpr_3695_test.res.js b/tests/build_tests/react_ppx/src/gpr_3695_test.res.js index 11c0cca5c42..9804c0f5b2b 100644 --- a/tests/build_tests/react_ppx/src/gpr_3695_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3695_test.res.js @@ -12,13 +12,10 @@ function test(className) { let Gpr_3695_test$Test = Foo; -let Gpr_3695_test$Test$jsx = true; - export { React, Test, test, Gpr_3695_test$Test, - Gpr_3695_test$Test$jsx, } /* Gpr_3695_test$Test Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/recursive_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_component_test.res.js index f3aa4d5fc91..64226768532 100644 --- a/tests/build_tests/react_ppx/src/recursive_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_component_test.res.js @@ -20,11 +20,8 @@ let Rec = { let Recursive_component_test$Rec = make; -let Recursive_component_test$Rec$jsx = true; - export { Rec, Recursive_component_test$Rec, - Recursive_component_test$Rec$jsx, } /* No side effect */ diff --git a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt index ec718c8b55d..4763e177e54 100644 --- a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt @@ -162,10 +162,10 @@ module C6 = { \"AliasProps$C6" } } -@warning("-32") let \"AliasProps$C0" = C0.make @warning("-32") and \"AliasProps$C0$jsx" = true -@warning("-32") let \"AliasProps$C1" = C1.make @warning("-32") and \"AliasProps$C1$jsx" = true -@warning("-32") let \"AliasProps$C2" = C2.make @warning("-32") and \"AliasProps$C2$jsx" = true -@warning("-32") let \"AliasProps$C3" = C3.make @warning("-32") and \"AliasProps$C3$jsx" = true -@warning("-32") let \"AliasProps$C4" = C4.make @warning("-32") and \"AliasProps$C4$jsx" = true -@warning("-32") let \"AliasProps$C5" = C5.make @warning("-32") and \"AliasProps$C5$jsx" = true -@warning("-32") let \"AliasProps$C6" = C6.make @warning("-32") and \"AliasProps$C6$jsx" = true +@warning("-32") let \"AliasProps$C0" = C0.make +@warning("-32") let \"AliasProps$C1" = C1.make +@warning("-32") let \"AliasProps$C2" = C2.make +@warning("-32") let \"AliasProps$C3" = C3.make +@warning("-32") let \"AliasProps$C4" = C4.make +@warning("-32") let \"AliasProps$C5" = C5.make +@warning("-32") let \"AliasProps$C6" = C6.make diff --git a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt index 4a2ccb3500c..01ef3a2faa8 100644 --- a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt @@ -35,5 +35,5 @@ module C1 = { \"AsyncAwait$C1" } } -@warning("-32") let \"AsyncAwait$C0" = C0.make @warning("-32") and \"AsyncAwait$C0$jsx" = true -@warning("-32") let \"AsyncAwait$C1" = C1.make @warning("-32") and \"AsyncAwait$C1$jsx" = true +@warning("-32") let \"AsyncAwait$C0" = C0.make +@warning("-32") let \"AsyncAwait$C1" = C1.make diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt index e4a96d16e0c..8aeee1d57d7 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt @@ -34,6 +34,4 @@ module C0 = { } } @warning("-32") let \"DefaultPatternProp$C0$M" = C0.M.make -@warning("-32") and \"DefaultPatternProp$C0$M$jsx" = true @warning("-32") let \"DefaultPatternProp$C0" = C0.make -@warning("-32") and \"DefaultPatternProp$C0$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt index 7b24b91d0b4..1c0104335a4 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt @@ -92,10 +92,6 @@ module C3 = { } } @warning("-32") let \"DefaultValueProp$C0" = C0.make -@warning("-32") and \"DefaultValueProp$C0$jsx" = true @warning("-32") let \"DefaultValueProp$C1" = C1.make -@warning("-32") and \"DefaultValueProp$C1$jsx" = true @warning("-32") let \"DefaultValueProp$C2" = C2.make -@warning("-32") and \"DefaultValueProp$C2$jsx" = true @warning("-32") let \"DefaultValueProp$C3" = C3.make -@warning("-32") and \"DefaultValueProp$C3$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt index 13e42c9dae9..648bf3e6ecd 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt @@ -11,4 +11,3 @@ module V4C = { external make: React.component> = "component" } @warning("-32") let \"ExternalWithRef$V4C" = V4C.make -@warning("-32") and \"ExternalWithRef$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt index 72c71617ae4..97e26dd8798 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt @@ -11,4 +11,3 @@ module V4C = { external make: React.component, React.element>> = "component" } @warning("-32") let \"ExternalWithTypeVariables$V4C" = V4C.make -@warning("-32") and \"ExternalWithTypeVariables$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt index 98b0b1dd63a..23dac80a5ec 100644 --- a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt @@ -16,4 +16,3 @@ module V4A = { } } @warning("-32") let \"FileLevelConfig$V4A" = V4A.make -@warning("-32") and \"FileLevelConfig$V4A$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt b/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt index 9a3cb5536f3..303e200c456 100644 --- a/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt @@ -30,4 +30,3 @@ let \"FirstClassModules$Select": React.component< array<'a>, >, > -let \"FirstClassModules$Select$jsx": bool diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt index ee69fc32062..65e810f643f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt @@ -122,9 +122,6 @@ module V4AUncurried = { } } @warning("-32") let \"ForwardRef$V4A$FancyInput" = V4A.FancyInput.make -@warning("-32") and \"ForwardRef$V4A$FancyInput$jsx" = true -@warning("-32") let \"ForwardRef$V4A" = V4A.make @warning("-32") and \"ForwardRef$V4A$jsx" = true +@warning("-32") let \"ForwardRef$V4A" = V4A.make @warning("-32") let \"ForwardRef$V4AUncurried$FancyInput" = V4AUncurried.FancyInput.make -@warning("-32") and \"ForwardRef$V4AUncurried$FancyInput$jsx" = true @warning("-32") let \"ForwardRef$V4AUncurried" = V4AUncurried.make -@warning("-32") and \"ForwardRef$V4AUncurried$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt index 89055b25426..f8ac515ecef 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt @@ -92,32 +92,24 @@ module V4AUncurried: { let \"ForwardRef$V4C$FancyInput": React.component< V4C.FancyInput.props, > -let \"ForwardRef$V4C$FancyInput$jsx": bool let \"ForwardRef$V4C$ForwardRef": React.component< V4C.ForwardRef.props>>, > -let \"ForwardRef$V4C$ForwardRef$jsx": bool let \"ForwardRef$V4CUncurried$FancyInput": React.component< V4CUncurried.FancyInput.props, > -let \"ForwardRef$V4CUncurried$FancyInput$jsx": bool let \"ForwardRef$V4CUncurried$ForwardRef": React.component< V4CUncurried.ForwardRef.props>>, > -let \"ForwardRef$V4CUncurried$ForwardRef$jsx": bool let \"ForwardRef$V4A$FancyInput": React.component< V4A.FancyInput.props, > -let \"ForwardRef$V4A$FancyInput$jsx": bool let \"ForwardRef$V4A$ForwardRef": React.component< V4A.ForwardRef.props>>, > -let \"ForwardRef$V4A$ForwardRef$jsx": bool let \"ForwardRef$V4AUncurried$FancyInput": React.component< V4AUncurried.FancyInput.props, > -let \"ForwardRef$V4AUncurried$FancyInput$jsx": bool let \"ForwardRef$V4AUncurried$ForwardRef": React.component< V4AUncurried.ForwardRef.props>>, > -let \"ForwardRef$V4AUncurried$ForwardRef$jsx": bool diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt index 674cfc85dcf..8268a3aabcd 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt @@ -21,6 +21,5 @@ module NoProps = { \"Interface$NoProps" } } -@warning("-32") let \"Interface$A" = A.make @warning("-32") and \"Interface$A$jsx" = true +@warning("-32") let \"Interface$A" = A.make @warning("-32") let \"Interface$NoProps" = NoProps.make -@warning("-32") and \"Interface$NoProps$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt b/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt index 6a21c4d722c..eddc8eb4b7b 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt @@ -13,6 +13,4 @@ module NoProps: { let make: React.component } let \"Interface$A": React.component> -let \"Interface$A$jsx": bool let \"Interface$NoProps": React.component -let \"Interface$NoProps$jsx": bool diff --git a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt index 1d79a25571c..b5f9e038e50 100644 --- a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt @@ -28,6 +28,4 @@ module C4A1 = { let c4a0 = React.jsx(@res.jsxComponentPath C4A0.make, {_open: "x", _type: "t"}) let c4a1 = React.jsx(@res.jsxComponentPath C4A1.make, {_open: "x", _type: "t"}) @warning("-32") let \"MangleKeyword$C4A0" = C4A0.make -@warning("-32") and \"MangleKeyword$C4A0$jsx" = true @warning("-32") let \"MangleKeyword$C4A1" = C4A1.make -@warning("-32") and \"MangleKeyword$C4A1$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt index 76e62072ce8..e0e66fb44fd 100644 --- a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt @@ -22,5 +22,4 @@ module Outer = { } } @warning("-32") let \"Nested$Outer$Inner" = Outer.Inner.make -@warning("-32") and \"Nested$Outer$Inner$jsx" = true -@warning("-32") let \"Nested$Outer" = Outer.make @warning("-32") and \"Nested$Outer$jsx" = true +@warning("-32") let \"Nested$Outer" = Outer.make diff --git a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt index fdfc5714bfa..9b10eb2de43 100644 --- a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt @@ -99,9 +99,8 @@ module Uncurried = { \"Newtype$Uncurried" } } -@warning("-32") let \"Newtype$V4A" = V4A.make @warning("-32") and \"Newtype$V4A$jsx" = true -@warning("-32") let \"Newtype$V4A1" = V4A1.make @warning("-32") and \"Newtype$V4A1$jsx" = true -@warning("-32") let \"Newtype$V4A2" = V4A2.make @warning("-32") and \"Newtype$V4A2$jsx" = true -@warning("-32") let \"Newtype$V4A3" = V4A3.make @warning("-32") and \"Newtype$V4A3$jsx" = true +@warning("-32") let \"Newtype$V4A" = V4A.make +@warning("-32") let \"Newtype$V4A1" = V4A1.make +@warning("-32") let \"Newtype$V4A2" = V4A2.make +@warning("-32") let \"Newtype$V4A3" = V4A3.make @warning("-32") let \"Newtype$Uncurried" = Uncurried.make -@warning("-32") and \"Newtype$Uncurried$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt index 7b4e9bd7ea3..9f285c97426 100644 --- a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt @@ -41,8 +41,5 @@ module V4C = { } } @warning("-32") let \"NoPropsWithKey$V4CA" = V4CA.make -@warning("-32") and \"NoPropsWithKey$V4CA$jsx" = true @warning("-32") let \"NoPropsWithKey$V4CB" = V4CB.make -@warning("-32") and \"NoPropsWithKey$V4CB$jsx" = true @warning("-32") let \"NoPropsWithKey$V4C" = V4C.make -@warning("-32") and \"NoPropsWithKey$V4C$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt index b0b191ffde8..eaa9e31be87 100644 --- a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt @@ -19,4 +19,3 @@ module User = { } } @warning("-32") let \"OptimizeAutomaticMode$User" = User.make -@warning("-32") and \"OptimizeAutomaticMode$User$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt index 3320905230f..4373e3c5627 100644 --- a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt @@ -48,10 +48,6 @@ module Async = { } } @warning("-32") let \"ReturnConstraint$Standard" = Standard.make -@warning("-32") and \"ReturnConstraint$Standard$jsx" = true @warning("-32") let \"ReturnConstraint$ForwardRef" = ForwardRef.make -@warning("-32") and \"ReturnConstraint$ForwardRef$jsx" = true @warning("-32") let \"ReturnConstraint$WithProps" = WithProps.make -@warning("-32") and \"ReturnConstraint$WithProps$jsx" = true @warning("-32") let \"ReturnConstraint$Async" = Async.make -@warning("-32") and \"ReturnConstraint$Async$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt index 777b9cff874..d00313b8992 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt @@ -68,18 +68,10 @@ module V4A8 = { external make: React.component = "default" } @warning("-32") let \"SharedProps$V4A1" = V4A1.make -@warning("-32") and \"SharedProps$V4A1$jsx" = true @warning("-32") let \"SharedProps$V4A2" = V4A2.make -@warning("-32") and \"SharedProps$V4A2$jsx" = true @warning("-32") let \"SharedProps$V4A3" = V4A3.make -@warning("-32") and \"SharedProps$V4A3$jsx" = true @warning("-32") let \"SharedProps$V4A4" = V4A4.make -@warning("-32") and \"SharedProps$V4A4$jsx" = true @warning("-32") let \"SharedProps$V4A5" = V4A5.make -@warning("-32") and \"SharedProps$V4A5$jsx" = true @warning("-32") let \"SharedProps$V4A6" = V4A6.make -@warning("-32") and \"SharedProps$V4A6$jsx" = true @warning("-32") let \"SharedProps$V4A7" = V4A7.make -@warning("-32") and \"SharedProps$V4A7$jsx" = true @warning("-32") let \"SharedProps$V4A8" = V4A8.make -@warning("-32") and \"SharedProps$V4A8$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt index 7674ec9fc3a..84be2b20b63 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt @@ -50,18 +50,10 @@ module V4A4: { let make: React.component } let \"SharedProps$V4C1": React.component -let \"SharedProps$V4C1$jsx": bool let \"SharedProps$V4C2": React.component> -let \"SharedProps$V4C2$jsx": bool let \"SharedProps$V4C3": React.component> -let \"SharedProps$V4C3$jsx": bool let \"SharedProps$V4C4": React.component -let \"SharedProps$V4C4$jsx": bool let \"SharedProps$V4A1": React.component -let \"SharedProps$V4A1$jsx": bool let \"SharedProps$V4A2": React.component> -let \"SharedProps$V4A2$jsx": bool let \"SharedProps$V4A3": React.component> -let \"SharedProps$V4A3$jsx": bool let \"SharedProps$V4A4": React.component -let \"SharedProps$V4A4$jsx": bool diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt index 98ea0b39a8f..499d53ff840 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt @@ -77,16 +77,9 @@ module V4A7 = { } } @warning("-32") let \"SharedPropsWithProps$V4A1" = V4A1.make -@warning("-32") and \"SharedPropsWithProps$V4A1$jsx" = true @warning("-32") let \"SharedPropsWithProps$V4A2" = V4A2.make -@warning("-32") and \"SharedPropsWithProps$V4A2$jsx" = true @warning("-32") let \"SharedPropsWithProps$V4A3" = V4A3.make -@warning("-32") and \"SharedPropsWithProps$V4A3$jsx" = true @warning("-32") let \"SharedPropsWithProps$V4A4" = V4A4.make -@warning("-32") and \"SharedPropsWithProps$V4A4$jsx" = true @warning("-32") let \"SharedPropsWithProps$V4A5" = V4A5.make -@warning("-32") and \"SharedPropsWithProps$V4A5$jsx" = true @warning("-32") let \"SharedPropsWithProps$V4A6" = V4A6.make -@warning("-32") and \"SharedPropsWithProps$V4A6$jsx" = true @warning("-32") let \"SharedPropsWithProps$V4A7" = V4A7.make -@warning("-32") and \"SharedPropsWithProps$V4A7$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt index 36a14fdaf62..e85f96b38a6 100644 --- a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt @@ -17,4 +17,4 @@ module V4A = { \"TopLevel$V4A" } } -@warning("-32") let \"TopLevel$V4A" = V4A.make @warning("-32") and \"TopLevel$V4A$jsx" = true +@warning("-32") let \"TopLevel$V4A" = V4A.make diff --git a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt index 30f6be7931d..1618145f5df 100644 --- a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt @@ -15,4 +15,3 @@ module V4A = { } } @warning("-32") let \"TypeConstraint$V4A" = V4A.make -@warning("-32") and \"TypeConstraint$V4A$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt index 8cb02b596d9..e674baa5db6 100644 --- a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt @@ -66,6 +66,4 @@ module Bar = { } } @warning("-32") let \"UncurriedProps$Foo" = Foo.make -@warning("-32") and \"UncurriedProps$Foo$jsx" = true @warning("-32") let \"UncurriedProps$Bar" = Bar.make -@warning("-32") and \"UncurriedProps$Bar$jsx" = true diff --git a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt index 1bd7275a719..2ffed5a8e74 100644 --- a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt @@ -116,10 +116,9 @@ module Rec2 = { } and mm = x => make(x) } -@warning("-32") let \"V4$Uncurried" = Uncurried.make @warning("-32") and \"V4$Uncurried$jsx" = true -@warning("-32") let \"V4$E" = E.make @warning("-32") and \"V4$E$jsx" = true +@warning("-32") let \"V4$Uncurried" = Uncurried.make +@warning("-32") let \"V4$E" = E.make @warning("-32") let \"V4$EUncurried" = EUncurried.make -@warning("-32") and \"V4$EUncurried$jsx" = true -@warning("-32") let \"V4$Rec" = Rec.make @warning("-32") and \"V4$Rec$jsx" = true -@warning("-32") let \"V4$Rec1" = Rec1.make @warning("-32") and \"V4$Rec1$jsx" = true -@warning("-32") let \"V4$Rec2" = Rec2.make @warning("-32") and \"V4$Rec2$jsx" = true +@warning("-32") let \"V4$Rec" = Rec.make +@warning("-32") let \"V4$Rec1" = Rec1.make +@warning("-32") let \"V4$Rec2" = Rec2.make From 450959b73fea8e8ddb3f74a64396480d341f4a80 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 24 Apr 2026 22:14:00 +0200 Subject: [PATCH 32/56] Fix changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efd2dda26da..271d66995d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ #### :boom: Breaking Change -- Change `Intl.Collator.compare` return type from `int` to `Ordering.t` (`float`). https://github.com/rescript-lang/rescript/pull/8289 - Nested React component modules now export only the namespaced component value instead of a public `make` wrapper, and GenType mirrors that new boundary shape. https://github.com/rescript-lang/rescript/pull/8293 #### :eyeglasses: Spec Compliance From 36e6edd99f19b6fc056b37956d30c4fb4c61af7a Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 24 Apr 2026 22:17:42 +0200 Subject: [PATCH 33/56] More test fixes --- .../src/expected/CompletionAttributes.res.txt | 18 ------------------ .../tests/src/expected/CreateInterface.res.txt | 3 --- .../tests/src/expected/JsxV4.res.txt | 3 --- .../typescript-react-example/src/Hooks.res.js | 6 ------ tests/tests/src/ExternalArity.mjs | 3 --- tests/tests/src/jsx_preserve_test.mjs | 6 ------ 6 files changed, 39 deletions(-) diff --git a/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt b/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt index 0145d56593c..1c7c3fb9df5 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionAttributes.res.txt @@ -24,12 +24,6 @@ Resolved opens 1 Stdlib "tags": [], "detail": "Package", "documentation": null - }, { - "label": "./CompletionJsxProps.cmt", - "kind": 4, - "tags": [], - "detail": "Local file", - "documentation": null }, { "label": "./test.json", "kind": 4, @@ -197,12 +191,6 @@ Resolved opens 1 Stdlib "tags": [], "detail": "Package", "documentation": null - }, { - "label": "./CompletionJsxProps.cmt", - "kind": 4, - "tags": [], - "detail": "Local file", - "documentation": null }, { "label": "./test.json", "kind": 4, @@ -228,12 +216,6 @@ Resolved opens 1 Stdlib "tags": [], "detail": "Package", "documentation": null - }, { - "label": "./CompletionJsxProps.cmt", - "kind": 4, - "tags": [], - "detail": "Local file", - "documentation": null }, { "label": "./test.json", "kind": 4, diff --git a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt index 6a135ff1b04..e4c3ca83b04 100644 --- a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt +++ b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt @@ -133,11 +133,8 @@ module OrderedSet: { let make: (~id: module(Belt.Id.Comparable with type identity = 'a and type t = 'b)) => t<'b, 'a> } let CreateInterface$Mod: Mod.props => React.element -let CreateInterface$Mod$jsx: bool let CreateInterface$Memo: React.component> -let CreateInterface$Memo$jsx: bool let CreateInterface$ComponentWithPolyProp: ComponentWithPolyProp.props< [< #large | #small], > => React.element -let CreateInterface$ComponentWithPolyProp$jsx: bool diff --git a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt index 45682c0a53c..0ad883fa86c 100644 --- a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt +++ b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt @@ -33,9 +33,6 @@ module Other: { let make: (~name: string) => React.element } let JsxV4$M4: M4.props => React.element -let JsxV4$M4$jsx: bool let JsxV4$MM: MM.props => React.element -let JsxV4$MM$jsx: bool let JsxV4$Other: Other.props => React.element -let JsxV4$Other$jsx: bool diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js index 2c1ef8d56b1..de565745ba1 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js @@ -204,10 +204,6 @@ let make$1 = Hooks; let $$default = Hooks; -let Hooks$Inner$jsx = true; - -let Hooks$Inner$Inner2$jsx = true; - let Hooks$WithRef = make; export { @@ -222,9 +218,7 @@ export { Fun, WithChildren, Hooks$Inner, - Hooks$Inner$jsx, Hooks$Inner$Inner2, - Hooks$Inner$Inner2$jsx, Hooks$NoProps, Hooks$WithRef, Hooks$RenderPropRequiresConversion, diff --git a/tests/tests/src/ExternalArity.mjs b/tests/tests/src/ExternalArity.mjs index 9f669373f66..d714c53dfc9 100644 --- a/tests/tests/src/ExternalArity.mjs +++ b/tests/tests/src/ExternalArity.mjs @@ -39,14 +39,11 @@ let ReactTest = { let ExternalArity$ReactTest$FormattedMessage = ReactIntl.FormattedMessage; -let ExternalArity$ReactTest$FormattedMessage$jsx = true; - export { f1, f2, FromTypeConstructor, ReactTest, ExternalArity$ReactTest$FormattedMessage, - ExternalArity$ReactTest$FormattedMessage$jsx, } /* test1 Not a pure module */ diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs index 519fde3c5f3..dd02b2c4653 100644 --- a/tests/tests/src/jsx_preserve_test.mjs +++ b/tests/tests/src/jsx_preserve_test.mjs @@ -251,12 +251,8 @@ let Jsx_preserve_test$A = QueryClientProvider; let make$3 = Jsx_preserve_test; -let Jsx_preserve_test$A$jsx = true; - let Jsx_preserve_test$Y$1 = make$1; -let Jsx_preserve_test$Y$jsx = true; - export { _single_element_child, _multiple_element_children, @@ -287,11 +283,9 @@ export { make$3 as make, Jsx_preserve_test$Icon, Jsx_preserve_test$A, - Jsx_preserve_test$A$jsx, Jsx_preserve_test$B, Jsx_preserve_test$MyWeirdComponent, Jsx_preserve_test$ComponentWithOptionalProps, Jsx_preserve_test$Y$1 as Jsx_preserve_test$Y, - Jsx_preserve_test$Y$jsx, } /* _single_element_child Not a pure module */ From 467fcf5fd6ced729bbbc441e67ab55f85a08ccd6 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 24 Apr 2026 22:27:33 +0200 Subject: [PATCH 34/56] Fix absurd preserved JSX --- compiler/core/js_of_lam_block.ml | 4 +++- tests/tests/src/jsx_preserve_test.mjs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/core/js_of_lam_block.ml b/compiler/core/js_of_lam_block.ml index bfc943d99f5..981bf1a231c 100644 --- a/compiler/core/js_of_lam_block.ml +++ b/compiler/core/js_of_lam_block.ml @@ -43,7 +43,9 @@ let field (field_info : Lam_compat.field_dbg_info) e (i : int32) = | Fld_cons -> E.cons_access e i | Fld_record_inline {name} -> E.inline_record_access e name i | Fld_record {name} -> E.record_access e name i - | Fld_module {name; jsx_component = _} -> E.module_access e name i + | Fld_module {name; jsx_component = true} -> + if !Js_config.jsx_preserve then E.dot e name else E.module_access e name i + | Fld_module {name; jsx_component = false} -> E.module_access e name i let field_by_exp e i = E.array_index e i diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs index dd02b2c4653..81c9b66ecc3 100644 --- a/tests/tests/src/jsx_preserve_test.mjs +++ b/tests/tests/src/jsx_preserve_test.mjs @@ -229,7 +229,7 @@ let Y = { make: make$1 }; -<42 />; +; let context = React.createContext(0); From 23530663a49bf0defdc02c56130415ef94afc33c Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Fri, 24 Apr 2026 23:02:50 +0200 Subject: [PATCH 35/56] Refactor --- .../core/js_pass_nested_component_exports.ml | 96 +++++++------- compiler/core/lam_compile.ml | 117 ++++++------------ 2 files changed, 87 insertions(+), 126 deletions(-) diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml index 760bc8ed695..b3b32757708 100644 --- a/compiler/core/js_pass_nested_component_exports.ml +++ b/compiler/core/js_pass_nested_component_exports.ml @@ -28,30 +28,6 @@ module StringSet = Set.Make (String) type candidate = {module_ident: Ident.t} -let dynamic_import_module_root (expr : J.expression) = - match expr.expression_desc with - | Await - { - expression_desc = - Call ({expression_desc = Var (Id import_ident); _}, [arg], _); - _; - } - when String.equal import_ident.name "import" -> ( - match arg.expression_desc with - | Str {txt; _} -> - let basename = Filename.basename txt in - let suffixes = [".res.mjs"; ".res.js"; ".mjs"; ".js"] in - let rec strip_suffix = function - | [] -> basename - | suffix :: rest -> - if Filename.check_suffix basename suffix then - Filename.chop_suffix basename suffix - else strip_suffix rest - in - Some (strip_suffix suffixes) - | _ -> None) - | _ -> None - let hidden_component_suffix (module_ident : Ident.t) = "$" ^ Ident.name module_ident @@ -117,23 +93,48 @@ let hidden_export_names_to_remove candidates = Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> StringSet.add (Ident.name candidate.module_ident) acc) -let candidate_by_module_ident candidates module_ident = - List.find_map - (fun candidate -> - if Ident.same candidate.module_ident module_ident then Some candidate - else None) - candidates +let dynamic_import_module_root (expr : J.expression) = + match expr.expression_desc with + | Await + { + expression_desc = + Call ({expression_desc = Var (Id import_ident); _}, [arg], _); + _; + } + when String.equal import_ident.name "import" -> ( + match arg.expression_desc with + | Str {txt; _} -> + let basename = Filename.basename txt in + let suffixes = [".res.mjs"; ".res.js"; ".mjs"; ".js"] in + let rec strip_suffix = function + | [] -> basename + | suffix :: rest -> ( + match Ext_string.ends_with_then_chop basename suffix with + | Some basename -> basename + | None -> strip_suffix rest) + in + Some (strip_suffix suffixes) + | _ -> None) + | _ -> None -let rewrite_block block candidates = - List.concat_map - (fun (st : J.statement) -> - match st.statement_desc with - | Variable {ident; _} -> ( - match candidate_by_module_ident candidates ident with - | Some _ -> [st] - | None -> [st]) - | _ -> [st]) - block +let known_hidden_component_exports block = + let hidden_exports = ref StringSet.empty in + let mapper = + { + Js_record_map.super with + expression = + (fun self expr -> + (match expr.expression_desc with + | Var (Qualified (_, Some name)) -> + hidden_exports := StringSet.add name !hidden_exports + | Static_index (_, name, _) when String.contains name '$' -> + hidden_exports := StringSet.add name !hidden_exports + | _ -> ()); + Js_record_map.super.expression self expr); + } + in + ignore (mapper.block mapper block); + !hidden_exports let dynamic_import_aliases block = List.fold_left @@ -146,7 +147,8 @@ let dynamic_import_aliases block = | _ -> aliases) Map_ident.empty block -let rewrite_dynamic_import_component_access aliases (expr : J.expression) = +let rewrite_dynamic_import_component_access aliases known_hidden_exports + (expr : J.expression) = let rec collect_segments segments (expr : J.expression) = match expr.expression_desc with | Static_index (inner, field, _) -> @@ -168,7 +170,9 @@ let rewrite_dynamic_import_component_access aliases (expr : J.expression) = String.concat "$" segments | _ -> String.concat "$" (module_root :: segments) in - {expr with expression_desc = Static_index (E.var id, hidden_name, None)} + if StringSet.mem hidden_name known_hidden_exports then + {expr with expression_desc = Static_index (E.var id, hidden_name, None)} + else expr | None -> expr) | _ -> expr @@ -176,13 +180,15 @@ let rewrite_dynamic_import_block block = let aliases = dynamic_import_aliases block in if Map_ident.is_empty aliases then block else + let known_hidden_exports = known_hidden_component_exports block in let mapper = { Js_record_map.super with expression = (fun self expr -> let expr = Js_record_map.super.expression self expr in - rewrite_dynamic_import_component_access aliases expr); + rewrite_dynamic_import_component_access aliases known_hidden_exports + expr); } in mapper.block mapper block @@ -195,7 +201,5 @@ let program (js : J.program) : J.program = not (StringSet.mem (Ident.name ident) removed_export_names)) in let export_set = Set_ident.of_list exports in - let block = - rewrite_dynamic_import_block (rewrite_block js.block candidates) - in + let block = rewrite_dynamic_import_block js.block in {J.block; exports; export_set} diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 6b73a8e74b3..2882c724aaf 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -241,8 +241,34 @@ let compile output_prefix = | Some index -> String.sub id.name 0 index | None -> id.name) in - let rec extract_nested_external_component_segments segments - ((lam : Lam.t), (make_dynamic_import : bool option ref)) : + let nested_component_path id dynamic_import segments = + let root_name = root_module_name id in + let denamespace_segment segment = + let namespaced_prefix = root_name ^ "$" in + if Ext_string.starts_with segment namespaced_prefix then + match String.split_on_char '$' segment with + | root :: _namespace :: rest when rest <> [] -> + String.concat "$" (root :: rest) + | _ -> segment + else segment + in + let segments = + match segments with + | head :: rest + when head = id.name || head = root_name + || Ext_string.starts_with head (root_name ^ "$") -> + rest + | _ -> segments + in + match segments with + | [] -> None + | head :: rest -> + Some + ( id, + dynamic_import, + String.concat "$" (root_name :: denamespace_segment head :: rest) ) + in + let rec extract_nested_external_component_segments segments (lam : Lam.t) : (Ident.t * bool * string list) option = match lam with | Lprim @@ -251,21 +277,15 @@ let compile output_prefix = args = [arg]; _; } -> - extract_nested_external_component_segments (name :: segments) - (arg, make_dynamic_import) + extract_nested_external_component_segments (name :: segments) arg | Lprim {primitive = Pawait; args = [arg]; _} -> - extract_nested_external_component_segments segments - (arg, make_dynamic_import) + extract_nested_external_component_segments segments arg | Lvar id -> ( match Map_ident.find_opt !local_module_aliases id with | Some alias_lam -> - extract_nested_external_component_segments segments - (alias_lam, make_dynamic_import) - | None -> - make_dynamic_import := Some false; - Some (id, false, List.rev segments)) + extract_nested_external_component_segments segments alias_lam + | None -> Some (id, false, List.rev segments)) | Lglobal_module (id, dynamic_import) -> - make_dynamic_import := Some dynamic_import; Some (id, dynamic_import, List.rev segments) | _ -> None in @@ -294,42 +314,9 @@ let compile output_prefix = in let extract_nested_external_component_path (lam : Lam.t) : (Ident.t * bool * string) option = - let dynamic_import = ref None in - match - extract_nested_external_component_segments [] (lam, dynamic_import) - with - | Some (id, dynamic_import, segments) -> ( - let denamespace_segment segment = - let root_name = root_module_name id in - let namespaced_prefix = root_name ^ "$" in - if Ext_string.starts_with segment namespaced_prefix then - match String.split_on_char '$' segment with - | root :: _namespace :: rest when rest <> [] -> - String.concat "$" (root :: rest) - | _ -> segment - else segment - in - let segments = - match segments with - | head :: rest - when head = id.name - || head = root_module_name id - || Ext_string.starts_with head (root_module_name id ^ "$") -> - rest - | _ -> segments - in - let segments = - match segments with - | head :: rest -> denamespace_segment head :: rest - | [] -> [] - in - match segments with - | [] -> None - | _ -> - Some - ( id, - dynamic_import, - String.concat "$" (root_module_name id :: segments) )) + match extract_nested_external_component_segments [] lam with + | Some (id, dynamic_import, segments) -> + nested_component_path id dynamic_import segments | None -> None in let extract_nested_external_component_field (lam : Lam.t) : @@ -347,38 +334,8 @@ let compile output_prefix = let extract_static_nested_external_component_path (lam : Lam.t) : (Ident.t * bool * string) option = match extract_static_nested_external_component_segments [] lam with - | Some (id, dynamic_import, segments) -> ( - let denamespace_segment segment = - let root_name = root_module_name id in - let namespaced_prefix = root_name ^ "$" in - if Ext_string.starts_with segment namespaced_prefix then - match String.split_on_char '$' segment with - | root :: _namespace :: rest when rest <> [] -> - String.concat "$" (root :: rest) - | _ -> segment - else segment - in - let segments = - match segments with - | head :: rest - when head = id.name - || head = root_module_name id - || Ext_string.starts_with head (root_module_name id ^ "$") -> - rest - | _ -> segments - in - let segments = - match segments with - | head :: rest -> denamespace_segment head :: rest - | [] -> [] - in - match segments with - | [] -> None - | _ -> - Some - ( id, - dynamic_import, - String.concat "$" (root_module_name id :: segments) )) + | Some (id, dynamic_import, segments) -> + nested_component_path id dynamic_import segments | None -> None in let normalize_hidden_component_name (id : Ident.t) (hidden_name : string) = From 7f0d42c730bed2a8e6caabec4418db4b14421ade Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Sat, 25 Apr 2026 00:16:02 +0200 Subject: [PATCH 36/56] Some more refactoring --- compiler/core/lam_compile.ml | 48 ++++++++++++++---------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 2882c724aaf..7d1a1d4c201 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -268,28 +268,7 @@ let compile output_prefix = dynamic_import, String.concat "$" (root_name :: denamespace_segment head :: rest) ) in - let rec extract_nested_external_component_segments segments (lam : Lam.t) : - (Ident.t * bool * string list) option = - match lam with - | Lprim - { - primitive = Pfield (_, Fld_module {name; jsx_component = _}); - args = [arg]; - _; - } -> - extract_nested_external_component_segments (name :: segments) arg - | Lprim {primitive = Pawait; args = [arg]; _} -> - extract_nested_external_component_segments segments arg - | Lvar id -> ( - match Map_ident.find_opt !local_module_aliases id with - | Some alias_lam -> - extract_nested_external_component_segments segments alias_lam - | None -> Some (id, false, List.rev segments)) - | Lglobal_module (id, dynamic_import) -> - Some (id, dynamic_import, List.rev segments) - | _ -> None - in - let rec extract_static_nested_external_component_segments segments + let rec extract_component_segments ~allow_import ~allow_unbound_var segments (lam : Lam.t) : (Ident.t * bool * string list) option = match lam with | Lprim @@ -298,23 +277,29 @@ let compile output_prefix = args = [arg]; _; } -> - extract_static_nested_external_component_segments (name :: segments) arg + extract_component_segments ~allow_import ~allow_unbound_var + (name :: segments) arg | Lprim {primitive = Pawait; args = [arg]; _} -> - extract_static_nested_external_component_segments segments arg - | Lprim {primitive = Pimport; args = [arg]; _} -> - extract_static_nested_external_component_segments segments arg + extract_component_segments ~allow_import ~allow_unbound_var segments arg + | Lprim {primitive = Pimport; args = [arg]; _} when allow_import -> + extract_component_segments ~allow_import ~allow_unbound_var segments arg | Lvar id -> ( match Map_ident.find_opt !local_module_aliases id with | Some alias_lam -> - extract_static_nested_external_component_segments segments alias_lam - | None -> None) + extract_component_segments ~allow_import ~allow_unbound_var segments + alias_lam + | None -> + if allow_unbound_var then Some (id, false, List.rev segments) else None) | Lglobal_module (id, dynamic_import) -> Some (id, dynamic_import, List.rev segments) | _ -> None in let extract_nested_external_component_path (lam : Lam.t) : (Ident.t * bool * string) option = - match extract_nested_external_component_segments [] lam with + match + extract_component_segments ~allow_import:false ~allow_unbound_var:true [] + lam + with | Some (id, dynamic_import, segments) -> nested_component_path id dynamic_import segments | None -> None @@ -333,7 +318,10 @@ let compile output_prefix = in let extract_static_nested_external_component_path (lam : Lam.t) : (Ident.t * bool * string) option = - match extract_static_nested_external_component_segments [] lam with + match + extract_component_segments ~allow_import:true ~allow_unbound_var:false [] + lam + with | Some (id, dynamic_import, segments) -> nested_component_path id dynamic_import segments | None -> None From c89fd5b79566c6387d97d87c060e0b3d7147b832 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 00:46:58 +0200 Subject: [PATCH 37/56] Fix tests --- .../src/abstract_component_test.res.js | 4 ++-- .../react_ppx/src/gpr_3987_test.res.js | 2 +- .../src/recursive_component_test.res.js | 2 -- .../recursive_explicit_component_test.res.js | 5 +++-- .../src/React.res | 4 +++- .../src/React.res | 4 +++- .../src/React.res | 4 +++- .../src/React.res | 4 +++- .../rsc_mixed_runtime_import/src/React.res | 4 +++- .../rsc_nested_jsx_alias_chain/input.js | 4 ---- .../src/PlainAccess.res | 2 -- .../rsc_nested_jsx_alias_chain/src/React.res | 4 +++- .../rsc_nested_jsx_deep/src/React.res | 4 +++- .../src/React.res | 4 +++- .../rsc_nested_jsx_members/input.js | 4 ---- .../src/PlainAccess.res | 2 -- .../rsc_nested_jsx_members/src/React.res | 4 +++- .../src/React.res | 4 +++- .../rsc_nested_jsx_warn_error/src/React.res | 4 +++- .../rsc_suffix_runtime_import/src/React.res | 4 +++- ...component_prop_plain_function.res.expected | 19 ------------------- .../jsx_plain_function_component.res.expected | 19 ------------------- ...te_element_requires_component.res.expected | 18 ------------------ .../jsx_component_prop_plain_function.res | 2 +- 24 files changed, 43 insertions(+), 88 deletions(-) diff --git a/tests/build_tests/react_ppx/src/abstract_component_test.res.js b/tests/build_tests/react_ppx/src/abstract_component_test.res.js index 8863c77600f..2091b800b26 100644 --- a/tests/build_tests/react_ppx/src/abstract_component_test.res.js +++ b/tests/build_tests/react_ppx/src/abstract_component_test.res.js @@ -25,7 +25,7 @@ let GenericRenderProp = { }; export { - PolyVariantLowerBound, - GenericRenderProp, + Abstract_component_test$PolyVariantLowerBound, + Abstract_component_test$GenericRenderProp, } /* No side effect */ diff --git a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js index bed24550d34..1d0586eff6c 100644 --- a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js @@ -57,7 +57,7 @@ JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError, { export { makeContainer, - Gpr3987ReproOk, + Gpr_3987_test$Gpr3987ReproOk, Gpr_3987_test$Gpr3987ReproOk2, Gpr_3987_test$Gpr3987ReproError, } diff --git a/tests/build_tests/react_ppx/src/recursive_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_component_test.res.js index c778221ccb2..9a03762e55e 100644 --- a/tests/build_tests/react_ppx/src/recursive_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_component_test.res.js @@ -20,8 +20,6 @@ let Rec = { make: Recursive_component_test$Rec }; -let Recursive_component_test$Rec = make; - export { Rec, Recursive_component_test$Rec, diff --git a/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js index 9577ffd28ab..b0f675b80af 100644 --- a/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js @@ -50,9 +50,10 @@ let componentWithPropsElement = React.createElement(Recursive_explicit_component }); export { - SelfCreateElement, RawSiblingCreateElement, - ComponentWithProps, componentWithPropsElement, + Recursive_explicit_component_test$SelfCreateElement, + Recursive_explicit_component_test$RawSiblingCreateElement, + Recursive_explicit_component_test$ComponentWithProps, } /* componentWithPropsElement Not a pure module */ diff --git a/tests/build_tests/rsc_component_with_props_members/src/React.res b/tests/build_tests/rsc_component_with_props_members/src/React.res index 733b8ee02f0..243ed2acd91 100644 --- a/tests/build_tests/rsc_component_with_props_members/src/React.res +++ b/tests/build_tests/rsc_component_with_props_members/src/React.res @@ -4,7 +4,9 @@ type element type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/src/React.res b/tests/build_tests/rsc_component_with_props_members_no_namespace/src/React.res index 733b8ee02f0..243ed2acd91 100644 --- a/tests/build_tests/rsc_component_with_props_members_no_namespace/src/React.res +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/src/React.res @@ -4,7 +4,9 @@ type element type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_component_with_props_nested/src/React.res b/tests/build_tests/rsc_component_with_props_nested/src/React.res index 75e3b2c2d78..09509f0643a 100644 --- a/tests/build_tests/rsc_component_with_props_nested/src/React.res +++ b/tests/build_tests/rsc_component_with_props_nested/src/React.res @@ -6,7 +6,9 @@ external string: string => element = "%identity" type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/src/React.res b/tests/build_tests/rsc_dynamic_import_nested_jsx/src/React.res index 75e3b2c2d78..09509f0643a 100644 --- a/tests/build_tests/rsc_dynamic_import_nested_jsx/src/React.res +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/src/React.res @@ -6,7 +6,9 @@ external string: string => element = "%identity" type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_mixed_runtime_import/src/React.res b/tests/build_tests/rsc_mixed_runtime_import/src/React.res index 733b8ee02f0..243ed2acd91 100644 --- a/tests/build_tests/rsc_mixed_runtime_import/src/React.res +++ b/tests/build_tests/rsc_mixed_runtime_import/src/React.res @@ -4,7 +4,9 @@ type element type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js index 504abfd4c8b..6a238a6f9ff 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -28,10 +28,6 @@ assert.match( plainAccessOutput, /let provider = Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider;/, ); -assert.match( - plainAccessOutput, - /let callProvider = Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider\(\{/, -); assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); assert.doesNotMatch( plainAccessOutput, diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/src/PlainAccess.res b/tests/build_tests/rsc_nested_jsx_alias_chain/src/PlainAccess.res index 73b07478a5c..b2771f6252f 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/src/PlainAccess.res +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/src/PlainAccess.res @@ -2,5 +2,3 @@ module SidebarAlias = Sidebar module ProviderAlias = SidebarAlias.Provider let provider = ProviderAlias.make - -let callProvider = ProviderAlias.make({children: React.null}) diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/src/React.res b/tests/build_tests/rsc_nested_jsx_alias_chain/src/React.res index 733b8ee02f0..243ed2acd91 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/src/React.res +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/src/React.res @@ -4,7 +4,9 @@ type element type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_deep/src/React.res b/tests/build_tests/rsc_nested_jsx_deep/src/React.res index 733b8ee02f0..243ed2acd91 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/src/React.res +++ b/tests/build_tests/rsc_nested_jsx_deep/src/React.res @@ -4,7 +4,9 @@ type element type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/React.res b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/React.res index 733b8ee02f0..243ed2acd91 100644 --- a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/React.res +++ b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/src/React.res @@ -4,7 +4,9 @@ type element type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 33960dfcb18..7d08d73cfa9 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -91,10 +91,6 @@ assert.match( plainAccessOutput, /let provider = Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider;/, ); -assert.match( - plainAccessOutput, - /let callProvider = Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider\(\{/, -); assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); assert.doesNotMatch( plainAccessOutput, diff --git a/tests/build_tests/rsc_nested_jsx_members/src/PlainAccess.res b/tests/build_tests/rsc_nested_jsx_members/src/PlainAccess.res index 50ad67b4bb5..bbab6dcb6ff 100644 --- a/tests/build_tests/rsc_nested_jsx_members/src/PlainAccess.res +++ b/tests/build_tests/rsc_nested_jsx_members/src/PlainAccess.res @@ -1,3 +1 @@ let provider = Sidebar.Provider.make - -let callProvider = Sidebar.Provider.make({children: React.null}) diff --git a/tests/build_tests/rsc_nested_jsx_members/src/React.res b/tests/build_tests/rsc_nested_jsx_members/src/React.res index 0a2d41f0096..06cba8f2824 100644 --- a/tests/build_tests/rsc_nested_jsx_members/src/React.res +++ b/tests/build_tests/rsc_nested_jsx_members/src/React.res @@ -8,7 +8,9 @@ external array: array => element = "%identity" type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react") external createElement: (component<'props>, 'props) => element = "createElement" diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/React.res b/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/React.res index 733b8ee02f0..243ed2acd91 100644 --- a/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/React.res +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/src/React.res @@ -4,7 +4,9 @@ type element type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_nested_jsx_warn_error/src/React.res b/tests/build_tests/rsc_nested_jsx_warn_error/src/React.res index 733b8ee02f0..243ed2acd91 100644 --- a/tests/build_tests/rsc_nested_jsx_warn_error/src/React.res +++ b/tests/build_tests/rsc_nested_jsx_warn_error/src/React.res @@ -4,7 +4,9 @@ type element type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/rsc_suffix_runtime_import/src/React.res b/tests/build_tests/rsc_suffix_runtime_import/src/React.res index 75e3b2c2d78..09509f0643a 100644 --- a/tests/build_tests/rsc_suffix_runtime_import/src/React.res +++ b/tests/build_tests/rsc_suffix_runtime_import/src/React.res @@ -6,7 +6,9 @@ external string: string => element = "%identity" type componentLike<'props, 'return> = 'props => 'return -type component<'props> = componentLike<'props, element> +type component<'props> + +external component: componentLike<'props, element> => component<'props> = "%component_identity" @module("react/jsx-runtime") external jsx: (component<'props>, 'props) => element = "jsx" diff --git a/tests/build_tests/super_errors/expected/jsx_component_prop_plain_function.res.expected b/tests/build_tests/super_errors/expected/jsx_component_prop_plain_function.res.expected index 9258619424b..e69de29bb2d 100644 --- a/tests/build_tests/super_errors/expected/jsx_component_prop_plain_function.res.expected +++ b/tests/build_tests/super_errors/expected/jsx_component_prop_plain_function.res.expected @@ -1,19 +0,0 @@ - - We've found a bug for you! - /.../fixtures/jsx_component_prop_plain_function.res:19:46-58 - - 17 │ } - 18 │ - 19 │ let _ = React.null} /> - 20 │ - - This has type: 'a => 'b - But it's expected to have type: - React.component (defined as - Jsx.component) - - A React component is expected here, but this expression is a plain function. - - Possible solutions: - - Extract it to a component annotated with @react.component or @react.componentWithProps - - If this is already a valid component-like value, wrap it with React.component(...) \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/jsx_plain_function_component.res.expected b/tests/build_tests/super_errors/expected/jsx_plain_function_component.res.expected index c4c1c500e29..e69de29bb2d 100644 --- a/tests/build_tests/super_errors/expected/jsx_plain_function_component.res.expected +++ b/tests/build_tests/super_errors/expected/jsx_plain_function_component.res.expected @@ -1,19 +0,0 @@ - - We've found a bug for you! - /.../fixtures/jsx_plain_function_component.res:26:10-16 - - 24 │ } - 25 │ - 26 │ let _ = <Wrapper value="hello" /> - 27 │ - - This JSX tag has type: Wrapper.props => Jsx.element - But JSX component positions require: - React.component<'a> (defined as Jsx.component<'a>) - - JSX tags must be React components, not plain functions. - - Possible solutions: - - If this function takes labeled props, annotate it with @react.component - - If this function takes a single props record, annotate it with @react.componentWithProps - - If this is already a valid component-like value, wrap it with React.component(...) \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/recursive_component_create_element_requires_component.res.expected b/tests/build_tests/super_errors/expected/recursive_component_create_element_requires_component.res.expected index f531ae3fbb0..e69de29bb2d 100644 --- a/tests/build_tests/super_errors/expected/recursive_component_create_element_requires_component.res.expected +++ b/tests/build_tests/super_errors/expected/recursive_component_create_element_requires_component.res.expected @@ -1,18 +0,0 @@ - - We've found a bug for you! - /.../fixtures/recursive_component_create_element_requires_component.res:13:46-49 - - 11 │ - 12 │ @react.component - 13 │ let rec make = (~foo) => React.createElement(make, {foo: foo}) - 14 │ - - This has type: props<'a> => React.element - But this function argument is expecting: - React.component<'b> (defined as Jsx.component<'b>) - - A React component is expected here, but this expression is a plain function. - - Possible solutions: - - Extract it to a component annotated with @react.component or @react.componentWithProps - - If this is already a valid component-like value, wrap it with React.component(...) \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/jsx_component_prop_plain_function.res b/tests/build_tests/super_errors/fixtures/jsx_component_prop_plain_function.res index 39b4e47d5fb..86a1282d66a 100644 --- a/tests/build_tests/super_errors/fixtures/jsx_component_prop_plain_function.res +++ b/tests/build_tests/super_errors/fixtures/jsx_component_prop_plain_function.res @@ -13,7 +13,7 @@ module List = { type separatorProps = {index: int} @react.component - let make = (~itemSeparatorComponent: React.component) => React.null + let make = (~itemSeparatorComponent as _: React.component) => React.null } let _ = React.null} /> From f683a841411d0dc6c8afa6e5bc0f5b53d800edeb Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 00:50:19 +0200 Subject: [PATCH 38/56] Fix snapshots --- ...component_prop_plain_function.res.expected | 19 +++++++++++++++++++ .../jsx_plain_function_component.res.expected | 19 +++++++++++++++++++ ...te_element_requires_component.res.expected | 18 ++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/tests/build_tests/super_errors/expected/jsx_component_prop_plain_function.res.expected b/tests/build_tests/super_errors/expected/jsx_component_prop_plain_function.res.expected index e69de29bb2d..9258619424b 100644 --- a/tests/build_tests/super_errors/expected/jsx_component_prop_plain_function.res.expected +++ b/tests/build_tests/super_errors/expected/jsx_component_prop_plain_function.res.expected @@ -0,0 +1,19 @@ + + We've found a bug for you! + /.../fixtures/jsx_component_prop_plain_function.res:19:46-58 + + 17 │ } + 18 │ + 19 │ let _ = React.null} /> + 20 │ + + This has type: 'a => 'b + But it's expected to have type: + React.component (defined as + Jsx.component) + + A React component is expected here, but this expression is a plain function. + + Possible solutions: + - Extract it to a component annotated with @react.component or @react.componentWithProps + - If this is already a valid component-like value, wrap it with React.component(...) \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/jsx_plain_function_component.res.expected b/tests/build_tests/super_errors/expected/jsx_plain_function_component.res.expected index e69de29bb2d..c4c1c500e29 100644 --- a/tests/build_tests/super_errors/expected/jsx_plain_function_component.res.expected +++ b/tests/build_tests/super_errors/expected/jsx_plain_function_component.res.expected @@ -0,0 +1,19 @@ + + We've found a bug for you! + /.../fixtures/jsx_plain_function_component.res:26:10-16 + + 24 │ } + 25 │ + 26 │ let _ = <Wrapper value="hello" /> + 27 │ + + This JSX tag has type: Wrapper.props => Jsx.element + But JSX component positions require: + React.component<'a> (defined as Jsx.component<'a>) + + JSX tags must be React components, not plain functions. + + Possible solutions: + - If this function takes labeled props, annotate it with @react.component + - If this function takes a single props record, annotate it with @react.componentWithProps + - If this is already a valid component-like value, wrap it with React.component(...) \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/recursive_component_create_element_requires_component.res.expected b/tests/build_tests/super_errors/expected/recursive_component_create_element_requires_component.res.expected index e69de29bb2d..f531ae3fbb0 100644 --- a/tests/build_tests/super_errors/expected/recursive_component_create_element_requires_component.res.expected +++ b/tests/build_tests/super_errors/expected/recursive_component_create_element_requires_component.res.expected @@ -0,0 +1,18 @@ + + We've found a bug for you! + /.../fixtures/recursive_component_create_element_requires_component.res:13:46-49 + + 11 │ + 12 │ @react.component + 13 │ let rec make = (~foo) => React.createElement(make, {foo: foo}) + 14 │ + + This has type: props<'a> => React.element + But this function argument is expecting: + React.component<'b> (defined as Jsx.component<'b>) + + A React component is expected here, but this expression is a plain function. + + Possible solutions: + - Extract it to a component annotated with @react.component or @react.componentWithProps + - If this is already a valid component-like value, wrap it with React.component(...) \ No newline at end of file From 1f66b1d79221f62e67f5799d177af74613b901d6 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 09:54:58 +0200 Subject: [PATCH 39/56] More snapshots --- .../tests/src/expected/CreateInterface.res.txt | 8 ++++---- tests/analysis_tests/tests/src/expected/JsxV4.res.txt | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt index e4c3ca83b04..36420aabca2 100644 --- a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt +++ b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt @@ -132,9 +132,9 @@ module OrderedSet: { } let make: (~id: module(Belt.Id.Comparable with type identity = 'a and type t = 'b)) => t<'b, 'a> } -let CreateInterface$Mod: Mod.props => React.element +let CreateInterface$Mod: React.component> let CreateInterface$Memo: React.component> -let CreateInterface$ComponentWithPolyProp: ComponentWithPolyProp.props< - [< #large | #small], -> => React.element +let CreateInterface$ComponentWithPolyProp: React.component< + ComponentWithPolyProp.props<[< #large | #small]>, +> diff --git a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt index c2bb8fa9e8a..c9854d2f5e3 100644 --- a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt +++ b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt @@ -32,7 +32,7 @@ module Other: { @react.component let make: (~name: string) => React.element } -let JsxV4$M4: M4.props => React.element -let JsxV4$MM: MM.props => React.element -let JsxV4$Other: Other.props => React.element +let JsxV4$M4: React.component> +let JsxV4$MM: React.component +let JsxV4$Other: React.component> From 3bdd0fec9e3149b736faeb703a7739b7715aca4a Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 10:33:27 +0200 Subject: [PATCH 40/56] Even more snapshots --- rewatch/tests/snapshots/clean-rebuild.txt | 1 - rewatch/tests/snapshots/dependency-cycle.txt | 4 ++-- .../tests/snapshots/dev-dependency-used-by-non-dev-source.txt | 4 ++-- rewatch/tests/snapshots/remove-file.txt | 4 ++-- .../tests/snapshots/rename-file-internal-dep-namespace.txt | 4 ++-- rewatch/tests/snapshots/rename-file-internal-dep.txt | 4 ++-- rewatch/tests/snapshots/rename-file-with-interface.txt | 4 ++-- rewatch/tests/snapshots/rename-file.txt | 1 - rewatch/tests/snapshots/rename-interface-file.txt | 4 ++-- 9 files changed, 14 insertions(+), 16 deletions(-) diff --git a/rewatch/tests/snapshots/clean-rebuild.txt b/rewatch/tests/snapshots/clean-rebuild.txt index 5d6736b3b05..726edeff7f3 100644 --- a/rewatch/tests/snapshots/clean-rebuild.txt +++ b/rewatch/tests/snapshots/clean-rebuild.txt @@ -25,7 +25,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): diff --git a/rewatch/tests/snapshots/dependency-cycle.txt b/rewatch/tests/snapshots/dependency-cycle.txt index eca324b9ed8..d9fd8965a7b 100644 --- a/rewatch/tests/snapshots/dependency-cycle.txt +++ b/rewatch/tests/snapshots/dependency-cycle.txt @@ -25,7 +25,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -41,4 +40,5 @@ Dep01 (packages/dep01/src/Dep01.res) Possible solutions: - Extract shared code into a new module both depend on. -Incremental build failed. Error:  Failed to Compile. See Errors Above +Incremental build failed. Error:  + Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/dev-dependency-used-by-non-dev-source.txt b/rewatch/tests/snapshots/dev-dependency-used-by-non-dev-source.txt index 3f5bf7f5404..d0536dc66ad 100644 --- a/rewatch/tests/snapshots/dev-dependency-used-by-non-dev-source.txt +++ b/rewatch/tests/snapshots/dev-dependency-used-by-non-dev-source.txt @@ -25,7 +25,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -46,4 +45,5 @@ Please report this to the package maintainer: https://github.com/DZakh/sury/issu -Incremental build failed. Error:  Failed to Compile. See Errors Above +Incremental build failed. Error:  + Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/remove-file.txt b/rewatch/tests/snapshots/remove-file.txt index ff4d3970ac4..726d8023154 100644 --- a/rewatch/tests/snapshots/remove-file.txt +++ b/rewatch/tests/snapshots/remove-file.txt @@ -25,7 +25,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -48,4 +47,5 @@ Please report this to the package maintainer: https://github.com/DZakh/sury/issu -Incremental build failed. Error:  Failed to Compile. See Errors Above +Incremental build failed. Error:  + Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt b/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt index 6e0311b7589..d32b92d0d5d 100644 --- a/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt +++ b/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt @@ -25,7 +25,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -48,4 +47,5 @@ Please report this to the package maintainer: https://github.com/DZakh/sury/issu Hint: Did you mean Other_module2? -Incremental build failed. Error:  Failed to Compile. See Errors Above +Incremental build failed. Error:  + Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-internal-dep.txt b/rewatch/tests/snapshots/rename-file-internal-dep.txt index 68215bf2153..16911fccab8 100644 --- a/rewatch/tests/snapshots/rename-file-internal-dep.txt +++ b/rewatch/tests/snapshots/rename-file-internal-dep.txt @@ -25,7 +25,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -48,4 +47,5 @@ Please report this to the package maintainer: https://github.com/DZakh/sury/issu -Incremental build failed. Error:  Failed to Compile. See Errors Above +Incremental build failed. Error:  + Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-with-interface.txt b/rewatch/tests/snapshots/rename-file-with-interface.txt index f26462d8151..388442b40e2 100644 --- a/rewatch/tests/snapshots/rename-file-with-interface.txt +++ b/rewatch/tests/snapshots/rename-file-with-interface.txt @@ -1,4 +1,5 @@ - No implementation file found for interface file (skipping): src/ModuleWithInterface.resi + + No implementation file found for interface file (skipping): src/ModuleWithInterface.resi Cleaned 2/432 Parsed 2 source files Compiled 2 modules @@ -26,7 +27,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): diff --git a/rewatch/tests/snapshots/rename-file.txt b/rewatch/tests/snapshots/rename-file.txt index 347bc002df3..76af6e8b3ff 100644 --- a/rewatch/tests/snapshots/rename-file.txt +++ b/rewatch/tests/snapshots/rename-file.txt @@ -25,7 +25,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): diff --git a/rewatch/tests/snapshots/rename-interface-file.txt b/rewatch/tests/snapshots/rename-interface-file.txt index b7c694c4c33..23c89d05b8d 100644 --- a/rewatch/tests/snapshots/rename-interface-file.txt +++ b/rewatch/tests/snapshots/rename-interface-file.txt @@ -1,4 +1,5 @@ - No implementation file found for interface file (skipping): src/ModuleWithInterface2.resi + + No implementation file found for interface file (skipping): src/ModuleWithInterface2.resi Cleaned 1/432 Parsed 2 source files Compiled 2 modules @@ -26,7 +27,6 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead - - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): From 76e188f9b32b0f3023f28ce8b44c69fb9a4d2a01 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 11:21:02 +0200 Subject: [PATCH 41/56] Remove unrelated rewatch changes Signed-off-by: Florian Hammerschmidt --- rewatch/tests/snapshots/clean-rebuild.txt | 1 + rewatch/tests/snapshots/dependency-cycle.txt | 4 ++-- .../dev-dependency-used-by-non-dev-source.txt | 4 ++-- rewatch/tests/snapshots/remove-file.txt | 4 ++-- .../rename-file-internal-dep-namespace.txt | 4 ++-- .../snapshots/rename-file-internal-dep.txt | 4 ++-- .../snapshots/rename-file-with-interface.txt | 4 ++-- rewatch/tests/snapshots/rename-file.txt | 1 + .../tests/snapshots/rename-interface-file.txt | 4 ++-- .../watch/06-watch-missing-source-folder.sh | 24 ++----------------- 10 files changed, 18 insertions(+), 36 deletions(-) diff --git a/rewatch/tests/snapshots/clean-rebuild.txt b/rewatch/tests/snapshots/clean-rebuild.txt index 726edeff7f3..5d6736b3b05 100644 --- a/rewatch/tests/snapshots/clean-rebuild.txt +++ b/rewatch/tests/snapshots/clean-rebuild.txt @@ -25,6 +25,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): diff --git a/rewatch/tests/snapshots/dependency-cycle.txt b/rewatch/tests/snapshots/dependency-cycle.txt index d9fd8965a7b..eca324b9ed8 100644 --- a/rewatch/tests/snapshots/dependency-cycle.txt +++ b/rewatch/tests/snapshots/dependency-cycle.txt @@ -25,6 +25,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -40,5 +41,4 @@ Dep01 (packages/dep01/src/Dep01.res) Possible solutions: - Extract shared code into a new module both depend on. -Incremental build failed. Error:  - Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/dev-dependency-used-by-non-dev-source.txt b/rewatch/tests/snapshots/dev-dependency-used-by-non-dev-source.txt index d0536dc66ad..3f5bf7f5404 100644 --- a/rewatch/tests/snapshots/dev-dependency-used-by-non-dev-source.txt +++ b/rewatch/tests/snapshots/dev-dependency-used-by-non-dev-source.txt @@ -25,6 +25,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -45,5 +46,4 @@ Please report this to the package maintainer: https://github.com/DZakh/sury/issu -Incremental build failed. Error:  - Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/remove-file.txt b/rewatch/tests/snapshots/remove-file.txt index 726d8023154..ff4d3970ac4 100644 --- a/rewatch/tests/snapshots/remove-file.txt +++ b/rewatch/tests/snapshots/remove-file.txt @@ -25,6 +25,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -47,5 +48,4 @@ Please report this to the package maintainer: https://github.com/DZakh/sury/issu -Incremental build failed. Error:  - Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt b/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt index d32b92d0d5d..6e0311b7589 100644 --- a/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt +++ b/rewatch/tests/snapshots/rename-file-internal-dep-namespace.txt @@ -25,6 +25,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -47,5 +48,4 @@ Please report this to the package maintainer: https://github.com/DZakh/sury/issu Hint: Did you mean Other_module2? -Incremental build failed. Error:  - Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-internal-dep.txt b/rewatch/tests/snapshots/rename-file-internal-dep.txt index 16911fccab8..68215bf2153 100644 --- a/rewatch/tests/snapshots/rename-file-internal-dep.txt +++ b/rewatch/tests/snapshots/rename-file-internal-dep.txt @@ -25,6 +25,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): @@ -47,5 +48,4 @@ Please report this to the package maintainer: https://github.com/DZakh/sury/issu -Incremental build failed. Error:  - Failed to Compile. See Errors Above +Incremental build failed. Error:  Failed to Compile. See Errors Above diff --git a/rewatch/tests/snapshots/rename-file-with-interface.txt b/rewatch/tests/snapshots/rename-file-with-interface.txt index 388442b40e2..f26462d8151 100644 --- a/rewatch/tests/snapshots/rename-file-with-interface.txt +++ b/rewatch/tests/snapshots/rename-file-with-interface.txt @@ -1,5 +1,4 @@ - - No implementation file found for interface file (skipping): src/ModuleWithInterface.resi + No implementation file found for interface file (skipping): src/ModuleWithInterface.resi Cleaned 2/432 Parsed 2 source files Compiled 2 modules @@ -27,6 +26,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): diff --git a/rewatch/tests/snapshots/rename-file.txt b/rewatch/tests/snapshots/rename-file.txt index 76af6e8b3ff..347bc002df3 100644 --- a/rewatch/tests/snapshots/rename-file.txt +++ b/rewatch/tests/snapshots/rename-file.txt @@ -25,6 +25,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): diff --git a/rewatch/tests/snapshots/rename-interface-file.txt b/rewatch/tests/snapshots/rename-interface-file.txt index 23c89d05b8d..b7c694c4c33 100644 --- a/rewatch/tests/snapshots/rename-interface-file.txt +++ b/rewatch/tests/snapshots/rename-interface-file.txt @@ -1,5 +1,4 @@ - - No implementation file found for interface file (skipping): src/ModuleWithInterface2.resi + No implementation file found for interface file (skipping): src/ModuleWithInterface2.resi Cleaned 1/432 Parsed 2 source files Compiled 2 modules @@ -27,6 +26,7 @@ Unknown field 'some-new-field' found in the package config of '@testrepo/depreca Package 'rescript-nodejs' uses deprecated config (support will be removed in a future version): - field 'bs-dependencies' — use 'dependencies' instead - field 'bs-dev-dependencies' — use 'dev-dependencies' instead + - filename 'bsconfig.json' — rename to 'rescript.json' Please report this to the package maintainer: https://github.com/TheSpyder/rescript-nodejs/issues Package 'sury' uses deprecated config (support will be removed in a future version): diff --git a/rewatch/tests/watch/06-watch-missing-source-folder.sh b/rewatch/tests/watch/06-watch-missing-source-folder.sh index 11d4336c4ca..08436c4fe19 100755 --- a/rewatch/tests/watch/06-watch-missing-source-folder.sh +++ b/rewatch/tests/watch/06-watch-missing-source-folder.sh @@ -54,34 +54,14 @@ fi # where the config change triggers a full rebuild that runs concurrently # with the subsequent `rewatch build`. exit_watcher -if ! wait_for_file_gone "lib/rescript.lock" 20; then - error "Watcher did not stop in time" - git checkout "$DEP01_CONFIG" - exit 1 -fi +sleep 1 # Restore dep01's rescript.json git checkout "$DEP01_CONFIG" # Rebuild to regenerate any artifacts that were removed by `rewatch clean` # but not rebuilt due to the modified config (e.g. Dep01.mjs). -if ! rewatch build > /dev/null 2>&1; then - error "Rebuild after restoring config failed" - rm -f rewatch.log - exit 1 -fi - -# Slow CI runners can still be catching up on file restoration when the build -# command returns. Wait until git no longer sees tracked deletions before doing -# the final cleanliness check. -timeout=20 -while [ "$timeout" -gt 0 ]; do - if [ -z "$(git diff --name-only --diff-filter=D .)" ]; then - break - fi - sleep 1 - timeout=$((timeout - 1)) -done +rewatch build > /dev/null 2>&1 rm -f rewatch.log if git diff --exit-code . > /dev/null 2>&1 && [ -z "$(git ls-files --others --exclude-standard .)" ]; From 58fb9987cd61489bf9d886c29f27e1f05dd96a7a Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 12:05:41 +0200 Subject: [PATCH 42/56] Don't hardcode file extensions --- .../core/js_pass_nested_component_exports.ml | 17 ++++++----------- .../rsc_dynamic_import_nested_jsx/input.js | 8 ++++++-- .../rsc_dynamic_import_nested_jsx/rescript.json | 2 +- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml index b3b32757708..4e342b59ad7 100644 --- a/compiler/core/js_pass_nested_component_exports.ml +++ b/compiler/core/js_pass_nested_component_exports.ml @@ -93,6 +93,11 @@ let hidden_export_names_to_remove candidates = Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> StringSet.add (Ident.name candidate.module_ident) acc) +let chop_js_suffix basename = + match String.index_opt basename '.' with + | Some index -> String.sub basename 0 index + | None -> basename + let dynamic_import_module_root (expr : J.expression) = match expr.expression_desc with | Await @@ -103,17 +108,7 @@ let dynamic_import_module_root (expr : J.expression) = } when String.equal import_ident.name "import" -> ( match arg.expression_desc with - | Str {txt; _} -> - let basename = Filename.basename txt in - let suffixes = [".res.mjs"; ".res.js"; ".mjs"; ".js"] in - let rec strip_suffix = function - | [] -> basename - | suffix :: rest -> ( - match Ext_string.ends_with_then_chop basename suffix with - | Some basename -> basename - | None -> strip_suffix rest) - in - Some (strip_suffix suffixes) + | Str {txt; _} -> Some (Filename.basename txt |> chop_js_suffix) | _ -> None) | _ -> None diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js index 1d629929c9d..9c8a7c8a6d6 100644 --- a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js @@ -10,12 +10,16 @@ const { execBuild, execClean } = setup(import.meta.dirname); await execClean(); await execBuild(); -const outputPath = path.join(import.meta.dirname, "src", "MainLayout.res.mjs"); +const outputPath = path.join( + import.meta.dirname, + "src", + "MainLayout.custom.mjs", +); const output = await fs.readFile(outputPath, "utf8"); assert.match( output, - /let DynamicSidebar = await import\("\.\/Sidebar\.res\.mjs"\);/, + /let DynamicSidebar = await import\("\.\/Sidebar\.custom\.mjs"\);/, ); assert.match( output, diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/rescript.json b/tests/build_tests/rsc_dynamic_import_nested_jsx/rescript.json index f4772ad950b..91cc1d24d5b 100644 --- a/tests/build_tests/rsc_dynamic_import_nested_jsx/rescript.json +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/rescript.json @@ -10,7 +10,7 @@ "package-specs": { "module": "esmodule", "in-source": true, - "suffix": ".res.mjs" + "suffix": ".custom.mjs" }, "namespace": true } From fb754b65b9186c0183a0e0a450dbb905ee45c672 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 12:40:34 +0200 Subject: [PATCH 43/56] Reuse some more logic --- compiler/gentype/EmitJs.ml | 17 ++--------------- compiler/gentype/ExportModule.ml | 13 +------------ compiler/gentype/ModuleName.ml | 11 +++++++++++ compiler/gentype/ModuleName.mli | 2 ++ 4 files changed, 16 insertions(+), 27 deletions(-) diff --git a/compiler/gentype/EmitJs.ml b/compiler/gentype/EmitJs.ml index 964b34a2928..e8da2060855 100644 --- a/compiler/gentype/EmitJs.ml +++ b/compiler/gentype/EmitJs.ml @@ -145,21 +145,8 @@ let emit_code_item ~config ~emitters ~module_items_emitter ~env ~file_name | Runtime.Dot (path, module_item) -> flatten path @ [module_item |> Runtime.module_item_to_string] in - match flatten module_access_path with - | [] -> None - | _module_path :: _ as path -> ( - match List.rev path with - | "make" :: module_path_rev -> ( - match List.rev module_path_rev with - | [] -> None - | module_path -> - Some - ((file_name |> ModuleName.for_js_file |> ModuleName.to_string) - ^ "." - ^ (file_name |> ModuleName.to_string) - ^ "$" - ^ String.concat "$" module_path)) - | _ -> None) + ModuleName.nested_make_hidden_export_access ~file_name + (flatten module_access_path) in if !Debug.code_items then Log_.item "Code Item: %s\n" diff --git a/compiler/gentype/ExportModule.ml b/compiler/gentype/ExportModule.ml index 54b913b2801..30ac630deb9 100644 --- a/compiler/gentype/ExportModule.ml +++ b/compiler/gentype/ExportModule.ml @@ -119,18 +119,7 @@ let emit_all_module_items ~config ~emitters ~file_name | _ -> false in let hidden_export_access resolved_name = - match List.rev resolved_name with - | "make" :: module_path_rev -> ( - match List.rev module_path_rev with - | [] -> None - | module_path -> - Some - ((file_name |> ModuleName.for_js_file |> ModuleName.to_string) - ^ "." - ^ (file_name |> ModuleName.to_string) - ^ "$" - ^ String.concat "$" module_path)) - | _ -> None + ModuleName.nested_make_hidden_export_access ~file_name resolved_name in let single_make_component_export export_module_item = match Hashtbl.length export_module_item with diff --git a/compiler/gentype/ModuleName.ml b/compiler/gentype/ModuleName.ml index efd0d25370a..e9bb8956484 100644 --- a/compiler/gentype/ModuleName.ml +++ b/compiler/gentype/ModuleName.ml @@ -22,6 +22,17 @@ let for_js_file s = sanitize_id s ^ "JS" let for_inner_module ~file_name ~inner_module_name = (file_name |> for_js_file) ^ "." ^ inner_module_name +let nested_make_hidden_export_access ~file_name path = + match List.rev path with + | "make" :: module_path_rev -> ( + match List.rev module_path_rev with + | [] -> None + | module_path -> + Some + ((file_name |> for_js_file) ^ "." ^ file_name ^ "$" + ^ String.concat "$" module_path)) + | _ -> None + let from_string_unsafe s = s let to_string s = s let compare (s1 : string) s2 = compare s1 s2 diff --git a/compiler/gentype/ModuleName.mli b/compiler/gentype/ModuleName.mli index fcd55553025..d76e0859241 100644 --- a/compiler/gentype/ModuleName.mli +++ b/compiler/gentype/ModuleName.mli @@ -4,6 +4,8 @@ val compare : t -> t -> int val curry : t val for_js_file : t -> t val for_inner_module : file_name:t -> inner_module_name:string -> t +val nested_make_hidden_export_access : + file_name:t -> string list -> string option val from_string_unsafe : string -> t (** Used to turn strings read from external files into module names. *) From fbddf472ad9dd91d02f1bbd2932e1a3cd5ba96bf Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 13:12:23 +0200 Subject: [PATCH 44/56] Reuse nested component separator literal --- .../core/js_pass_nested_component_exports.ml | 15 ++++--- compiler/core/lam_compile.ml | 40 ++++++++++++++----- compiler/ext/ext_modulename.ml | 11 +++++ compiler/ext/ext_modulename.mli | 5 +++ compiler/gentype/ModuleName.ml | 5 ++- compiler/syntax/src/jsx_v4.ml | 10 +++-- 6 files changed, 65 insertions(+), 21 deletions(-) diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml index 4e342b59ad7..ea46b459412 100644 --- a/compiler/core/js_pass_nested_component_exports.ml +++ b/compiler/core/js_pass_nested_component_exports.ml @@ -29,7 +29,7 @@ module StringSet = Set.Make (String) type candidate = {module_ident: Ident.t} let hidden_component_suffix (module_ident : Ident.t) = - "$" ^ Ident.name module_ident + Ext_modulename.nested_component_suffix (Ident.name module_ident) let is_hidden_component_name_for module_ident ident = Ext_string.ends_with (Ident.name ident) (hidden_component_suffix module_ident) @@ -122,7 +122,9 @@ let known_hidden_component_exports block = (match expr.expression_desc with | Var (Qualified (_, Some name)) -> hidden_exports := StringSet.add name !hidden_exports - | Static_index (_, name, _) when String.contains name '$' -> + | Static_index (_, name, _) + when String.contains name + Ext_modulename.nested_component_separator_char -> hidden_exports := StringSet.add name !hidden_exports | _ -> ()); Js_record_map.super.expression self expr); @@ -161,9 +163,12 @@ let rewrite_dynamic_import_component_access aliases known_hidden_exports let segments = List.rev segments in let hidden_name = match segments with - | first :: _ when Ext_string.starts_with first (module_root ^ "$") -> - String.concat "$" segments - | _ -> String.concat "$" (module_root :: segments) + | first :: _ + when Ext_string.starts_with first + (Ext_modulename.nested_component_prefix module_root) -> + Ext_modulename.concat_nested_component_name segments + | _ -> + Ext_modulename.concat_nested_component_name (module_root :: segments) in if StringSet.mem hidden_name known_hidden_exports then {expr with expression_desc = Static_index (E.var id, hidden_name, None)} diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 7d1a1d4c201..54facbba671 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -237,18 +237,25 @@ let compile output_prefix = match Ext_namespace.try_split_module_name id.name with | Some (_namespace, module_name) -> module_name | None -> ( - match String.index_opt id.name '$' with + match + String.index_opt id.name Ext_modulename.nested_component_separator_char + with | Some index -> String.sub id.name 0 index | None -> id.name) in let nested_component_path id dynamic_import segments = let root_name = root_module_name id in let denamespace_segment segment = - let namespaced_prefix = root_name ^ "$" in + let namespaced_prefix = + Ext_modulename.nested_component_prefix root_name + in if Ext_string.starts_with segment namespaced_prefix then - match String.split_on_char '$' segment with + match + String.split_on_char Ext_modulename.nested_component_separator_char + segment + with | root :: _namespace :: rest when rest <> [] -> - String.concat "$" (root :: rest) + Ext_modulename.concat_nested_component_name (root :: rest) | _ -> segment else segment in @@ -256,7 +263,8 @@ let compile output_prefix = match segments with | head :: rest when head = id.name || head = root_name - || Ext_string.starts_with head (root_name ^ "$") -> + || Ext_string.starts_with head + (Ext_modulename.nested_component_prefix root_name) -> rest | _ -> segments in @@ -266,7 +274,8 @@ let compile output_prefix = Some ( id, dynamic_import, - String.concat "$" (root_name :: denamespace_segment head :: rest) ) + Ext_modulename.concat_nested_component_name + (root_name :: denamespace_segment head :: rest) ) in let rec extract_component_segments ~allow_import ~allow_unbound_var segments (lam : Lam.t) : (Ident.t * bool * string list) option = @@ -328,13 +337,19 @@ let compile output_prefix = in let normalize_hidden_component_name (id : Ident.t) (hidden_name : string) = let root_name = root_module_name id in - let id_parts = String.split_on_char '$' id.name in + let id_parts = + String.split_on_char Ext_modulename.nested_component_separator_char + id.name + in let namespace_parts = match id_parts with | _root :: rest -> rest | [] -> [] in - let hidden_parts = String.split_on_char '$' hidden_name in + let hidden_parts = + String.split_on_char Ext_modulename.nested_component_separator_char + hidden_name + in let hidden_parts_without_root = match hidden_parts with | first :: rest when String.equal first root_name -> rest @@ -349,7 +364,7 @@ let compile output_prefix = let tail = drop_prefix namespace_parts hidden_parts_without_root in match tail with | [] -> hidden_name - | _ -> String.concat "$" (root_name :: tail) + | _ -> Ext_modulename.concat_nested_component_name (root_name :: tail) in let hidden_component_name_candidates (id : Ident.t) (hidden_name : string) = let candidates = ref [] in @@ -357,9 +372,12 @@ let compile output_prefix = if not (List.mem candidate !candidates) then candidates := candidate :: !candidates in - (match String.split_on_char '$' hidden_name with + (match + String.split_on_char Ext_modulename.nested_component_separator_char + hidden_name + with | root :: _namespace :: rest when rest <> [] -> - push (String.concat "$" (root :: rest)) + push (Ext_modulename.concat_nested_component_name (root :: rest)) | _ -> ()); push (normalize_hidden_component_name id hidden_name); push hidden_name; diff --git a/compiler/ext/ext_modulename.ml b/compiler/ext/ext_modulename.ml index d2d46930f04..a1b2e3018ab 100644 --- a/compiler/ext/ext_modulename.ml +++ b/compiler/ext/ext_modulename.ml @@ -22,6 +22,17 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) +let nested_component_separator_char = '$' +let nested_component_separator = String.make 1 nested_component_separator_char + +let nested_component_prefix module_name = + module_name ^ nested_component_separator + +let nested_component_suffix module_name = + nested_component_separator ^ module_name + +let concat_nested_component_name = String.concat nested_component_separator + let good_hint_name module_name offset = let len = String.length module_name in len > offset diff --git a/compiler/ext/ext_modulename.mli b/compiler/ext/ext_modulename.mli index d59902439b2..acef85c5ad4 100644 --- a/compiler/ext/ext_modulename.mli +++ b/compiler/ext/ext_modulename.mli @@ -22,6 +22,11 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) +val nested_component_separator_char : char +val nested_component_prefix : string -> string +val nested_component_suffix : string -> string +val concat_nested_component_name : string list -> string + val js_id_name_of_hint_name : string -> string (** Given an JS bundle name, generate a meaningful bounded module name diff --git a/compiler/gentype/ModuleName.ml b/compiler/gentype/ModuleName.ml index e9bb8956484..8463f1ef577 100644 --- a/compiler/gentype/ModuleName.ml +++ b/compiler/gentype/ModuleName.ml @@ -29,8 +29,9 @@ let nested_make_hidden_export_access ~file_name path = | [] -> None | module_path -> Some - ((file_name |> for_js_file) ^ "." ^ file_name ^ "$" - ^ String.concat "$" module_path)) + ((file_name |> for_js_file) ^ "." + ^ Ext_modulename.concat_nested_component_name (file_name :: module_path) + )) | _ -> None let from_string_unsafe s = s diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 1476c967831..b1625f06f3b 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -128,7 +128,9 @@ let filename_from_loc (pstr_loc : Location.t) = file_name let unnamespace_module_name file_name = - match String.index_opt file_name '$' with + match + String.index_opt file_name Ext_modulename.nested_component_separator_char + with | Some index -> String.sub file_name 0 index | None -> ( match Ext_namespace.try_split_module_name file_name with @@ -167,7 +169,7 @@ let maybe_hoist_nested_make_signature ~(config : Jsx_common.jsx_config) config.hoisted_signature_items <- full_sig :: config.hoisted_signature_items | _ -> () -(* Build a string representation of a module name with segments separated by $ *) +(* Build a string representation of a nested component module name. *) let make_module_name file_name nested_modules fn_name = let file_name = unnamespace_module_name file_name in let full_module_name = @@ -179,7 +181,9 @@ let make_module_name file_name nested_modules fn_name = | file_name, nested_modules, fn_name -> file_name :: List.rev (fn_name :: nested_modules) in - let full_module_name = String.concat "$" full_module_name in + let full_module_name = + Ext_modulename.concat_nested_component_name full_module_name + in full_module_name (* make type params for make fn arguments *) From 761069ab968f8f1ea8f50b7d2ed5382265812ebc Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 13:46:48 +0200 Subject: [PATCH 45/56] Extract nested component compilation logic into separate module --- .../core/js_pass_nested_component_exports.mli | 2 +- compiler/core/lam_compile.ml | 258 ++---------------- compiler/core/lam_compile_nested_component.ml | 249 +++++++++++++++++ compiler/gentype/EmitJs.ml | 4 +- compiler/gentype/ExportModule.ml | 4 +- 5 files changed, 280 insertions(+), 237 deletions(-) create mode 100644 compiler/core/lam_compile_nested_component.ml diff --git a/compiler/core/js_pass_nested_component_exports.mli b/compiler/core/js_pass_nested_component_exports.mli index 624543fc9a8..dc90a18a772 100644 --- a/compiler/core/js_pass_nested_component_exports.mli +++ b/compiler/core/js_pass_nested_component_exports.mli @@ -22,7 +22,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -(* Rewrite nested React component module exports from `{make: fn}` wrappers to +(* Rewrite nested JSX component module exports from `{make: fn}` wrappers to direct component exports, while keeping `.make` as a self-reference for compatibility with existing generated call sites. *) val program : J.program -> J.program diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 54facbba671..8cf304a732f 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -233,221 +233,27 @@ type initialization = J.block let compile output_prefix = let local_module_aliases : Lam.t Map_ident.t ref = ref Map_ident.empty in - let root_module_name (id : Ident.t) = - match Ext_namespace.try_split_module_name id.name with - | Some (_namespace, module_name) -> module_name - | None -> ( - match - String.index_opt id.name Ext_modulename.nested_component_separator_char - with - | Some index -> String.sub id.name 0 index - | None -> id.name) - in - let nested_component_path id dynamic_import segments = - let root_name = root_module_name id in - let denamespace_segment segment = - let namespaced_prefix = - Ext_modulename.nested_component_prefix root_name - in - if Ext_string.starts_with segment namespaced_prefix then - match - String.split_on_char Ext_modulename.nested_component_separator_char - segment - with - | root :: _namespace :: rest when rest <> [] -> - Ext_modulename.concat_nested_component_name (root :: rest) - | _ -> segment - else segment - in - let segments = - match segments with - | head :: rest - when head = id.name || head = root_name - || Ext_string.starts_with head - (Ext_modulename.nested_component_prefix root_name) -> - rest - | _ -> segments - in - match segments with - | [] -> None - | head :: rest -> - Some - ( id, - dynamic_import, - Ext_modulename.concat_nested_component_name - (root_name :: denamespace_segment head :: rest) ) - in - let rec extract_component_segments ~allow_import ~allow_unbound_var segments - (lam : Lam.t) : (Ident.t * bool * string list) option = - match lam with - | Lprim - { - primitive = Pfield (_, Fld_module {name; jsx_component = _}); - args = [arg]; - _; - } -> - extract_component_segments ~allow_import ~allow_unbound_var - (name :: segments) arg - | Lprim {primitive = Pawait; args = [arg]; _} -> - extract_component_segments ~allow_import ~allow_unbound_var segments arg - | Lprim {primitive = Pimport; args = [arg]; _} when allow_import -> - extract_component_segments ~allow_import ~allow_unbound_var segments arg - | Lvar id -> ( - match Map_ident.find_opt !local_module_aliases id with - | Some alias_lam -> - extract_component_segments ~allow_import ~allow_unbound_var segments - alias_lam - | None -> - if allow_unbound_var then Some (id, false, List.rev segments) else None) - | Lglobal_module (id, dynamic_import) -> - Some (id, dynamic_import, List.rev segments) - | _ -> None - in - let extract_nested_external_component_path (lam : Lam.t) : - (Ident.t * bool * string) option = - match - extract_component_segments ~allow_import:false ~allow_unbound_var:true [] - lam - with - | Some (id, dynamic_import, segments) -> - nested_component_path id dynamic_import segments - | None -> None - in - let extract_nested_external_component_field (lam : Lam.t) : - (Ident.t * bool * string) option = - match lam with - | Lprim - { - primitive = Pfield (_, Fld_module {name = "make"; jsx_component = _}); - args = [arg]; - _; - } -> - extract_nested_external_component_path arg - | _ -> None - in - let extract_static_nested_external_component_path (lam : Lam.t) : - (Ident.t * bool * string) option = - match - extract_component_segments ~allow_import:true ~allow_unbound_var:false [] - lam - with - | Some (id, dynamic_import, segments) -> - nested_component_path id dynamic_import segments - | None -> None - in - let normalize_hidden_component_name (id : Ident.t) (hidden_name : string) = - let root_name = root_module_name id in - let id_parts = - String.split_on_char Ext_modulename.nested_component_separator_char - id.name - in - let namespace_parts = - match id_parts with - | _root :: rest -> rest - | [] -> [] - in - let hidden_parts = - String.split_on_char Ext_modulename.nested_component_separator_char - hidden_name - in - let hidden_parts_without_root = - match hidden_parts with - | first :: rest when String.equal first root_name -> rest - | _ -> hidden_parts - in - let rec drop_prefix prefix parts = - match (prefix, parts) with - | [], _ -> parts - | x :: xs, y :: ys when String.equal x y -> drop_prefix xs ys - | _ -> parts - in - let tail = drop_prefix namespace_parts hidden_parts_without_root in - match tail with - | [] -> hidden_name - | _ -> Ext_modulename.concat_nested_component_name (root_name :: tail) - in - let hidden_component_name_candidates (id : Ident.t) (hidden_name : string) = - let candidates = ref [] in - let push candidate = - if not (List.mem candidate !candidates) then - candidates := candidate :: !candidates - in - (match - String.split_on_char Ext_modulename.nested_component_separator_char - hidden_name - with - | root :: _namespace :: rest when rest <> [] -> - push (Ext_modulename.concat_nested_component_name (root :: rest)) - | _ -> ()); - push (normalize_hidden_component_name id hidden_name); - push hidden_name; - List.rev !candidates - in - let exported_hidden_component_name ~(id : Ident.t) ~(dynamic_import : bool) - (hidden_name_candidates : string list) = - let rec loop = function - | [] -> None - | candidate :: rest -> ( - match - Lam_compile_env.query_external_id_info ~dynamic_import id candidate - with - | _ -> Some candidate - | exception Not_found -> loop rest) - in - loop hidden_name_candidates - in - let rec extract_root_expr (expr : J.expression) = - match expr.expression_desc with - | Var (Qualified (module_id, Some _)) -> - Some {expr with expression_desc = Var (Qualified (module_id, None))} - | Static_index (inner, _, _) -> extract_root_expr inner - | Var _ -> Some expr - | _ -> None - in - let hidden_component_access (root_expr : J.expression) hidden_name = - match root_expr.expression_desc with - | Var (Qualified (module_id, None)) -> - { - root_expr with - expression_desc = Var (Qualified (module_id, Some hidden_name)); - } - | _ -> E.dot root_expr hidden_name - in let rewrite_nested_jsx_component_expr (jsx_tag : Lam.t) (compiled_expr : J.expression) : J.expression = - match extract_nested_external_component_field jsx_tag with - | Some (id, dynamic_import, hidden_name) -> ( - let hidden_name_candidates = - hidden_component_name_candidates id hidden_name - in - match extract_root_expr compiled_expr with - | Some root_expr -> ( - match - exported_hidden_component_name ~id ~dynamic_import - hidden_name_candidates - with - | Some hidden_name -> hidden_component_access root_expr hidden_name - | None -> compiled_expr) - | None -> compiled_expr) - | None -> compiled_expr + Lam_compile_nested_component.rewrite_jsx_component_expr local_module_aliases + jsx_tag compiled_expr in let rewrite_nested_component_make_expr (lam : Lam.t) (compiled_expr : J.expression) : J.expression = - match extract_static_nested_external_component_path lam with - | Some (id, dynamic_import, hidden_name) -> ( - let hidden_name_candidates = - hidden_component_name_candidates id hidden_name - in - match extract_root_expr compiled_expr with - | Some root_expr -> ( - match - exported_hidden_component_name ~id ~dynamic_import - hidden_name_candidates - with - | Some hidden_name -> hidden_component_access root_expr hidden_name - | None -> compiled_expr) - | None -> compiled_expr) - | None -> compiled_expr + Lam_compile_nested_component.rewrite_component_make_expr + local_module_aliases lam compiled_expr + in + let is_module_alias_candidate lam = + Lam_compile_nested_component.is_module_alias_candidate local_module_aliases + lam + in + let rewrite_transformed_jsx_args (appinfo : Lam.apply) args = + if appinfo.ap_transformed_jsx then + match (appinfo.ap_args, args) with + | jsx_tag :: _, jsx_expr :: rest_args -> + rewrite_nested_jsx_component_expr jsx_tag jsx_expr :: rest_args + | _ -> args + else args in let rec compile_external_field (* Like [List.empty]*) ?(dynamic_import = false) (lamba_cxt : Lam_compile_context.t) @@ -517,14 +323,7 @@ let compile output_prefix = (Ext_list.append block args_code, b :: args) | _ -> assert false) in - let args = - if appinfo.ap_transformed_jsx then - match (appinfo.ap_args, args) with - | jsx_tag :: _, jsx_expr :: rest_args -> - rewrite_nested_jsx_component_expr jsx_tag jsx_expr :: rest_args - | _ -> args - else args - in + let args = rewrite_transformed_jsx_args appinfo args in let fn = E.ml_var_dot ~dynamic_import module_id ident_info.name in let expression = match appinfo.ap_info.ap_status with @@ -1802,14 +1601,7 @@ let compile output_prefix = (Ext_list.append block args_code, b :: fn_code) | {value = None} -> assert false) in - let args = - if appinfo.ap_transformed_jsx then - match (appinfo.ap_args, args) with - | jsx_tag :: _, jsx_expr :: rest_args -> - rewrite_nested_jsx_component_expr jsx_tag jsx_expr :: rest_args - | _ -> args - else args - in + let args = rewrite_transformed_jsx_args appinfo args in match (ap_func, lambda_cxt.continuation) with | ( Lvar fn_id, ( EffectCall (Maybe_tail_is_return (Tail_with_name {label = Some ret})) @@ -1903,7 +1695,7 @@ let compile output_prefix = exp | { primitive = - Pfield (_, (Fld_module {name = "make"; jsx_component = _} as fld_info)); + Pfield (_, (Fld_module {name = "make"; jsx_component = true} as fld_info)); args = [arg]; _; } -> ( @@ -2149,12 +1941,14 @@ let compile output_prefix = {lambda_cxt with continuation = Declare (let_kind, id)} arg in - let previous_aliases = !local_module_aliases in - local_module_aliases := Map_ident.add previous_aliases id arg; let body_output = - Fun.protect - ~finally:(fun () -> local_module_aliases := previous_aliases) - (fun () -> compile_lambda lambda_cxt body) + if is_module_alias_candidate arg then ( + let previous_aliases = !local_module_aliases in + local_module_aliases := Map_ident.add previous_aliases id arg; + Fun.protect + ~finally:(fun () -> local_module_aliases := previous_aliases) + (fun () -> compile_lambda lambda_cxt body)) + else compile_lambda lambda_cxt body in Js_output.append_output args_code body_output | Lletrec (id_args, body) -> diff --git a/compiler/core/lam_compile_nested_component.ml b/compiler/core/lam_compile_nested_component.ml new file mode 100644 index 00000000000..8d5211697c3 --- /dev/null +++ b/compiler/core/lam_compile_nested_component.ml @@ -0,0 +1,249 @@ +(* Copyright (C) 2026 - Authors of ReScript + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * In addition to the permissions granted to you by the LGPL, you may combine + * or link a "work that uses the Library" with a publicly distributed version + * of this file to produce a combined library or application, then distribute + * that combined work under the terms of your choosing, with no requirement + * to comply with the obligations normally placed on you by section 4 of the + * LGPL version 3 (or the corresponding section of a later version of the LGPL + * should you choose to use a later version). + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) + +module E = Js_exp_make + +let root_module_name (id : Ident.t) = + match Ext_namespace.try_split_module_name id.name with + | Some (_namespace, module_name) -> module_name + | None -> ( + match + String.index_opt id.name Ext_modulename.nested_component_separator_char + with + | Some index -> String.sub id.name 0 index + | None -> id.name) + +let nested_component_path id dynamic_import segments = + let root_name = root_module_name id in + let denamespace_segment segment = + let namespaced_prefix = Ext_modulename.nested_component_prefix root_name in + if Ext_string.starts_with segment namespaced_prefix then + match + String.split_on_char Ext_modulename.nested_component_separator_char + segment + with + | root :: _namespace :: rest when rest <> [] -> + Ext_modulename.concat_nested_component_name (root :: rest) + | _ -> segment + else segment + in + let segments = + match segments with + | head :: rest + when head = id.name || head = root_name + || Ext_string.starts_with head + (Ext_modulename.nested_component_prefix root_name) -> + rest + | _ -> segments + in + match segments with + | [] -> None + | head :: rest -> + Some + ( id, + dynamic_import, + Ext_modulename.concat_nested_component_name + (root_name :: denamespace_segment head :: rest) ) + +let rec extract_component_segments aliases ~allow_import ~allow_unbound_var + segments (lam : Lam.t) : (Ident.t * bool * string list) option = + match lam with + | Lprim + { + primitive = Pfield (_, Fld_module {name; jsx_component = _}); + args = [arg]; + _; + } -> + extract_component_segments aliases ~allow_import ~allow_unbound_var + (name :: segments) arg + | Lprim {primitive = Pawait; args = [arg]; _} -> + extract_component_segments aliases ~allow_import ~allow_unbound_var segments + arg + | Lprim {primitive = Pimport; args = [arg]; _} when allow_import -> + extract_component_segments aliases ~allow_import ~allow_unbound_var segments + arg + | Lvar id -> ( + match Map_ident.find_opt !aliases id with + | Some alias_lam -> + extract_component_segments aliases ~allow_import ~allow_unbound_var + segments alias_lam + | None -> + if allow_unbound_var then Some (id, false, List.rev segments) else None) + | Lglobal_module (id, dynamic_import) -> + Some (id, dynamic_import, List.rev segments) + | _ -> None + +let rec is_module_alias_candidate aliases (lam : Lam.t) = + match lam with + | Lglobal_module _ -> true + | Lvar id -> Map_ident.mem !aliases id + | Lprim {primitive = Pfield (_, Fld_module _); args = [arg]; _} + | Lprim {primitive = Pawait | Pimport; args = [arg]; _} -> + is_module_alias_candidate aliases arg + | _ -> false + +let extract_nested_external_component_path aliases (lam : Lam.t) : + (Ident.t * bool * string) option = + match + extract_component_segments aliases ~allow_import:false + ~allow_unbound_var:true [] lam + with + | Some (id, dynamic_import, segments) -> + nested_component_path id dynamic_import segments + | None -> None + +let extract_nested_external_component_field aliases (lam : Lam.t) : + (Ident.t * bool * string) option = + match lam with + | Lprim + { + primitive = Pfield (_, Fld_module {name = "make"; jsx_component = _}); + args = [arg]; + _; + } -> + extract_nested_external_component_path aliases arg + | _ -> None + +let extract_static_nested_external_component_path aliases (lam : Lam.t) : + (Ident.t * bool * string) option = + match + extract_component_segments aliases ~allow_import:true + ~allow_unbound_var:false [] lam + with + | Some (id, dynamic_import, segments) -> + nested_component_path id dynamic_import segments + | None -> None + +let normalize_hidden_component_name (id : Ident.t) (hidden_name : string) = + let root_name = root_module_name id in + let id_parts = + String.split_on_char Ext_modulename.nested_component_separator_char id.name + in + let namespace_parts = + match id_parts with + | _root :: rest -> rest + | [] -> [] + in + let hidden_parts = + String.split_on_char Ext_modulename.nested_component_separator_char + hidden_name + in + let hidden_parts_without_root = + match hidden_parts with + | first :: rest when String.equal first root_name -> rest + | _ -> hidden_parts + in + let rec drop_prefix prefix parts = + match (prefix, parts) with + | [], _ -> parts + | x :: xs, y :: ys when String.equal x y -> drop_prefix xs ys + | _ -> parts + in + let tail = drop_prefix namespace_parts hidden_parts_without_root in + match tail with + | [] -> hidden_name + | _ -> Ext_modulename.concat_nested_component_name (root_name :: tail) + +let hidden_component_name_candidates (id : Ident.t) (hidden_name : string) = + let candidates = ref [] in + let push candidate = + if not (List.mem candidate !candidates) then + candidates := candidate :: !candidates + in + (match + String.split_on_char Ext_modulename.nested_component_separator_char + hidden_name + with + | root :: _namespace :: rest when rest <> [] -> + push (Ext_modulename.concat_nested_component_name (root :: rest)) + | _ -> ()); + push (normalize_hidden_component_name id hidden_name); + push hidden_name; + List.rev !candidates + +let exported_hidden_component_name ~(id : Ident.t) ~(dynamic_import : bool) + hidden_name_candidates = + let rec loop = function + | [] -> None + | candidate :: rest -> ( + match + Lam_compile_env.query_external_id_info ~dynamic_import id candidate + with + | _ -> Some candidate + | exception Not_found -> loop rest) + in + loop hidden_name_candidates + +let rec extract_root_expr (expr : J.expression) = + match expr.expression_desc with + | Var (Qualified (module_id, Some _)) -> + Some {expr with expression_desc = Var (Qualified (module_id, None))} + | Static_index (inner, _, _) -> extract_root_expr inner + | Var _ -> Some expr + | _ -> None + +let hidden_component_access (root_expr : J.expression) hidden_name = + match root_expr.expression_desc with + | Var (Qualified (module_id, None)) -> + { + root_expr with + expression_desc = Var (Qualified (module_id, Some hidden_name)); + } + | _ -> E.dot root_expr hidden_name + +let rewrite_jsx_component_expr aliases (jsx_tag : Lam.t) + (compiled_expr : J.expression) : J.expression = + match extract_nested_external_component_field aliases jsx_tag with + | Some (id, dynamic_import, hidden_name) -> ( + let hidden_name_candidates = + hidden_component_name_candidates id hidden_name + in + match extract_root_expr compiled_expr with + | Some root_expr -> ( + match + exported_hidden_component_name ~id ~dynamic_import + hidden_name_candidates + with + | Some hidden_name -> hidden_component_access root_expr hidden_name + | None -> compiled_expr) + | None -> compiled_expr) + | None -> compiled_expr + +let rewrite_component_make_expr aliases (lam : Lam.t) + (compiled_expr : J.expression) : J.expression = + match extract_static_nested_external_component_path aliases lam with + | Some (id, dynamic_import, hidden_name) -> ( + let hidden_name_candidates = + hidden_component_name_candidates id hidden_name + in + match extract_root_expr compiled_expr with + | Some root_expr -> ( + match + exported_hidden_component_name ~id ~dynamic_import + hidden_name_candidates + with + | Some hidden_name -> hidden_component_access root_expr hidden_name + | None -> compiled_expr) + | None -> compiled_expr) + | None -> compiled_expr diff --git a/compiler/gentype/EmitJs.ml b/compiler/gentype/EmitJs.ml index e8da2060855..8112c9403f4 100644 --- a/compiler/gentype/EmitJs.ml +++ b/compiler/gentype/EmitJs.ml @@ -359,7 +359,7 @@ let emit_code_item ~config ~emitters ~module_items_emitter ~env ~file_name (comp_type, None) | _ -> (type_, None) in - let is_react_component_export = + let is_jsx_component_export = match type_ with | Function {arg_types = [{a_type = Object (_, fields)}]; ret_type; _} -> ret_type |> EmitType.is_type_function_component ~fields @@ -391,7 +391,7 @@ let emit_code_item ~config ~emitters ~module_items_emitter ~env ~file_name in let emitters = (match - ( original_name = make && is_react_component_export, + ( original_name = make && is_jsx_component_export, nested_make_hidden_export_access ~file_name module_access_path ) with | true, Some hidden_access -> hidden_access diff --git a/compiler/gentype/ExportModule.ml b/compiler/gentype/ExportModule.ml index 30ac630deb9..323b971c2cf 100644 --- a/compiler/gentype/ExportModule.ml +++ b/compiler/gentype/ExportModule.ml @@ -112,7 +112,7 @@ let rev_fold f tbl base = let emit_all_module_items ~config ~emitters ~file_name (export_module_items : export_module_items) = - let is_react_component_type type_ = + let is_jsx_component_type type_ = match type_ with | Function {arg_types = [{a_type = Object (_, fields)}]; ret_type; _} -> ret_type |> EmitType.is_type_function_component ~fields @@ -125,7 +125,7 @@ let emit_all_module_items ~config ~emitters ~file_name match Hashtbl.length export_module_item with | 1 -> ( match Hashtbl.find_opt export_module_item "make" with - | Some (S {path; type_; doc_string; _}) when is_react_component_type type_ + | Some (S {path; type_; doc_string; _}) when is_jsx_component_type type_ -> ( match hidden_export_access path with | Some access -> Some (access, type_, doc_string) From 88e55f9c63b3e2cd56305ca713ea330e11615656 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 13:53:58 +0200 Subject: [PATCH 46/56] Get rid of some local wrapper functions --- compiler/core/lam_compile.ml | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 8cf304a732f..ad6f95bf768 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -238,15 +238,6 @@ let compile output_prefix = Lam_compile_nested_component.rewrite_jsx_component_expr local_module_aliases jsx_tag compiled_expr in - let rewrite_nested_component_make_expr (lam : Lam.t) - (compiled_expr : J.expression) : J.expression = - Lam_compile_nested_component.rewrite_component_make_expr - local_module_aliases lam compiled_expr - in - let is_module_alias_candidate lam = - Lam_compile_nested_component.is_module_alias_candidate local_module_aliases - lam - in let rewrite_transformed_jsx_args (appinfo : Lam.apply) args = if appinfo.ap_transformed_jsx then match (appinfo.ap_args, args) with @@ -1708,7 +1699,8 @@ let compile output_prefix = | {block; value = Some compiled_arg} -> let compiled_expr = Js_of_lam_block.field fld_info compiled_arg 0l in let compiled_expr = - rewrite_nested_component_make_expr arg compiled_expr + Lam_compile_nested_component.rewrite_component_make_expr + local_module_aliases arg compiled_expr in Js_output.output_of_block_and_expression lambda_cxt.continuation block compiled_expr @@ -1942,7 +1934,10 @@ let compile output_prefix = arg in let body_output = - if is_module_alias_candidate arg then ( + if + Lam_compile_nested_component.is_module_alias_candidate + local_module_aliases arg + then ( let previous_aliases = !local_module_aliases in local_module_aliases := Map_ident.add previous_aliases id arg; Fun.protect From 3302728cf8de2c24271a6e8cc8e9fdb6e24e3907 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 14:30:06 +0200 Subject: [PATCH 47/56] Fix nested component plain make access rewrite --- compiler/core/lam_compile.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index ad6f95bf768..521f122139e 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -1686,7 +1686,7 @@ let compile output_prefix = exp | { primitive = - Pfield (_, (Fld_module {name = "make"; jsx_component = true} as fld_info)); + Pfield (_, (Fld_module {name = "make"; jsx_component = _} as fld_info)); args = [arg]; _; } -> ( From 0c0a77fd4e8950f772453a9340dd6d679824df6a Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 15:08:36 +0200 Subject: [PATCH 48/56] Fix segment order bugs --- compiler/core/js_pass_nested_component_exports.ml | 1 - compiler/core/lam_compile_nested_component.ml | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml index ea46b459412..5ed70aca6d8 100644 --- a/compiler/core/js_pass_nested_component_exports.ml +++ b/compiler/core/js_pass_nested_component_exports.ml @@ -160,7 +160,6 @@ let rewrite_dynamic_import_component_access aliases known_hidden_exports | Static_index (inner, "make", _) -> ( match collect_segments [] inner with | Some (id, module_root, segments) -> - let segments = List.rev segments in let hidden_name = match segments with | first :: _ diff --git a/compiler/core/lam_compile_nested_component.ml b/compiler/core/lam_compile_nested_component.ml index 8d5211697c3..f85e509ba8d 100644 --- a/compiler/core/lam_compile_nested_component.ml +++ b/compiler/core/lam_compile_nested_component.ml @@ -88,10 +88,8 @@ let rec extract_component_segments aliases ~allow_import ~allow_unbound_var | Some alias_lam -> extract_component_segments aliases ~allow_import ~allow_unbound_var segments alias_lam - | None -> - if allow_unbound_var then Some (id, false, List.rev segments) else None) - | Lglobal_module (id, dynamic_import) -> - Some (id, dynamic_import, List.rev segments) + | None -> if allow_unbound_var then Some (id, false, segments) else None) + | Lglobal_module (id, dynamic_import) -> Some (id, dynamic_import, segments) | _ -> None let rec is_module_alias_candidate aliases (lam : Lam.t) = From 67080d89fb4ce4490324241e22589c58be9ed5bf Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Wed, 29 Apr 2026 15:25:31 +0200 Subject: [PATCH 49/56] Fix nested JSX path segment handling --- compiler/core/lam_compile.ml | 6 ++++-- compiler/syntax/src/jsx_v4.ml | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 521f122139e..1be69e05c2e 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -1686,7 +1686,7 @@ let compile output_prefix = exp | { primitive = - Pfield (_, (Fld_module {name = "make"; jsx_component = _} as fld_info)); + Pfield (pos, (Fld_module {name = "make"; jsx_component = _} as fld_info)); args = [arg]; _; } -> ( @@ -1697,7 +1697,9 @@ let compile output_prefix = let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in match compile_lambda new_cxt arg with | {block; value = Some compiled_arg} -> - let compiled_expr = Js_of_lam_block.field fld_info compiled_arg 0l in + let compiled_expr = + Js_of_lam_block.field fld_info compiled_arg (Int32.of_int pos) + in let compiled_expr = Lam_compile_nested_component.rewrite_component_make_expr local_module_aliases arg compiled_expr diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index b1625f06f3b..df0183b23a8 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -1409,13 +1409,23 @@ let expr ~(config : Jsx_common.jsx_config) mapper expression = pexp_attributes = attrs; } -> config.nested_modules <- name.txt :: config.nested_modules; - let mapped_module_expr = default_mapper.module_expr mapper module_expr in - let mapped_body = mapper.expr mapper body in - let () = + let pop_nested_module () = match config.nested_modules with | _ :: rest -> config.nested_modules <- rest | [] -> () in + let mapped_module_expr, mapped_body = + try + let mapped_module_expr = + default_mapper.module_expr mapper module_expr + in + let mapped_body = mapper.expr mapper body in + pop_nested_module (); + (mapped_module_expr, mapped_body) + with e -> + pop_nested_module (); + raise e + in Exp.letmodule ~loc ~attrs name mapped_module_expr mapped_body | { pexp_desc = Pexp_jsx_element jsx_element; From f0c5dff1e9891b0df31290a29a845580cf0637d6 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 30 Apr 2026 12:54:25 +0200 Subject: [PATCH 50/56] Fix componentWithProps interfaces and same-name nested JSX components --- compiler/core/lam_compile_nested_component.ml | 2 +- compiler/syntax/src/jsx_v4.ml | 68 ++++++++- .../src/Sidebar.resi | 6 + .../rsc_nested_jsx_members/input.js | 18 +++ .../rsc_nested_jsx_members/src/Button.res | 6 + .../src/ButtonLayout.res | 4 + .../react/componentWithPropsInterface.resi | 8 + .../componentWithPropsInterface.resi.txt | 8 + typechecker-performance-notes.md | 144 ++++++++++++++++++ 9 files changed, 259 insertions(+), 5 deletions(-) create mode 100644 tests/build_tests/rsc_component_with_props_nested/src/Sidebar.resi create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/Button.res create mode 100644 tests/build_tests/rsc_nested_jsx_members/src/ButtonLayout.res create mode 100644 tests/syntax_tests/data/ppx/react/componentWithPropsInterface.resi create mode 100644 tests/syntax_tests/data/ppx/react/expected/componentWithPropsInterface.resi.txt create mode 100644 typechecker-performance-notes.md diff --git a/compiler/core/lam_compile_nested_component.ml b/compiler/core/lam_compile_nested_component.ml index f85e509ba8d..ab9776a391f 100644 --- a/compiler/core/lam_compile_nested_component.ml +++ b/compiler/core/lam_compile_nested_component.ml @@ -51,7 +51,7 @@ let nested_component_path id dynamic_import segments = let segments = match segments with | head :: rest - when head = id.name || head = root_name + when head = id.name || Ext_string.starts_with head (Ext_modulename.nested_component_prefix root_name) -> rest diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index df0183b23a8..463f8331f66 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -169,6 +169,25 @@ let maybe_hoist_nested_make_signature ~(config : Jsx_common.jsx_config) config.hoisted_signature_items <- full_sig :: config.hoisted_signature_items | _ -> () +let component_with_props_type ~loc core_type = + match core_type.ptyp_desc with + | Ptyp_arrow {arg = {lbl = Nolabel; typ}; _} -> typ + | _ -> + Jsx_common.raise_error ~loc + "@react.componentWithProps expects a function taking one props argument." + +let props_type_for_hoist nested_modules core_type = + match core_type.ptyp_desc with + | Ptyp_constr (({txt = Lident "props"} as lid), args) -> + { + core_type with + ptyp_desc = + Ptyp_constr + ( {lid with txt = props_longident_for_nested_module nested_modules}, + args ); + } + | _ -> core_type + (* Build a string representation of a nested component module name. *) let make_module_name file_name nested_modules fn_name = let file_name = unnamespace_module_name file_name in @@ -1119,16 +1138,57 @@ let transform_structure_item ~config item = ]) | _ -> [item] -let transform_signature_item ~config item = +let transform_signature_item ~(config : Jsx_common.jsx_config) item = match item with | { psig_loc; psig_desc = Psig_value ({pval_attributes; pval_type; pval_name} as psig_desc); } as psig -> ( - match List.filter Jsx_common.has_attr pval_attributes with - | [] -> [item] - | [_] -> + match + ( List.filter Jsx_common.has_attr pval_attributes, + List.filter Jsx_common.has_attr_with_props pval_attributes ) + with + | [], [] -> [item] + | [], [_] -> + let props_type = component_with_props_type ~loc:psig_loc pval_type in + let hoisted_props_type = + props_type_for_hoist config.nested_modules props_type + in + let new_external_type = + Ptyp_constr + ( {loc = psig_loc; txt = module_access_name config "component"}, + [props_type] ) + in + let new_external_type_for_hoist = + Ptyp_constr + ( {loc = psig_loc; txt = module_access_name config "component"}, + [hoisted_props_type] ) + in + let new_structure = + { + psig with + psig_desc = + Psig_value + { + psig_desc with + pval_type = {pval_type with ptyp_desc = new_external_type}; + pval_attributes = List.filter other_attrs_pure pval_attributes; + }; + } + in + let file_name = filename_from_loc psig_loc in + let empty_loc = Location.in_file file_name in + let full_module_name = + make_module_name file_name config.nested_modules pval_name.txt + in + let component_type = + {pval_type with ptyp_desc = new_external_type_for_hoist} + in + maybe_hoist_nested_make_signature ~config ~empty_loc ~full_module_name + ~component_type pval_name.txt; + [new_structure] + | [_], [] -> check_multiple_components ~config ~loc:psig_loc; check_string_int_attribute_iter.signature_item check_string_int_attribute_iter item; diff --git a/tests/build_tests/rsc_component_with_props_nested/src/Sidebar.resi b/tests/build_tests/rsc_component_with_props_nested/src/Sidebar.resi new file mode 100644 index 00000000000..1331e7ec965 --- /dev/null +++ b/tests/build_tests/rsc_component_with_props_nested/src/Sidebar.resi @@ -0,0 +1,6 @@ +module Provider: { + type props = {children: React.element} + + @react.componentWithProps + let make: props => React.element +} diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 7d08d73cfa9..8e2ef73e69a 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -39,6 +39,14 @@ const plainAccessOutputPath = path.join( "PlainAccess.res.js", ); const plainAccessOutput = await fs.readFile(plainAccessOutputPath, "utf8"); +const buttonLayoutOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "ButtonLayout.res.js"), + "utf8", +); +const buttonOutput = await fs.readFile( + path.join(import.meta.dirname, "src", "Button.res.js"), + "utf8", +); assert.match( output, @@ -96,5 +104,15 @@ assert.doesNotMatch( plainAccessOutput, /Sidebar\$RscNestedJsxMembers\.Provider/, ); +assert.match( + buttonLayoutOutput, + /JsxRuntime\.jsx\(Button\$RscNestedJsxMembers\.Button\$Button,/, +); +assert.doesNotMatch(buttonLayoutOutput, /\.Button\.make,/); +assert.match( + buttonOutput, + /let Button = \{[\s\S]*make: Button\$Button[\s\S]*\};/s, +); +assert.match(buttonOutput, /export \{[\s\S]*Button\$Button[\s\S]*\}/s); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members/src/Button.res b/tests/build_tests/rsc_nested_jsx_members/src/Button.res new file mode 100644 index 00000000000..75224faf4e0 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/Button.res @@ -0,0 +1,6 @@ +module Button = { + @react.component + let make = (~children) => { + children + } +} diff --git a/tests/build_tests/rsc_nested_jsx_members/src/ButtonLayout.res b/tests/build_tests/rsc_nested_jsx_members/src/ButtonLayout.res new file mode 100644 index 00000000000..a29e221aa96 --- /dev/null +++ b/tests/build_tests/rsc_nested_jsx_members/src/ButtonLayout.res @@ -0,0 +1,4 @@ +@react.component +let make = (~children) => { + {children} +} diff --git a/tests/syntax_tests/data/ppx/react/componentWithPropsInterface.resi b/tests/syntax_tests/data/ppx/react/componentWithPropsInterface.resi new file mode 100644 index 00000000000..021c1807341 --- /dev/null +++ b/tests/syntax_tests/data/ppx/react/componentWithPropsInterface.resi @@ -0,0 +1,8 @@ +@@jsxConfig({version: 4}) + +module Sidebar: { + type props = {children: React.element} + + @react.componentWithProps + let make: props => React.element +} diff --git a/tests/syntax_tests/data/ppx/react/expected/componentWithPropsInterface.resi.txt b/tests/syntax_tests/data/ppx/react/expected/componentWithPropsInterface.resi.txt new file mode 100644 index 00000000000..bcf0da54728 --- /dev/null +++ b/tests/syntax_tests/data/ppx/react/expected/componentWithPropsInterface.resi.txt @@ -0,0 +1,8 @@ +@@jsxConfig({version: 4}) + +module Sidebar: { + type props = {children: React.element} + + let make: React.component +} +let \"ComponentWithPropsInterface$Sidebar": React.component diff --git a/typechecker-performance-notes.md b/typechecker-performance-notes.md new file mode 100644 index 00000000000..ae3b7ecd5ae --- /dev/null +++ b/typechecker-performance-notes.md @@ -0,0 +1,144 @@ +# Typechecker Performance Notes + +These are rough notes on possible ways to make ReScript typechecking faster. +The short version: expect the biggest wins from profiling, caching, and reducing +frontend churn rather than from a new typechecking theory drop-in. + +## Most Promising Directions + +| Direction | Likely payoff | Why it matters | +|---|---:|---| +| Profile real projects | High | We need to know whether time is in parsing, JSX rewrite, `Ctype`, env lookup, CMI/CMJ loading, error formatting, or build orchestration. | +| Merlin-style incrementality | High for editor/watch | Merlin keeps pipeline state, caches parsed/typechecked items, and backtracks to valid environments after edits. | +| Better CMI/CMJ/env caching | Medium-high | Watch mode should avoid repeated loads, path normalization, and environment reconstruction across modules. | +| Reduce JSX PPX churn | Medium | JSX currently creates extra AST/lambda plumbing. A language-level JSX path could reduce work and complexity. | +| Query-based architecture | High long-term | Salsa/rust-analyzer-style queries are a strong model for IDE/watch mode, but adopting this is invasive. | +| Audit upstream OCaml typing changes | Unknown | Worth checking, but recent OCaml releases look more runtime/codegen/error-message heavy than typechecker-speed heavy. | + +## Practical Plan + +1. Add timing spans around the pipeline: + - parse + - JSX rewrite + - typecheck + - lambda + - JS emit + - CMI/CMJ loading + - dependency analysis + +2. Benchmark on representative inputs: + - real production apps + - large generated codebases + - JSX-heavy projects + - namespace-heavy projects + - pathological nested-module cases + +3. Compare against upstream OCaml and Merlin: + - scan `typing/` for performance-related changes we missed + - inspect Merlin’s pipeline caches and environment backtracking + - identify anything portable to the batch compiler or rewatch + +4. Optimize only the top hotspots. + +## Likely Wins + +### Watch Mode + +The most likely major win is avoiding repeated work between builds. ReScript’s +fast-refresh story depends on keeping small edits cheap, so incremental reuse is +more important than optimizing cold full builds. + +Good candidates: + +- Cache parsed ASTs where file contents are unchanged. +- Cache PPX/JSX rewrite results. +- Cache loaded CMI/CMJ metadata across rebuilds. +- Reuse environments for unchanged prefixes of a module. +- Avoid rebuilding dependency information for unaffected files. + +### JSX + +JSX is currently still PPX-shaped. That tends to create extra intermediate +structure and forces later compiler phases to recover intent from generated AST +and attributes. + +A language-level JSX implementation could: + +- avoid some PPX rewriting work, +- preserve component-path intent structurally, +- reduce lambda/JS rewrite heuristics, +- make diagnostics cleaner, +- reduce snapshot churn. + +This is probably more of a medium-term cleanup than a tactical speed patch. + +### Environment And Metadata Loading + +For large projects, repeated metadata work can dominate. Things to inspect: + +- `Env` construction and lookup patterns +- path normalization +- CMI loading +- CMJ loading +- namespace resolution +- repeated package/runtime metadata reads + +## Research And Existing Work + +### Merlin + +Merlin is probably the most relevant practical reference. It is designed around +interactive latency, incomplete programs, pipeline caching, and environment +backtracking. + +Things worth studying: + +- typed pipeline cache +- reader/parser cache +- PPX cache +- environment cache +- recovery after partial errors + +### Salsa / Query Systems + +Salsa and rust-analyzer model compiler work as memoized queries with tracked +dependencies. This is attractive for watch mode and language tooling. + +Potential benefits: + +- recompute only affected queries, +- make dependencies explicit, +- support IDE and build-system reuse, +- make performance easier to reason about. + +Cost: + +- very invasive architecture change, +- not a small compiler patch. + +### Incremental Typechecking Papers + +There is research on mechanically incrementalizing typing algorithms. It is +conceptually relevant, but likely less immediately useful than Merlin/Salsa-style +engineering. + +Useful takeaway: + +- Retype only changed subtrees where possible. +- Make dependencies explicit. +- Cache derivations/results with stable names. +- Be careful about invalidation boundaries. + +## Current Bet + +The biggest realistic ReScript win is probably: + +> Less repeated work in watch mode, plus less generated PPX/lambda junk for JSX. + +Not: + +> A radically cleverer unification algorithm. + +The language already has a good performance profile because the type system is +strong but not too ambitious. We should protect that and improve the incremental +pipeline around it. From 2821909f23d829755fac04781439e95d3607c9d9 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 30 Apr 2026 13:03:58 +0200 Subject: [PATCH 51/56] Whoops --- typechecker-performance-notes.md | 144 ------------------------------- 1 file changed, 144 deletions(-) delete mode 100644 typechecker-performance-notes.md diff --git a/typechecker-performance-notes.md b/typechecker-performance-notes.md deleted file mode 100644 index ae3b7ecd5ae..00000000000 --- a/typechecker-performance-notes.md +++ /dev/null @@ -1,144 +0,0 @@ -# Typechecker Performance Notes - -These are rough notes on possible ways to make ReScript typechecking faster. -The short version: expect the biggest wins from profiling, caching, and reducing -frontend churn rather than from a new typechecking theory drop-in. - -## Most Promising Directions - -| Direction | Likely payoff | Why it matters | -|---|---:|---| -| Profile real projects | High | We need to know whether time is in parsing, JSX rewrite, `Ctype`, env lookup, CMI/CMJ loading, error formatting, or build orchestration. | -| Merlin-style incrementality | High for editor/watch | Merlin keeps pipeline state, caches parsed/typechecked items, and backtracks to valid environments after edits. | -| Better CMI/CMJ/env caching | Medium-high | Watch mode should avoid repeated loads, path normalization, and environment reconstruction across modules. | -| Reduce JSX PPX churn | Medium | JSX currently creates extra AST/lambda plumbing. A language-level JSX path could reduce work and complexity. | -| Query-based architecture | High long-term | Salsa/rust-analyzer-style queries are a strong model for IDE/watch mode, but adopting this is invasive. | -| Audit upstream OCaml typing changes | Unknown | Worth checking, but recent OCaml releases look more runtime/codegen/error-message heavy than typechecker-speed heavy. | - -## Practical Plan - -1. Add timing spans around the pipeline: - - parse - - JSX rewrite - - typecheck - - lambda - - JS emit - - CMI/CMJ loading - - dependency analysis - -2. Benchmark on representative inputs: - - real production apps - - large generated codebases - - JSX-heavy projects - - namespace-heavy projects - - pathological nested-module cases - -3. Compare against upstream OCaml and Merlin: - - scan `typing/` for performance-related changes we missed - - inspect Merlin’s pipeline caches and environment backtracking - - identify anything portable to the batch compiler or rewatch - -4. Optimize only the top hotspots. - -## Likely Wins - -### Watch Mode - -The most likely major win is avoiding repeated work between builds. ReScript’s -fast-refresh story depends on keeping small edits cheap, so incremental reuse is -more important than optimizing cold full builds. - -Good candidates: - -- Cache parsed ASTs where file contents are unchanged. -- Cache PPX/JSX rewrite results. -- Cache loaded CMI/CMJ metadata across rebuilds. -- Reuse environments for unchanged prefixes of a module. -- Avoid rebuilding dependency information for unaffected files. - -### JSX - -JSX is currently still PPX-shaped. That tends to create extra intermediate -structure and forces later compiler phases to recover intent from generated AST -and attributes. - -A language-level JSX implementation could: - -- avoid some PPX rewriting work, -- preserve component-path intent structurally, -- reduce lambda/JS rewrite heuristics, -- make diagnostics cleaner, -- reduce snapshot churn. - -This is probably more of a medium-term cleanup than a tactical speed patch. - -### Environment And Metadata Loading - -For large projects, repeated metadata work can dominate. Things to inspect: - -- `Env` construction and lookup patterns -- path normalization -- CMI loading -- CMJ loading -- namespace resolution -- repeated package/runtime metadata reads - -## Research And Existing Work - -### Merlin - -Merlin is probably the most relevant practical reference. It is designed around -interactive latency, incomplete programs, pipeline caching, and environment -backtracking. - -Things worth studying: - -- typed pipeline cache -- reader/parser cache -- PPX cache -- environment cache -- recovery after partial errors - -### Salsa / Query Systems - -Salsa and rust-analyzer model compiler work as memoized queries with tracked -dependencies. This is attractive for watch mode and language tooling. - -Potential benefits: - -- recompute only affected queries, -- make dependencies explicit, -- support IDE and build-system reuse, -- make performance easier to reason about. - -Cost: - -- very invasive architecture change, -- not a small compiler patch. - -### Incremental Typechecking Papers - -There is research on mechanically incrementalizing typing algorithms. It is -conceptually relevant, but likely less immediately useful than Merlin/Salsa-style -engineering. - -Useful takeaway: - -- Retype only changed subtrees where possible. -- Make dependencies explicit. -- Cache derivations/results with stable names. -- Be careful about invalidation boundaries. - -## Current Bet - -The biggest realistic ReScript win is probably: - -> Less repeated work in watch mode, plus less generated PPX/lambda junk for JSX. - -Not: - -> A radically cleverer unification algorithm. - -The language already has a good performance profile because the type system is -strong but not too ambitious. We should protect that and improve the incremental -pipeline around it. From 61129c406dcdc8158436aa50c74c4332055ccc1a Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 30 Apr 2026 19:57:32 +0200 Subject: [PATCH 52/56] Simplify nested JSX component lowering Signed-off-by: Florian Hammerschmidt --- CHANGELOG.md | 2 +- compiler/core/js_of_lam_block.ml | 4 +- .../core/js_pass_nested_component_exports.ml | 204 --------------- .../core/js_pass_nested_component_exports.mli | 28 -- compiler/core/lam.ml | 6 +- compiler/core/lam_analysis.ml | 7 +- compiler/core/lam_arity_analysis.ml | 4 +- compiler/core/lam_compat.ml | 2 +- compiler/core/lam_compat.mli | 2 +- compiler/core/lam_compile.ml | 89 +------ compiler/core/lam_compile_main.ml | 3 - compiler/core/lam_compile_nested_component.ml | 247 ------------------ compiler/core/lam_pass_remove_alias.ml | 3 +- compiler/core/lam_print.ml | 2 +- compiler/ml/lambda.ml | 26 +- compiler/ml/lambda.mli | 5 +- compiler/ml/printlambda.ml | 2 +- compiler/ml/translcore.ml | 44 +++- compiler/ml/translmod.ml | 15 +- .../src/abstract_component_test.res.js | 2 + .../react_ppx/src/gpr_3987_test.res.js | 3 + .../react_ppx/src/issue_7917_test.res.js | 1 + .../recursive_explicit_component_test.res.js | 2 + .../rsc_component_with_props_members/input.js | 10 +- .../rsc_dynamic_import_nested_jsx/input.js | 7 +- .../rsc_nested_jsx_alias_chain/input.js | 5 +- .../build_tests/rsc_nested_jsx_deep/input.js | 4 +- .../rsc_nested_jsx_members/input.js | 5 +- .../typescript-react-example/src/Hooks.res.js | 4 + .../typescript-react-example/src/JSXV4.res.js | 1 + 30 files changed, 87 insertions(+), 652 deletions(-) delete mode 100644 compiler/core/js_pass_nested_component_exports.ml delete mode 100644 compiler/core/js_pass_nested_component_exports.mli delete mode 100644 compiler/core/lam_compile_nested_component.ml diff --git a/CHANGELOG.md b/CHANGELOG.md index e306e7aa534..28b37f19214 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ #### :boom: Breaking Change -- Nested React component modules now export only the namespaced component value instead of a public `make` wrapper, and GenType mirrors that new boundary shape. https://github.com/rescript-lang/rescript/pull/8293 - Make Jsx.component abstract. https://github.com/rescript-lang/rescript/pull/8390 #### :eyeglasses: Spec Compliance @@ -23,6 +22,7 @@ #### :bug: Bug fix +- JSX v4 nested React component tags now use generated direct component exports when available, while preserving normal nested module exports. https://github.com/rescript-lang/rescript/pull/8293 - Fix directive `@warning("-102")` not working. https://github.com/rescript-lang/rescript/pull/8322 - Fix duplicated comments in `for`..`of` formatter. https://github.com/rescript-lang/rescript/pull/8395 diff --git a/compiler/core/js_of_lam_block.ml b/compiler/core/js_of_lam_block.ml index 981bf1a231c..850e8ad2f94 100644 --- a/compiler/core/js_of_lam_block.ml +++ b/compiler/core/js_of_lam_block.ml @@ -43,9 +43,7 @@ let field (field_info : Lam_compat.field_dbg_info) e (i : int32) = | Fld_cons -> E.cons_access e i | Fld_record_inline {name} -> E.inline_record_access e name i | Fld_record {name} -> E.record_access e name i - | Fld_module {name; jsx_component = true} -> - if !Js_config.jsx_preserve then E.dot e name else E.module_access e name i - | Fld_module {name; jsx_component = false} -> E.module_access e name i + | Fld_module {name} -> E.module_access e name i let field_by_exp e i = E.array_index e i diff --git a/compiler/core/js_pass_nested_component_exports.ml b/compiler/core/js_pass_nested_component_exports.ml deleted file mode 100644 index 5ed70aca6d8..00000000000 --- a/compiler/core/js_pass_nested_component_exports.ml +++ /dev/null @@ -1,204 +0,0 @@ -(* Copyright (C) 2026 - Authors of ReScript - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * In addition to the permissions granted to you by the LGPL, you may combine - * or link a "work that uses the Library" with a publicly distributed version - * of this file to produce a combined library or application, then distribute - * that combined work under the terms of your choosing, with no requirement - * to comply with the obligations normally placed on you by section 4 of the - * LGPL version 3 (or the corresponding section of a later version of the LGPL - * should you choose to use a later version). - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) - -module E = Js_exp_make - -module StringSet = Set.Make (String) - -type candidate = {module_ident: Ident.t} - -let hidden_component_suffix (module_ident : Ident.t) = - Ext_modulename.nested_component_suffix (Ident.name module_ident) - -let is_hidden_component_name_for module_ident ident = - Ext_string.ends_with (Ident.name ident) (hidden_component_suffix module_ident) - -let find_hidden_alias_by_value block module_ident value_ident = - List.find_map - (fun (st : J.statement) -> - match st.statement_desc with - | Variable - { - ident; - value = Some {expression_desc = Var (Id target); _}; - property = _; - ident_info = _; - } - when Ident.same target value_ident - && is_hidden_component_name_for module_ident ident -> - Some ident - | _ -> None) - block - -let has_export_name exports name = - List.exists - (fun (ident : Ident.t) -> String.equal (Ident.name ident) name) - exports - -let candidate_of_statement block exports (st : J.statement) = - match st.statement_desc with - | Variable - { - ident = module_ident; - value = - Some - { - expression_desc = - Caml_block - ( [{expression_desc = Var (Id value_ident); _}], - Immutable, - _, - Blk_module ["make"] ); - _; - }; - property = _; - ident_info = _; - } -> ( - let hidden_ident = - if is_hidden_component_name_for module_ident value_ident then - Some value_ident - else find_hidden_alias_by_value block module_ident value_ident - in - match hidden_ident with - | Some hidden_ident when has_export_name exports hidden_ident.name -> - Some {module_ident} - | Some _ | None -> None) - | _ -> None - -let collect_candidates block exports = - Ext_list.filter_map block (candidate_of_statement block exports) - -let hidden_export_names_to_remove candidates = - Ext_list.fold_left candidates StringSet.empty (fun acc candidate -> - StringSet.add (Ident.name candidate.module_ident) acc) - -let chop_js_suffix basename = - match String.index_opt basename '.' with - | Some index -> String.sub basename 0 index - | None -> basename - -let dynamic_import_module_root (expr : J.expression) = - match expr.expression_desc with - | Await - { - expression_desc = - Call ({expression_desc = Var (Id import_ident); _}, [arg], _); - _; - } - when String.equal import_ident.name "import" -> ( - match arg.expression_desc with - | Str {txt; _} -> Some (Filename.basename txt |> chop_js_suffix) - | _ -> None) - | _ -> None - -let known_hidden_component_exports block = - let hidden_exports = ref StringSet.empty in - let mapper = - { - Js_record_map.super with - expression = - (fun self expr -> - (match expr.expression_desc with - | Var (Qualified (_, Some name)) -> - hidden_exports := StringSet.add name !hidden_exports - | Static_index (_, name, _) - when String.contains name - Ext_modulename.nested_component_separator_char -> - hidden_exports := StringSet.add name !hidden_exports - | _ -> ()); - Js_record_map.super.expression self expr); - } - in - ignore (mapper.block mapper block); - !hidden_exports - -let dynamic_import_aliases block = - List.fold_left - (fun aliases (st : J.statement) -> - match st.statement_desc with - | Variable {ident; value = Some expr; _} -> ( - match dynamic_import_module_root expr with - | Some module_root -> Map_ident.add aliases ident module_root - | None -> aliases) - | _ -> aliases) - Map_ident.empty block - -let rewrite_dynamic_import_component_access aliases known_hidden_exports - (expr : J.expression) = - let rec collect_segments segments (expr : J.expression) = - match expr.expression_desc with - | Static_index (inner, field, _) -> - collect_segments (field :: segments) inner - | Var (Id id) -> ( - match Map_ident.find_opt aliases id with - | Some module_root when segments <> [] -> Some (id, module_root, segments) - | Some _ | None -> None) - | _ -> None - in - match expr.expression_desc with - | Static_index (inner, "make", _) -> ( - match collect_segments [] inner with - | Some (id, module_root, segments) -> - let hidden_name = - match segments with - | first :: _ - when Ext_string.starts_with first - (Ext_modulename.nested_component_prefix module_root) -> - Ext_modulename.concat_nested_component_name segments - | _ -> - Ext_modulename.concat_nested_component_name (module_root :: segments) - in - if StringSet.mem hidden_name known_hidden_exports then - {expr with expression_desc = Static_index (E.var id, hidden_name, None)} - else expr - | None -> expr) - | _ -> expr - -let rewrite_dynamic_import_block block = - let aliases = dynamic_import_aliases block in - if Map_ident.is_empty aliases then block - else - let known_hidden_exports = known_hidden_component_exports block in - let mapper = - { - Js_record_map.super with - expression = - (fun self expr -> - let expr = Js_record_map.super.expression self expr in - rewrite_dynamic_import_component_access aliases known_hidden_exports - expr); - } - in - mapper.block mapper block - -let program (js : J.program) : J.program = - let candidates = collect_candidates js.block js.exports in - let removed_export_names = hidden_export_names_to_remove candidates in - let exports = - Ext_list.filter js.exports (fun (ident : Ident.t) -> - not (StringSet.mem (Ident.name ident) removed_export_names)) - in - let export_set = Set_ident.of_list exports in - let block = rewrite_dynamic_import_block js.block in - {J.block; exports; export_set} diff --git a/compiler/core/js_pass_nested_component_exports.mli b/compiler/core/js_pass_nested_component_exports.mli deleted file mode 100644 index dc90a18a772..00000000000 --- a/compiler/core/js_pass_nested_component_exports.mli +++ /dev/null @@ -1,28 +0,0 @@ -(* Copyright (C) 2026 - Authors of ReScript - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * In addition to the permissions granted to you by the LGPL, you may combine - * or link a "work that uses the Library" with a publicly distributed version - * of this file to produce a combined library or application, then distribute - * that combined work under the terms of your choosing, with no requirement - * to comply with the obligations normally placed on you by section 4 of the - * LGPL version 3 (or the corresponding section of a later version of the LGPL - * should you choose to use a later version). - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) - -(* Rewrite nested JSX component module exports from `{make: fn}` wrappers to - direct component exports, while keeping `.make` as a self-reference for - compatibility with existing generated call sites. *) -val program : J.program -> J.program diff --git a/compiler/core/lam.ml b/compiler/core/lam.ml index 4d41c4fc9bb..15875608b97 100644 --- a/compiler/core/lam.ml +++ b/compiler/core/lam.ml @@ -581,8 +581,7 @@ let prim ~primitive:(prim : Lam_primitive.t) ~args loc : t = | ( f :: fields, Lprim { - primitive = - Pfield (pos, Fld_module {name = f1; jsx_component = false}); + primitive = Pfield (pos, Fld_module {name = f1}); args = [(Lglobal_module (v1, _) | Lvar v1)]; } :: args ) -> @@ -593,8 +592,7 @@ let prim ~primitive:(prim : Lam_primitive.t) ~args loc : t = | ( field1 :: rest, Lprim { - primitive = - Pfield (pos, Fld_module {name = f1; jsx_component = false}); + primitive = Pfield (pos, Fld_module {name = f1}); args = [((Lglobal_module (v1, _) | Lvar v1) as lam)]; } :: args1 ) -> diff --git a/compiler/core/lam_analysis.ml b/compiler/core/lam_analysis.ml index 7625994a439..a6b815fdad2 100644 --- a/compiler/core/lam_analysis.ml +++ b/compiler/core/lam_analysis.ml @@ -125,12 +125,7 @@ let rec no_side_effects (lam : Lam.t) : bool = (* | Lsend _ -> false *) | Lapply { - ap_func = - Lprim - { - primitive = - Pfield (_, Fld_module {name = "from_fun"; jsx_component = _}); - }; + ap_func = Lprim {primitive = Pfield (_, Fld_module {name = "from_fun"})}; ap_args = [arg]; } -> no_side_effects arg diff --git a/compiler/core/lam_arity_analysis.ml b/compiler/core/lam_arity_analysis.ml index 27584564a76..5a5d4bbccda 100644 --- a/compiler/core/lam_arity_analysis.ml +++ b/compiler/core/lam_arity_analysis.ml @@ -42,7 +42,7 @@ let rec get_arity (meta : Lam_stats.t) (lam : Lam.t) : Lam_arity.t = | Llet (_, _, _, l) -> get_arity meta l | Lprim { - primitive = Pfield (_, Fld_module {name; jsx_component = _}); + primitive = Pfield (_, Fld_module {name}); args = [Lglobal_module (id, dynamic_import)]; _; } -> ( @@ -58,7 +58,7 @@ let rec get_arity (meta : Lam_stats.t) (lam : Lam.t) : Lam_arity.t = [ Lprim { - primitive = Pfield (_, Fld_module {name; jsx_component = _}); + primitive = Pfield (_, Fld_module {name}); args = [Lglobal_module (id, dynamic_import)]; }; ]; diff --git a/compiler/core/lam_compat.ml b/compiler/core/lam_compat.ml index 70c3486c5e9..a652d74ca43 100644 --- a/compiler/core/lam_compat.ml +++ b/compiler/core/lam_compat.ml @@ -64,7 +64,7 @@ type let_kind = Lambda.let_kind = Strict | Alias | StrictOpt | Variable type field_dbg_info = Lambda.field_dbg_info = | Fld_record of {name: string; mutable_flag: Asttypes.mutable_flag} - | Fld_module of {name: string; jsx_component: bool} + | Fld_module of {name: string} | Fld_record_inline of {name: string} | Fld_record_extension of {name: string} | Fld_tuple diff --git a/compiler/core/lam_compat.mli b/compiler/core/lam_compat.mli index cbea5cd5ea8..4d67d95242e 100644 --- a/compiler/core/lam_compat.mli +++ b/compiler/core/lam_compat.mli @@ -28,7 +28,7 @@ type let_kind = Lambda.let_kind = Strict | Alias | StrictOpt | Variable type field_dbg_info = Lambda.field_dbg_info = | Fld_record of {name: string; mutable_flag: Asttypes.mutable_flag} - | Fld_module of {name: string; jsx_component: bool} + | Fld_module of {name: string} | Fld_record_inline of {name: string} | Fld_record_extension of {name: string} | Fld_tuple diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 1be69e05c2e..9897f0c01e4 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -232,20 +232,6 @@ type initialization = J.block *) let compile output_prefix = - let local_module_aliases : Lam.t Map_ident.t ref = ref Map_ident.empty in - let rewrite_nested_jsx_component_expr (jsx_tag : Lam.t) - (compiled_expr : J.expression) : J.expression = - Lam_compile_nested_component.rewrite_jsx_component_expr local_module_aliases - jsx_tag compiled_expr - in - let rewrite_transformed_jsx_args (appinfo : Lam.apply) args = - if appinfo.ap_transformed_jsx then - match (appinfo.ap_args, args) with - | jsx_tag :: _, jsx_expr :: rest_args -> - rewrite_nested_jsx_component_expr jsx_tag jsx_expr :: rest_args - | _ -> args - else args - in let rec compile_external_field (* Like [List.empty]*) ?(dynamic_import = false) (lamba_cxt : Lam_compile_context.t) (id : Ident.t) name : Js_output.t = @@ -314,7 +300,7 @@ let compile output_prefix = (Ext_list.append block args_code, b :: args) | _ -> assert false) in - let args = rewrite_transformed_jsx_args appinfo args in + let fn = E.ml_var_dot ~dynamic_import module_id ident_info.name in let expression = match appinfo.ap_info.ap_status with @@ -1573,7 +1559,7 @@ let compile output_prefix = }; } -> ( match fld_info with - | Fld_module {name; jsx_component = _} -> + | Fld_module {name} -> compile_external_field_apply ~dynamic_import appinfo id name lambda_cxt | _ -> assert false) | _ -> ( @@ -1592,7 +1578,6 @@ let compile output_prefix = (Ext_list.append block args_code, b :: fn_code) | {value = None} -> assert false) in - let args = rewrite_transformed_jsx_args appinfo args in match (ap_func, lambda_cxt.continuation) with | ( Lvar fn_id, ( EffectCall (Maybe_tail_is_return (Tail_with_name {label = Some ret})) @@ -1653,60 +1638,6 @@ let compile output_prefix = and compile_prim (prim_info : Lam.prim_info) (lambda_cxt : Lam_compile_context.t) = match prim_info with - | { - primitive = - Pjs_call - { - prim_name = "jsx" | "jsxs" | "jsxKeyed" | "jsxsKeyed"; - transformed_jsx = true; - _; - }; - args = jsx_tag :: rest_args; - loc; - } -> - let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in - let tag_block, tag_expr = - match compile_lambda new_cxt jsx_tag with - | {block; value = Some b} -> - (block, rewrite_nested_jsx_component_expr jsx_tag b) - | {value = None} -> assert false - in - let rest_blocks, rest_exprs = - Ext_list.split_map rest_args (fun x -> - match compile_lambda new_cxt x with - | {block; value = Some b} -> (block, b) - | {value = None} -> assert false) - in - let args_code : J.block = List.concat (tag_block :: rest_blocks) in - let exp = - Lam_compile_primitive.translate output_prefix loc lambda_cxt - prim_info.primitive (tag_expr :: rest_exprs) - in - Js_output.output_of_block_and_expression lambda_cxt.continuation args_code - exp - | { - primitive = - Pfield (pos, (Fld_module {name = "make"; jsx_component = _} as fld_info)); - args = [arg]; - _; - } -> ( - match arg with - | Lglobal_module (id, dynamic_import) -> - compile_external_field ~dynamic_import lambda_cxt id "make" - | _ -> ( - let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in - match compile_lambda new_cxt arg with - | {block; value = Some compiled_arg} -> - let compiled_expr = - Js_of_lam_block.field fld_info compiled_arg (Int32.of_int pos) - in - let compiled_expr = - Lam_compile_nested_component.rewrite_component_make_expr - local_module_aliases arg compiled_expr - in - Js_output.output_of_block_and_expression lambda_cxt.continuation block - compiled_expr - | {value = None} -> assert false)) | { primitive = Pfield (_, fld_info); args = [Lglobal_module (id, dynamic_import)]; @@ -1714,7 +1645,7 @@ let compile output_prefix = } -> ( (* should be before Lglobal_global *) match fld_info with - | Fld_module {name = field; jsx_component = _} -> + | Fld_module {name = field} -> compile_external_field ~dynamic_import lambda_cxt id field | _ -> assert false) | {primitive = Praise; args = [e]; _} -> ( @@ -1935,19 +1866,7 @@ let compile output_prefix = {lambda_cxt with continuation = Declare (let_kind, id)} arg in - let body_output = - if - Lam_compile_nested_component.is_module_alias_candidate - local_module_aliases arg - then ( - let previous_aliases = !local_module_aliases in - local_module_aliases := Map_ident.add previous_aliases id arg; - Fun.protect - ~finally:(fun () -> local_module_aliases := previous_aliases) - (fun () -> compile_lambda lambda_cxt body)) - else compile_lambda lambda_cxt body - in - Js_output.append_output args_code body_output + Js_output.append_output args_code (compile_lambda lambda_cxt body) | Lletrec (id_args, body) -> (* There is a bug in our current design, it requires compile args first (register that some objects are jsidentifiers) diff --git a/compiler/core/lam_compile_main.ml b/compiler/core/lam_compile_main.ml index ecd64bcccbd..cdecf32ef8e 100644 --- a/compiler/core/lam_compile_main.ml +++ b/compiler/core/lam_compile_main.ml @@ -263,9 +263,6 @@ js |> (fun js -> ignore @@ Js_pass_scope.program js ; js ) |> Js_shake.shake_program |> _j "shake" -|> (fun js -> - if !Js_config.cmj_only then js - else Js_pass_nested_component_exports.program js) |> ( fun (program: J.program) -> let external_module_ids : Lam_module_ident.t list = if !Js_config.all_module_aliases then [] diff --git a/compiler/core/lam_compile_nested_component.ml b/compiler/core/lam_compile_nested_component.ml deleted file mode 100644 index ab9776a391f..00000000000 --- a/compiler/core/lam_compile_nested_component.ml +++ /dev/null @@ -1,247 +0,0 @@ -(* Copyright (C) 2026 - Authors of ReScript - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * In addition to the permissions granted to you by the LGPL, you may combine - * or link a "work that uses the Library" with a publicly distributed version - * of this file to produce a combined library or application, then distribute - * that combined work under the terms of your choosing, with no requirement - * to comply with the obligations normally placed on you by section 4 of the - * LGPL version 3 (or the corresponding section of a later version of the LGPL - * should you choose to use a later version). - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) - -module E = Js_exp_make - -let root_module_name (id : Ident.t) = - match Ext_namespace.try_split_module_name id.name with - | Some (_namespace, module_name) -> module_name - | None -> ( - match - String.index_opt id.name Ext_modulename.nested_component_separator_char - with - | Some index -> String.sub id.name 0 index - | None -> id.name) - -let nested_component_path id dynamic_import segments = - let root_name = root_module_name id in - let denamespace_segment segment = - let namespaced_prefix = Ext_modulename.nested_component_prefix root_name in - if Ext_string.starts_with segment namespaced_prefix then - match - String.split_on_char Ext_modulename.nested_component_separator_char - segment - with - | root :: _namespace :: rest when rest <> [] -> - Ext_modulename.concat_nested_component_name (root :: rest) - | _ -> segment - else segment - in - let segments = - match segments with - | head :: rest - when head = id.name - || Ext_string.starts_with head - (Ext_modulename.nested_component_prefix root_name) -> - rest - | _ -> segments - in - match segments with - | [] -> None - | head :: rest -> - Some - ( id, - dynamic_import, - Ext_modulename.concat_nested_component_name - (root_name :: denamespace_segment head :: rest) ) - -let rec extract_component_segments aliases ~allow_import ~allow_unbound_var - segments (lam : Lam.t) : (Ident.t * bool * string list) option = - match lam with - | Lprim - { - primitive = Pfield (_, Fld_module {name; jsx_component = _}); - args = [arg]; - _; - } -> - extract_component_segments aliases ~allow_import ~allow_unbound_var - (name :: segments) arg - | Lprim {primitive = Pawait; args = [arg]; _} -> - extract_component_segments aliases ~allow_import ~allow_unbound_var segments - arg - | Lprim {primitive = Pimport; args = [arg]; _} when allow_import -> - extract_component_segments aliases ~allow_import ~allow_unbound_var segments - arg - | Lvar id -> ( - match Map_ident.find_opt !aliases id with - | Some alias_lam -> - extract_component_segments aliases ~allow_import ~allow_unbound_var - segments alias_lam - | None -> if allow_unbound_var then Some (id, false, segments) else None) - | Lglobal_module (id, dynamic_import) -> Some (id, dynamic_import, segments) - | _ -> None - -let rec is_module_alias_candidate aliases (lam : Lam.t) = - match lam with - | Lglobal_module _ -> true - | Lvar id -> Map_ident.mem !aliases id - | Lprim {primitive = Pfield (_, Fld_module _); args = [arg]; _} - | Lprim {primitive = Pawait | Pimport; args = [arg]; _} -> - is_module_alias_candidate aliases arg - | _ -> false - -let extract_nested_external_component_path aliases (lam : Lam.t) : - (Ident.t * bool * string) option = - match - extract_component_segments aliases ~allow_import:false - ~allow_unbound_var:true [] lam - with - | Some (id, dynamic_import, segments) -> - nested_component_path id dynamic_import segments - | None -> None - -let extract_nested_external_component_field aliases (lam : Lam.t) : - (Ident.t * bool * string) option = - match lam with - | Lprim - { - primitive = Pfield (_, Fld_module {name = "make"; jsx_component = _}); - args = [arg]; - _; - } -> - extract_nested_external_component_path aliases arg - | _ -> None - -let extract_static_nested_external_component_path aliases (lam : Lam.t) : - (Ident.t * bool * string) option = - match - extract_component_segments aliases ~allow_import:true - ~allow_unbound_var:false [] lam - with - | Some (id, dynamic_import, segments) -> - nested_component_path id dynamic_import segments - | None -> None - -let normalize_hidden_component_name (id : Ident.t) (hidden_name : string) = - let root_name = root_module_name id in - let id_parts = - String.split_on_char Ext_modulename.nested_component_separator_char id.name - in - let namespace_parts = - match id_parts with - | _root :: rest -> rest - | [] -> [] - in - let hidden_parts = - String.split_on_char Ext_modulename.nested_component_separator_char - hidden_name - in - let hidden_parts_without_root = - match hidden_parts with - | first :: rest when String.equal first root_name -> rest - | _ -> hidden_parts - in - let rec drop_prefix prefix parts = - match (prefix, parts) with - | [], _ -> parts - | x :: xs, y :: ys when String.equal x y -> drop_prefix xs ys - | _ -> parts - in - let tail = drop_prefix namespace_parts hidden_parts_without_root in - match tail with - | [] -> hidden_name - | _ -> Ext_modulename.concat_nested_component_name (root_name :: tail) - -let hidden_component_name_candidates (id : Ident.t) (hidden_name : string) = - let candidates = ref [] in - let push candidate = - if not (List.mem candidate !candidates) then - candidates := candidate :: !candidates - in - (match - String.split_on_char Ext_modulename.nested_component_separator_char - hidden_name - with - | root :: _namespace :: rest when rest <> [] -> - push (Ext_modulename.concat_nested_component_name (root :: rest)) - | _ -> ()); - push (normalize_hidden_component_name id hidden_name); - push hidden_name; - List.rev !candidates - -let exported_hidden_component_name ~(id : Ident.t) ~(dynamic_import : bool) - hidden_name_candidates = - let rec loop = function - | [] -> None - | candidate :: rest -> ( - match - Lam_compile_env.query_external_id_info ~dynamic_import id candidate - with - | _ -> Some candidate - | exception Not_found -> loop rest) - in - loop hidden_name_candidates - -let rec extract_root_expr (expr : J.expression) = - match expr.expression_desc with - | Var (Qualified (module_id, Some _)) -> - Some {expr with expression_desc = Var (Qualified (module_id, None))} - | Static_index (inner, _, _) -> extract_root_expr inner - | Var _ -> Some expr - | _ -> None - -let hidden_component_access (root_expr : J.expression) hidden_name = - match root_expr.expression_desc with - | Var (Qualified (module_id, None)) -> - { - root_expr with - expression_desc = Var (Qualified (module_id, Some hidden_name)); - } - | _ -> E.dot root_expr hidden_name - -let rewrite_jsx_component_expr aliases (jsx_tag : Lam.t) - (compiled_expr : J.expression) : J.expression = - match extract_nested_external_component_field aliases jsx_tag with - | Some (id, dynamic_import, hidden_name) -> ( - let hidden_name_candidates = - hidden_component_name_candidates id hidden_name - in - match extract_root_expr compiled_expr with - | Some root_expr -> ( - match - exported_hidden_component_name ~id ~dynamic_import - hidden_name_candidates - with - | Some hidden_name -> hidden_component_access root_expr hidden_name - | None -> compiled_expr) - | None -> compiled_expr) - | None -> compiled_expr - -let rewrite_component_make_expr aliases (lam : Lam.t) - (compiled_expr : J.expression) : J.expression = - match extract_static_nested_external_component_path aliases lam with - | Some (id, dynamic_import, hidden_name) -> ( - let hidden_name_candidates = - hidden_component_name_candidates id hidden_name - in - match extract_root_expr compiled_expr with - | Some root_expr -> ( - match - exported_hidden_component_name ~id ~dynamic_import - hidden_name_candidates - with - | Some hidden_name -> hidden_component_access root_expr hidden_name - | None -> compiled_expr) - | None -> compiled_expr) - | None -> compiled_expr diff --git a/compiler/core/lam_pass_remove_alias.ml b/compiler/core/lam_pass_remove_alias.ml index 67bb577e95b..52a88ad02eb 100644 --- a/compiler/core/lam_pass_remove_alias.ml +++ b/compiler/core/lam_pass_remove_alias.ml @@ -133,8 +133,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = ap_func = Lprim { - primitive = - Pfield (_, Fld_module {name = fld_name; jsx_component = _}); + primitive = Pfield (_, Fld_module {name = fld_name}); args = [Lglobal_module (ident, dynamic_import)]; _; } as l1; diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index 28b427ffec2..42b9a294b30 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -323,7 +323,7 @@ let lambda ppf v = fprintf ppf ")@ %a)@]" lam body | Lprim { - primitive = Pfield (n, Fld_module {name = s; jsx_component = _}); + primitive = Pfield (n, Fld_module {name = s}); args = [Lglobal_module (id, dynamic_import)]; _; } -> diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index 75010b90017..a111bc388f8 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -113,7 +113,7 @@ let ref_tag_info : tag_info = type field_dbg_info = | Fld_record of {name: string; mutable_flag: Asttypes.mutable_flag} - | Fld_module of {name: string; jsx_component: bool} + | Fld_module of {name: string} | Fld_record_inline of {name: string} | Fld_record_extension of {name: string} | Fld_tuple @@ -134,8 +134,6 @@ let fld_record_extension (lbl : label) = Fld_record_extension {name = Ext_list.find_def lbl.lbl_attributes find_name lbl.lbl_name} -let fld_module ~name ~jsx_component = Fld_module {name; jsx_component} - let ref_field_info : field_dbg_info = Fld_record {name = "contents"; mutable_flag = Mutable} @@ -640,28 +638,11 @@ let rec transl_normal_path = function else Lvar id | Pdot (p, s, pos) -> Lprim - ( Pfield (pos, Fld_module {name = s; jsx_component = false}), + ( Pfield (pos, Fld_module {name = s}), [transl_normal_path p], Location.none ) | Papply _ -> assert false -let transl_jsx_path path = - let rec aux ~is_final = function - | Path.Pident id -> - if Ident.global id then Lprim (Pgetglobal id, [], Location.none) - else Lvar id - | Pdot (p, s, pos) -> - Lprim - ( Pfield - ( pos, - Fld_module - {name = s; jsx_component = is_final && String.equal s "make"} ), - [aux ~is_final:false p], - Location.none ) - | Papply _ -> assert false - in - aux ~is_final:true path - (* Translation of identifiers *) let transl_module_path ?(loc = Location.none) env path = @@ -670,9 +651,6 @@ let transl_module_path ?(loc = Location.none) env path = let transl_value_path ?(loc = Location.none) env path = transl_normal_path (Env.normalize_path_prefix (Some loc) env path) -let transl_jsx_value_path ?(loc = Location.none) env path = - transl_jsx_path (Env.normalize_path_prefix (Some loc) env path) - let transl_extension_path = transl_value_path (* Apply a substitution to a lambda-term. diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index afc9d13f887..b55caf8c364 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -84,7 +84,7 @@ val ref_tag_info : tag_info type field_dbg_info = | Fld_record of {name: string; mutable_flag: Asttypes.mutable_flag} - | Fld_module of {name: string; jsx_component: bool} + | Fld_module of {name: string} | Fld_record_inline of {name: string} | Fld_record_extension of {name: string} | Fld_tuple @@ -100,8 +100,6 @@ val fld_record_inline : Types.label_description -> field_dbg_info val fld_record_extension : Types.label_description -> field_dbg_info -val fld_module : name:string -> jsx_component:bool -> field_dbg_info - val ref_field_info : field_dbg_info type set_field_dbg_info = @@ -399,7 +397,6 @@ val transl_normal_path : Path.t -> lambda (* Path.t is already normal *) val transl_module_path : ?loc:Location.t -> Env.t -> Path.t -> lambda val transl_value_path : ?loc:Location.t -> Env.t -> Path.t -> lambda -val transl_jsx_value_path : ?loc:Location.t -> Env.t -> Path.t -> lambda val transl_extension_path : ?loc:Location.t -> Env.t -> Path.t -> lambda val subst_lambda : lambda Ident.tbl -> lambda -> lambda diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index c9ae26f1d77..27fa0452461 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -72,7 +72,7 @@ let string_of_loc_kind = function let str_of_field_info (fld_info : Lambda.field_dbg_info) = match fld_info with - | Fld_module {name; jsx_component = _} + | Fld_module {name} | Fld_record {name} | Fld_record_inline {name} | Fld_record_extension {name} -> diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 1e304702a18..f7a48d1f9ad 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -672,6 +672,38 @@ let has_jsx_component_path_attr (exp : Typedtree.expression) = (fun ({txt; _}, _) -> String.equal txt "res.jsxComponentPath") exp.exp_attributes +let nested_component_root_name module_name = + match Ext_namespace.try_split_module_name module_name with + | Some (_namespace, module_name) -> module_name + | None -> ( + match + String.index_opt module_name + Ext_modulename.nested_component_separator_char + with + | Some index -> String.sub module_name 0 index + | None -> module_name) + +let nested_jsx_component_export_path ~loc env path = + match Env.normalize_path_prefix (Some loc) env path |> Path.flatten with + | `Ok (root, segments) -> ( + match List.rev segments with + | "make" :: nested_modules_rev -> ( + match List.rev nested_modules_rev with + | [] -> None + | nested_modules -> ( + let hidden_name = + Ext_modulename.concat_nested_component_name + (nested_component_root_name (Ident.name root) :: nested_modules) + in + let hidden_path = + Path.Pdot (Path.Pident root, hidden_name, Path.nopos) + in + match Env.find_value hidden_path env with + | _ -> Some hidden_path + | exception Not_found -> None)) + | _ -> None) + | `Contains_apply -> None + let rec transl_exp e = Builtin_attributes.warning_scope ~ppwarning:false e.exp_attributes (fun () -> List.iter (Translattribute.check_attribute e) e.exp_attributes; @@ -681,10 +713,14 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = match e.exp_desc with | Texp_ident (_, _, {val_kind = Val_prim p}) -> transl_primitive e.exp_loc p e.exp_env e.exp_type - | Texp_ident (path, _, {val_kind = Val_reg}) -> - if has_jsx_component_path_attr e then - transl_jsx_value_path ~loc:e.exp_loc e.exp_env path - else transl_value_path ~loc:e.exp_loc e.exp_env path + | Texp_ident (path, _, {val_kind = Val_reg}) -> ( + match + if has_jsx_component_path_attr e then + nested_jsx_component_export_path ~loc:e.exp_loc e.exp_env path + else None + with + | Some path -> transl_value_path ~loc:e.exp_loc e.exp_env path + | None -> transl_value_path ~loc:e.exp_loc e.exp_env path) | Texp_constant cst -> Lconst (Const_base cst) | Texp_let (rec_flag, pat_expr_list, body) -> transl_let rec_flag pat_expr_list (transl_exp body) diff --git a/compiler/ml/translmod.ml b/compiler/ml/translmod.ml index caf2b47dde6..87471ac26bf 100644 --- a/compiler/ml/translmod.ml +++ b/compiler/ml/translmod.ml @@ -64,20 +64,14 @@ let rec apply_coercion loc strict (restr : Typedtree.module_coercion) arg = | Tcoerce_structure (pos_cc_list, id_pos_list, runtime_fields) -> Lambda.name_lambda strict arg (fun id -> let get_field_name name pos = - Lambda.Lprim - ( Pfield (pos, Fld_module {name; jsx_component = false}), - [Lvar id], - loc ) + Lambda.Lprim (Pfield (pos, Fld_module {name}), [Lvar id], loc) in let lam = Lambda.Lprim ( Pmakeblock (Blk_module runtime_fields), Ext_list.map2 pos_cc_list runtime_fields (fun (pos, cc) name -> apply_coercion loc Alias cc - (Lprim - ( Pfield (pos, Fld_module {name; jsx_component = false}), - [Lvar id], - loc ))), + (Lprim (Pfield (pos, Fld_module {name}), [Lvar id], loc))), loc ) in wrap_id_pos_list loc id_pos_list get_field_name lam) @@ -438,10 +432,7 @@ and transl_structure loc fields cc rootpath final_env = function Pgenval, id, Lprim - ( Pfield - ( pos, - Fld_module {name = Ident.name id; jsx_component = false} - ), + ( Pfield (pos, Fld_module {name = Ident.name id}), [Lvar mid], incl.incl_loc ), body ), diff --git a/tests/build_tests/react_ppx/src/abstract_component_test.res.js b/tests/build_tests/react_ppx/src/abstract_component_test.res.js index 2091b800b26..981baba07d5 100644 --- a/tests/build_tests/react_ppx/src/abstract_component_test.res.js +++ b/tests/build_tests/react_ppx/src/abstract_component_test.res.js @@ -25,6 +25,8 @@ let GenericRenderProp = { }; export { + PolyVariantLowerBound, + GenericRenderProp, Abstract_component_test$PolyVariantLowerBound, Abstract_component_test$GenericRenderProp, } diff --git a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js index 1d0586eff6c..33234612d44 100644 --- a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js @@ -57,6 +57,9 @@ JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError, { export { makeContainer, + Gpr3987ReproOk, + Gpr3987ReproOk2, + Gpr3987ReproError, Gpr_3987_test$Gpr3987ReproOk, Gpr_3987_test$Gpr3987ReproOk2, Gpr_3987_test$Gpr3987ReproError, diff --git a/tests/build_tests/react_ppx/src/issue_7917_test.res.js b/tests/build_tests/react_ppx/src/issue_7917_test.res.js index 169c1dee854..abc1d85a612 100644 --- a/tests/build_tests/react_ppx/src/issue_7917_test.res.js +++ b/tests/build_tests/react_ppx/src/issue_7917_test.res.js @@ -19,6 +19,7 @@ function Issue_7917_test(props) { let make = Issue_7917_test; export { + M, make, Issue_7917_test$M, } diff --git a/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js index b0f675b80af..0290fd6c0c6 100644 --- a/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js @@ -50,7 +50,9 @@ let componentWithPropsElement = React.createElement(Recursive_explicit_component }); export { + SelfCreateElement, RawSiblingCreateElement, + ComponentWithProps, componentWithPropsElement, Recursive_explicit_component_test$SelfCreateElement, Recursive_explicit_component_test$RawSiblingCreateElement, diff --git a/tests/build_tests/rsc_component_with_props_members/input.js b/tests/build_tests/rsc_component_with_props_members/input.js index 74e28a26632..2d09b00a673 100644 --- a/tests/build_tests/rsc_component_with_props_members/input.js +++ b/tests/build_tests/rsc_component_with_props_members/input.js @@ -51,21 +51,19 @@ assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Inset\$jsx/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider;/, + /let provider = Sidebar\$RscComponentWithPropsMembers\.Provider\.make;/, ); assert.match( plainAccessOutput, - /let inset = Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset;/, + /let inset = Sidebar\$RscComponentWithPropsMembers\.Inset\.make;/, ); -assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); -assert.doesNotMatch(plainAccessOutput, /\.Inset\.make/); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscComponentWithPropsMembers\.Provider/, + /Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscComponentWithPropsMembers\.Inset/, + /Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset/, ); await execClean(); diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js index 9c8a7c8a6d6..4856beef38a 100644 --- a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js @@ -21,17 +21,14 @@ assert.match( output, /let DynamicSidebar = await import\("\.\/Sidebar\.custom\.mjs"\);/, ); -assert.match( - output, - /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/, -); +assert.match(output, /let dynamicProvider = DynamicSidebar\.Provider\.make;/); assert.match( output, /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider,/, ); assert.doesNotMatch( output, - /let dynamicProvider = DynamicSidebar\.Provider\.make;/, + /let dynamicProvider = DynamicSidebar\.Sidebar\$Provider;/, ); assert.doesNotMatch( output, diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js index 6a238a6f9ff..bc38c5e767e 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -26,12 +26,11 @@ assert.match( assert.doesNotMatch(layoutOutput, /\.Provider\.make,/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider;/, + /let provider = Sidebar\$RscNestedJsxAliasChain\.Provider\.make;/, ); -assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscNestedJsxAliasChain\.Provider/, + /Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider/, ); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_deep/input.js b/tests/build_tests/rsc_nested_jsx_deep/input.js index bb5b0bb1eca..13e9fbe0219 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/input.js +++ b/tests/build_tests/rsc_nested_jsx_deep/input.js @@ -35,13 +35,13 @@ assert.doesNotMatch(sidebarOutput, /Sidebar\$Group\$jsx/); const brandIcons = await import("./src/BrandIcons.res.js"); assert.deepStrictEqual( new Set(Object.keys(brandIcons)), - new Set(["BrandIcons$ReScript", "getIconForLanguageExtension"]), + new Set(["ReScript", "BrandIcons$ReScript", "getIconForLanguageExtension"]), ); const multipleNested = await import("./src/MultipleNested.res.js"); assert.deepStrictEqual( new Set(Object.keys(multipleNested)), - new Set(["MultipleNested$Group", "MultipleNested$Other"]), + new Set(["Group", "Other", "MultipleNested$Group", "MultipleNested$Other"]), ); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 8e2ef73e69a..4fb96cdf2ee 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -97,12 +97,11 @@ assert.match( ); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider;/, + /let provider = Sidebar\$RscNestedJsxMembers\.Provider\.make;/, ); -assert.doesNotMatch(plainAccessOutput, /\.Provider\.make/); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscNestedJsxMembers\.Provider/, + /Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider/, ); assert.match( buttonLayoutOutput, diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js index de565745ba1..d756b6989d4 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js @@ -211,12 +211,16 @@ export { $$default as default, Another, Inner, + NoProps, functionWithRenamedArgs, WithRename, + WithRef, ForwardRef, Poly, Fun, + RenderPropRequiresConversion, WithChildren, + DD, Hooks$Inner, Hooks$Inner$Inner2, Hooks$NoProps, diff --git a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js index 79505cae9bb..61c8f744406 100644 --- a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js +++ b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js @@ -13,6 +13,7 @@ let CompV4 = { let make = JSXV4Gen.make; export { + CompV4, make, JSXV4$CompV4, } From 8f7f1e1582c03a62786b63108161ffd58e826814 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 30 Apr 2026 20:07:00 +0200 Subject: [PATCH 53/56] Update JSX component export snapshots Signed-off-by: Florian Hammerschmidt --- tests/tests/src/alias_default_value_test.mjs | 8 ++++++++ tests/tests/src/async_jsx.mjs | 2 ++ tests/tests/src/jsx_optional_props_test.mjs | 1 + tests/tests/src/jsx_preserve_test.mjs | 5 +++++ tests/tests/src/jsxv4_newtype.mjs | 4 ++++ tests/tests/src/recursive_react_component.mjs | 4 ++++ 6 files changed, 24 insertions(+) diff --git a/tests/tests/src/alias_default_value_test.mjs b/tests/tests/src/alias_default_value_test.mjs index 40dd576932b..fce9a47f580 100644 --- a/tests/tests/src/alias_default_value_test.mjs +++ b/tests/tests/src/alias_default_value_test.mjs @@ -92,6 +92,14 @@ let C8 = { }; export { + C0, + C1, + C2, + C3, + C4, + C6, + C7, + C8, Alias_default_value_test$C0, Alias_default_value_test$C1, Alias_default_value_test$C2, diff --git a/tests/tests/src/async_jsx.mjs b/tests/tests/src/async_jsx.mjs index 536673c09dc..f19b9549c2a 100644 --- a/tests/tests/src/async_jsx.mjs +++ b/tests/tests/src/async_jsx.mjs @@ -35,6 +35,8 @@ let Bar = { export { getNow, + Foo, + Bar, Async_jsx$Foo, Async_jsx$Bar, } diff --git a/tests/tests/src/jsx_optional_props_test.mjs b/tests/tests/src/jsx_optional_props_test.mjs index 851759f336c..1d458a890d2 100644 --- a/tests/tests/src/jsx_optional_props_test.mjs +++ b/tests/tests/src/jsx_optional_props_test.mjs @@ -17,6 +17,7 @@ let _element = JsxRuntime.jsx(Jsx_optional_props_test$ComponentWithOptionalProps }); export { + ComponentWithOptionalProps, _element, Jsx_optional_props_test$ComponentWithOptionalProps, } diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs index c6967b499d3..76fb981e3b9 100644 --- a/tests/tests/src/jsx_preserve_test.mjs +++ b/tests/tests/src/jsx_preserve_test.mjs @@ -254,6 +254,7 @@ let make$2 = Jsx_preserve_test; let Jsx_preserve_test$Y$1 = make; export { + Icon, _single_element_child, _multiple_element_children, _single_element_fragment, @@ -268,14 +269,18 @@ export { _container_with_spread_props_keyed, _unary_element_with_only_spread_props, A, + B, _external_component_with_children, + MyWeirdComponent, _escaped_jsx_prop, _large_component, + ComponentWithOptionalProps, _optional_props, _props_with_hyphen, _empty_fragment, _fragment, _youtube_iframe, + X, Y, context, ContextProvider, diff --git a/tests/tests/src/jsxv4_newtype.mjs b/tests/tests/src/jsxv4_newtype.mjs index bf5594f4efc..5dd32460796 100644 --- a/tests/tests/src/jsxv4_newtype.mjs +++ b/tests/tests/src/jsxv4_newtype.mjs @@ -34,6 +34,10 @@ let V4A3 = { }; export { + V4A, + V4A1, + V4A2, + V4A3, Jsxv4_newtype$V4A, Jsxv4_newtype$V4A1, Jsxv4_newtype$V4A2, diff --git a/tests/tests/src/recursive_react_component.mjs b/tests/tests/src/recursive_react_component.mjs index 95ebac4932d..7a991e78ca0 100644 --- a/tests/tests/src/recursive_react_component.mjs +++ b/tests/tests/src/recursive_react_component.mjs @@ -52,6 +52,10 @@ let make$1 = Recursive_react_component; export { make$1 as make, + ShadowedSelfReference, + Leaf, + ShadowedByLocalLet, + ShadowedByNestedParameter, Recursive_react_component$ShadowedSelfReference, Recursive_react_component$Leaf, Recursive_react_component$ShadowedByLocalLet, From af68c1fb0988eb799fe78a50f7a2d093dbac4f84 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 30 Apr 2026 20:45:12 +0200 Subject: [PATCH 54/56] Use hoisted value metadata for nested JSX components Signed-off-by: Florian Hammerschmidt --- compiler/gentype/ModuleName.ml | 4 +- compiler/ml/translcore.ml | 38 ++++---- compiler/ml/typecore.ml | 30 ++++++- compiler/syntax/src/jsx_v4.ml | 87 ++++++++++++++----- .../src/expected/CreateInterface.res.txt | 6 +- .../tests/src/expected/JsxV4.res.txt | 6 +- .../src/abstract_component_test.res.js | 12 +-- .../react_ppx/src/gpr_3695_test.res.js | 6 +- .../react_ppx/src/gpr_3987_test.res.js | 24 ++--- .../react_ppx/src/issue_7917_test.res.js | 6 +- .../src/recursive_component_test.res.js | 6 +- .../recursive_explicit_component_test.res.js | 20 ++--- .../rsc_component_with_props_members/input.js | 10 +-- .../input.js | 4 +- .../rsc_component_with_props_nested/input.js | 4 +- .../rsc_dynamic_import_nested_jsx/input.js | 2 +- .../rsc_mixed_runtime_import/input.js | 2 +- .../rsc_nested_jsx_alias_chain/input.js | 2 +- .../build_tests/rsc_nested_jsx_deep/input.js | 15 +++- .../rsc_nested_jsx_local_hidden_resi/input.js | 2 +- .../rsc_nested_jsx_members/input.js | 18 ++-- .../input.js | 4 +- .../rsc_suffix_runtime_import/input.js | 2 +- .../src/Hooks.gen.tsx | 20 ++--- .../typescript-react-example/src/Hooks.res.js | 34 ++++---- .../src/JSXV4.gen.tsx | 4 +- .../typescript-react-example/src/JSXV4.res.js | 6 +- .../ppx/react/expected/aliasProps.res.txt | 50 ++++++----- .../ppx/react/expected/asyncAwait.res.txt | 14 +-- .../componentWithPropsInterface.resi.txt | 3 +- .../react/expected/defaultPatternProp.res.txt | 14 +-- .../react/expected/defaultValueProp.res.txt | 28 +++--- .../react/expected/externalWithRef.res.txt | 4 +- .../externalWithTypeVariables.res.txt | 4 +- .../react/expected/fileLevelConfig.res.txt | 7 +- .../react/expected/firstClassModules.resi.txt | 3 +- .../ppx/react/expected/forwardRef.res.txt | 28 +++--- .../ppx/react/expected/forwardRef.resi.txt | 24 +++-- .../data/ppx/react/expected/interface.res.txt | 14 +-- .../ppx/react/expected/interface.resi.txt | 6 +- .../ppx/react/expected/mangleKeyword.res.txt | 10 ++- .../data/ppx/react/expected/nested.res.txt | 14 +-- .../data/ppx/react/expected/newtype.res.txt | 35 ++++---- .../ppx/react/expected/noPropsWithKey.res.txt | 18 ++-- .../expected/optimizeAutomaticMode.res.txt | 7 +- .../react/expected/returnConstraint.res.txt | 28 +++--- .../ppx/react/expected/sharedProps.res.txt | 40 +++++---- .../ppx/react/expected/sharedProps.resi.txt | 24 +++-- .../expected/sharedPropsWithProps.res.txt | 51 ++++++----- .../data/ppx/react/expected/topLevel.res.txt | 7 +- .../ppx/react/expected/typeConstraint.res.txt | 7 +- .../ppx/react/expected/uncurriedProps.res.txt | 14 +-- .../data/ppx/react/expected/v4.res.txt | 34 +++++--- tests/tests/src/ExternalArity.mjs | 4 +- tests/tests/src/alias_default_value_test.mjs | 48 +++++----- tests/tests/src/async_jsx.mjs | 12 +-- tests/tests/src/jsx_optional_props_test.mjs | 8 +- tests/tests/src/jsx_preserve_test.mjs | 42 ++++----- tests/tests/src/jsxv4_newtype.mjs | 24 ++--- tests/tests/src/recursive_react_component.mjs | 28 +++--- 60 files changed, 595 insertions(+), 433 deletions(-) diff --git a/compiler/gentype/ModuleName.ml b/compiler/gentype/ModuleName.ml index 8463f1ef577..e16666d9802 100644 --- a/compiler/gentype/ModuleName.ml +++ b/compiler/gentype/ModuleName.ml @@ -30,8 +30,8 @@ let nested_make_hidden_export_access ~file_name path = | module_path -> Some ((file_name |> for_js_file) ^ "." - ^ Ext_modulename.concat_nested_component_name (file_name :: module_path) - )) + ^ Ext_modulename.concat_nested_component_name + (file_name :: (module_path @ ["make"])))) | _ -> None let from_string_unsafe s = s diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index f7a48d1f9ad..aaadb5e8fa8 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -672,6 +672,9 @@ let has_jsx_component_path_attr (exp : Typedtree.expression) = (fun ({txt; _}, _) -> String.equal txt "res.jsxComponentPath") exp.exp_attributes +let has_hoisted_value_attr (attrs : Parsetree.attributes) = + List.exists (fun ({txt; _}, _) -> String.equal txt "res.hoistedValue") attrs + let nested_component_root_name module_name = match Ext_namespace.try_split_module_name module_name with | Some (_namespace, module_name) -> module_name @@ -686,22 +689,17 @@ let nested_component_root_name module_name = let nested_jsx_component_export_path ~loc env path = match Env.normalize_path_prefix (Some loc) env path |> Path.flatten with | `Ok (root, segments) -> ( - match List.rev segments with - | "make" :: nested_modules_rev -> ( - match List.rev nested_modules_rev with - | [] -> None - | nested_modules -> ( - let hidden_name = - Ext_modulename.concat_nested_component_name - (nested_component_root_name (Ident.name root) :: nested_modules) - in - let hidden_path = - Path.Pdot (Path.Pident root, hidden_name, Path.nopos) - in - match Env.find_value hidden_path env with - | _ -> Some hidden_path - | exception Not_found -> None)) - | _ -> None) + match segments with + | [] -> None + | _ :: _ -> ( + let hidden_name = + Ext_modulename.concat_nested_component_name + (nested_component_root_name (Ident.name root) :: segments) + in + let hidden_path = Path.Pdot (Path.Pident root, hidden_name, Path.nopos) in + match Env.find_value hidden_path env with + | _ -> Some hidden_path + | exception Not_found -> None)) | `Contains_apply -> None let rec transl_exp e = @@ -713,10 +711,12 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = match e.exp_desc with | Texp_ident (_, _, {val_kind = Val_prim p}) -> transl_primitive e.exp_loc p e.exp_env e.exp_type - | Texp_ident (path, _, {val_kind = Val_reg}) -> ( + | Texp_ident (path, _, ({val_kind = Val_reg} as desc)) -> ( match - if has_jsx_component_path_attr e then - nested_jsx_component_export_path ~loc:e.exp_loc e.exp_env path + if + has_jsx_component_path_attr e + && has_hoisted_value_attr desc.val_attributes + then nested_jsx_component_export_path ~loc:e.exp_loc e.exp_env path else None with | Some path -> transl_value_path ~loc:e.exp_loc e.exp_env path diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 4f0b3be38e4..f78d09a0855 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -1758,17 +1758,29 @@ let check_unused ?(lev = get_current_level ()) env expected_ty cases = spat) cases -let add_pattern_variables ?check ?check_as env = +let res_internal_value_attributes attrs = + List.filter (fun ({txt; _}, _) -> String.equal txt "res.hoistedValue") attrs + +let add_pattern_variables ?(attrs_by_id = []) ?check ?check_as env = let pv = get_ref pattern_variables in ( List.fold_right (fun (id, ty, _name, loc, as_var) env -> let check = if as_var then check_as else check in + let val_attributes = + match + List.find_opt + (fun (attr_id, _) -> Ident.same attr_id id) + attrs_by_id + with + | Some (_, attrs) -> attrs + | None -> [] + in Env.add_value ?check id { val_type = ty; val_kind = Val_reg; Types.val_loc = loc; - val_attributes = []; + val_attributes; } env) pv env, @@ -1793,7 +1805,19 @@ let type_pattern_list env spatl scope expected_tys allow = type_pat new_env pat ty) in let patl = List.map2 type_pat spatl expected_tys in - let new_env, unpacks = add_pattern_variables !new_env in + let attrs_by_id = + List.fold_left2 + (fun attrs_by_id (attrs, _) pat -> + match res_internal_value_attributes attrs with + | [] -> attrs_by_id + | attrs -> + List.fold_left + (fun attrs_by_id id -> (id, attrs) :: attrs_by_id) + attrs_by_id + (Typedtree.pat_bound_idents pat)) + [] spatl patl + in + let new_env, unpacks = add_pattern_variables ~attrs_by_id !new_env in (patl, new_env, get_ref pattern_force, unpacks) let rec final_subexpression sexp = diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml index 463f8331f66..bf2f35209a0 100644 --- a/compiler/syntax/src/jsx_v4.ml +++ b/compiler/syntax/src/jsx_v4.ml @@ -87,9 +87,38 @@ let props_longident_for_nested_module nested_modules = in Ldot (mod_path, "props") -(* Hoisted File$Nested aliases exist for JS/RSC exports; they are not always - referenced from ReScript when a nested module is absent from the [.resi], so - suppress unused-value (32) on these bindings only. *) +let res_hoisted_value_attr = + ({txt = "res.hoistedValue"; loc = Location.none}, PStr []) + +let should_hoist_nested_make ~(config : Jsx_common.jsx_config) fn_name = + match (fn_name, config.nested_modules, config.functor_depth) with + | "make", _ :: _, 0 -> true + | _ -> false + +let with_hoisted_value_attr attrs = res_hoisted_value_attr :: attrs + +let maybe_mark_hoisted_binding ~(config : Jsx_common.jsx_config) fn_name binding + = + if should_hoist_nested_make ~config fn_name then + { + binding with + pvb_attributes = with_hoisted_value_attr binding.pvb_attributes; + } + else binding + +let maybe_mark_hoisted_value_description ~(config : Jsx_common.jsx_config) + fn_name value_description = + if should_hoist_nested_make ~config fn_name then + { + value_description with + pval_attributes = + with_hoisted_value_attr value_description.pval_attributes; + } + else value_description + +(* Hoisted File$Nested$make aliases exist for JS/RSC exports; they are not + always referenced from ReScript when a nested module is absent from the + [.resi], so suppress unused-value (32) on these bindings only. *) let jsx_hoisted_binding_warning_attrs = [ ( Location.mknoloc "warning", @@ -194,9 +223,12 @@ let make_module_name file_name nested_modules fn_name = let full_module_name = match (file_name, nested_modules, fn_name) with (* TODO: is this even reachable? It seems like the fileName always exists *) - | "", nested_modules, "make" -> nested_modules + | "", [], "make" -> [] + | "", nested_modules, "make" -> List.rev ("make" :: nested_modules) | "", nested_modules, fn_name -> List.rev (fn_name :: nested_modules) - | file_name, nested_modules, "make" -> file_name :: List.rev nested_modules + | file_name, [], "make" -> [file_name] + | file_name, nested_modules, "make" -> + file_name :: List.rev ("make" :: nested_modules) | file_name, nested_modules, fn_name -> file_name :: List.rev (fn_name :: nested_modules) in @@ -898,7 +930,11 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name binding = pvb_pat = Pat.var {txt = fn_name; loc = Location.none}; } in - let new_binding = Some (binding_wrapper full_expression) in + let new_binding = + Some + (binding_wrapper full_expression + |> maybe_mark_hoisted_binding ~config fn_name) + in let () = maybe_hoist_nested_make_component ~config ~empty_loc ~full_module_name fn_name @@ -981,7 +1017,11 @@ let map_binding ~config ~empty_loc ~pstr_loc ~file_name binding = jsx_component_expr config ~loc internal_expression in - Vb.mk ~attrs:modified_binding.pvb_attributes + Vb.mk + ~attrs: + (if should_hoist_nested_make ~config fn_name then + with_hoisted_value_attr modified_binding.pvb_attributes + else modified_binding.pvb_attributes) (Pat.var {txt = fn_name; loc}) internal_expression in @@ -1081,11 +1121,12 @@ let transform_structure_item ~config item = pstr with pstr_desc = Pstr_primitive - { - value_description with - pval_type = {pval_type with ptyp_desc = new_external_type}; - pval_attributes = List.filter other_attrs_pure pval_attributes; - }; + ({ + value_description with + pval_type = {pval_type with ptyp_desc = new_external_type}; + pval_attributes = List.filter other_attrs_pure pval_attributes; + } + |> maybe_mark_hoisted_value_description ~config pval_name.txt); } in let file_name = filename_from_loc pstr_loc in @@ -1170,11 +1211,12 @@ let transform_signature_item ~(config : Jsx_common.jsx_config) item = psig with psig_desc = Psig_value - { - psig_desc with - pval_type = {pval_type with ptyp_desc = new_external_type}; - pval_attributes = List.filter other_attrs_pure pval_attributes; - }; + ({ + psig_desc with + pval_type = {pval_type with ptyp_desc = new_external_type}; + pval_attributes = List.filter other_attrs_pure pval_attributes; + } + |> maybe_mark_hoisted_value_description ~config pval_name.txt); } in let file_name = filename_from_loc psig_loc in @@ -1239,11 +1281,12 @@ let transform_signature_item ~(config : Jsx_common.jsx_config) item = psig with psig_desc = Psig_value - { - psig_desc with - pval_type = {pval_type with ptyp_desc = new_external_type}; - pval_attributes = List.filter other_attrs_pure pval_attributes; - }; + ({ + psig_desc with + pval_type = {pval_type with ptyp_desc = new_external_type}; + pval_attributes = List.filter other_attrs_pure pval_attributes; + } + |> maybe_mark_hoisted_value_description ~config pval_name.txt); } in let file_name = filename_from_loc psig_loc in diff --git a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt index 36420aabca2..3a0db701fbf 100644 --- a/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt +++ b/tests/analysis_tests/tests/src/expected/CreateInterface.res.txt @@ -132,9 +132,9 @@ module OrderedSet: { } let make: (~id: module(Belt.Id.Comparable with type identity = 'a and type t = 'b)) => t<'b, 'a> } -let CreateInterface$Mod: React.component> -let CreateInterface$Memo: React.component> -let CreateInterface$ComponentWithPolyProp: React.component< +let CreateInterface$Mod$make: React.component> +let CreateInterface$Memo$make: React.component> +let CreateInterface$ComponentWithPolyProp$make: React.component< ComponentWithPolyProp.props<[< #large | #small]>, > diff --git a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt index c9854d2f5e3..c576e4767dc 100644 --- a/tests/analysis_tests/tests/src/expected/JsxV4.res.txt +++ b/tests/analysis_tests/tests/src/expected/JsxV4.res.txt @@ -32,7 +32,7 @@ module Other: { @react.component let make: (~name: string) => React.element } -let JsxV4$M4: React.component> -let JsxV4$MM: React.component -let JsxV4$Other: React.component> +let JsxV4$M4$make: React.component> +let JsxV4$MM$make: React.component +let JsxV4$Other$make: React.component> diff --git a/tests/build_tests/react_ppx/src/abstract_component_test.res.js b/tests/build_tests/react_ppx/src/abstract_component_test.res.js index 981baba07d5..a9600524d09 100644 --- a/tests/build_tests/react_ppx/src/abstract_component_test.res.js +++ b/tests/build_tests/react_ppx/src/abstract_component_test.res.js @@ -1,7 +1,7 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -function Abstract_component_test$PolyVariantLowerBound(props) { +function Abstract_component_test$PolyVariantLowerBound$make(props) { let x = props.x; if (x === "a") { return "A"; @@ -13,21 +13,21 @@ function Abstract_component_test$PolyVariantLowerBound(props) { } let PolyVariantLowerBound = { - make: Abstract_component_test$PolyVariantLowerBound + make: Abstract_component_test$PolyVariantLowerBound$make }; -function Abstract_component_test$GenericRenderProp(props) { +function Abstract_component_test$GenericRenderProp$make(props) { return props.render(props.x); } let GenericRenderProp = { - make: Abstract_component_test$GenericRenderProp + make: Abstract_component_test$GenericRenderProp$make }; export { PolyVariantLowerBound, GenericRenderProp, - Abstract_component_test$PolyVariantLowerBound, - Abstract_component_test$GenericRenderProp, + Abstract_component_test$PolyVariantLowerBound$make, + Abstract_component_test$GenericRenderProp$make, } /* No side effect */ diff --git a/tests/build_tests/react_ppx/src/gpr_3695_test.res.js b/tests/build_tests/react_ppx/src/gpr_3695_test.res.js index 9804c0f5b2b..60a95198734 100644 --- a/tests/build_tests/react_ppx/src/gpr_3695_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3695_test.res.js @@ -10,12 +10,12 @@ function test(className) { return Foo; } -let Gpr_3695_test$Test = Foo; +let Gpr_3695_test$Test$make = Foo; export { React, Test, test, - Gpr_3695_test$Test, + Gpr_3695_test$Test$make, } -/* Gpr_3695_test$Test Not a pure module */ +/* Gpr_3695_test$Test$make Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js index 33234612d44..b068e060cf8 100644 --- a/tests/build_tests/react_ppx/src/gpr_3987_test.res.js +++ b/tests/build_tests/react_ppx/src/gpr_3987_test.res.js @@ -16,41 +16,41 @@ function makeContainer(text) { return content; } -function Gpr_3987_test$Gpr3987ReproOk(props) { +function Gpr_3987_test$Gpr3987ReproOk$make(props) { return null; } let Gpr3987ReproOk = { - make: Gpr_3987_test$Gpr3987ReproOk + make: Gpr_3987_test$Gpr3987ReproOk$make }; -JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproOk, { +JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproOk$make, { value: "test", onChange: (param, param$1) => {} }); -function Gpr_3987_test$Gpr3987ReproOk2(props) { +function Gpr_3987_test$Gpr3987ReproOk2$make(props) { return null; } let Gpr3987ReproOk2 = { - make: Gpr_3987_test$Gpr3987ReproOk2 + make: Gpr_3987_test$Gpr3987ReproOk2$make }; -JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproOk2, { +JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproOk2$make, { value: "test", onChange: (param, param$1) => {} }); -function Gpr_3987_test$Gpr3987ReproError(props) { +function Gpr_3987_test$Gpr3987ReproError$make(props) { return null; } let Gpr3987ReproError = { - make: Gpr_3987_test$Gpr3987ReproError + make: Gpr_3987_test$Gpr3987ReproError$make }; -JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError, { +JsxRuntime.jsx(Gpr_3987_test$Gpr3987ReproError$make, { value: "test", onChange: (param, param$1) => {} }); @@ -60,8 +60,8 @@ export { Gpr3987ReproOk, Gpr3987ReproOk2, Gpr3987ReproError, - Gpr_3987_test$Gpr3987ReproOk, - Gpr_3987_test$Gpr3987ReproOk2, - Gpr_3987_test$Gpr3987ReproError, + Gpr_3987_test$Gpr3987ReproOk$make, + Gpr_3987_test$Gpr3987ReproOk2$make, + Gpr_3987_test$Gpr3987ReproError$make, } /* Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/issue_7917_test.res.js b/tests/build_tests/react_ppx/src/issue_7917_test.res.js index abc1d85a612..2d95787dbaa 100644 --- a/tests/build_tests/react_ppx/src/issue_7917_test.res.js +++ b/tests/build_tests/react_ppx/src/issue_7917_test.res.js @@ -2,12 +2,12 @@ import * as JsxRuntime from "react/jsx-runtime"; -function Issue_7917_test$M(props) { +function Issue_7917_test$M$make(props) { return null; } let M = { - make: Issue_7917_test$M + make: Issue_7917_test$M$make }; function Issue_7917_test(props) { @@ -21,6 +21,6 @@ let make = Issue_7917_test; export { M, make, - Issue_7917_test$M, + Issue_7917_test$M$make, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/build_tests/react_ppx/src/recursive_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_component_test.res.js index 9a03762e55e..bf4e364b39e 100644 --- a/tests/build_tests/react_ppx/src/recursive_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_component_test.res.js @@ -13,15 +13,15 @@ function mm(x) { }); } -let Recursive_component_test$Rec = make; +let Recursive_component_test$Rec$make = make; let Rec = { mm: mm, - make: Recursive_component_test$Rec + make: Recursive_component_test$Rec$make }; export { Rec, - Recursive_component_test$Rec, + Recursive_component_test$Rec$make, } /* No side effect */ diff --git a/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js b/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js index 0290fd6c0c6..67cf5b76eb2 100644 --- a/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js +++ b/tests/build_tests/react_ppx/src/recursive_explicit_component_test.res.js @@ -8,17 +8,17 @@ function make(param) { }); } -let Recursive_explicit_component_test$SelfCreateElement = make; +let Recursive_explicit_component_test$SelfCreateElement$make = make; let SelfCreateElement = { - make: Recursive_explicit_component_test$SelfCreateElement + make: Recursive_explicit_component_test$SelfCreateElement$make }; function other(param) { return param.foo.toString(); } -function Recursive_explicit_component_test$RawSiblingCreateElement(props) { +function Recursive_explicit_component_test$RawSiblingCreateElement$make(props) { return React.createElement(other, { foo: props.foo }); @@ -26,7 +26,7 @@ function Recursive_explicit_component_test$RawSiblingCreateElement(props) { let RawSiblingCreateElement = { other: other, - make: Recursive_explicit_component_test$RawSiblingCreateElement + make: Recursive_explicit_component_test$RawSiblingCreateElement$make }; function make$1(props) { @@ -39,13 +39,13 @@ function make$1(props) { } } -let Recursive_explicit_component_test$ComponentWithProps = make$1; +let Recursive_explicit_component_test$ComponentWithProps$make = make$1; let ComponentWithProps = { - make: Recursive_explicit_component_test$ComponentWithProps + make: Recursive_explicit_component_test$ComponentWithProps$make }; -let componentWithPropsElement = React.createElement(Recursive_explicit_component_test$ComponentWithProps, { +let componentWithPropsElement = React.createElement(Recursive_explicit_component_test$ComponentWithProps$make, { foo: 1 }); @@ -54,8 +54,8 @@ export { RawSiblingCreateElement, ComponentWithProps, componentWithPropsElement, - Recursive_explicit_component_test$SelfCreateElement, - Recursive_explicit_component_test$RawSiblingCreateElement, - Recursive_explicit_component_test$ComponentWithProps, + Recursive_explicit_component_test$SelfCreateElement$make, + Recursive_explicit_component_test$RawSiblingCreateElement$make, + Recursive_explicit_component_test$ComponentWithProps$make, } /* componentWithPropsElement Not a pure module */ diff --git a/tests/build_tests/rsc_component_with_props_members/input.js b/tests/build_tests/rsc_component_with_props_members/input.js index 2d09b00a673..86e2f84ca16 100644 --- a/tests/build_tests/rsc_component_with_props_members/input.js +++ b/tests/build_tests/rsc_component_with_props_members/input.js @@ -25,25 +25,25 @@ const plainAccessOutput = await fs.readFile( assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider\$make,/, ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset\$make,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); assert.doesNotMatch(output, /\.Inset\.make,/); assert.match( sidebarOutput, - /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, + /let Provider = \{[\s\S]*make: Sidebar\$Provider\$make[\s\S]*\};/s, ); assert.match( sidebarOutput, - /let Inset = \{[\s\S]*make: Sidebar\$Inset[\s\S]*\};/s, + /let Inset = \{[\s\S]*make: Sidebar\$Inset\$make[\s\S]*\};/s, ); assert.match( sidebarOutput, - /export \{[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset[\s\S]*\}/s, + /export \{[\s\S]*Sidebar\$Provider\$make,[\s\S]*Sidebar\$Inset\$make[\s\S]*\}/s, ); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Inset\.make = Inset;/); diff --git a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js index f762ee4f172..d6d4d11c9f1 100644 --- a/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js +++ b/tests/build_tests/rsc_component_with_props_members_no_namespace/input.js @@ -19,12 +19,12 @@ const sidebarOutput = await fs.readFile( "utf8", ); -assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); +assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider\$make,/); assert.doesNotMatch(output, /Sidebar\.Provider\.make/); assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); assert.match( sidebarOutput, - /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, + /let Provider = \{[\s\S]*make: Sidebar\$Provider\$make[\s\S]*\};/s, ); assert.match(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); diff --git a/tests/build_tests/rsc_component_with_props_nested/input.js b/tests/build_tests/rsc_component_with_props_nested/input.js index 7b00e9a0ecc..da50d95a67c 100644 --- a/tests/build_tests/rsc_component_with_props_nested/input.js +++ b/tests/build_tests/rsc_component_with_props_nested/input.js @@ -21,12 +21,12 @@ const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsNested\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscComponentWithPropsNested\.Sidebar\$Provider\$make,/, ); assert.doesNotMatch(output, /\.Provider\.make,/); assert.match( sidebarOutput, - /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, + /let Provider = \{[\s\S]*make: Sidebar\$Provider\$make[\s\S]*\};/s, ); assert.match(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); diff --git a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js index 4856beef38a..bd117d28a3d 100644 --- a/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js +++ b/tests/build_tests/rsc_dynamic_import_nested_jsx/input.js @@ -24,7 +24,7 @@ assert.match( assert.match(output, /let dynamicProvider = DynamicSidebar\.Provider\.make;/); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscDynamicImportNestedJsx\.Sidebar\$Provider\$make,/, ); assert.doesNotMatch( output, diff --git a/tests/build_tests/rsc_mixed_runtime_import/input.js b/tests/build_tests/rsc_mixed_runtime_import/input.js index 1e4f73b8bd2..4a14a476270 100644 --- a/tests/build_tests/rsc_mixed_runtime_import/input.js +++ b/tests/build_tests/rsc_mixed_runtime_import/input.js @@ -23,7 +23,7 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscMixedRuntimeImport\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscMixedRuntimeImport\.Sidebar\$Provider\$make,/, ); assert.doesNotMatch(output, /@rescript\/runtime\/lib\/es6\/Stdlib_Option\.mjs/); assert.equal( diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js index bc38c5e767e..b3406de00e8 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -21,7 +21,7 @@ const plainAccessOutput = await fs.readFile( assert.match( layoutOutput, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider\$make,/, ); assert.doesNotMatch(layoutOutput, /\.Provider\.make,/); assert.match( diff --git a/tests/build_tests/rsc_nested_jsx_deep/input.js b/tests/build_tests/rsc_nested_jsx_deep/input.js index 13e9fbe0219..831b8e404af 100644 --- a/tests/build_tests/rsc_nested_jsx_deep/input.js +++ b/tests/build_tests/rsc_nested_jsx_deep/input.js @@ -21,7 +21,7 @@ const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxDeep\.Sidebar\$Group,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxDeep\.Sidebar\$Group\$make,/, ); assert.doesNotMatch(output, /\.Group\.make,/); assert.match( @@ -35,13 +35,22 @@ assert.doesNotMatch(sidebarOutput, /Sidebar\$Group\$jsx/); const brandIcons = await import("./src/BrandIcons.res.js"); assert.deepStrictEqual( new Set(Object.keys(brandIcons)), - new Set(["ReScript", "BrandIcons$ReScript", "getIconForLanguageExtension"]), + new Set([ + "ReScript", + "BrandIcons$ReScript$make", + "getIconForLanguageExtension", + ]), ); const multipleNested = await import("./src/MultipleNested.res.js"); assert.deepStrictEqual( new Set(Object.keys(multipleNested)), - new Set(["Group", "Other", "MultipleNested$Group", "MultipleNested$Other"]), + new Set([ + "Group", + "Other", + "MultipleNested$Group$make", + "MultipleNested$Other$make", + ]), ); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/input.js b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/input.js index 2017210dc4f..c251e202568 100644 --- a/tests/build_tests/rsc_nested_jsx_local_hidden_resi/input.js +++ b/tests/build_tests/rsc_nested_jsx_local_hidden_resi/input.js @@ -23,7 +23,7 @@ const consumerOutput = await fs.readFile( "utf8", ); -assert.match(localOnlyOutput, /JsxRuntime\.jsx\(LocalOnly\$Hidden,/); +assert.match(localOnlyOutput, /JsxRuntime\.jsx\(LocalOnly\$Hidden\$make,/); assert.doesNotMatch(localOnlyOutput, /export \{[\s\S]*Hidden[\s\S]*\}/s); assert.doesNotMatch(localOnlyOutput, /LocalOnly\$Hidden\$jsx/); assert.match(consumerOutput, /JsxRuntime\.jsx\(LocalOnly\.make,/); diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 4fb96cdf2ee..4f5a1e1ea04 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -54,11 +54,11 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider\$make,/, ); assert.match( externalOutput, - /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.SidebarExternal\$Provider,/, + /JsxRuntime\.jsx\(SidebarExternal\$RscNestedJsxMembers\.SidebarExternal\$Provider\$make,/, ); assert.doesNotMatch( output, @@ -71,11 +71,11 @@ assert.doesNotMatch( assert.doesNotMatch(output, /\.Provider\.make,/); assert.match( sidebarOutput, - /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, + /let Provider = \{[\s\S]*make: Sidebar\$Provider\$make[\s\S]*\};/s, ); assert.match( sidebarOutput, - /let Inset = \{[\s\S]*make: Sidebar\$Inset[\s\S]*\};/s, + /let Inset = \{[\s\S]*make: Sidebar\$Inset\$make[\s\S]*\};/s, ); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(sidebarOutput, /Inset\.make = Inset;/); @@ -83,7 +83,7 @@ assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Inset\$jsx/); assert.match( sidebarOutput, - /export \{[\s\S]*Sidebar\$Provider,[\s\S]*Sidebar\$Inset[\s\S]*\}/s, + /export \{[\s\S]*Sidebar\$Provider\$make,[\s\S]*Sidebar\$Inset\$make[\s\S]*\}/s, ); assert.match( externalSidebarOutput, @@ -93,7 +93,7 @@ assert.doesNotMatch(externalSidebarOutput, /Provider\.make = Provider;/); assert.doesNotMatch(externalSidebarOutput, /SidebarExternal\$Provider\$jsx/); assert.match( externalSidebarOutput, - /export \{[\s\S]*SidebarExternal\$Provider[\s\S]*\}/s, + /export \{[\s\S]*SidebarExternal\$Provider\$make[\s\S]*\}/s, ); assert.match( plainAccessOutput, @@ -105,13 +105,13 @@ assert.doesNotMatch( ); assert.match( buttonLayoutOutput, - /JsxRuntime\.jsx\(Button\$RscNestedJsxMembers\.Button\$Button,/, + /JsxRuntime\.jsx\(Button\$RscNestedJsxMembers\.Button\$Button\$make,/, ); assert.doesNotMatch(buttonLayoutOutput, /\.Button\.make,/); assert.match( buttonOutput, - /let Button = \{[\s\S]*make: Button\$Button[\s\S]*\};/s, + /let Button = \{[\s\S]*make: Button\$Button\$make[\s\S]*\};/s, ); -assert.match(buttonOutput, /export \{[\s\S]*Button\$Button[\s\S]*\}/s); +assert.match(buttonOutput, /export \{[\s\S]*Button\$Button\$make[\s\S]*\}/s); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js index 1b3ff51f092..f93b8648295 100644 --- a/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js +++ b/tests/build_tests/rsc_nested_jsx_members_no_namespace/input.js @@ -19,12 +19,12 @@ const sidebarOutputPath = path.join( ); const sidebarOutput = await fs.readFile(sidebarOutputPath, "utf8"); -assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider,/); +assert.match(output, /JsxRuntime\.jsx\(Sidebar\.Sidebar\$Provider\$make,/); assert.doesNotMatch(output, /Sidebar\.Provider\.make/); assert.doesNotMatch(output, /JsxRuntime\.jsx\(Sidebar\.Provider,/); assert.match( sidebarOutput, - /let Provider = \{[\s\S]*make: Sidebar\$Provider[\s\S]*\};/s, + /let Provider = \{[\s\S]*make: Sidebar\$Provider\$make[\s\S]*\};/s, ); assert.match(sidebarOutput, /export \{[\s\S]*Sidebar\$Provider[\s\S]*\}/s); assert.doesNotMatch(sidebarOutput, /Provider\.make = Provider;/); diff --git a/tests/build_tests/rsc_suffix_runtime_import/input.js b/tests/build_tests/rsc_suffix_runtime_import/input.js index ee1dd122963..dd6ff7b0bab 100644 --- a/tests/build_tests/rsc_suffix_runtime_import/input.js +++ b/tests/build_tests/rsc_suffix_runtime_import/input.js @@ -23,7 +23,7 @@ assert.match( ); assert.match( output, - /JsxRuntime\.jsx\(Sidebar\$RscSuffixRuntimeImport\.Sidebar\$Provider,/, + /JsxRuntime\.jsx\(Sidebar\$RscSuffixRuntimeImport\.Sidebar\$Provider\$make,/, ); assert.equal(output.match(/Stdlib_Option\.(js|mjs)/g)?.length ?? 0, 1); assert.doesNotMatch( diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.gen.tsx b/tests/gentype_tests/typescript-react-example/src/Hooks.gen.tsx index fd481eff2ad..a875a3c8b00 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.gen.tsx +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.gen.tsx @@ -61,15 +61,15 @@ export default $$default; export const Another_anotherComponent: React.ComponentType<{ readonly vehicle: vehicle; readonly callback: () => void }> = HooksJS.Another.anotherComponent as any; -export const Inner_make: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Hooks$Inner as any; +export const Inner_make: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Hooks$Inner$make as any; export const Inner_Another_anotherComponent: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Inner.Another.anotherComponent as any; -export const Inner_Inner2_make: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Hooks$Inner$Inner2 as any; +export const Inner_Inner2_make: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Hooks$Inner$Inner2$make as any; export const Inner_Inner2_Another_anotherComponent: React.ComponentType<{ readonly vehicle: vehicle }> = HooksJS.Inner.Inner2.Another.anotherComponent as any; -export const NoProps_make: React.ComponentType<{}> = HooksJS.Hooks$NoProps as any; +export const NoProps_make: React.ComponentType<{}> = HooksJS.Hooks$NoProps$make as any; export const functionWithRenamedArgs: (_to:vehicle, _Type:vehicle, cb:cb) => string = HooksJS.functionWithRenamedArgs as any; @@ -79,7 +79,7 @@ export const WithRename_componentWithRenamedArgs: React.ComponentType<{ readonly cb: cb }> = HooksJS.WithRename.componentWithRenamedArgs as any; -export const WithRef_make: React.ComponentType<{ readonly vehicle: vehicle; readonly ref?: any }> = HooksJS.Hooks$WithRef as any; +export const WithRef_make: React.ComponentType<{ readonly vehicle: vehicle; readonly ref?: any }> = HooksJS.Hooks$WithRef$make as any; export const ForwardRef_input: (_1:r) => JSX.Element = HooksJS.ForwardRef.input as any; @@ -87,13 +87,13 @@ export const Poly_polymorphicComponent: React.ComponentType<{ readonly p: [vehic export const Fun_functionReturningReactElement: React.ComponentType<{ readonly name: string }> = HooksJS.Fun.functionReturningReactElement as any; -export const RenderPropRequiresConversion_make: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> = HooksJS.Hooks$RenderPropRequiresConversion as any; +export const RenderPropRequiresConversion_make: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> = HooksJS.Hooks$RenderPropRequiresConversion$make as any; export const WithChildren_aComponentWithChildren: React.ComponentType<{ readonly vehicle: vehicle; readonly children: React.ReactNode }> = HooksJS.WithChildren.aComponentWithChildren as any; -export const DD_make: React.ComponentType<{ readonly array: Js_TypedArray2_Uint8Array_t; readonly name: string }> = HooksJS.Hooks$DD as any; +export const DD_make: React.ComponentType<{ readonly array: Js_TypedArray2_Uint8Array_t; readonly name: string }> = HooksJS.Hooks$DD$make as any; -export const NoProps: React.ComponentType<{}> = HooksJS.Hooks$NoProps as any; +export const NoProps: React.ComponentType<{}> = HooksJS.Hooks$NoProps$make as any; export const Inner: { Inner2: { @@ -116,7 +116,7 @@ export const Inner: { }> } = HooksJS.Inner as any; -export const RenderPropRequiresConversion: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> = HooksJS.Hooks$RenderPropRequiresConversion as any; +export const RenderPropRequiresConversion: React.ComponentType<{ readonly renderVehicle: React.ComponentType<{ readonly number: number; readonly vehicle: vehicle }> }> = HooksJS.Hooks$RenderPropRequiresConversion$make as any; export const WithRename: { componentWithRenamedArgs: React.ComponentType<{ readonly _to: vehicle; @@ -128,11 +128,11 @@ export const ForwardRef: { input: (_1:r) => JSX.Element } = HooksJS.ForwardRef a export const Fun: { functionReturningReactElement: React.ComponentType<{ readonly name: string }> } = HooksJS.Fun as any; -export const WithRef: React.ComponentType<{ readonly vehicle: vehicle; readonly ref?: any }> = HooksJS.Hooks$WithRef as any; +export const WithRef: React.ComponentType<{ readonly vehicle: vehicle; readonly ref?: any }> = HooksJS.Hooks$WithRef$make as any; export const WithChildren: { aComponentWithChildren: React.ComponentType<{ readonly vehicle: vehicle; readonly children: React.ReactNode }> } = HooksJS.WithChildren as any; -export const DD: React.ComponentType<{ readonly array: Js_TypedArray2_Uint8Array_t; readonly name: string }> = HooksJS.Hooks$DD as any; +export const DD: React.ComponentType<{ readonly array: Js_TypedArray2_Uint8Array_t; readonly name: string }> = HooksJS.Hooks$DD$make as any; export const Another: { anotherComponent: React.ComponentType<{ readonly vehicle: vehicle; readonly callback: () => void }> } = HooksJS.Another as any; diff --git a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js index d756b6989d4..b85fa7c213e 100644 --- a/tests/gentype_tests/typescript-react-example/src/Hooks.res.js +++ b/tests/gentype_tests/typescript-react-example/src/Hooks.res.js @@ -56,7 +56,7 @@ let Another = { anotherComponent: Hooks$Another$anotherComponent }; -function Hooks$Inner(props) { +function Hooks$Inner$make(props) { return JsxRuntime.jsx("div", { children: "Another Hook " + props.vehicle.name }); @@ -72,7 +72,7 @@ let Another$1 = { anotherComponent: Hooks$Inner$Another$anotherComponent }; -function Hooks$Inner$Inner2(props) { +function Hooks$Inner$Inner2$make(props) { return JsxRuntime.jsx("div", { children: "Another Hook " + props.vehicle.name }); @@ -89,24 +89,24 @@ let Another$2 = { }; let Inner2 = { - make: Hooks$Inner$Inner2, + make: Hooks$Inner$Inner2$make, Another: Another$2 }; let Inner = { - make: Hooks$Inner, + make: Hooks$Inner$make, Another: Another$1, Inner2: Inner2 }; -function Hooks$NoProps(props) { +function Hooks$NoProps$make(props) { return JsxRuntime.jsx("div", { children: null }); } let NoProps = { - make: Hooks$NoProps + make: Hooks$NoProps$make }; function functionWithRenamedArgs(_to, _Type, cb) { @@ -164,7 +164,7 @@ let Fun = { functionReturningReactElement: Hooks$Fun$functionReturningReactElement }; -function Hooks$RenderPropRequiresConversion(props) { +function Hooks$RenderPropRequiresConversion$make(props) { return props.renderVehicle({ vehicle: { name: "Car" @@ -174,7 +174,7 @@ function Hooks$RenderPropRequiresConversion(props) { } let RenderPropRequiresConversion = { - make: Hooks$RenderPropRequiresConversion + make: Hooks$RenderPropRequiresConversion$make }; function Hooks$WithChildren$aComponentWithChildren(props) { @@ -192,19 +192,19 @@ let WithChildren = { aComponentWithChildren: Hooks$WithChildren$aComponentWithChildren }; -function Hooks$DD(props) { +function Hooks$DD$make(props) { return props.name; } let DD = { - make: Hooks$DD + make: Hooks$DD$make }; let make$1 = Hooks; let $$default = Hooks; -let Hooks$WithRef = make; +let Hooks$WithRef$make = make; export { make$1 as make, @@ -221,11 +221,11 @@ export { RenderPropRequiresConversion, WithChildren, DD, - Hooks$Inner, - Hooks$Inner$Inner2, - Hooks$NoProps, - Hooks$WithRef, - Hooks$RenderPropRequiresConversion, - Hooks$DD, + Hooks$Inner$make, + Hooks$Inner$Inner2$make, + Hooks$NoProps$make, + Hooks$WithRef$make, + Hooks$RenderPropRequiresConversion$make, + Hooks$DD$make, } /* make Not a pure module */ diff --git a/tests/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx b/tests/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx index 77509ddeaae..2bf3aae0d65 100644 --- a/tests/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx +++ b/tests/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx @@ -38,6 +38,6 @@ export type props = { readonly renderMe: renderMe }; -export const CompV4_make: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4JS.JSXV4$CompV4 as any; +export const CompV4_make: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4JS.JSXV4$CompV4$make as any; -export const CompV4: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4JS.JSXV4$CompV4 as any; +export const CompV4: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4JS.JSXV4$CompV4$make as any; diff --git a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js index 61c8f744406..27453f680fd 100644 --- a/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js +++ b/tests/gentype_tests/typescript-react-example/src/JSXV4.res.js @@ -2,12 +2,12 @@ import * as JSXV4Gen from "./JSXV4.gen"; -function JSXV4$CompV4(props) { +function JSXV4$CompV4$make(props) { return props.x + props.y; } let CompV4 = { - make: JSXV4$CompV4 + make: JSXV4$CompV4$make }; let make = JSXV4Gen.make; @@ -15,6 +15,6 @@ let make = JSXV4Gen.make; export { CompV4, make, - JSXV4$CompV4, + JSXV4$CompV4$make, } /* make Not a pure module */ diff --git a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt index 90296044b5c..0f62212308f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/aliasProps.res.txt @@ -15,10 +15,11 @@ module C0 = { let text = __text_value (React.string(text): React.element) } + @res.hoistedValue let make = React.component({ - let \"AliasProps$C0" = (props: props<_>) => make(props) + let \"AliasProps$C0$make" = (props: props<_>) => make(props) - \"AliasProps$C0" + \"AliasProps$C0$make" }) } @@ -37,10 +38,11 @@ module C1 = { let text = __text_value (React.string(p ++ text): React.element) } + @res.hoistedValue let make = React.component({ - let \"AliasProps$C1" = (props: props<_>) => make(props) + let \"AliasProps$C1$make" = (props: props<_>) => make(props) - \"AliasProps$C1" + \"AliasProps$C1$make" }) } @@ -58,10 +60,11 @@ module C2 = { let bar = __foo_value (React.string(bar): React.element) } + @res.hoistedValue let make = React.component({ - let \"AliasProps$C2" = (props: props<_>) => make(props) + let \"AliasProps$C2$make" = (props: props<_>) => make(props) - \"AliasProps$C2" + \"AliasProps$C2$make" }) } @@ -90,10 +93,11 @@ module C3 = { }: React.element ) } + @res.hoistedValue let make = React.component({ - let \"AliasProps$C3" = (props: props<_>) => make(props) + let \"AliasProps$C3$make" = (props: props<_>) => make(props) - \"AliasProps$C3" + \"AliasProps$C3$make" }) } @@ -112,10 +116,11 @@ module C4 = { let x = __x_value (ReactDOM.jsx("div", {children: ?ReactDOM.someElement(b)}): React.element) } + @res.hoistedValue let make = React.component({ - let \"AliasProps$C4" = (props: props<_>) => make(props) + let \"AliasProps$C4$make" = (props: props<_>) => make(props) - \"AliasProps$C4" + \"AliasProps$C4$make" }) } @@ -134,10 +139,11 @@ module C5 = { let z = __z_value (x + y + z: React.element) } + @res.hoistedValue let make = React.component({ - let \"AliasProps$C5" = (props: props<_>) => make(props) + let \"AliasProps$C5$make" = (props: props<_>) => make(props) - \"AliasProps$C5" + \"AliasProps$C5$make" }) } @@ -146,6 +152,7 @@ module C6 = { @res.jsxComponentProps type props = {} + @res.hoistedValue let make: React.component } @res.jsxComponentProps @@ -156,16 +163,17 @@ module C6 = { let make = ({comp: module(Comp: Comp), x: (a, b), _}: props<_, _>): React.element => React.jsx(@res.jsxComponentPath Comp.make, {}) + @res.hoistedValue let make = React.component({ - let \"AliasProps$C6" = (props: props<_>) => make(props) + let \"AliasProps$C6$make" = (props: props<_>) => make(props) - \"AliasProps$C6" + \"AliasProps$C6$make" }) } -@warning("-32") let \"AliasProps$C0" = C0.make -@warning("-32") let \"AliasProps$C1" = C1.make -@warning("-32") let \"AliasProps$C2" = C2.make -@warning("-32") let \"AliasProps$C3" = C3.make -@warning("-32") let \"AliasProps$C4" = C4.make -@warning("-32") let \"AliasProps$C5" = C5.make -@warning("-32") let \"AliasProps$C6" = C6.make +@warning("-32") let \"AliasProps$C0$make" = C0.make +@warning("-32") let \"AliasProps$C1$make" = C1.make +@warning("-32") let \"AliasProps$C2$make" = C2.make +@warning("-32") let \"AliasProps$C3$make" = C3.make +@warning("-32") let \"AliasProps$C4$make" = C4.make +@warning("-32") let \"AliasProps$C5$make" = C5.make +@warning("-32") let \"AliasProps$C6$make" = C6.make diff --git a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt index 48c9f96a4b2..a6db73a5017 100644 --- a/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/asyncAwait.res.txt @@ -10,10 +10,11 @@ module C0 = { let a = await f(a) (ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.int(a)})}): React.element) } + @res.hoistedValue let make = React.component({ - let \"AsyncAwait$C0" = (props: props<_>) => Jsx.promise(make(props)) + let \"AsyncAwait$C0$make" = (props: props<_>) => Jsx.promise(make(props)) - \"AsyncAwait$C0" + \"AsyncAwait$C0$make" }) } @@ -29,11 +30,12 @@ module C1 = { | #off => React.string("off") } } + @res.hoistedValue let make = React.component({ - let \"AsyncAwait$C1" = (props: props<_>) => Jsx.promise(make(props)) + let \"AsyncAwait$C1$make" = (props: props<_>) => Jsx.promise(make(props)) - \"AsyncAwait$C1" + \"AsyncAwait$C1$make" }) } -@warning("-32") let \"AsyncAwait$C0" = C0.make -@warning("-32") let \"AsyncAwait$C1" = C1.make +@warning("-32") let \"AsyncAwait$C0$make" = C0.make +@warning("-32") let \"AsyncAwait$C1$make" = C1.make diff --git a/tests/syntax_tests/data/ppx/react/expected/componentWithPropsInterface.resi.txt b/tests/syntax_tests/data/ppx/react/expected/componentWithPropsInterface.resi.txt index bcf0da54728..5ca3cacdff6 100644 --- a/tests/syntax_tests/data/ppx/react/expected/componentWithPropsInterface.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/componentWithPropsInterface.resi.txt @@ -3,6 +3,7 @@ module Sidebar: { type props = {children: React.element} + @res.hoistedValue let make: React.component } -let \"ComponentWithPropsInterface$Sidebar": React.component +let \"ComponentWithPropsInterface$Sidebar$make": React.component diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt index d4b4da68efc..191d2a2dff2 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultPatternProp.res.txt @@ -6,10 +6,11 @@ module C0 = { type props = {} let make = (_: props): React.element => React.null + @res.hoistedValue let make = React.component({ - let \"DefaultPatternProp$C0$M" = props => make(props) + let \"DefaultPatternProp$C0$M$make" = props => make(props) - \"DefaultPatternProp$C0$M" + \"DefaultPatternProp$C0$M$make" }) } @@ -27,11 +28,12 @@ module C0 = { let module(C: S) = __component_value (React.jsx(@res.jsxComponentPath C.make, {}): React.element) } + @res.hoistedValue let make = React.component({ - let \"DefaultPatternProp$C0" = (props: props<_>) => make(props) + let \"DefaultPatternProp$C0$make" = (props: props<_>) => make(props) - \"DefaultPatternProp$C0" + \"DefaultPatternProp$C0$make" }) } -@warning("-32") let \"DefaultPatternProp$C0$M" = C0.M.make -@warning("-32") let \"DefaultPatternProp$C0" = C0.make +@warning("-32") let \"DefaultPatternProp$C0$M$make" = C0.M.make +@warning("-32") let \"DefaultPatternProp$C0$make" = C0.make diff --git a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt index cf594735433..8e71b14cdd1 100644 --- a/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/defaultValueProp.res.txt @@ -17,9 +17,10 @@ module C0 = { let b = __b_value (React.int(a + b): React.element) } + @res.hoistedValue let make = React.component({ - let \"DefaultValueProp$C0" = (props: props<_>) => make(props) - \"DefaultValueProp$C0" + let \"DefaultValueProp$C0$make" = (props: props<_>) => make(props) + \"DefaultValueProp$C0$make" }) } @@ -38,10 +39,11 @@ module C1 = { let a = __a_value (React.int(a + b): React.element) } + @res.hoistedValue let make = React.component({ - let \"DefaultValueProp$C1" = (props: props<_>) => make(props) + let \"DefaultValueProp$C1$make" = (props: props<_>) => make(props) - \"DefaultValueProp$C1" + \"DefaultValueProp$C1$make" }) } @@ -60,10 +62,11 @@ module C2 = { let a = __a_value (React.string(a): React.element) } + @res.hoistedValue let make = React.component({ - let \"DefaultValueProp$C2" = (props: props<_>) => make(props) + let \"DefaultValueProp$C2$make" = (props: props<_>) => make(props) - \"DefaultValueProp$C2" + \"DefaultValueProp$C2$make" }) } @@ -85,13 +88,14 @@ module C3 = { }: React.element ) } + @res.hoistedValue let make = React.component({ - let \"DefaultValueProp$C3" = (props: props<_>) => make(props) + let \"DefaultValueProp$C3$make" = (props: props<_>) => make(props) - \"DefaultValueProp$C3" + \"DefaultValueProp$C3$make" }) } -@warning("-32") let \"DefaultValueProp$C0" = C0.make -@warning("-32") let \"DefaultValueProp$C1" = C1.make -@warning("-32") let \"DefaultValueProp$C2" = C2.make -@warning("-32") let \"DefaultValueProp$C3" = C3.make +@warning("-32") let \"DefaultValueProp$C0$make" = C0.make +@warning("-32") let \"DefaultValueProp$C1$make" = C1.make +@warning("-32") let \"DefaultValueProp$C2$make" = C2.make +@warning("-32") let \"DefaultValueProp$C3$make" = C3.make diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt index 648bf3e6ecd..95bfb0f4469 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithRef.res.txt @@ -7,7 +7,7 @@ module V4C = { ref?: 'ref, } - @module("componentForwardRef") + @res.hoistedValue @module("componentForwardRef") external make: React.component> = "component" } -@warning("-32") let \"ExternalWithRef$V4C" = V4C.make +@warning("-32") let \"ExternalWithRef$V4C$make" = V4C.make diff --git a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt index 97e26dd8798..1b04965a950 100644 --- a/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/externalWithTypeVariables.res.txt @@ -7,7 +7,7 @@ module V4C = { children: 'children, } - @module("c") + @res.hoistedValue @module("c") external make: React.component, React.element>> = "component" } -@warning("-32") let \"ExternalWithTypeVariables$V4C" = V4C.make +@warning("-32") let \"ExternalWithTypeVariables$V4C$make" = V4C.make diff --git a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt index 304688a142b..d9cd42858e6 100644 --- a/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/fileLevelConfig.res.txt @@ -9,10 +9,11 @@ module V4A = { let make = ({msg, _}: props<_>): React.element => { ReactDOM.jsx("div", {children: ?ReactDOM.someElement({msg->React.string})}) } + @res.hoistedValue let make = React.component({ - let \"FileLevelConfig$V4A" = (props: props<_>) => make(props) + let \"FileLevelConfig$V4A$make" = (props: props<_>) => make(props) - \"FileLevelConfig$V4A" + \"FileLevelConfig$V4A$make" }) } -@warning("-32") let \"FileLevelConfig$V4A" = V4A.make +@warning("-32") let \"FileLevelConfig$V4A$make" = V4A.make diff --git a/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt b/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt index 303e200c456..36dfbf6c15f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/firstClassModules.resi.txt @@ -13,6 +13,7 @@ module Select: { items: 'items, } + @res.hoistedValue let make: React.component< props< module(T with type t = 'a and type key = 'key), @@ -22,7 +23,7 @@ module Select: { >, > } -let \"FirstClassModules$Select": React.component< +let \"FirstClassModules$Select$make": React.component< Select.props< module(T with type t = 'a and type key = 'key), option<'key>, diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt index 0fd414c25bc..3af6fe67a2a 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.res.txt @@ -29,10 +29,11 @@ module V4A = { ]), }, ) + @res.hoistedValue let make = React.forwardRef({ - let \"ForwardRef$V4A$FancyInput" = (props: props<_>, ref) => make(props, ref) + let \"ForwardRef$V4A$FancyInput$make" = (props: props<_>, ref) => make(props, ref) - \"ForwardRef$V4A$FancyInput" + \"ForwardRef$V4A$FancyInput$make" }) } @res.jsxComponentProps @@ -54,10 +55,11 @@ module V4A = { ): React.element ) } + @res.hoistedValue let make = React.component({ - let \"ForwardRef$V4A" = props => make(props) + let \"ForwardRef$V4A$make" = props => make(props) - \"ForwardRef$V4A" + \"ForwardRef$V4A$make" }) } @@ -90,10 +92,11 @@ module V4AUncurried = { ]), }, ) + @res.hoistedValue let make = React.forwardRef({ - let \"ForwardRef$V4AUncurried$FancyInput" = (props: props<_>, ref) => make(props, ref) + let \"ForwardRef$V4AUncurried$FancyInput$make" = (props: props<_>, ref) => make(props, ref) - \"ForwardRef$V4AUncurried$FancyInput" + \"ForwardRef$V4AUncurried$FancyInput$make" }) } @res.jsxComponentProps @@ -115,13 +118,14 @@ module V4AUncurried = { ): React.element ) } + @res.hoistedValue let make = React.component({ - let \"ForwardRef$V4AUncurried" = props => make(props) + let \"ForwardRef$V4AUncurried$make" = props => make(props) - \"ForwardRef$V4AUncurried" + \"ForwardRef$V4AUncurried$make" }) } -@warning("-32") let \"ForwardRef$V4A$FancyInput" = V4A.FancyInput.make -@warning("-32") let \"ForwardRef$V4A" = V4A.make -@warning("-32") let \"ForwardRef$V4AUncurried$FancyInput" = V4AUncurried.FancyInput.make -@warning("-32") let \"ForwardRef$V4AUncurried" = V4AUncurried.make +@warning("-32") let \"ForwardRef$V4A$FancyInput$make" = V4A.FancyInput.make +@warning("-32") let \"ForwardRef$V4A$make" = V4A.make +@warning("-32") let \"ForwardRef$V4AUncurried$FancyInput$make" = V4AUncurried.FancyInput.make +@warning("-32") let \"ForwardRef$V4AUncurried$make" = V4AUncurried.make diff --git a/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt b/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt index f8ac515ecef..726d845f63c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/forwardRef.resi.txt @@ -9,6 +9,7 @@ module V4C: { ref?: 'ref, } + @res.hoistedValue let make: React.component> } @@ -18,6 +19,7 @@ module V4C: { ref?: 'ref, } + @res.hoistedValue let make: React.component>>> } } @@ -31,6 +33,7 @@ module V4CUncurried: { ref?: 'ref, } + @res.hoistedValue let make: React.component> } @@ -40,6 +43,7 @@ module V4CUncurried: { ref?: 'ref, } + @res.hoistedValue let make: React.component>>> } } @@ -55,6 +59,7 @@ module V4A: { ref?: 'ref, } + @res.hoistedValue let make: React.component> } @@ -64,6 +69,7 @@ module V4A: { ref?: 'ref, } + @res.hoistedValue let make: React.component>>> } } @@ -77,6 +83,7 @@ module V4AUncurried: { ref?: 'ref, } + @res.hoistedValue let make: React.component> } @@ -86,30 +93,31 @@ module V4AUncurried: { ref?: 'ref, } + @res.hoistedValue let make: React.component>>> } } -let \"ForwardRef$V4C$FancyInput": React.component< +let \"ForwardRef$V4C$FancyInput$make": React.component< V4C.FancyInput.props, > -let \"ForwardRef$V4C$ForwardRef": React.component< +let \"ForwardRef$V4C$ForwardRef$make": React.component< V4C.ForwardRef.props>>, > -let \"ForwardRef$V4CUncurried$FancyInput": React.component< +let \"ForwardRef$V4CUncurried$FancyInput$make": React.component< V4CUncurried.FancyInput.props, > -let \"ForwardRef$V4CUncurried$ForwardRef": React.component< +let \"ForwardRef$V4CUncurried$ForwardRef$make": React.component< V4CUncurried.ForwardRef.props>>, > -let \"ForwardRef$V4A$FancyInput": React.component< +let \"ForwardRef$V4A$FancyInput$make": React.component< V4A.FancyInput.props, > -let \"ForwardRef$V4A$ForwardRef": React.component< +let \"ForwardRef$V4A$ForwardRef$make": React.component< V4A.ForwardRef.props>>, > -let \"ForwardRef$V4AUncurried$FancyInput": React.component< +let \"ForwardRef$V4AUncurried$FancyInput$make": React.component< V4AUncurried.FancyInput.props, > -let \"ForwardRef$V4AUncurried$ForwardRef": React.component< +let \"ForwardRef$V4AUncurried$ForwardRef$make": React.component< V4AUncurried.ForwardRef.props>>, > diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt index 6f5e162eca5..555a03c213b 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.res.txt @@ -4,9 +4,10 @@ module A = { x: 'x, } let make = ({x, _}: props<_>): React.element => React.string(x) + @res.hoistedValue let make = React.component({ - let \"Interface$A" = (props: props<_>) => make(props) - \"Interface$A" + let \"Interface$A$make" = (props: props<_>) => make(props) + \"Interface$A$make" }) } @@ -15,11 +16,12 @@ module NoProps = { type props = {} let make = (_: props): React.element => ReactDOM.jsx("div", {}) + @res.hoistedValue let make = React.component({ - let \"Interface$NoProps" = props => make(props) + let \"Interface$NoProps$make" = props => make(props) - \"Interface$NoProps" + \"Interface$NoProps$make" }) } -@warning("-32") let \"Interface$A" = A.make -@warning("-32") let \"Interface$NoProps" = NoProps.make +@warning("-32") let \"Interface$A$make" = A.make +@warning("-32") let \"Interface$NoProps$make" = NoProps.make diff --git a/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt b/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt index eddc8eb4b7b..d1808ac0482 100644 --- a/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/interface.resi.txt @@ -3,6 +3,7 @@ module A: { type props<'x> = { x: 'x, } + @res.hoistedValue let make: React.component> } @@ -10,7 +11,8 @@ module NoProps: { @res.jsxComponentProps type props = {} + @res.hoistedValue let make: React.component } -let \"Interface$A": React.component> -let \"Interface$NoProps": React.component +let \"Interface$A$make": React.component> +let \"Interface$NoProps$make": React.component diff --git a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt index adf4472809a..b4bda95656f 100644 --- a/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/mangleKeyword.res.txt @@ -9,10 +9,11 @@ module C4A0 = { let make = ({@as("open") _open, @as("type") _type, _}: props<_, string>): React.element => React.string(_open) + @res.hoistedValue let make = React.component({ - let \"MangleKeyword$C4A0" = (props: props<_>) => make(props) + let \"MangleKeyword$C4A0$make" = (props: props<_>) => make(props) - \"MangleKeyword$C4A0" + \"MangleKeyword$C4A0$make" }) } module C4A1 = { @@ -22,10 +23,11 @@ module C4A1 = { @as("type") _type: 'T_type, } + @res.hoistedValue external make: React.component> = "default" } let c4a0 = React.jsx(@res.jsxComponentPath C4A0.make, {_open: "x", _type: "t"}) let c4a1 = React.jsx(@res.jsxComponentPath C4A1.make, {_open: "x", _type: "t"}) -@warning("-32") let \"MangleKeyword$C4A0" = C4A0.make -@warning("-32") let \"MangleKeyword$C4A1" = C4A1.make +@warning("-32") let \"MangleKeyword$C4A0$make" = C4A0.make +@warning("-32") let \"MangleKeyword$C4A1$make" = C4A1.make diff --git a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt index 16defbc131a..35e8dfc1877 100644 --- a/tests/syntax_tests/data/ppx/react/expected/nested.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/nested.res.txt @@ -7,19 +7,21 @@ module Outer = { type props = {} let make = (_: props): React.element => ReactDOM.jsx("div", {}) + @res.hoistedValue let make = React.component({ - let \"Nested$Outer$Inner" = props => make(props) + let \"Nested$Outer$Inner$make" = props => make(props) - \"Nested$Outer$Inner" + \"Nested$Outer$Inner$make" }) } React.jsx(@res.jsxComponentPath Inner.make, {}) } + @res.hoistedValue let make = React.component({ - let \"Nested$Outer" = props => make(props) - \"Nested$Outer" + let \"Nested$Outer$make" = props => make(props) + \"Nested$Outer$make" }) } -@warning("-32") let \"Nested$Outer$Inner" = Outer.Inner.make -@warning("-32") let \"Nested$Outer" = Outer.make +@warning("-32") let \"Nested$Outer$Inner$make" = Outer.Inner.make +@warning("-32") let \"Nested$Outer$make" = Outer.make diff --git a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt index 18b1cb213d2..d517d0043e8 100644 --- a/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/newtype.res.txt @@ -10,10 +10,11 @@ module V4A = { let make = (type a, {a, b, c, _}: props>, 'a>): React.element => ReactDOM.jsx("div", {}) + @res.hoistedValue let make = React.component({ - let \"Newtype$V4A" = (props: props<_>) => make(props) + let \"Newtype$V4A$make" = (props: props<_>) => make(props) - \"Newtype$V4A" + \"Newtype$V4A$make" }) } @@ -27,10 +28,11 @@ module V4A1 = { let make = (type x y, {a, b, c, _}: props, 'a>): React.element => ReactDOM.jsx("div", {}) + @res.hoistedValue let make = React.component({ - let \"Newtype$V4A1" = (props: props<_>) => make(props) + let \"Newtype$V4A1$make" = (props: props<_>) => make(props) - \"Newtype$V4A1" + \"Newtype$V4A1$make" }) } @@ -48,10 +50,11 @@ module V4A2 = { module T = unpack(foo) ReactDOM.jsx("div", {}) } + @res.hoistedValue let make = React.component({ - let \"Newtype$V4A2" = (props: props<_>) => make(props) + let \"Newtype$V4A2$make" = (props: props<_>) => make(props) - \"Newtype$V4A2" + \"Newtype$V4A2$make" }) } @@ -65,10 +68,11 @@ module V4A3 = { module T = unpack(foo: T with type t = a) foo } + @res.hoistedValue let make = React.component({ - let \"Newtype$V4A3" = (props: props<_>) => make(props) + let \"Newtype$V4A3$make" = (props: props<_>) => make(props) - \"Newtype$V4A3" + \"Newtype$V4A3$make" }) } @res.jsxComponentProps @@ -93,14 +97,15 @@ module Uncurried = { } let make = (type a, {?foo, _}: props<_>): React.element => React.null + @res.hoistedValue let make = React.component({ - let \"Newtype$Uncurried" = (props: props<_>) => make(props) + let \"Newtype$Uncurried$make" = (props: props<_>) => make(props) - \"Newtype$Uncurried" + \"Newtype$Uncurried$make" }) } -@warning("-32") let \"Newtype$V4A" = V4A.make -@warning("-32") let \"Newtype$V4A1" = V4A1.make -@warning("-32") let \"Newtype$V4A2" = V4A2.make -@warning("-32") let \"Newtype$V4A3" = V4A3.make -@warning("-32") let \"Newtype$Uncurried" = Uncurried.make +@warning("-32") let \"Newtype$V4A$make" = V4A.make +@warning("-32") let \"Newtype$V4A1$make" = V4A1.make +@warning("-32") let \"Newtype$V4A2$make" = V4A2.make +@warning("-32") let \"Newtype$V4A3$make" = V4A3.make +@warning("-32") let \"Newtype$Uncurried$make" = Uncurried.make diff --git a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt index 4ba4373b84b..f877ba5f4d8 100644 --- a/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/noPropsWithKey.res.txt @@ -5,10 +5,11 @@ module V4CA = { type props = {} let make = (_: props): React.element => ReactDOM.jsx("div", {}) + @res.hoistedValue let make = React.component({ - let \"NoPropsWithKey$V4CA" = props => make(props) + let \"NoPropsWithKey$V4CA$make" = props => make(props) - \"NoPropsWithKey$V4CA" + \"NoPropsWithKey$V4CA$make" }) } @@ -16,7 +17,7 @@ module V4CB = { @res.jsxComponentProps @live type props = {} - @module("c") + @res.hoistedValue @module("c") external make: React.component = "component" } @@ -34,12 +35,13 @@ module V4C = { ]), }, ) + @res.hoistedValue let make = React.component({ - let \"NoPropsWithKey$V4C" = props => make(props) + let \"NoPropsWithKey$V4C$make" = props => make(props) - \"NoPropsWithKey$V4C" + \"NoPropsWithKey$V4C$make" }) } -@warning("-32") let \"NoPropsWithKey$V4CA" = V4CA.make -@warning("-32") let \"NoPropsWithKey$V4CB" = V4CB.make -@warning("-32") let \"NoPropsWithKey$V4C" = V4C.make +@warning("-32") let \"NoPropsWithKey$V4CA$make" = V4CA.make +@warning("-32") let \"NoPropsWithKey$V4CB$make" = V4CB.make +@warning("-32") let \"NoPropsWithKey$V4C$make" = V4C.make diff --git a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt index 44c880aba78..a935e6b9e7c 100644 --- a/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/optimizeAutomaticMode.res.txt @@ -12,10 +12,11 @@ module User = { let make = ({doctor, _}: props<_>): React.element => { ReactDOM.jsx("h1", {id: "h1", children: ?ReactDOM.someElement({React.string(format(doctor))})}) } + @res.hoistedValue let make = React.component({ - let \"OptimizeAutomaticMode$User" = (props: props<_>) => make(props) + let \"OptimizeAutomaticMode$User$make" = (props: props<_>) => make(props) - \"OptimizeAutomaticMode$User" + \"OptimizeAutomaticMode$User$make" }) } -@warning("-32") let \"OptimizeAutomaticMode$User" = User.make +@warning("-32") let \"OptimizeAutomaticMode$User$make" = User.make diff --git a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt index 17d62c28723..748f3b2d4c3 100644 --- a/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/returnConstraint.res.txt @@ -5,10 +5,11 @@ module Standard = { type props = {} let make = (_: props): React.element => React.string("ok") + @res.hoistedValue let make = React.component({ - let \"ReturnConstraint$Standard" = props => make(props) + let \"ReturnConstraint$Standard$make" = props => make(props) - \"ReturnConstraint$Standard" + \"ReturnConstraint$Standard$make" }) } @@ -17,10 +18,11 @@ module ForwardRef = { type props = {} let make = (_: props, _ref): React.element => ReactDOM.jsx("div", {}) + @res.hoistedValue let make = React.forwardRef({ - let \"ReturnConstraint$ForwardRef" = (props, ref) => make(props, ref) + let \"ReturnConstraint$ForwardRef$make" = (props, ref) => make(props, ref) - \"ReturnConstraint$ForwardRef" + \"ReturnConstraint$ForwardRef$make" }) } @@ -29,9 +31,10 @@ module WithProps = { let make = (props: props): React.element => ReactDOM.jsx("span", {children: ?ReactDOM.someElement({React.int(props.value)})}) + @res.hoistedValue let make = React.component({ - let \"ReturnConstraint$WithProps" = (props: props): React.element => make(props) - \"ReturnConstraint$WithProps" + let \"ReturnConstraint$WithProps$make" = (props: props): React.element => make(props) + \"ReturnConstraint$WithProps$make" }) } @@ -41,13 +44,14 @@ module Async = { let make = async (_: props): React.element => ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.string("async")})}) + @res.hoistedValue let make = React.component({ - let \"ReturnConstraint$Async" = props => Jsx.promise(make(props)) + let \"ReturnConstraint$Async$make" = props => Jsx.promise(make(props)) - \"ReturnConstraint$Async" + \"ReturnConstraint$Async$make" }) } -@warning("-32") let \"ReturnConstraint$Standard" = Standard.make -@warning("-32") let \"ReturnConstraint$ForwardRef" = ForwardRef.make -@warning("-32") let \"ReturnConstraint$WithProps" = WithProps.make -@warning("-32") let \"ReturnConstraint$Async" = Async.make +@warning("-32") let \"ReturnConstraint$Standard$make" = Standard.make +@warning("-32") let \"ReturnConstraint$ForwardRef$make" = ForwardRef.make +@warning("-32") let \"ReturnConstraint$WithProps$make" = WithProps.make +@warning("-32") let \"ReturnConstraint$Async$make" = Async.make diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt index 6c708235a39..3dfb57c815e 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.res.txt @@ -4,10 +4,11 @@ module V4A1 = { type props = sharedProps let make = ({x, y, _}: props): React.element => React.string(x ++ y) + @res.hoistedValue let make = React.component({ - let \"SharedProps$V4A1" = props => make(props) + let \"SharedProps$V4A1$make" = props => make(props) - \"SharedProps$V4A1" + \"SharedProps$V4A1$make" }) } @@ -15,10 +16,11 @@ module V4A2 = { type props<'a> = sharedProps<'a> let make = ({x, y, _}: props<_>): React.element => React.string(x ++ y) + @res.hoistedValue let make = React.component({ - let \"SharedProps$V4A2" = (props: props<_>) => make(props) + let \"SharedProps$V4A2$make" = (props: props<_>) => make(props) - \"SharedProps$V4A2" + \"SharedProps$V4A2$make" }) } @@ -26,10 +28,11 @@ module V4A3 = { type props<'a> = sharedProps let make = ({x, y, _}: props<_>): React.element => React.string(x ++ y) + @res.hoistedValue let make = React.component({ - let \"SharedProps$V4A3" = (props: props<_>) => make(props) + let \"SharedProps$V4A3$make" = (props: props<_>) => make(props) - \"SharedProps$V4A3" + \"SharedProps$V4A3$make" }) } @@ -37,41 +40,46 @@ module V4A4 = { type props = sharedProps let make = ({x, y, _}: props): React.element => React.string(x ++ y) + @res.hoistedValue let make = React.component({ - let \"SharedProps$V4A4" = props => make(props) + let \"SharedProps$V4A4$make" = props => make(props) - \"SharedProps$V4A4" + \"SharedProps$V4A4$make" }) } module V4A5 = { type props = sharedProps + @res.hoistedValue external make: React.component = "default" } module V4A6 = { type props<'a> = sharedProps<'a> + @res.hoistedValue external make: React.component> = "default" } module V4A7 = { type props<'a> = sharedProps + @res.hoistedValue external make: React.component> = "default" } module V4A8 = { type props = sharedProps + @res.hoistedValue external make: React.component = "default" } -@warning("-32") let \"SharedProps$V4A1" = V4A1.make -@warning("-32") let \"SharedProps$V4A2" = V4A2.make -@warning("-32") let \"SharedProps$V4A3" = V4A3.make -@warning("-32") let \"SharedProps$V4A4" = V4A4.make -@warning("-32") let \"SharedProps$V4A5" = V4A5.make -@warning("-32") let \"SharedProps$V4A6" = V4A6.make -@warning("-32") let \"SharedProps$V4A7" = V4A7.make -@warning("-32") let \"SharedProps$V4A8" = V4A8.make +@warning("-32") let \"SharedProps$V4A1$make" = V4A1.make +@warning("-32") let \"SharedProps$V4A2$make" = V4A2.make +@warning("-32") let \"SharedProps$V4A3$make" = V4A3.make +@warning("-32") let \"SharedProps$V4A4$make" = V4A4.make +@warning("-32") let \"SharedProps$V4A5$make" = V4A5.make +@warning("-32") let \"SharedProps$V4A6$make" = V4A6.make +@warning("-32") let \"SharedProps$V4A7$make" = V4A7.make +@warning("-32") let \"SharedProps$V4A8$make" = V4A8.make diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt b/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt index 84be2b20b63..73d6d41f254 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedProps.resi.txt @@ -3,24 +3,28 @@ module V4C1: { type props = sharedProps + @res.hoistedValue let make: React.component } module V4C2: { type props<'a> = sharedProps<'a> + @res.hoistedValue let make: React.component> } module V4C3: { type props<'a> = sharedProps + @res.hoistedValue let make: React.component> } module V4C4: { type props = sharedProps + @res.hoistedValue let make: React.component } @@ -29,31 +33,35 @@ module V4C4: { module V4A1: { type props = sharedProps + @res.hoistedValue let make: React.component } module V4A2: { type props<'a> = sharedProps<'a> + @res.hoistedValue let make: React.component> } module V4A3: { type props<'a> = sharedProps + @res.hoistedValue let make: React.component> } module V4A4: { type props = sharedProps + @res.hoistedValue let make: React.component } -let \"SharedProps$V4C1": React.component -let \"SharedProps$V4C2": React.component> -let \"SharedProps$V4C3": React.component> -let \"SharedProps$V4C4": React.component -let \"SharedProps$V4A1": React.component -let \"SharedProps$V4A2": React.component> -let \"SharedProps$V4A3": React.component> -let \"SharedProps$V4A4": React.component +let \"SharedProps$V4C1$make": React.component +let \"SharedProps$V4C2$make": React.component> +let \"SharedProps$V4C3$make": React.component> +let \"SharedProps$V4C4$make": React.component +let \"SharedProps$V4A1$make": React.component +let \"SharedProps$V4A2$make": React.component> +let \"SharedProps$V4A3$make": React.component> +let \"SharedProps$V4A4$make": React.component diff --git a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt index f6aac665995..97049af6199 100644 --- a/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/sharedPropsWithProps.res.txt @@ -5,36 +5,40 @@ let f = a => Js.Promise.resolve(a + a) module V4A1 = { type props = sharedProps let make = (props): React.element => React.string(props.x ++ props.y) + @res.hoistedValue let make = React.component({ - let \"SharedPropsWithProps$V4A1" = (props): React.element => make(props) - \"SharedPropsWithProps$V4A1" + let \"SharedPropsWithProps$V4A1$make" = (props): React.element => make(props) + \"SharedPropsWithProps$V4A1$make" }) } module V4A2 = { type props = sharedProps let make = (props: props): React.element => React.string(props.x ++ props.y) + @res.hoistedValue let make = React.component({ - let \"SharedPropsWithProps$V4A2" = (props: props): React.element => make(props) - \"SharedPropsWithProps$V4A2" + let \"SharedPropsWithProps$V4A2$make" = (props: props): React.element => make(props) + \"SharedPropsWithProps$V4A2$make" }) } module V4A3 = { type props<'a> = sharedProps<'a> let make = ({x, y}: props<_>): React.element => React.string(x ++ y) + @res.hoistedValue let make = React.component({ - let \"SharedPropsWithProps$V4A3" = (props: props<_>): React.element => make(props) - \"SharedPropsWithProps$V4A3" + let \"SharedPropsWithProps$V4A3$make" = (props: props<_>): React.element => make(props) + \"SharedPropsWithProps$V4A3$make" }) } module V4A4 = { type props<'a> = sharedProps let make = ({x, y}: props<_>): React.element => React.string(x ++ y) + @res.hoistedValue let make = React.component({ - let \"SharedPropsWithProps$V4A4" = (props: props<_>): React.element => make(props) - \"SharedPropsWithProps$V4A4" + let \"SharedPropsWithProps$V4A4$make" = (props: props<_>): React.element => make(props) + \"SharedPropsWithProps$V4A4$make" }) } @@ -44,9 +48,11 @@ module V4A5 = { let a = await f(a) (ReactDOM.jsx("div", {children: ?ReactDOM.someElement({React.int(a)})}): React.element) } + @res.hoistedValue let make = React.component({ - let \"SharedPropsWithProps$V4A5" = (props: props<_>): React.element => Jsx.promise(make(props)) - \"SharedPropsWithProps$V4A5" + let \"SharedPropsWithProps$V4A5$make" = (props: props<_>): React.element => + Jsx.promise(make(props)) + \"SharedPropsWithProps$V4A5$make" }) } @@ -58,9 +64,11 @@ module V4A6 = { | #off => React.string("off") } } + @res.hoistedValue let make = React.component({ - let \"SharedPropsWithProps$V4A6" = (props: props<_>): React.element => Jsx.promise(make(props)) - \"SharedPropsWithProps$V4A6" + let \"SharedPropsWithProps$V4A6$make" = (props: props<_>): React.element => + Jsx.promise(make(props)) + \"SharedPropsWithProps$V4A6$make" }) } @@ -70,16 +78,17 @@ module V4A7 = { let make = (props): React.element => { React.int(props.count) } + @res.hoistedValue let make = React.component({ - let \"SharedPropsWithProps$V4A7" = + let \"SharedPropsWithProps$V4A7$make" = @directive("'use memo'") (props): React.element => make(props) - \"SharedPropsWithProps$V4A7" + \"SharedPropsWithProps$V4A7$make" }) } -@warning("-32") let \"SharedPropsWithProps$V4A1" = V4A1.make -@warning("-32") let \"SharedPropsWithProps$V4A2" = V4A2.make -@warning("-32") let \"SharedPropsWithProps$V4A3" = V4A3.make -@warning("-32") let \"SharedPropsWithProps$V4A4" = V4A4.make -@warning("-32") let \"SharedPropsWithProps$V4A5" = V4A5.make -@warning("-32") let \"SharedPropsWithProps$V4A6" = V4A6.make -@warning("-32") let \"SharedPropsWithProps$V4A7" = V4A7.make +@warning("-32") let \"SharedPropsWithProps$V4A1$make" = V4A1.make +@warning("-32") let \"SharedPropsWithProps$V4A2$make" = V4A2.make +@warning("-32") let \"SharedPropsWithProps$V4A3$make" = V4A3.make +@warning("-32") let \"SharedPropsWithProps$V4A4$make" = V4A4.make +@warning("-32") let \"SharedPropsWithProps$V4A5$make" = V4A5.make +@warning("-32") let \"SharedPropsWithProps$V4A6$make" = V4A6.make +@warning("-32") let \"SharedPropsWithProps$V4A7$make" = V4A7.make diff --git a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt index e386499fc25..56047d2dc17 100644 --- a/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/topLevel.res.txt @@ -11,10 +11,11 @@ module V4A = { Js.log("This function should be named 'TopLevel.react'") (ReactDOM.jsx("div", {}): React.element) } + @res.hoistedValue let make = React.component({ - let \"TopLevel$V4A" = (props: props<_>) => make(props) + let \"TopLevel$V4A$make" = (props: props<_>) => make(props) - \"TopLevel$V4A" + \"TopLevel$V4A$make" }) } -@warning("-32") let \"TopLevel$V4A" = V4A.make +@warning("-32") let \"TopLevel$V4A$make" = V4A.make diff --git a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt index 8582e351f44..3d6401bd087 100644 --- a/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/typeConstraint.res.txt @@ -8,10 +8,11 @@ module V4A = { } let make = (type a, {a, b, _}: props<_, _>): React.element => ReactDOM.jsx("div", {}) + @res.hoistedValue let make = React.component({ - let \"TypeConstraint$V4A" = (props: props<_>) => make(props) + let \"TypeConstraint$V4A$make" = (props: props<_>) => make(props) - \"TypeConstraint$V4A" + \"TypeConstraint$V4A$make" }) } -@warning("-32") let \"TypeConstraint$V4A" = V4A.make +@warning("-32") let \"TypeConstraint$V4A$make" = V4A.make diff --git a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt index 096135c718a..16c94843bf0 100644 --- a/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/uncurriedProps.res.txt @@ -46,10 +46,11 @@ module Foo = { }: React.element ) } + @res.hoistedValue let make = React.component({ - let \"UncurriedProps$Foo" = (props: props<_>) => make(props) + let \"UncurriedProps$Foo$make" = (props: props<_>) => make(props) - \"UncurriedProps$Foo" + \"UncurriedProps$Foo$make" }) } @@ -59,11 +60,12 @@ module Bar = { let make = (_: props): React.element => React.jsx(@res.jsxComponentPath Foo.make, {callback: {(_, _, _) => ()}}) + @res.hoistedValue let make = React.component({ - let \"UncurriedProps$Bar" = props => make(props) + let \"UncurriedProps$Bar$make" = props => make(props) - \"UncurriedProps$Bar" + \"UncurriedProps$Bar$make" }) } -@warning("-32") let \"UncurriedProps$Foo" = Foo.make -@warning("-32") let \"UncurriedProps$Bar" = Bar.make +@warning("-32") let \"UncurriedProps$Foo$make" = Foo.make +@warning("-32") let \"UncurriedProps$Bar$make" = Bar.make diff --git a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt index 4d38e2b9918..ea62a6b71a9 100644 --- a/tests/syntax_tests/data/ppx/react/expected/v4.res.txt +++ b/tests/syntax_tests/data/ppx/react/expected/v4.res.txt @@ -31,10 +31,11 @@ module Uncurried = { } let make = ({x, _}: props<_>): React.element => React.string(x) + @res.hoistedValue let make = React.component({ - let \"V4$Uncurried" = (props: props<_>) => make(props) + let \"V4$Uncurried$make" = (props: props<_>) => make(props) - \"V4$Uncurried" + \"V4$Uncurried$make" }) } @@ -53,6 +54,7 @@ module E = { x: 'x, } + @res.hoistedValue external make: React.component> = "default" } @@ -62,6 +64,7 @@ module EUncurried = { x: 'x, } + @res.hoistedValue external make: React.component> = "default" } @@ -72,10 +75,11 @@ module Rec = { let rec make = (_: props): React.element => { make(({}: props)) } + @res.hoistedValue let make = React.component({ - let \"V4$Rec" = props => make(props) + let \"V4$Rec$make" = props => make(props) - \"V4$Rec" + \"V4$Rec$make" }) } @@ -86,10 +90,11 @@ module Rec1 = { let rec make = (_: props): React.element => { React.null } + @res.hoistedValue let make = React.component({ - let \"V4$Rec1" = props => make(props) + let \"V4$Rec1$make" = props => make(props) - \"V4$Rec1" + \"V4$Rec1$make" }) } @@ -102,15 +107,16 @@ module Rec2 = { } and mm = x => make(x) + @res.hoistedValue let make = React.component({ - let \"V4$Rec2" = props => make(props) + let \"V4$Rec2$make" = props => make(props) - \"V4$Rec2" + \"V4$Rec2$make" }) } -@warning("-32") let \"V4$Uncurried" = Uncurried.make -@warning("-32") let \"V4$E" = E.make -@warning("-32") let \"V4$EUncurried" = EUncurried.make -@warning("-32") let \"V4$Rec" = Rec.make -@warning("-32") let \"V4$Rec1" = Rec1.make -@warning("-32") let \"V4$Rec2" = Rec2.make +@warning("-32") let \"V4$Uncurried$make" = Uncurried.make +@warning("-32") let \"V4$E$make" = E.make +@warning("-32") let \"V4$EUncurried$make" = EUncurried.make +@warning("-32") let \"V4$Rec$make" = Rec.make +@warning("-32") let \"V4$Rec1$make" = Rec1.make +@warning("-32") let \"V4$Rec2$make" = Rec2.make diff --git a/tests/tests/src/ExternalArity.mjs b/tests/tests/src/ExternalArity.mjs index d714c53dfc9..575efd12dea 100644 --- a/tests/tests/src/ExternalArity.mjs +++ b/tests/tests/src/ExternalArity.mjs @@ -37,13 +37,13 @@ let ReactTest = { FormattedMessage: FormattedMessage }; -let ExternalArity$ReactTest$FormattedMessage = ReactIntl.FormattedMessage; +let ExternalArity$ReactTest$FormattedMessage$make = ReactIntl.FormattedMessage; export { f1, f2, FromTypeConstructor, ReactTest, - ExternalArity$ReactTest$FormattedMessage, + ExternalArity$ReactTest$FormattedMessage$make, } /* test1 Not a pure module */ diff --git a/tests/tests/src/alias_default_value_test.mjs b/tests/tests/src/alias_default_value_test.mjs index fce9a47f580..f02518d1492 100644 --- a/tests/tests/src/alias_default_value_test.mjs +++ b/tests/tests/src/alias_default_value_test.mjs @@ -1,7 +1,7 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -function Alias_default_value_test$C0(props) { +function Alias_default_value_test$C0$make(props) { let __b = props.b; let __a = props.a; let __a_value = __a !== undefined ? __a : 2; @@ -10,10 +10,10 @@ function Alias_default_value_test$C0(props) { } let C0 = { - make: Alias_default_value_test$C0 + make: Alias_default_value_test$C0$make }; -function Alias_default_value_test$C1(props) { +function Alias_default_value_test$C1$make(props) { let __foo = props.foo; if (__foo !== undefined) { return __foo; @@ -23,10 +23,10 @@ function Alias_default_value_test$C1(props) { } let C1 = { - make: Alias_default_value_test$C1 + make: Alias_default_value_test$C1$make }; -function Alias_default_value_test$C2(props) { +function Alias_default_value_test$C2$make(props) { let __a = props.a; let __foo = props.foo; let __foo_value = __foo !== undefined ? __foo : ""; @@ -35,10 +35,10 @@ function Alias_default_value_test$C2(props) { } let C2 = { - make: Alias_default_value_test$C2 + make: Alias_default_value_test$C2$make }; -function Alias_default_value_test$C3(props) { +function Alias_default_value_test$C3$make(props) { let __text = props.text; if (__text !== undefined) { return __text; @@ -48,26 +48,26 @@ function Alias_default_value_test$C3(props) { } let C3 = { - make: Alias_default_value_test$C3 + make: Alias_default_value_test$C3$make }; -function Alias_default_value_test$C4(props) { +function Alias_default_value_test$C4$make(props) { return props.a; } let C4 = { - make: Alias_default_value_test$C4 + make: Alias_default_value_test$C4$make }; -function Alias_default_value_test$C6(props) { +function Alias_default_value_test$C6$make(props) { return props.comp.xx; } let C6 = { - make: Alias_default_value_test$C6 + make: Alias_default_value_test$C6$make }; -function Alias_default_value_test$C7(props) { +function Alias_default_value_test$C7$make(props) { 'use memo'; let username = props.username; let count = props.count; @@ -79,16 +79,16 @@ function Alias_default_value_test$C7(props) { } let C7 = { - make: Alias_default_value_test$C7 + make: Alias_default_value_test$C7$make }; -function Alias_default_value_test$C8(props) { +function Alias_default_value_test$C8$make(props) { 'use memo'; return props.count; } let C8 = { - make: Alias_default_value_test$C8 + make: Alias_default_value_test$C8$make }; export { @@ -100,13 +100,13 @@ export { C6, C7, C8, - Alias_default_value_test$C0, - Alias_default_value_test$C1, - Alias_default_value_test$C2, - Alias_default_value_test$C3, - Alias_default_value_test$C4, - Alias_default_value_test$C6, - Alias_default_value_test$C7, - Alias_default_value_test$C8, + Alias_default_value_test$C0$make, + Alias_default_value_test$C1$make, + Alias_default_value_test$C2$make, + Alias_default_value_test$C3$make, + Alias_default_value_test$C4$make, + Alias_default_value_test$C6$make, + Alias_default_value_test$C7$make, + Alias_default_value_test$C8$make, } /* No side effect */ diff --git a/tests/tests/src/async_jsx.mjs b/tests/tests/src/async_jsx.mjs index f19b9549c2a..3284328fb6b 100644 --- a/tests/tests/src/async_jsx.mjs +++ b/tests/tests/src/async_jsx.mjs @@ -17,27 +17,27 @@ async function make(param) { ; } -let Async_jsx$Foo = make; +let Async_jsx$Foo$make = make; let Foo = { - make: Async_jsx$Foo + make: Async_jsx$Foo$make }; -function Async_jsx$Bar(props) { +function Async_jsx$Bar$make(props) { return
; } let Bar = { - make: Async_jsx$Bar + make: Async_jsx$Bar$make }; export { getNow, Foo, Bar, - Async_jsx$Foo, - Async_jsx$Bar, + Async_jsx$Foo$make, + Async_jsx$Bar$make, } /* react/jsx-runtime Not a pure module */ diff --git a/tests/tests/src/jsx_optional_props_test.mjs b/tests/tests/src/jsx_optional_props_test.mjs index 1d458a890d2..0fa7988371b 100644 --- a/tests/tests/src/jsx_optional_props_test.mjs +++ b/tests/tests/src/jsx_optional_props_test.mjs @@ -2,15 +2,15 @@ import * as JsxRuntime from "react/jsx-runtime"; -function Jsx_optional_props_test$ComponentWithOptionalProps(props) { +function Jsx_optional_props_test$ComponentWithOptionalProps$make(props) { return null; } let ComponentWithOptionalProps = { - make: Jsx_optional_props_test$ComponentWithOptionalProps + make: Jsx_optional_props_test$ComponentWithOptionalProps$make }; -let _element = JsxRuntime.jsx(Jsx_optional_props_test$ComponentWithOptionalProps, { +let _element = JsxRuntime.jsx(Jsx_optional_props_test$ComponentWithOptionalProps$make, { i: 1, s: "test", element: JsxRuntime.jsx("div", {}) @@ -19,6 +19,6 @@ let _element = JsxRuntime.jsx(Jsx_optional_props_test$ComponentWithOptionalProps export { ComponentWithOptionalProps, _element, - Jsx_optional_props_test$ComponentWithOptionalProps, + Jsx_optional_props_test$ComponentWithOptionalProps$make, } /* _element Not a pure module */ diff --git a/tests/tests/src/jsx_preserve_test.mjs b/tests/tests/src/jsx_preserve_test.mjs index 76fb981e3b9..94f85f79e05 100644 --- a/tests/tests/src/jsx_preserve_test.mjs +++ b/tests/tests/src/jsx_preserve_test.mjs @@ -3,12 +3,12 @@ import * as React from "react"; import * as JsxRuntime from "react/jsx-runtime"; -function Jsx_preserve_test$Icon(props) { +function Jsx_preserve_test$Icon$make(props) { return ; } let Icon = { - make: Jsx_preserve_test$Icon + make: Jsx_preserve_test$Icon$make }; let _single_element_child =
@@ -113,14 +113,14 @@ function QueryClientProvider(props) { return props.children } let A = {}; -function Jsx_preserve_test$B(props) { +function Jsx_preserve_test$B$make(props) { return

{"Hello, world!"}

; } let B = { - make: Jsx_preserve_test$B + make: Jsx_preserve_test$B$make }; let _external_component_with_children = @@ -128,7 +128,7 @@ let _external_component_with_children = ; -function Jsx_preserve_test$MyWeirdComponent(props) { +function Jsx_preserve_test$MyWeirdComponent$make(props) { return

{"foo"} {props.MyWeirdProp} @@ -136,7 +136,7 @@ function Jsx_preserve_test$MyWeirdComponent(props) { } let MyWeirdComponent = { - make: Jsx_preserve_test$MyWeirdComponent + make: Jsx_preserve_test$MyWeirdComponent$make }; let _escaped_jsx_prop =

; -function Jsx_preserve_test$ComponentWithOptionalProps(props) { +function Jsx_preserve_test$ComponentWithOptionalProps$make(props) { return null; } let ComponentWithOptionalProps = { - make: Jsx_preserve_test$ComponentWithOptionalProps + make: Jsx_preserve_test$ComponentWithOptionalProps$make }; let _optional_props = ; -function Jsx_preserve_test$X(props) { +function Jsx_preserve_test$X$make(props) { return null; } let X = { - make: Jsx_preserve_test$X + make: Jsx_preserve_test$X$make }; ; -function Jsx_preserve_test$Y(props) { +function Jsx_preserve_test$Y$make(props) { return null; } -let make = React.memo(Jsx_preserve_test$Y); +let make = React.memo(Jsx_preserve_test$Y$make); let Y = { x: 42, @@ -247,11 +247,11 @@ function Jsx_preserve_test(props) { ; } -let Jsx_preserve_test$A = QueryClientProvider; +let Jsx_preserve_test$A$make = QueryClientProvider; let make$2 = Jsx_preserve_test; -let Jsx_preserve_test$Y$1 = make; +let Jsx_preserve_test$Y$make$1 = make; export { Icon, @@ -285,12 +285,12 @@ export { context, ContextProvider, make$2 as make, - Jsx_preserve_test$Icon, - Jsx_preserve_test$A, - Jsx_preserve_test$B, - Jsx_preserve_test$MyWeirdComponent, - Jsx_preserve_test$ComponentWithOptionalProps, - Jsx_preserve_test$X, - Jsx_preserve_test$Y$1 as Jsx_preserve_test$Y, + Jsx_preserve_test$Icon$make, + Jsx_preserve_test$A$make, + Jsx_preserve_test$B$make, + Jsx_preserve_test$MyWeirdComponent$make, + Jsx_preserve_test$ComponentWithOptionalProps$make, + Jsx_preserve_test$X$make, + Jsx_preserve_test$Y$make$1 as Jsx_preserve_test$Y$make, } /* _single_element_child Not a pure module */ diff --git a/tests/tests/src/jsxv4_newtype.mjs b/tests/tests/src/jsxv4_newtype.mjs index 5dd32460796..41a4bb8b14e 100644 --- a/tests/tests/src/jsxv4_newtype.mjs +++ b/tests/tests/src/jsxv4_newtype.mjs @@ -1,36 +1,36 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -function Jsxv4_newtype$V4A(props) { +function Jsxv4_newtype$V4A$make(props) { return null; } let V4A = { - make: Jsxv4_newtype$V4A + make: Jsxv4_newtype$V4A$make }; -function Jsxv4_newtype$V4A1(props) { +function Jsxv4_newtype$V4A1$make(props) { return null; } let V4A1 = { - make: Jsxv4_newtype$V4A1 + make: Jsxv4_newtype$V4A1$make }; -function Jsxv4_newtype$V4A2(props) { +function Jsxv4_newtype$V4A2$make(props) { return null; } let V4A2 = { - make: Jsxv4_newtype$V4A2 + make: Jsxv4_newtype$V4A2$make }; -function Jsxv4_newtype$V4A3(props) { +function Jsxv4_newtype$V4A3$make(props) { return null; } let V4A3 = { - make: Jsxv4_newtype$V4A3 + make: Jsxv4_newtype$V4A3$make }; export { @@ -38,9 +38,9 @@ export { V4A1, V4A2, V4A3, - Jsxv4_newtype$V4A, - Jsxv4_newtype$V4A1, - Jsxv4_newtype$V4A2, - Jsxv4_newtype$V4A3, + Jsxv4_newtype$V4A$make, + Jsxv4_newtype$V4A1$make, + Jsxv4_newtype$V4A2$make, + Jsxv4_newtype$V4A3$make, } /* No side effect */ diff --git a/tests/tests/src/recursive_react_component.mjs b/tests/tests/src/recursive_react_component.mjs index 7a991e78ca0..2c263b1aaca 100644 --- a/tests/tests/src/recursive_react_component.mjs +++ b/tests/tests/src/recursive_react_component.mjs @@ -10,42 +10,42 @@ function make(param) { let Recursive_react_component = make; -function Recursive_react_component$ShadowedSelfReference(props) { +function Recursive_react_component$ShadowedSelfReference$make(props) { return React.createElement(props.make, { foo: props.foo }); } let ShadowedSelfReference = { - make: Recursive_react_component$ShadowedSelfReference + make: Recursive_react_component$ShadowedSelfReference$make }; -function Recursive_react_component$Leaf(props) { +function Recursive_react_component$Leaf$make(props) { return props.foo; } let Leaf = { - make: Recursive_react_component$Leaf + make: Recursive_react_component$Leaf$make }; -function Recursive_react_component$ShadowedByLocalLet(props) { - return React.createElement(Recursive_react_component$Leaf, { +function Recursive_react_component$ShadowedByLocalLet$make(props) { + return React.createElement(Recursive_react_component$Leaf$make, { foo: props.foo }); } let ShadowedByLocalLet = { - make: Recursive_react_component$ShadowedByLocalLet + make: Recursive_react_component$ShadowedByLocalLet$make }; -function Recursive_react_component$ShadowedByNestedParameter(props) { - return React.createElement(Recursive_react_component$Leaf, { +function Recursive_react_component$ShadowedByNestedParameter$make(props) { + return React.createElement(Recursive_react_component$Leaf$make, { foo: props.foo }); } let ShadowedByNestedParameter = { - make: Recursive_react_component$ShadowedByNestedParameter + make: Recursive_react_component$ShadowedByNestedParameter$make }; let make$1 = Recursive_react_component; @@ -56,9 +56,9 @@ export { Leaf, ShadowedByLocalLet, ShadowedByNestedParameter, - Recursive_react_component$ShadowedSelfReference, - Recursive_react_component$Leaf, - Recursive_react_component$ShadowedByLocalLet, - Recursive_react_component$ShadowedByNestedParameter, + Recursive_react_component$ShadowedSelfReference$make, + Recursive_react_component$Leaf$make, + Recursive_react_component$ShadowedByLocalLet$make, + Recursive_react_component$ShadowedByNestedParameter$make, } /* react Not a pure module */ From 9d3d4fee951298f4d138a1c7c03a569da6f9ae2f Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 30 Apr 2026 23:15:04 +0200 Subject: [PATCH 55/56] Resolve hoisted component paths in value lowering Signed-off-by: Florian Hammerschmidt --- compiler/ml/lambda.ml | 54 ++++++++++++++++++- compiler/ml/translcore.ml | 47 +--------------- .../rsc_component_with_props_members/input.js | 8 +-- .../rsc_nested_jsx_alias_chain/input.js | 4 +- .../rsc_nested_jsx_members/input.js | 4 +- 5 files changed, 63 insertions(+), 54 deletions(-) diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index a111bc388f8..710e7149e90 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -643,13 +643,65 @@ let rec transl_normal_path = function Location.none ) | Papply _ -> assert false +let has_hoisted_value_attr attrs = + List.exists + (fun ({Location.txt; _}, _) -> String.equal txt "res.hoistedValue") + attrs + +let hoisted_value_root_name module_name = + match Ext_namespace.try_split_module_name module_name with + | Some (_namespace, module_name) -> module_name + | None -> ( + match + String.index_opt module_name + Ext_modulename.nested_component_separator_char + with + | Some index -> String.sub module_name 0 index + | None -> module_name) + +let hoisted_value_path env path = + match Path.flatten path with + | `Ok (root, (_ :: _ as segments)) -> + let root_name = Ident.name root in + let hidden_path_for_global_root () = + let hidden_name = + Ext_modulename.concat_nested_component_name + (hoisted_value_root_name root_name :: segments) + in + Path.Pdot (Path.Pident root, hidden_name, Path.nopos) + in + let hidden_path_for_local_root () = + let unit_name = Env.get_unit_name () in + let hidden_segments = + if String.equal unit_name "" then + hoisted_value_root_name root_name :: segments + else hoisted_value_root_name unit_name :: root_name :: segments + in + let hidden_name = + Ext_modulename.concat_nested_component_name hidden_segments + in + match Env.lookup_value (Longident.Lident hidden_name) env with + | hidden_path, _ -> Some hidden_path + | exception Not_found -> None + in + if Ident.global root then Some (hidden_path_for_global_root ()) + else hidden_path_for_local_root () + | `Ok (_, []) | `Contains_apply -> None + (* Translation of identifiers *) let transl_module_path ?(loc = Location.none) env path = transl_normal_path (Env.normalize_path (Some loc) env path) let transl_value_path ?(loc = Location.none) env path = - transl_normal_path (Env.normalize_path_prefix (Some loc) env path) + let path = Env.normalize_path_prefix (Some loc) env path in + match Env.find_value path env with + | desc when has_hoisted_value_attr desc.val_attributes -> ( + match hoisted_value_path env path with + | Some hidden_path -> + transl_normal_path (Env.normalize_path_prefix None env hidden_path) + | None -> transl_normal_path path) + | _ | (exception Not_found) -> transl_normal_path path let transl_extension_path = transl_value_path diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index aaadb5e8fa8..ecf3a73528d 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -667,41 +667,6 @@ let extract_directive_for_fn exp = if txt = "directive" then Ast_payload.is_single_string payload else None) -let has_jsx_component_path_attr (exp : Typedtree.expression) = - List.exists - (fun ({txt; _}, _) -> String.equal txt "res.jsxComponentPath") - exp.exp_attributes - -let has_hoisted_value_attr (attrs : Parsetree.attributes) = - List.exists (fun ({txt; _}, _) -> String.equal txt "res.hoistedValue") attrs - -let nested_component_root_name module_name = - match Ext_namespace.try_split_module_name module_name with - | Some (_namespace, module_name) -> module_name - | None -> ( - match - String.index_opt module_name - Ext_modulename.nested_component_separator_char - with - | Some index -> String.sub module_name 0 index - | None -> module_name) - -let nested_jsx_component_export_path ~loc env path = - match Env.normalize_path_prefix (Some loc) env path |> Path.flatten with - | `Ok (root, segments) -> ( - match segments with - | [] -> None - | _ :: _ -> ( - let hidden_name = - Ext_modulename.concat_nested_component_name - (nested_component_root_name (Ident.name root) :: segments) - in - let hidden_path = Path.Pdot (Path.Pident root, hidden_name, Path.nopos) in - match Env.find_value hidden_path env with - | _ -> Some hidden_path - | exception Not_found -> None)) - | `Contains_apply -> None - let rec transl_exp e = Builtin_attributes.warning_scope ~ppwarning:false e.exp_attributes (fun () -> List.iter (Translattribute.check_attribute e) e.exp_attributes; @@ -711,16 +676,8 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = match e.exp_desc with | Texp_ident (_, _, {val_kind = Val_prim p}) -> transl_primitive e.exp_loc p e.exp_env e.exp_type - | Texp_ident (path, _, ({val_kind = Val_reg} as desc)) -> ( - match - if - has_jsx_component_path_attr e - && has_hoisted_value_attr desc.val_attributes - then nested_jsx_component_export_path ~loc:e.exp_loc e.exp_env path - else None - with - | Some path -> transl_value_path ~loc:e.exp_loc e.exp_env path - | None -> transl_value_path ~loc:e.exp_loc e.exp_env path) + | Texp_ident (path, _, {val_kind = Val_reg}) -> + transl_value_path ~loc:e.exp_loc e.exp_env path | Texp_constant cst -> Lconst (Const_base cst) | Texp_let (rec_flag, pat_expr_list, body) -> transl_let rec_flag pat_expr_list (transl_exp body) diff --git a/tests/build_tests/rsc_component_with_props_members/input.js b/tests/build_tests/rsc_component_with_props_members/input.js index 86e2f84ca16..4a7690eeccd 100644 --- a/tests/build_tests/rsc_component_with_props_members/input.js +++ b/tests/build_tests/rsc_component_with_props_members/input.js @@ -51,19 +51,19 @@ assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Inset\$jsx/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscComponentWithPropsMembers\.Provider\.make;/, + /let provider = Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider\$make;/, ); assert.match( plainAccessOutput, - /let inset = Sidebar\$RscComponentWithPropsMembers\.Inset\.make;/, + /let inset = Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset\$make;/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider/, + /Sidebar\$RscComponentWithPropsMembers\.Provider\.make/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset/, + /Sidebar\$RscComponentWithPropsMembers\.Inset\.make/, ); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js index b3406de00e8..73e78a56f54 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -26,11 +26,11 @@ assert.match( assert.doesNotMatch(layoutOutput, /\.Provider\.make,/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxAliasChain\.Provider\.make;/, + /let provider = Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider\$make;/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider/, + /Sidebar\$RscNestedJsxAliasChain\.Provider\.make/, ); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 4f5a1e1ea04..03132c77f34 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -97,11 +97,11 @@ assert.match( ); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxMembers\.Provider\.make;/, + /let provider = Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider\$make;/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider/, + /Sidebar\$RscNestedJsxMembers\.Provider\.make/, ); assert.match( buttonLayoutOutput, From aac7e2f2a087cb319423507c26f4e1d0d9a12abd Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Thu, 30 Apr 2026 23:24:42 +0200 Subject: [PATCH 56/56] Revert "Resolve hoisted component paths in value lowering" This reverts commit 9d3d4fee951298f4d138a1c7c03a569da6f9ae2f. --- compiler/ml/lambda.ml | 54 +------------------ compiler/ml/translcore.ml | 47 +++++++++++++++- .../rsc_component_with_props_members/input.js | 8 +-- .../rsc_nested_jsx_alias_chain/input.js | 4 +- .../rsc_nested_jsx_members/input.js | 4 +- 5 files changed, 54 insertions(+), 63 deletions(-) diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index 710e7149e90..a111bc388f8 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -643,65 +643,13 @@ let rec transl_normal_path = function Location.none ) | Papply _ -> assert false -let has_hoisted_value_attr attrs = - List.exists - (fun ({Location.txt; _}, _) -> String.equal txt "res.hoistedValue") - attrs - -let hoisted_value_root_name module_name = - match Ext_namespace.try_split_module_name module_name with - | Some (_namespace, module_name) -> module_name - | None -> ( - match - String.index_opt module_name - Ext_modulename.nested_component_separator_char - with - | Some index -> String.sub module_name 0 index - | None -> module_name) - -let hoisted_value_path env path = - match Path.flatten path with - | `Ok (root, (_ :: _ as segments)) -> - let root_name = Ident.name root in - let hidden_path_for_global_root () = - let hidden_name = - Ext_modulename.concat_nested_component_name - (hoisted_value_root_name root_name :: segments) - in - Path.Pdot (Path.Pident root, hidden_name, Path.nopos) - in - let hidden_path_for_local_root () = - let unit_name = Env.get_unit_name () in - let hidden_segments = - if String.equal unit_name "" then - hoisted_value_root_name root_name :: segments - else hoisted_value_root_name unit_name :: root_name :: segments - in - let hidden_name = - Ext_modulename.concat_nested_component_name hidden_segments - in - match Env.lookup_value (Longident.Lident hidden_name) env with - | hidden_path, _ -> Some hidden_path - | exception Not_found -> None - in - if Ident.global root then Some (hidden_path_for_global_root ()) - else hidden_path_for_local_root () - | `Ok (_, []) | `Contains_apply -> None - (* Translation of identifiers *) let transl_module_path ?(loc = Location.none) env path = transl_normal_path (Env.normalize_path (Some loc) env path) let transl_value_path ?(loc = Location.none) env path = - let path = Env.normalize_path_prefix (Some loc) env path in - match Env.find_value path env with - | desc when has_hoisted_value_attr desc.val_attributes -> ( - match hoisted_value_path env path with - | Some hidden_path -> - transl_normal_path (Env.normalize_path_prefix None env hidden_path) - | None -> transl_normal_path path) - | _ | (exception Not_found) -> transl_normal_path path + transl_normal_path (Env.normalize_path_prefix (Some loc) env path) let transl_extension_path = transl_value_path diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index ecf3a73528d..aaadb5e8fa8 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -667,6 +667,41 @@ let extract_directive_for_fn exp = if txt = "directive" then Ast_payload.is_single_string payload else None) +let has_jsx_component_path_attr (exp : Typedtree.expression) = + List.exists + (fun ({txt; _}, _) -> String.equal txt "res.jsxComponentPath") + exp.exp_attributes + +let has_hoisted_value_attr (attrs : Parsetree.attributes) = + List.exists (fun ({txt; _}, _) -> String.equal txt "res.hoistedValue") attrs + +let nested_component_root_name module_name = + match Ext_namespace.try_split_module_name module_name with + | Some (_namespace, module_name) -> module_name + | None -> ( + match + String.index_opt module_name + Ext_modulename.nested_component_separator_char + with + | Some index -> String.sub module_name 0 index + | None -> module_name) + +let nested_jsx_component_export_path ~loc env path = + match Env.normalize_path_prefix (Some loc) env path |> Path.flatten with + | `Ok (root, segments) -> ( + match segments with + | [] -> None + | _ :: _ -> ( + let hidden_name = + Ext_modulename.concat_nested_component_name + (nested_component_root_name (Ident.name root) :: segments) + in + let hidden_path = Path.Pdot (Path.Pident root, hidden_name, Path.nopos) in + match Env.find_value hidden_path env with + | _ -> Some hidden_path + | exception Not_found -> None)) + | `Contains_apply -> None + let rec transl_exp e = Builtin_attributes.warning_scope ~ppwarning:false e.exp_attributes (fun () -> List.iter (Translattribute.check_attribute e) e.exp_attributes; @@ -676,8 +711,16 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = match e.exp_desc with | Texp_ident (_, _, {val_kind = Val_prim p}) -> transl_primitive e.exp_loc p e.exp_env e.exp_type - | Texp_ident (path, _, {val_kind = Val_reg}) -> - transl_value_path ~loc:e.exp_loc e.exp_env path + | Texp_ident (path, _, ({val_kind = Val_reg} as desc)) -> ( + match + if + has_jsx_component_path_attr e + && has_hoisted_value_attr desc.val_attributes + then nested_jsx_component_export_path ~loc:e.exp_loc e.exp_env path + else None + with + | Some path -> transl_value_path ~loc:e.exp_loc e.exp_env path + | None -> transl_value_path ~loc:e.exp_loc e.exp_env path) | Texp_constant cst -> Lconst (Const_base cst) | Texp_let (rec_flag, pat_expr_list, body) -> transl_let rec_flag pat_expr_list (transl_exp body) diff --git a/tests/build_tests/rsc_component_with_props_members/input.js b/tests/build_tests/rsc_component_with_props_members/input.js index 4a7690eeccd..86e2f84ca16 100644 --- a/tests/build_tests/rsc_component_with_props_members/input.js +++ b/tests/build_tests/rsc_component_with_props_members/input.js @@ -51,19 +51,19 @@ assert.doesNotMatch(sidebarOutput, /Sidebar\$Provider\$jsx/); assert.doesNotMatch(sidebarOutput, /Sidebar\$Inset\$jsx/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider\$make;/, + /let provider = Sidebar\$RscComponentWithPropsMembers\.Provider\.make;/, ); assert.match( plainAccessOutput, - /let inset = Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset\$make;/, + /let inset = Sidebar\$RscComponentWithPropsMembers\.Inset\.make;/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscComponentWithPropsMembers\.Provider\.make/, + /Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Provider/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscComponentWithPropsMembers\.Inset\.make/, + /Sidebar\$RscComponentWithPropsMembers\.Sidebar\$Inset/, ); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js index 73e78a56f54..b3406de00e8 100644 --- a/tests/build_tests/rsc_nested_jsx_alias_chain/input.js +++ b/tests/build_tests/rsc_nested_jsx_alias_chain/input.js @@ -26,11 +26,11 @@ assert.match( assert.doesNotMatch(layoutOutput, /\.Provider\.make,/); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider\$make;/, + /let provider = Sidebar\$RscNestedJsxAliasChain\.Provider\.make;/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscNestedJsxAliasChain\.Provider\.make/, + /Sidebar\$RscNestedJsxAliasChain\.Sidebar\$Provider/, ); await execClean(); diff --git a/tests/build_tests/rsc_nested_jsx_members/input.js b/tests/build_tests/rsc_nested_jsx_members/input.js index 03132c77f34..4f5a1e1ea04 100644 --- a/tests/build_tests/rsc_nested_jsx_members/input.js +++ b/tests/build_tests/rsc_nested_jsx_members/input.js @@ -97,11 +97,11 @@ assert.match( ); assert.match( plainAccessOutput, - /let provider = Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider\$make;/, + /let provider = Sidebar\$RscNestedJsxMembers\.Provider\.make;/, ); assert.doesNotMatch( plainAccessOutput, - /Sidebar\$RscNestedJsxMembers\.Provider\.make/, + /Sidebar\$RscNestedJsxMembers\.Sidebar\$Provider/, ); assert.match( buttonLayoutOutput,