[compiler] More fbt compatibility

In my previous PR I fixed some cases but broke others. So, new approach. Two phase algorithm:

* First pass is forward data flow to determine all usages of macros. This is necessary because many of Meta's macros have variants that can be accessed via properties, eg you can do `macro(...)` but also `macro.variant(...)`.
* Second pass is backwards data flow to find macro invocations (JSX and calls) and then merge their operands into the same scope as the macro call.

Note that this required updating PromoteUsedTemporaries to avoid promoting macro calls that have interposing instructions between their creation and usage. Macro calls in general are pure so it should be safe to reorder them.
This commit is contained in:
Joe Savona
2025-10-17 08:49:43 -07:00
parent 1324e1bb1f
commit 08cbe39cc9
16 changed files with 480 additions and 298 deletions
@@ -499,7 +499,7 @@ function runWithEnvironment(
value: reactiveFunction,
});
promoteUsedTemporaries(reactiveFunction);
promoteUsedTemporaries(reactiveFunction, fbtOperands);
log({
kind: 'reactive',
name: 'PromoteUsedTemporaries',
@@ -83,21 +83,11 @@ export type ExternalFunction = z.infer<typeof ExternalFunctionSchema>;
export const USE_FIRE_FUNCTION_NAME = 'useFire';
export const EMIT_FREEZE_GLOBAL_GATING = '__DEV__';
export const MacroMethodSchema = z.union([
z.object({type: z.literal('wildcard')}),
z.object({type: z.literal('name'), name: z.string()}),
]);
// Would like to change this to drop the string option, but breaks compatibility with existing configs
export const MacroSchema = z.union([
z.string(),
z.tuple([z.string(), z.array(MacroMethodSchema)]),
]);
export const MacroSchema = z.string();
export type CompilerMode = 'all_features' | 'no_inferred_memo';
export type Macro = z.infer<typeof MacroSchema>;
export type MacroMethod = z.infer<typeof MacroMethodSchema>;
const HookSchema = z.object({
/*
@@ -7,7 +7,6 @@
import {
HIRFunction,
Identifier,
IdentifierId,
InstructionValue,
makeInstructionId,
@@ -15,9 +14,35 @@ import {
Place,
ReactiveScope,
} from '../HIR';
import {Macro, MacroMethod} from '../HIR/Environment';
import {Macro} from '../HIR/Environment';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {Iterable_some} from '../Utils/utils';
/**
* Whether a macro requires its arguments to be transitively inlined (eg fbt)
* or just avoid having the top-level values be converted to variables (eg fbt.param)
*/
enum InlineLevel {
Transitive = 'Transitive',
Shallow = 'Shallow',
}
type MacroDefinition = {
level: InlineLevel;
properties: Map<string, MacroDefinition> | null;
};
const SHALLOW_MACRO: MacroDefinition = {
level: InlineLevel.Shallow,
properties: null,
};
const TRANSITIVE_MACRO: MacroDefinition = {
level: InlineLevel.Transitive,
properties: null,
};
const FBT_MACRO: MacroDefinition = {
level: InlineLevel.Transitive,
properties: new Map([['*', SHALLOW_MACRO]]),
};
FBT_MACRO.properties!.set('enum', FBT_MACRO);
/**
* This pass supports the `fbt` translation system (https://facebook.github.io/fbt/)
@@ -42,250 +67,210 @@ import {Iterable_some} from '../Utils/utils';
* ## User-defined macro-like function
*
* Users can also specify their own functions to be treated similarly to fbt via the
* `customMacros` environment configuration.
* `customMacros` environment configuration. By default, user-supplied custom macros
* have their arguments transitively inlined.
*/
export function memoizeFbtAndMacroOperandsInSameScope(
fn: HIRFunction,
): Set<IdentifierId> {
const fbtMacroTags = new Set<Macro>([
...Array.from(FBT_TAGS).map((tag): Macro => [tag, []]),
...(fn.env.config.customMacros ?? []),
const macroKinds = new Map<Macro, MacroDefinition>([
...Array.from(FBT_TAGS.entries()),
...(fn.env.config.customMacros ?? []).map(
name => [name, TRANSITIVE_MACRO] as [Macro, MacroDefinition],
),
]);
/**
* Set of all identifiers that load fbt or other macro functions or their nested
* properties, as well as values known to be the results of invoking macros
* Forward data-flow analysis to identify all macro tags, including
* things like `fbt.foo.bar(...)`
*/
const macroTagsCalls: Set<IdentifierId> = new Set();
const macroTags = populateMacroTags(fn, macroKinds);
/**
* Mapping of lvalue => list of operands for all expressions where either
* the lvalue is a known fbt/macro call and/or the operands transitively
* contain fbt/macro calls.
*
* This is the key data structure that powers the scope merging: we start
* at the lvalues and merge operands into the lvalue's scope.
* Reverse data-flow analysis to merge arguments to macro *invocations*
* based on the kind of the macro
*/
const macroValues: Map<Identifier, Array<Identifier>> = new Map();
// Tracks methods loaded from macros, like fbt.param or idx.foo
const macroMethods = new Map<IdentifierId, Array<Array<MacroMethod>>>();
const macroValues = mergeMacroArguments(fn, macroTags, macroKinds);
visit(fn, fbtMacroTags, macroTagsCalls, macroMethods, macroValues);
for (const root of macroValues.keys()) {
const scope = root.scope;
if (scope == null) {
continue;
}
// Merge the operands into the same scope if this is a known macro invocation
if (!macroTagsCalls.has(root.id)) {
continue;
}
mergeScopes(root, scope, macroValues, macroTagsCalls);
}
return macroTagsCalls;
return macroValues;
}
export const FBT_TAGS: Set<string> = new Set([
'fbt',
'fbt:param',
'fbt:enum',
'fbt:plural',
'fbs',
'fbs:param',
'fbs:enum',
'fbs:plural',
const FBT_TAGS: Map<string, MacroDefinition> = new Map([
['fbt', FBT_MACRO],
['fbt:param', SHALLOW_MACRO],
['fbt:enum', FBT_MACRO],
['fbt:plural', SHALLOW_MACRO],
['fbs', FBT_MACRO],
['fbs:param', SHALLOW_MACRO],
['fbs:enum', FBT_MACRO],
['fbs:plural', SHALLOW_MACRO],
]);
export const SINGLE_CHILD_FBT_TAGS: Set<string> = new Set([
'fbt:param',
'fbs:param',
]);
function visit(
function populateMacroTags(
fn: HIRFunction,
fbtMacroTags: Set<Macro>,
macroTagsCalls: Set<IdentifierId>,
macroMethods: Map<IdentifierId, Array<Array<MacroMethod>>>,
macroValues: Map<Identifier, Array<Identifier>>,
): void {
for (const [, block] of fn.body.blocks) {
for (const phi of block.phis) {
const macroOperands: Array<Identifier> = [];
for (const operand of phi.operands.values()) {
if (macroValues.has(operand.identifier)) {
macroOperands.push(operand.identifier);
}
}
if (macroOperands.length !== 0) {
macroValues.set(phi.place.identifier, macroOperands);
}
}
for (const instruction of block.instructions) {
const {lvalue, value} = instruction;
if (lvalue === null) {
continue;
}
if (
value.kind === 'Primitive' &&
typeof value.value === 'string' &&
matchesExactTag(value.value, fbtMacroTags)
) {
/*
* We don't distinguish between tag names and strings, so record
* all `fbt` string literals in case they are used as a jsx tag.
*/
macroTagsCalls.add(lvalue.identifier.id);
} else if (
value.kind === 'LoadGlobal' &&
matchesExactTag(value.binding.name, fbtMacroTags)
) {
// Record references to `fbt` as a global
macroTagsCalls.add(lvalue.identifier.id);
} else if (
value.kind === 'LoadGlobal' &&
matchTagRoot(value.binding.name, fbtMacroTags) !== null
) {
const methods = matchTagRoot(value.binding.name, fbtMacroTags)!;
macroMethods.set(lvalue.identifier.id, methods);
} else if (
value.kind === 'PropertyLoad' &&
macroMethods.has(value.object.identifier.id)
) {
const methods = macroMethods.get(value.object.identifier.id)!;
const newMethods = [];
for (const method of methods) {
if (
method.length > 0 &&
(method[0].type === 'wildcard' ||
(method[0].type === 'name' && method[0].name === value.property))
) {
if (method.length > 1) {
newMethods.push(method.slice(1));
} else {
macroTagsCalls.add(lvalue.identifier.id);
macroKinds: Map<Macro, MacroDefinition>,
): Map<IdentifierId, MacroDefinition> {
const macroTags = new Map<IdentifierId, MacroDefinition>();
for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) {
const {lvalue, value} = instr;
switch (value.kind) {
case 'Primitive': {
if (typeof value.value === 'string') {
const macroDefinition = macroKinds.get(value.value);
if (macroDefinition != null) {
/*
* We don't distinguish between tag names and strings, so record
* all `fbt` string literals in case they are used as a jsx tag.
*/
macroTags.set(lvalue.identifier.id, macroDefinition);
}
}
break;
}
if (newMethods.length > 0) {
macroMethods.set(lvalue.identifier.id, newMethods);
}
} else if (
value.kind === 'PropertyLoad' &&
macroTagsCalls.has(value.object.identifier.id)
) {
macroTagsCalls.add(lvalue.identifier.id);
} else if (
isFbtJsxExpression(fbtMacroTags, macroTagsCalls, value) ||
isFbtJsxChild(macroTagsCalls, lvalue, value) ||
isFbtCallExpression(macroTagsCalls, value)
) {
macroTagsCalls.add(lvalue.identifier.id);
macroValues.set(
lvalue.identifier,
Array.from(
eachInstructionValueOperand(value),
operand => operand.identifier,
),
);
} else if (
Iterable_some(eachInstructionValueOperand(value), operand =>
macroValues.has(operand.identifier),
)
) {
const macroOperands: Array<Identifier> = [];
for (const operand of eachInstructionValueOperand(value)) {
if (macroValues.has(operand.identifier)) {
macroOperands.push(operand.identifier);
case 'LoadGlobal': {
let macroDefinition = macroKinds.get(value.binding.name);
if (macroDefinition != null) {
macroTags.set(lvalue.identifier.id, macroDefinition);
}
break;
}
case 'PropertyLoad': {
if (typeof value.property === 'string') {
const macroDefinition = macroTags.get(value.object.identifier.id);
if (macroDefinition != null) {
const propertyDefinition =
macroDefinition.properties != null
? (macroDefinition.properties.get(value.property) ??
macroDefinition.properties.get('*'))
: null;
const propertyMacro = propertyDefinition ?? macroDefinition;
macroTags.set(lvalue.identifier.id, propertyMacro);
}
}
break;
}
macroValues.set(lvalue.identifier, macroOperands);
}
}
}
return macroTags;
}
function mergeScopes(
root: Identifier,
scope: ReactiveScope,
macroValues: Map<Identifier, Array<Identifier>>,
macroTagsCalls: Set<IdentifierId>,
): void {
const operands = macroValues.get(root);
if (operands == null) {
return;
}
for (const operand of operands) {
operand.scope = scope;
expandFbtScopeRange(scope.range, operand.mutableRange);
macroTagsCalls.add(operand.id);
mergeScopes(operand, scope, macroValues, macroTagsCalls);
}
}
function matchesExactTag(s: string, tags: Set<Macro>): boolean {
return Array.from(tags).some(macro =>
typeof macro === 'string'
? s === macro
: macro[1].length === 0 && macro[0] === s,
);
}
function matchTagRoot(
s: string,
tags: Set<Macro>,
): Array<Array<MacroMethod>> | null {
const methods: Array<Array<MacroMethod>> = [];
for (const macro of tags) {
if (typeof macro === 'string') {
continue;
function mergeMacroArguments(
fn: HIRFunction,
macroTags: Map<IdentifierId, MacroDefinition>,
macroKinds: Map<Macro, MacroDefinition>,
): Set<IdentifierId> {
const macroValues = new Set<IdentifierId>(macroTags.keys());
for (const block of Array.from(fn.body.blocks.values()).reverse()) {
for (let i = block.instructions.length - 1; i >= 0; i--) {
const instr = block.instructions[i]!;
const {lvalue, value} = instr;
switch (value.kind) {
case 'DeclareContext':
case 'DeclareLocal':
case 'Destructure':
case 'LoadContext':
case 'LoadLocal':
case 'PostfixUpdate':
case 'PrefixUpdate':
case 'StoreContext':
case 'StoreLocal': {
// Instructions that never need to be merged
break;
}
case 'CallExpression':
case 'MethodCall': {
const scope = lvalue.identifier.scope;
if (scope == null) {
continue;
}
const callee =
value.kind === 'CallExpression' ? value.callee : value.property;
const macroDefinition =
macroTags.get(callee.identifier.id) ??
macroTags.get(lvalue.identifier.id);
if (macroDefinition != null) {
visitOperands(
macroDefinition,
scope,
lvalue,
value,
macroValues,
macroTags,
);
}
break;
}
case 'JsxExpression': {
const scope = lvalue.identifier.scope;
if (scope == null) {
continue;
}
let macroDefinition;
if (value.tag.kind === 'Identifier') {
macroDefinition = macroTags.get(value.tag.identifier.id);
} else {
macroDefinition = macroKinds.get(value.tag.name);
}
macroDefinition ??= macroTags.get(lvalue.identifier.id);
if (macroDefinition != null) {
visitOperands(
macroDefinition,
scope,
lvalue,
value,
macroValues,
macroTags,
);
}
break;
}
default: {
const scope = lvalue.identifier.scope;
if (scope == null) {
continue;
}
const macroDefinition = macroTags.get(lvalue.identifier.id);
if (macroDefinition != null) {
visitOperands(
macroDefinition,
scope,
lvalue,
value,
macroValues,
macroTags,
);
}
break;
}
}
}
const [tag, rest] = macro;
if (tag === s && rest.length > 0) {
methods.push(rest);
for (const phi of block.phis) {
const scope = phi.place.identifier.scope;
if (scope == null) {
continue;
}
const macroDefinition = macroTags.get(phi.place.identifier.id);
if (
macroDefinition == null ||
macroDefinition.level === InlineLevel.Shallow
) {
continue;
}
macroValues.add(phi.place.identifier.id);
for (const operand of phi.operands.values()) {
operand.identifier.scope = scope;
expandFbtScopeRange(scope.range, operand.identifier.mutableRange);
macroTags.set(operand.identifier.id, macroDefinition);
macroValues.add(operand.identifier.id);
}
}
}
if (methods.length > 0) {
return methods;
} else {
return null;
}
}
function isFbtCallExpression(
macroTagsCalls: Set<IdentifierId>,
value: InstructionValue,
): boolean {
return (
(value.kind === 'CallExpression' &&
macroTagsCalls.has(value.callee.identifier.id)) ||
(value.kind === 'MethodCall' &&
macroTagsCalls.has(value.property.identifier.id))
);
}
function isFbtJsxExpression(
fbtMacroTags: Set<Macro>,
macroTagsCalls: Set<IdentifierId>,
value: InstructionValue,
): boolean {
return (
value.kind === 'JsxExpression' &&
((value.tag.kind === 'Identifier' &&
macroTagsCalls.has(value.tag.identifier.id)) ||
(value.tag.kind === 'BuiltinTag' &&
matchesExactTag(value.tag.name, fbtMacroTags)))
);
}
function isFbtJsxChild(
macroTagsCalls: Set<IdentifierId>,
lvalue: Place | null,
value: InstructionValue,
): boolean {
return (
(value.kind === 'JsxExpression' || value.kind === 'JsxFragment') &&
lvalue !== null &&
macroTagsCalls.has(lvalue.identifier.id)
);
return macroValues;
}
function expandFbtScopeRange(
@@ -298,3 +283,22 @@ function expandFbtScopeRange(
);
}
}
function visitOperands(
macroDefinition: MacroDefinition,
scope: ReactiveScope,
lvalue: Place,
value: InstructionValue,
macroValues: Set<IdentifierId>,
macroTags: Map<IdentifierId, MacroDefinition>,
): void {
macroValues.add(lvalue.identifier.id);
for (const operand of eachInstructionValueOperand(value)) {
if (macroDefinition.level === InlineLevel.Transitive) {
operand.identifier.scope = scope;
expandFbtScopeRange(scope.range, operand.identifier.mutableRange);
macroTags.set(operand.identifier.id, macroDefinition);
}
macroValues.add(operand.identifier.id);
}
}
@@ -177,6 +177,7 @@ type State = {
DeclarationId,
{activeScopes: Array<ScopeId>; usedOutsideScope: boolean}
>; // true if referenced within another scope, false if only accessed outside of scopes
fbtOperands: Set<IdentifierId>;
};
/**
@@ -270,7 +271,8 @@ class PromoteInterposedTemporaries extends ReactiveFunctionVisitor<InterState> {
if (
needsPromotion &&
identifier.name === null &&
!this.#consts.has(identifier.id)
!this.#consts.has(identifier.id) &&
!this.#promotable.fbtOperands.has(place.identifier.id)
) {
/*
* If the identifier hasn't been promoted but is marked as needing
@@ -429,11 +431,15 @@ class PromoteInterposedTemporaries extends ReactiveFunctionVisitor<InterState> {
}
}
export function promoteUsedTemporaries(fn: ReactiveFunction): void {
export function promoteUsedTemporaries(
fn: ReactiveFunction,
fbtOperands: Set<IdentifierId>,
): void {
const state: State = {
tags: new Set(),
promoted: new Set(),
pruned: new Map(),
fbtOperands,
};
visitReactiveFunction(fn, new CollectPromotableTemporaries(), state);
for (const operand of fn.params) {
@@ -135,16 +135,7 @@ function parseConfigPragmaEnvironmentForTest(
} else if (val) {
const parsedVal = tryParseTestPragmaValue(val).unwrap();
if (key === 'customMacros' && typeof parsedVal === 'string') {
const valSplit = parsedVal.split('.');
const props = [];
for (const elt of valSplit.slice(1)) {
if (elt === '*') {
props.push({type: 'wildcard'});
} else if (elt.length > 0) {
props.push({type: 'name', name: elt});
}
}
maybeConfig[key] = [[valSplit[0], props]];
maybeConfig[key] = [parsedVal.split('.')[0]];
continue;
}
maybeConfig[key] = parsedVal;
@@ -44,15 +44,23 @@ import fbt from "fbt";
import { identity } from "shared-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(5);
let t0;
if ($[0] !== props.count || $[1] !== props.option) {
let t1;
if ($[3] !== props.count) {
t1 = identity(props.count);
$[3] = props.count;
$[4] = t1;
} else {
t1 = $[4];
}
t0 = (
<span>
{fbt._(
{ "*": "{count} votes for {option}", _1: "1 vote for {option}" },
[
fbt._plural(identity(props.count), "count"),
fbt._plural(t1, "count"),
fbt._param(
"option",
@@ -44,15 +44,23 @@ import fbt from "fbt";
import { identity } from "shared-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(5);
let t0;
if ($[0] !== props.count || $[1] !== props.option) {
let t1;
if ($[3] !== props.count) {
t1 = identity(props.count);
$[3] = props.count;
$[4] = t1;
} else {
t1 = $[4];
}
t0 = (
<span>
{fbt._(
{ "*": "{count} votes for {option}", _1: "1 vote for {option}" },
[
fbt._plural(identity(props.count), "count"),
fbt._plural(t1, "count"),
fbt._param(
"option",
@@ -37,7 +37,7 @@ import { c as _c } from "react/compiler-runtime";
import fbt from "fbt";
function Foo(t0) {
const $ = _c(7);
const $ = _c(13);
const { name1, name2 } = t0;
let t1;
if ($[0] !== name1 || $[1] !== name2) {
@@ -50,19 +50,34 @@ function Foo(t0) {
t2 = $[4];
}
let t3;
if ($[5] !== name2) {
t3 = <b>{name2}</b>;
$[5] = name2;
$[6] = t3;
if ($[5] !== name1 || $[6] !== t2) {
t3 = <span key={name1}>{t2}</span>;
$[5] = name1;
$[6] = t2;
$[7] = t3;
} else {
t3 = $[6];
t3 = $[7];
}
let t4;
if ($[8] !== name2) {
t4 = <b>{name2}</b>;
$[8] = name2;
$[9] = t4;
} else {
t4 = $[9];
}
let t5;
if ($[10] !== name2 || $[11] !== t4) {
t5 = <span key={name2}>{t4}</span>;
$[10] = name2;
$[11] = t4;
$[12] = t5;
} else {
t5 = $[12];
}
t1 = fbt._(
"{user1} and {user2} accepted your PR!",
[
fbt._param("user1", <span key={name1}>{t2}</span>),
fbt._param("user2", <span key={name2}>{t3}</span>),
],
[fbt._param("user1", t3), fbt._param("user2", t5)],
{ hk: "2PxMie" },
);
$[0] = name1;
@@ -29,20 +29,24 @@ import { c as _c } from "react/compiler-runtime";
import fbt from "fbt";
function Component(t0) {
const $ = _c(4);
const $ = _c(6);
const { name, data, icon } = t0;
let t1;
if ($[0] !== data || $[1] !== icon || $[2] !== name) {
let t2;
if ($[4] !== name) {
t2 = <Text type="h4">{name}</Text>;
$[4] = name;
$[5] = t2;
} else {
t2 = $[5];
}
t1 = (
<Text type="body4">
{fbt._(
"{item author}{icon}{=m2}",
[
fbt._param(
"item author",
<Text type="h4">{name}</Text>,
),
fbt._param("item author", t2),
fbt._param(
"icon",
@@ -27,16 +27,21 @@ import fbt from "fbt";
import { identity } from "shared-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(4);
let t0;
if ($[0] !== props.text) {
const t1 = identity(props.text);
let t2;
if ($[2] !== t1) {
t2 = <>{t1}</>;
$[2] = t1;
$[3] = t2;
} else {
t2 = $[3];
}
t0 = (
<Foo
value={fbt._(
"{value}%",
[fbt._param("value", <>{identity(props.text)}</>)],
{ hk: "10F5Cc" },
)}
value={fbt._("{value}%", [fbt._param("value", t2)], { hk: "10F5Cc" })}
/>
);
$[0] = props.text;
@@ -0,0 +1,109 @@
## Input
```javascript
// @flow
import {fbt} from 'fbt';
function Example({x}) {
// "Inner Text" needs to be visible to fbt: the <Bar> element cannot
// be memoized separately
return (
<fbt desc="Description">
Outer Text
<Foo key="b" x={x}>
<Bar key="a">Inner Text</Bar>
</Foo>
</fbt>
);
}
function Foo({x, children}) {
'use no memo';
return (
<>
<div>{x}</div>
<span>{children}</span>
</>
);
}
function Bar({children}) {
'use no memo';
return children;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{x: 'Hello'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { fbt } from "fbt";
function Example(t0) {
const $ = _c(2);
const { x } = t0;
let t1;
if ($[0] !== x) {
t1 = fbt._(
"Outer Text {=m1}",
[
fbt._implicitParam(
"=m1",
<Foo key="b" x={x}>
{fbt._(
"{=m1}",
[
fbt._implicitParam(
"=m1",
<Bar key="a">
{fbt._("Inner Text", null, { hk: "32YB0l" })}
</Bar>,
),
],
{ hk: "23dJsI" },
)}
</Foo>,
),
],
{ hk: "2RVA7V" },
);
$[0] = x;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
function Foo({ x, children }) {
"use no memo";
return (
<>
<div>{x}</div>
<span>{children}</span>
</>
);
}
function Bar({ children }) {
"use no memo";
return children;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{ x: "Hello" }],
};
```
### Eval output
(kind: ok) Outer Text <div>Hello</div><span>Inner Text</span>
@@ -0,0 +1,35 @@
// @flow
import {fbt} from 'fbt';
function Example({x}) {
// "Inner Text" needs to be visible to fbt: the <Bar> element cannot
// be memoized separately
return (
<fbt desc="Description">
Outer Text
<Foo key="b" x={x}>
<Bar key="a">Inner Text</Bar>
</Foo>
</fbt>
);
}
function Foo({x, children}) {
'use no memo';
return (
<>
<div>{x}</div>
<span>{children}</span>
</>
);
}
function Bar({children}) {
'use no memo';
return children;
}
export const FIXTURE_ENTRYPOINT = {
fn: Example,
params: [{x: 'Hello'}],
};
@@ -3,7 +3,7 @@
```javascript
import fbt from 'fbt';
import {Stringify} from 'shared-runtime';
import {identity} from 'shared-runtime';
/**
* MemoizeFbtAndMacroOperands needs to account for nested fbt calls.
@@ -16,22 +16,25 @@ import {Stringify} from 'shared-runtime';
function Component({firstname, lastname}) {
'use memo';
return (
<Stringify>
<div>
{fbt(
[
'Name: ',
fbt.param('firstname', <Stringify key={0} name={firstname} />),
fbt.param('firstname', identity(firstname)),
', ',
fbt.param(
'lastname',
<Stringify key={0} name={lastname}>
{fbt('(inner fbt)', 'Inner fbt value')}
</Stringify>
identity(
fbt(
'(inner)' + fbt.param('lastname', identity(lastname)),
'Inner fbt value'
)
)
),
],
'Name'
)}
</Stringify>
</div>
);
}
@@ -48,7 +51,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
import fbt from "fbt";
import { Stringify } from "shared-runtime";
import { identity } from "shared-runtime";
/**
* MemoizeFbtAndMacroOperands needs to account for nested fbt calls.
@@ -70,14 +73,24 @@ function Component(t0) {
fbt._param(
"firstname",
<Stringify key={0} name={firstname} />,
identity(firstname),
),
fbt._param(
"lastname",
<Stringify key={0} name={lastname}>
{fbt._("(inner fbt)", null, { hk: "36qNwF" })}
</Stringify>,
identity(
fbt._(
"(inner){lastname}",
[
fbt._param(
"lastname",
identity(lastname),
),
],
{ hk: "1Kdxyo" },
),
),
),
],
{ hk: "3AiIf8" },
@@ -90,7 +103,7 @@ function Component(t0) {
}
let t2;
if ($[3] !== t1) {
t2 = <Stringify>{t1}</Stringify>;
t2 = <div>{t1}</div>;
$[3] = t1;
$[4] = t2;
} else {
@@ -108,4 +121,4 @@ export const FIXTURE_ENTRYPOINT = {
```
### Eval output
(kind: ok) <div>{"children":"Name: , "}</div>
(kind: ok) <div>Name: first, (inner)last</div>
@@ -1,5 +1,5 @@
import fbt from 'fbt';
import {Stringify} from 'shared-runtime';
import {identity} from 'shared-runtime';
/**
* MemoizeFbtAndMacroOperands needs to account for nested fbt calls.
@@ -12,22 +12,25 @@ import {Stringify} from 'shared-runtime';
function Component({firstname, lastname}) {
'use memo';
return (
<Stringify>
<div>
{fbt(
[
'Name: ',
fbt.param('firstname', <Stringify key={0} name={firstname} />),
fbt.param('firstname', identity(firstname)),
', ',
fbt.param(
'lastname',
<Stringify key={0} name={lastname}>
{fbt('(inner fbt)', 'Inner fbt value')}
</Stringify>
identity(
fbt(
'(inner)' + fbt.param('lastname', identity(lastname)),
'Inner fbt value'
)
)
),
],
'Name'
)}
</Stringify>
</div>
);
}
@@ -37,7 +37,7 @@ function Component(props) {
const $ = _c(16);
let t0;
if ($[0] !== props) {
t0 = idx(props, _temp);
t0 = idx(props, (_) => _.group.label);
$[0] = props;
$[1] = t0;
} else {
@@ -46,7 +46,7 @@ function Component(props) {
const groupName1 = t0;
let t1;
if ($[2] !== props) {
t1 = idx.a(props, _temp2);
t1 = idx.a(props, (__0) => __0.group.label);
$[2] = props;
$[3] = t1;
} else {
@@ -108,12 +108,6 @@ function Component(props) {
}
return t5;
}
function _temp2(__0) {
return __0.group.label;
}
function _temp(_) {
return _.group.label;
}
```
@@ -31,7 +31,7 @@ function Component(props) {
const $ = _c(10);
let t0;
if ($[0] !== props) {
t0 = idx(props, _temp);
t0 = idx(props, (_) => _.group.label);
$[0] = props;
$[1] = t0;
} else {
@@ -74,9 +74,6 @@ function Component(props) {
}
return t3;
}
function _temp(_) {
return _.group.label;
}
```