diff --git a/jscomp/gentype/EmitJs.ml b/jscomp/gentype/EmitJs.ml index 892afec66d..8a939ff3af 100644 --- a/jscomp/gentype/EmitJs.ml +++ b/jscomp/gentype/EmitJs.ml @@ -124,9 +124,9 @@ let emitExportFromTypeDeclarations ~config ~emitters ~env ~typeGetNormalized ~typeNameIsInterface) (env, emitters) -let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName - ~outputFileRelative ~resolver ~typeGetConverter ~typeGetNormalized - ~typeNameIsInterface ~variantTables codeItem = +let emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName + ~outputFileRelative ~resolver ~typeGetConverter ~inlineOneLevel + ~typeGetNormalized ~typeNameIsInterface ~variantTables codeItem = if !Debug.codeItems then Log_.item "Code Item: %s\n" (codeItem |> codeItemToString ~config ~typeNameIsInterface); @@ -162,6 +162,7 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName retType; } as function_) when retType |> EmitType.isTypeFunctionComponent ~fields -> + (* JSX V3 *) let componentName = match importFile with "." | ".." -> None | _ -> Some importFile in @@ -182,6 +183,41 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName } in Function { function_ with componentName } + | Function + ({ + argTypes = [ { aType = Ident { name } as propsType; aName } ]; + retType; + } as function_) + when Filename.check_suffix name "props" + && retType |> EmitType.isTypeFunctionComponent ~fields:[] -> ( + match inlineOneLevel propsType with + | Object (closedFlags, fields) -> + (* JSX V3 *) + let componentName = + match importFile with + | "." | ".." -> None + | _ -> Some importFile + in + let fields = + Ext_list.filter_map fields (fun (field : field) -> + match field.nameJS with + | "children" + when field.type_ |> EmitType.isTypeReactElement -> + Some { field with type_ = EmitType.typeReactChild } + | "key" -> + (* Filter out key, which is added to the props type definition in V4 *) + None + | _ -> Some field) + in + let function_ = + { + function_ with + argTypes = + [ { aType = Object (closedFlags, fields); aName } ]; + } + in + Function { function_ with componentName } + | _ -> type_) | _ -> type_ in let converter = type_ |> typeGetConverter in @@ -249,6 +285,23 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName typeVars : string list; } end in + let getHookName () = + let chopSuffix suffix = + match resolvedNameStr = suffix with + | true -> "" + | false -> ( + match Filename.check_suffix resolvedNameStr ("_" ^ suffix) with + | true -> Filename.chop_suffix resolvedNameStr ("_" ^ suffix) + | false -> resolvedNameStr) + in + let suffix = + if originalName = default then chopSuffix default + else if originalName = make then chopSuffix make + else resolvedNameStr + in + (fileName |> ModuleName.toString) + ^ match suffix = "" with true -> suffix | false -> "_" ^ suffix + in let type_, hookType = match type_ with | Function @@ -258,6 +311,7 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName typeVars; } as function_) when retType |> EmitType.isTypeFunctionComponent ~fields -> + (* JSX V3 *) let propsType = let fields = fields @@ -277,25 +331,6 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName argTypes = [ { aName = ""; aType = propsType } ]; } in - let chopSuffix suffix = - match resolvedNameStr = suffix with - | true -> "" - | false -> ( - match - Filename.check_suffix resolvedNameStr ("_" ^ suffix) - with - | true -> Filename.chop_suffix resolvedNameStr ("_" ^ suffix) - | false -> resolvedNameStr) - in - let suffix = - if originalName = default then chopSuffix default - else if originalName = make then chopSuffix make - else resolvedNameStr - in - let hookName = - (fileName |> ModuleName.toString) - ^ match suffix = "" with true -> suffix | false -> "_" ^ suffix - in let resolvedTypeName = if (not config.emitTypePropDone) @@ -305,8 +340,43 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName ResolvedName.fromString "Props") else ResolvedName.fromString name |> ResolvedName.dot "Props" in - ( Function { function_ with componentName = Some hookName }, + ( Function { function_ with componentName = Some (getHookName ()) }, Some { HookType.propsType; resolvedTypeName; typeVars } ) + | Function + ({ argTypes = [ { aType = Ident { name } as propsType } ]; retType } + as function_) + when Filename.check_suffix name "props" + && retType |> EmitType.isTypeFunctionComponent ~fields:[] -> + let compType = + match inlineOneLevel propsType with + | Object (closedFlags, fields) -> + (* JSX V4 *) + let propsType = + let fields = + Ext_list.filter_map fields (fun (field : field) -> + match field.nameJS with + | "children" + when field.type_ |> EmitType.isTypeReactElement -> + Some + { field with type_ = EmitType.typeReactChild } + | "key" -> + (* Filter out key, which is added to the props type definition in V4 *) + None + | _ -> Some field) + in + Object (closedFlags, fields) + in + let function_ = + { + function_ with + argTypes = [ { aName = ""; aType = propsType } ]; + } + in + Function + { function_ with componentName = Some (getHookName ()) } + | _ -> type_ + in + (compType, None) | _ -> (type_, None) in @@ -349,15 +419,15 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName in (envWithRequires, emitters) -and emitCodeItems ~config ~outputFileRelative ~emitters ~moduleItemsEmitter ~env - ~fileName ~resolver ~typeNameIsInterface ~typeGetConverter +let emitCodeItems ~config ~outputFileRelative ~emitters ~moduleItemsEmitter ~env + ~fileName ~resolver ~typeNameIsInterface ~typeGetConverter ~inlineOneLevel ~typeGetNormalized ~variantTables codeItems = codeItems |> List.fold_left (fun (env, emitters) -> emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName - ~outputFileRelative ~resolver ~typeGetConverter ~typeGetNormalized - ~typeNameIsInterface ~variantTables) + ~outputFileRelative ~resolver ~typeGetConverter ~inlineOneLevel + ~typeGetNormalized ~typeNameIsInterface ~variantTables) (env, emitters) let emitRequires ~importedValueOrComponent ~early ~config ~requires emitters = @@ -627,12 +697,12 @@ let emitTranslationAsString ~config ~fileName ~inputCmtTranslateTypeDeclarations try exportTypeMap |> StringMap.find s with Not_found -> env.exportTypeMapFromOtherFiles |> StringMap.find s in - let typeGetNormalized_ ~env type_ = + let typeGetNormalized__ ~inline ~env type_ = type_ - |> Converter.typeGetNormalized ~config ~inline:false - ~lookupId:(lookupId_ ~env) + |> Converter.typeGetNormalized ~config ~inline ~lookupId:(lookupId_ ~env) ~typeNameIsInterface:(typeNameIsInterface ~env) in + let typeGetNormalized_ = typeGetNormalized__ ~inline:false in let typeGetConverter_ ~env type_ = type_ |> Converter.typeGetConverter ~config ~lookupId:(lookupId_ ~env) @@ -654,10 +724,29 @@ let emitTranslationAsString ~config ~fileName ~inputCmtTranslateTypeDeclarations ~typeGetNormalized:(typeGetNormalized_ ~env) ~env ~typeNameIsInterface:(typeNameIsInterface ~env) in + let inlineOneLevel type_ = + match type_ with + | Ident { builtin = false; name; typeArgs } -> ( + match name |> lookupId_ ~env with + | { type_; typeVars } -> + let pairs = + try List.combine typeVars typeArgs with Invalid_argument _ -> [] + in + let f typeVar = + match + pairs |> List.find (fun (typeVar1, _) -> typeVar = typeVar1) + with + | _, typeArgument -> Some typeArgument + | exception Not_found -> None + in + type_ |> TypeVars.substitute ~f + | exception Not_found -> type_) + | _ -> type_ + in let env, emitters = translation.codeItems |> emitCodeItems ~config ~emitters ~moduleItemsEmitter ~env ~fileName - ~outputFileRelative ~resolver + ~outputFileRelative ~resolver ~inlineOneLevel ~typeGetNormalized:(typeGetNormalized_ ~env) ~typeGetConverter:(typeGetConverter_ ~env) ~typeNameIsInterface:(typeNameIsInterface ~env) ~variantTables diff --git a/jscomp/gentype_tests/typescript-react-example/package-lock.json b/jscomp/gentype_tests/typescript-react-example/package-lock.json index cff84db9eb..efa9acb5b8 100644 --- a/jscomp/gentype_tests/typescript-react-example/package-lock.json +++ b/jscomp/gentype_tests/typescript-react-example/package-lock.json @@ -20,6 +20,7 @@ } }, "../../..": { + "name": "rescript", "version": "10.1.0-alpha.1", "dev": true, "hasInstallScript": true, diff --git a/jscomp/gentype_tests/typescript-react-example/src/JSXV4.bs.js b/jscomp/gentype_tests/typescript-react-example/src/JSXV4.bs.js new file mode 100644 index 0000000000..7fec15984d --- /dev/null +++ b/jscomp/gentype_tests/typescript-react-example/src/JSXV4.bs.js @@ -0,0 +1,28 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as JSXV4Gen from "./JSXV4.gen"; + +function JSXV4$CompV4(props) { + return props.x + props.y; +} + +var CompV4 = { + make: JSXV4$CompV4 +}; + +function JSXV4$CompV3(Props) { + return Props.x + Props.y; +} + +var CompV3 = { + make: JSXV4$CompV3 +}; + +var make = JSXV4Gen.make; + +export { + CompV4 , + CompV3 , + make , +} +/* make Not a pure module */ diff --git a/jscomp/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx b/jscomp/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx new file mode 100644 index 0000000000..fe1b67a982 --- /dev/null +++ b/jscomp/gentype_tests/typescript-react-example/src/JSXV4.gen.tsx @@ -0,0 +1,59 @@ +/* TypeScript file generated from JSXV4.res by genType. */ +/* eslint-disable import/first */ + + +import {make as makeNotChecked} from './hookExample'; + +import * as React from 'react'; + +// In case of type error, check the type of 'make' in 'JSXV4.re' and './hookExample'. +export const makeTypeChecked: React.ComponentType<{ + readonly actions?: JSX.Element; + readonly person: person; + readonly children: React.ReactNode; + readonly renderMe: renderMe +}> = makeNotChecked; + +// Export 'make' early to allow circular import from the '.bs.js' file. +export const make: unknown = makeTypeChecked as React.ComponentType<{ + readonly actions?: JSX.Element; + readonly person: person; + readonly children: React.ReactNode; + readonly renderMe: renderMe +}>; + +// tslint:disable-next-line:no-var-requires +const JSXV4BS = require('./JSXV4.bs'); + +// tslint:disable-next-line:interface-over-type-literal +export type CompV4_props = { + readonly key?: string; + readonly x: x; + readonly y: y +}; + +// tslint:disable-next-line:interface-over-type-literal +export type person = { readonly name: string; readonly age: number }; + +// tslint:disable-next-line:interface-over-type-literal +export type renderMe = React.ComponentType<{ readonly randomString: string; readonly poly: a }>; + +// tslint:disable-next-line:interface-over-type-literal +export type props = { + readonly key?: string; + readonly actions?: actions; + readonly person: person; + readonly children: children; + readonly renderMe: renderMe +}; + +export const CompV4_make: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4BS.CompV4.make; + +// tslint:disable-next-line:interface-over-type-literal +export type Props = { readonly x: string; readonly y: string }; + +export const CompV3_make: React.ComponentType<{ readonly x: string; readonly y: string }> = JSXV4BS.CompV3.make; + +export const CompV3: { make: React.ComponentType<{ readonly x: string; readonly y: string }> } = JSXV4BS.CompV3 + +export const CompV4: { make: React.ComponentType<{ readonly x: string; readonly y: string }> } = JSXV4BS.CompV4 diff --git a/jscomp/gentype_tests/typescript-react-example/src/JSXV4.res b/jscomp/gentype_tests/typescript-react-example/src/JSXV4.res new file mode 100644 index 0000000000..97c7584aee --- /dev/null +++ b/jscomp/gentype_tests/typescript-react-example/src/JSXV4.res @@ -0,0 +1,31 @@ +@@jsxConfig({version: 4}) + +module CompV4 = { + @genType @react.component + let make = (~x, ~y) => React.string(x ++ y) +} + +@@jsxConfig({version: 3}) + +module CompV3 = { + @genType @react.component + let make = (~x, ~y) => React.string(x ++ y) +} + +@genType +type person = { + name: string, + age: int, +} + +@genType type renderMe<'a> = React.component<{"randomString": string, "poly": 'a}> + +@@jsxConfig({version: 4}) + +@genType.import("./hookExample") @react.component +external make: ( + ~actions: React.element=?, + ~person: person, + ~children: React.element, + ~renderMe: renderMe<'a>, +) => React.element = "make" diff --git a/lib/4.06.1/whole_compiler.ml b/lib/4.06.1/whole_compiler.ml index 4077392d09..3d3693dda7 100644 --- a/lib/4.06.1/whole_compiler.ml +++ b/lib/4.06.1/whole_compiler.ml @@ -240334,9 +240334,9 @@ let emitExportFromTypeDeclarations ~config ~emitters ~env ~typeGetNormalized ~typeNameIsInterface) (env, emitters) -let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName - ~outputFileRelative ~resolver ~typeGetConverter ~typeGetNormalized - ~typeNameIsInterface ~variantTables codeItem = +let emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName + ~outputFileRelative ~resolver ~typeGetConverter ~inlineOneLevel + ~typeGetNormalized ~typeNameIsInterface ~variantTables codeItem = if !Debug.codeItems then Log_.item "Code Item: %s\n" (codeItem |> codeItemToString ~config ~typeNameIsInterface); @@ -240372,6 +240372,7 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName retType; } as function_) when retType |> EmitType.isTypeFunctionComponent ~fields -> + (* JSX V3 *) let componentName = match importFile with "." | ".." -> None | _ -> Some importFile in @@ -240392,6 +240393,41 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName } in Function { function_ with componentName } + | Function + ({ + argTypes = [ { aType = Ident { name } as propsType; aName } ]; + retType; + } as function_) + when Filename.check_suffix name "props" + && retType |> EmitType.isTypeFunctionComponent ~fields:[] -> ( + match inlineOneLevel propsType with + | Object (closedFlags, fields) -> + (* JSX V3 *) + let componentName = + match importFile with + | "." | ".." -> None + | _ -> Some importFile + in + let fields = + Ext_list.filter_map fields (fun (field : field) -> + match field.nameJS with + | "children" + when field.type_ |> EmitType.isTypeReactElement -> + Some { field with type_ = EmitType.typeReactChild } + | "key" -> + (* Filter out key, which is added to the props type definition in V4 *) + None + | _ -> Some field) + in + let function_ = + { + function_ with + argTypes = + [ { aType = Object (closedFlags, fields); aName } ]; + } + in + Function { function_ with componentName } + | _ -> type_) | _ -> type_ in let converter = type_ |> typeGetConverter in @@ -240459,6 +240495,23 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName typeVars : string list; } end in + let getHookName () = + let chopSuffix suffix = + match resolvedNameStr = suffix with + | true -> "" + | false -> ( + match Filename.check_suffix resolvedNameStr ("_" ^ suffix) with + | true -> Filename.chop_suffix resolvedNameStr ("_" ^ suffix) + | false -> resolvedNameStr) + in + let suffix = + if originalName = default then chopSuffix default + else if originalName = make then chopSuffix make + else resolvedNameStr + in + (fileName |> ModuleName.toString) + ^ match suffix = "" with true -> suffix | false -> "_" ^ suffix + in let type_, hookType = match type_ with | Function @@ -240468,6 +240521,7 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName typeVars; } as function_) when retType |> EmitType.isTypeFunctionComponent ~fields -> + (* JSX V3 *) let propsType = let fields = fields @@ -240487,25 +240541,6 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName argTypes = [ { aName = ""; aType = propsType } ]; } in - let chopSuffix suffix = - match resolvedNameStr = suffix with - | true -> "" - | false -> ( - match - Filename.check_suffix resolvedNameStr ("_" ^ suffix) - with - | true -> Filename.chop_suffix resolvedNameStr ("_" ^ suffix) - | false -> resolvedNameStr) - in - let suffix = - if originalName = default then chopSuffix default - else if originalName = make then chopSuffix make - else resolvedNameStr - in - let hookName = - (fileName |> ModuleName.toString) - ^ match suffix = "" with true -> suffix | false -> "_" ^ suffix - in let resolvedTypeName = if (not config.emitTypePropDone) @@ -240515,8 +240550,43 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName ResolvedName.fromString "Props") else ResolvedName.fromString name |> ResolvedName.dot "Props" in - ( Function { function_ with componentName = Some hookName }, + ( Function { function_ with componentName = Some (getHookName ()) }, Some { HookType.propsType; resolvedTypeName; typeVars } ) + | Function + ({ argTypes = [ { aType = Ident { name } as propsType } ]; retType } + as function_) + when Filename.check_suffix name "props" + && retType |> EmitType.isTypeFunctionComponent ~fields:[] -> + let compType = + match inlineOneLevel propsType with + | Object (closedFlags, fields) -> + (* JSX V4 *) + let propsType = + let fields = + Ext_list.filter_map fields (fun (field : field) -> + match field.nameJS with + | "children" + when field.type_ |> EmitType.isTypeReactElement -> + Some + { field with type_ = EmitType.typeReactChild } + | "key" -> + (* Filter out key, which is added to the props type definition in V4 *) + None + | _ -> Some field) + in + Object (closedFlags, fields) + in + let function_ = + { + function_ with + argTypes = [ { aName = ""; aType = propsType } ]; + } + in + Function + { function_ with componentName = Some (getHookName ()) } + | _ -> type_ + in + (compType, None) | _ -> (type_, None) in @@ -240559,15 +240629,15 @@ let rec emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName in (envWithRequires, emitters) -and emitCodeItems ~config ~outputFileRelative ~emitters ~moduleItemsEmitter ~env - ~fileName ~resolver ~typeNameIsInterface ~typeGetConverter +let emitCodeItems ~config ~outputFileRelative ~emitters ~moduleItemsEmitter ~env + ~fileName ~resolver ~typeNameIsInterface ~typeGetConverter ~inlineOneLevel ~typeGetNormalized ~variantTables codeItems = codeItems |> List.fold_left (fun (env, emitters) -> emitCodeItem ~config ~emitters ~moduleItemsEmitter ~env ~fileName - ~outputFileRelative ~resolver ~typeGetConverter ~typeGetNormalized - ~typeNameIsInterface ~variantTables) + ~outputFileRelative ~resolver ~typeGetConverter ~inlineOneLevel + ~typeGetNormalized ~typeNameIsInterface ~variantTables) (env, emitters) let emitRequires ~importedValueOrComponent ~early ~config ~requires emitters = @@ -240837,12 +240907,12 @@ let emitTranslationAsString ~config ~fileName ~inputCmtTranslateTypeDeclarations try exportTypeMap |> StringMap.find s with Not_found -> env.exportTypeMapFromOtherFiles |> StringMap.find s in - let typeGetNormalized_ ~env type_ = + let typeGetNormalized__ ~inline ~env type_ = type_ - |> Converter.typeGetNormalized ~config ~inline:false - ~lookupId:(lookupId_ ~env) + |> Converter.typeGetNormalized ~config ~inline ~lookupId:(lookupId_ ~env) ~typeNameIsInterface:(typeNameIsInterface ~env) in + let typeGetNormalized_ = typeGetNormalized__ ~inline:false in let typeGetConverter_ ~env type_ = type_ |> Converter.typeGetConverter ~config ~lookupId:(lookupId_ ~env) @@ -240864,10 +240934,29 @@ let emitTranslationAsString ~config ~fileName ~inputCmtTranslateTypeDeclarations ~typeGetNormalized:(typeGetNormalized_ ~env) ~env ~typeNameIsInterface:(typeNameIsInterface ~env) in + let inlineOneLevel type_ = + match type_ with + | Ident { builtin = false; name; typeArgs } -> ( + match name |> lookupId_ ~env with + | { type_; typeVars } -> + let pairs = + try List.combine typeVars typeArgs with Invalid_argument _ -> [] + in + let f typeVar = + match + pairs |> List.find (fun (typeVar1, _) -> typeVar = typeVar1) + with + | _, typeArgument -> Some typeArgument + | exception Not_found -> None + in + type_ |> TypeVars.substitute ~f + | exception Not_found -> type_) + | _ -> type_ + in let env, emitters = translation.codeItems |> emitCodeItems ~config ~emitters ~moduleItemsEmitter ~env ~fileName - ~outputFileRelative ~resolver + ~outputFileRelative ~resolver ~inlineOneLevel ~typeGetNormalized:(typeGetNormalized_ ~env) ~typeGetConverter:(typeGetConverter_ ~env) ~typeNameIsInterface:(typeNameIsInterface ~env) ~variantTables