Skip to content

Commit

Permalink
Support optional types for C++ TurboModules
Browse files Browse the repository at this point in the history
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
  • Loading branch information
appden authored and facebook-github-bot committed Apr 1, 2022
1 parent 370f1ca commit 6e0fa5f
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 19 deletions.
21 changes: 21 additions & 0 deletions ReactCommon/react/bridging/Convert.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <jsi/jsi.h>

#include <optional>
#include <type_traits>

namespace facebook::react::bridging {
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions ReactCommon/react/bridging/Value.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions ReactCommon/react/bridging/tests/BridgingTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,47 +110,52 @@ 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));
}
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));
Expand Down

0 comments on commit 6e0fa5f

Please sign in to comment.