Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[compiler][be] Clean up compilation skipping logic in Program #30642

Merged
merged 2 commits into from
Aug 9, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ErrorSeverity,
} from '../CompilerError';
import {
EnvironmentConfig,
ExternalFunction,
ReactFunctionType,
parseEnvironmentConfig,
Expand Down Expand Up @@ -276,41 +277,27 @@ export function compileProgram(
program: NodePath<t.Program>,
pass: CompilerPass,
): void {
if (pass.opts.sources) {
if (pass.filename === null) {
const error = new CompilerError();
error.pushErrorDetail(
new CompilerErrorDetail({
reason: `Expected a filename but found none.`,
description:
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
severity: ErrorSeverity.InvalidConfig,
loc: null,
}),
);
handleError(error, pass, null);
return;
}

if (!isFilePartOfSources(pass.opts.sources, pass.filename)) {
return;
}
}

// Top level "use no forget", skip this file entirely
if (
findDirectiveDisablingMemoization(program.node.directives, pass.opts) !=
null
) {
if (shouldSkipCompilation(program, pass)) {
return;
}

const environment = parseEnvironmentConfig(pass.opts.environment ?? {});
/*
* TODO(lauren): Remove pass.opts.environment nullcheck once PluginOptions
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Previously, we waited until compiling a function to validate environment configs. Let's just do this unconditionally -- this should be a fatal error and hopefully never happens in prod

* is validated
*/
const environmentResult = parseEnvironmentConfig(pass.opts.environment ?? {});
if (environmentResult.isErr()) {
CompilerError.throwInvalidConfig({
reason:
'Error in validating environment config. This is an advanced setting and not meant to be used directly',
description: environmentResult.unwrapErr().toString(),
suggestions: null,
loc: null,
});
}
const environment = environmentResult.unwrap();
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
if (hasMemoCacheFunctionImport(program, moduleName)) {
return;
}

/*
* Record lint errors and critical errors as depending on Forget's config,
Expand All @@ -332,7 +319,7 @@ export function compileProgram(
const compiledFns: Array<CompileResult> = [];

const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => {
const fnType = getReactFunctionType(fn, pass);
const fnType = getReactFunctionType(fn, pass, environment);
if (fnType === null || ALREADY_COMPILED.has(fn.node)) {
return;
}
Expand Down Expand Up @@ -403,24 +390,9 @@ export function compileProgram(

let compiledFn: CodegenFunction;
try {
/*
* TODO(lauren): Remove pass.opts.environment nullcheck once PluginOptions
* is validated
*/
if (environment.isErr()) {
CompilerError.throwInvalidConfig({
reason:
'Error in validating environment config. This is an advanced setting and not meant to be used directly',
description: environment.unwrapErr().toString(),
suggestions: null,
loc: null,
});
}
const config = environment.unwrap();

compiledFn = compileFn(
fn,
config,
environment,
fnType,
useMemoCacheIdentifier.name,
pass.opts.logger,
Expand Down Expand Up @@ -514,43 +486,29 @@ export function compileProgram(
externalFunctions.push(gating);
}

const lowerContextAccess = pass.opts.environment?.lowerContextAccess;
const lowerContextAccess = environment.lowerContextAccess;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now we're able to rely on the parsed / validated EnvironmentConfig, which comes with default values (instead of using a PartialEnvironmentConfig and duplicating defaults

if (lowerContextAccess && hasLoweredContextAccess) {
externalFunctions.push(tryParseExternalFunction(lowerContextAccess));
externalFunctions.push(lowerContextAccess);
}

const enableEmitInstrumentForget =
pass.opts.environment?.enableEmitInstrumentForget;
const enableEmitInstrumentForget = environment.enableEmitInstrumentForget;
if (enableEmitInstrumentForget != null) {
externalFunctions.push(
tryParseExternalFunction(enableEmitInstrumentForget.fn),
);
externalFunctions.push(enableEmitInstrumentForget.fn);
if (enableEmitInstrumentForget.gating != null) {
externalFunctions.push(
tryParseExternalFunction(enableEmitInstrumentForget.gating),
);
externalFunctions.push(enableEmitInstrumentForget.gating);
}
}

if (pass.opts.environment?.enableEmitFreeze != null) {
const enableEmitFreeze = tryParseExternalFunction(
pass.opts.environment.enableEmitFreeze,
);
externalFunctions.push(enableEmitFreeze);
if (environment.enableEmitFreeze != null) {
externalFunctions.push(environment.enableEmitFreeze);
}

if (pass.opts.environment?.enableEmitHookGuards != null) {
const enableEmitHookGuards = tryParseExternalFunction(
pass.opts.environment.enableEmitHookGuards,
);
externalFunctions.push(enableEmitHookGuards);
if (environment.enableEmitHookGuards != null) {
externalFunctions.push(environment.enableEmitHookGuards);
}

if (pass.opts.environment?.enableChangeDetectionForDebugging != null) {
const enableChangeDetectionForDebugging = tryParseExternalFunction(
pass.opts.environment.enableChangeDetectionForDebugging,
);
externalFunctions.push(enableChangeDetectionForDebugging);
if (environment.enableChangeDetectionForDebugging != null) {
externalFunctions.push(environment.enableChangeDetectionForDebugging);
}
} catch (err) {
handleError(err, pass, null);
Expand Down Expand Up @@ -593,11 +551,65 @@ export function compileProgram(
}
}

function shouldSkipCompilation(
program: NodePath<t.Program>,
pass: CompilerPass,
): boolean {
if (pass.opts.sources) {
if (pass.filename === null) {
const error = new CompilerError();
error.pushErrorDetail(
new CompilerErrorDetail({
reason: `Expected a filename but found none.`,
description:
"When the 'sources' config options is specified, the React compiler will only compile files with a name",
severity: ErrorSeverity.InvalidConfig,
loc: null,
}),
);
handleError(error, pass, null);
return true;
}

if (!isFilePartOfSources(pass.opts.sources, pass.filename)) {
return true;
}
}

// Top level "use no forget", skip this file entirely
const useNoForget = findDirectiveDisablingMemoization(
program.node.directives,
pass.opts,
);
if (useNoForget != null) {
pass.opts.logger?.logEvent(pass.filename, {
kind: 'CompileError',
fnLoc: null,
detail: {
severity: ErrorSeverity.Todo,
reason: 'Skipped due to "use no forget" directive.',
loc: useNoForget.loc ?? null,
suggestions: null,
},
});
return true;
}
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
if (hasMemoCacheFunctionImport(program, moduleName)) {
return true;
}
return false;
}

function getReactFunctionType(
fn: BabelFn,
pass: CompilerPass,
/**
* TODO(mofeiZ): remove once we validate PluginOptions with Zod
*/
environment: EnvironmentConfig,
): ReactFunctionType | null {
const hookPattern = pass.opts.environment?.hookPattern ?? null;
const hookPattern = environment.hookPattern;
if (fn.node.body.type === 'BlockStatement') {
// Opt-outs disable compilation regardless of mode
const useNoForget = findDirectiveDisablingMemoization(
Expand Down