-
Notifications
You must be signed in to change notification settings - Fork 25.1k
Fixing schema types for component command params of Arrays #48476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Contributor
|
This pull request was exported from Phabricator. Differential Revision: D67806838 |
71afc31 to
eb38ff3
Compare
elicwhite
added a commit
to elicwhite/react-native
that referenced
this pull request
Jan 3, 2025
…48476) Summary: Command param array types were generating invalid schemas due to untyped parser code. The invalid schemas occurred for any alias type, including custom objects and basics like Int32. This was also inconsistent between Flow and TypeScript. We already had one component utilizing this issue, so this just codifies that support into the schema so it reflects reality. This is only a partial solution. The more full solution would be to fully encode the custom types in the schemas like we do for Native Modules. # More Information: Tl;dr, DebuggingOverlay is abusing a FlowFixMe in codegen commands. ## The problem: The [CodegenSchema](https://www.internalfb.com/code/fbsource/[d3ab2f79b377]/xplat/js/react-native-github/packages/react-native-codegen/src/CodegenSchema.js?lines=220) should be the source of truth for anything that can be in the schema. If something is in the schema that isn't allowed by the types, that's a bug. We have a bug. I'm adding compat-check support for components and it's blowing up on prod schemas because DebuggingOverlay causes an invalid schema. ## The details: Support for Arrays as arguments in commands was added to the Codegen in D51866557. [Code Pointer](https://fburl.com/code/8yy1rm0p) The intention appears to be to support arrays of primitives. There is a TODO for supporting complex types. ``` interface NativeCommands { +addOverlays: ( viewRef: React.ElementRef<NativeType>, overlayColorsReadOnly: $ReadOnlyArray<string>, ) } ``` This support was added to TypeScript in D52046165 where it types the allowed Array arguments to be only ``` { readonly type: 'ArrayTypeAnnotation'; readonly elementType: | Int32TypeAnnotation | DoubleTypeAnnotation | FloatTypeAnnotation | BooleanTypeAnnotation | StringTypeAnnotation }; ``` However, because the Parsers are treating the input type as `any`, it isn't safe to pass through an input value into the schema as Flow won't catch mismatches. The Flow parser just passes it through: ``` { type: 'ArrayTypeAnnotation', elementType: { // TODO: T172453752 support complex type annotation for array element type: paramValue.typeParameters.params[0].type, } ``` Whereas the TypeScript parser has the more correct behavior of validating the inputs and returning specific outputs. Unfortunately, the return type is also typed here as $FlowFixMe, losing most of the benefits. ``` function getPrimitiveTypeAnnotation(type: string): $FlowFixMe { switch (type) { case 'Int32': return { type: 'Int32TypeAnnotation', }; case 'Double': return { type: 'DoubleTypeAnnotation', }; case 'Float': return { type: 'FloatTypeAnnotation', }; case 'TSBooleanKeyword': return { type: 'BooleanTypeAnnotation', }; case 'Stringish': case 'TSStringKeyword': return { type: 'StringTypeAnnotation', }; default: throw new Error(`Unknown primitive type "${type}"`); } } ``` [DebuggingOverlay](https://fburl.com/code/zfe3ipq7) is abusing this gap in the Flow parser by sticking an Array of Objects in. ``` export type ElementRectangle = { x: number, y: number, width: number, height: number, }; ... +highlightElements: ( viewRef: React.ElementRef<DebuggingOverlayNativeComponentType>, elements: $ReadOnlyArray<ElementRectangle>, ) => void; ... ``` This isn't allowed in the schema, but it seems to fall through the holes of the flow parser and generators. The resulting schema from Flow is this. Note the GenericTypeAnnotation which isn't allowed to be in the schema. ``` { "name": "highlightElements", "optional": false, "typeAnnotation": { "type": "FunctionTypeAnnotation", "params": [ { "name": "elements", "optional": false, "typeAnnotation": { "type": "ArrayTypeAnnotation", "elementType": { "type": "GenericTypeAnnotation" } } } ], "returnTypeAnnotation": { "type": "VoidTypeAnnotation" } }, ``` The TypeScript parser fails with `Error: Unsupported type annotation: GenericTypeAnnotation`. The generators don't seem to check beyond the ArrayTypeAnnotation so they fall through to generating generic arrays. ``` // ios elements:(const NSArray *)elements // android ReadableArray locations ``` ## So how do I fix this? I think there are a couple of different options here. The key problem is that the Schema types need to represent reality of what can be in the schema. 1. We revert DebuggingOverlay to not use features that aren't supported (I assume nobody would be happy with this, but the change shouldn't have been made in the first place) 2. **(This is the approach taken in this diff)** We add MixedTypeAnnotation to the allowed types in Command arrays and have it generate that and add official support for that to the TypeScript parser as well. That is probably the quickest and easiest approach. It leaves the same type unsafety we have today on the native side. 3. NativeModules seem to have a lot more complex type safety here. They persist the alias type in the schema so that the CompatCheck can check them on changes. And then in C++ they generate structs and RCTConvert functions although for Java and ObjC it looks like they just use the same untyped native code. The matching approach here would be to add `aliasMap` and the whole data to the schema for commands, use that for the compat check, and still generate the same unsafe native code. ``` export type ObjectAlias = {| x: number, y: number, |}; export interface Spec extends TurboModule { +getAlias: (a: ObjectAlias) => string; } ``` stores the ObjectAlias in the schema ``` { "aliasMap": { "ObjectAlias": { "type": "ObjectTypeAnnotation", "properties": [ { "name": "x", "optional": false, "typeAnnotation": { "type": "NumberTypeAnnotation" } }, { "name": "y", "optional": false, "typeAnnotation": { "type": "NumberTypeAnnotation" } }, ] } }, "spec": { "methods": [ { "name": "getAlias", "optional": false, "typeAnnotation": { "type": "FunctionTypeAnnotation", "returnTypeAnnotation": { "type": "StringTypeAnnotation" }, "params": [ { "name": "a", "optional": false, "typeAnnotation": { "type": "TypeAliasTypeAnnotation", "name": "ObjectAlias" } } ] } } ] } } ``` and then generates the appropriate structs on the native side and generates [this](https://www.internalfb.com/code/fbsource/[d3ab2f79b377]/xplat/js/react-native-github/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap?lines=818) ``` Differential Revision: D67806838
Contributor
|
This pull request was exported from Phabricator. Differential Revision: D67806838 |
Summary:
Over the years of copying and moving and renaming types through CodegenSchema, this type ended up in the Command params, although the Command parser doesn't allow it.
I made this change to a fixture:
{F1974104959}
and got this error
```
FAIL xplat/js/react-native-github/packages/react-native-codegen/src/parsers/flow/components/__tests__/component-parser-test.js
● RN Codegen Flow Parser › can generate fixture COMMANDS_DEFINED_WITH_ALL_TYPES
Unsupported param type for method "scrollTo", param "speed". Found UnionTypeAnnotation
127 | default:
128 | (type: empty);
> 129 | throw new Error(
| ^
130 | `Unsupported param type for method "${name}", param "${paramName}". Found ${type}`,
131 | );
132 | }
```
Also, a default value for enum an argument of a Command doesn't make sense anyways.
Commands should probably have support for enums and string literal unions, but that's out of scope here.
Still need to add to this vec\concat on www: https://www.internalfb.com/code/www/[ebfa58f888a6064e17879934d447f59bcc2b6951]/flib/intern/sandcastle/react_native/ota_steps/SandcastleOTACompatibilityCheckReportingStep.php?lines=62
Changelog: [internal]
Differential Revision: D67806808
Summary: This is such a simpler approach lol. I'll need this later for when I want to pass in arrays or objects of these types to the compat check Changelog: [internal] Differential Revision: D67806812
Summary:
The previous definition said that you could put prop types into commands, which definitely isn't allowed.
For example, the schema would have allowed `WithDefault` types.
```
interface NativeCommands {
+methodInt: (viewRef: React.ElementRef<NativeType>, a: WithDefault<string, 'hi'>) => void;
}
```
This change separates out the things that are allowed in commands from what's allowed in props.
Commands should be very similar to what's allowed in native modules, but it isn't exact enough to be able to merge those.
Differential Revision: D67806818
Summary: This will be needed for the compat-check. Changelog: [Internal] Differential Revision: D67806831
Contributor
|
This pull request was exported from Phabricator. Differential Revision: D67806838 |
eb38ff3 to
fa34128
Compare
elicwhite
added a commit
to elicwhite/react-native
that referenced
this pull request
Jan 6, 2025
…48476) Summary: Pull Request resolved: facebook#48476 Command param array types were generating invalid schemas due to untyped parser code. The invalid schemas occurred for any alias type, including custom objects and basics like Int32. This was also inconsistent between Flow and TypeScript. We already had one component utilizing this issue, so this just codifies that support into the schema so it reflects reality. This is only a partial solution. The more full solution would be to fully encode the custom types in the schemas like we do for Native Modules. # More Information: Tl;dr, DebuggingOverlay is abusing a FlowFixMe in codegen commands. ## The problem: The [CodegenSchema](https://www.internalfb.com/code/fbsource/[d3ab2f79b377]/xplat/js/react-native-github/packages/react-native-codegen/src/CodegenSchema.js?lines=220) should be the source of truth for anything that can be in the schema. If something is in the schema that isn't allowed by the types, that's a bug. We have a bug. I'm adding compat-check support for components and it's blowing up on prod schemas because DebuggingOverlay causes an invalid schema. ## The details: Support for Arrays as arguments in commands was added to the Codegen in D51866557. [Code Pointer](https://fburl.com/code/8yy1rm0p) The intention appears to be to support arrays of primitives. There is a TODO for supporting complex types. ``` interface NativeCommands { +addOverlays: ( viewRef: React.ElementRef<NativeType>, overlayColorsReadOnly: $ReadOnlyArray<string>, ) } ``` This support was added to TypeScript in D52046165 where it types the allowed Array arguments to be only ``` { readonly type: 'ArrayTypeAnnotation'; readonly elementType: | Int32TypeAnnotation | DoubleTypeAnnotation | FloatTypeAnnotation | BooleanTypeAnnotation | StringTypeAnnotation }; ``` However, because the Parsers are treating the input type as `any`, it isn't safe to pass through an input value into the schema as Flow won't catch mismatches. The Flow parser just passes it through: ``` { type: 'ArrayTypeAnnotation', elementType: { // TODO: T172453752 support complex type annotation for array element type: paramValue.typeParameters.params[0].type, } ``` Whereas the TypeScript parser has the more correct behavior of validating the inputs and returning specific outputs. Unfortunately, the return type is also typed here as $FlowFixMe, losing most of the benefits. ``` function getPrimitiveTypeAnnotation(type: string): $FlowFixMe { switch (type) { case 'Int32': return { type: 'Int32TypeAnnotation', }; case 'Double': return { type: 'DoubleTypeAnnotation', }; case 'Float': return { type: 'FloatTypeAnnotation', }; case 'TSBooleanKeyword': return { type: 'BooleanTypeAnnotation', }; case 'Stringish': case 'TSStringKeyword': return { type: 'StringTypeAnnotation', }; default: throw new Error(`Unknown primitive type "${type}"`); } } ``` [DebuggingOverlay](https://fburl.com/code/zfe3ipq7) is abusing this gap in the Flow parser by sticking an Array of Objects in. ``` export type ElementRectangle = { x: number, y: number, width: number, height: number, }; ... +highlightElements: ( viewRef: React.ElementRef<DebuggingOverlayNativeComponentType>, elements: $ReadOnlyArray<ElementRectangle>, ) => void; ... ``` This isn't allowed in the schema, but it seems to fall through the holes of the flow parser and generators. The resulting schema from Flow is this. Note the GenericTypeAnnotation which isn't allowed to be in the schema. ``` { "name": "highlightElements", "optional": false, "typeAnnotation": { "type": "FunctionTypeAnnotation", "params": [ { "name": "elements", "optional": false, "typeAnnotation": { "type": "ArrayTypeAnnotation", "elementType": { "type": "GenericTypeAnnotation" } } } ], "returnTypeAnnotation": { "type": "VoidTypeAnnotation" } }, ``` The TypeScript parser fails with `Error: Unsupported type annotation: GenericTypeAnnotation`. The generators don't seem to check beyond the ArrayTypeAnnotation so they fall through to generating generic arrays. ``` // ios elements:(const NSArray *)elements // android ReadableArray locations ``` ## So how do I fix this? I think there are a couple of different options here. The key problem is that the Schema types need to represent reality of what can be in the schema. 1. We revert DebuggingOverlay to not use features that aren't supported (I assume nobody would be happy with this, but the change shouldn't have been made in the first place) 2. **(This is the approach taken in this diff)** We add MixedTypeAnnotation to the allowed types in Command arrays and have it generate that and add official support for that to the TypeScript parser as well. That is probably the quickest and easiest approach. It leaves the same type unsafety we have today on the native side. 3. NativeModules seem to have a lot more complex type safety here. They persist the alias type in the schema so that the CompatCheck can check them on changes. And then in C++ they generate structs and RCTConvert functions although for Java and ObjC it looks like they just use the same untyped native code. The matching approach here would be to add `aliasMap` and the whole data to the schema for commands, use that for the compat check, and still generate the same unsafe native code. ``` export type ObjectAlias = {| x: number, y: number, |}; export interface Spec extends TurboModule { +getAlias: (a: ObjectAlias) => string; } ``` stores the ObjectAlias in the schema ``` { "aliasMap": { "ObjectAlias": { "type": "ObjectTypeAnnotation", "properties": [ { "name": "x", "optional": false, "typeAnnotation": { "type": "NumberTypeAnnotation" } }, { "name": "y", "optional": false, "typeAnnotation": { "type": "NumberTypeAnnotation" } }, ] } }, "spec": { "methods": [ { "name": "getAlias", "optional": false, "typeAnnotation": { "type": "FunctionTypeAnnotation", "returnTypeAnnotation": { "type": "StringTypeAnnotation" }, "params": [ { "name": "a", "optional": false, "typeAnnotation": { "type": "TypeAliasTypeAnnotation", "name": "ObjectAlias" } } ] } } ] } } ``` and then generates the appropriate structs on the native side and generates [this](https://www.internalfb.com/code/fbsource/[d3ab2f79b377]/xplat/js/react-native-github/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap?lines=818) ``` Reviewed By: hoxyq Differential Revision: D67806838
…48476) Summary: Pull Request resolved: facebook#48476 Command param array types were generating invalid schemas due to untyped parser code. The invalid schemas occurred for any alias type, including custom objects and basics like Int32. This was also inconsistent between Flow and TypeScript. We already had one component utilizing this issue, so this just codifies that support into the schema so it reflects reality. This is only a partial solution. The more full solution would be to fully encode the custom types in the schemas like we do for Native Modules. # More Information: Tl;dr, DebuggingOverlay is abusing a FlowFixMe in codegen commands. ## The problem: The [CodegenSchema](https://www.internalfb.com/code/fbsource/[d3ab2f79b377]/xplat/js/react-native-github/packages/react-native-codegen/src/CodegenSchema.js?lines=220) should be the source of truth for anything that can be in the schema. If something is in the schema that isn't allowed by the types, that's a bug. We have a bug. I'm adding compat-check support for components and it's blowing up on prod schemas because DebuggingOverlay causes an invalid schema. ## The details: Support for Arrays as arguments in commands was added to the Codegen in D51866557. [Code Pointer](https://fburl.com/code/8yy1rm0p) The intention appears to be to support arrays of primitives. There is a TODO for supporting complex types. ``` interface NativeCommands { +addOverlays: ( viewRef: React.ElementRef<NativeType>, overlayColorsReadOnly: $ReadOnlyArray<string>, ) } ``` This support was added to TypeScript in D52046165 where it types the allowed Array arguments to be only ``` { readonly type: 'ArrayTypeAnnotation'; readonly elementType: | Int32TypeAnnotation | DoubleTypeAnnotation | FloatTypeAnnotation | BooleanTypeAnnotation | StringTypeAnnotation }; ``` However, because the Parsers are treating the input type as `any`, it isn't safe to pass through an input value into the schema as Flow won't catch mismatches. The Flow parser just passes it through: ``` { type: 'ArrayTypeAnnotation', elementType: { // TODO: T172453752 support complex type annotation for array element type: paramValue.typeParameters.params[0].type, } ``` Whereas the TypeScript parser has the more correct behavior of validating the inputs and returning specific outputs. Unfortunately, the return type is also typed here as $FlowFixMe, losing most of the benefits. ``` function getPrimitiveTypeAnnotation(type: string): $FlowFixMe { switch (type) { case 'Int32': return { type: 'Int32TypeAnnotation', }; case 'Double': return { type: 'DoubleTypeAnnotation', }; case 'Float': return { type: 'FloatTypeAnnotation', }; case 'TSBooleanKeyword': return { type: 'BooleanTypeAnnotation', }; case 'Stringish': case 'TSStringKeyword': return { type: 'StringTypeAnnotation', }; default: throw new Error(`Unknown primitive type "${type}"`); } } ``` [DebuggingOverlay](https://fburl.com/code/zfe3ipq7) is abusing this gap in the Flow parser by sticking an Array of Objects in. ``` export type ElementRectangle = { x: number, y: number, width: number, height: number, }; ... +highlightElements: ( viewRef: React.ElementRef<DebuggingOverlayNativeComponentType>, elements: $ReadOnlyArray<ElementRectangle>, ) => void; ... ``` This isn't allowed in the schema, but it seems to fall through the holes of the flow parser and generators. The resulting schema from Flow is this. Note the GenericTypeAnnotation which isn't allowed to be in the schema. ``` { "name": "highlightElements", "optional": false, "typeAnnotation": { "type": "FunctionTypeAnnotation", "params": [ { "name": "elements", "optional": false, "typeAnnotation": { "type": "ArrayTypeAnnotation", "elementType": { "type": "GenericTypeAnnotation" } } } ], "returnTypeAnnotation": { "type": "VoidTypeAnnotation" } }, ``` The TypeScript parser fails with `Error: Unsupported type annotation: GenericTypeAnnotation`. The generators don't seem to check beyond the ArrayTypeAnnotation so they fall through to generating generic arrays. ``` // ios elements:(const NSArray *)elements // android ReadableArray locations ``` ## So how do I fix this? I think there are a couple of different options here. The key problem is that the Schema types need to represent reality of what can be in the schema. 1. We revert DebuggingOverlay to not use features that aren't supported (I assume nobody would be happy with this, but the change shouldn't have been made in the first place) 2. **(This is the approach taken in this diff)** We add MixedTypeAnnotation to the allowed types in Command arrays and have it generate that and add official support for that to the TypeScript parser as well. That is probably the quickest and easiest approach. It leaves the same type unsafety we have today on the native side. 3. NativeModules seem to have a lot more complex type safety here. They persist the alias type in the schema so that the CompatCheck can check them on changes. And then in C++ they generate structs and RCTConvert functions although for Java and ObjC it looks like they just use the same untyped native code. The matching approach here would be to add `aliasMap` and the whole data to the schema for commands, use that for the compat check, and still generate the same unsafe native code. ``` export type ObjectAlias = {| x: number, y: number, |}; export interface Spec extends TurboModule { +getAlias: (a: ObjectAlias) => string; } ``` stores the ObjectAlias in the schema ``` { "aliasMap": { "ObjectAlias": { "type": "ObjectTypeAnnotation", "properties": [ { "name": "x", "optional": false, "typeAnnotation": { "type": "NumberTypeAnnotation" } }, { "name": "y", "optional": false, "typeAnnotation": { "type": "NumberTypeAnnotation" } }, ] } }, "spec": { "methods": [ { "name": "getAlias", "optional": false, "typeAnnotation": { "type": "FunctionTypeAnnotation", "returnTypeAnnotation": { "type": "StringTypeAnnotation" }, "params": [ { "name": "a", "optional": false, "typeAnnotation": { "type": "TypeAliasTypeAnnotation", "name": "ObjectAlias" } } ] } } ] } } ``` and then generates the appropriate structs on the native side and generates [this](https://www.internalfb.com/code/fbsource/[d3ab2f79b377]/xplat/js/react-native-github/packages/react-native-codegen/src/generators/modules/__tests__/__snapshots__/GenerateModuleHObjCpp-test.js.snap?lines=818) ``` Reviewed By: hoxyq Differential Revision: D67806838
Contributor
|
This pull request was exported from Phabricator. Differential Revision: D67806838 |
fa34128 to
e36140c
Compare
Contributor
|
This pull request has been merged in 25c673e. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Labels
CLA Signed
This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
fb-exported
Merged
This PR has been merged.
p: Facebook
Partner: Facebook
Partner
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary:
Command param array types were generating invalid schemas due to untyped parser code. The invalid schemas occurred for any alias type, including custom objects and basics like Int32. This was also inconsistent between Flow and TypeScript.
We already had one component utilizing this issue, so this just codifies that support into the schema so it reflects reality. This is only a partial solution. The more full solution would be to fully encode the custom types in the schemas like we do for Native Modules.
More Information:
Tl;dr, DebuggingOverlay is abusing a FlowFixMe in codegen commands.
The problem:
The CodegenSchema should be the source of truth for anything that can be in the schema. If something is in the schema that isn't allowed by the types, that's a bug. We have a bug. I'm adding compat-check support for components and it's blowing up on prod schemas because DebuggingOverlay causes an invalid schema.
The details:
Support for Arrays as arguments in commands was added to the Codegen in D51866557. Code Pointer
The intention appears to be to support arrays of primitives. There is a TODO for supporting complex types.
This support was added to TypeScript in D52046165 where it types the allowed Array arguments to be only
However, because the Parsers are treating the input type as
any, it isn't safe to pass through an input value into the schema as Flow won't catch mismatches.The Flow parser just passes it through:
Whereas the TypeScript parser has the more correct behavior of validating the inputs and returning specific outputs. Unfortunately, the return type is also typed here as $FlowFixMe, losing most of the benefits.
DebuggingOverlay is abusing this gap in the Flow parser by sticking an Array of Objects in.
This isn't allowed in the schema, but it seems to fall through the holes of the flow parser and generators.
The resulting schema from Flow is this. Note the GenericTypeAnnotation which isn't allowed to be in the schema.
The TypeScript parser fails with
Error: Unsupported type annotation: GenericTypeAnnotation.The generators don't seem to check beyond the ArrayTypeAnnotation so they fall through to generating generic arrays.
So how do I fix this?
I think there are a couple of different options here. The key problem is that the Schema types need to represent reality of what can be in the schema.
aliasMapand the whole data to the schema for commands, use that for the compat check, and still generate the same unsafe native code.stores the ObjectAlias in the schema
and then generates the appropriate structs on the native side and generates this