Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7455473e3a |
@@ -103,6 +103,7 @@ import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoF
|
||||
import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
|
||||
import {inferMutationAliasingRanges} from '../Inference/InferMutationAliasingRanges';
|
||||
import {validateNoDerivedComputationsInEffects} from '../Validation/ValidateNoDerivedComputationsInEffects';
|
||||
import {nameAnonymousFunctions} from '../Transform/NameAnonymousFunctions';
|
||||
|
||||
export type CompilerPipelineValue =
|
||||
| {kind: 'ast'; name: string; value: CodegenFunction}
|
||||
@@ -414,6 +415,15 @@ function runWithEnvironment(
|
||||
});
|
||||
}
|
||||
|
||||
if (env.config.enableNameAnonymousFunctions) {
|
||||
nameAnonymousFunctions(hir);
|
||||
log({
|
||||
kind: 'hir',
|
||||
name: 'NameAnonymougFunctions',
|
||||
value: hir,
|
||||
});
|
||||
}
|
||||
|
||||
const reactiveFunction = buildReactiveFunction(hir);
|
||||
log({
|
||||
kind: 'reactive',
|
||||
|
||||
@@ -3566,6 +3566,8 @@ function lowerFunctionToValue(
|
||||
let name: string | null = null;
|
||||
if (expr.isFunctionExpression()) {
|
||||
name = expr.get('id')?.node?.name ?? null;
|
||||
} else if (expr.isFunctionDeclaration()) {
|
||||
name = expr.get('id')?.node?.name ?? null;
|
||||
}
|
||||
const loweredFunc = lowerFunction(builder, expr);
|
||||
if (!loweredFunc) {
|
||||
|
||||
@@ -261,6 +261,8 @@ export const EnvironmentConfigSchema = z.object({
|
||||
|
||||
enableFire: z.boolean().default(false),
|
||||
|
||||
enableNameAnonymousFunctions: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* Enables inference and auto-insertion of effect dependencies. Takes in an array of
|
||||
* configurable module and import pairs to allow for user-land experimentation. For example,
|
||||
|
||||
@@ -15,6 +15,7 @@ import {Type, makeType} from './Types';
|
||||
import {z} from 'zod';
|
||||
import type {AliasingEffect} from '../Inference/AliasingEffects';
|
||||
import {isReservedWord} from '../Utils/Keyword';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
|
||||
/*
|
||||
* *******************************************************************************************
|
||||
@@ -1298,6 +1299,15 @@ export function forkTemporaryIdentifier(
|
||||
};
|
||||
}
|
||||
|
||||
export function validateIdentifierName(
|
||||
name: string,
|
||||
): Result<ValidIdentifierName, null> {
|
||||
if (isReservedWord(name) || !t.isValidIdentifier(name)) {
|
||||
return Err(null);
|
||||
}
|
||||
return Ok(makeIdentifierName(name).value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid identifier name. This should *not* be used for synthesizing
|
||||
* identifier names: only call this method for identifier names that appear in the
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
ValidIdentifierName,
|
||||
getHookKind,
|
||||
makeIdentifierName,
|
||||
validateIdentifierName,
|
||||
} from '../HIR/HIR';
|
||||
import {printIdentifier, printInstruction, printPlace} from '../HIR/PrintHIR';
|
||||
import {eachPatternOperand} from '../HIR/visitors';
|
||||
@@ -2326,6 +2327,11 @@ function codegenInstructionValue(
|
||||
),
|
||||
reactiveFunction,
|
||||
).unwrap();
|
||||
|
||||
const validatedName =
|
||||
instrValue.name != null
|
||||
? validateIdentifierName(instrValue.name)
|
||||
: Err(null);
|
||||
if (instrValue.type === 'ArrowFunctionExpression') {
|
||||
let body: t.BlockStatement | t.Expression = fn.body;
|
||||
if (body.body.length === 1 && loweredFunc.directives.length == 0) {
|
||||
@@ -2337,14 +2343,28 @@ function codegenInstructionValue(
|
||||
value = t.arrowFunctionExpression(fn.params, body, fn.async);
|
||||
} else {
|
||||
value = t.functionExpression(
|
||||
fn.id ??
|
||||
(instrValue.name != null ? t.identifier(instrValue.name) : null),
|
||||
validatedName
|
||||
.map<t.Identifier | null>(name => t.identifier(name))
|
||||
.unwrapOr(null),
|
||||
fn.params,
|
||||
fn.body,
|
||||
fn.generator,
|
||||
fn.async,
|
||||
);
|
||||
}
|
||||
if (
|
||||
cx.env.config.enableNameAnonymousFunctions &&
|
||||
validatedName.isErr() &&
|
||||
instrValue.name != null
|
||||
) {
|
||||
const name = instrValue.name;
|
||||
value = t.memberExpression(
|
||||
t.objectExpression([t.objectProperty(t.stringLiteral(name), value)]),
|
||||
t.stringLiteral(name),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'TaggedTemplateExpression': {
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {
|
||||
FunctionExpression,
|
||||
getHookKind,
|
||||
HIRFunction,
|
||||
IdentifierId,
|
||||
} from '../HIR';
|
||||
|
||||
export function nameAnonymousFunctions(fn: HIRFunction): void {
|
||||
if (fn.id == null) {
|
||||
return;
|
||||
}
|
||||
const parentName = fn.id;
|
||||
const functions = nameAnonymousFunctionsImpl(fn);
|
||||
function visit(node: Node, prefix: string): void {
|
||||
if (node.generatedName != null) {
|
||||
/**
|
||||
* Note that we don't generate a name for functions that already had one,
|
||||
* so we'll only add the prefix to anonymous functions regardless of
|
||||
* nesting depth.
|
||||
*/
|
||||
const name = `${prefix}${node.generatedName}]`;
|
||||
node.fn.name = name;
|
||||
}
|
||||
/**
|
||||
* Whether or not we generated a name for the function at this node,
|
||||
* traverse into its nested functions to assign them names
|
||||
*/
|
||||
const nextPrefix = `${prefix}${node.generatedName ?? node.fn.name ?? '<anonymous>'} > `;
|
||||
for (const inner of node.inner) {
|
||||
visit(inner, nextPrefix);
|
||||
}
|
||||
}
|
||||
for (const node of functions) {
|
||||
visit(node, `${parentName}[`);
|
||||
}
|
||||
}
|
||||
|
||||
type Node = {
|
||||
fn: FunctionExpression;
|
||||
generatedName: string | null;
|
||||
inner: Array<Node>;
|
||||
};
|
||||
|
||||
function nameAnonymousFunctionsImpl(fn: HIRFunction): Array<Node> {
|
||||
// Functions that we track to generate names for
|
||||
const functions: Map<IdentifierId, Node> = new Map();
|
||||
// Tracks temporaries that read from variables/globals/properties
|
||||
const names: Map<IdentifierId, string> = new Map();
|
||||
// Tracks all function nodes to bubble up for later renaming
|
||||
const nodes: Array<Node> = [];
|
||||
for (const block of fn.body.blocks.values()) {
|
||||
for (const instr of block.instructions) {
|
||||
const {lvalue, value} = instr;
|
||||
switch (value.kind) {
|
||||
case 'LoadGlobal': {
|
||||
names.set(lvalue.identifier.id, value.binding.name);
|
||||
break;
|
||||
}
|
||||
case 'LoadContext':
|
||||
case 'LoadLocal': {
|
||||
const name = value.place.identifier.name;
|
||||
if (name != null && name.kind === 'named') {
|
||||
names.set(lvalue.identifier.id, name.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'PropertyLoad': {
|
||||
const objectName = names.get(value.object.identifier.id);
|
||||
if (objectName != null) {
|
||||
names.set(
|
||||
lvalue.identifier.id,
|
||||
`${objectName}.${String(value.property)}`,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'FunctionExpression': {
|
||||
const inner = nameAnonymousFunctionsImpl(value.loweredFunc.func);
|
||||
const node: Node = {
|
||||
fn: value,
|
||||
generatedName: null,
|
||||
inner,
|
||||
};
|
||||
/**
|
||||
* Bubble-up all functions, even if they're named, so that we can
|
||||
* later generate names for any inner anonymous functions
|
||||
*/
|
||||
nodes.push(node);
|
||||
if (value.name == null) {
|
||||
// but only generate names for anonymous functions
|
||||
functions.set(lvalue.identifier.id, node);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'StoreContext':
|
||||
case 'StoreLocal': {
|
||||
const node = functions.get(value.value.identifier.id);
|
||||
const variableName = value.lvalue.place.identifier.name;
|
||||
if (
|
||||
node != null &&
|
||||
variableName != null &&
|
||||
variableName.kind === 'named'
|
||||
) {
|
||||
node.generatedName = variableName.value;
|
||||
functions.delete(value.value.identifier.id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'CallExpression':
|
||||
case 'MethodCall': {
|
||||
const callee =
|
||||
value.kind === 'MethodCall' ? value.property : value.callee;
|
||||
const hookKind = getHookKind(fn.env, callee.identifier);
|
||||
let calleeName: string | null = null;
|
||||
if (hookKind != null && hookKind !== 'Custom') {
|
||||
calleeName = hookKind;
|
||||
} else {
|
||||
calleeName = names.get(callee.identifier.id) ?? '(anonymous)';
|
||||
}
|
||||
let fnArgCount = 0;
|
||||
for (const arg of value.args) {
|
||||
if (arg.kind === 'Identifier' && functions.has(arg.identifier.id)) {
|
||||
fnArgCount++;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < value.args.length; i++) {
|
||||
const arg = value.args[i]!;
|
||||
if (arg.kind === 'Spread') {
|
||||
continue;
|
||||
}
|
||||
const node = functions.get(arg.identifier.id);
|
||||
if (node != null) {
|
||||
const generatedName =
|
||||
fnArgCount > 1 ? `${calleeName}(arg${i})` : `${calleeName}()`;
|
||||
node.generatedName = generatedName;
|
||||
functions.delete(arg.identifier.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'JsxExpression': {
|
||||
for (const attr of value.props) {
|
||||
if (attr.kind === 'JsxSpreadAttribute') {
|
||||
continue;
|
||||
}
|
||||
const node = functions.get(attr.place.identifier.id);
|
||||
if (node != null) {
|
||||
const elementName =
|
||||
value.tag.kind === 'BuiltinTag'
|
||||
? value.tag.name
|
||||
: (names.get(value.tag.identifier.id) ?? null);
|
||||
const propName =
|
||||
elementName == null
|
||||
? attr.name
|
||||
: `<${elementName}>.${attr.name}`;
|
||||
node.generatedName = `${propName}`;
|
||||
functions.delete(attr.place.identifier.id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableNameAnonymousFunctions
|
||||
|
||||
import {useEffect} from 'react';
|
||||
import {identity, Stringify, useIdentity} from 'shared-runtime';
|
||||
import * as SharedRuntime from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
function named() {
|
||||
const inner = () => props.named;
|
||||
return inner();
|
||||
}
|
||||
const namedVariable = function () {
|
||||
return props.namedVariable;
|
||||
};
|
||||
const methodCall = SharedRuntime.identity(() => props.methodCall);
|
||||
const call = identity(() => props.call);
|
||||
const builtinElementAttr = <div onClick={() => props.builtinElementAttr} />;
|
||||
const namedElementAttr = <Stringify onClick={() => props.namedElementAttr} />;
|
||||
const hookArgument = useIdentity(() => props.hookArgument);
|
||||
useEffect(() => {
|
||||
console.log(props.useEffect);
|
||||
JSON.stringify(null, null, () => props.useEffect);
|
||||
const g = () => props.useEffect;
|
||||
console.log(g());
|
||||
}, [props.useEffect]);
|
||||
return (
|
||||
<>
|
||||
{named()}
|
||||
{namedVariable()}
|
||||
{methodCall()}
|
||||
{call()}
|
||||
{builtinElementAttr}
|
||||
{namedElementAttr}
|
||||
{hookArgument()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [
|
||||
{
|
||||
named: '<named>',
|
||||
namedVariable: '<namedVariable>',
|
||||
methodCall: '<methodCall>',
|
||||
call: '<call>',
|
||||
builtinElementAttr: '<builtinElementAttr>',
|
||||
namedElementAttr: '<namedElementAttr>',
|
||||
hookArgument: '<hookArgument>',
|
||||
useEffect: '<useEffect>',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNameAnonymousFunctions
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { identity, Stringify, useIdentity } from "shared-runtime";
|
||||
import * as SharedRuntime from "shared-runtime";
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(31);
|
||||
let t0;
|
||||
if ($[0] !== props.named) {
|
||||
t0 = function named() {
|
||||
const inner = { "Component[named > inner]": () => props.named }[
|
||||
"Component[named > inner]"
|
||||
];
|
||||
return inner();
|
||||
};
|
||||
$[0] = props.named;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const named = t0;
|
||||
let t1;
|
||||
if ($[2] !== props.namedVariable) {
|
||||
t1 = {
|
||||
"Component[namedVariable]": function () {
|
||||
return props.namedVariable;
|
||||
},
|
||||
}["Component[namedVariable]"];
|
||||
$[2] = props.namedVariable;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const namedVariable = t1;
|
||||
let t2;
|
||||
if ($[4] !== props.methodCall) {
|
||||
t2 = { "Component[SharedRuntime.identity()]": () => props.methodCall }[
|
||||
"Component[SharedRuntime.identity()]"
|
||||
];
|
||||
$[4] = props.methodCall;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
const methodCall = SharedRuntime.identity(t2);
|
||||
let t3;
|
||||
if ($[6] !== props.call) {
|
||||
t3 = { "Component[identity()]": () => props.call }["Component[identity()]"];
|
||||
$[6] = props.call;
|
||||
$[7] = t3;
|
||||
} else {
|
||||
t3 = $[7];
|
||||
}
|
||||
const call = identity(t3);
|
||||
let t4;
|
||||
if ($[8] !== props.builtinElementAttr) {
|
||||
t4 = (
|
||||
<div
|
||||
onClick={
|
||||
{ "Component[<div>.onClick]": () => props.builtinElementAttr }[
|
||||
"Component[<div>.onClick]"
|
||||
]
|
||||
}
|
||||
/>
|
||||
);
|
||||
$[8] = props.builtinElementAttr;
|
||||
$[9] = t4;
|
||||
} else {
|
||||
t4 = $[9];
|
||||
}
|
||||
const builtinElementAttr = t4;
|
||||
let t5;
|
||||
if ($[10] !== props.namedElementAttr) {
|
||||
t5 = (
|
||||
<Stringify
|
||||
onClick={
|
||||
{ "Component[<Stringify>.onClick]": () => props.namedElementAttr }[
|
||||
"Component[<Stringify>.onClick]"
|
||||
]
|
||||
}
|
||||
/>
|
||||
);
|
||||
$[10] = props.namedElementAttr;
|
||||
$[11] = t5;
|
||||
} else {
|
||||
t5 = $[11];
|
||||
}
|
||||
const namedElementAttr = t5;
|
||||
let t6;
|
||||
if ($[12] !== props.hookArgument) {
|
||||
t6 = { "Component[useIdentity()]": () => props.hookArgument }[
|
||||
"Component[useIdentity()]"
|
||||
];
|
||||
$[12] = props.hookArgument;
|
||||
$[13] = t6;
|
||||
} else {
|
||||
t6 = $[13];
|
||||
}
|
||||
const hookArgument = useIdentity(t6);
|
||||
let t7;
|
||||
let t8;
|
||||
if ($[14] !== props.useEffect) {
|
||||
t7 = {
|
||||
"Component[useEffect()]": () => {
|
||||
console.log(props.useEffect);
|
||||
JSON.stringify(
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"Component[useEffect() > JSON.stringify()]": () => props.useEffect,
|
||||
}["Component[useEffect() > JSON.stringify()]"],
|
||||
);
|
||||
const g = { "Component[useEffect() > g]": () => props.useEffect }[
|
||||
"Component[useEffect() > g]"
|
||||
];
|
||||
console.log(g());
|
||||
},
|
||||
}["Component[useEffect()]"];
|
||||
t8 = [props.useEffect];
|
||||
$[14] = props.useEffect;
|
||||
$[15] = t7;
|
||||
$[16] = t8;
|
||||
} else {
|
||||
t7 = $[15];
|
||||
t8 = $[16];
|
||||
}
|
||||
useEffect(t7, t8);
|
||||
let t9;
|
||||
if ($[17] !== named) {
|
||||
t9 = named();
|
||||
$[17] = named;
|
||||
$[18] = t9;
|
||||
} else {
|
||||
t9 = $[18];
|
||||
}
|
||||
let t10;
|
||||
if ($[19] !== namedVariable) {
|
||||
t10 = namedVariable();
|
||||
$[19] = namedVariable;
|
||||
$[20] = t10;
|
||||
} else {
|
||||
t10 = $[20];
|
||||
}
|
||||
const t11 = methodCall();
|
||||
const t12 = call();
|
||||
let t13;
|
||||
if ($[21] !== hookArgument) {
|
||||
t13 = hookArgument();
|
||||
$[21] = hookArgument;
|
||||
$[22] = t13;
|
||||
} else {
|
||||
t13 = $[22];
|
||||
}
|
||||
let t14;
|
||||
if (
|
||||
$[23] !== builtinElementAttr ||
|
||||
$[24] !== namedElementAttr ||
|
||||
$[25] !== t10 ||
|
||||
$[26] !== t11 ||
|
||||
$[27] !== t12 ||
|
||||
$[28] !== t13 ||
|
||||
$[29] !== t9
|
||||
) {
|
||||
t14 = (
|
||||
<>
|
||||
{t9}
|
||||
{t10}
|
||||
{t11}
|
||||
{t12}
|
||||
{builtinElementAttr}
|
||||
{namedElementAttr}
|
||||
{t13}
|
||||
</>
|
||||
);
|
||||
$[23] = builtinElementAttr;
|
||||
$[24] = namedElementAttr;
|
||||
$[25] = t10;
|
||||
$[26] = t11;
|
||||
$[27] = t12;
|
||||
$[28] = t13;
|
||||
$[29] = t9;
|
||||
$[30] = t14;
|
||||
} else {
|
||||
t14 = $[30];
|
||||
}
|
||||
return t14;
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [
|
||||
{
|
||||
named: "<named>",
|
||||
namedVariable: "<namedVariable>",
|
||||
methodCall: "<methodCall>",
|
||||
call: "<call>",
|
||||
builtinElementAttr: "<builtinElementAttr>",
|
||||
namedElementAttr: "<namedElementAttr>",
|
||||
hookArgument: "<hookArgument>",
|
||||
useEffect: "<useEffect>",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
@@ -0,0 +1,53 @@
|
||||
// @enableNameAnonymousFunctions
|
||||
|
||||
import {useEffect} from 'react';
|
||||
import {identity, Stringify, useIdentity} from 'shared-runtime';
|
||||
import * as SharedRuntime from 'shared-runtime';
|
||||
|
||||
function Component(props) {
|
||||
function named() {
|
||||
const inner = () => props.named;
|
||||
return inner();
|
||||
}
|
||||
const namedVariable = function () {
|
||||
return props.namedVariable;
|
||||
};
|
||||
const methodCall = SharedRuntime.identity(() => props.methodCall);
|
||||
const call = identity(() => props.call);
|
||||
const builtinElementAttr = <div onClick={() => props.builtinElementAttr} />;
|
||||
const namedElementAttr = <Stringify onClick={() => props.namedElementAttr} />;
|
||||
const hookArgument = useIdentity(() => props.hookArgument);
|
||||
useEffect(() => {
|
||||
console.log(props.useEffect);
|
||||
JSON.stringify(null, null, () => props.useEffect);
|
||||
const g = () => props.useEffect;
|
||||
console.log(g());
|
||||
}, [props.useEffect]);
|
||||
return (
|
||||
<>
|
||||
{named()}
|
||||
{namedVariable()}
|
||||
{methodCall()}
|
||||
{call()}
|
||||
{builtinElementAttr}
|
||||
{namedElementAttr}
|
||||
{hookArgument()}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const TODO_FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [
|
||||
{
|
||||
named: '<named>',
|
||||
namedVariable: '<namedVariable>',
|
||||
methodCall: '<methodCall>',
|
||||
call: '<call>',
|
||||
builtinElementAttr: '<builtinElementAttr>',
|
||||
namedElementAttr: '<namedElementAttr>',
|
||||
hookArgument: '<hookArgument>',
|
||||
useEffect: '<useEffect>',
|
||||
},
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user