Skip to content

Commit 74f75a6

Browse files
Support importing types across Turbo Module specs (#56045)
Summary: ## Changelog: [General] [Added] - Add cross-file type import support for React Native Turbo Module codegen Turbo Module JavaScript specs previously required all types (enums, type aliases) to be defined in the same file. This forced developers to duplicate type definitions when sharing them across multiple modules. This diff adds an optional `importedTypes` parameter to `parseString()` that allows the parser to resolve types defined in external files. The approach: - `parseString()` accepts an optional `TypeDeclarationMap` of imported types, which are merged with local types (local definitions take precedence). - `parseFile()` automatically resolves relative `import type` declarations by parsing the source files and extracting their type AST nodes. - A shared `resolveImportedTypes()` utility handles filesystem resolution for both Flow and TypeScript parsers. - `getImportsFromAST()` extracts type-only import declarations from the AST, filtering out value imports to avoid unnecessary filesystem I/O. - The flow-schema OTA safety tool is updated to build imported types from its existing import map infrastructure and pass them to the codegen parser. Code generators require zero changes since they consume the flat schema (enumMap/aliasMap), and imported types produce identical schema entries to file-local types. Differential Revision: D95646987
1 parent af0cccc commit 74f75a6

20 files changed

Lines changed: 1290 additions & 46 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
import type {
12+
SharedNumEnum,
13+
SharedStateType,
14+
SharedStatusEnum,
15+
} from './SharedEnumTypes';
16+
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
17+
18+
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
19+
20+
export interface Spec extends TurboModule {
21+
+getStatus: (statusProp: SharedStateType) => SharedStatusEnum;
22+
+getNum: () => SharedNumEnum;
23+
+setStatus: (status: SharedStatusEnum) => void;
24+
}
25+
26+
export default (TurboModuleRegistry.getEnforcing<Spec>(
27+
'NativeImportedEnumTurboModule',
28+
): Spec);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
export enum SharedStatusEnum {
12+
Active = 'active',
13+
Paused = 'paused',
14+
Off = 'off',
15+
}
16+
17+
export enum SharedNumEnum {
18+
One = 1,
19+
Two = 2,
20+
Three = 3,
21+
}
22+
23+
export type SharedStateType = {
24+
status: SharedStatusEnum,
25+
count: number,
26+
};

packages/react-native-codegen/e2e/deep_imports/__tests__/modules/__snapshots__/GenerateModuleH-test.js.snap

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,153 @@ private:
419419
};
420420

421421

422+
#pragma mark - SharedEnumTypesSharedNumEnum
423+
424+
enum class SharedEnumTypesSharedNumEnum { One, Two, Three };
425+
426+
template <>
427+
struct Bridging<SharedEnumTypesSharedNumEnum> {
428+
static SharedEnumTypesSharedNumEnum fromJs(jsi::Runtime &rt, const jsi::Value &rawValue) {
429+
double value = (double)rawValue.asNumber();
430+
if (value == 1) {
431+
return SharedEnumTypesSharedNumEnum::One;
432+
} else if (value == 2) {
433+
return SharedEnumTypesSharedNumEnum::Two;
434+
} else if (value == 3) {
435+
return SharedEnumTypesSharedNumEnum::Three;
436+
} else {
437+
throw jsi::JSError(rt, \\"No appropriate enum member found for value in SharedEnumTypesSharedNumEnum\\");
438+
}
439+
}
440+
441+
static jsi::Value toJs(jsi::Runtime &rt, SharedEnumTypesSharedNumEnum value) {
442+
if (value == SharedEnumTypesSharedNumEnum::One) {
443+
return bridging::toJs(rt, 1);
444+
} else if (value == SharedEnumTypesSharedNumEnum::Two) {
445+
return bridging::toJs(rt, 2);
446+
} else if (value == SharedEnumTypesSharedNumEnum::Three) {
447+
return bridging::toJs(rt, 3);
448+
} else {
449+
throw jsi::JSError(rt, \\"No appropriate enum member found for enum value in SharedEnumTypesSharedNumEnum\\");
450+
}
451+
}
452+
};
453+
454+
#pragma mark - SharedEnumTypesSharedStatusEnum
455+
456+
enum class SharedEnumTypesSharedStatusEnum { Active, Paused, Off };
457+
458+
template <>
459+
struct Bridging<SharedEnumTypesSharedStatusEnum> {
460+
static SharedEnumTypesSharedStatusEnum fromJs(jsi::Runtime &rt, const jsi::String &rawValue) {
461+
std::string value = rawValue.utf8(rt);
462+
if (value == \\"active\\") {
463+
return SharedEnumTypesSharedStatusEnum::Active;
464+
} else if (value == \\"paused\\") {
465+
return SharedEnumTypesSharedStatusEnum::Paused;
466+
} else if (value == \\"off\\") {
467+
return SharedEnumTypesSharedStatusEnum::Off;
468+
} else {
469+
throw jsi::JSError(rt, \\"No appropriate enum member found for value in SharedEnumTypesSharedStatusEnum\\");
470+
}
471+
}
472+
473+
static jsi::String toJs(jsi::Runtime &rt, SharedEnumTypesSharedStatusEnum value) {
474+
if (value == SharedEnumTypesSharedStatusEnum::Active) {
475+
return bridging::toJs(rt, \\"active\\");
476+
} else if (value == SharedEnumTypesSharedStatusEnum::Paused) {
477+
return bridging::toJs(rt, \\"paused\\");
478+
} else if (value == SharedEnumTypesSharedStatusEnum::Off) {
479+
return bridging::toJs(rt, \\"off\\");
480+
} else {
481+
throw jsi::JSError(rt, \\"No appropriate enum member found for enum value in SharedEnumTypesSharedStatusEnum\\");
482+
}
483+
}
484+
};
485+
#pragma mark - SharedEnumTypesSharedStateType
486+
487+
template <typename P0, typename P1>
488+
struct SharedEnumTypesSharedStateType {
489+
P0 status{};
490+
P1 count;
491+
bool operator==(const SharedEnumTypesSharedStateType &other) const {
492+
return status == other.status && count == other.count;
493+
}
494+
};
495+
496+
template <typename T>
497+
struct SharedEnumTypesSharedStateTypeBridging {
498+
static T types;
499+
500+
static T fromJs(
501+
jsi::Runtime &rt,
502+
const jsi::Object &value,
503+
const std::shared_ptr<CallInvoker> &jsInvoker) {
504+
T result{
505+
bridging::fromJs<decltype(types.status)>(rt, value.getProperty(rt, \\"status\\"), jsInvoker),
506+
bridging::fromJs<decltype(types.count)>(rt, value.getProperty(rt, \\"count\\"), jsInvoker)};
507+
return result;
508+
}
509+
510+
#ifdef DEBUG
511+
static jsi::String statusToJs(jsi::Runtime &rt, decltype(types.status) value) {
512+
return bridging::toJs(rt, value);
513+
}
514+
static double countToJs(jsi::Runtime &rt, decltype(types.count) value) {
515+
return bridging::toJs(rt, value);
516+
}
517+
#endif
518+
519+
static jsi::Object toJs(
520+
jsi::Runtime &rt,
521+
const T &value,
522+
const std::shared_ptr<CallInvoker> &jsInvoker) {
523+
auto result = facebook::jsi::Object(rt);
524+
result.setProperty(rt, \\"status\\", bridging::toJs(rt, value.status, jsInvoker));
525+
result.setProperty(rt, \\"count\\", bridging::toJs(rt, value.count, jsInvoker));
526+
return result;
527+
}
528+
};
529+
530+
531+
template <typename T>
532+
class JSI_EXPORT NativeImportedEnumTurboModuleCxxSpec : public TurboModule {
533+
public:
534+
static constexpr std::string_view kModuleName = \\"NativeImportedEnumTurboModule\\";
535+
536+
protected:
537+
NativeImportedEnumTurboModuleCxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{NativeImportedEnumTurboModuleCxxSpec::kModuleName}, jsInvoker) {
538+
methodMap_[\\"getStatus\\"] = MethodMetadata {.argCount = 1, .invoker = __getStatus};
539+
methodMap_[\\"getNum\\"] = MethodMetadata {.argCount = 0, .invoker = __getNum};
540+
methodMap_[\\"setStatus\\"] = MethodMetadata {.argCount = 1, .invoker = __setStatus};
541+
}
542+
543+
private:
544+
static jsi::Value __getStatus(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
545+
static_assert(
546+
bridging::getParameterCount(&T::getStatus) == 2,
547+
\\"Expected getStatus(...) to have 2 parameters\\");
548+
return bridging::callFromJs<jsi::String>(rt, &T::getStatus, static_cast<NativeImportedEnumTurboModuleCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule),
549+
count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asObject(rt));
550+
}
551+
552+
static jsi::Value __getNum(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) {
553+
static_assert(
554+
bridging::getParameterCount(&T::getNum) == 1,
555+
\\"Expected getNum(...) to have 1 parameters\\");
556+
return bridging::callFromJs<jsi::Value>(rt, &T::getNum, static_cast<NativeImportedEnumTurboModuleCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule));
557+
}
558+
559+
static jsi::Value __setStatus(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
560+
static_assert(
561+
bridging::getParameterCount(&T::setStatus) == 2,
562+
\\"Expected setStatus(...) to have 2 parameters\\");
563+
bridging::callFromJs<void>(rt, &T::setStatus, static_cast<NativeImportedEnumTurboModuleCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule),
564+
count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asString(rt));return jsi::Value::undefined();
565+
}
566+
};
567+
568+
422569
template <typename T>
423570
class JSI_EXPORT NativeNullableTurboModuleCxxSpec : public TurboModule {
424571
public:
@@ -2016,6 +2163,153 @@ private:
20162163
};
20172164

20182165

2166+
#pragma mark - SharedEnumTypesSharedNumEnum
2167+
2168+
enum class SharedEnumTypesSharedNumEnum { One, Two, Three };
2169+
2170+
template <>
2171+
struct Bridging<SharedEnumTypesSharedNumEnum> {
2172+
static SharedEnumTypesSharedNumEnum fromJs(jsi::Runtime &rt, const jsi::Value &rawValue) {
2173+
double value = (double)rawValue.asNumber();
2174+
if (value == 1) {
2175+
return SharedEnumTypesSharedNumEnum::One;
2176+
} else if (value == 2) {
2177+
return SharedEnumTypesSharedNumEnum::Two;
2178+
} else if (value == 3) {
2179+
return SharedEnumTypesSharedNumEnum::Three;
2180+
} else {
2181+
throw jsi::JSError(rt, \\"No appropriate enum member found for value in SharedEnumTypesSharedNumEnum\\");
2182+
}
2183+
}
2184+
2185+
static jsi::Value toJs(jsi::Runtime &rt, SharedEnumTypesSharedNumEnum value) {
2186+
if (value == SharedEnumTypesSharedNumEnum::One) {
2187+
return bridging::toJs(rt, 1);
2188+
} else if (value == SharedEnumTypesSharedNumEnum::Two) {
2189+
return bridging::toJs(rt, 2);
2190+
} else if (value == SharedEnumTypesSharedNumEnum::Three) {
2191+
return bridging::toJs(rt, 3);
2192+
} else {
2193+
throw jsi::JSError(rt, \\"No appropriate enum member found for enum value in SharedEnumTypesSharedNumEnum\\");
2194+
}
2195+
}
2196+
};
2197+
2198+
#pragma mark - SharedEnumTypesSharedStatusEnum
2199+
2200+
enum class SharedEnumTypesSharedStatusEnum { Active, Paused, Off };
2201+
2202+
template <>
2203+
struct Bridging<SharedEnumTypesSharedStatusEnum> {
2204+
static SharedEnumTypesSharedStatusEnum fromJs(jsi::Runtime &rt, const jsi::String &rawValue) {
2205+
std::string value = rawValue.utf8(rt);
2206+
if (value == \\"active\\") {
2207+
return SharedEnumTypesSharedStatusEnum::Active;
2208+
} else if (value == \\"paused\\") {
2209+
return SharedEnumTypesSharedStatusEnum::Paused;
2210+
} else if (value == \\"off\\") {
2211+
return SharedEnumTypesSharedStatusEnum::Off;
2212+
} else {
2213+
throw jsi::JSError(rt, \\"No appropriate enum member found for value in SharedEnumTypesSharedStatusEnum\\");
2214+
}
2215+
}
2216+
2217+
static jsi::String toJs(jsi::Runtime &rt, SharedEnumTypesSharedStatusEnum value) {
2218+
if (value == SharedEnumTypesSharedStatusEnum::Active) {
2219+
return bridging::toJs(rt, \\"active\\");
2220+
} else if (value == SharedEnumTypesSharedStatusEnum::Paused) {
2221+
return bridging::toJs(rt, \\"paused\\");
2222+
} else if (value == SharedEnumTypesSharedStatusEnum::Off) {
2223+
return bridging::toJs(rt, \\"off\\");
2224+
} else {
2225+
throw jsi::JSError(rt, \\"No appropriate enum member found for enum value in SharedEnumTypesSharedStatusEnum\\");
2226+
}
2227+
}
2228+
};
2229+
#pragma mark - SharedEnumTypesSharedStateType
2230+
2231+
template <typename P0, typename P1>
2232+
struct SharedEnumTypesSharedStateType {
2233+
P0 status{};
2234+
P1 count;
2235+
bool operator==(const SharedEnumTypesSharedStateType &other) const {
2236+
return status == other.status && count == other.count;
2237+
}
2238+
};
2239+
2240+
template <typename T>
2241+
struct SharedEnumTypesSharedStateTypeBridging {
2242+
static T types;
2243+
2244+
static T fromJs(
2245+
jsi::Runtime &rt,
2246+
const jsi::Object &value,
2247+
const std::shared_ptr<CallInvoker> &jsInvoker) {
2248+
T result{
2249+
bridging::fromJs<decltype(types.status)>(rt, value.getProperty(rt, \\"status\\"), jsInvoker),
2250+
bridging::fromJs<decltype(types.count)>(rt, value.getProperty(rt, \\"count\\"), jsInvoker)};
2251+
return result;
2252+
}
2253+
2254+
#ifdef DEBUG
2255+
static jsi::String statusToJs(jsi::Runtime &rt, decltype(types.status) value) {
2256+
return bridging::toJs(rt, value);
2257+
}
2258+
static double countToJs(jsi::Runtime &rt, decltype(types.count) value) {
2259+
return bridging::toJs(rt, value);
2260+
}
2261+
#endif
2262+
2263+
static jsi::Object toJs(
2264+
jsi::Runtime &rt,
2265+
const T &value,
2266+
const std::shared_ptr<CallInvoker> &jsInvoker) {
2267+
auto result = facebook::jsi::Object(rt);
2268+
result.setProperty(rt, \\"status\\", bridging::toJs(rt, value.status, jsInvoker));
2269+
result.setProperty(rt, \\"count\\", bridging::toJs(rt, value.count, jsInvoker));
2270+
return result;
2271+
}
2272+
};
2273+
2274+
2275+
template <typename T>
2276+
class JSI_EXPORT NativeImportedEnumTurboModuleCxxSpec : public TurboModule {
2277+
public:
2278+
static constexpr std::string_view kModuleName = \\"NativeImportedEnumTurboModule\\";
2279+
2280+
protected:
2281+
NativeImportedEnumTurboModuleCxxSpec(std::shared_ptr<CallInvoker> jsInvoker) : TurboModule(std::string{NativeImportedEnumTurboModuleCxxSpec::kModuleName}, jsInvoker) {
2282+
methodMap_[\\"getStatus\\"] = MethodMetadata {.argCount = 1, .invoker = __getStatus};
2283+
methodMap_[\\"getNum\\"] = MethodMetadata {.argCount = 0, .invoker = __getNum};
2284+
methodMap_[\\"setStatus\\"] = MethodMetadata {.argCount = 1, .invoker = __setStatus};
2285+
}
2286+
2287+
private:
2288+
static jsi::Value __getStatus(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
2289+
static_assert(
2290+
bridging::getParameterCount(&T::getStatus) == 2,
2291+
\\"Expected getStatus(...) to have 2 parameters\\");
2292+
return bridging::callFromJs<jsi::String>(rt, &T::getStatus, static_cast<NativeImportedEnumTurboModuleCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule),
2293+
count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asObject(rt));
2294+
}
2295+
2296+
static jsi::Value __getNum(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* /*args*/, size_t /*count*/) {
2297+
static_assert(
2298+
bridging::getParameterCount(&T::getNum) == 1,
2299+
\\"Expected getNum(...) to have 1 parameters\\");
2300+
return bridging::callFromJs<jsi::Value>(rt, &T::getNum, static_cast<NativeImportedEnumTurboModuleCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule));
2301+
}
2302+
2303+
static jsi::Value __setStatus(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {
2304+
static_assert(
2305+
bridging::getParameterCount(&T::setStatus) == 2,
2306+
\\"Expected setStatus(...) to have 2 parameters\\");
2307+
bridging::callFromJs<void>(rt, &T::setStatus, static_cast<NativeImportedEnumTurboModuleCxxSpec*>(&turboModule)->jsInvoker_, static_cast<T*>(&turboModule),
2308+
count <= 0 ? throw jsi::JSError(rt, \\"Expected argument in position 0 to be passed\\") : args[0].asString(rt));return jsi::Value::undefined();
2309+
}
2310+
};
2311+
2312+
20192313
template <typename T>
20202314
class JSI_EXPORT NativeNullableTurboModuleCxxSpec : public TurboModule {
20212315
public:

0 commit comments

Comments
 (0)