Skip to content

Commit

Permalink
Add supportsFromJs and supportsToJs template variables
Browse files Browse the repository at this point in the history
Summary:
These `constexpr` template variables make it really easy to test for bridging conversion to/from the specified types. The unit tests for this actually uncovered a bug with incompatible casts from lvalue references that was fixed in this diff as well.

Changelog:
Internal

Reviewed By: christophpurrer

Differential Revision: D35105398

fbshipit-source-id: 6e5f16e44ba99b296284970bf32c1f2f47201391
  • Loading branch information
appden authored and facebook-github-bot committed Mar 30, 2022
1 parent 57a90f7 commit 087624c
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 26 deletions.
30 changes: 30 additions & 0 deletions ReactCommon/react/bridging/Base.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,35 @@ auto toJs(
return Bridging<bridging_t<T>>::toJs(rt, std::forward<T>(value), jsInvoker);
}

template <typename, typename = jsi::Value, typename = void>
inline constexpr bool supportsFromJs = false;

template <typename T, typename Arg = jsi::Value>
inline constexpr bool supportsFromJs<
T,
Arg,
std::void_t<decltype(fromJs<T>(
std::declval<jsi::Runtime &>(),
std::declval<Arg>(),
nullptr))>> = true;

template <typename, typename = jsi::Value, typename = void>
inline constexpr bool supportsToJs = false;

template <typename T, typename Ret = jsi::Value>
inline constexpr bool supportsToJs<
T,
Ret,
std::void_t<decltype(toJs(
std::declval<jsi::Runtime &>(),
std::declval<T>(),
nullptr))>> =
std::is_convertible_v<
decltype(toJs(
std::declval<jsi::Runtime &>(),
std::declval<T>(),
nullptr)),
Ret>;

} // namespace bridging
} // namespace facebook::react
6 changes: 6 additions & 0 deletions ReactCommon/react/bridging/Class.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ T callFromJs(
JSArgs &&...args) {
static_assert(
sizeof...(Args) == sizeof...(JSArgs), "Incorrect arguments length");
static_assert(
(supportsFromJs<Args, JSArgs> && ...), "Incompatible arguments");

if constexpr (std::is_void_v<T>) {
(instance->*method)(
Expand All @@ -40,13 +42,17 @@ T callFromJs(
return jsi::Value();

} else if constexpr (is_jsi_v<T>) {
static_assert(supportsToJs<R, T>, "Incompatible return type");

return toJs(
rt,
(instance->*method)(
rt, fromJs<Args>(rt, std::forward<JSArgs>(args), jsInvoker)...),
jsInvoker);

} else {
static_assert(std::is_convertible_v<R, T>, "Incompatible return type");

return (instance->*method)(
rt, fromJs<Args>(rt, std::forward<JSArgs>(args), jsInvoker)...);
}
Expand Down
66 changes: 40 additions & 26 deletions ReactCommon/react/bridging/Convert.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,53 @@ inline constexpr bool is_jsi_v =
std::is_same_v<jsi::String, remove_cvref_t<T>> ||
std::is_base_of_v<jsi::Object, remove_cvref_t<T>>;

template <typename T>
struct Converter;

template <typename T>
struct ConverterBase {
using BaseT = remove_cvref_t<T>;

ConverterBase(jsi::Runtime &rt, T &&value)
: rt_(rt), value_(std::forward<T>(value)) {}

operator T() && {
return std::forward<T>(this->value_);
operator BaseT() && {
if constexpr (std::is_lvalue_reference_v<T>) {
// Copy the reference into a Value that then can be moved from.
auto value = jsi::Value(rt_, value_);

if constexpr (std::is_same_v<BaseT, jsi::Value>) {
return std::move(value);
} else if constexpr (std::is_same_v<BaseT, jsi::String>) {
return std::move(value).getString(rt_);
} else if constexpr (std::is_same_v<BaseT, jsi::Object>) {
return std::move(value).getObject(rt_);
} else if constexpr (std::is_same_v<BaseT, jsi::Array>) {
return std::move(value).getObject(rt_).getArray(rt_);
} else if constexpr (std::is_same_v<BaseT, jsi::Function>) {
return std::move(value).getObject(rt_).getFunction(rt_);
}
} else {
return std::move(value_);
}
}

template <
typename U,
std::enable_if_t<
std::is_lvalue_reference_v<T> &&
// Ensure non-reference type can be converted to the desired type.
std::is_convertible_v<Converter<BaseT>, U>,
int> = 0>
operator U() && {
return Converter<BaseT>(rt_, std::move(*this).operator BaseT());
}

template <
typename U,
std::enable_if_t<is_jsi_v<T> && std::is_same_v<U, jsi::Value>, int> = 0>
operator U() && = delete; // Prevent unwanted upcasting of JSI values.

protected:
jsi::Runtime &rt_;
T value_;
Expand Down Expand Up @@ -76,30 +114,6 @@ struct Converter<jsi::Object> : public ConverterBase<jsi::Object> {
}
};

template <typename T>
struct Converter<T &> {
Converter(jsi::Runtime &rt, T &value) : rt_(rt), value_(value) {}

operator T() && {
// Copy the reference into a Value that then can be moved from.
return Converter<jsi::Value>(rt_, jsi::Value(rt_, value_));
}

template <
typename U,
// Ensure the non-reference type can be converted to the desired type.
std::enable_if_t<
std::is_convertible_v<Converter<std::remove_cv_t<T>>, U>,
int> = 0>
operator U() && {
return Converter<jsi::Value>(rt_, jsi::Value(rt_, value_));
}

private:
jsi::Runtime &rt_;
const T &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));
Expand Down
95 changes: 95 additions & 0 deletions ReactCommon/react/bridging/tests/BridgingTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,99 @@ TEST_F(BridgingTest, pointerTest) {
EXPECT_TRUE(bridging::toJs(rt, weak, invoker).isNull());
}

TEST_F(BridgingTest, supportTest) {
// Ensure sure can convert some basic types, including primitives that can be
// trivially converted to JSI values.
EXPECT_TRUE((bridging::supportsFromJs<bool>));
EXPECT_TRUE((bridging::supportsFromJs<bool, bool>));
EXPECT_TRUE((bridging::supportsFromJs<bool, jsi::Value &>));
EXPECT_TRUE((bridging::supportsFromJs<int>));
EXPECT_TRUE((bridging::supportsFromJs<int, int>));
EXPECT_TRUE((bridging::supportsFromJs<int, jsi::Value &>));
EXPECT_TRUE((bridging::supportsFromJs<double>));
EXPECT_TRUE((bridging::supportsFromJs<double, double>));
EXPECT_TRUE((bridging::supportsFromJs<double, jsi::Value &>));
EXPECT_TRUE((bridging::supportsFromJs<std::string>));
EXPECT_TRUE((bridging::supportsFromJs<std::string, jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<std::string, jsi::String &>));
EXPECT_TRUE((bridging::supportsFromJs<std::vector<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<std::vector<int>, jsi::Array &>));
EXPECT_TRUE(
(bridging::supportsFromJs<std::map<std::string, int>, jsi::Object>));
EXPECT_TRUE(
(bridging::supportsFromJs<std::map<std::string, int>, jsi::Object &>));

// Ensure incompatible conversions will fail.
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::String &>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::String &>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::String &>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<bool, jsi::Object &>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<int, jsi::Object &>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<double, jsi::Object &>));
EXPECT_FALSE((bridging::supportsFromJs<std::string, jsi::Object>));
EXPECT_FALSE((bridging::supportsFromJs<std::string, jsi::Object &>));
EXPECT_FALSE((bridging::supportsFromJs<std::vector<int>, jsi::String>));
EXPECT_FALSE((bridging::supportsFromJs<std::vector<int>, jsi::String &>));

// Ensure copying and up/down casting JSI values is also supported.
EXPECT_TRUE((bridging::supportsFromJs<jsi::Value>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Value, jsi::Value &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Value, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Value, jsi::Object &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String, jsi::String>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::String, jsi::String &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Object &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Array &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Object, jsi::Function &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Array>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Array &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Array, jsi::Object &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Function>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Function &>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Object>));
EXPECT_TRUE((bridging::supportsFromJs<jsi::Function, jsi::Object &>));

// Ensure incorrect casts will fail.
EXPECT_FALSE((bridging::supportsFromJs<jsi::Array, jsi::Function>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Array, jsi::Function &>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Function, jsi::Array>));
EXPECT_FALSE((bridging::supportsFromJs<jsi::Function, jsi::Array &>));

// Ensure we can convert some basic types to JSI values.
EXPECT_TRUE((bridging::supportsToJs<bool>));
EXPECT_TRUE((bridging::supportsToJs<int>));
EXPECT_TRUE((bridging::supportsToJs<double>));
EXPECT_TRUE((bridging::supportsToJs<std::string>));
EXPECT_TRUE((bridging::supportsToJs<std::string, jsi::String>));
EXPECT_TRUE((bridging::supportsToJs<std::vector<int>>));
EXPECT_TRUE((bridging::supportsToJs<std::vector<int>, jsi::Array>));
EXPECT_TRUE((bridging::supportsToJs<std::map<std::string, int>>));
EXPECT_TRUE(
(bridging::supportsToJs<std::map<std::string, int>, jsi::Object>));
EXPECT_TRUE((bridging::supportsToJs<void (*)()>));
EXPECT_TRUE((bridging::supportsToJs<void (*)(), jsi::Function>));

// Ensure invalid conversions to JSI values are not supported.
EXPECT_FALSE((bridging::supportsToJs<void *>));
EXPECT_FALSE((bridging::supportsToJs<bool, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<int, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<double, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<std::string, jsi::Object>));
EXPECT_FALSE((bridging::supportsToJs<std::vector<int>, jsi::Function>));
}

} // namespace facebook::react

0 comments on commit 087624c

Please sign in to comment.