Skip to content

Commit d7167c3

Browse files
committed
[compiler] Implement support for hoisted and recursive functions
Summary: Introduces a new binding kind for functions that allows them to be hoisted. Also has the result of causing all nested function declarations to be outputted as function declarations, not as let bindings. ghstack-source-id: fa40d49 Pull Request resolved: #30922
1 parent e78c936 commit d7167c3

14 files changed

+234
-139
lines changed

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,19 @@ function lowerStatement(
420420
// Already hoisted
421421
continue;
422422
}
423-
if (!binding.path.isVariableDeclarator()) {
423+
424+
let kind:
425+
| InstructionKind.Let
426+
| InstructionKind.HoistedConst
427+
| InstructionKind.HoistedLet
428+
| InstructionKind.HoistedFunction;
429+
if (binding.kind === 'const' || binding.kind === 'var') {
430+
kind = InstructionKind.HoistedConst;
431+
} else if (binding.kind === 'let') {
432+
kind = InstructionKind.HoistedLet;
433+
} else if (binding.path.isFunctionDeclaration()) {
434+
kind = InstructionKind.HoistedFunction;
435+
} else if (!binding.path.isVariableDeclarator()) {
424436
builder.errors.push({
425437
severity: ErrorSeverity.Todo,
426438
reason: 'Unsupported declaration type for hoisting',
@@ -429,11 +441,7 @@ function lowerStatement(
429441
loc: id.parentPath.node.loc ?? GeneratedSource,
430442
});
431443
continue;
432-
} else if (
433-
binding.kind !== 'const' &&
434-
binding.kind !== 'var' &&
435-
binding.kind !== 'let'
436-
) {
444+
} else {
437445
builder.errors.push({
438446
severity: ErrorSeverity.Todo,
439447
reason: 'Handle non-const declarations for hoisting',
@@ -443,6 +451,7 @@ function lowerStatement(
443451
});
444452
continue;
445453
}
454+
446455
const identifier = builder.resolveIdentifier(id);
447456
CompilerError.invariant(identifier.kind === 'Identifier', {
448457
reason:
@@ -456,13 +465,6 @@ function lowerStatement(
456465
reactive: false,
457466
loc: id.node.loc ?? GeneratedSource,
458467
};
459-
const kind =
460-
// Avoid double errors on var declarations, which we do not plan to support anyways
461-
binding.kind === 'const' || binding.kind === 'var'
462-
? InstructionKind.HoistedConst
463-
: binding.kind === 'let'
464-
? InstructionKind.HoistedLet
465-
: assertExhaustive(binding.kind, 'Unexpected binding kind');
466468
lowerValueToTemporary(builder, {
467469
kind: 'DeclareContext',
468470
lvalue: {
@@ -999,7 +1001,7 @@ function lowerStatement(
9991001
lowerAssignment(
10001002
builder,
10011003
stmt.node.loc ?? GeneratedSource,
1002-
InstructionKind.Let,
1004+
InstructionKind.Function,
10031005
id,
10041006
fn,
10051007
'Assignment',

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,9 @@ export enum InstructionKind {
746746

747747
// hoisted const declarations
748748
HoistedLet = 'HoistedLet',
749+
750+
HoistedFunction = 'HoistedFunction',
751+
Function = 'Function',
749752
}
750753

751754
function _staticInvariantInstructionValueHasLocation(
@@ -865,7 +868,8 @@ export type InstructionValue =
865868
kind:
866869
| InstructionKind.Let
867870
| InstructionKind.HoistedConst
868-
| InstructionKind.HoistedLet;
871+
| InstructionKind.HoistedLet
872+
| InstructionKind.HoistedFunction;
869873
place: Place;
870874
};
871875
loc: SourceLocation;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,12 @@ export function printLValue(lval: LValue): string {
765765
case InstructionKind.HoistedLet: {
766766
return `HoistedLet ${lvalue}$`;
767767
}
768+
case InstructionKind.Function: {
769+
return `Function ${lvalue}$`;
770+
}
771+
case InstructionKind.HoistedFunction: {
772+
return `HoistedFunction ${lvalue}$`;
773+
}
768774
default: {
769775
assertExhaustive(lval.kind, `Unexpected lvalue kind \`${lval.kind}\``);
770776
}

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -981,22 +981,12 @@ function codegenTerminal(
981981
suggestions: null,
982982
});
983983
case InstructionKind.Catch:
984-
CompilerError.invariant(false, {
985-
reason: 'Unexpected catch variable as for..in collection',
986-
description: null,
987-
loc: iterableItem.loc,
988-
suggestions: null,
989-
});
990984
case InstructionKind.HoistedConst:
991-
CompilerError.invariant(false, {
992-
reason: 'Unexpected HoistedConst variable in for..in collection',
993-
description: null,
994-
loc: iterableItem.loc,
995-
suggestions: null,
996-
});
997985
case InstructionKind.HoistedLet:
986+
case InstructionKind.HoistedFunction:
987+
case InstructionKind.Function:
998988
CompilerError.invariant(false, {
999-
reason: 'Unexpected HoistedLet variable in for..in collection',
989+
reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..in collection`,
1000990
description: null,
1001991
loc: iterableItem.loc,
1002992
suggestions: null,
@@ -1075,30 +1065,13 @@ function codegenTerminal(
10751065
varDeclKind = 'let' as const;
10761066
break;
10771067
case InstructionKind.Reassign:
1078-
CompilerError.invariant(false, {
1079-
reason:
1080-
'Destructure should never be Reassign as it would be an Object/ArrayPattern',
1081-
description: null,
1082-
loc: iterableItem.loc,
1083-
suggestions: null,
1084-
});
10851068
case InstructionKind.Catch:
1086-
CompilerError.invariant(false, {
1087-
reason: 'Unexpected catch variable as for..of collection',
1088-
description: null,
1089-
loc: iterableItem.loc,
1090-
suggestions: null,
1091-
});
10921069
case InstructionKind.HoistedConst:
1093-
CompilerError.invariant(false, {
1094-
reason: 'Unexpected HoistedConst variable in for..of collection',
1095-
description: null,
1096-
loc: iterableItem.loc,
1097-
suggestions: null,
1098-
});
10991070
case InstructionKind.HoistedLet:
1071+
case InstructionKind.HoistedFunction:
1072+
case InstructionKind.Function:
11001073
CompilerError.invariant(false, {
1101-
reason: 'Unexpected HoistedLet variable in for..of collection',
1074+
reason: `Unexpected ${iterableItem.value.lvalue.kind} variable in for..of collection`,
11021075
description: null,
11031076
loc: iterableItem.loc,
11041077
suggestions: null,
@@ -1261,6 +1234,35 @@ function codegenInstructionNullable(
12611234
t.variableDeclarator(codegenLValue(cx, lvalue), value),
12621235
]);
12631236
}
1237+
case InstructionKind.Function: {
1238+
CompilerError.invariant(instr.lvalue === null, {
1239+
reason: `Function declaration cannot be referenced as an expression`,
1240+
description: null,
1241+
loc: instr.value.loc,
1242+
suggestions: null,
1243+
});
1244+
const genLvalue = codegenLValue(cx, lvalue);
1245+
CompilerError.invariant(genLvalue.type === 'Identifier', {
1246+
reason: 'Expected an identifier as a function declaration lvalue',
1247+
description: null,
1248+
loc: instr.value.loc,
1249+
suggestions: null,
1250+
});
1251+
CompilerError.invariant(value?.type === 'FunctionExpression', {
1252+
reason: 'Expected a function as a function declaration value',
1253+
description: null,
1254+
loc: instr.value.loc,
1255+
suggestions: null,
1256+
});
1257+
return createFunctionDeclaration(
1258+
instr.loc,
1259+
genLvalue,
1260+
value.params,
1261+
value.body,
1262+
value.generator,
1263+
value.async,
1264+
);
1265+
}
12641266
case InstructionKind.Let: {
12651267
CompilerError.invariant(instr.lvalue === null, {
12661268
reason: `Const declaration cannot be referenced as an expression`,
@@ -1303,19 +1305,11 @@ function codegenInstructionNullable(
13031305
case InstructionKind.Catch: {
13041306
return t.emptyStatement();
13051307
}
1306-
case InstructionKind.HoistedLet: {
1307-
CompilerError.invariant(false, {
1308-
reason:
1309-
'Expected HoistedLet to have been pruned in PruneHoistedContexts',
1310-
description: null,
1311-
loc: instr.loc,
1312-
suggestions: null,
1313-
});
1314-
}
1315-
case InstructionKind.HoistedConst: {
1308+
case InstructionKind.HoistedLet:
1309+
case InstructionKind.HoistedConst:
1310+
case InstructionKind.HoistedFunction: {
13161311
CompilerError.invariant(false, {
1317-
reason:
1318-
'Expected HoistedConsts to have been pruned in PruneHoistedContexts',
1312+
reason: `Expected ${kind} to have been pruned in PruneHoistedContexts`,
13191313
description: null,
13201314
loc: instr.loc,
13211315
suggestions: null,
@@ -1486,6 +1480,7 @@ const createBinaryExpression = withLoc(t.binaryExpression);
14861480
const createExpressionStatement = withLoc(t.expressionStatement);
14871481
const _createLabelledStatement = withLoc(t.labeledStatement);
14881482
const createVariableDeclaration = withLoc(t.variableDeclaration);
1483+
const createFunctionDeclaration = withLoc(t.functionDeclaration);
14891484
const _createWhileStatement = withLoc(t.whileStatement);
14901485
const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression);
14911486
const createLogicalExpression = withLoc(t.logicalExpression);

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ class Visitor extends ReactiveFunctionTransform<HoistedIdentifiers> {
5757
return {kind: 'remove'};
5858
}
5959

60+
if (
61+
instruction.value.kind === 'DeclareContext' &&
62+
instruction.value.lvalue.kind === 'HoistedFunction'
63+
) {
64+
state.set(
65+
instruction.value.lvalue.place.identifier.declarationId,
66+
InstructionKind.Function,
67+
);
68+
return {kind: 'remove'};
69+
}
70+
6071
if (
6172
instruction.value.kind === 'StoreContext' &&
6273
state.has(instruction.value.lvalue.place.identifier.declarationId)

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.expect.md

Lines changed: 0 additions & 29 deletions
This file was deleted.

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisted-function-declaration.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ export const FIXTURE_ENTRYPOINT = {
2424
## Error
2525

2626
```
27-
3 | return x;
28-
4 | }
29-
> 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting
30-
| ^^^^^ Todo: Unsupported declaration type for hoisting. variable "baz" declared with FunctionDeclaration (5:5)
31-
6 | function baz() {
32-
7 | return bar();
33-
8 | }
27+
5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting
28+
6 | function baz() {
29+
> 7 | return bar();
30+
| ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7)
31+
8 | }
32+
9 | }
33+
10 |
3434
```
3535
3636

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoist-function-decls.expect.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@ function Component() {
1616

1717
```
1818
1 | function Component() {
19-
> 2 | return get2();
20-
| ^^^^^^ Todo: Unsupported declaration type for hoisting. variable "get2" declared with FunctionDeclaration (2:2)
21-
3 | function get2() {
22-
4 | return 2;
23-
5 | }
19+
2 | return get2();
20+
> 3 | function get2() {
21+
| ^^^^^^^^^^^^^^^^^
22+
> 4 | return 2;
23+
| ^^^^^^^^^^^^^
24+
> 5 | }
25+
| ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (3:5)
26+
6 | }
27+
7 |
2428
```
2529
2630

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-recursive-function-expression.expect.md

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)