Skip to content

Commit 0755914

Browse files
committed
[compiler] Override type provider for hook-like names
If an imported name is hook-like but a type provider provides a non-hook type, we now override the returned value and treat it as a generic custom hook. This is meant as an extra check to prevent miscompilation. ghstack-source-id: 740a7cc Pull Request resolved: #30868
1 parent e56f4ae commit 0755914

9 files changed

Lines changed: 499 additions & 14 deletions

compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
Type,
3434
ValidatedIdentifier,
3535
ValueKind,
36+
getHookKindForType,
3637
makeBlockId,
3738
makeIdentifierId,
3839
makeIdentifierName,
@@ -788,14 +789,9 @@ export class Environment {
788789
);
789790
} else {
790791
const moduleType = this.#resolveModuleType(binding.module, loc);
792+
let propertyType: Type | null = null;
791793
if (moduleType !== null) {
792-
const importedType = this.getPropertyType(
793-
moduleType,
794-
binding.imported,
795-
);
796-
if (importedType != null) {
797-
return importedType;
798-
}
794+
propertyType = this.getPropertyType(moduleType, binding.imported);
799795
}
800796

801797
/**
@@ -806,9 +802,18 @@ export class Environment {
806802
* `import {useHook as foo} ...`
807803
* `import {foo as useHook} ...`
808804
*/
809-
return isHookName(binding.imported) || isHookName(binding.name)
810-
? this.#getCustomHookType()
811-
: null;
805+
const expectHook =
806+
isHookName(binding.imported) || isHookName(binding.name);
807+
if (expectHook) {
808+
if (
809+
propertyType &&
810+
getHookKindForType(this, propertyType) !== null
811+
) {
812+
return propertyType;
813+
}
814+
return this.#getCustomHookType();
815+
}
816+
return propertyType;
812817
}
813818
}
814819
case 'ImportDefault':
@@ -821,17 +826,27 @@ export class Environment {
821826
);
822827
} else {
823828
const moduleType = this.#resolveModuleType(binding.module, loc);
829+
let importedType: Type | null = null;
824830
if (moduleType !== null) {
825831
if (binding.kind === 'ImportDefault') {
826832
const defaultType = this.getPropertyType(moduleType, 'default');
827833
if (defaultType !== null) {
828-
return defaultType;
834+
importedType = defaultType;
829835
}
830836
} else {
831-
return moduleType;
837+
importedType = moduleType;
838+
}
839+
}
840+
if (isHookName(binding.name)) {
841+
if (
842+
importedType !== null &&
843+
getHookKindForType(this, importedType) !== null
844+
) {
845+
return importedType;
832846
}
847+
return this.#getCustomHookType();
833848
}
834-
return isHookName(binding.name) ? this.#getCustomHookType() : null;
849+
return importedType;
835850
}
836851
}
837852
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
2+
## Input
3+
4+
```javascript
5+
import {useMemo} from 'react';
6+
import {
7+
useArrayConcatNotTypedAsHook,
8+
ValidateMemoization,
9+
} from 'shared-runtime';
10+
11+
export function Component({a, b}) {
12+
const item1 = useMemo(() => [a], [a]);
13+
const item2 = useMemo(() => [b], [b]);
14+
const item3 = useArrayConcatNotTypedAsHook(item1, item2);
15+
16+
return (
17+
<>
18+
<ValidateMemoization inputs={[a, b]} output={item3} />
19+
</>
20+
);
21+
}
22+
23+
export const FIXTURE_ENTRYPOINT = {
24+
fn: Component,
25+
params: [{a: 0, b: 0}],
26+
sequentialRenders: [
27+
{a: 0, b: 0},
28+
{a: 1, b: 0},
29+
{a: 1, b: 1},
30+
{a: 1, b: 2},
31+
{a: 2, b: 2},
32+
{a: 3, b: 2},
33+
{a: 0, b: 0},
34+
],
35+
};
36+
37+
```
38+
39+
## Code
40+
41+
```javascript
42+
import { c as _c } from "react/compiler-runtime";
43+
import { useMemo } from "react";
44+
import {
45+
useArrayConcatNotTypedAsHook,
46+
ValidateMemoization,
47+
} from "shared-runtime";
48+
49+
export function Component(t0) {
50+
const $ = _c(10);
51+
const { a, b } = t0;
52+
let t1;
53+
let t2;
54+
if ($[0] !== a) {
55+
t2 = [a];
56+
$[0] = a;
57+
$[1] = t2;
58+
} else {
59+
t2 = $[1];
60+
}
61+
t1 = t2;
62+
const item1 = t1;
63+
let t3;
64+
let t4;
65+
if ($[2] !== b) {
66+
t4 = [b];
67+
$[2] = b;
68+
$[3] = t4;
69+
} else {
70+
t4 = $[3];
71+
}
72+
t3 = t4;
73+
const item2 = t3;
74+
const item3 = useArrayConcatNotTypedAsHook(item1, item2);
75+
let t5;
76+
if ($[4] !== a || $[5] !== b) {
77+
t5 = [a, b];
78+
$[4] = a;
79+
$[5] = b;
80+
$[6] = t5;
81+
} else {
82+
t5 = $[6];
83+
}
84+
let t6;
85+
if ($[7] !== t5 || $[8] !== item3) {
86+
t6 = (
87+
<>
88+
<ValidateMemoization inputs={t5} output={item3} />
89+
</>
90+
);
91+
$[7] = t5;
92+
$[8] = item3;
93+
$[9] = t6;
94+
} else {
95+
t6 = $[9];
96+
}
97+
return t6;
98+
}
99+
100+
export const FIXTURE_ENTRYPOINT = {
101+
fn: Component,
102+
params: [{ a: 0, b: 0 }],
103+
sequentialRenders: [
104+
{ a: 0, b: 0 },
105+
{ a: 1, b: 0 },
106+
{ a: 1, b: 1 },
107+
{ a: 1, b: 2 },
108+
{ a: 2, b: 2 },
109+
{ a: 3, b: 2 },
110+
{ a: 0, b: 0 },
111+
],
112+
};
113+
114+
```
115+
116+
### Eval output
117+
(kind: ok) <div>{"inputs":[0,0],"output":[0,0]}</div>
118+
<div>{"inputs":[1,0],"output":[1,0]}</div>
119+
<div>{"inputs":[1,1],"output":[1,1]}</div>
120+
<div>{"inputs":[1,2],"output":[1,2]}</div>
121+
<div>{"inputs":[2,2],"output":[2,2]}</div>
122+
<div>{"inputs":[3,2],"output":[3,2]}</div>
123+
<div>{"inputs":[0,0],"output":[0,0]}</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {useMemo} from 'react';
2+
import {
3+
useArrayConcatNotTypedAsHook,
4+
ValidateMemoization,
5+
} from 'shared-runtime';
6+
7+
export function Component({a, b}) {
8+
const item1 = useMemo(() => [a], [a]);
9+
const item2 = useMemo(() => [b], [b]);
10+
const item3 = useArrayConcatNotTypedAsHook(item1, item2);
11+
12+
return (
13+
<>
14+
<ValidateMemoization inputs={[a, b]} output={item3} />
15+
</>
16+
);
17+
}
18+
19+
export const FIXTURE_ENTRYPOINT = {
20+
fn: Component,
21+
params: [{a: 0, b: 0}],
22+
sequentialRenders: [
23+
{a: 0, b: 0},
24+
{a: 1, b: 0},
25+
{a: 1, b: 1},
26+
{a: 1, b: 2},
27+
{a: 2, b: 2},
28+
{a: 3, b: 2},
29+
{a: 0, b: 0},
30+
],
31+
};
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
2+
## Input
3+
4+
```javascript
5+
import {useMemo} from 'react';
6+
import useHook from 'shared-runtime';
7+
8+
export function Component({a, b}) {
9+
const item1 = useMemo(() => ({a}), [a]);
10+
const item2 = useMemo(() => ({b}), [b]);
11+
useHook(item1, item2);
12+
13+
return (
14+
<>
15+
<ValidateMemoization inputs={[a]} output={item1} />
16+
<ValidateMemoization inputs={[b]} output={item2} />
17+
</>
18+
);
19+
}
20+
21+
```
22+
23+
## Code
24+
25+
```javascript
26+
import { c as _c } from "react/compiler-runtime";
27+
import { useMemo } from "react";
28+
import useHook from "shared-runtime";
29+
30+
export function Component(t0) {
31+
const $ = _c(17);
32+
const { a, b } = t0;
33+
let t1;
34+
let t2;
35+
if ($[0] !== a) {
36+
t2 = { a };
37+
$[0] = a;
38+
$[1] = t2;
39+
} else {
40+
t2 = $[1];
41+
}
42+
t1 = t2;
43+
const item1 = t1;
44+
let t3;
45+
let t4;
46+
if ($[2] !== b) {
47+
t4 = { b };
48+
$[2] = b;
49+
$[3] = t4;
50+
} else {
51+
t4 = $[3];
52+
}
53+
t3 = t4;
54+
const item2 = t3;
55+
useHook(item1, item2);
56+
let t5;
57+
if ($[4] !== a) {
58+
t5 = [a];
59+
$[4] = a;
60+
$[5] = t5;
61+
} else {
62+
t5 = $[5];
63+
}
64+
let t6;
65+
if ($[6] !== t5 || $[7] !== item1) {
66+
t6 = <ValidateMemoization inputs={t5} output={item1} />;
67+
$[6] = t5;
68+
$[7] = item1;
69+
$[8] = t6;
70+
} else {
71+
t6 = $[8];
72+
}
73+
let t7;
74+
if ($[9] !== b) {
75+
t7 = [b];
76+
$[9] = b;
77+
$[10] = t7;
78+
} else {
79+
t7 = $[10];
80+
}
81+
let t8;
82+
if ($[11] !== t7 || $[12] !== item2) {
83+
t8 = <ValidateMemoization inputs={t7} output={item2} />;
84+
$[11] = t7;
85+
$[12] = item2;
86+
$[13] = t8;
87+
} else {
88+
t8 = $[13];
89+
}
90+
let t9;
91+
if ($[14] !== t6 || $[15] !== t8) {
92+
t9 = (
93+
<>
94+
{t6}
95+
{t8}
96+
</>
97+
);
98+
$[14] = t6;
99+
$[15] = t8;
100+
$[16] = t9;
101+
} else {
102+
t9 = $[16];
103+
}
104+
return t9;
105+
}
106+
107+
```
108+
109+
### Eval output
110+
(kind: exception) Fixture not implemented
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {useMemo} from 'react';
2+
import useHook from 'shared-runtime';
3+
4+
export function Component({a, b}) {
5+
const item1 = useMemo(() => ({a}), [a]);
6+
const item2 = useMemo(() => ({b}), [b]);
7+
useHook(item1, item2);
8+
9+
return (
10+
<>
11+
<ValidateMemoization inputs={[a]} output={item1} />
12+
<ValidateMemoization inputs={[b]} output={item2} />
13+
</>
14+
);
15+
}

0 commit comments

Comments
 (0)