From 6e0fa5f15eef71abcfb47750eb3669065ba2ab7d Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Fri, 1 Apr 2022 16:58:52 -0700 Subject: [PATCH] Support optional types for C++ TurboModules Summary: Update C++ TurboModule codegen to wrap nullable types in `std::optional` whereas before the conversion would cause a crash. Changelog: Internal Reviewed By: mdvacca, nlutsenko Differential Revision: D35299708 fbshipit-source-id: 7daa50fe8b16879c5b3a55a633aa3f724dc5be30 --- ReactCommon/react/bridging/Convert.h | 21 +++++++++++++ ReactCommon/react/bridging/Value.h | 11 +++++++ .../react/bridging/tests/BridgingTest.cpp | 10 ++++++ .../generators/modules/GenerateModuleCpp.js | 19 ++++++++---- .../src/generators/modules/GenerateModuleH.js | 31 +++++++++++-------- 5 files changed, 73 insertions(+), 19 deletions(-) diff --git a/ReactCommon/react/bridging/Convert.h b/ReactCommon/react/bridging/Convert.h index df23f474d6a380..c11250a80d7093 100644 --- a/ReactCommon/react/bridging/Convert.h +++ b/ReactCommon/react/bridging/Convert.h @@ -9,6 +9,7 @@ #include +#include #include namespace facebook::react::bridging { @@ -114,11 +115,31 @@ struct Converter : public ConverterBase { } }; +template +struct Converter> : public ConverterBase { + Converter(jsi::Runtime &rt, std::optional value) + : ConverterBase(rt, value ? std::move(*value) : jsi::Value::null()) {} + + operator std::optional() && { + if (value_.isNull() || value_.isUndefined()) { + return {}; + } + return std::move(value_); + } +}; + template , int> = 0> auto convert(jsi::Runtime &rt, T &&value) { return Converter(rt, std::forward(value)); } +template < + typename T, + std::enable_if_t || std::is_scalar_v, int> = 0> +auto convert(jsi::Runtime &rt, std::optional value) { + return Converter>(rt, std::move(value)); +} + template , int> = 0> auto convert(jsi::Runtime &rt, T &&value) { return value; diff --git a/ReactCommon/react/bridging/Value.h b/ReactCommon/react/bridging/Value.h index 295565c1dd207e..07d949858397ff 100644 --- a/ReactCommon/react/bridging/Value.h +++ b/ReactCommon/react/bridging/Value.h @@ -41,6 +41,17 @@ struct Bridging> { return bridging::fromJs(rt, value, jsInvoker); } + template + static std::optional fromJs( + jsi::Runtime &rt, + const std::optional &value, + const std::shared_ptr &jsInvoker) { + if (value) { + return bridging::fromJs(rt, *value, jsInvoker); + } + return {}; + } + static jsi::Value toJs( jsi::Runtime &rt, const std::optional &value, diff --git a/ReactCommon/react/bridging/tests/BridgingTest.cpp b/ReactCommon/react/bridging/tests/BridgingTest.cpp index 6ff769a2ddd883..f213ba5c0d516d 100644 --- a/ReactCommon/react/bridging/tests/BridgingTest.cpp +++ b/ReactCommon/react/bridging/tests/BridgingTest.cpp @@ -273,6 +273,16 @@ TEST_F(BridgingTest, promiseTest) { TEST_F(BridgingTest, optionalTest) { EXPECT_EQ( 1, bridging::fromJs>(rt, jsi::Value(1), invoker)); + EXPECT_EQ( + 1, + bridging::fromJs>( + rt, std::make_optional(jsi::Value(1)), invoker)); + EXPECT_EQ( + "hi"s, + bridging::fromJs>( + rt, + std::make_optional(jsi::String::createFromAscii(rt, "hi")), + invoker)); EXPECT_FALSE( bridging::fromJs>(rt, jsi::Value::undefined(), invoker) .has_value()); diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js index ec1d92309e8cf4..2e6da494e43117 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js @@ -105,19 +105,26 @@ function serializeArg( index: number, resolveAlias: AliasResolver, ): string { - function wrap(suffix) { - return `args[${index}]${suffix}`; - } const {typeAnnotation: nullableTypeAnnotation} = arg; - const [typeAnnotation] = unwrapNullable( - nullableTypeAnnotation, - ); + const [typeAnnotation, nullable] = + unwrapNullable(nullableTypeAnnotation); let realTypeAnnotation = typeAnnotation; if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') { realTypeAnnotation = resolveAlias(realTypeAnnotation.name); } + function wrap(suffix) { + const val = `args[${index}]`; + const expression = `${val}${suffix}`; + + if (nullable) { + return `${val}.isNull() || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`; + } + + return expression; + } + switch (realTypeAnnotation.type) { case 'ReservedTypeAnnotation': switch (realTypeAnnotation.name) { diff --git a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js index 4ef9cbe34e9cf7..17c4f927118b59 100644 --- a/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js +++ b/packages/react-native-codegen/src/generators/modules/GenerateModuleH.js @@ -110,19 +110,24 @@ function translatePrimitiveJSTypeToCpp( createErrorMessage: (typeName: string) => string, resolveAlias: AliasResolver, ) { - const [typeAnnotation] = unwrapNullable( + const [typeAnnotation, nullable] = unwrapNullable( nullableTypeAnnotation, ); + let realTypeAnnotation = typeAnnotation; if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') { realTypeAnnotation = resolveAlias(realTypeAnnotation.name); } + function wrap(type) { + return nullable ? `std::optional<${type}>` : type; + } + switch (realTypeAnnotation.type) { case 'ReservedTypeAnnotation': switch (realTypeAnnotation.name) { case 'RootTag': - return 'double'; + return wrap('double'); default: (realTypeAnnotation.name: empty); throw new Error(createErrorMessage(realTypeAnnotation.name)); @@ -130,27 +135,27 @@ function translatePrimitiveJSTypeToCpp( case 'VoidTypeAnnotation': return 'void'; case 'StringTypeAnnotation': - return 'jsi::String'; + return wrap('jsi::String'); case 'NumberTypeAnnotation': - return 'double'; + return wrap('double'); case 'DoubleTypeAnnotation': - return 'double'; + return wrap('double'); case 'FloatTypeAnnotation': - return 'double'; + return wrap('double'); case 'Int32TypeAnnotation': - return 'int'; + return wrap('int'); case 'BooleanTypeAnnotation': - return 'bool'; + return wrap('bool'); case 'GenericObjectTypeAnnotation': - return 'jsi::Object'; + return wrap('jsi::Object'); case 'ObjectTypeAnnotation': - return 'jsi::Object'; + return wrap('jsi::Object'); case 'ArrayTypeAnnotation': - return 'jsi::Array'; + return wrap('jsi::Array'); case 'FunctionTypeAnnotation': - return 'jsi::Function'; + return wrap('jsi::Function'); case 'PromiseTypeAnnotation': - return 'jsi::Value'; + return wrap('jsi::Value'); default: (realTypeAnnotation.type: empty); throw new Error(createErrorMessage(realTypeAnnotation.type));