-
Notifications
You must be signed in to change notification settings - Fork 49.9k
[compiler][ez] Stop bailing out early for hoisted gated functions #32597
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ import { | |
| EnvironmentConfig, | ||
| ExternalFunction, | ||
| parseEnvironmentConfig, | ||
| tryParseExternalFunction, | ||
| } from '../HIR/Environment'; | ||
| import {hasOwnProperty} from '../Utils/utils'; | ||
| import {fromZodError} from 'zod-validation-error'; | ||
|
|
@@ -271,6 +272,14 @@ export function parsePluginOptions(obj: unknown): PluginOptions { | |
| parsedOptions[key] = parseTargetConfig(value); | ||
| break; | ||
| } | ||
| case 'gating': { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved gating config parsing to BabelPlugin entrypoint -- now it's unconditional and eager like the rest of our config parsing / validation. Previously, we only tried to parse gating if we had successful compilation events. |
||
| if (value == null) { | ||
| parsedOptions[key] = null; | ||
| } else { | ||
| parsedOptions[key] = tryParseExternalFunction(value); | ||
| } | ||
| break; | ||
| } | ||
| default: { | ||
| parsedOptions[key] = value; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,6 @@ import { | |
| ExternalFunction, | ||
| ReactFunctionType, | ||
| MINIMAL_RETRY_CONFIG, | ||
| tryParseExternalFunction, | ||
| } from '../HIR/Environment'; | ||
| import {CodegenFunction} from '../ReactiveScopes'; | ||
| import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; | ||
|
|
@@ -541,30 +540,26 @@ export function compileProgram( | |
| if (moduleScopeOptOutDirectives.length > 0) { | ||
| return; | ||
| } | ||
|
|
||
| let gating: null | { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changes in this file are all code movement.
|
||
| gatingFn: ExternalFunction; | ||
| referencedBeforeDeclared: Set<CompileResult>; | ||
| } = null; | ||
| if (pass.opts.gating != null) { | ||
| const error = checkFunctionReferencedBeforeDeclarationAtTopLevel( | ||
| program, | ||
| compiledFns.map(result => { | ||
| return result.originalFn; | ||
| }), | ||
| ); | ||
| if (error) { | ||
| handleError(error, pass, null); | ||
| return; | ||
| } | ||
| gating = { | ||
| gatingFn: pass.opts.gating, | ||
| referencedBeforeDeclared: | ||
| getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns), | ||
| }; | ||
| } | ||
|
|
||
| const hasLoweredContextAccess = compiledFns.some( | ||
| c => c.compiledFn.hasLoweredContextAccess, | ||
| ); | ||
| const externalFunctions: Array<ExternalFunction> = []; | ||
| let gating: null | ExternalFunction = null; | ||
| try { | ||
| // TODO: check for duplicate import specifiers | ||
| if (pass.opts.gating != null) { | ||
| gating = tryParseExternalFunction(pass.opts.gating); | ||
| externalFunctions.push(gating); | ||
| if (gating != null) { | ||
| externalFunctions.push(gating.gatingFn); | ||
| } | ||
|
|
||
| const lowerContextAccess = environment.lowerContextAccess; | ||
|
|
@@ -613,7 +608,12 @@ export function compileProgram( | |
| const transformedFn = createNewFunctionNode(originalFn, compiledFn); | ||
|
|
||
| if (gating != null && kind === 'original') { | ||
| insertGatedFunctionDeclaration(originalFn, transformedFn, gating); | ||
| insertGatedFunctionDeclaration( | ||
| originalFn, | ||
| transformedFn, | ||
| gating.gatingFn, | ||
| gating.referencedBeforeDeclared.has(result), | ||
| ); | ||
| } else { | ||
| originalFn.replaceWith(transformedFn); | ||
| } | ||
|
|
@@ -1093,20 +1093,23 @@ function getFunctionName( | |
| } | ||
| } | ||
|
|
||
| function checkFunctionReferencedBeforeDeclarationAtTopLevel( | ||
| function getFunctionReferencedBeforeDeclarationAtTopLevel( | ||
| program: NodePath<t.Program>, | ||
| fns: Array<BabelFn>, | ||
| ): CompilerError | null { | ||
| const fnIds = new Set( | ||
| fns: Array<CompileResult>, | ||
| ): Set<CompileResult> { | ||
| const fnNames = new Map<string, {id: t.Identifier; fn: CompileResult}>( | ||
| fns | ||
| .map(fn => getFunctionName(fn)) | ||
| .map<[NodePath<t.Expression> | null, CompileResult]>(fn => [ | ||
| getFunctionName(fn.originalFn), | ||
| fn, | ||
| ]) | ||
| .filter( | ||
| (name): name is NodePath<t.Identifier> => !!name && name.isIdentifier(), | ||
| (entry): entry is [NodePath<t.Identifier>, CompileResult] => | ||
| !!entry[0] && entry[0].isIdentifier(), | ||
| ) | ||
| .map(name => name.node), | ||
| .map(entry => [entry[0].node.name, {id: entry[0].node, fn: entry[1]}]), | ||
| ); | ||
| const fnNames = new Map([...fnIds].map(id => [id.name, id])); | ||
| const errors = new CompilerError(); | ||
| const referencedBeforeDeclaration = new Set<CompileResult>(); | ||
|
|
||
| program.traverse({ | ||
| TypeAnnotation(path) { | ||
|
|
@@ -1132,8 +1135,7 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel( | |
| * We've reached the declaration, hoisting is no longer possible, stop | ||
| * checking for this component name. | ||
| */ | ||
| if (fnIds.has(id.node)) { | ||
| fnIds.delete(id.node); | ||
| if (id.node === fn.id) { | ||
| fnNames.delete(id.node.name); | ||
| return; | ||
| } | ||
|
|
@@ -1144,20 +1146,12 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel( | |
| * top level scope. | ||
| */ | ||
| if (scope === null && id.isReferencedIdentifier()) { | ||
| errors.pushErrorDetail( | ||
| new CompilerErrorDetail({ | ||
| reason: `Encountered a function used before its declaration, which breaks Forget's gating codegen due to hoisting`, | ||
| description: `Rewrite the reference to ${fn.name} to not rely on hoisting to fix this issue`, | ||
| loc: fn.loc ?? null, | ||
| suggestions: null, | ||
| severity: ErrorSeverity.Invariant, | ||
| }), | ||
| ); | ||
| referencedBeforeDeclaration.add(fn.fn); | ||
| } | ||
| }, | ||
| }); | ||
|
|
||
| return errors.details.length > 0 ? errors : null; | ||
| return referencedBeforeDeclaration; | ||
| } | ||
|
|
||
| function getReactCompilerRuntimeModule(opts: PluginOptions): string { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(unchanged)