From 0fdd4801787eb4affafd7824d08710c2a5f453e5 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 10 Sep 2025 12:19:20 +0000
Subject: [PATCH 1/4] Initial plan
From 4b8a0c53a536a66e33c407663ecea4da8e759bd3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 10 Sep 2025 12:34:17 +0000
Subject: [PATCH 2/4] Drop support of JSX children spread
Co-authored-by: tsnobip <2479216+tsnobip@users.noreply.github.com>
---
CHANGELOG.md | 1 +
analysis/src/CompletionFrontEnd.ml | 3 +-
analysis/src/CompletionJsx.ml | 2 +-
analysis/src/SemanticTokens.ml | 1 -
compiler/frontend/bs_ast_mapper.ml | 1 -
compiler/ml/ast_iterator.ml | 1 -
compiler/ml/ast_mapper.ml | 1 -
compiler/ml/ast_mapper_from0.ml | 2 +-
compiler/ml/ast_mapper_to0.ml | 1 -
compiler/ml/depend.ml | 1 -
compiler/ml/parsetree.ml | 4 +-
compiler/ml/pprintast.ml | 1 -
compiler/ml/printast.ml | 1 -
compiler/syntax/src/jsx_v4.ml | 3 +-
compiler/syntax/src/res_ast_debugger.ml | 2 -
compiler/syntax/src/res_comments_table.ml | 2 -
compiler/syntax/src/res_core.ml | 15 +--
compiler/syntax/src/res_printer.ml | 6 +-
.../reason/expected/jsxProps.res.txt | 8 --
.../data/conversion/reason/jsxProps.res | 8 --
.../grammar/expressions/expected/jsx.res.txt | 63 ------------
.../data/parsing/grammar/expressions/jsx.res | 99 -------------------
.../data/printer/expr/expected/jsx.res.txt | 66 -------------
tests/syntax_tests/data/printer/expr/jsx.res | 84 ----------------
tests/tests/src/jsx_preserve_test.mjs | 25 +----
tests/tests/src/jsx_preserve_test.res | 5 -
26 files changed, 13 insertions(+), 393 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 22769c4fd6..ae21718c62 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
#### :boom: Breaking Change
- Fix return type of `String.charCodeAt`. https://github.com/rescript-lang/rescript/pull/7864
+- Remove support of JSX children spread. https://github.com/rescript-lang/rescript/pull/7869
#### :eyeglasses: Spec Compliance
diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml
index 510db09f65..b24f5b4194 100644
--- a/analysis/src/CompletionFrontEnd.ml
+++ b/analysis/src/CompletionFrontEnd.ml
@@ -1412,8 +1412,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
(Jsx_container_element
{
jsx_container_element_closing_tag = None;
- jsx_container_element_children =
- JSXChildrenSpreading _ | JSXChildrenItems (_ :: _);
+ jsx_container_element_children = JSXChildrenItems (_ :: _);
}) ) ->
None
| Some jsxProps, _ ->
diff --git a/analysis/src/CompletionJsx.ml b/analysis/src/CompletionJsx.ml
index 0dcadf7e5b..0104378441 100644
--- a/analysis/src/CompletionJsx.ml
+++ b/analysis/src/CompletionJsx.ml
@@ -461,7 +461,7 @@ let extractJsxProps ~(compName : Longident.t Location.loc) ~props ~children =
let childrenStart =
match children with
| JSXChildrenItems [] -> None
- | JSXChildrenSpreading child | JSXChildrenItems (child :: _) ->
+ | JSXChildrenItems (child :: _) ->
if child.pexp_loc.loc_ghost then None else Some (Loc.start child.pexp_loc)
in
let props =
diff --git a/analysis/src/SemanticTokens.ml b/analysis/src/SemanticTokens.ml
index 7868f01cd1..576d95626e 100644
--- a/analysis/src/SemanticTokens.ml
+++ b/analysis/src/SemanticTokens.ml
@@ -312,7 +312,6 @@ let command ~debug ~emitter ~path =
(* children *)
(match children with
- | Parsetree.JSXChildrenSpreading child -> iterator.expr iterator child
| Parsetree.JSXChildrenItems children ->
List.iter (iterator.expr iterator) children);
diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml
index 216761f515..f6d1559592 100644
--- a/compiler/frontend/bs_ast_mapper.ml
+++ b/compiler/frontend/bs_ast_mapper.ml
@@ -293,7 +293,6 @@ end
module E = struct
let map_jsx_children sub = function
- | JSXChildrenSpreading e -> JSXChildrenSpreading (sub.expr sub e)
| JSXChildrenItems xs -> JSXChildrenItems (List.map (sub.expr sub) xs)
let map_jsx_prop sub = function
diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml
index 4380ca1af2..b662e4b6a5 100644
--- a/compiler/ml/ast_iterator.ml
+++ b/compiler/ml/ast_iterator.ml
@@ -268,7 +268,6 @@ end
module E = struct
let iter_jsx_children sub = function
- | JSXChildrenSpreading e -> sub.expr sub e
| JSXChildrenItems xs -> List.iter (sub.expr sub) xs
let iter_jsx_prop sub = function
diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml
index e2f4d6cad0..e457766bfa 100644
--- a/compiler/ml/ast_mapper.ml
+++ b/compiler/ml/ast_mapper.ml
@@ -264,7 +264,6 @@ end
module E = struct
let map_jsx_children sub = function
- | JSXChildrenSpreading e -> JSXChildrenSpreading (sub.expr sub e)
| JSXChildrenItems xs -> JSXChildrenItems (List.map (sub.expr sub) xs)
let map_jsx_prop sub = function
diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml
index eb01c014f7..6bb20ed380 100644
--- a/compiler/ml/ast_mapper_from0.ml
+++ b/compiler/ml/ast_mapper_from0.ml
@@ -327,7 +327,7 @@ module E = struct
| Pexp_construct ({txt = Longident.Lident "[]" | Longident.Lident "::"}, _)
->
JSXChildrenItems (visit e)
- | _ -> JSXChildrenSpreading (sub.expr sub e)
+ | _ -> JSXChildrenItems [sub.expr sub e]
let try_map_jsx_prop (sub : mapper) (lbl : Asttypes.Noloc.arg_label)
(e : expression) : Parsetree.jsx_prop option =
diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml
index 978ba001c3..cd51b05a39 100644
--- a/compiler/ml/ast_mapper_to0.ml
+++ b/compiler/ml/ast_mapper_to0.ml
@@ -340,7 +340,6 @@ module E = struct
let map_jsx_children sub loc children =
match children with
- | JSXChildrenSpreading e -> sub.expr sub e
| JSXChildrenItems xs ->
let list_expr = Ast_helper.Exp.make_list_expression loc xs None in
sub.expr sub list_expr
diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml
index bb467ef592..7b6aec4b1b 100644
--- a/compiler/ml/depend.ml
+++ b/compiler/ml/depend.ml
@@ -313,7 +313,6 @@ let rec add_expr bv exp =
add_jsx_children bv children
and add_jsx_children bv = function
- | JSXChildrenSpreading e -> add_expr bv e
| JSXChildrenItems xs -> List.iter (add_expr bv) xs
and add_jsx_prop bv = function
diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml
index cc99af9b92..e2d36b754e 100644
--- a/compiler/ml/parsetree.ml
+++ b/compiler/ml/parsetree.ml
@@ -372,9 +372,7 @@ and jsx_prop =
Location.t
* expression
-and jsx_children =
- | JSXChildrenSpreading of expression
- | JSXChildrenItems of expression list
+and jsx_children = JSXChildrenItems of expression list
and jsx_props = jsx_prop list
diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml
index 2a91e8fd1f..ed1e7f4f29 100644
--- a/compiler/ml/pprintast.ml
+++ b/compiler/ml/pprintast.ml
@@ -840,7 +840,6 @@ and simple_expr ctxt f x =
| _ -> paren true (expression ctxt) f x
and collect_jsx_children = function
- | JSXChildrenSpreading e -> [e]
| JSXChildrenItems xs -> xs
and print_jsx_prop ctxt f = function
diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml
index 756511f0bb..e74ebe3700 100644
--- a/compiler/ml/printast.ml
+++ b/compiler/ml/printast.ml
@@ -384,7 +384,6 @@ and expression i ppf x =
and jsx_children i ppf children =
line i ppf "jsx_children =\n";
match children with
- | JSXChildrenSpreading e -> expression (i + 1) ppf e
| JSXChildrenItems xs -> list (i + 1) expression ppf xs
and jsx_prop i ppf = function
diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml
index 68f791ca7a..abf685b2ec 100644
--- a/compiler/syntax/src/jsx_v4.ml
+++ b/compiler/syntax/src/jsx_v4.ml
@@ -1183,7 +1183,7 @@ let append_children_prop (config : Jsx_common.jsx_config) mapper
(children : jsx_children) : jsx_props =
match children with
| JSXChildrenItems [] -> props
- | JSXChildrenItems [child] | JSXChildrenSpreading child ->
+ | JSXChildrenItems [child] ->
let expr =
(* I don't quite know why fragment and uppercase don't do this additional ReactDOM.someElement wrapping *)
match component_description with
@@ -1232,7 +1232,6 @@ let mk_react_jsx (config : Jsx_common.jsx_config) mapper loc attrs
(props : jsx_props) (children : jsx_children) : expression =
let more_than_one_children =
match children with
- | JSXChildrenSpreading _ -> false
| JSXChildrenItems xs -> List.length xs > 1
in
let props_with_children =
diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml
index a4f3a405f2..75dae6d0cb 100644
--- a/compiler/syntax/src/res_ast_debugger.ml
+++ b/compiler/syntax/src/res_ast_debugger.ml
@@ -710,7 +710,6 @@ module SexpAst = struct
| Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) ->
let xs =
match children with
- | JSXChildrenSpreading e -> [e]
| JSXChildrenItems xs -> xs
in
Sexp.list
@@ -732,7 +731,6 @@ module SexpAst = struct
}) ->
let xs =
match children with
- | JSXChildrenSpreading e -> [e]
| JSXChildrenItems xs -> xs
in
Sexp.list
diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml
index 2c4608476f..67430c07a0 100644
--- a/compiler/syntax/src/res_comments_table.ml
+++ b/compiler/syntax/src/res_comments_table.ml
@@ -1660,7 +1660,6 @@ and walk_expression expr t comments =
attach t.trailing opening_token on_same_line;
let exprs =
match children with
- | Parsetree.JSXChildrenSpreading e -> [e]
| Parsetree.JSXChildrenItems xs -> xs
in
let xs = exprs |> List.map (fun e -> Expression e) in
@@ -1803,7 +1802,6 @@ and walk_expression expr t comments =
| children ->
let children_nodes =
match children with
- | Parsetree.JSXChildrenSpreading e -> [Expression e]
| Parsetree.JSXChildrenItems xs -> List.map (fun e -> Expression e) xs
in
diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml
index 805874b161..4b30c39fc5 100644
--- a/compiler/syntax/src/res_core.ml
+++ b/compiler/syntax/src/res_core.ml
@@ -2999,19 +2999,8 @@ and parse_jsx_children p : Parsetree.jsx_children =
loop p (child :: children)
| _ -> children
in
- let children =
- match p.Parser.token with
- | DotDotDot ->
- Parser.next p;
- let expr =
- parse_primary_expr ~operand:(parse_atomic_expr p) ~no_call:true p
- in
- Parsetree.JSXChildrenSpreading expr
- | _ ->
- let children = List.rev (loop p []) in
- Parsetree.JSXChildrenItems children
- in
- children
+ let children = List.rev (loop p []) in
+ Parsetree.JSXChildrenItems children
and parse_braced_or_record_expr p =
let start_pos = p.Parser.start_pos in
diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml
index 1bf0a714ed..ed71ae8297 100644
--- a/compiler/syntax/src/res_printer.ml
+++ b/compiler/syntax/src/res_printer.ml
@@ -4516,7 +4516,7 @@ and print_jsx_container_tag ~state tag_name
(*
*)
let has_children =
match children with
- | JSXChildrenSpreading _ | JSXChildrenItems (_ :: _) -> true
+ | JSXChildrenItems (_ :: _) -> true
| JSXChildrenItems [] -> false
in
let line_sep = get_line_sep_for_jsx_children children in
@@ -4607,7 +4607,7 @@ and print_jsx_fragment ~state (opening_greater_than : Lexing.position)
let has_children =
match children with
| JSXChildrenItems [] -> false
- | JSXChildrenSpreading _ | JSXChildrenItems (_ :: _) -> true
+ | JSXChildrenItems (_ :: _) -> true
in
let line_sep = get_line_sep_for_jsx_children children in
Doc.group
@@ -4622,7 +4622,6 @@ and print_jsx_fragment ~state (opening_greater_than : Lexing.position)
and get_line_sep_for_jsx_children (children : Parsetree.jsx_children) =
match children with
- | JSXChildrenSpreading _ -> Doc.line
| JSXChildrenItems children ->
if
List.length children > 1
@@ -4670,7 +4669,6 @@ and print_jsx_children ~state (children : Parsetree.jsx_children) cmt_tbl =
in
match children with
| JSXChildrenItems [] -> Doc.nil
- | JSXChildrenSpreading child -> Doc.concat [Doc.dotdotdot; print_expr child]
| JSXChildrenItems children ->
let rec visit acc children =
match children with
diff --git a/tests/syntax_tests/data/conversion/reason/expected/jsxProps.res.txt b/tests/syntax_tests/data/conversion/reason/expected/jsxProps.res.txt
index d368bc9529..3d98360833 100644
--- a/tests/syntax_tests/data/conversion/reason/expected/jsxProps.res.txt
+++ b/tests/syntax_tests/data/conversion/reason/expected/jsxProps.res.txt
@@ -7,11 +7,3 @@ let handleClick = (href, event) =>
@react.component
let make = (~href, ~className="", ~children) =>
handleClick(href, event)}> children
-
- ...{x => }
-
-
...element
-
...{a => 1}
-
...
-
...[a, b]
-
...{(1, 2)}
diff --git a/tests/syntax_tests/data/conversion/reason/jsxProps.res b/tests/syntax_tests/data/conversion/reason/jsxProps.res
index d368bc9529..3d98360833 100644
--- a/tests/syntax_tests/data/conversion/reason/jsxProps.res
+++ b/tests/syntax_tests/data/conversion/reason/jsxProps.res
@@ -7,11 +7,3 @@ let handleClick = (href, event) =>
@react.component
let make = (~href, ~className="", ~children) =>
handleClick(href, event)}> children
-
- ...{x => }
-
-
...element
-
...{a => 1}
-
...
-
...[a, b]
-
...{(1, 2)}
diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt
index 3a12a3942b..cbff81b679 100644
--- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt
+++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/jsx.res.txt
@@ -40,12 +40,6 @@ let _ =
-
let _unary_element_with_spread_props_keyed =
let _container_with_spread_props_keyed =
From aaf204b324b6f721539b704b5182397882f6c4c3 Mon Sep 17 00:00:00 2001
From: tsnobip
Date: Wed, 10 Sep 2025 16:03:36 +0200
Subject: [PATCH 3/4] remove JSXChildrenItems variant
---
analysis/src/CompletionFrontEnd.ml | 4 +--
analysis/src/CompletionJsx.ml | 4 +--
analysis/src/SemanticTokens.ml | 4 +--
compiler/frontend/bs_ast_mapper.ml | 3 +--
compiler/ml/ast_iterator.ml | 3 +--
compiler/ml/ast_mapper.ml | 3 +--
compiler/ml/ast_mapper_from0.ml | 4 +--
compiler/ml/ast_mapper_to0.ml | 2 +-
compiler/ml/depend.ml | 3 +--
compiler/ml/parsetree.ml | 2 +-
compiler/ml/pprintast.ml | 13 +++------
compiler/ml/printast.ml | 3 +--
compiler/syntax/src/jsx_v4.ml | 15 +++++------
compiler/syntax/src/res_ast_debugger.ml | 12 ++-------
compiler/syntax/src/res_comments_table.ml | 13 +++------
compiler/syntax/src/res_core.ml | 3 +--
compiler/syntax/src/res_printer.ml | 32 +++++++++++------------
17 files changed, 44 insertions(+), 79 deletions(-)
diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml
index b24f5b4194..65a26a1467 100644
--- a/analysis/src/CompletionFrontEnd.ml
+++ b/analysis/src/CompletionFrontEnd.ml
@@ -1353,7 +1353,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
(Jsx_container_element
{jsx_container_element_children = children}) ->
children
- | _ -> JSXChildrenItems []
+ | _ -> []
in
let compName_loc = compName.loc in
let compName_lid =
@@ -1412,7 +1412,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor
(Jsx_container_element
{
jsx_container_element_closing_tag = None;
- jsx_container_element_children = JSXChildrenItems (_ :: _);
+ jsx_container_element_children = _ :: _;
}) ) ->
None
| Some jsxProps, _ ->
diff --git a/analysis/src/CompletionJsx.ml b/analysis/src/CompletionJsx.ml
index 0104378441..4ddba8c642 100644
--- a/analysis/src/CompletionJsx.ml
+++ b/analysis/src/CompletionJsx.ml
@@ -460,8 +460,8 @@ let extractJsxProps ~(compName : Longident.t Location.loc) ~props ~children =
let open Parsetree in
let childrenStart =
match children with
- | JSXChildrenItems [] -> None
- | JSXChildrenItems (child :: _) ->
+ | [] -> None
+ | child :: _ ->
if child.pexp_loc.loc_ghost then None else Some (Loc.start child.pexp_loc)
in
let props =
diff --git a/analysis/src/SemanticTokens.ml b/analysis/src/SemanticTokens.ml
index 576d95626e..ddccba9b2b 100644
--- a/analysis/src/SemanticTokens.ml
+++ b/analysis/src/SemanticTokens.ml
@@ -311,9 +311,7 @@ let command ~debug ~emitter ~path =
~pos:(Pos.ofLexing posOfGreatherthanAfterProps);
(* children *)
- (match children with
- | Parsetree.JSXChildrenItems children ->
- List.iter (iterator.expr iterator) children);
+ List.iter (iterator.expr iterator) children;
(* closing tag *)
closing_tag_opt
diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml
index f6d1559592..292e199b5a 100644
--- a/compiler/frontend/bs_ast_mapper.ml
+++ b/compiler/frontend/bs_ast_mapper.ml
@@ -292,8 +292,7 @@ module M = struct
end
module E = struct
- let map_jsx_children sub = function
- | JSXChildrenItems xs -> JSXChildrenItems (List.map (sub.expr sub) xs)
+ let map_jsx_children sub xs = List.map (sub.expr sub) xs
let map_jsx_prop sub = function
| JSXPropPunning (optional, name) ->
diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml
index b662e4b6a5..a430bb0b7b 100644
--- a/compiler/ml/ast_iterator.ml
+++ b/compiler/ml/ast_iterator.ml
@@ -267,8 +267,7 @@ module M = struct
end
module E = struct
- let iter_jsx_children sub = function
- | JSXChildrenItems xs -> List.iter (sub.expr sub) xs
+ let iter_jsx_children sub xs = List.iter (sub.expr sub) xs
let iter_jsx_prop sub = function
| JSXPropPunning (_, name) -> iter_loc sub name
diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml
index e457766bfa..673465477b 100644
--- a/compiler/ml/ast_mapper.ml
+++ b/compiler/ml/ast_mapper.ml
@@ -263,8 +263,7 @@ module M = struct
end
module E = struct
- let map_jsx_children sub = function
- | JSXChildrenItems xs -> JSXChildrenItems (List.map (sub.expr sub) xs)
+ let map_jsx_children sub xs = List.map (sub.expr sub) xs
let map_jsx_prop sub = function
| JSXPropPunning (optional, name) ->
diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml
index 6bb20ed380..3f91d6ac1e 100644
--- a/compiler/ml/ast_mapper_from0.ml
+++ b/compiler/ml/ast_mapper_from0.ml
@@ -326,8 +326,8 @@ module E = struct
match e.pexp_desc with
| Pexp_construct ({txt = Longident.Lident "[]" | Longident.Lident "::"}, _)
->
- JSXChildrenItems (visit e)
- | _ -> JSXChildrenItems [sub.expr sub e]
+ visit e
+ | _ -> [sub.expr sub e]
let try_map_jsx_prop (sub : mapper) (lbl : Asttypes.Noloc.arg_label)
(e : expression) : Parsetree.jsx_prop option =
diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml
index cd51b05a39..d0ac43d737 100644
--- a/compiler/ml/ast_mapper_to0.ml
+++ b/compiler/ml/ast_mapper_to0.ml
@@ -340,7 +340,7 @@ module E = struct
let map_jsx_children sub loc children =
match children with
- | JSXChildrenItems xs ->
+ | xs ->
let list_expr = Ast_helper.Exp.make_list_expression loc xs None in
sub.expr sub list_expr
diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml
index 7b6aec4b1b..e5e39eb4b5 100644
--- a/compiler/ml/depend.ml
+++ b/compiler/ml/depend.ml
@@ -312,8 +312,7 @@ let rec add_expr bv exp =
and_jsx_props bv props;
add_jsx_children bv children
-and add_jsx_children bv = function
- | JSXChildrenItems xs -> List.iter (add_expr bv) xs
+and add_jsx_children bv xs = List.iter (add_expr bv) xs
and add_jsx_prop bv = function
| JSXPropPunning (_, _) -> ()
diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml
index e2d36b754e..78c9899f74 100644
--- a/compiler/ml/parsetree.ml
+++ b/compiler/ml/parsetree.ml
@@ -372,7 +372,7 @@ and jsx_prop =
Location.t
* expression
-and jsx_children = JSXChildrenItems of expression list
+and jsx_children = expression list
and jsx_props = jsx_prop list
diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml
index ed1e7f4f29..585ac64b81 100644
--- a/compiler/ml/pprintast.ml
+++ b/compiler/ml/pprintast.ml
@@ -798,7 +798,7 @@ and simple_expr ctxt f x =
pp f fmt (pattern ctxt) s expression e1 direction_flag df expression e2
expression e3
| Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) ->
- pp f "<>%a>" (list (simple_expr ctxt)) (collect_jsx_children children)
+ pp f "<>%a>" (list (simple_expr ctxt)) children
| Pexp_jsx_element
(Jsx_unary_element
{
@@ -828,20 +828,13 @@ and simple_expr ctxt f x =
in
match props with
| [] ->
- pp f "<%s>%a%s" name
- (list (simple_expr ctxt))
- (collect_jsx_children children)
- closing_name
+ pp f "<%s>%a%s" name (list (simple_expr ctxt)) children closing_name
| _ ->
pp f "<%s %a>%a%s" name (print_jsx_props ctxt) props
(list (simple_expr ctxt))
- (collect_jsx_children children)
- closing_name)
+ children closing_name)
| _ -> paren true (expression ctxt) f x
-and collect_jsx_children = function
- | JSXChildrenItems xs -> xs
-
and print_jsx_prop ctxt f = function
| JSXPropPunning (is_optional, name) ->
pp f "%s" (if is_optional then "?" ^ name.txt else name.txt)
diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml
index e74ebe3700..44d699eb38 100644
--- a/compiler/ml/printast.ml
+++ b/compiler/ml/printast.ml
@@ -383,8 +383,7 @@ and expression i ppf x =
and jsx_children i ppf children =
line i ppf "jsx_children =\n";
- match children with
- | JSXChildrenItems xs -> list (i + 1) expression ppf xs
+ list (i + 1) expression ppf children
and jsx_prop i ppf = function
| JSXPropPunning (opt, name) ->
diff --git a/compiler/syntax/src/jsx_v4.ml b/compiler/syntax/src/jsx_v4.ml
index abf685b2ec..f35e3014ab 100644
--- a/compiler/syntax/src/jsx_v4.ml
+++ b/compiler/syntax/src/jsx_v4.ml
@@ -1182,8 +1182,8 @@ let append_children_prop (config : Jsx_common.jsx_config) mapper
(component_description : componentDescription) (props : jsx_props)
(children : jsx_children) : jsx_props =
match children with
- | JSXChildrenItems [] -> props
- | JSXChildrenItems [child] ->
+ | [] -> props
+ | [child] ->
let expr =
(* I don't quite know why fragment and uppercase don't do this additional ReactDOM.someElement wrapping *)
match component_description with
@@ -1209,7 +1209,7 @@ let append_children_prop (config : Jsx_common.jsx_config) mapper
JSXPropValue
({txt = "children"; loc = child.pexp_loc}, is_optional, expr);
]
- | JSXChildrenItems (head :: _ as xs) ->
+ | head :: _ as xs ->
let loc =
match List.rev xs with
| [] -> head.pexp_loc
@@ -1230,10 +1230,7 @@ let append_children_prop (config : Jsx_common.jsx_config) mapper
let mk_react_jsx (config : Jsx_common.jsx_config) mapper loc attrs
(component_description : componentDescription) (elementTag : expression)
(props : jsx_props) (children : jsx_children) : expression =
- let more_than_one_children =
- match children with
- | JSXChildrenItems xs -> List.length xs > 1
- in
+ let more_than_one_children = List.length children > 1 in
let props_with_children =
append_children_prop config mapper component_description props children
in
@@ -1313,12 +1310,12 @@ let expr ~(config : Jsx_common.jsx_config) mapper expression =
(* For example 'input' *)
let component_name_expr = constant_string ~loc:tag_loc name in
mk_react_jsx config mapper loc attrs LowercasedComponent
- component_name_expr props (JSXChildrenItems [])
+ component_name_expr props []
| JsxUpperTag _ | JsxQualifiedLowerTag _ ->
(* MyModule.make *)
let make_id = mk_uppercase_tag_name_expr tag_name in
mk_react_jsx config mapper loc attrs UppercasedComponent make_id props
- (JSXChildrenItems [])
+ []
| JsxTagInvalid name ->
Jsx_common.raise_error ~loc
"JSX: element name is neither upper- or lowercase, got \"%s\"" name)
diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml
index 75dae6d0cb..52a57b89c5 100644
--- a/compiler/syntax/src/res_ast_debugger.ml
+++ b/compiler/syntax/src/res_ast_debugger.ml
@@ -707,11 +707,7 @@ module SexpAst = struct
| Pexp_extension ext ->
Sexp.list [Sexp.atom "Pexp_extension"; extension ext]
| Pexp_await e -> Sexp.list [Sexp.atom "Pexp_await"; expression e]
- | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) ->
- let xs =
- match children with
- | JSXChildrenItems xs -> xs
- in
+ | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = xs}) ->
Sexp.list
[
Sexp.atom "Pexp_jsx_fragment"; Sexp.list (map_empty ~f:expression xs);
@@ -727,12 +723,8 @@ module SexpAst = struct
(Jsx_container_element
{
jsx_container_element_props = props;
- jsx_container_element_children = children;
+ jsx_container_element_children = xs;
}) ->
- let xs =
- match children with
- | JSXChildrenItems xs -> xs
- in
Sexp.list
[
Sexp.atom "Pexp_jsx_container_element";
diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml
index 67430c07a0..b9a81d53a9 100644
--- a/compiler/syntax/src/res_comments_table.ml
+++ b/compiler/syntax/src/res_comments_table.ml
@@ -1658,11 +1658,7 @@ and walk_expression expr t comments =
let opening_token = {expr.pexp_loc with loc_end = opening_greater_than} in
let on_same_line, rest = partition_by_on_same_line opening_token comments in
attach t.trailing opening_token on_same_line;
- let exprs =
- match children with
- | Parsetree.JSXChildrenItems xs -> xs
- in
- let xs = exprs |> List.map (fun e -> Expression e) in
+ let xs = children |> List.map (fun e -> Expression e) in
walk_list xs t rest
| Pexp_jsx_element
(Jsx_unary_element
@@ -1768,7 +1764,7 @@ and walk_expression expr t comments =
partition_leading_trailing rest closing_tag_loc
in
match children with
- | Parsetree.JSXChildrenItems [] -> (
+ | [] -> (
(* attach all comments to the closing tag if there are no children *)
match closing_tag with
| None ->
@@ -1800,10 +1796,7 @@ and walk_expression expr t comments =
(* if the closing tag is on the same line, attach comments to the opening tag *)
attach t.leading closing_tag_loc comments_for_children)
| children ->
- let children_nodes =
- match children with
- | Parsetree.JSXChildrenItems xs -> List.map (fun e -> Expression e) xs
- in
+ let children_nodes = List.map (fun e -> Expression e) children in
walk_list children_nodes t comments_for_children
(* It is less likely that there are comments inside the closing tag,
diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml
index 4b30c39fc5..384b368139 100644
--- a/compiler/syntax/src/res_core.ml
+++ b/compiler/syntax/src/res_core.ml
@@ -2999,8 +2999,7 @@ and parse_jsx_children p : Parsetree.jsx_children =
loop p (child :: children)
| _ -> children
in
- let children = List.rev (loop p []) in
- Parsetree.JSXChildrenItems children
+ List.rev (loop p [])
and parse_braced_or_record_expr p =
let start_pos = p.Parser.start_pos in
diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml
index ed71ae8297..2f73bd2684 100644
--- a/compiler/syntax/src/res_printer.ml
+++ b/compiler/syntax/src/res_printer.ml
@@ -4516,8 +4516,8 @@ and print_jsx_container_tag ~state tag_name
(* *)
let has_children =
match children with
- | JSXChildrenItems (_ :: _) -> true
- | JSXChildrenItems [] -> false
+ | _ :: _ -> true
+ | [] -> false
in
let line_sep = get_line_sep_for_jsx_children children in
let print_children children =
@@ -4606,8 +4606,8 @@ and print_jsx_fragment ~state (opening_greater_than : Lexing.position)
in
let has_children =
match children with
- | JSXChildrenItems [] -> false
- | JSXChildrenItems (_ :: _) -> true
+ | [] -> false
+ | _ :: _ -> true
in
let line_sep = get_line_sep_for_jsx_children children in
Doc.group
@@ -4621,17 +4621,15 @@ and print_jsx_fragment ~state (opening_greater_than : Lexing.position)
])
and get_line_sep_for_jsx_children (children : Parsetree.jsx_children) =
- match children with
- | JSXChildrenItems children ->
- if
- List.length children > 1
- || List.exists
- (function
- | {Parsetree.pexp_desc = Pexp_jsx_element _} -> true
- | _ -> false)
- children
- then Doc.hard_line
- else Doc.line
+ if
+ List.length children > 1
+ || List.exists
+ (function
+ | {Parsetree.pexp_desc = Pexp_jsx_element _} -> true
+ | _ -> false)
+ children
+ then Doc.hard_line
+ else Doc.line
and print_jsx_children ~state (children : Parsetree.jsx_children) cmt_tbl =
let open Parsetree in
@@ -4668,8 +4666,8 @@ and print_jsx_children ~state (children : Parsetree.jsx_children) cmt_tbl =
print_comments (add_parens_or_braces expr_doc) cmt_tbl braces_loc
in
match children with
- | JSXChildrenItems [] -> Doc.nil
- | JSXChildrenItems children ->
+ | [] -> Doc.nil
+ | children ->
let rec visit acc children =
match children with
| [] -> acc
From 605b78d8196a327f6f23d6b27b8e6410ccdd2da4 Mon Sep 17 00:00:00 2001
From: tsnobip
Date: Wed, 10 Sep 2025 16:30:46 +0200
Subject: [PATCH 4/4] add error message when parsing children spread
---
compiler/syntax/src/res_core.ml | 11 ++++++++++-
.../errors/expressions/expected/jsx.res.txt | 16 +++++++++++++++-
.../data/parsing/errors/expressions/jsx.res | 5 ++++-
3 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml
index 384b368139..c8ae7659c8 100644
--- a/compiler/syntax/src/res_core.ml
+++ b/compiler/syntax/src/res_core.ml
@@ -188,6 +188,9 @@ module ErrorMessages = struct
let type_definition_in_function =
"Type definitions are not allowed inside functions.\n"
^ " Move this `type` declaration to the top level or into a module."
+
+ let spread_children_no_longer_supported =
+ "Spreading JSX children is no longer supported."
end
module InExternal = struct
@@ -2999,7 +3002,13 @@ and parse_jsx_children p : Parsetree.jsx_children =
loop p (child :: children)
| _ -> children
in
- List.rev (loop p [])
+ match p.Parser.token with
+ | DotDotDot ->
+ Parser.err ~start_pos:p.start_pos ~end_pos:p.end_pos p
+ (Diagnostics.message ErrorMessages.spread_children_no_longer_supported);
+ Parser.next p;
+ [parse_primary_expr ~operand:(parse_atomic_expr p) ~no_call:true p]
+ | _ -> List.rev (loop p [])
and parse_braced_or_record_expr p =
let start_pos = p.Parser.start_pos in
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
index 67191bd9ca..abacb8dbbf 100644
--- a/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
+++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
@@ -101,6 +101,7 @@
13 │ // Trailing dots in tag names
14 │ let x =
15 │ let x =
+ 16 │
expected identifier after '.' in JSX tag name
@@ -111,9 +112,21 @@
13 │ // Trailing dots in tag names
14 │ let x =
15 │ let x =
+ 16 │
+ 17 │ // spread children
expected identifier after '.' in JSX tag name
+
+ Syntax error!
+ syntax_tests/data/parsing/errors/expressions/jsx.res:18:15-17
+
+ 16 │
+ 17 │ // spread children
+ 18 │ let x =
...c
+
+ Spreading JSX children is no longer supported.
+
let x =
let x =
let x =
@@ -124,4 +137,5 @@ let x =
let x =
let x =
let x =
-let x =
\ No newline at end of file
+let x =
+let x =
c
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/jsx.res b/tests/syntax_tests/data/parsing/errors/expressions/jsx.res
index 1be9e395d3..02c0a75f0e 100644
--- a/tests/syntax_tests/data/parsing/errors/expressions/jsx.res
+++ b/tests/syntax_tests/data/parsing/errors/expressions/jsx.res
@@ -12,4 +12,7 @@ let x =
// Trailing dots in tag names
let x =
-let x =
\ No newline at end of file
+let x =
+
+// spread children
+let x =