Skip to content

Conversation

cknitt
Copy link
Member

@cknitt cknitt commented Sep 2, 2025

Closes #7432 by disabling two optimization when in JSX preserve mode.

Ideally, the optimization should only be disabled for actual JSX expressions, but I have not found how to do that yet.

Copy link

pkg-pr-new bot commented Sep 2, 2025

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript-lang/rescript@7831

@rescript/darwin-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-arm64@7831

@rescript/darwin-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-x64@7831

@rescript/linux-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-arm64@7831

@rescript/linux-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-x64@7831

@rescript/runtime

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/runtime@7831

@rescript/win32-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/win32-x64@7831

commit: bd76cf1

nojaf
nojaf previously requested changes Sep 2, 2025
Copy link
Member

@nojaf nojaf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your continued efforts to resolve this.

Could you add a new test case using the example from #7432?

I also think this code no longer makes sense because of that change:

(* Build a string representation of a module name with segments separated by $ *)
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
| "", nested_modules, fn_name -> List.rev (fn_name :: nested_modules)
| file_name, nested_modules, "make" -> file_name :: List.rev nested_modules
| 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
full_module_name

Transforming make to FileName$ModuleName appears irrelevant now since that won't be called anymore.

Related test: https://github.com/cknitt/rescript/blob/a1e7cc545c0da894aa3f992ae1b783fe684af220/tests/tests/src/jsx_preserve_test.mjs#L147C28-L160

Given this, should this behavior become the default regardless of preserve JSX? If so, the work in #7203 could be undone.

@cknitt
Copy link
Member Author

cknitt commented Sep 2, 2025

Thanks for your continued efforts to resolve this.

Could you add a new test case using the example from #7432?

I also think this code no longer makes sense because of that change:

(* Build a string representation of a module name with segments separated by $ *)
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
| "", nested_modules, fn_name -> List.rev (fn_name :: nested_modules)
| file_name, nested_modules, "make" -> file_name :: List.rev nested_modules
| 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
full_module_name

Transforming make to FileName$ModuleName appears irrelevant now since that won't be called anymore.

Related test: https://github.com/cknitt/rescript/blob/a1e7cc545c0da894aa3f992ae1b783fe684af220/tests/tests/src/jsx_preserve_test.mjs#L147C28-L160

Given this, should this behavior become the default regardless of preserve JSX? If so, the work in #7203 could be undone.

The uppercase function name is relevant for the React devtools. Otherwise all your components will have the name "make" there.

Also, React Compiler does not recognize lowercase component functions.

@nojaf
Copy link
Member

nojaf commented Sep 2, 2025

I assumed wrongly that the React compiler would follow <C.make> and figure out it is a component.

function make(props) {
  return null;
}

let C = {
  make: make
};

let c = <C.make />;

The C in the jsx expression being uppercase, doesn't change anything.
function make needs to be uppercase before it will trigger.

Copy link
Member

@nojaf nojaf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this to @cristianoc. I can't quite tell whether it's harmless, so I suggested a few changes to tighten it up. Think of them as ideal-world tips since I'm not sure they're all feasible.

@@ -245,6 +245,10 @@ let subst_map (substitution : J.expression Hash_ident.t) =
turn a runtime crash into compile time crash : )
*)
match Ext_list.nth_opt ls (Int32.to_int i) with
(* 7432: prevent optimization in JSX preserve mode *)
| Some {expression_desc = J.Var (Id {name = "make"})}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check could in theory affect each make function right?
Could we add some sort of check if that make returns a Jsx.element type? Just to further limit the scope when this kicks in.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's no type this down the compiler stack

(* 7432: prevent optimization in JSX preserve mode *)
| Lprim
{
primitive = Pjs_call {prim_name = "jsx" | "jsxs"} as primitive;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same remark, very unlikely that jsx is not going to come from ReactRuntime call, if we can further tighten it that be great.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's plenty guarded against risk already

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not the same check that preserve mode is on?
I guess it's kind of implicit, and only a super corner case would have the same name, but since it's so easy to add why not

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check for preserve mode is already there (below).

@@ -224,6 +223,47 @@ let _youtube_iframe = <iframe
referrerPolicy={"strict-origin-when-cross-origin"}
/>;

function make(_props) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the tests!

@nojaf nojaf dismissed their stale review September 2, 2025 16:05

No longer valid.

Copy link
Collaborator

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks plenty safe -- good to go for me
nice fix!

(* 7432: prevent optimization in JSX preserve mode *)
| Lprim
{
primitive = Pjs_call {prim_name = "jsx" | "jsxs"} as primitive;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's plenty guarded against risk already

(* 7432: prevent optimization in JSX preserve mode *)
| Lprim
{
primitive = Pjs_call {prim_name = "jsx" | "jsxs"} as primitive;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not the same check that preserve mode is on?
I guess it's kind of implicit, and only a super corner case would have the same name, but since it's so easy to add why not

@cknitt
Copy link
Member Author

cknitt commented Sep 2, 2025

I tested against a large project, and the changes looked fine to me.

BTW, if I remove the checks for the JSX preserve mode and do make test, I only have a single change in the test output.

@cknitt cknitt merged commit 2dc0103 into rescript-lang:master Sep 2, 2025
25 checks passed
@cknitt cknitt deleted the jsx-preserve-fix branch September 2, 2025 17:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

JSX preserve mode: make is not a valid component name
3 participants