mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
ForInStatement: specialize inference/types
Replaces the use of `NextIterableOf` in for-in with a new `NextPropertyOf` instruction. The key distinction is `for-of` invokes an arbitrary iterator, which means a) each iteration may mutate the collection being iterated and b) the returned value may be mutable. However, `for-in` invokes a language-level mechanism to iterate: simply iterating alone _cannot_ modify the collection, and the returned value is known to be a primitive.
This commit is contained in:
@@ -943,7 +943,7 @@ function lowerStatement(
|
||||
initBlock
|
||||
);
|
||||
|
||||
// The init of a ForOf statement is compound over a left (VariableDeclaration | LVal) and
|
||||
// The init of a ForIn statement is compound over a left (VariableDeclaration | LVal) and
|
||||
// right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple
|
||||
// instructions when we handle other syntax like Patterns)
|
||||
const left = stmt.get("left");
|
||||
@@ -952,14 +952,14 @@ function lowerStatement(
|
||||
if (left.isVariableDeclaration()) {
|
||||
const declarations = left.get("declarations");
|
||||
CompilerError.invariant(declarations.length === 1, {
|
||||
reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
|
||||
reason: `Expected only one declaration in the init of a ForInStatement, got ${declarations.length}`,
|
||||
description: null,
|
||||
loc: left.node.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
const id = declarations[0].get("id");
|
||||
const nextIterableOf = lowerValueToTemporary(builder, {
|
||||
kind: "NextIterableOf", // TODO: change this to reflect for-in semantics (returns immutable keys, does not modify collection)
|
||||
kind: "NextPropertyOf",
|
||||
loc: leftLoc,
|
||||
value,
|
||||
});
|
||||
@@ -973,7 +973,7 @@ function lowerStatement(
|
||||
test = lowerValueToTemporary(builder, assign);
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
|
||||
reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForInStatement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
loc: left.node.loc ?? null,
|
||||
suggestions: null,
|
||||
|
||||
@@ -788,6 +788,11 @@ export type InstructionValue =
|
||||
value: Place; // the collection
|
||||
loc: SourceLocation;
|
||||
}
|
||||
| {
|
||||
kind: "NextPropertyOf";
|
||||
value: Place; // the collection
|
||||
loc: SourceLocation;
|
||||
}
|
||||
// Models a prefix update expression such as --x or ++y
|
||||
// This instructions increments or decrements the <lvalue>
|
||||
// but evaluates to the value of <value> prior to the update.
|
||||
|
||||
@@ -542,6 +542,10 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
|
||||
value = `NextIterableOf ${printPlace(instrValue.value)}`;
|
||||
break;
|
||||
}
|
||||
case "NextPropertyOf": {
|
||||
value = `NextPropertyOf ${printPlace(instrValue.value)}`;
|
||||
break;
|
||||
}
|
||||
case "Debugger": {
|
||||
value = `Debugger`;
|
||||
break;
|
||||
|
||||
@@ -193,6 +193,10 @@ export function* eachInstructionValueOperand(
|
||||
yield instrValue.value;
|
||||
break;
|
||||
}
|
||||
case "NextPropertyOf": {
|
||||
yield instrValue.value;
|
||||
break;
|
||||
}
|
||||
case "PostfixUpdate":
|
||||
case "PrefixUpdate": {
|
||||
yield instrValue.value;
|
||||
@@ -476,6 +480,10 @@ export function mapInstructionOperands(
|
||||
instrValue.value = fn(instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "NextPropertyOf": {
|
||||
instrValue.value = fn(instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "PostfixUpdate":
|
||||
case "PrefixUpdate": {
|
||||
instrValue.value = fn(instrValue.value);
|
||||
|
||||
@@ -1020,6 +1020,12 @@ function inferBlock(
|
||||
valueKind = ValueKind.Mutable;
|
||||
break;
|
||||
}
|
||||
case "NextPropertyOf": {
|
||||
effectKind = Effect.Read;
|
||||
lvalueEffect = Effect.Store;
|
||||
valueKind = ValueKind.Immutable;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(instrValue, "Unexpected instruction kind");
|
||||
}
|
||||
|
||||
@@ -217,9 +217,11 @@ function pruneableValue(value: InstructionValue, state: State): boolean {
|
||||
// Potentially safe to prune, since they should just be creating new values
|
||||
return false;
|
||||
}
|
||||
case "NextPropertyOf":
|
||||
case "NextIterableOf": {
|
||||
// Technically a NextIterableOf will never be unused because it's always used later by
|
||||
// another StoreLocal or Destructure instruction, but conceptually we can't prune
|
||||
// Technically a NextIterableOf/NextPropertyOf will never be unused because it's
|
||||
// always used later by another StoreLocal or Destructure instruction, but conceptually
|
||||
// we can't prune
|
||||
return false;
|
||||
}
|
||||
case "LoadContext":
|
||||
|
||||
+4
@@ -1212,6 +1212,10 @@ function codegenInstructionValue(
|
||||
value = codegenPlace(cx, instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "NextPropertyOf": {
|
||||
value = codegenPlace(cx, instrValue.value);
|
||||
break;
|
||||
}
|
||||
case "PostfixUpdate": {
|
||||
value = t.updateExpression(
|
||||
instrValue.operation,
|
||||
|
||||
+1
@@ -244,6 +244,7 @@ function mayAllocate(env: Environment, instruction: Instruction): boolean {
|
||||
case "TemplateLiteral":
|
||||
case "Primitive":
|
||||
case "NextIterableOf":
|
||||
case "NextPropertyOf":
|
||||
case "Debugger": {
|
||||
return false;
|
||||
}
|
||||
|
||||
+1
@@ -430,6 +430,7 @@ function computeMemoizationInputs(
|
||||
rvalues: value.children,
|
||||
};
|
||||
}
|
||||
case "NextPropertyOf":
|
||||
case "Debugger":
|
||||
case "ComputedDelete":
|
||||
case "PropertyDelete":
|
||||
|
||||
@@ -260,6 +260,11 @@ function* generateInstructionTypes(
|
||||
break;
|
||||
}
|
||||
|
||||
case "NextPropertyOf": {
|
||||
yield equation(left, { kind: "Primitive" });
|
||||
break;
|
||||
}
|
||||
|
||||
case "DeclareLocal":
|
||||
case "NewExpression":
|
||||
case "JsxExpression":
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
const { identity, mutate } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
let x;
|
||||
const object = { ...props.value };
|
||||
for (const y in object) {
|
||||
x = y;
|
||||
}
|
||||
mutate(x); // can't modify, x is known primitive!
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: { a: "a", b: "B", c: "C!" } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
const { identity, mutate } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
let x;
|
||||
const object = { ...props.value };
|
||||
for (const y in object) {
|
||||
x = y;
|
||||
}
|
||||
|
||||
mutate(x);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: { a: "a", b: "B", c: "C!" } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
const { identity, mutate } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
let x;
|
||||
const object = { ...props.value };
|
||||
for (const y in object) {
|
||||
x = y;
|
||||
}
|
||||
mutate(x); // can't modify, x is known primitive!
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ value: { a: "a", b: "B", c: "C!" } }],
|
||||
};
|
||||
Reference in New Issue
Block a user