Skip to content

Commit cde8f96

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: ac69abc Pull Request resolved: #30868
1 parent c8288ee commit cde8f96

9 files changed

+548
-14
lines changed

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
}
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+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
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+
export const FIXTURE_ENTRYPOINT = {
22+
fn: Component,
23+
params: [{a: 0, b: 0}],
24+
sequentialRenders: [
25+
{a: 0, b: 0},
26+
{a: 1, b: 0},
27+
{a: 1, b: 1},
28+
{a: 1, b: 2},
29+
{a: 2, b: 2},
30+
{a: 3, b: 2},
31+
{a: 0, b: 0},
32+
],
33+
};
34+
35+
```
36+
37+
## Code
38+
39+
```javascript
40+
import { c as _c } from "react/compiler-runtime";
41+
import { useMemo } from "react";
42+
import useHook from "shared-runtime";
43+
44+
export function Component(t0) {
45+
const $ = _c(17);
46+
const { a, b } = t0;
47+
let t1;
48+
let t2;
49+
if ($[0] !== a) {
50+
t2 = { a };
51+
$[0] = a;
52+
$[1] = t2;
53+
} else {
54+
t2 = $[1];
55+
}
56+
t1 = t2;
57+
const item1 = t1;
58+
let t3;
59+
let t4;
60+
if ($[2] !== b) {
61+
t4 = { b };
62+
$[2] = b;
63+
$[3] = t4;
64+
} else {
65+
t4 = $[3];
66+
}
67+
t3 = t4;
68+
const item2 = t3;
69+
useHook(item1, item2);
70+
let t5;
71+
if ($[4] !== a) {
72+
t5 = [a];
73+
$[4] = a;
74+
$[5] = t5;
75+
} else {
76+
t5 = $[5];
77+
}
78+
let t6;
79+
if ($[6] !== t5 || $[7] !== item1) {
80+
t6 = <ValidateMemoization inputs={t5} output={item1} />;
81+
$[6] = t5;
82+
$[7] = item1;
83+
$[8] = t6;
84+
} else {
85+
t6 = $[8];
86+
}
87+
let t7;
88+
if ($[9] !== b) {
89+
t7 = [b];
90+
$[9] = b;
91+
$[10] = t7;
92+
} else {
93+
t7 = $[10];
94+
}
95+
let t8;
96+
if ($[11] !== t7 || $[12] !== item2) {
97+
t8 = <ValidateMemoization inputs={t7} output={item2} />;
98+
$[11] = t7;
99+
$[12] = item2;
100+
$[13] = t8;
101+
} else {
102+
t8 = $[13];
103+
}
104+
let t9;
105+
if ($[14] !== t6 || $[15] !== t8) {
106+
t9 = (
107+
<>
108+
{t6}
109+
{t8}
110+
</>
111+
);
112+
$[14] = t6;
113+
$[15] = t8;
114+
$[16] = t9;
115+
} else {
116+
t9 = $[16];
117+
}
118+
return t9;
119+
}
120+
121+
export const FIXTURE_ENTRYPOINT = {
122+
fn: Component,
123+
params: [{ a: 0, b: 0 }],
124+
sequentialRenders: [
125+
{ a: 0, b: 0 },
126+
{ a: 1, b: 0 },
127+
{ a: 1, b: 1 },
128+
{ a: 1, b: 2 },
129+
{ a: 2, b: 2 },
130+
{ a: 3, b: 2 },
131+
{ a: 0, b: 0 },
132+
],
133+
};
134+
135+
```
136+
137+
### Eval output
138+
(kind: ok) [[ (exception in render) ReferenceError: ValidateMemoization is not defined ]]
139+
[[ (exception in render) ReferenceError: ValidateMemoization is not defined ]]
140+
[[ (exception in render) ReferenceError: ValidateMemoization is not defined ]]
141+
[[ (exception in render) ReferenceError: ValidateMemoization is not defined ]]
142+
[[ (exception in render) ReferenceError: ValidateMemoization is not defined ]]
143+
[[ (exception in render) ReferenceError: ValidateMemoization is not defined ]]
144+
[[ (exception in render) ReferenceError: ValidateMemoization is not defined ]]
145+
logs: [{ a: 0 },{ b: 0 },{ a: 0 },{ b: 0 },{ a: 1 },{ b: 1 },{ a: 1 },{ b: 2 },{ a: 2 },{ b: 2 },{ a: 3 },{ b: 2 },{ a: 0 },{ b: 0 }]

0 commit comments

Comments
 (0)