Skip to content

Commit

Permalink
fix: option type in external generation (#791)
Browse files Browse the repository at this point in the history
* fix: option type in external generation

* test: add test for signature + option type

* fix structure vs signature issue

* address review
  • Loading branch information
anmonteiro authored Oct 10, 2023
1 parent 1fdc7b1 commit 76d5ea8
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 29 deletions.
14 changes: 7 additions & 7 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 25 additions & 21 deletions ppx/reason_react_ppx.ml
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,11 @@ let makeModuleName fileName nestedModules fnName =

(* Build an AST node representing all named args for the `external` definition
for a component's props *)
let rec recursivelyMakeNamedArgsForExternal list args =
let rec recursivelyMakeNamedArgsForExternal ~types_come_from_signature list args
=
match list with
| (label, default, loc, interiorType) :: tl ->
recursivelyMakeNamedArgsForExternal tl
recursivelyMakeNamedArgsForExternal ~types_come_from_signature tl
(Builder.ptyp_arrow ~loc:Location.none label
(match (label, interiorType, default) with
(* ~foo=1 *)
Expand All @@ -320,27 +321,24 @@ let rec recursivelyMakeNamedArgsForExternal list args =
(* ~foo: int=1 *)
| _label, Some type_, Some _ -> type_
(* ~foo: option(int)=? *)
| ( _label,
Some
{
ptyp_desc = Ptyp_constr ({ txt = Lident "option" }, [ type_ ]);
},
_ ) ->
Builder.ptyp_constr ~loc { loc; txt = optionIdent } [ type_ ]
| ( label,
| ( Optional _,
Some
{
ptyp_desc =
Ptyp_constr
({ txt = Ldot (Lident "*predef*", "option") }, [ type_ ]);
( {
txt =
Lident "option" | Ldot (Lident "*predef*", "option");
},
[ type_ ] );
},
_ )
when isOptional label ->
when not types_come_from_signature ->
type_
(* ~foo: int=? - note this isnt valid. but we want to get a type error *)
| label, Some type_, _ when isOptional label -> type_
| Optional _, Some type_, _ -> type_
(* ~foo=? *)
| label, None, _ when isOptional label ->
| Optional _, None, _ ->
Builder.ptyp_var ~loc (safeTypeFromValue label)
(* ~foo *)
| label, None, _ -> Builder.ptyp_var ~loc (safeTypeFromValue label)
Expand All @@ -350,12 +348,14 @@ let rec recursivelyMakeNamedArgsForExternal list args =
[@@raises Invalid_argument]

(* Build an AST node for the [@bs.obj] representing props for a component *)
let makePropsValue fnName loc namedArgListWithKeyAndRef propsType =
let makePropsValue fnName ~types_come_from_signature loc
namedArgListWithKeyAndRef propsType =
let propsName = fnName ^ "Props" in
{
pval_name = { txt = propsName; loc };
pval_type =
recursivelyMakeNamedArgsForExternal namedArgListWithKeyAndRef
recursivelyMakeNamedArgsForExternal ~types_come_from_signature
namedArgListWithKeyAndRef
(Builder.ptyp_arrow ~loc nolabel
{
ptyp_desc = Ptyp_constr ({ txt = Lident "unit"; loc }, []);
Expand All @@ -379,12 +379,14 @@ let makePropsValue fnName loc namedArgListWithKeyAndRef propsType =

(* Build an AST node representing an `external` with the definition of the
[@bs.obj] *)
let makePropsExternal fnName loc namedArgListWithKeyAndRef propsType =
let makePropsExternal fnName loc ~component_is_external
namedArgListWithKeyAndRef propsType =
{
pstr_loc = loc;
pstr_desc =
Pstr_primitive
(makePropsValue fnName loc namedArgListWithKeyAndRef propsType);
(makePropsValue ~types_come_from_signature:component_is_external fnName
loc namedArgListWithKeyAndRef propsType);
}
[@@raises Invalid_argument]

Expand All @@ -393,7 +395,9 @@ let makePropsExternalSig fnName loc namedArgListWithKeyAndRef propsType =
{
psig_loc = loc;
psig_desc =
Psig_value (makePropsValue fnName loc namedArgListWithKeyAndRef propsType);
Psig_value
(makePropsValue ~types_come_from_signature:true fnName loc
namedArgListWithKeyAndRef propsType);
}
[@@raises Invalid_argument]

Expand Down Expand Up @@ -478,7 +482,7 @@ let reactDomJsxExprAndChildren =

(* Builds an AST node for the entire `external` definition of props *)
let makeExternalDecl fnName loc namedArgListWithKeyAndRef namedTypeList =
makePropsExternal fnName loc
makePropsExternal ~component_is_external:false fnName loc
(List.map pluckLabelDefaultLocType namedArgListWithKeyAndRef)
(makePropsType ~loc namedTypeList)
[@@raises Invalid_argument]
Expand Down Expand Up @@ -801,7 +805,7 @@ let jsxMapper =
in
let retPropsType = makePropsType ~loc:pstr_loc namedTypeList in
let externalPropsDecl =
makePropsExternal fnName pstr_loc
makePropsExternal ~component_is_external:true fnName pstr_loc
((optional "key", None, pstr_loc, Some (keyType pstr_loc))
:: List.map pluckLabelAndLoc propTypes)
retPropsType
Expand Down
42 changes: 42 additions & 0 deletions ppx/test/signature-optional.t/input.re
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Greeting: {
[@react.component]
let make: (~mockup: string=?) => React.element;
} = {
[@react.component]
let make = (~mockup: option(string)=?) => {
<button> {React.string("Hello!")} </button>;
};
};

module MyPropIsOptionBool = {
[@react.component] external make: (~myProp: bool=?) => React.element = "A";
};

module MyPropIsOptionOptionBool = {
[@react.component]
external make: (~myProp: option(bool)=?) => React.element = "B";
};

module MyPropIsOptionOptionBoolWithSig: {
[@react.component]
external make: (~myProp: option(bool)=?) => React.element = "B";
} = {
[@react.component]
external make: (~myProp: option(bool)=?) => React.element = "B";
};

module MyPropIsOptionOptionBoolWithValSig: {
[@react.component]
let make: (~myProp: option(bool)=?) => React.element;
} = {
[@react.component]
external make: (~myProp: option(bool)=?) => React.element = "B";
};

module MyPropIsOptionOptionBoolLetWithValSig: {
[@react.component]
let make: (~myProp: option(bool)=?) => React.element;
} = {
[@react.component]
let make = (~myProp: option(option(bool))=?) => React.null;
};
106 changes: 106 additions & 0 deletions ppx/test/signature-optional.t/run.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
$ ../ppx.sh --output ml input.re
module Greeting :
sig
external makeProps :
?mockup:string ->
?key:string -> unit -> < mockup: string option > Js.t = ""
[@@mel.obj ]
val make :
(< mockup: string option > Js.t, React.element) React.componentLike
end =
struct
external makeProps :
?mockup:string ->
?key:string -> unit -> < mockup: string option > Js.t = ""
[@@mel.obj ]
let make =
((fun ?mockup:(mockup : string option) ->
React.DOM.jsx "button"
(((React.DOM.domProps)[@merlin.hide ])
~children:(React.string "Hello!") ()))
[@warning "-16"])
let make =
let Output$Greeting (Props : < mockup: string option > Js.t) =
make ?mockup:(Props ## mockup) in
Output$Greeting
end
module MyPropIsOptionBool =
struct
external makeProps :
?myProp:bool -> ?key:string -> unit -> < myProp: bool option > Js.t
= ""[@@mel.obj ]
external make :
(< myProp: bool option > Js.t, React.element) React.componentLike =
"A"
end
module MyPropIsOptionOptionBool =
struct
external makeProps :
?myProp:bool option ->
?key:string -> unit -> < myProp: bool option option > Js.t = ""
[@@mel.obj ]
external make :
(< myProp: bool option option > Js.t, React.element)
React.componentLike = "B"
end
module MyPropIsOptionOptionBoolWithSig :
sig
external makeProps :
?myProp:bool option ->
?key:string -> unit -> < myProp: bool option option > Js.t = ""
[@@mel.obj ]
external make :
(< myProp: bool option option > Js.t, React.element)
React.componentLike = "B"
end =
struct
external makeProps :
?myProp:bool option ->
?key:string -> unit -> < myProp: bool option option > Js.t = ""
[@@mel.obj ]
external make :
(< myProp: bool option option > Js.t, React.element)
React.componentLike = "B"
end
module MyPropIsOptionOptionBoolWithValSig :
sig
external makeProps :
?myProp:bool option ->
?key:string -> unit -> < myProp: bool option option > Js.t = ""
[@@mel.obj ]
val make :
(< myProp: bool option option > Js.t, React.element)
React.componentLike
end =
struct
external makeProps :
?myProp:bool option ->
?key:string -> unit -> < myProp: bool option option > Js.t = ""
[@@mel.obj ]
external make :
(< myProp: bool option option > Js.t, React.element)
React.componentLike = "B"
end
module MyPropIsOptionOptionBoolLetWithValSig :
sig
external makeProps :
?myProp:bool option ->
?key:string -> unit -> < myProp: bool option option > Js.t = ""
[@@mel.obj ]
val make :
(< myProp: bool option option > Js.t, React.element)
React.componentLike
end =
struct
external makeProps :
?myProp:bool option ->
?key:string -> unit -> < myProp: bool option option > Js.t = ""
[@@mel.obj ]
let make = ((fun ?myProp:(myProp : bool option option) -> React.null)
[@warning "-16"])
let make =
let Output$MyPropIsOptionOptionBoolLetWithValSig
(Props : < myProp: bool option option > Js.t) =
make ?myProp:(Props ## myProp) in
Output$MyPropIsOptionOptionBoolLetWithValSig
end
3 changes: 2 additions & 1 deletion ppx/test/signature.t/input.re
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ module MyPropIsOptionBool = {
};

module MyPropIsOptionOptionBool = {
[@react.component] external make: (~myProp: option(bool)=?) => React.element = "B";
[@react.component]
external make: (~myProp: option(bool)=?) => React.element = "B";
};

0 comments on commit 76d5ea8

Please sign in to comment.