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
This commit is contained in:
Scott Kyle
2022-04-01 16:58:52 -07:00
committed by Facebook GitHub Bot
parent 370f1ca0cd
commit 6e0fa5f15e
5 changed files with 73 additions and 19 deletions
+21
View File
@@ -9,6 +9,7 @@
#include <jsi/jsi.h>
#include <optional>
#include <type_traits>
namespace facebook::react::bridging {
@@ -114,11 +115,31 @@ struct Converter<jsi::Object> : public ConverterBase<jsi::Object> {
}
};
template <typename T>
struct Converter<std::optional<T>> : public ConverterBase<jsi::Value> {
Converter(jsi::Runtime &rt, std::optional<T> value)
: ConverterBase(rt, value ? std::move(*value) : jsi::Value::null()) {}
operator std::optional<T>() && {
if (value_.isNull() || value_.isUndefined()) {
return {};
}
return std::move(value_);
}
};
template <typename T, std::enable_if_t<is_jsi_v<T>, int> = 0>
auto convert(jsi::Runtime &rt, T &&value) {
return Converter<T>(rt, std::forward<T>(value));
}
template <
typename T,
std::enable_if_t<is_jsi_v<T> || std::is_scalar_v<T>, int> = 0>
auto convert(jsi::Runtime &rt, std::optional<T> value) {
return Converter<std::optional<T>>(rt, std::move(value));
}
template <typename T, std::enable_if_t<std::is_scalar_v<T>, int> = 0>
auto convert(jsi::Runtime &rt, T &&value) {
return value;
+11
View File
@@ -41,6 +41,17 @@ struct Bridging<std::optional<T>> {
return bridging::fromJs<T>(rt, value, jsInvoker);
}
template <typename U>
static std::optional<T> fromJs(
jsi::Runtime &rt,
const std::optional<U> &value,
const std::shared_ptr<CallInvoker> &jsInvoker) {
if (value) {
return bridging::fromJs<T>(rt, *value, jsInvoker);
}
return {};
}
static jsi::Value toJs(
jsi::Runtime &rt,
const std::optional<T> &value,
@@ -273,6 +273,16 @@ TEST_F(BridgingTest, promiseTest) {
TEST_F(BridgingTest, optionalTest) {
EXPECT_EQ(
1, bridging::fromJs<std::optional<int>>(rt, jsi::Value(1), invoker));
EXPECT_EQ(
1,
bridging::fromJs<std::optional<int>>(
rt, std::make_optional(jsi::Value(1)), invoker));
EXPECT_EQ(
"hi"s,
bridging::fromJs<std::optional<std::string>>(
rt,
std::make_optional(jsi::String::createFromAscii(rt, "hi")),
invoker));
EXPECT_FALSE(
bridging::fromJs<std::optional<int>>(rt, jsi::Value::undefined(), invoker)
.has_value());
@@ -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<NativeModuleParamTypeAnnotation>(
nullableTypeAnnotation,
);
const [typeAnnotation, nullable] =
unwrapNullable<NativeModuleParamTypeAnnotation>(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) {
@@ -110,19 +110,24 @@ function translatePrimitiveJSTypeToCpp(
createErrorMessage: (typeName: string) => string,
resolveAlias: AliasResolver,
) {
const [typeAnnotation] = unwrapNullable<NativeModuleTypeAnnotation>(
const [typeAnnotation, nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
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));