Skip to content

Commit f820f5a

Browse files
committed
[compiler] Type inference for tagged template literals
At Meta we have a pattern of using tagged template literals for features that are compiled away: ``` // Relay: graphql`...graphql text...` ``` In many cases these tags produce a primitive value, and we can get even more optimal output if we can tell the compiler about these types. The new moduleTypeProvider gives us the ability to declare such types, this PR extends the compiler to use this type information for TaggedTemplateExpression values. ghstack-source-id: 3cd6511b7f4e708bcb86f3f3fde5773bc51c7197 Pull Request resolved: #30869
1 parent 0123d7c commit f820f5a

9 files changed

+271
-58
lines changed

compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts

+41-12
Original file line numberDiff line numberDiff line change
@@ -1180,18 +1180,6 @@ function inferBlock(
11801180
};
11811181
break;
11821182
}
1183-
case 'TaggedTemplateExpression': {
1184-
valueKind = {
1185-
kind: ValueKind.Mutable,
1186-
reason: new Set([ValueReason.Other]),
1187-
context: new Set(),
1188-
};
1189-
effect = {
1190-
kind: Effect.ConditionallyMutate,
1191-
reason: ValueReason.Other,
1192-
};
1193-
break;
1194-
}
11951183
case 'TemplateLiteral': {
11961184
/*
11971185
* template literal (with no tag function) always produces
@@ -1312,6 +1300,47 @@ function inferBlock(
13121300
instr.lvalue.effect = Effect.Store;
13131301
continue;
13141302
}
1303+
case 'TaggedTemplateExpression': {
1304+
const operands = [...eachInstructionValueOperand(instrValue)];
1305+
if (operands.length !== 1) {
1306+
// future-proofing to make sure we update this case when we support interpolation
1307+
CompilerError.throwTodo({
1308+
reason: 'Support tagged template expressions with interpolations',
1309+
loc: instrValue.loc,
1310+
});
1311+
}
1312+
const signature = getFunctionCallSignature(
1313+
env,
1314+
instrValue.tag.identifier.type,
1315+
);
1316+
let calleeEffect =
1317+
signature?.calleeEffect ?? Effect.ConditionallyMutate;
1318+
const returnValueKind: AbstractValue =
1319+
signature !== null
1320+
? {
1321+
kind: signature.returnValueKind,
1322+
reason: new Set([
1323+
signature.returnValueReason ??
1324+
ValueReason.KnownReturnSignature,
1325+
]),
1326+
context: new Set(),
1327+
}
1328+
: {
1329+
kind: ValueKind.Mutable,
1330+
reason: new Set([ValueReason.Other]),
1331+
context: new Set(),
1332+
};
1333+
state.referenceAndRecordEffects(
1334+
instrValue.tag,
1335+
calleeEffect,
1336+
ValueReason.Other,
1337+
functionEffects,
1338+
);
1339+
state.initialize(instrValue, returnValueKind);
1340+
state.define(instr.lvalue, instrValue);
1341+
instr.lvalue.effect = Effect.ConditionallyMutate;
1342+
continue;
1343+
}
13151344
case 'CallExpression': {
13161345
const signature = getFunctionCallSignature(
13171346
env,

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/InferReactiveScopeVariables.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean {
227227
case 'StoreGlobal': {
228228
return false;
229229
}
230+
case 'TaggedTemplateExpression':
230231
case 'CallExpression':
231232
case 'MethodCall': {
232233
return instruction.lvalue.identifier.type.kind !== 'Primitive';
@@ -241,8 +242,7 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean {
241242
case 'ObjectExpression':
242243
case 'UnsupportedNode':
243244
case 'ObjectMethod':
244-
case 'FunctionExpression':
245-
case 'TaggedTemplateExpression': {
245+
case 'FunctionExpression': {
246246
return true;
247247
}
248248
default: {

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonEscapingScopes.ts

+28-3
Original file line numberDiff line numberDiff line change
@@ -671,12 +671,37 @@ function computeMemoizationInputs(
671671
],
672672
};
673673
}
674+
case 'TaggedTemplateExpression': {
675+
const signature = getFunctionCallSignature(
676+
env,
677+
value.tag.identifier.type,
678+
);
679+
let lvalues = [];
680+
if (lvalue !== null) {
681+
lvalues.push({place: lvalue, level: MemoizationLevel.Memoized});
682+
}
683+
if (signature?.noAlias === true) {
684+
return {
685+
lvalues,
686+
rvalues: [],
687+
};
688+
}
689+
const operands = [...eachReactiveValueOperand(value)];
690+
lvalues.push(
691+
...operands
692+
.filter(operand => isMutableEffect(operand.effect, operand.loc))
693+
.map(place => ({place, level: MemoizationLevel.Memoized})),
694+
);
695+
return {
696+
lvalues,
697+
rvalues: operands,
698+
};
699+
}
674700
case 'CallExpression': {
675701
const signature = getFunctionCallSignature(
676702
env,
677703
value.callee.identifier.type,
678704
);
679-
const operands = [...eachReactiveValueOperand(value)];
680705
let lvalues = [];
681706
if (lvalue !== null) {
682707
lvalues.push({place: lvalue, level: MemoizationLevel.Memoized});
@@ -687,6 +712,7 @@ function computeMemoizationInputs(
687712
rvalues: [],
688713
};
689714
}
715+
const operands = [...eachReactiveValueOperand(value)];
690716
lvalues.push(
691717
...operands
692718
.filter(operand => isMutableEffect(operand.effect, operand.loc))
@@ -702,7 +728,6 @@ function computeMemoizationInputs(
702728
env,
703729
value.property.identifier.type,
704730
);
705-
const operands = [...eachReactiveValueOperand(value)];
706731
let lvalues = [];
707732
if (lvalue !== null) {
708733
lvalues.push({place: lvalue, level: MemoizationLevel.Memoized});
@@ -713,6 +738,7 @@ function computeMemoizationInputs(
713738
rvalues: [],
714739
};
715740
}
741+
const operands = [...eachReactiveValueOperand(value)];
716742
lvalues.push(
717743
...operands
718744
.filter(operand => isMutableEffect(operand.effect, operand.loc))
@@ -726,7 +752,6 @@ function computeMemoizationInputs(
726752
case 'RegExpLiteral':
727753
case 'ObjectMethod':
728754
case 'FunctionExpression':
729-
case 'TaggedTemplateExpression':
730755
case 'ArrayExpression':
731756
case 'NewExpression':
732757
case 'ObjectExpression':

compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ function* generateInstructionTypes(
250250
}
251251

252252
case 'CallExpression': {
253+
const returnType = makeType();
253254
/*
254255
* TODO: callee could be a hook or a function, so this type equation isn't correct.
255256
* We should change Hook to a subtype of Function or change unifier logic.
@@ -258,8 +259,25 @@ function* generateInstructionTypes(
258259
yield equation(value.callee.identifier.type, {
259260
kind: 'Function',
260261
shapeId: null,
261-
return: left,
262+
return: returnType,
262263
});
264+
yield equation(left, returnType);
265+
break;
266+
}
267+
268+
case 'TaggedTemplateExpression': {
269+
const returnType = makeType();
270+
/*
271+
* TODO: callee could be a hook or a function, so this type equation isn't correct.
272+
* We should change Hook to a subtype of Function or change unifier logic.
273+
* (see https://github.com/facebook/react-forget/pull/1427)
274+
*/
275+
yield equation(value.tag.identifier.type, {
276+
kind: 'Function',
277+
shapeId: null,
278+
return: returnType,
279+
});
280+
yield equation(left, returnType);
263281
break;
264282
}
265283

@@ -392,7 +410,6 @@ function* generateInstructionTypes(
392410
case 'MetaProperty':
393411
case 'ComputedStore':
394412
case 'ComputedLoad':
395-
case 'TaggedTemplateExpression':
396413
case 'Await':
397414
case 'GetIterator':
398415
case 'IteratorNext':

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts

+8
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,14 @@ function getContextReassignment(
161161
if (signature?.noAlias) {
162162
operands = [value.receiver, value.property];
163163
}
164+
} else if (value.kind === 'TaggedTemplateExpression') {
165+
const signature = getFunctionCallSignature(
166+
fn.env,
167+
value.tag.identifier.type,
168+
);
169+
if (signature?.noAlias) {
170+
operands = [value.tag];
171+
}
164172
}
165173
for (const operand of operands) {
166174
CompilerError.invariant(operand.effect !== Effect.Unknown, {

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/destructuring-mixed-scope-and-local-variables-with-default.expect.md

+35-39
Original file line numberDiff line numberDiff line change
@@ -63,67 +63,63 @@ function useFragment(_arg1, _arg2) {
6363
}
6464

6565
function Component(props) {
66-
const $ = _c(9);
67-
let t0;
68-
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
69-
t0 = graphql`
66+
const $ = _c(8);
67+
const post = useFragment(
68+
graphql`
7069
fragment F on T {
7170
id
7271
}
73-
`;
74-
$[0] = t0;
75-
} else {
76-
t0 = $[0];
77-
}
78-
const post = useFragment(t0, props.post);
79-
let t1;
80-
if ($[1] !== post) {
72+
`,
73+
props.post,
74+
);
75+
let t0;
76+
if ($[0] !== post) {
8177
const allUrls = [];
8278

83-
const { media: t2, comments: t3, urls: t4 } = post;
84-
const media = t2 === undefined ? null : t2;
79+
const { media: t1, comments: t2, urls: t3 } = post;
80+
const media = t1 === undefined ? null : t1;
81+
let t4;
82+
if ($[2] !== t2) {
83+
t4 = t2 === undefined ? [] : t2;
84+
$[2] = t2;
85+
$[3] = t4;
86+
} else {
87+
t4 = $[3];
88+
}
89+
const comments = t4;
8590
let t5;
86-
if ($[3] !== t3) {
91+
if ($[4] !== t3) {
8792
t5 = t3 === undefined ? [] : t3;
88-
$[3] = t3;
89-
$[4] = t5;
93+
$[4] = t3;
94+
$[5] = t5;
9095
} else {
91-
t5 = $[4];
96+
t5 = $[5];
9297
}
93-
const comments = t5;
98+
const urls = t5;
9499
let t6;
95-
if ($[5] !== t4) {
96-
t6 = t4 === undefined ? [] : t4;
97-
$[5] = t4;
98-
$[6] = t6;
99-
} else {
100-
t6 = $[6];
101-
}
102-
const urls = t6;
103-
let t7;
104-
if ($[7] !== comments.length) {
105-
t7 = (e) => {
100+
if ($[6] !== comments.length) {
101+
t6 = (e) => {
106102
if (!comments.length) {
107103
return;
108104
}
109105

110106
console.log(comments.length);
111107
};
112-
$[7] = comments.length;
113-
$[8] = t7;
108+
$[6] = comments.length;
109+
$[7] = t6;
114110
} else {
115-
t7 = $[8];
111+
t6 = $[7];
116112
}
117-
const onClick = t7;
113+
const onClick = t6;
118114

119115
allUrls.push(...urls);
120-
t1 = <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
121-
$[1] = post;
122-
$[2] = t1;
116+
t0 = <Stringify media={media} allUrls={allUrls} onClick={onClick} />;
117+
$[0] = post;
118+
$[1] = t0;
123119
} else {
124-
t1 = $[2];
120+
t0 = $[1];
125121
}
126-
return t1;
122+
return t0;
127123
}
128124

129125
export const FIXTURE_ENTRYPOINT = {

0 commit comments

Comments
 (0)