Skip to content

Commit ecb81c8

Browse files
committed
[compiler] Infer return types of function expressions
Uses the returnIdentifier added in the previous PR to provide a stable identifier for which we can infer a return type for functions, then wires up the equations in InferTypes to infer the type. ghstack-source-id: c46c802 Pull Request resolved: #30785
1 parent af29925 commit ecb81c8

15 files changed

+232
-133
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export function printFunction(fn: HIRFunction): string {
7272
if (definition.length !== 0) {
7373
output.push(definition);
7474
}
75+
output.push(printType(fn.returnIdentifier.type));
7576
output.push(printHIR(fn.body));
7677
output.push(...fn.directives);
7778
return output.join('\n');
@@ -555,7 +556,8 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
555556
}
556557
})
557558
.join(', ') ?? '';
558-
value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]:\n${fn}`;
559+
const type = printType(instrValue.loweredFunc.func.returnIdentifier.type).trim();
560+
value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type!== '' ? ` return${type}` : ''}:\n${fn}`;
559561
break;
560562
}
561563
case 'TaggedTemplateExpression': {

compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ function apply(func: HIRFunction, unifier: Unifier): void {
8888
}
8989
}
9090
}
91+
func.returnIdentifier.type = unifier.get(func.returnIdentifier.type);
9192
}
9293

9394
type TypeEquation = {
@@ -122,6 +123,7 @@ function* generate(
122123
}
123124

124125
const names = new Map();
126+
const returnTypes: Array<Type> = [];
125127
for (const [_, block] of func.body.blocks) {
126128
for (const phi of block.phis) {
127129
yield equation(phi.type, {
@@ -133,6 +135,18 @@ function* generate(
133135
for (const instr of block.instructions) {
134136
yield* generateInstructionTypes(func.env, names, instr);
135137
}
138+
const terminal = block.terminal;
139+
if (terminal.kind === 'return') {
140+
returnTypes.push(terminal.value.identifier.type);
141+
}
142+
}
143+
if (returnTypes.length > 1) {
144+
yield equation(func.returnIdentifier.type, {
145+
kind: 'Phi',
146+
operands: returnTypes,
147+
});
148+
} else if (returnTypes.length === 1) {
149+
yield equation(func.returnIdentifier.type, returnTypes[0]!);
136150
}
137151
}
138152

@@ -346,7 +360,7 @@ function* generateInstructionTypes(
346360

347361
case 'FunctionExpression': {
348362
yield* generate(value.loweredFunc.func);
349-
yield equation(left, {kind: 'Object', shapeId: BuiltInFunctionId});
363+
yield equation(left, {kind: 'Function', shapeId: BuiltInFunctionId, return: value.loweredFunc.func.returnIdentifier.type});
350364
break;
351365
}
352366

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-declarations-and-locals.expect.md

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import { c as _c } from "react/compiler-runtime";
4141
import { useFragment } from "shared-runtime";
4242

4343
function Component(props) {
44-
const $ = _c(4);
44+
const $ = _c(8);
4545
const post = useFragment(
4646
graphql`
4747
fragment F on T {
@@ -50,33 +50,45 @@ function Component(props) {
5050
`,
5151
props.post,
5252
);
53-
let t0;
53+
let media;
54+
let onClick;
5455
if ($[0] !== post) {
5556
const allUrls = [];
5657

57-
const { media, comments, urls } = post;
58+
const { media: t0, comments, urls } = post;
59+
media = t0;
5860
let t1;
59-
if ($[2] !== comments.length) {
61+
if ($[3] !== comments.length) {
6062
t1 = (e) => {
6163
if (!comments.length) {
6264
return;
6365
}
6466

6567
console.log(comments.length);
6668
};
67-
$[2] = comments.length;
68-
$[3] = t1;
69+
$[3] = comments.length;
70+
$[4] = t1;
6971
} else {
70-
t1 = $[3];
72+
t1 = $[4];
7173
}
72-
const onClick = t1;
74+
onClick = t1;
7375

7476
allUrls.push(...urls);
75-
t0 = <Media media={media} onClick={onClick} />;
7677
$[0] = post;
77-
$[1] = t0;
78+
$[1] = media;
79+
$[2] = onClick;
80+
} else {
81+
media = $[1];
82+
onClick = $[2];
83+
}
84+
let t0;
85+
if ($[5] !== media || $[6] !== onClick) {
86+
t0 = <Media media={media} onClick={onClick} />;
87+
$[5] = media;
88+
$[6] = onClick;
89+
$[7] = t0;
7890
} else {
79-
t0 = $[1];
91+
t0 = $[7];
8092
}
8193
return t0;
8294
}

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/flag-enable-emit-hook-guards.expect.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ import {
4949

5050
const MyContext = createContext("my context value");
5151
function Component(t0) {
52-
const $ = _c(4);
52+
const $ = _c(5);
5353
try {
5454
$dispatcherGuard(0);
5555
const { value } = t0;
@@ -71,21 +71,24 @@ function Component(t0) {
7171
})();
7272
print(value, state);
7373
let t2;
74-
let t3;
7574
if ($[1] !== state) {
7675
t2 = () => {
7776
if (state === 4) {
7877
setState(5);
7978
}
8079
};
81-
82-
t3 = [state];
8380
$[1] = state;
8481
$[2] = t2;
85-
$[3] = t3;
8682
} else {
8783
t2 = $[2];
88-
t3 = $[3];
84+
}
85+
let t3;
86+
if ($[3] !== state) {
87+
t3 = [state];
88+
$[3] = state;
89+
$[4] = t3;
90+
} else {
91+
t3 = $[4];
8992
}
9093
(function () {
9194
try {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-simple-const-declaration.expect.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,17 @@ export const FIXTURE_ENTRYPOINT = {
2525
import { c as _c } from "react/compiler-runtime";
2626
function hoisting() {
2727
const $ = _c(1);
28-
let t0;
28+
let foo;
2929
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30-
const foo = () => bar + baz;
30+
foo = () => bar + baz;
3131

3232
const bar = 3;
3333
const baz = 2;
34-
t0 = foo();
35-
$[0] = t0;
34+
$[0] = foo;
3635
} else {
37-
t0 = $[0];
36+
foo = $[0];
3837
}
39-
return t0;
38+
return foo();
4039
}
4140

4241
export const FIXTURE_ENTRYPOINT = {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hoisting-simple-let-declaration.expect.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,17 @@ export const FIXTURE_ENTRYPOINT = {
2525
import { c as _c } from "react/compiler-runtime";
2626
function hoisting() {
2727
const $ = _c(1);
28-
let t0;
28+
let foo;
2929
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
30-
const foo = () => bar + baz;
30+
foo = () => bar + baz;
3131

3232
let bar = 3;
3333
let baz = 2;
34-
t0 = foo();
35-
$[0] = t0;
34+
$[0] = foo;
3635
} else {
37-
t0 = $[0];
36+
foo = $[0];
3837
}
39-
return t0;
38+
return foo();
4039
}
4140

4241
export const FIXTURE_ENTRYPOINT = {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/jsx-ternary-local-variable.expect.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,25 @@ import { c as _c } from "react/compiler-runtime";
2424
import { RenderPropAsChild, StaticText1, StaticText2 } from "shared-runtime";
2525

2626
function Component(props) {
27-
const $ = _c(2);
27+
const $ = _c(4);
2828
const Foo = props.showText1 ? StaticText1 : StaticText2;
2929
let t0;
3030
if ($[0] !== Foo) {
31-
t0 = <RenderPropAsChild items={[() => <Foo key="0" />]} />;
31+
t0 = () => <Foo key="0" />;
3232
$[0] = Foo;
3333
$[1] = t0;
3434
} else {
3535
t0 = $[1];
3636
}
37-
return t0;
37+
let t1;
38+
if ($[2] !== t0) {
39+
t1 = <RenderPropAsChild items={[t0]} />;
40+
$[2] = t0;
41+
$[3] = t1;
42+
} else {
43+
t1 = $[3];
44+
}
45+
return t1;
3846
}
3947

4048
export const FIXTURE_ENTRYPOINT = {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-deps-subset-of-decls.expect.md

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,55 @@ import { c as _c } from "react/compiler-runtime";
3333
import { useState } from "react";
3434

3535
function Component() {
36-
const $ = _c(2);
36+
const $ = _c(11);
3737
const [count, setCount] = useState(0);
3838
let t0;
3939
if ($[0] !== count) {
40-
t0 = (
41-
<div>
42-
<button onClick={() => setCount(count - 1)}>Decrement</button>
43-
44-
<button onClick={() => setCount(count + 1)}>Increment</button>
45-
</div>
46-
);
40+
t0 = () => setCount(count - 1);
4741
$[0] = count;
4842
$[1] = t0;
4943
} else {
5044
t0 = $[1];
5145
}
52-
return t0;
46+
let t1;
47+
if ($[2] !== t0) {
48+
t1 = <button onClick={t0}>Decrement</button>;
49+
$[2] = t0;
50+
$[3] = t1;
51+
} else {
52+
t1 = $[3];
53+
}
54+
let t2;
55+
if ($[4] !== count) {
56+
t2 = () => setCount(count + 1);
57+
$[4] = count;
58+
$[5] = t2;
59+
} else {
60+
t2 = $[5];
61+
}
62+
let t3;
63+
if ($[6] !== t2) {
64+
t3 = <button onClick={t2}>Increment</button>;
65+
$[6] = t2;
66+
$[7] = t3;
67+
} else {
68+
t3 = $[7];
69+
}
70+
let t4;
71+
if ($[8] !== t1 || $[9] !== t3) {
72+
t4 = (
73+
<div>
74+
{t1}
75+
{t3}
76+
</div>
77+
);
78+
$[8] = t1;
79+
$[9] = t3;
80+
$[10] = t4;
81+
} else {
82+
t4 = $[10];
83+
}
84+
return t4;
5385
}
5486

5587
export const FIXTURE_ENTRYPOINT = {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/merge-consecutive-scopes-reordering.expect.md

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,47 +34,59 @@ import { useState } from "react";
3434
import { Stringify } from "shared-runtime";
3535

3636
function Component() {
37-
const $ = _c(7);
37+
const $ = _c(10);
3838
const [state, setState] = useState(0);
3939
let t0;
40-
let t1;
4140
if ($[0] !== state) {
42-
t0 = (
43-
<button data-testid="button" onClick={() => setState(state + 1)}>
44-
increment
45-
</button>
46-
);
47-
t1 = <span>{state}</span>;
41+
t0 = () => setState(state + 1);
4842
$[0] = state;
4943
$[1] = t0;
50-
$[2] = t1;
5144
} else {
5245
t0 = $[1];
53-
t1 = $[2];
46+
}
47+
let t1;
48+
if ($[2] !== t0) {
49+
t1 = (
50+
<button data-testid="button" onClick={t0}>
51+
increment
52+
</button>
53+
);
54+
$[2] = t0;
55+
$[3] = t1;
56+
} else {
57+
t1 = $[3];
5458
}
5559
let t2;
56-
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
57-
t2 = <Stringify text="Counter" />;
58-
$[3] = t2;
60+
if ($[4] !== state) {
61+
t2 = <span>{state}</span>;
62+
$[4] = state;
63+
$[5] = t2;
5964
} else {
60-
t2 = $[3];
65+
t2 = $[5];
6166
}
6267
let t3;
63-
if ($[4] !== t1 || $[5] !== t0) {
64-
t3 = (
68+
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
69+
t3 = <Stringify text="Counter" />;
70+
$[6] = t3;
71+
} else {
72+
t3 = $[6];
73+
}
74+
let t4;
75+
if ($[7] !== t2 || $[8] !== t1) {
76+
t4 = (
6577
<div>
78+
{t3}
6679
{t2}
6780
{t1}
68-
{t0}
6981
</div>
7082
);
71-
$[4] = t1;
72-
$[5] = t0;
73-
$[6] = t3;
83+
$[7] = t2;
84+
$[8] = t1;
85+
$[9] = t4;
7486
} else {
75-
t3 = $[6];
87+
t4 = $[9];
7688
}
77-
return t3;
89+
return t4;
7890
}
7991

8092
export const FIXTURE_ENTRYPOINT = {

0 commit comments

Comments
 (0)