Skip to content

Commit

Permalink
Add support for basic unions for native modules (#46747)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #46747

You can now use unions in native modules.

Support for this pretty much existed, but one callsite was throwing for non-cpp modules which meant nobody could use this.

Previously, StructCollector would throw that this was not supported because it couldn't generate it for objc. Instead of generating something smart in the native code for each option, it just tells native to treat them like the base type (number, string, object).

Changelog: [General][Added] Codegen now supports Union Types in NativeModules

Reviewed By: GijsWeterings

Differential Revision: D63664505

fbshipit-source-id: 73278ed9cd64452173c5170aba44ced71181510f
  • Loading branch information
elicwhite authored and facebook-github-bot committed Oct 4, 2024
1 parent af3bee6 commit 3af126b
Show file tree
Hide file tree
Showing 13 changed files with 752 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,28 @@ class StructCollector {
case 'MixedTypeAnnotation':
throw new Error('Mixed types are unsupported in structs');
case 'UnionTypeAnnotation':
throw new Error('Union types are unsupported in structs');
switch (typeAnnotation.memberType) {
case 'StringTypeAnnotation':
return wrapNullable(nullable, {
type: 'StringTypeAnnotation',
});
case 'NumberTypeAnnotation':
return wrapNullable(nullable, {
type: 'NumberTypeAnnotation',
});
case 'ObjectTypeAnnotation':
// This isn't smart enough to actually know how to generate the
// options on the native side. So we just treat it as an unknown object type
return wrapNullable(nullable, {
type: 'GenericObjectTypeAnnotation',
});
default:
(typeAnnotation.memberType: empty);
throw new Error(
'Union types are unsupported in structs' +
JSON.stringify(typeAnnotation),
);
}
default: {
return wrapNullable(nullable, typeAnnotation);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2575,6 +2575,67 @@ const SAMPLE_WITH_UPPERCASE_NAME: SchemaType = {
},
};

const UNION_MODULE: SchemaType = {
modules: {
NativeSampleTurboModule: {
type: 'NativeModule',
aliasMap: {},
enumMap: {},
spec: {
eventEmitters: [],
methods: [
{
name: 'getUnion',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'UnionTypeAnnotation',
memberType: 'ObjectTypeAnnotation',
},
params: [
{
name: 'chooseInt',
optional: false,
typeAnnotation: {
type: 'UnionTypeAnnotation',
memberType: 'NumberTypeAnnotation',
},
},
{
name: 'chooseFloat',
optional: false,
typeAnnotation: {
type: 'UnionTypeAnnotation',
memberType: 'NumberTypeAnnotation',
},
},
{
name: 'chooseObject',
optional: false,
typeAnnotation: {
type: 'UnionTypeAnnotation',
memberType: 'ObjectTypeAnnotation',
},
},
{
name: 'chooseString',
optional: false,
typeAnnotation: {
type: 'UnionTypeAnnotation',
memberType: 'StringTypeAnnotation',
},
},
],
},
},
],
},
moduleName: 'SampleTurboModule',
},
},
};

module.exports = {
complex_objects: COMPLEX_OBJECTS,
two_modules_different_files: TWO_MODULES_DIFFERENT_FILES,
Expand All @@ -2585,4 +2646,5 @@ module.exports = {
real_module_example: REAL_MODULE_EXAMPLE,
cxx_only_native_modules: CXX_ONLY_NATIVE_MODULES,
SampleWithUppercaseName: SAMPLE_WITH_UPPERCASE_NAME,
union_module: UNION_MODULE,
};
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,42 @@ NativeSampleTurboModule2CxxSpecJSI::NativeSampleTurboModule2CxxSpecJSI(std::shar
}
} // namespace facebook::react
",
}
`;
exports[`GenerateModuleCpp can generate fixture union_module 1`] = `
Map {
"union_moduleJSI-generated.cpp" => "/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleCpp.js
*/
#include \\"union_moduleJSI.h\\"
namespace facebook::react {
static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getUnion(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
return static_cast<NativeSampleTurboModuleCxxSpecJSI *>(&turboModule)->getUnion(
rt,
count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asNumber(),
count <= 1 ? throw jsi::JSError(rt, \\"Expected argument in position 1 to be passed\\") : args[1].asNumber(),
count <= 2 ? throw jsi::JSError(rt, \\"Expected argument in position 2 to be passed\\") : args[2].asObject(rt),
count <= 3 ? throw jsi::JSError(rt, \\"Expected argument in position 3 to be passed\\") : args[3].asString(rt)
);
}
NativeSampleTurboModuleCxxSpecJSI::NativeSampleTurboModuleCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(\\"SampleTurboModule\\", jsInvoker) {
methodMap_[\\"getUnion\\"] = MethodMetadata {4, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getUnion};
}
} // namespace facebook::react
",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2412,3 +2412,80 @@ private:
",
}
`;

exports[`GenerateModuleH can generate fixture union_module 1`] = `
Map {
"union_moduleJSI.h" => "/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleH.js
*/

#pragma once

#include <ReactCommon/TurboModule.h>
#include <react/bridging/Bridging.h>

namespace facebook::react {


class JSI_EXPORT NativeSampleTurboModuleCxxSpecJSI : public TurboModule {
protected:
NativeSampleTurboModuleCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker);

public:
virtual jsi::Object getUnion(jsi::Runtime &rt, double chooseInt, double chooseFloat, jsi::Object chooseObject, jsi::String chooseString) = 0;

};

template <typename T>
class JSI_EXPORT NativeSampleTurboModuleCxxSpec : public TurboModule {
public:
jsi::Value create(jsi::Runtime &rt, const jsi::PropNameID &propName) override {
return delegate_.create(rt, propName);
}

std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& runtime) override {
return delegate_.getPropertyNames(runtime);
}

static constexpr std::string_view kModuleName = \\"SampleTurboModule\\";

protected:
NativeSampleTurboModuleCxxSpec(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(std::string{NativeSampleTurboModuleCxxSpec::kModuleName}, jsInvoker),
delegate_(reinterpret_cast<T*>(this), jsInvoker) {}


private:
class Delegate : public NativeSampleTurboModuleCxxSpecJSI {
public:
Delegate(T *instance, std::shared_ptr<CallInvoker> jsInvoker) :
NativeSampleTurboModuleCxxSpecJSI(std::move(jsInvoker)), instance_(instance) {

}

jsi::Object getUnion(jsi::Runtime &rt, double chooseInt, double chooseFloat, jsi::Object chooseObject, jsi::String chooseString) override {
static_assert(
bridging::getParameterCount(&T::getUnion) == 5,
\\"Expected getUnion(...) to have 5 parameters\\");

return bridging::callFromJs<jsi::Object>(
rt, &T::getUnion, jsInvoker_, instance_, std::move(chooseInt), std::move(chooseFloat), std::move(chooseObject), std::move(chooseString));
}

private:
friend class NativeSampleTurboModuleCxxSpec;
T *instance_;
};

Delegate delegate_;
};

} // namespace facebook::react
",
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -1186,3 +1186,71 @@ namespace facebook::react {
",
}
`;
exports[`GenerateModuleHObjCpp can generate fixture union_module 1`] = `
Map {
"union_module.h" => "/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleObjCpp
*
* We create an umbrella header (and corresponding implementation) here since
* Cxx compilation in BUCK has a limitation: source-code producing genrule()s
* must have a single output. More files => more genrule()s => slower builds.
*/
#ifndef __cplusplus
#error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm.
#endif
// Avoid multiple includes of union_module symbols
#ifndef union_module_H
#define union_module_H
#import <Foundation/Foundation.h>
#import <RCTRequired/RCTRequired.h>
#import <RCTTypeSafety/RCTConvertHelpers.h>
#import <RCTTypeSafety/RCTTypedModuleConstants.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTCxxConvert.h>
#import <React/RCTManagedPointer.h>
#import <ReactCommon/RCTTurboModule.h>
#import <optional>
#import <vector>
@protocol NativeSampleTurboModuleSpec <RCTBridgeModule, RCTTurboModule>
- (NSDictionary *)getUnion:(double)chooseInt
chooseFloat:(double)chooseFloat
chooseObject:(NSDictionary *)chooseObject
chooseString:(NSString *)chooseString;
@end
@interface NativeSampleTurboModuleSpecBase : NSObject {
@protected
facebook::react::EventEmitterCallback _eventEmitterCallback;
}
- (void)setEventEmitterCallback:(EventEmitterCallbackWrapper *)eventEmitterCallbackWrapper;
@end
namespace facebook::react {
/**
* ObjC++ class for module 'NativeSampleTurboModule'
*/
class JSI_EXPORT NativeSampleTurboModuleSpecJSI : public ObjCTurboModule {
public:
NativeSampleTurboModuleSpecJSI(const ObjCTurboModule::InitParams &params);
};
} // namespace facebook::react
#endif // union_module_H
",
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -591,3 +591,48 @@ public abstract class NativeSampleTurboModule2Spec extends ReactContextBaseJavaM
",
}
`;
exports[`GenerateModuleJavaSpec can generate fixture union_module 1`] = `
Map {
"java/com/facebook/fbreact/specs/NativeSampleTurboModuleSpec.java" => "
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleJavaSpec.js
*
* @nolint
*/
package com.facebook.fbreact.specs;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
import javax.annotation.Nonnull;
public abstract class NativeSampleTurboModuleSpec extends ReactContextBaseJavaModule implements TurboModule {
public static final String NAME = \\"SampleTurboModule\\";
public NativeSampleTurboModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public @Nonnull String getName() {
return NAME;
}
@ReactMethod(isBlockingSynchronousMethod = true)
@DoNotStrip
public abstract WritableMap getUnion(double chooseInt, double chooseFloat, ReadableMap chooseObject, String chooseString);
}
",
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,41 @@ std::shared_ptr<TurboModule> two_modules_different_files_ModuleProvider(const st
",
}
`;
exports[`GenerateModuleJniCpp can generate fixture union_module 1`] = `
Map {
"jni/union_module-generated.cpp" => "
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Do not edit this file as changes may cause incorrect behavior and will be lost
* once the code is regenerated.
*
* @generated by codegen project: GenerateModuleJniCpp.js
*/
#include \\"union_module.h\\"
namespace facebook::react {
static facebook::jsi::Value __hostFunction_NativeSampleTurboModuleSpecJSI_getUnion(facebook::jsi::Runtime& rt, TurboModule &turboModule, const facebook::jsi::Value* args, size_t count) {
static jmethodID cachedMethodId = nullptr;
return static_cast<JavaTurboModule &>(turboModule).invokeJavaMethod(rt, ObjectKind, \\"getUnion\\", \\"(DDLcom/facebook/react/bridge/ReadableMap;Ljava/lang/String;)Lcom/facebook/react/bridge/WritableMap;\\", args, count, cachedMethodId);
}
NativeSampleTurboModuleSpecJSI::NativeSampleTurboModuleSpecJSI(const JavaTurboModule::InitParams &params)
: JavaTurboModule(params) {
methodMap_[\\"getUnion\\"] = MethodMetadata {4, __hostFunction_NativeSampleTurboModuleSpecJSI_getUnion};
}
std::shared_ptr<TurboModule> union_module_ModuleProvider(const std::string &moduleName, const JavaTurboModule::InitParams &params) {
if (moduleName == \\"SampleTurboModule\\") {
return std::make_shared<NativeSampleTurboModuleSpecJSI>(params);
}
return nullptr;
}
} // namespace facebook::react
",
}
`;
Loading

0 comments on commit 3af126b

Please sign in to comment.