Skip to content

Commit 784f76c

Browse files
committed
Fix codegen hanging on type aliases that shadow CodegenTypes members
A spec containing `type Double = CodegenTypes.Double;` sent the codegen parsers into an infinite loop: getResolvedTypeAnnotation strips the namespace qualifier when looking up local aliases, so the alias's RHS resolved back to the alias itself forever. This hung the Node process of anything driving the parser, including ESLint via @react-native/eslint-plugin-specs. Both the Flow and the TypeScript parsers were affected. Stop resolving qualified names (CodegenTypes.Double names a namespace member, not a local alias), and track resolved alias names so genuinely cyclic aliases (type A = B; type B = A) fail with the existing UnsupportedGenericParserError instead of hanging. Fixes #57956 Changelog: [GENERAL] [FIXED] - Codegen no longer hangs on type aliases that shadow CodegenTypes member names
1 parent 5bb9639 commit 784f76c

6 files changed

Lines changed: 191 additions & 2 deletions

File tree

‎packages/react-native-codegen/src/parsers/flow/modules/__test_fixtures__/fixtures.js‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,34 @@ export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
946946
947947
`;
948948

949+
const NAMESPACED_NATIVE_MODULE_WITH_LOCAL_TYPE_ALIASES = `
950+
/**
951+
* Copyright (c) Meta Platforms, Inc. and affiliates.
952+
*
953+
* This source code is licensed under the MIT license found in the
954+
* LICENSE file in the root directory of this source tree.
955+
*
956+
* @flow strict-local
957+
* @format
958+
*/
959+
960+
'use strict';
961+
962+
import type {TurboModule} from '../RCTExport';
963+
import * as TurboModuleRegistry from '../TurboModuleRegistry';
964+
import type {CodegenTypes} from 'react-native';
965+
966+
type Double = CodegenTypes.Double;
967+
type MyFloat = CodegenTypes.Float;
968+
969+
export interface Spec extends TurboModule {
970+
+getDouble: (arg: Double) => Double;
971+
+getFloat: (arg: MyFloat) => MyFloat;
972+
}
973+
974+
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
975+
`;
976+
949977
const NAMESPACED_NATIVE_MODULE_WITH_EVENT_EMITTERS = `
950978
/**
951979
* Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1046,4 +1074,5 @@ module.exports = {
10461074
NAMESPACED_NATIVE_MODULE_WITH_FLOAT_AND_INT32,
10471075
NAMESPACED_NATIVE_MODULE_WITH_UNSAFE_OBJECT,
10481076
NAMESPACED_NATIVE_MODULE_WITH_EVENT_EMITTERS,
1077+
NAMESPACED_NATIVE_MODULE_WITH_LOCAL_TYPE_ALIASES,
10491078
};

‎packages/react-native-codegen/src/parsers/flow/modules/__tests__/__snapshots__/module-parser-snapshot-test.js.snap‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,62 @@ exports[`RN Codegen Flow Parser can generate fixture NAMESPACED_NATIVE_MODULE_WI
845845
}"
846846
`;
847847

848+
exports[`RN Codegen Flow Parser can generate fixture NAMESPACED_NATIVE_MODULE_WITH_LOCAL_TYPE_ALIASES 1`] = `
849+
"{
850+
'modules': {
851+
'NativeSampleTurboModule': {
852+
'type': 'NativeModule',
853+
'aliasMap': {},
854+
'enumMap': {},
855+
'spec': {
856+
'eventEmitters': [],
857+
'methods': [
858+
{
859+
'name': 'getDouble',
860+
'optional': false,
861+
'typeAnnotation': {
862+
'type': 'FunctionTypeAnnotation',
863+
'returnTypeAnnotation': {
864+
'type': 'DoubleTypeAnnotation'
865+
},
866+
'params': [
867+
{
868+
'name': 'arg',
869+
'optional': false,
870+
'typeAnnotation': {
871+
'type': 'DoubleTypeAnnotation'
872+
}
873+
}
874+
]
875+
}
876+
},
877+
{
878+
'name': 'getFloat',
879+
'optional': false,
880+
'typeAnnotation': {
881+
'type': 'FunctionTypeAnnotation',
882+
'returnTypeAnnotation': {
883+
'type': 'FloatTypeAnnotation'
884+
},
885+
'params': [
886+
{
887+
'name': 'arg',
888+
'optional': false,
889+
'typeAnnotation': {
890+
'type': 'FloatTypeAnnotation'
891+
}
892+
}
893+
]
894+
}
895+
}
896+
]
897+
},
898+
'moduleName': 'SampleTurboModule'
899+
}
900+
}
901+
}"
902+
`;
903+
848904
exports[`RN Codegen Flow Parser can generate fixture NAMESPACED_NATIVE_MODULE_WITH_UNSAFE_OBJECT 1`] = `
849905
"{
850906
'modules': {

‎packages/react-native-codegen/src/parsers/flow/parser.js‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ class FlowParser implements Parser {
418418
let typeResolutionStatus: TypeResolutionStatus = {
419419
successful: false,
420420
};
421+
const resolvedTypeAliases = new Set<string>();
421422

422423
for (;;) {
423424
if (node.type === 'NullableTypeAnnotation') {
@@ -430,11 +431,21 @@ class FlowParser implements Parser {
430431
break;
431432
}
432433

434+
// A qualified name (e.g. CodegenTypes.Double) refers to a namespace
435+
// member, not to the local type alias of the same unqualified name.
436+
if (node.id.type === 'QualifiedTypeIdentifier') {
437+
break;
438+
}
439+
433440
const typeAnnotationName = this.getTypeAnnotationName(node);
434441
const resolvedTypeAnnotation = types[typeAnnotationName];
435-
if (resolvedTypeAnnotation == null) {
442+
if (
443+
resolvedTypeAnnotation == null ||
444+
resolvedTypeAliases.has(typeAnnotationName)
445+
) {
436446
break;
437447
}
448+
resolvedTypeAliases.add(typeAnnotationName);
438449
const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} =
439450
handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this);
440451
typeResolutionStatus = status;

‎packages/react-native-codegen/src/parsers/typescript/modules/__test_fixtures__/fixtures.js‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,31 @@ export interface Spec extends TurboModule {
959959
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
960960
`;
961961

962+
const NAMESPACED_NATIVE_MODULE_WITH_LOCAL_TYPE_ALIASES = `
963+
/**
964+
* Copyright (c) Meta Platforms, Inc. and affiliates.
965+
*
966+
* This source code is licensed under the MIT license found in the
967+
* LICENSE file in the root directory of this source tree.
968+
*
969+
* @format
970+
*/
971+
972+
import type {TurboModule} from 'react-native/Libraries/TurboModule/RCTExport';
973+
import type {CodegenTypes} from 'react-native';
974+
import * as TurboModuleRegistry from 'react-native/Libraries/TurboModule/TurboModuleRegistry';
975+
976+
type Double = CodegenTypes.Double;
977+
type MyFloat = CodegenTypes.Float;
978+
979+
export interface Spec extends TurboModule {
980+
readonly getDouble: (arg: Double) => Double;
981+
readonly getFloat: (arg: MyFloat) => MyFloat;
982+
}
983+
984+
export default TurboModuleRegistry.getEnforcing<Spec>('SampleTurboModule');
985+
`;
986+
962987
const NAMESPACED_NATIVE_MODULE_WITH_EVENT_EMITTERS = `
963988
/**
964989
* Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -1057,4 +1082,5 @@ module.exports = {
10571082
NAMESPACED_NATIVE_MODULE_WITH_FLOAT_AND_INT32,
10581083
NAMESPACED_NATIVE_MODULE_WITH_UNSAFE_OBJECT,
10591084
NAMESPACED_NATIVE_MODULE_WITH_EVENT_EMITTERS,
1085+
NAMESPACED_NATIVE_MODULE_WITH_LOCAL_TYPE_ALIASES,
10601086
};

‎packages/react-native-codegen/src/parsers/typescript/modules/__tests__/__snapshots__/typescript-module-parser-snapshot-test.js.snap‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,62 @@ exports[`RN Codegen TypeScript Parser can generate fixture NAMESPACED_NATIVE_MOD
843843
}"
844844
`;
845845

846+
exports[`RN Codegen TypeScript Parser can generate fixture NAMESPACED_NATIVE_MODULE_WITH_LOCAL_TYPE_ALIASES 1`] = `
847+
"{
848+
'modules': {
849+
'NativeSampleTurboModule': {
850+
'type': 'NativeModule',
851+
'aliasMap': {},
852+
'enumMap': {},
853+
'spec': {
854+
'eventEmitters': [],
855+
'methods': [
856+
{
857+
'name': 'getDouble',
858+
'optional': false,
859+
'typeAnnotation': {
860+
'type': 'FunctionTypeAnnotation',
861+
'returnTypeAnnotation': {
862+
'type': 'DoubleTypeAnnotation'
863+
},
864+
'params': [
865+
{
866+
'name': 'arg',
867+
'optional': false,
868+
'typeAnnotation': {
869+
'type': 'DoubleTypeAnnotation'
870+
}
871+
}
872+
]
873+
}
874+
},
875+
{
876+
'name': 'getFloat',
877+
'optional': false,
878+
'typeAnnotation': {
879+
'type': 'FunctionTypeAnnotation',
880+
'returnTypeAnnotation': {
881+
'type': 'FloatTypeAnnotation'
882+
},
883+
'params': [
884+
{
885+
'name': 'arg',
886+
'optional': false,
887+
'typeAnnotation': {
888+
'type': 'FloatTypeAnnotation'
889+
}
890+
}
891+
]
892+
}
893+
}
894+
]
895+
},
896+
'moduleName': 'SampleTurboModule'
897+
}
898+
}
899+
}"
900+
`;
901+
846902
exports[`RN Codegen TypeScript Parser can generate fixture NAMESPACED_NATIVE_MODULE_WITH_UNSAFE_OBJECT 1`] = `
847903
"{
848904
'modules': {

‎packages/react-native-codegen/src/parsers/typescript/parser.js‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,7 @@ class TypeScriptParser implements Parser {
455455
let typeResolutionStatus: TypeResolutionStatus = {
456456
successful: false,
457457
};
458+
const resolvedTypeAliases = new Set<string>();
458459

459460
for (;;) {
460461
const topLevelType = parseTopLevelType(node, parser);
@@ -465,11 +466,21 @@ class TypeScriptParser implements Parser {
465466
break;
466467
}
467468

469+
// A qualified name (e.g. CodegenTypes.Double) refers to a namespace
470+
// member, not to the local type alias of the same unqualified name.
471+
if (node.typeName.type === 'TSQualifiedName') {
472+
break;
473+
}
474+
468475
const typeAnnotationName = this.getTypeAnnotationName(node);
469476
const resolvedTypeAnnotation = types[typeAnnotationName];
470-
if (resolvedTypeAnnotation == null) {
477+
if (
478+
resolvedTypeAnnotation == null ||
479+
resolvedTypeAliases.has(typeAnnotationName)
480+
) {
471481
break;
472482
}
483+
resolvedTypeAliases.add(typeAnnotationName);
473484

474485
const {typeAnnotation: typeAnnotationNode, typeResolutionStatus: status} =
475486
handleGenericTypeAnnotation(node, resolvedTypeAnnotation, this);

0 commit comments

Comments
 (0)