Skip to content
331 changes: 331 additions & 0 deletions compiler/fault-tolerance-overview.md

Large diffs are not rendered by default.

224 changes: 155 additions & 69 deletions compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -704,19 +704,21 @@ function tryCompileFunction(
}

try {
return {
kind: 'compile',
compiledFn: compileFn(
fn,
programContext.opts.environment,
fnType,
outputMode,
programContext,
programContext.opts.logger,
programContext.filename,
programContext.code,
),
};
const result = compileFn(
fn,
programContext.opts.environment,
fnType,
outputMode,
programContext,
programContext.opts.logger,
programContext.filename,
programContext.code,
);
if (result.isOk()) {
return {kind: 'compile', compiledFn: result.unwrap()};
} else {
return {kind: 'error', error: result.unwrapErr()};
}
} catch (err) {
return {kind: 'error', error: err};
}
Expand Down
180 changes: 115 additions & 65 deletions compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
CompilerSuggestionOperation,
ErrorCategory,
} from '../CompilerError';
import {Err, Ok, Result} from '../Utils/Result';
import {assertExhaustive, hasNode} from '../Utils/utils';
import {Environment} from './Environment';
import {
Expand Down Expand Up @@ -75,7 +74,7 @@
// Bindings captured from the outer function, in case lower() is called recursively (for lambdas)
bindings: Bindings | null = null,
capturedRefs: Map<t.Identifier, SourceLocation> = new Map(),
): Result<HIRFunction, CompilerError> {
): HIRFunction {
const builder = new HIRBuilder(env, {
bindings,
context: capturedRefs,
Expand Down Expand Up @@ -186,32 +185,51 @@

let directives: Array<string> = [];
const body = func.get('body');
if (body.isExpression()) {
const fallthrough = builder.reserve('block');
const terminal: ReturnTerminal = {
kind: 'return',
returnVariant: 'Implicit',
loc: GeneratedSource,
value: lowerExpressionToTemporary(builder, body),
id: makeInstructionId(0),
effects: null,
};
builder.terminateWithContinuation(terminal, fallthrough);
} else if (body.isBlockStatement()) {
lowerStatement(builder, body);
directives = body.get('directives').map(d => d.node.value.value);
} else {
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
category: ErrorCategory.Syntax,
reason: `Unexpected function body kind`,
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
}).withDetails({
kind: 'error',
loc: body.node.loc ?? null,
message: 'Expected a block statement or expression',
}),
);
try {
if (body.isExpression()) {
const fallthrough = builder.reserve('block');
const terminal: ReturnTerminal = {
kind: 'return',
returnVariant: 'Implicit',
loc: GeneratedSource,
value: lowerExpressionToTemporary(builder, body),
id: makeInstructionId(0),
effects: null,
};
builder.terminateWithContinuation(terminal, fallthrough);
} else if (body.isBlockStatement()) {
lowerStatement(builder, body);
directives = body.get('directives').map(d => d.node.value.value);
} else {
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
category: ErrorCategory.Syntax,
reason: `Unexpected function body kind`,
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
}).withDetails({
kind: 'error',
loc: body.node.loc ?? null,
message: 'Expected a block statement or expression',
}),
);
}
} catch (err) {
if (err instanceof CompilerError) {
// Re-throw invariant errors immediately
for (const detail of err.details) {
if (
(detail instanceof CompilerDiagnostic
? detail.category
: detail.category) === ErrorCategory.Invariant
) {
Comment on lines +220 to +224

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant ternary condition

Both branches of this ternary access detail.category, making the conditional unnecessary. This was likely intended to differentiate between CompilerDiagnostic and CompilerErrorDetail, but since both have a .category property, the ternary is a no-op.

Suggested change
if (
(detail instanceof CompilerDiagnostic
? detail.category
: detail.category) === ErrorCategory.Invariant
) {
detail.category === ErrorCategory.Invariant
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts
Line: 220-224

Comment:
**Redundant ternary condition**

Both branches of this ternary access `detail.category`, making the conditional unnecessary. This was likely intended to differentiate between `CompilerDiagnostic` and `CompilerErrorDetail`, but since both have a `.category` property, the ternary is a no-op.

```suggestion
          detail.category === ErrorCategory.Invariant
```

How can I resolve this? If you propose a fix, please make it concise.

throw err;
}
}
// Record non-invariant errors and continue to produce partial HIR
builder.errors.merge(err);
} else {
throw err;
}
}

let validatedId: HIRFunction['id'] = null;
Expand All @@ -224,10 +242,6 @@
}
}

if (builder.errors.hasAnyErrors()) {
return Err(builder.errors);
}

builder.terminate(
{
kind: 'return',
Expand All @@ -244,23 +258,29 @@
null,
);

return Ok({
const hirBody = builder.build();

// Record all accumulated errors (including any from build()) on env
if (builder.errors.hasAnyErrors()) {
env.recordErrors(builder.errors);
}

return {
id: validatedId,
nameHint: null,
params,
fnType: bindings == null ? env.fnType : 'Other',
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
returns: createTemporaryPlace(env, func.node.loc ?? GeneratedSource),
body: builder.build(),
body: hirBody,
context,
generator: func.node.generator === true,
async: func.node.async === true,
loc: func.node.loc ?? GeneratedSource,
env,
effects: null,
aliasingEffects: null,
directives,
});
};
}

// Helper to lower a statement
Expand Down Expand Up @@ -555,6 +575,22 @@

const initBlock = builder.enter('loop', _blockId => {
const init = stmt.get('init');
if (init.node == null) {
// No init expression (e.g., `for (; ...)`), add a placeholder to avoid

Check failure on line 579 in compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts

View workflow job for this annotation

GitHub Actions / Lint babel-plugin-react-compiler

Expected a block comment instead of consecutive line comments
// invariant about empty blocks
lowerValueToTemporary(builder, {
kind: 'Primitive',
value: undefined,
loc: stmt.node.loc ?? GeneratedSource,
});
return {
kind: 'goto',
block: testBlock.id,
variant: GotoVariant.Break,
id: makeInstructionId(0),
loc: stmt.node.loc ?? GeneratedSource,
};
}
if (!init.isVariableDeclaration()) {
builder.errors.push({
reason:
Expand All @@ -563,8 +599,14 @@
loc: stmt.node.loc ?? null,
suggestions: null,
});
// Lower the init expression as best-effort and continue
if (init.isExpression()) {
lowerExpressionToTemporary(builder, init as NodePath<t.Expression>);
}
return {
kind: 'unsupported',
kind: 'goto',
block: testBlock.id,
variant: GotoVariant.Break,
id: makeInstructionId(0),
loc: init.node?.loc ?? GeneratedSource,
};
Expand Down Expand Up @@ -635,6 +677,23 @@
loc: stmt.node.loc ?? null,
suggestions: null,
});
// Treat `for(;;)` as `while(true)` to keep the builder state consistent
builder.terminateWithContinuation(
{
kind: 'branch',
test: lowerValueToTemporary(builder, {
kind: 'Primitive',
value: true,
loc: stmt.node.loc ?? GeneratedSource,
}),
consequent: bodyBlock,
alternate: continuationBlock.id,
fallthrough: continuationBlock.id,
id: makeInstructionId(0),
loc: stmt.node.loc ?? GeneratedSource,
},
continuationBlock,
);
} else {
builder.terminateWithContinuation(
{
Expand Down Expand Up @@ -858,10 +917,12 @@
loc: stmt.node.loc ?? null,
suggestions: null,
});
return;
// Treat `var` as `let` so references to the variable don't break
}
const kind =
nodeKind === 'let' ? InstructionKind.Let : InstructionKind.Const;
nodeKind === 'let' || nodeKind === 'var'
? InstructionKind.Let
: InstructionKind.Const;
for (const declaration of stmt.get('declarations')) {
const id = declaration.get('id');
const init = declaration.get('init');
Expand Down Expand Up @@ -1494,9 +1555,6 @@
): InstructionValue {
const loc = property.node.loc ?? GeneratedSource;
const loweredFunc = lowerFunction(builder, property);
if (!loweredFunc) {
return {kind: 'UnsupportedNode', node: property.node, loc: loc};
}

return {
kind: 'ObjectMethod',
Expand Down Expand Up @@ -2276,18 +2334,20 @@
});
for (const [name, locations] of Object.entries(fbtLocations)) {
if (locations.length > 1) {
CompilerError.throwDiagnostic({
category: ErrorCategory.Todo,
reason: 'Support duplicate fbt tags',
description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`,
details: locations.map(loc => {
return {
kind: 'error',
message: `Multiple \`<${tagName}:${name}>\` tags found`,
loc,
};
builder.errors.pushDiagnostic(
new CompilerDiagnostic({
category: ErrorCategory.Todo,
reason: 'Support duplicate fbt tags',
description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`,
details: locations.map(loc => {
return {
kind: 'error' as const,
message: `Multiple \`<${tagName}:${name}>\` tags found`,
loc,
};
}),
}),
});
);
}
}
}
Expand Down Expand Up @@ -3468,9 +3528,6 @@
const exprNode = expr.node;
const exprLoc = exprNode.loc ?? GeneratedSource;
const loweredFunc = lowerFunction(builder, expr);
if (!loweredFunc) {
return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc};
}
return {
kind: 'FunctionExpression',
name: loweredFunc.func.id,
Expand All @@ -3489,7 +3546,7 @@
| t.FunctionDeclaration
| t.ObjectMethod
>,
): LoweredFunction | null {
): LoweredFunction {
const componentScope: Scope = builder.environment.parentFunction.scope;
const capturedContext = gatherCapturedContext(expr, componentScope);

Expand All @@ -3501,19 +3558,12 @@
* This isn't a problem in practice because use Babel's scope analysis to
* identify the correct references.
*/
const lowering = lower(
const loweredFunc = lower(
expr,
builder.environment,
builder.bindings,
new Map([...builder.context, ...capturedContext]),
);
let loweredFunc: HIRFunction;
if (lowering.isErr()) {
const functionErrors = lowering.unwrapErr();
builder.errors.merge(functionErrors);
return null;
}
loweredFunc = lowering.unwrap();
return {
func: loweredFunc,
};
Expand Down
Loading
Loading