-
Notifications
You must be signed in to change notification settings - Fork 24.4k
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
Fixing schema types for component command params of Arrays #48476
Open
elicwhite
wants to merge
5
commits into
facebook:main
Choose a base branch
from
elicwhite:export-D67806838
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains 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
facebook-github-bot
added
CLA Signed
This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.
p: Facebook
Partner: Facebook
Partner
labels
Jan 3, 2025
This pull request was exported from Phabricator. Differential Revision: D67806838 |
…ok#48477) 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
…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
elicwhite
force-pushed
the
export-D67806838
branch
from
January 3, 2025 23:32
71afc31
to
eb38ff3
Compare
This pull request was exported from Phabricator. Differential Revision: D67806838 |
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
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.
aliasMap
and 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