@samitouri / QOS-React / commits / a26e3f403e

[compiler] Expect component props annotations to be potential objects

Summary: We now expect that candidate components that have Flow or TS type annotations on their first parameters have annotations that are potentially objects--this lets us reject compiling functions that explicitly take e.g. `number` as a parameter. ghstack-source-id: e2c23348265b7ef651232b962ed7be7f6fed1930 Pull Request resolved: https://github.com/facebook/react/pull/29866

Mike Vitousek committed Jun 11, 2024 at 14:08 UTC a26e3f403e1d732ecde87db119b4ce21cee20038
3 files changed +112 -10
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
+61 -10
@@ -651,24 +651,75 @@ function isMemoCallback(path: NodePath<t.Expression>): boolean {
651 );
652 }
653
654 +function isValidPropsAnnotation(
655 + annot: t.TypeAnnotation | t.TSTypeAnnotation | t.Noop | null | undefined
656 +): boolean {
657 + if (annot == null) {
658 + return true;
659 + } else if (annot.type === "TSTypeAnnotation") {
660 + switch (annot.typeAnnotation.type) {
661 + case "TSArrayType":
662 + case "TSBigIntKeyword":
663 + case "TSBooleanKeyword":
664 + case "TSConstructorType":
665 + case "TSFunctionType":
666 + case "TSLiteralType":
667 + case "TSNeverKeyword":
668 + case "TSNumberKeyword":
669 + case "TSStringKeyword":
670 + case "TSSymbolKeyword":
671 + case "TSTupleType":
672 + return false;
673 + }
674 + return true;
675 + } else if (annot.type === "TypeAnnotation") {
676 + switch (annot.typeAnnotation.type) {
677 + case "ArrayTypeAnnotation":
678 + case "BooleanLiteralTypeAnnotation":
679 + case "BooleanTypeAnnotation":
680 + case "EmptyTypeAnnotation":
681 + case "FunctionTypeAnnotation":
682 + case "NumberLiteralTypeAnnotation":
683 + case "NumberTypeAnnotation":
684 + case "StringLiteralTypeAnnotation":
685 + case "StringTypeAnnotation":
686 + case "SymbolTypeAnnotation":
687 + case "ThisTypeAnnotation":
688 + case "TupleTypeAnnotation":
689 + return false;
690 + }
691 + return true;
692 + } else if (annot.type === "Noop") {
693 + return true;
694 + } else {
695 + assertExhaustive(annot, `Unexpected annotation node \`${annot}\``);
696 + }
697 +}
698 +
699 function isValidComponentParams(
700 params: Array<NodePath<t.Identifier | t.Pattern | t.RestElement>>
701 ): boolean {
702 if (params.length === 0) {
703 return true;
659 - } else if (params.length === 1) {
660 - return !params[0].isRestElement();
661 - } else if (params.length === 2) {
662 - // check if second param might be a ref
663 - if (params[1].isIdentifier()) {
704 + } else if (params.length > 0 && params.length <= 2) {
705 + if (!isValidPropsAnnotation(params[0].node.typeAnnotation)) {
706 + return false;
707 + }
708 +
709 + if (params.length === 1) {
710 + return !params[0].isRestElement();
711 + } else if (params[1].isIdentifier()) {
712 + // check if second param might be a ref
713 const { name } = params[1].node;
714 return name.includes("ref") || name.includes("Ref");
715 + } else {
716 + /**
717 + * Otherwise, avoid helper functions that take more than one argument.
718 + * Helpers are _usually_ named with lowercase, but some code may
719 + * violate this rule
720 + */
721 + return false;
722 }
667 - /**
668 - * Otherwise, avoid helper functions that take more than one argument.
669 - * Helpers are _usually_ named with lowercase, but some code may
670 - * violate this rule
671 - */
723 }
724 return false;
725 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md new
+39
@@ -0,0 +1,39 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @compilationMode(infer)
6 +import { useIdentity, identity } from "shared-runtime";
7 +
8 +function Component(fakeProps: number) {
9 + const x = useIdentity(fakeProps);
10 + return identity(x);
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [42],
16 +};
17 +
18 +```
19 +
20 +## Code
21 +
22 +```javascript
23 +// @compilationMode(infer)
24 +import { useIdentity, identity } from "shared-runtime";
25 +
26 +function Component(fakeProps: number) {
27 + const x = useIdentity(fakeProps);
28 + return identity(x);
29 +}
30 +
31 +export const FIXTURE_ENTRYPOINT = {
32 + fn: Component,
33 + params: [42],
34 +};
35 +
36 +```
37 +
38 +### Eval output
39 +(kind: ok) 42
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts new
+12
@@ -0,0 +1,12 @@
1 +// @compilationMode(infer)
2 +import { useIdentity, identity } from "shared-runtime";
3 +
4 +function Component(fakeProps: number) {
5 + const x = useIdentity(fakeProps);
6 + return identity(x);
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: Component,
11 + params: [42],
12 +};