Compiler Diagnostics & Errors
The React Compiler employs a robust diagnostic system designed to enforce React's programming model and ensure the safety of generated memoization. Diagnostics are managed via the CompilerError class, which aggregates issues discovered during various passes of the compiler's pipeline, such as constant folding, inference, and final validation.
The CompilerError Architecture
Diagnostics are not merely strings; they are structured objects that provide the compiler's frontend (e.g., the Babel plugin) with sufficient metadata to generate actionable feedback, including source code locations and refactoring suggestions.
Error Structure
Errors are categorized to help developers distinguish between syntax violations, React-specific rule breaks, and internal compiler limitations.
type CompilerErrorDetail = {
category: ErrorCategory;
reason: string;
description: string | null;
loc: SourceLocation | null;
suggestions: Array<Suggestion> | null;
};
- Category: Defined in
ErrorCategory, these distinguish between types likeInvalidReact,CapitalizedCalls, orInvariant. - Reason: A high-level explanation of the rule being enforced.
- Description: Specific details about the identifier or expression triggering the error.
- Loc: The precise line and column information from the original source code.
Validation Pass: Capitalized Calls
One of the primary diagnostic checks is ValidateNoCapitalizedCalls. In React, capitalized functions are reserved for components and must be invoked using JSX syntax (<Component />) rather than direct function calls (Component()). This ensures that React can manage component lifecycles, hooks, and reconciliation correctly.
Validation Logic
The compiler identifies CallExpression and MethodCall nodes in the HIR (High-level Intermediate Representation). It triggers a diagnostic error if:
- The callee's name starts with a capital letter.
- The name is not a known global (e.g.,
String,Number,Boolean). - The name does not match the configured
hookPattern. - The name is not an all-caps constant (e.g.,
CONST_VALUE()).
Configuration and Allow-listing
The diagnostic behavior can be tuned via the EnvironmentConfig. This allows projects with specific naming conventions or legacy global utilities to bypass certain checks.
// Example Compiler Configuration
{
"validateNoCapitalizedCalls": ["MyGlobalUtility"],
"hookPattern": ".*\\b(use[^$]+)$"
}
Memoization & Escape Analysis Diagnostics
The compiler performs complex "escape analysis" to determine which values need to be memoized. While many of these checks are silent optimizations, the validatePreserveExistingMemoizationGuarantees flag enables diagnostics that ensure the compiler does not prune scopes that are critical to the stability of downstream memoization.
Scope Pruning
The PruneNonEscapingScopes pass identifies identifiers that do not "escape" the component's scope. A value escapes if:
- It is directly returned by the function.
- It is transitively aliased by a return value.
- It is passed as an argument to a hook (since hooks may store references externally).
If a scope is non-escaping but acts as a dependency for an escaping scope, the compiler ensures the dependency remains memoized. Failure to do so would cause "memoization invalidation cascades," where a stable output is recalculated because a non-escaping internal dependency changed its reference identity.
Common Diagnostic Scenarios
Invalid Component Invocations
If the compiler encounters a capitalized call that isn't a known global or hook, it throws an InvalidReact error.
Error Code:
function Component() {
const x = SomeOtherComponent(); // Error: Capitalized functions are reserved for components
return <div /> ;
}
Diagnostic Output:
"Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter."
Non-Escaping Memoization
In @compilationMode:"infer", the compiler may choose not to memoize certain blocks if they don't impact the output or hook stability.
function Component(props) {
// This object does not escape or affect downstream computations
// The compiler diagnostics system may prune this scope during optimization
const internalConfig = { debug: true };
return <View props={props} />;
}
Handling Results
Passes that perform validation return a Result<void, CompilerError>. This allows the compiler to accumulate multiple errors across different blocks before deciding whether to abort the compilation of a specific function.
const errors = new CompilerError();
// ... during validation ...
errors.push({
category: ErrorCategory.CapitalizedCalls,
reason: REASON_STRING,
loc: value.loc,
// ...
});
return errors.asResult();