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

Make empty props as generic type #640

Closed
wants to merge 1 commit into from
Closed
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
94 changes: 63 additions & 31 deletions cli/reactjs_jsx_v4.ml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ let constantString ~loc str =
(* {} empty record *)
let emptyRecord ~loc = Exp.record ~loc [] None

(* <_> *)
let genericCoreTypes ~loc =
[{ptyp_desc = Ptyp_any; ptyp_loc = loc; ptyp_attributes = []}]

let safeTypeFromValue valueStr =
let valueStr = getLabel valueStr in
if valueStr = "" || (valueStr.[0] [@doesNotRaise]) <> '_' then valueStr
Expand Down Expand Up @@ -228,13 +232,18 @@ let recordFromProps ~loc ~removeKey callArguments =
pexp_attributes = [];
}

(* make type params for make fn arguments *)
(* Make type params of the props type *)
(* let make = ({id, name, children}: props<'id, 'name, 'children>) *)
let makePropsTypeParamsTvar namedTypeList =
namedTypeList
|> List.filter_map (fun (_isOptional, label, _, _interiorType) ->
if label = "key" then None
else Some (Typ.var @@ safeTypeFromValue (Labelled label)))
let makePropsTypeParamsTvar ~default namedTypeList =
let params =
namedTypeList
|> List.filter_map (fun (_isOptional, label, _, _interiorType) ->
if label = "key" then None
else Some (Typ.var @@ safeTypeFromValue (Labelled label)))
in
match params with
| [] -> default
| _ -> params

let stripOption coreType =
match coreType with
Expand All @@ -251,31 +260,37 @@ let stripJsNullable coreType =
List.nth_opt coreTypes 0 [@doesNotRaise]
| _ -> Some coreType

(* Make type params of the props type *)
(* Make type params for make fn arguments *)
(* (Sig) let make: React.componentLike<props<string>, React.element> *)
(* (Str) let make = ({x, _}: props<'x>) => body *)
(* (Str) external make: React.componentLike<props< .. >, React.element> = "default" *)
let makePropsTypeParams ?(stripExplicitOption = false)
?(stripExplicitJsNullableOfRef = false) namedTypeList =
namedTypeList
|> List.filter_map (fun (isOptional, label, _, interiorType) ->
if label = "key" then None
(* TODO: Worth thinking how about "ref_" or "_ref" usages *)
else if label = "ref" then
(*
?(stripExplicitJsNullableOfRef = false) ~default namedTypeList =
let params =
namedTypeList
|> List.filter_map (fun (isOptional, label, _, interiorType) ->
if label = "key" then None
(* TODO: Worth thinking how about "ref_" or "_ref" usages *)
else if label = "ref" then
(*
If ref has a type annotation then use it, else `ReactDOM.Ref.currentDomRef.
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)
| _ ->
(* Strip explicit Js.Nullable.t in case of forwardRef *)
if stripExplicitJsNullableOfRef then stripJsNullable interiorType
else Some interiorType
(* Strip the explicit option type in implementation *)
(* let make = (~x: option<string>=?) => ... *)
else if isOptional && stripExplicitOption then stripOption interiorType
else Some interiorType)
match interiorType with
| {ptyp_desc = Ptyp_var "ref"} -> Some (refType Location.none)
| _ ->
(* Strip explicit Js.Nullable.t in case of forwardRef *)
if stripExplicitJsNullableOfRef then stripJsNullable interiorType
else Some interiorType
(* Strip the explicit option type in implementation *)
(* let make = (~x: option<string>=?) => ... *)
else if isOptional && stripExplicitOption then
stripOption interiorType
else Some interiorType)
in
match params with
| [] -> default
| _ -> params

let makeLabelDecls ~loc namedTypeList =
namedTypeList
Expand All @@ -293,7 +308,10 @@ let makeTypeDecls propsName loc namedTypeList =
let labelDeclList = makeLabelDecls ~loc namedTypeList in
(* 'id, 'className, ... *)
let params =
makePropsTypeParamsTvar namedTypeList
makePropsTypeParamsTvar
~default:(genericCoreTypes ~loc:Location.none)
(* If empty props type props<_> = {} *)
namedTypeList
|> List.map (fun coreType -> (coreType, Invariant))
in
[
Expand Down Expand Up @@ -745,9 +763,15 @@ let transformStructureItem ~config mapper item =
let innerType, propTypes = getPropTypes [] pval_type in
let namedTypeList = List.fold_left argToConcreteType [] propTypes in
let retPropsType =
let params =
makePropsTypeParams
~default:(genericCoreTypes ~loc:Location.none)
namedTypeList
in

Typ.constr ~loc:pstr_loc
(Location.mkloc (Lident "props") pstr_loc)
(makePropsTypeParams namedTypeList)
params
in
(* type props<'x, 'y> = { x: 'x, y?: 'y, ... } *)
let propsRecordType =
Expand Down Expand Up @@ -1093,12 +1117,17 @@ let transformStructureItem ~config mapper item =
| _ -> Pat.record (List.rev patternsWithLabel) Open
in
let expression =
let params =
makePropsTypeParams ~stripExplicitOption:true
~stripExplicitJsNullableOfRef:hasForwardRef
~default:(genericCoreTypes ~loc:Location.none)
namedTypeList
in
Exp.fun_ Nolabel None
(Pat.constraint_ recordPattern
(Typ.constr ~loc:emptyLoc
{txt = Lident "props"; loc = emptyLoc}
(makePropsTypeParams ~stripExplicitOption:true
~stripExplicitJsNullableOfRef:hasForwardRef namedTypeList)))
params))
expression
in
(* let make = ({id, name, ...}: props<'id, 'name, ...>) => { ... } *)
Expand Down Expand Up @@ -1192,9 +1221,12 @@ let transformSignatureItem ~config _mapper item =
let innerType, propTypes = getPropTypes [] pval_type in
let namedTypeList = List.fold_left argToConcreteType [] propTypes in
let retPropsType =
Typ.constr
(Location.mkloc (Lident "props") psig_loc)
(makePropsTypeParams namedTypeList)
let params =
makePropsTypeParams
~default:(genericCoreTypes ~loc:Location.none)
namedTypeList
in
Typ.constr (Location.mkloc (Lident "props") psig_loc) params
in
let propsRecordType =
makePropsRecordTypeSig "props" Location.none
Expand Down
8 changes: 4 additions & 4 deletions tests/ppx/react/expected/forwardRef.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ module V4C = {
\"ForwardRef$V4C$FancyInput"
})
}
type props = {}
type props<_> = {}

@react.component
let make = (_: props) => {
let make = (_: props<_>) => {
let input = React.useRef(Js.Nullable.null)

ReactDOMRe.createDOMElementVariadic(
Expand Down Expand Up @@ -159,10 +159,10 @@ module V4A = {
\"ForwardRef$V4A$FancyInput"
})
}
type props = {}
type props<_> = {}

@react.component
let make = (_: props) => {
let make = (_: props<_>) => {
let input = React.useRef(Js.Nullable.null)
ReactDOM.jsx(
"div",
Expand Down
19 changes: 19 additions & 0 deletions tests/ppx/react/expected/interface.res.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module A = {
type props<'x> = {x: 'x}
@react.component let make = ({x, _}: props<'x>) => React.string(x)
let make = {
let \"Interface$A" = (props: props<_>) => make(props)
\"Interface$A"
}
}

module NoProps = {
type props<_> = {}

@react.component let make = (_: props<_>) => ReactDOM.jsx("div", {})
let make = {
let \"Interface$NoProps" = props => make(props)

\"Interface$NoProps"
}
}
12 changes: 10 additions & 2 deletions tests/ppx/react/expected/interface.resi.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
type props<'x> = {x: 'x}
let make: React.componentLike<props<string>, React.element>
module A: {
type props<'x> = {x: 'x}
let make: React.componentLike<props<string>, React.element>
}

module NoProps: {
type props<_> = {}

let make: React.componentLike<props<_>, React.element>
}
38 changes: 38 additions & 0 deletions tests/ppx/react/expected/noPropsWithKey.res.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
@@jsxConfig({version: 4, mode: "classic"})

module V4CA = {
type props<_> = {}

@react.component let make = (_: props<_>) => ReactDOMRe.createDOMElementVariadic("div", [])
let make = {
let \"NoPropsWithKey$V4CA" = props => make(props)

\"NoPropsWithKey$V4CA"
}
}

module V4CB = {
type props<_> = {}

@module("c")
external make: React.componentLike<props<_>, React.element> = "component"
}

module V4C = {
type props<_> = {}

@react.component
let make = (_: props<_>) =>
ReactDOMRe.createElement(
ReasonReact.fragment,
[
React.createElement(V4CA.make, Jsx.addKeyProp(({}: V4CA.props<_>), "k")),
React.createElement(V4CB.make, Jsx.addKeyProp(({}: V4CB.props<_>), "k")),
],
)
let make = {
let \"NoPropsWithKey$V4C" = props => make(props)

\"NoPropsWithKey$V4C"
}
}
4 changes: 2 additions & 2 deletions tests/ppx/react/expected/removedKeyProp.res.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ module HasChildren = {
\"RemovedKeyProp$HasChildren"
}
}
type props = {}
type props<_> = {}

@react.component
let make = (_: props) =>
let make = (_: props<_>) =>
ReactDOMRe.createElement(
ReasonReact.fragment,
[
Expand Down
9 changes: 9 additions & 0 deletions tests/ppx/react/interface.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module A = {
@react.component
let make = (~x) => React.string(x)
}

module NoProps = {
@react.component
let make = () => <div />
}
11 changes: 9 additions & 2 deletions tests/ppx/react/interface.resi
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
@react.component
let make: (~x:string) => React.element
module A: {
@react.component
let make: (~x: string) => React.element
}

module NoProps: {
@react.component
let make: unit => React.element
}
16 changes: 16 additions & 0 deletions tests/ppx/react/noPropsWithKey.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
@@jsxConfig({version: 4, mode: "classic"})

module V4CA = {
@react.component
let make = () => <div />
}

module V4CB = {
@module("c") @react.component
external make: unit => React.element = "component"
}

module V4C = {
@react.component
let make = () => <><V4CA key="k" /> <V4CB key="k" /></>
}