19
Environment,
20
FunctionExpression,
21
GeneratedSource,
22
+ getHookKind,
23
HIRFunction,
24
Hole,
25
IdentifierId,
199
isFunctionExpression,
200
fn,
201
hoistedContextDeclarations,
202
+ findNonMutatedDestructureSpreads(fn),
203
);
204
205
let iterationCount = 0;
289
isFuctionExpression: boolean;
290
fn: HIRFunction;
291
hoistedContextDeclarations: Map<DeclarationId, Place | null>;
292
+ nonMutatingSpreads: Set<IdentifierId>;
293
294
constructor(
295
isFunctionExpression: boolean,
296
fn: HIRFunction,
297
hoistedContextDeclarations: Map<DeclarationId, Place | null>,
298
+ nonMutatingSpreads: Set<IdentifierId>,
299
) {
300
this.isFuctionExpression = isFunctionExpression;
301
this.fn = fn;
302
this.hoistedContextDeclarations = hoistedContextDeclarations;
303
+ this.nonMutatingSpreads = nonMutatingSpreads;
304
}
305
306
cacheApplySignature(
327
}
328
}
329
330
+/**
331
+ * Finds objects created via ObjectPattern spread destructuring
332
+ * (`const {x, ...spread} = ...`) where a) the rvalue is known frozen and
333
+ * b) the spread value cannot possibly be directly mutated. The idea is that
334
+ * for this set of values, we can treat the spread object as frozen.
335
+ *
336
+ * The primary use case for this is props spreading:
337
+ *
338
+ * ```
339
+ * function Component({prop, ...otherProps}) {
340
+ * const transformedProp = transform(prop, otherProps.foo);
341
+ * // pass `otherProps` down:
342
+ * return <Foo {...otherProps} prop={transformedProp} />;
343
+ * }
344
+ * ```
345
+ *
346
+ * Here we know that since `otherProps` cannot be mutated, we don't have to treat
347
+ * it as mutable: `otherProps.foo` only reads a value that must be frozen, so it
348
+ * can be treated as frozen too.
349
+ */
350
+function findNonMutatedDestructureSpreads(fn: HIRFunction): Set<IdentifierId> {
351
+ const knownFrozen = new Set<IdentifierId>();
352
+ if (fn.fnType === 'Component') {
353
+ const [props] = fn.params;
354
+ if (props != null && props.kind === 'Identifier') {
355
+ knownFrozen.add(props.identifier.id);
356
+ }
357
+ } else {
358
+ for (const param of fn.params) {
359
+ if (param.kind === 'Identifier') {
360
+ knownFrozen.add(param.identifier.id);
361
+ }
362
+ }
363
+ }
364
+
365
+ // Map of temporaries to identifiers for spread objects
366
+ const candidateNonMutatingSpreads = new Map<IdentifierId, IdentifierId>();
367
+ for (const block of fn.body.blocks.values()) {
368
+ if (candidateNonMutatingSpreads.size !== 0) {
369
+ for (const phi of block.phis) {
370
+ for (const operand of phi.operands.values()) {
371
+ const spread = candidateNonMutatingSpreads.get(operand.identifier.id);
372
+ if (spread != null) {
373
+ candidateNonMutatingSpreads.delete(spread);
374
+ }
375
+ }
376
+ }
377
+ }
378
+ for (const instr of block.instructions) {
379
+ const {lvalue, value} = instr;
380
+ switch (value.kind) {
381
+ case 'Destructure': {
382
+ if (
383
+ !knownFrozen.has(value.value.identifier.id) ||
384
+ !(
385
+ value.lvalue.kind === InstructionKind.Let ||
386
+ value.lvalue.kind === InstructionKind.Const
387
+ ) ||
388
+ value.lvalue.pattern.kind !== 'ObjectPattern'
389
+ ) {
390
+ continue;
391
+ }
392
+ for (const item of value.lvalue.pattern.properties) {
393
+ if (item.kind !== 'Spread') {
394
+ continue;
395
+ }
396
+ candidateNonMutatingSpreads.set(
397
+ item.place.identifier.id,
398
+ item.place.identifier.id,
399
+ );
400
+ }
401
+ break;
402
+ }
403
+ case 'LoadLocal': {
404
+ const spread = candidateNonMutatingSpreads.get(
405
+ value.place.identifier.id,
406
+ );
407
+ if (spread != null) {
408
+ candidateNonMutatingSpreads.set(lvalue.identifier.id, spread);
409
+ }
410
+ break;
411
+ }
412
+ case 'StoreLocal': {
413
+ const spread = candidateNonMutatingSpreads.get(
414
+ value.value.identifier.id,
415
+ );
416
+ if (spread != null) {
417
+ candidateNonMutatingSpreads.set(lvalue.identifier.id, spread);
418
+ candidateNonMutatingSpreads.set(
419
+ value.lvalue.place.identifier.id,
420
+ spread,
421
+ );
422
+ }
423
+ break;
424
+ }
425
+ case 'JsxFragment':
426
+ case 'JsxExpression': {
427
+ // Passing objects created with spread to jsx can't mutate them
428
+ break;
429
+ }
430
+ case 'PropertyLoad': {
431
+ // Properties must be frozen since the original value was frozen
432
+ break;
433
+ }
434
+ case 'CallExpression':
435
+ case 'MethodCall': {
436
+ const callee =
437
+ value.kind === 'CallExpression' ? value.callee : value.property;
438
+ if (getHookKind(fn.env, callee.identifier) != null) {
439
+ // Hook calls have frozen arguments, and non-ref returns are frozen
440
+ if (!isRefOrRefValue(lvalue.identifier)) {
441
+ knownFrozen.add(lvalue.identifier.id);
442
+ }
443
+ } else {
444
+ // Non-hook calls check their operands, since they are potentially mutable
445
+ if (candidateNonMutatingSpreads.size !== 0) {
446
+ // Otherwise any reference to the spread object itself may mutate
447
+ for (const operand of eachInstructionValueOperand(value)) {
448
+ const spread = candidateNonMutatingSpreads.get(
449
+ operand.identifier.id,
450
+ );
451
+ if (spread != null) {
452
+ candidateNonMutatingSpreads.delete(spread);
453
+ }
454
+ }
455
+ }
456
+ }
457
+ break;
458
+ }
459
+ default: {
460
+ if (candidateNonMutatingSpreads.size !== 0) {
461
+ // Otherwise any reference to the spread object itself may mutate
462
+ for (const operand of eachInstructionValueOperand(value)) {
463
+ const spread = candidateNonMutatingSpreads.get(
464
+ operand.identifier.id,
465
+ );
466
+ if (spread != null) {
467
+ candidateNonMutatingSpreads.delete(spread);
468
+ }
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }
474
+ }
475
+
476
+ const nonMutatingSpreads = new Set<IdentifierId>();
477
+ for (const [key, value] of candidateNonMutatingSpreads) {
478
+ if (key === value) {
479
+ nonMutatingSpreads.add(key);
480
+ }
481
+ }
482
+ return nonMutatingSpreads;
483
+}
484
+
485
function inferParam(
486
param: Place | SpreadPattern,
487
initialState: InferenceState,
2214
kind: 'Create',
2215
into: place,
2216
reason: ValueReason.Other,
2057
- value: ValueKind.Mutable,
2217
+ value: context.nonMutatingSpreads.has(place.identifier.id)
2218
+ ? ValueKind.Frozen
2219
+ : ValueKind.Mutable,
2220
});
2221
effects.push({
2222
kind: 'Capture',