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

Large diffs are not rendered by default.

249 changes: 173 additions & 76 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 @@ -728,19 +728,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 Expand Up @@ -779,10 +781,16 @@ function retryCompileFunction(
programContext.code,
);

if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) {
if (retryResult.isErr()) {
const err = retryResult.unwrapErr();
programContext.retryErrors.push({fn, error: err});
return null;
}
const compiledFn = retryResult.unwrap();
if (!compiledFn.hasFireRewrite && !compiledFn.hasInferredEffect) {
return null;
}
return retryResult;
return compiledFn;
} catch (err) {
// TODO: we might want to log error here, but this will also result in duplicate logging
if (err instanceof CompilerError) {
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 @@ import {
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 @@ export function lower(
// 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 @@ export function lower(

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 +219 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 in category check

Both branches of the ternary produce detail.category, since CompilerDiagnostic also has a category property. The ternary (detail instanceof CompilerDiagnostic ? detail.category : detail.category) always evaluates to detail.category regardless of the type check.

Suggested change
for (const detail of err.details) {
if (
(detail instanceof CompilerDiagnostic
? detail.category
: detail.category) === ErrorCategory.Invariant
) {
if (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: 219-224

Comment:
**Redundant ternary in category check**

Both branches of the ternary produce `detail.category`, since `CompilerDiagnostic` also has a `category` property. The ternary `(detail instanceof CompilerDiagnostic ? detail.category : detail.category)` always evaluates to `detail.category` regardless of the type check.

```suggestion
          if (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 @@ export function lower(
}
}

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

builder.terminate(
{
kind: 'return',
Expand All @@ -244,23 +258,29 @@ export function lower(
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 @@ function lowerStatement(

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
// 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 @@ function lowerStatement(
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 @@ function lowerStatement(
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 @@ function lowerStatement(
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 @@ function lowerObjectMethod(
): 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 @@ function lowerExpression(
});
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 @@ function lowerFunctionToValue(
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 @@ function lowerFunction(
| 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 @@ function lowerFunction(
* 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