Skip to content

Commit

Permalink
Add support for StringLiteralTypeAnnotation (#46827)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #46827

You can now define a native module that takes an argument that is a specific string literal:

Like this:
```
export interface Spec extends TurboModule {
  +passString: (arg: string) => void;
  +passStringLiteral: (arg: 'A String Literal') => void;
}
```

On the native side, this will still generate `string`, but the schema will now store the string literal, and it will be allowed in JS. This should allow more strict flow / typescript types for modules.

This will also allow the compatibility check to fail if the literal changes.

This is a step towards accepting a union of string literals.

Changelog: [General][Added] Codegen for Native Modules now supports string literals

Reviewed By: GijsWeterings

Differential Revision: D63872440

fbshipit-source-id: e54b97d34af4a3d1af51727db0777f26fb7b778c
  • Loading branch information
elicwhite authored and facebook-github-bot committed Oct 7, 2024
1 parent 73ce198 commit d2f3f06
Show file tree
Hide file tree
Showing 26 changed files with 593 additions and 7 deletions.
7 changes: 7 additions & 0 deletions packages/react-native-codegen/src/CodegenSchema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ export interface NativeModuleStringTypeAnnotation {
readonly type: 'StringTypeAnnotation';
}

export interface NativeModuleStringLiteralTypeAnnotation {
readonly type: 'StringLiteralTypeAnnotation';
readonly value: string;
}

export interface NativeModuleNumberTypeAnnotation {
readonly type: 'NumberTypeAnnotation';
}
Expand Down Expand Up @@ -371,6 +376,7 @@ export type NativeModuleEventEmitterBaseTypeAnnotation =
| NativeModuleInt32TypeAnnotation
| NativeModuleNumberTypeAnnotation
| NativeModuleStringTypeAnnotation
| NativeModuleStringLiteralTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleGenericObjectTypeAnnotation
| VoidTypeAnnotation;
Expand All @@ -384,6 +390,7 @@ export type NativeModuleEventEmitterTypeAnnotation =

export type NativeModuleBaseTypeAnnotation =
| NativeModuleStringTypeAnnotation
| NativeModuleStringLiteralTypeAnnotation
| NativeModuleNumberTypeAnnotation
| NativeModuleInt32TypeAnnotation
| NativeModuleDoubleTypeAnnotation
Expand Down
7 changes: 7 additions & 0 deletions packages/react-native-codegen/src/CodegenSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export type StringTypeAnnotation = $ReadOnly<{
type: 'StringTypeAnnotation',
}>;

export type StringLiteralTypeAnnotation = $ReadOnly<{
type: 'StringLiteralTypeAnnotation',
value: string,
}>;

export type StringEnumTypeAnnotation = $ReadOnly<{
type: 'StringEnumTypeAnnotation',
options: $ReadOnlyArray<string>,
Expand Down Expand Up @@ -357,6 +362,7 @@ type NativeModuleEventEmitterBaseTypeAnnotation =
| Int32TypeAnnotation
| NativeModuleNumberTypeAnnotation
| StringTypeAnnotation
| StringLiteralTypeAnnotation
| NativeModuleTypeAliasTypeAnnotation
| NativeModuleGenericObjectTypeAnnotation
| VoidTypeAnnotation;
Expand All @@ -370,6 +376,7 @@ export type NativeModuleEventEmitterTypeAnnotation =

export type NativeModuleBaseTypeAnnotation =
| StringTypeAnnotation
| StringLiteralTypeAnnotation
| NativeModuleNumberTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ function serializeArg(
}
case 'StringTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'StringLiteralTypeAnnotation':
return wrap(val => `${val}.asString(rt)`);
case 'BooleanTypeAnnotation':
return wrap(val => `${val}.asBool()`);
case 'EnumDeclaration':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ function translatePrimitiveJSTypeToCpp(
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('jsi::String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'DoubleTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,12 @@ function translateEventEmitterTypeToJavaType(
eventEmitter: NativeModuleEventEmitterShape,
imports: Set<string>,
): string {
switch (eventEmitter.typeAnnotation.typeAnnotation.type) {
const type = eventEmitter.typeAnnotation.typeAnnotation.type;
switch (type) {
case 'StringTypeAnnotation':
return 'String';
case 'StringLiteralTypeAnnotation':
return 'String';
case 'NumberTypeAnnotation':
case 'FloatTypeAnnotation':
case 'DoubleTypeAnnotation':
Expand All @@ -145,7 +148,16 @@ function translateEventEmitterTypeToJavaType(
case 'ArrayTypeAnnotation':
imports.add('com.facebook.react.bridge.ReadableArray');
return 'ReadableArray';
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'VoidTypeAnnotation':
// TODO: Add support for these types
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
default:
(type: empty);
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
Expand Down Expand Up @@ -183,6 +195,8 @@ function translateFunctionParamToJavaType(
}
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
Expand Down Expand Up @@ -273,6 +287,8 @@ function translateFunctionReturnTypeToJavaType(
return 'void';
case 'StringTypeAnnotation':
return wrapOptional('String', isRequired);
case 'StringLiteralTypeAnnotation':
return wrapOptional('String', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('double', isRequired);
case 'FloatTypeAnnotation':
Expand Down Expand Up @@ -383,6 +399,8 @@ function getFalsyReturnStatementFromReturnType(
}
case 'StringTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'StringLiteralTypeAnnotation':
return nullable ? 'return null;' : 'return "";';
case 'ObjectTypeAnnotation':
return 'return null;';
case 'GenericObjectTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ function translateReturnTypeToKind(
return 'VoidKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'StringLiteralTypeAnnotation':
return 'StringKind';
case 'BooleanTypeAnnotation':
return 'BooleanKind';
case 'EnumDeclaration':
Expand Down Expand Up @@ -244,6 +246,8 @@ function translateParamTypeToJniType(
}
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return !isRequired ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
Expand Down Expand Up @@ -320,6 +324,8 @@ function translateReturnTypeToJniType(
return 'V';
case 'StringTypeAnnotation':
return 'Ljava/lang/String;';
case 'StringLiteralTypeAnnotation':
return 'Ljava/lang/String;';
case 'BooleanTypeAnnotation':
return nullable ? 'Ljava/lang/Boolean;' : 'Z';
case 'EnumDeclaration':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type {
NativeModuleNumberTypeAnnotation,
NativeModuleObjectTypeAnnotation,
StringTypeAnnotation,
StringLiteralTypeAnnotation,
NativeModuleTypeAliasTypeAnnotation,
Nullable,
ReservedTypeAnnotation,
Expand Down Expand Up @@ -58,6 +59,7 @@ export type StructProperty = $ReadOnly<{

export type StructTypeAnnotation =
| StringTypeAnnotation
| StringLiteralTypeAnnotation
| NativeModuleNumberTypeAnnotation
| Int32TypeAnnotation
| DoubleTypeAnnotation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ function toObjCType(
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
Expand Down Expand Up @@ -173,6 +175,8 @@ function toObjCValue(
}
case 'StringTypeAnnotation':
return value;
case 'StringLiteralTypeAnnotation':
return value;
case 'NumberTypeAnnotation':
return wrapPrimitive('double');
case 'FloatTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ function toObjCType(
}
case 'StringTypeAnnotation':
return 'NSString *';
case 'StringLiteralTypeAnnotation':
return 'NSString *';
case 'NumberTypeAnnotation':
return wrapCxxOptional('double', isRequired);
case 'FloatTypeAnnotation':
Expand Down Expand Up @@ -163,6 +165,8 @@ function toObjCValue(
}
case 'StringTypeAnnotation':
return RCTBridgingTo('String');
case 'StringLiteralTypeAnnotation':
return RCTBridgingTo('String');
case 'NumberTypeAnnotation':
return RCTBridgingTo('Double');
case 'FloatTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ const {toPascalCase} = require('../../Utils');
function getEventEmitterTypeObjCType(
eventEmitter: NativeModuleEventEmitterShape,
): string {
switch (eventEmitter.typeAnnotation.typeAnnotation.type) {
const type = eventEmitter.typeAnnotation.typeAnnotation.type;

switch (type) {
case 'StringTypeAnnotation':
return 'NSString *_Nonnull';
case 'StringLiteralTypeAnnotation':
return 'NSString *_Nonnull';
case 'NumberTypeAnnotation':
return 'NSNumber *_Nonnull';
case 'BooleanTypeAnnotation':
Expand All @@ -28,7 +32,16 @@ function getEventEmitterTypeObjCType(
return 'NSDictionary *';
case 'ArrayTypeAnnotation':
return 'NSArray<id<NSObject>> *';
case 'DoubleTypeAnnotation':
case 'FloatTypeAnnotation':
case 'Int32TypeAnnotation':
case 'VoidTypeAnnotation':
// TODO: Add support for these types
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
default:
(type: empty);
throw new Error(
`Unsupported eventType for ${eventEmitter.name}. Found: ${eventEmitter.typeAnnotation.typeAnnotation.type}`,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ function getParamObjCType(
}
case 'StringTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
case 'StringLiteralTypeAnnotation':
return notStruct(wrapOptional('NSString *', !nullable));
case 'NumberTypeAnnotation':
return notStruct(isRequired ? 'double' : 'NSNumber *');
case 'FloatTypeAnnotation':
Expand Down Expand Up @@ -330,6 +332,10 @@ function getReturnObjCType(
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapOptional('NSString *', isRequired);
case 'StringLiteralTypeAnnotation':
// TODO: Can NSString * returns not be _Nullable?
// In the legacy codegen, we don't surround NSSTring * with _Nullable
return wrapOptional('NSString *', isRequired);
case 'NumberTypeAnnotation':
return wrapOptional('NSNumber *', isRequired);
case 'FloatTypeAnnotation':
Expand Down Expand Up @@ -396,6 +402,8 @@ function getReturnJSType(
return 'NumberKind';
case 'StringTypeAnnotation':
return 'StringKind';
case 'StringLiteralTypeAnnotation':
return 'StringKind';
case 'NumberTypeAnnotation':
return 'NumberKind';
case 'FloatTypeAnnotation':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2636,6 +2636,55 @@ const UNION_MODULE: SchemaType = {
},
};

const STRING_LITERALS = {
modules: {
NativeSampleTurboModule: {
type: 'NativeModule',
aliasMap: {},
enumMap: {},
spec: {
eventEmitters: [
{
name: 'literalEvent',
optional: false,
typeAnnotation: {
type: 'EventEmitterTypeAnnotation',
typeAnnotation: {
type: 'StringLiteralTypeAnnotation',
value: 'A String Literal Event',
},
},
},
],
methods: [
{
name: 'getStringLiteral',
optional: false,
typeAnnotation: {
type: 'FunctionTypeAnnotation',
returnTypeAnnotation: {
type: 'StringLiteralTypeAnnotation',
value: 'A String Literal Return',
},
params: [
{
name: 'literalParam',
optional: false,
typeAnnotation: {
type: 'StringLiteralTypeAnnotation',
value: 'A String Literal Param',
},
},
],
},
},
],
},
moduleName: 'SampleTurboModule',
},
},
};

module.exports = {
complex_objects: COMPLEX_OBJECTS,
two_modules_different_files: TWO_MODULES_DIFFERENT_FILES,
Expand All @@ -2647,4 +2696,5 @@ module.exports = {
cxx_only_native_modules: CXX_ONLY_NATIVE_MODULES,
SampleWithUppercaseName: SAMPLE_WITH_UPPERCASE_NAME,
union_module: UNION_MODULE,
string_literals: STRING_LITERALS,
};
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,39 @@ NativeSampleTurboModuleCxxSpecJSI::NativeSampleTurboModuleCxxSpecJSI(std::shared
}
} // namespace facebook::react
",
}
`;
exports[`GenerateModuleCpp can generate fixture string_literals 1`] = `
Map {
"string_literalsJSI-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 \\"string_literalsJSI.h\\"
namespace facebook::react {
static jsi::Value __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getStringLiteral(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
return static_cast<NativeSampleTurboModuleCxxSpecJSI *>(&turboModule)->getStringLiteral(
rt,
count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asString(rt)
);
}
NativeSampleTurboModuleCxxSpecJSI::NativeSampleTurboModuleCxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule(\\"SampleTurboModule\\", jsInvoker) {
methodMap_[\\"getStringLiteral\\"] = MethodMetadata {1, __hostFunction_NativeSampleTurboModuleCxxSpecJSI_getStringLiteral};
}
} // namespace facebook::react
",
}
Expand Down
Loading

0 comments on commit d2f3f06

Please sign in to comment.