Skip to content

Commit 6e0fa5f

Browse files
appdenfacebook-github-bot
authored andcommitted
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
1 parent 370f1ca commit 6e0fa5f

5 files changed

Lines changed: 73 additions & 19 deletions

File tree

ReactCommon/react/bridging/Convert.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <jsi/jsi.h>
1111

12+
#include <optional>
1213
#include <type_traits>
1314

1415
namespace facebook::react::bridging {
@@ -114,11 +115,31 @@ struct Converter<jsi::Object> : public ConverterBase<jsi::Object> {
114115
}
115116
};
116117

118+
template <typename T>
119+
struct Converter<std::optional<T>> : public ConverterBase<jsi::Value> {
120+
Converter(jsi::Runtime &rt, std::optional<T> value)
121+
: ConverterBase(rt, value ? std::move(*value) : jsi::Value::null()) {}
122+
123+
operator std::optional<T>() && {
124+
if (value_.isNull() || value_.isUndefined()) {
125+
return {};
126+
}
127+
return std::move(value_);
128+
}
129+
};
130+
117131
template <typename T, std::enable_if_t<is_jsi_v<T>, int> = 0>
118132
auto convert(jsi::Runtime &rt, T &&value) {
119133
return Converter<T>(rt, std::forward<T>(value));
120134
}
121135

136+
template <
137+
typename T,
138+
std::enable_if_t<is_jsi_v<T> || std::is_scalar_v<T>, int> = 0>
139+
auto convert(jsi::Runtime &rt, std::optional<T> value) {
140+
return Converter<std::optional<T>>(rt, std::move(value));
141+
}
142+
122143
template <typename T, std::enable_if_t<std::is_scalar_v<T>, int> = 0>
123144
auto convert(jsi::Runtime &rt, T &&value) {
124145
return value;

ReactCommon/react/bridging/Value.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ struct Bridging<std::optional<T>> {
4141
return bridging::fromJs<T>(rt, value, jsInvoker);
4242
}
4343

44+
template <typename U>
45+
static std::optional<T> fromJs(
46+
jsi::Runtime &rt,
47+
const std::optional<U> &value,
48+
const std::shared_ptr<CallInvoker> &jsInvoker) {
49+
if (value) {
50+
return bridging::fromJs<T>(rt, *value, jsInvoker);
51+
}
52+
return {};
53+
}
54+
4455
static jsi::Value toJs(
4556
jsi::Runtime &rt,
4657
const std::optional<T> &value,

ReactCommon/react/bridging/tests/BridgingTest.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@ TEST_F(BridgingTest, promiseTest) {
273273
TEST_F(BridgingTest, optionalTest) {
274274
EXPECT_EQ(
275275
1, bridging::fromJs<std::optional<int>>(rt, jsi::Value(1), invoker));
276+
EXPECT_EQ(
277+
1,
278+
bridging::fromJs<std::optional<int>>(
279+
rt, std::make_optional(jsi::Value(1)), invoker));
280+
EXPECT_EQ(
281+
"hi"s,
282+
bridging::fromJs<std::optional<std::string>>(
283+
rt,
284+
std::make_optional(jsi::String::createFromAscii(rt, "hi")),
285+
invoker));
276286
EXPECT_FALSE(
277287
bridging::fromJs<std::optional<int>>(rt, jsi::Value::undefined(), invoker)
278288
.has_value());

packages/react-native-codegen/src/generators/modules/GenerateModuleCpp.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,26 @@ function serializeArg(
105105
index: number,
106106
resolveAlias: AliasResolver,
107107
): string {
108-
function wrap(suffix) {
109-
return `args[${index}]${suffix}`;
110-
}
111108
const {typeAnnotation: nullableTypeAnnotation} = arg;
112-
const [typeAnnotation] = unwrapNullable<NativeModuleParamTypeAnnotation>(
113-
nullableTypeAnnotation,
114-
);
109+
const [typeAnnotation, nullable] =
110+
unwrapNullable<NativeModuleParamTypeAnnotation>(nullableTypeAnnotation);
115111

116112
let realTypeAnnotation = typeAnnotation;
117113
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
118114
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
119115
}
120116

117+
function wrap(suffix) {
118+
const val = `args[${index}]`;
119+
const expression = `${val}${suffix}`;
120+
121+
if (nullable) {
122+
return `${val}.isNull() || ${val}.isUndefined() ? std::nullopt : std::make_optional(${expression})`;
123+
}
124+
125+
return expression;
126+
}
127+
121128
switch (realTypeAnnotation.type) {
122129
case 'ReservedTypeAnnotation':
123130
switch (realTypeAnnotation.name) {

packages/react-native-codegen/src/generators/modules/GenerateModuleH.js

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,47 +110,52 @@ function translatePrimitiveJSTypeToCpp(
110110
createErrorMessage: (typeName: string) => string,
111111
resolveAlias: AliasResolver,
112112
) {
113-
const [typeAnnotation] = unwrapNullable<NativeModuleTypeAnnotation>(
113+
const [typeAnnotation, nullable] = unwrapNullable<NativeModuleTypeAnnotation>(
114114
nullableTypeAnnotation,
115115
);
116+
116117
let realTypeAnnotation = typeAnnotation;
117118
if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
118119
realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
119120
}
120121

122+
function wrap(type) {
123+
return nullable ? `std::optional<${type}>` : type;
124+
}
125+
121126
switch (realTypeAnnotation.type) {
122127
case 'ReservedTypeAnnotation':
123128
switch (realTypeAnnotation.name) {
124129
case 'RootTag':
125-
return 'double';
130+
return wrap('double');
126131
default:
127132
(realTypeAnnotation.name: empty);
128133
throw new Error(createErrorMessage(realTypeAnnotation.name));
129134
}
130135
case 'VoidTypeAnnotation':
131136
return 'void';
132137
case 'StringTypeAnnotation':
133-
return 'jsi::String';
138+
return wrap('jsi::String');
134139
case 'NumberTypeAnnotation':
135-
return 'double';
140+
return wrap('double');
136141
case 'DoubleTypeAnnotation':
137-
return 'double';
142+
return wrap('double');
138143
case 'FloatTypeAnnotation':
139-
return 'double';
144+
return wrap('double');
140145
case 'Int32TypeAnnotation':
141-
return 'int';
146+
return wrap('int');
142147
case 'BooleanTypeAnnotation':
143-
return 'bool';
148+
return wrap('bool');
144149
case 'GenericObjectTypeAnnotation':
145-
return 'jsi::Object';
150+
return wrap('jsi::Object');
146151
case 'ObjectTypeAnnotation':
147-
return 'jsi::Object';
152+
return wrap('jsi::Object');
148153
case 'ArrayTypeAnnotation':
149-
return 'jsi::Array';
154+
return wrap('jsi::Array');
150155
case 'FunctionTypeAnnotation':
151-
return 'jsi::Function';
156+
return wrap('jsi::Function');
152157
case 'PromiseTypeAnnotation':
153-
return 'jsi::Value';
158+
return wrap('jsi::Value');
154159
default:
155160
(realTypeAnnotation.type: empty);
156161
throw new Error(createErrorMessage(realTypeAnnotation.type));

0 commit comments

Comments
 (0)