diff --git a/res_syntax/src/reactjs_jsx_v4.ml b/res_syntax/src/reactjs_jsx_v4.ml index 183e61df1a..a277f52075 100644 --- a/res_syntax/src/reactjs_jsx_v4.ml +++ b/res_syntax/src/reactjs_jsx_v4.ml @@ -242,9 +242,9 @@ let recordFromProps ~loc ~removeKey callArguments = (* let make = ({id, name, children}: props<'id, 'name, 'children>) *) let makePropsTypeParamsTvar namedTypeList = namedTypeList - |> List.filter_map (fun (_isOptional, label, _, _interiorType) -> + |> List.filter_map (fun (_isOptional, label, _, loc, _interiorType) -> if label = "key" then None - else Some (Typ.var @@ safeTypeFromValue (Labelled label))) + else Some (Typ.var ~loc @@ safeTypeFromValue (Labelled label))) let stripOption coreType = match coreType with @@ -268,7 +268,7 @@ let stripJsNullable coreType = let makePropsTypeParams ?(stripExplicitOption = false) ?(stripExplicitJsNullableOfRef = false) namedTypeList = namedTypeList - |> List.filter_map (fun (isOptional, label, _, interiorType) -> + |> List.filter_map (fun (isOptional, label, _, loc, interiorType) -> if label = "key" then None (* TODO: Worth thinking how about "ref_" or "_ref" usages *) else if label = "ref" then @@ -277,7 +277,7 @@ let makePropsTypeParams ?(stripExplicitOption = false) For example, if JSX ppx is used for React Native, type would be different. *) match interiorType with - | {ptyp_desc = Ptyp_var "ref"} -> Some (refType Location.none) + | {ptyp_desc = Ptyp_var "ref"} -> Some (refType loc) | _ -> (* Strip explicit Js.Nullable.t in case of forwardRef *) if stripExplicitJsNullableOfRef then stripJsNullable interiorType @@ -287,9 +287,25 @@ let makePropsTypeParams ?(stripExplicitOption = false) else if isOptional && stripExplicitOption then stripOption interiorType else Some interiorType) -let makeLabelDecls ~loc namedTypeList = +let makeLabelDecls namedTypeList = + let rec checkDuplicatedLabel l = + let rec mem_label ((_, (la : string), _, _, _) as x) = function + | [] -> false + | (_, (lb : string), _, _, _) :: l -> lb = la || mem_label x l + in + match l with + | [] -> () + | hd :: tl -> + if mem_label hd tl then + let _, label, _, loc, _ = hd in + React_jsx_common.raiseError ~loc "JSX: found the duplicated prop `%s`" + label + else checkDuplicatedLabel tl + in + let () = namedTypeList |> List.rev |> checkDuplicatedLabel in + namedTypeList - |> List.map (fun (isOptional, label, attrs, interiorType) -> + |> List.map (fun (isOptional, label, attrs, loc, interiorType) -> if label = "key" then Type.field ~loc ~attrs:(optionalAttrs @ attrs) {txt = label; loc} interiorType @@ -301,7 +317,7 @@ let makeLabelDecls ~loc namedTypeList = (Typ.var @@ safeTypeFromValue @@ Labelled label)) let makeTypeDecls propsName loc namedTypeList = - let labelDeclList = makeLabelDecls ~loc namedTypeList in + let labelDeclList = makeLabelDecls namedTypeList in (* 'id, 'className, ... *) let params = makePropsTypeParamsTvar namedTypeList @@ -702,17 +718,22 @@ let argToType ~newtypes ~(typeConstraints : core_type option) types in match (type_, name, default) with | Some type_, name, _ when isOptional name -> - (true, getLabel name, attrs, {type_ with ptyp_attributes = optionalAttrs}) + ( true, + getLabel name, + attrs, + loc, + {type_ with ptyp_attributes = optionalAttrs} ) :: types - | Some type_, name, _ -> (false, getLabel name, attrs, type_) :: types + | Some type_, name, _ -> (false, getLabel name, attrs, loc, type_) :: types | None, name, _ when isOptional name -> ( true, getLabel name, attrs, + loc, Typ.var ~loc ~attrs:optionalAttrs (safeTypeFromValue name) ) :: types | None, name, _ when isLabelled name -> - (false, getLabel name, attrs, Typ.var ~loc (safeTypeFromValue name)) + (false, getLabel name, attrs, loc, Typ.var ~loc (safeTypeFromValue name)) :: types | _ -> types @@ -721,10 +742,12 @@ let argWithDefaultValue (name, default, _, _, _, _) = | Some default when isOptional name -> Some (getLabel name, default) | _ -> None -let argToConcreteType types (name, attrs, _loc, type_) = +let argToConcreteType types (name, attrs, loc, type_) = match name with - | name when isLabelled name -> (false, getLabel name, attrs, type_) :: types - | name when isOptional name -> (true, getLabel name, attrs, type_) :: types + | name when isLabelled name -> + (false, getLabel name, attrs, loc, type_) :: types + | name when isOptional name -> + (true, getLabel name, attrs, loc, type_) :: types | _ -> types let check_string_int_attribute_iter = @@ -1306,7 +1329,8 @@ let transformSignatureItem ~config _mapper item = makePropsRecordTypeSig ~coreTypeOfAttr ~typVarsOfCoreType "props" psig_loc ((* If there is Nolabel arg, regard the type as ref in forwardRef *) - (if !hasForwardRef then [(true, "ref", [], refType Location.none)] + (if !hasForwardRef then + [(true, "ref", [], Location.none, refType Location.none)] else []) @ namedTypeList) in diff --git a/res_syntax/tests/ppx/react/expected/aliasProps.res.txt b/res_syntax/tests/ppx/react/expected/aliasProps.res.txt index d99b8e2209..721846d189 100644 --- a/res_syntax/tests/ppx/react/expected/aliasProps.res.txt +++ b/res_syntax/tests/ppx/react/expected/aliasProps.res.txt @@ -1,10 +1,7 @@ @@jsxConfig({version: 4, mode: "automatic"}) module C0 = { - type props<'priority, 'text> = { - priority: 'priority, - text?: 'text, - } + type props<'priority, 'text> = {priority: 'priority, text?: 'text} @react.component let make = ({priority: _, ?text, _}: props<'priority, 'text>) => { @@ -23,10 +20,7 @@ module C0 = { } module C1 = { - type props<'priority, 'text> = { - priority: 'priority, - text?: 'text, - } + type props<'priority, 'text> = {priority: 'priority, text?: 'text} @react.component let make = ({priority: p, ?text, _}: props<'priority, 'text>) => { diff --git a/res_syntax/tests/ppx/react/expected/commentAtTop.res.txt b/res_syntax/tests/ppx/react/expected/commentAtTop.res.txt index b2c0af59d3..4c7eba771a 100644 --- a/res_syntax/tests/ppx/react/expected/commentAtTop.res.txt +++ b/res_syntax/tests/ppx/react/expected/commentAtTop.res.txt @@ -1,6 +1,4 @@ -type props<'msg> = { // test React JSX file - msg: 'msg, -} +type props<'msg> = {msg: 'msg} // test React JSX file @react.component let make = ({msg, _}: props<'msg>) => { diff --git a/res_syntax/tests/ppx/react/expected/externalWithCustomName.res.txt b/res_syntax/tests/ppx/react/expected/externalWithCustomName.res.txt index a4fd91d61b..335c670fe5 100644 --- a/res_syntax/tests/ppx/react/expected/externalWithCustomName.res.txt +++ b/res_syntax/tests/ppx/react/expected/externalWithCustomName.res.txt @@ -13,10 +13,7 @@ let t = React.createElement(Foo.component, Foo.componentProps(~a=1, ~b={"1"}, () @@jsxConfig({version: 4, mode: "classic"}) module Foo = { - type props<'a, 'b> = { - a: 'a, - b: 'b, - } + type props<'a, 'b> = {a: 'a, b: 'b} @module("Foo") external component: React.componentLike, React.element> = "component" @@ -27,10 +24,7 @@ let t = React.createElement(Foo.component, {a: 1, b: "1"}) @@jsxConfig({version: 4, mode: "automatic"}) module Foo = { - type props<'a, 'b> = { - a: 'a, - b: 'b, - } + type props<'a, 'b> = {a: 'a, b: 'b} @module("Foo") external component: React.componentLike, React.element> = "component" diff --git a/res_syntax/tests/ppx/react/expected/fileLevelConfig.res.txt b/res_syntax/tests/ppx/react/expected/fileLevelConfig.res.txt index ab0fe23f95..c1f3c9d17a 100644 --- a/res_syntax/tests/ppx/react/expected/fileLevelConfig.res.txt +++ b/res_syntax/tests/ppx/react/expected/fileLevelConfig.res.txt @@ -18,9 +18,7 @@ module V3 = { @@jsxConfig({version: 4, mode: "classic"}) module V4C = { - type props<'msg> = { - msg: 'msg, - } + type props<'msg> = {msg: 'msg} @react.component let make = ({msg, _}: props<'msg>) => { @@ -36,9 +34,7 @@ module V4C = { @@jsxConfig({version: 4, mode: "automatic"}) module V4A = { - type props<'msg> = { - msg: 'msg, - } + type props<'msg> = {msg: 'msg} @react.component let make = ({msg, _}: props<'msg>) => { diff --git a/res_syntax/tests/ppx/react/expected/interface.res.txt b/res_syntax/tests/ppx/react/expected/interface.res.txt index 6488424d96..9b6062dd3d 100644 --- a/res_syntax/tests/ppx/react/expected/interface.res.txt +++ b/res_syntax/tests/ppx/react/expected/interface.res.txt @@ -1,7 +1,5 @@ module A = { - type props<'x> = { - x: 'x, - } + type props<'x> = {x: 'x} @react.component let make = ({x, _}: props<'x>) => React.string(x) let make = { let \"Interface$A" = (props: props<_>) => make(props) diff --git a/res_syntax/tests/ppx/react/expected/interface.resi.txt b/res_syntax/tests/ppx/react/expected/interface.resi.txt index 0a6e4c2dcd..f4ffd9b65c 100644 --- a/res_syntax/tests/ppx/react/expected/interface.resi.txt +++ b/res_syntax/tests/ppx/react/expected/interface.resi.txt @@ -1,7 +1,5 @@ module A: { - type props<'x> = { - x: 'x, - } + type props<'x> = {x: 'x} let make: React.componentLike, React.element> } diff --git a/res_syntax/tests/ppx/react/expected/interfaceWithRef.res.txt b/res_syntax/tests/ppx/react/expected/interfaceWithRef.res.txt index 1157c8385d..7249ceb5bb 100644 --- a/res_syntax/tests/ppx/react/expected/interfaceWithRef.res.txt +++ b/res_syntax/tests/ppx/react/expected/interfaceWithRef.res.txt @@ -1,7 +1,4 @@ -type props<'x, 'ref> = { - x: 'x, - ref?: 'ref, -} +type props<'x, 'ref> = {x: 'x, ref?: 'ref} @react.component let make = ( {x, _}: props, diff --git a/res_syntax/tests/ppx/react/expected/interfaceWithRef.resi.txt b/res_syntax/tests/ppx/react/expected/interfaceWithRef.resi.txt index e5f91674c0..ee5da3ca6d 100644 --- a/res_syntax/tests/ppx/react/expected/interfaceWithRef.resi.txt +++ b/res_syntax/tests/ppx/react/expected/interfaceWithRef.resi.txt @@ -1,5 +1,2 @@ -type props<'x, 'ref> = { - x: 'x, - ref?: 'ref, -} +type props<'x, 'ref> = {x: 'x, ref?: 'ref} let make: React.componentLike, React.element> diff --git a/res_syntax/tests/ppx/react/expected/mangleKeyword.res.txt b/res_syntax/tests/ppx/react/expected/mangleKeyword.res.txt index 78108dd056..e216b70376 100644 --- a/res_syntax/tests/ppx/react/expected/mangleKeyword.res.txt +++ b/res_syntax/tests/ppx/react/expected/mangleKeyword.res.txt @@ -20,10 +20,7 @@ let c31 = React.createElement(C31.make, C31.makeProps(~_open="x", ())) @@jsxConfig({version: 4, mode: "classic"}) module C4C0 = { - type props<'T_open, 'T_type> = { - @as("open") _open: 'T_open, - @as("type") _type: 'T_type, - } + type props<'T_open, 'T_type> = {@as("open") _open: 'T_open, @as("type") _type: 'T_type} @react.component let make = ({@as("open") _open, @as("type") _type, _}: props<'T_open, string>) => @@ -35,10 +32,7 @@ module C4C0 = { } } module C4C1 = { - type props<'T_open, 'T_type> = { - @as("open") _open: 'T_open, - @as("type") _type: 'T_type, - } + type props<'T_open, 'T_type> = {@as("open") _open: 'T_open, @as("type") _type: 'T_type} external make: @as("open") React.componentLike, React.element> = "default" } @@ -49,10 +43,7 @@ let c4c1 = React.createElement(C4C1.make, {_open: "x", _type: "t"}) @@jsxConfig({version: 4, mode: "automatic"}) module C4A0 = { - type props<'T_open, 'T_type> = { - @as("open") _open: 'T_open, - @as("type") _type: 'T_type, - } + type props<'T_open, 'T_type> = {@as("open") _open: 'T_open, @as("type") _type: 'T_type} @react.component let make = ({@as("open") _open, @as("type") _type, _}: props<'T_open, string>) => @@ -64,10 +55,7 @@ module C4A0 = { } } module C4A1 = { - type props<'T_open, 'T_type> = { - @as("open") _open: 'T_open, - @as("type") _type: 'T_type, - } + type props<'T_open, 'T_type> = {@as("open") _open: 'T_open, @as("type") _type: 'T_type} external make: @as("open") React.componentLike, React.element> = "default" } diff --git a/res_syntax/tests/ppx/react/expected/newtype.res.txt b/res_syntax/tests/ppx/react/expected/newtype.res.txt index 23c4ac0a12..fb810ec54b 100644 --- a/res_syntax/tests/ppx/react/expected/newtype.res.txt +++ b/res_syntax/tests/ppx/react/expected/newtype.res.txt @@ -24,11 +24,7 @@ module V3 = { @@jsxConfig({version: 4, mode: "classic"}) module V4C = { - type props<'a, 'b, 'c> = { - a: 'a, - b: 'b, - c: 'c, - } + type props<'a, 'b, 'c> = {a: 'a, b: 'b, c: 'c} @react.component let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => @@ -43,11 +39,7 @@ module V4C = { @@jsxConfig({version: 4, mode: "automatic"}) module V4A = { - type props<'a, 'b, 'c> = { - a: 'a, - b: 'b, - c: 'c, - } + type props<'a, 'b, 'c> = {a: 'a, b: 'b, c: 'c} @react.component let make = ({a, b, c, _}: props<'\"type-a", array>, 'a>) => diff --git a/res_syntax/tests/ppx/react/expected/optimizeAutomaticMode.res.txt b/res_syntax/tests/ppx/react/expected/optimizeAutomaticMode.res.txt index 6cbff4531f..25c30a1245 100644 --- a/res_syntax/tests/ppx/react/expected/optimizeAutomaticMode.res.txt +++ b/res_syntax/tests/ppx/react/expected/optimizeAutomaticMode.res.txt @@ -4,9 +4,7 @@ module User = { type t = {firstName: string, lastName: string} let format = user => "Dr." ++ user.lastName - type props<'doctor> = { - doctor: 'doctor, - } + type props<'doctor> = {doctor: 'doctor} @react.component let make = ({doctor, _}: props<'doctor>) => { diff --git a/res_syntax/tests/ppx/react/expected/removedKeyProp.res.txt b/res_syntax/tests/ppx/react/expected/removedKeyProp.res.txt index e842a5355e..a7e5a464c9 100644 --- a/res_syntax/tests/ppx/react/expected/removedKeyProp.res.txt +++ b/res_syntax/tests/ppx/react/expected/removedKeyProp.res.txt @@ -1,10 +1,7 @@ @@jsxConfig({version: 4, mode: "classic"}) module Foo = { - type props<'x, 'y> = { - x: 'x, - y: 'y, - } + type props<'x, 'y> = {x: 'x, y: 'y} @react.component let make = ({x, y, _}: props<'x, 'y>) => React.string(x ++ y) let make = { @@ -15,9 +12,7 @@ module Foo = { } module HasChildren = { - type props<'children> = { - children: 'children, - } + type props<'children> = {children: 'children} @react.component let make = ({children, _}: props<'children>) => ReactDOM.createElement(React.fragment, [children]) diff --git a/res_syntax/tests/ppx/react/expected/topLevel.res.txt b/res_syntax/tests/ppx/react/expected/topLevel.res.txt index 35138e80da..bf6f95ec76 100644 --- a/res_syntax/tests/ppx/react/expected/topLevel.res.txt +++ b/res_syntax/tests/ppx/react/expected/topLevel.res.txt @@ -22,10 +22,7 @@ module V3 = { @@jsxConfig({version: 4, mode: "classic"}) module V4C = { - type props<'a, 'b> = { - a: 'a, - b: 'b, - } + type props<'a, 'b> = {a: 'a, b: 'b} @react.component let make = ({a, b, _}: props<'a, 'b>) => { @@ -42,10 +39,7 @@ module V4C = { @@jsxConfig({version: 4, mode: "automatic"}) module V4A = { - type props<'a, 'b> = { - a: 'a, - b: 'b, - } + type props<'a, 'b> = {a: 'a, b: 'b} @react.component let make = ({a, b, _}: props<'a, 'b>) => { diff --git a/res_syntax/tests/ppx/react/expected/typeConstraint.res.txt b/res_syntax/tests/ppx/react/expected/typeConstraint.res.txt index 88b0769f47..e7b3e4838f 100644 --- a/res_syntax/tests/ppx/react/expected/typeConstraint.res.txt +++ b/res_syntax/tests/ppx/react/expected/typeConstraint.res.txt @@ -17,10 +17,7 @@ module V3 = { @@jsxConfig({version: 4, mode: "classic"}) module V4C = { - type props<'a, 'b> = { - a: 'a, - b: 'b, - } + type props<'a, 'b> = {a: 'a, b: 'b} @react.component let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => @@ -35,10 +32,7 @@ module V4C = { @@jsxConfig({version: 4, mode: "automatic"}) module V4A = { - type props<'a, 'b> = { - a: 'a, - b: 'b, - } + type props<'a, 'b> = {a: 'a, b: 'b} @react.component let make = ({a, b, _}: props<'\"type-a", '\"type-a">) => ReactDOM.jsx("div", {}) let make = { diff --git a/res_syntax/tests/ppx/react/expected/v4.res.txt b/res_syntax/tests/ppx/react/expected/v4.res.txt index a705527ae6..ffb927df32 100644 --- a/res_syntax/tests/ppx/react/expected/v4.res.txt +++ b/res_syntax/tests/ppx/react/expected/v4.res.txt @@ -1,7 +1,4 @@ -type props<'x, 'y> = { // Component with type constraint - x: 'x, - y: 'y, -} +type props<'x, 'y> = {x: 'x, y: 'y} // Component with type constraint @react.component let make = ({x, y, _}: props) => React.string(x ++ y) let make = { let \"V4" = (props: props<_>) => make(props) @@ -10,9 +7,7 @@ let make = { module AnotherName = { type // Component with another name than "make" - props<'x> = { - x: 'x, - } + props<'x> = {x: 'x} @react.component let anotherName = ({x, _}: props<'x>) => React.string(x) let anotherName = { @@ -23,9 +18,7 @@ module AnotherName = { } module Uncurried = { - type props<'x> = { - x: 'x, - } + type props<'x> = {x: 'x} @react.component let make = ({x, _}: props<'x>) => React.string(x) let make = { @@ -36,25 +29,19 @@ module Uncurried = { } module type TUncurried = { - type props<'x> = { - x: 'x, - } + type props<'x> = {x: 'x} let make: React.componentLike, React.element> } module E = { - type props<'x> = { - x: 'x, - } + type props<'x> = {x: 'x} external make: React.componentLike, React.element> = "default" } module EUncurried = { - type props<'x> = { - x: 'x, - } + type props<'x> = {x: 'x} external make: React.componentLike, React.element> = "default" }