Skip to content
This repository was archived by the owner on Jun 15, 2023. It is now read-only.

JSX v4 shared props #699

Merged
merged 2 commits into from
Oct 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions cli/react_jsx_common.ml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,25 @@ let hasAttr (loc, _) = loc.txt = "react.component"
let hasAttrOnBinding {pvb_attributes} =
List.find_opt hasAttr pvb_attributes <> None

let coreTypeOfAttrs attributes =
List.find_map
(fun ({txt}, payload) ->
match (txt, payload) with
| "react.component", PTyp coreType -> Some coreType
| _ -> None)
attributes

let typVarsOfCoreType {ptyp_desc} =
match ptyp_desc with
| Ptyp_constr (_, coreTypes) ->
List.filter
(fun {ptyp_desc} ->
match ptyp_desc with
| Ptyp_var _ -> true
| _ -> false)
coreTypes
| _ -> []

let raiseError ~loc msg = Location.raise_errorf ~loc msg

let raiseErrorMultipleReactComponent ~loc =
Expand Down
86 changes: 69 additions & 17 deletions cli/reactjs_jsx_v4.ml
Original file line number Diff line number Diff line change
Expand Up @@ -311,13 +311,30 @@ let makeTypeDecls propsName loc namedTypeList =
~kind:(Ptype_record labelDeclList);
]

let makeTypeDeclsWithCoreType propsName loc coreType typVars =
[
Type.mk ~loc {txt = propsName; loc} ~kind:Ptype_abstract
~params:(typVars |> List.map (fun v -> (v, Invariant)))
~manifest:coreType;
]

(* type props<'x, 'y, ...> = { x: 'x, y?: 'y, ... } *)
let makePropsRecordType propsName loc namedTypeList =
Str.type_ Nonrecursive (makeTypeDecls propsName loc namedTypeList)
let makePropsRecordType ~coreTypeOfAttr ~typVarsOfCoreType propsName loc
namedTypeList =
Str.type_ Nonrecursive
(match coreTypeOfAttr with
| None -> makeTypeDecls propsName loc namedTypeList
| Some coreType ->
makeTypeDeclsWithCoreType propsName loc coreType typVarsOfCoreType)

(* type props<'x, 'y, ...> = { x: 'x, y?: 'y, ... } *)
let makePropsRecordTypeSig propsName loc namedTypeList =
Sig.type_ Nonrecursive (makeTypeDecls propsName loc namedTypeList)
let makePropsRecordTypeSig ~coreTypeOfAttr ~typVarsOfCoreType propsName loc
namedTypeList =
Sig.type_ Nonrecursive
(match coreTypeOfAttr with
| None -> makeTypeDecls propsName loc namedTypeList
| Some coreType ->
makeTypeDeclsWithCoreType propsName loc coreType typVarsOfCoreType)

let transformUppercaseCall3 ~config modulePath mapper jsxExprLoc callExprLoc
attrs callArguments =
Expand Down Expand Up @@ -733,6 +750,12 @@ let transformStructureItem ~config mapper item =
config.hasReactComponent <- true;
check_string_int_attribute_iter.structure_item
check_string_int_attribute_iter item;
let coreTypeOfAttr = React_jsx_common.coreTypeOfAttrs pval_attributes in
let typVarsOfCoreType =
coreTypeOfAttr
|> Option.map React_jsx_common.typVarsOfCoreType
|> Option.value ~default:[]
in
let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) =
match ptyp_desc with
| Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest))
Expand All @@ -749,11 +772,14 @@ let transformStructureItem ~config mapper item =
let retPropsType =
Typ.constr ~loc:pstr_loc
(Location.mkloc (Lident "props") pstr_loc)
(makePropsTypeParams namedTypeList)
(match coreTypeOfAttr with
| None -> makePropsTypeParams namedTypeList
| Some _ -> typVarsOfCoreType)
in
(* type props<'x, 'y> = { x: 'x, y?: 'y, ... } *)
let propsRecordType =
makePropsRecordType "props" pstr_loc namedTypeList
makePropsRecordType ~coreTypeOfAttr ~typVarsOfCoreType "props"
pstr_loc namedTypeList
in
(* can't be an arrow because it will defensively uncurry *)
let newExternalType =
Expand Down Expand Up @@ -787,6 +813,14 @@ let transformStructureItem ~config mapper item =
React_jsx_common.raiseErrorMultipleReactComponent ~loc:pstr_loc
else (
config.hasReactComponent <- true;
let coreTypeOfAttr =
React_jsx_common.coreTypeOfAttrs binding.pvb_attributes
in
let typVarsOfCoreType =
coreTypeOfAttr
|> Option.map React_jsx_common.typVarsOfCoreType
|> Option.value ~default:[]
in
let bindingLoc = binding.pvb_loc in
let bindingPatLoc = binding.pvb_pat.ppat_loc in
let binding =
Expand Down Expand Up @@ -977,7 +1011,8 @@ let transformStructureItem ~config mapper item =
let vbMatchList = List.map vbMatch namedArgWithDefaultValueList in
(* type props = { ... } *)
let propsRecordType =
makePropsRecordType "props" pstr_loc namedTypeList
makePropsRecordType ~coreTypeOfAttr ~typVarsOfCoreType "props"
pstr_loc namedTypeList
in
let innerExpression =
Exp.apply
Expand All @@ -989,19 +1024,23 @@ let transformStructureItem ~config mapper item =
[(Nolabel, Exp.ident (Location.mknoloc @@ Lident "ref"))]
| false -> [])
in
let makePropsPattern = function
| [] -> Pat.var @@ Location.mknoloc "props"
| _ ->
Pat.constraint_
(Pat.var @@ Location.mknoloc "props")
(Typ.constr (Location.mknoloc @@ Lident "props") [Typ.any ()])
in
let fullExpression =
(* React component name should start with uppercase letter *)
(* let make = { let \"App" = props => make(props); \"App" } *)
(* let make = React.forwardRef({
let \"App" = (props, ref) => make({...props, ref: @optional (Js.Nullabel.toOption(ref))})
})*)
Exp.fun_ nolabel None
(match namedTypeList with
| [] -> Pat.var @@ Location.mknoloc "props"
| _ ->
Pat.constraint_
(Pat.var @@ Location.mknoloc "props")
(Typ.constr (Location.mknoloc @@ Lident "props") [Typ.any ()]))
(match coreTypeOfAttr with
| None -> makePropsPattern namedTypeList
| Some _ -> makePropsPattern typVarsOfCoreType)
(if hasForwardRef then
Exp.fun_ nolabel None
(Pat.var @@ Location.mknoloc "ref")
Expand Down Expand Up @@ -1105,8 +1144,12 @@ let transformStructureItem ~config mapper item =
(Pat.constraint_ recordPattern
(Typ.constr ~loc:emptyLoc
{txt = Lident "props"; loc = emptyLoc}
(makePropsTypeParams ~stripExplicitOption:true
~stripExplicitJsNullableOfRef:hasForwardRef namedTypeList)))
(match coreTypeOfAttr with
| None ->
makePropsTypeParams ~stripExplicitOption:true
~stripExplicitJsNullableOfRef:hasForwardRef
namedTypeList
| Some _ -> typVarsOfCoreType)))
expression
in
(* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *)
Expand Down Expand Up @@ -1182,6 +1225,12 @@ let transformSignatureItem ~config _mapper item =
check_string_int_attribute_iter.signature_item
check_string_int_attribute_iter item;
let hasForwardRef = ref false in
let coreTypeOfAttr = React_jsx_common.coreTypeOfAttrs pval_attributes in
let typVarsOfCoreType =
coreTypeOfAttr
|> Option.map React_jsx_common.typVarsOfCoreType
|> Option.value ~default:[]
in
let rec getPropTypes types ({ptyp_loc; ptyp_desc} as fullType) =
match ptyp_desc with
| Ptyp_arrow (name, type_, ({ptyp_desc = Ptyp_arrow _} as rest))
Expand All @@ -1204,10 +1253,13 @@ let transformSignatureItem ~config _mapper item =
let retPropsType =
Typ.constr
(Location.mkloc (Lident "props") psig_loc)
(makePropsTypeParams namedTypeList)
(match coreTypeOfAttr with
| None -> makePropsTypeParams namedTypeList
| Some _ -> typVarsOfCoreType)
in
let propsRecordType =
makePropsRecordTypeSig "props" psig_loc
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)]
else [])
Expand Down
141 changes: 141 additions & 0 deletions tests/ppx/react/expected/sharedProps.res.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
@@jsxConfig({version: 4, mode: "classic"})

module V4C1 = {
type props = sharedProps<string>

@react.component(: sharedProps<string>) let make = ({x, y, _}: props) => React.string(x ++ y)
let make = {
let \"SharedProps$V4C1" = props => make(props)

\"SharedProps$V4C1"
}
}

module V4C2 = {
type props<'a> = sharedProps<'a>

@react.component(: sharedProps<'a>) let make = ({x, y, _}: props<'a>) => React.string(x ++ y)
let make = {
let \"SharedProps$V4C2" = (props: props<_>) => make(props)

\"SharedProps$V4C2"
}
}

module V4C3 = {
type props<'a> = sharedProps<string, 'a>

@react.component(: sharedProps<string, 'a>)
let make = ({x, y, _}: props<'a>) => React.string(x ++ y)
let make = {
let \"SharedProps$V4C3" = (props: props<_>) => make(props)

\"SharedProps$V4C3"
}
}

module V4C4 = {
type props = sharedProps

@react.component(: sharedProps) let make = ({x, y, _}: props) => React.string(x ++ y)
let make = {
let \"SharedProps$V4C4" = props => make(props)

\"SharedProps$V4C4"
}
}

module V4C5 = {
type props = sharedProps<string>

external make: React.componentLike<props, React.element> = "default"
}

module V4C6 = {
type props<'a> = sharedProps<'a>

external make: React.componentLike<props<'a>, React.element> = "default"
}

module V4C7 = {
type props<'a> = sharedProps<string, 'a>

external make: React.componentLike<props<'a>, React.element> = "default"
}

module V4C8 = {
type props = sharedProps

external make: React.componentLike<props, React.element> = "default"
}

@@jsxConfig({version: 4, mode: "automatic"})

module V4A1 = {
type props = sharedProps<string>

@react.component(: sharedProps<string>) let make = ({x, y, _}: props) => React.string(x ++ y)
let make = {
let \"SharedProps$V4A1" = props => make(props)

\"SharedProps$V4A1"
}
}

module V4A2 = {
type props<'a> = sharedProps<'a>

@react.component(: sharedProps<'a>) let make = ({x, y, _}: props<'a>) => React.string(x ++ y)
let make = {
let \"SharedProps$V4A2" = (props: props<_>) => make(props)

\"SharedProps$V4A2"
}
}

module V4A3 = {
type props<'a> = sharedProps<string, 'a>

@react.component(: sharedProps<string, 'a>)
let make = ({x, y, _}: props<'a>) => React.string(x ++ y)
let make = {
let \"SharedProps$V4A3" = (props: props<_>) => make(props)

\"SharedProps$V4A3"
}
}

module V4A4 = {
type props = sharedProps

@react.component(: sharedProps) let make = ({x, y, _}: props) => React.string(x ++ y)
let make = {
let \"SharedProps$V4A4" = props => make(props)

\"SharedProps$V4A4"
}
}

module V4A5 = {
type props = sharedProps<string>

external make: React.componentLike<props, React.element> = "default"
}

module V4A6 = {
type props<'a> = sharedProps<'a>

external make: React.componentLike<props<'a>, React.element> = "default"
}

module V4A7 = {
type props<'a> = sharedProps<string, 'a>

external make: React.componentLike<props<'a>, React.element> = "default"
}

module V4A8 = {
type props = sharedProps

external make: React.componentLike<props, React.element> = "default"
}
51 changes: 51 additions & 0 deletions tests/ppx/react/expected/sharedProps.resi.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@@jsxConfig({version: 4, mode: "classic"})

module V4C1: {
type props = sharedProps<string>

let make: React.componentLike<props, React.element>
}

module V4C2: {
type props<'a> = sharedProps<'a>

let make: React.componentLike<props<'a>, React.element>
}

module V4C3: {
type props<'a> = sharedProps<string, 'a>

let make: React.componentLike<props<'a>, React.element>
}

module V4C4: {
type props = sharedProps

let make: React.componentLike<props, React.element>
}

@@jsxConfig({version: 4, mode: "automatic"})

module V4A1: {
type props = sharedProps<string>

let make: React.componentLike<props, React.element>
}

module V4A2: {
type props<'a> = sharedProps<'a>

let make: React.componentLike<props<'a>, React.element>
}

module V4A3: {
type props<'a> = sharedProps<string, 'a>

let make: React.componentLike<props<'a>, React.element>
}

module V4A4: {
type props = sharedProps

let make: React.componentLike<props, React.element>
}
Loading