diff --git a/change/@react-native-windows-codegen-8977b235-04fe-4aff-a06e-c6b3f481de6e.json b/change/@react-native-windows-codegen-8977b235-04fe-4aff-a06e-c6b3f481de6e.json new file mode 100644 index 00000000000..c1fe60afbeb --- /dev/null +++ b/change/@react-native-windows-codegen-8977b235-04fe-4aff-a06e-c6b3f481de6e.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "forking GenerateModuleCpp", + "packageName": "@react-native-windows/codegen", + "email": "66076509+vineethkuttan@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/react-native-windows-83578d60-30a3-4822-a835-4f85d5359b12.json b/change/react-native-windows-83578d60-30a3-4822-a835-4f85d5359b12.json new file mode 100644 index 00000000000..5731798d676 --- /dev/null +++ b/change/react-native-windows-83578d60-30a3-4822-a835-4f85d5359b12.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "forking GenerateModuleCpp", + "packageName": "react-native-windows", + "email": "66076509+vineethkuttan@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/packages/@react-native-windows/codegen/src/generators/GenerateModuleCpp.js b/packages/@react-native-windows/codegen/src/generators/GenerateModuleCpp.js new file mode 100644 index 00000000000..97e63c85c3c --- /dev/null +++ b/packages/@react-native-windows/codegen/src/generators/GenerateModuleCpp.js @@ -0,0 +1,245 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * + * @format + */ + +'use strict'; + +const path = require('path'); + +// Load dependencies from @react-native/codegen +const rnPath = path.dirname(require.resolve('react-native/package.json')); +const rncodegenPath = path.dirname( + require.resolve('@react-native/codegen/package.json', {paths: [rnPath]}), +); + +const {unwrapNullable} = require( + path.resolve(rncodegenPath, 'lib/parsers/parsers-commons'), +); +const {createAliasResolver, getModules} = require( + path.resolve(rncodegenPath, 'lib/generators/modules/Utils'), +); +const HostFunctionTemplate = ({ + hasteModuleName, + methodName, + returnTypeAnnotation, + args, +}) => { + const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation'; + const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation'; + const methodCallArgs = [' rt', ...args].join(',\n '); + const methodCall = `static_cast<${hasteModuleName}CxxSpecJSI *>(&turboModule)->${methodName}(\n${methodCallArgs}\n )`; + return `static jsi::Value __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {${isVoid ? `\n ${methodCall};` : isNullable ? `\n auto result = ${methodCall};` : ''} + return ${isVoid ? 'jsi::Value::undefined()' : isNullable ? 'result ? jsi::Value(std::move(*result)) : jsi::Value::null()' : methodCall}; +}`; +}; +const ModuleTemplate = ({ + hasteModuleName, + hostFunctions, + moduleName, + methods, +}) => { + return `${hostFunctions.join('\n')} + +${hasteModuleName}CxxSpecJSI::${hasteModuleName}CxxSpecJSI(std::shared_ptr jsInvoker) + : TurboModule("${moduleName}", jsInvoker) { +${methods + .map(({methodName, paramCount}) => { + return ` methodMap_["${methodName}"] = MethodMetadata {${paramCount}, __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}};`; + }) + .join('\n')} +}`; +}; +const FileTemplate = ({libraryName, modules}) => { + return `/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * ${'@'}generated by codegen project: GenerateModuleCpp.js + */ + +#include "${libraryName}JSI.h" + +namespace facebook::react { + +${modules} + + +} // namespace facebook::react +`; +}; +function serializeArg(moduleName, arg, index, resolveAlias, enumMap) { + const {typeAnnotation: nullableTypeAnnotation, optional} = arg; + const [typeAnnotation, nullable] = unwrapNullable(nullableTypeAnnotation); + let realTypeAnnotation = typeAnnotation; + if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') { + realTypeAnnotation = resolveAlias(realTypeAnnotation.name); + } + function wrap(callback) { + const val = `args[${index}]`; + const expression = callback(val); + + // param?: T + if (optional && !nullable) { + // throw new Error('are we hitting this case? ' + moduleName); + return `count <= ${index} || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`; + } + + // param: ?T + // param?: ?T + if (nullable || optional) { + return `count <= ${index} || ${val}.isNull() || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`; + } + + // param: T + //return `count <= ${index} ? throw jsi::JSError(rt, "Expected argument in position ${index} to be passed") : ${expression}`; + //Windows #15545 + return `(count > ${index} || (throw jsi::JSError(rt, "Expected argument in position ${index} to be passed"), false), ${expression})`; + //Windows + } + switch (realTypeAnnotation.type) { + case 'ReservedTypeAnnotation': + switch (realTypeAnnotation.name) { + case 'RootTag': + return wrap(val => `${val}.asNumber()`); + default: + realTypeAnnotation.name; + throw new Error( + `Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`, + ); + } + case 'StringTypeAnnotation': + return wrap(val => `${val}.asString(rt)`); + case 'StringLiteralTypeAnnotation': + return wrap(val => `${val}.asString(rt)`); + case 'StringLiteralUnionTypeAnnotation': + return wrap(val => `${val}.asString(rt)`); + case 'BooleanTypeAnnotation': + return wrap(val => `${val}.asBool()`); + case 'EnumDeclaration': + switch (realTypeAnnotation.memberType) { + case 'NumberTypeAnnotation': + return wrap(val => `${val}.asNumber()`); + case 'StringTypeAnnotation': + return wrap(val => `${val}.asString(rt)`); + default: + throw new Error( + `Unknown enum type for "${arg.name}, found: ${realTypeAnnotation.type}"`, + ); + } + case 'NumberTypeAnnotation': + return wrap(val => `${val}.asNumber()`); + case 'FloatTypeAnnotation': + return wrap(val => `${val}.asNumber()`); + case 'DoubleTypeAnnotation': + return wrap(val => `${val}.asNumber()`); + case 'Int32TypeAnnotation': + return wrap(val => `${val}.asNumber()`); + case 'NumberLiteralTypeAnnotation': + return wrap(val => `${val}.asNumber()`); + case 'ArrayTypeAnnotation': + return wrap(val => `${val}.asObject(rt).asArray(rt)`); + case 'FunctionTypeAnnotation': + return wrap(val => `${val}.asObject(rt).asFunction(rt)`); + case 'GenericObjectTypeAnnotation': + return wrap(val => `${val}.asObject(rt)`); + case 'UnionTypeAnnotation': + switch (typeAnnotation.memberType) { + case 'NumberTypeAnnotation': + return wrap(val => `${val}.asNumber()`); + case 'ObjectTypeAnnotation': + return wrap(val => `${val}.asObject(rt)`); + case 'StringTypeAnnotation': + return wrap(val => `${val}.asString(rt)`); + default: + throw new Error( + `Unsupported union member type for param "${arg.name}, found: ${realTypeAnnotation.memberType}"`, + ); + } + case 'ObjectTypeAnnotation': + return wrap(val => `${val}.asObject(rt)`); + case 'MixedTypeAnnotation': + return wrap(val => `jsi::Value(rt, ${val})`); + default: + realTypeAnnotation.type; + throw new Error( + `Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`, + ); + } +} +function serializePropertyIntoHostFunction( + moduleName, + hasteModuleName, + property, + resolveAlias, + enumMap, +) { + const [propertyTypeAnnotation] = unwrapNullable(property.typeAnnotation); + return HostFunctionTemplate({ + hasteModuleName, + methodName: property.name, + returnTypeAnnotation: propertyTypeAnnotation.returnTypeAnnotation, + args: propertyTypeAnnotation.params.map((p, i) => + serializeArg(moduleName, p, i, resolveAlias, enumMap), + ), + }); +} +module.exports = { + generate( + libraryName, + schema, + packageName, + assumeNonnull = false, + headerPrefix, + ) { + const nativeModules = getModules(schema); + const modules = Object.keys(nativeModules) + .map(hasteModuleName => { + const nativeModule = nativeModules[hasteModuleName]; + const { + aliasMap, + enumMap, + spec: {methods}, + moduleName, + } = nativeModule; + const resolveAlias = createAliasResolver(aliasMap); + const hostFunctions = methods.map(property => + serializePropertyIntoHostFunction( + moduleName, + hasteModuleName, + property, + resolveAlias, + enumMap, + ), + ); + return ModuleTemplate({ + hasteModuleName, + hostFunctions, + moduleName, + methods: methods.map( + ({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => { + const [{params}] = unwrapNullable(nullableTypeAnnotation); + return { + methodName: propertyName, + paramCount: params.length, + }; + }, + ), + }); + }) + .join('\n'); + const fileName = `${libraryName}JSI-generated.cpp`; + const replacedTemplate = FileTemplate({ + modules, + libraryName, + }); + return new Map([[fileName, replacedTemplate]]); + }, +}; diff --git a/packages/@react-native-windows/codegen/src/index.ts b/packages/@react-native-windows/codegen/src/index.ts index 507732b8eac..b380d00db91 100644 --- a/packages/@react-native-windows/codegen/src/index.ts +++ b/packages/@react-native-windows/codegen/src/index.ts @@ -254,10 +254,10 @@ export function generate( rncodegenPath, 'lib/generators/modules/GenerateModuleH', )).generate; - const generateJsiModuleCpp = require(path.resolve( - rncodegenPath, - 'lib/generators/modules/GenerateModuleCpp', - )).generate; + // Use local fixed version instead of upstream to fix x86 union padding issue + // with MixedTypeAnnotation. See GenerateModuleCpp.js for details. + const generateJsiModuleCpp = + require('./generators/GenerateModuleCpp').generate; const generatorPropsH = require(path.resolve( rncodegenPath, 'lib/generators/components/GeneratePropsH', diff --git a/packages/@react-native-windows/codegen/tsconfig.json b/packages/@react-native-windows/codegen/tsconfig.json index c62faa78baf..acd6f9e9cdd 100644 --- a/packages/@react-native-windows/codegen/tsconfig.json +++ b/packages/@react-native-windows/codegen/tsconfig.json @@ -1,5 +1,12 @@ { "extends": "@rnw-scripts/ts-config", - "include": ["src"], - "exclude": ["node_modules"] -} + "compilerOptions": { + "allowJs": true + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/codegen/msrnIntegrationTestsJSI-generated.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/codegen/msrnIntegrationTestsJSI-generated.cpp index f0e68494f24..ba16c78b52f 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/codegen/msrnIntegrationTestsJSI-generated.cpp +++ b/vnext/Microsoft.ReactNative.IntegrationTests/codegen/msrnIntegrationTestsJSI-generated.cpp @@ -19,8 +19,8 @@ static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getConst static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_logAction(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { static_cast(&turboModule)->logAction( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt), - count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : jsi::Value(rt, args[1]) + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asString(rt)), + (count > 1 || (throw jsi::JSError(rt, "Expected argument in position 1 to be passed"), false), jsi::Value(rt, args[1])) ); return jsi::Value::undefined(); } @@ -33,52 +33,52 @@ static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_voidFunc static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getBool(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getBool( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asBool() + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asBool()) ); } static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getNumber(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getNumber( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asNumber() + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asNumber()) ); } static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getString(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getString( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asString(rt) + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asString(rt)) ); } static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getArray(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getArray( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt).asArray(rt) + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asObject(rt).asArray(rt)) ); } static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getObject(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getObject( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt) + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asObject(rt)) ); } static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getValue(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getValue( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asNumber(), - count <= 1 ? throw jsi::JSError(rt, "Expected argument in position 1 to be passed") : args[1].asString(rt), - count <= 2 ? throw jsi::JSError(rt, "Expected argument in position 2 to be passed") : args[2].asObject(rt) + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asNumber()), + (count > 1 || (throw jsi::JSError(rt, "Expected argument in position 1 to be passed"), false), args[1].asString(rt)), + (count > 2 || (throw jsi::JSError(rt, "Expected argument in position 2 to be passed"), false), args[2].asObject(rt)) ); } static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getValueWithCallback(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { static_cast(&turboModule)->getValueWithCallback( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asObject(rt).asFunction(rt) + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asObject(rt).asFunction(rt)) ); return jsi::Value::undefined(); } static jsi::Value __hostFunction_NativeMySimpleTurboModuleCxxCxxSpecJSI_getValueWithPromise(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) { return static_cast(&turboModule)->getValueWithPromise( rt, - count <= 0 ? throw jsi::JSError(rt, "Expected argument in position 0 to be passed") : args[0].asBool() + (count > 0 || (throw jsi::JSError(rt, "Expected argument in position 0 to be passed"), false), args[0].asBool()) ); }