mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[compiler] Represent array accesses with PropertyLoad (#32287)
Prior to this PR, our HIR represented property access with numeric literals (e.g. `myVar[0]`) as ComputedLoads. This means that they were subject to some deopts (most notably, not being easily dedupable / hoistable as dependencies). Now, `PropertyLoad`, `PropertyStore`, etc reference numeric and string literals (although not yet string literals that aren't valid babel identifiers). The difference between PropertyLoad and ComputedLoad is fuzzy now (maybe we should rename these). - PropertyLoad: property keys are string and numeric literals, only when the string literals are valid babel identifiers - ComputedLoad: non-valid babel identifier string literals (rare) and other non-literal expressions The biggest feature from this PR is that it trivially enables array-indicing expressions as dependencies. The compiler can also specify global and imported types for arrays (e.g. return value of `useState`) I'm happy to close this if it complicates more than it helps -- alternative options are to entirely rely on instruction reordering-based approaches like ReactiveGraphIR or make dependency-specific parsing + hoisting logic more robust.
This commit is contained in:
parent
19cc5af41e
commit
a9575dcf62
|
|
@ -36,12 +36,14 @@ import {
|
|||
ObjectProperty,
|
||||
ObjectPropertyKey,
|
||||
Place,
|
||||
PropertyLiteral,
|
||||
ReturnTerminal,
|
||||
SourceLocation,
|
||||
SpreadPattern,
|
||||
ThrowTerminal,
|
||||
Type,
|
||||
makeInstructionId,
|
||||
makePropertyLiteral,
|
||||
makeType,
|
||||
promoteTemporary,
|
||||
} from './HIR';
|
||||
|
|
@ -2017,11 +2019,11 @@ function lowerExpression(
|
|||
});
|
||||
|
||||
// Save the result back to the property
|
||||
if (typeof property === 'string') {
|
||||
if (typeof property === 'string' || typeof property === 'number') {
|
||||
return {
|
||||
kind: 'PropertyStore',
|
||||
object: {...object},
|
||||
property,
|
||||
property: makePropertyLiteral(property),
|
||||
value: {...newValuePlace},
|
||||
loc: leftExpr.node.loc ?? GeneratedSource,
|
||||
};
|
||||
|
|
@ -2316,11 +2318,11 @@ function lowerExpression(
|
|||
const argument = expr.get('argument');
|
||||
if (argument.isMemberExpression()) {
|
||||
const {object, property} = lowerMemberExpression(builder, argument);
|
||||
if (typeof property === 'string') {
|
||||
if (typeof property === 'string' || typeof property === 'number') {
|
||||
return {
|
||||
kind: 'PropertyDelete',
|
||||
object,
|
||||
property,
|
||||
property: makePropertyLiteral(property),
|
||||
loc: exprLoc,
|
||||
};
|
||||
} else {
|
||||
|
|
@ -2427,11 +2429,11 @@ function lowerExpression(
|
|||
|
||||
// Save the result back to the property
|
||||
let newValuePlace;
|
||||
if (typeof property === 'string') {
|
||||
if (typeof property === 'string' || typeof property === 'number') {
|
||||
newValuePlace = lowerValueToTemporary(builder, {
|
||||
kind: 'PropertyStore',
|
||||
object: {...object},
|
||||
property,
|
||||
property: makePropertyLiteral(property),
|
||||
value: {...updatedValue},
|
||||
loc: leftExpr.node.loc ?? GeneratedSource,
|
||||
});
|
||||
|
|
@ -3057,7 +3059,7 @@ function lowerArguments(
|
|||
|
||||
type LoweredMemberExpression = {
|
||||
object: Place;
|
||||
property: Place | string;
|
||||
property: Place | string | number;
|
||||
value: InstructionValue;
|
||||
};
|
||||
function lowerMemberExpression(
|
||||
|
|
@ -3072,8 +3074,13 @@ function lowerMemberExpression(
|
|||
const object =
|
||||
loweredObject ?? lowerExpressionToTemporary(builder, objectNode);
|
||||
|
||||
if (!expr.node.computed) {
|
||||
if (!propertyNode.isIdentifier()) {
|
||||
if (!expr.node.computed || expr.node.property.type === 'NumericLiteral') {
|
||||
let property: PropertyLiteral;
|
||||
if (propertyNode.isIdentifier()) {
|
||||
property = makePropertyLiteral(propertyNode.node.name);
|
||||
} else if (propertyNode.isNumericLiteral()) {
|
||||
property = makePropertyLiteral(propertyNode.node.value);
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerMemberExpression) Handle ${propertyNode.type} property`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
|
|
@ -3089,10 +3096,10 @@ function lowerMemberExpression(
|
|||
const value: InstructionValue = {
|
||||
kind: 'PropertyLoad',
|
||||
object: {...object},
|
||||
property: propertyNode.node.name,
|
||||
property,
|
||||
loc: exprLoc,
|
||||
};
|
||||
return {object, property: propertyNode.node.name, value};
|
||||
return {object, property, value};
|
||||
} else {
|
||||
if (!propertyNode.isExpression()) {
|
||||
builder.errors.push({
|
||||
|
|
@ -3210,7 +3217,7 @@ function lowerJsxMemberExpression(
|
|||
return lowerValueToTemporary(builder, {
|
||||
kind: 'PropertyLoad',
|
||||
object: objectPlace,
|
||||
property,
|
||||
property: makePropertyLiteral(property),
|
||||
loc,
|
||||
});
|
||||
}
|
||||
|
|
@ -3626,8 +3633,25 @@ function lowerAssignment(
|
|||
const lvalue = lvaluePath as NodePath<t.MemberExpression>;
|
||||
const property = lvalue.get('property');
|
||||
const object = lowerExpressionToTemporary(builder, lvalue.get('object'));
|
||||
if (!lvalue.node.computed) {
|
||||
if (!property.isIdentifier()) {
|
||||
if (!lvalue.node.computed || lvalue.get('property').isNumericLiteral()) {
|
||||
let temporary;
|
||||
if (property.isIdentifier()) {
|
||||
temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'PropertyStore',
|
||||
object,
|
||||
property: makePropertyLiteral(property.node.name),
|
||||
value,
|
||||
loc,
|
||||
});
|
||||
} else if (property.isNumericLiteral()) {
|
||||
temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'PropertyStore',
|
||||
object,
|
||||
property: makePropertyLiteral(property.node.value),
|
||||
value,
|
||||
loc,
|
||||
});
|
||||
} else {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerAssignment) Handle ${property.type} properties in MemberExpression`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
|
|
@ -3636,13 +3660,6 @@ function lowerAssignment(
|
|||
});
|
||||
return {kind: 'UnsupportedNode', node: lvalueNode, loc};
|
||||
}
|
||||
const temporary = lowerValueToTemporary(builder, {
|
||||
kind: 'PropertyStore',
|
||||
object,
|
||||
property: property.node.name,
|
||||
value,
|
||||
loc,
|
||||
});
|
||||
return {kind: 'LoadLocal', place: temporary, loc: temporary.loc};
|
||||
} else {
|
||||
if (!property.isExpression()) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
IdentifierId,
|
||||
InstructionId,
|
||||
InstructionValue,
|
||||
PropertyLiteral,
|
||||
ReactiveScopeDependency,
|
||||
ScopeId,
|
||||
} from './HIR';
|
||||
|
|
@ -172,8 +173,8 @@ export type BlockInfo = {
|
|||
* and make computing sets intersections simpler.
|
||||
*/
|
||||
type RootNode = {
|
||||
properties: Map<string, PropertyPathNode>;
|
||||
optionalProperties: Map<string, PropertyPathNode>;
|
||||
properties: Map<PropertyLiteral, PropertyPathNode>;
|
||||
optionalProperties: Map<PropertyLiteral, PropertyPathNode>;
|
||||
parent: null;
|
||||
// Recorded to make later computations simpler
|
||||
fullPath: ReactiveScopeDependency;
|
||||
|
|
@ -183,8 +184,8 @@ type RootNode = {
|
|||
|
||||
type PropertyPathNode =
|
||||
| {
|
||||
properties: Map<string, PropertyPathNode>;
|
||||
optionalProperties: Map<string, PropertyPathNode>;
|
||||
properties: Map<PropertyLiteral, PropertyPathNode>;
|
||||
optionalProperties: Map<PropertyLiteral, PropertyPathNode>;
|
||||
parent: PropertyPathNode;
|
||||
fullPath: ReactiveScopeDependency;
|
||||
hasOptional: boolean;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
DependencyPathEntry,
|
||||
Instruction,
|
||||
Terminal,
|
||||
PropertyLiteral,
|
||||
} from './HIR';
|
||||
import {printIdentifier} from './PrintHIR';
|
||||
|
||||
|
|
@ -157,7 +158,7 @@ function matchOptionalTestBlock(
|
|||
blocks: ReadonlyMap<BlockId, BasicBlock>,
|
||||
): {
|
||||
consequentId: IdentifierId;
|
||||
property: string;
|
||||
property: PropertyLiteral;
|
||||
propertyId: IdentifierId;
|
||||
storeLocalInstr: Instruction;
|
||||
consequentGoto: BlockId;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
DependencyPathEntry,
|
||||
GeneratedSource,
|
||||
Identifier,
|
||||
PropertyLiteral,
|
||||
ReactiveScopeDependency,
|
||||
} from '../HIR';
|
||||
import {printIdentifier} from '../HIR/PrintHIR';
|
||||
|
|
@ -286,7 +287,7 @@ function merge(
|
|||
}
|
||||
|
||||
type TreeNode<T extends string> = {
|
||||
properties: Map<string, TreeNode<T>>;
|
||||
properties: Map<PropertyLiteral, TreeNode<T>>;
|
||||
accessType: T;
|
||||
};
|
||||
type HoistableNode = TreeNode<'Optional' | 'NonNull'>;
|
||||
|
|
@ -343,7 +344,7 @@ function printSubtree(
|
|||
|
||||
function makeOrMergeProperty(
|
||||
node: DependencyNode,
|
||||
property: string,
|
||||
property: PropertyLiteral,
|
||||
accessType: PropertyAccessType,
|
||||
): DependencyNode {
|
||||
let child = node.properties.get(property);
|
||||
|
|
|
|||
|
|
@ -937,7 +937,7 @@ export type InstructionValue =
|
|||
| {
|
||||
kind: 'PropertyStore';
|
||||
object: Place;
|
||||
property: string;
|
||||
property: PropertyLiteral;
|
||||
value: Place;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
|
|
@ -947,7 +947,7 @@ export type InstructionValue =
|
|||
| {
|
||||
kind: 'PropertyDelete';
|
||||
object: Place;
|
||||
property: string;
|
||||
property: PropertyLiteral;
|
||||
loc: SourceLocation;
|
||||
}
|
||||
|
||||
|
|
@ -1121,7 +1121,7 @@ export type StoreLocal = {
|
|||
export type PropertyLoad = {
|
||||
kind: 'PropertyLoad';
|
||||
object: Place;
|
||||
property: string;
|
||||
property: PropertyLiteral;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
|
|
@ -1502,7 +1502,17 @@ export type ReactiveScopeDeclaration = {
|
|||
scope: ReactiveScope; // the scope in which the variable was originally declared
|
||||
};
|
||||
|
||||
export type DependencyPathEntry = {property: string; optional: boolean};
|
||||
const opaquePropertyLiteral = Symbol();
|
||||
export type PropertyLiteral = (string | number) & {
|
||||
[opaquePropertyLiteral]: 'PropertyLiteral';
|
||||
};
|
||||
export function makePropertyLiteral(value: string | number): PropertyLiteral {
|
||||
return value as PropertyLiteral;
|
||||
}
|
||||
export type DependencyPathEntry = {
|
||||
property: PropertyLiteral;
|
||||
optional: boolean;
|
||||
};
|
||||
export type DependencyPath = Array<DependencyPathEntry>;
|
||||
export type ReactiveScopeDependency = {
|
||||
identifier: Identifier;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
TInstruction,
|
||||
FunctionExpression,
|
||||
ObjectMethod,
|
||||
PropertyLiteral,
|
||||
} from './HIR';
|
||||
import {
|
||||
collectHoistablePropertyLoads,
|
||||
|
|
@ -321,7 +322,7 @@ function collectTemporariesSidemapImpl(
|
|||
|
||||
function getProperty(
|
||||
object: Place,
|
||||
propertyName: string,
|
||||
propertyName: PropertyLiteral,
|
||||
optional: boolean,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
): ReactiveScopeDependency {
|
||||
|
|
@ -519,7 +520,11 @@ class Context {
|
|||
);
|
||||
}
|
||||
|
||||
visitProperty(object: Place, property: string, optional: boolean): void {
|
||||
visitProperty(
|
||||
object: Place,
|
||||
property: PropertyLiteral,
|
||||
optional: boolean,
|
||||
): void {
|
||||
const nextDependency = getProperty(
|
||||
object,
|
||||
property,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {PropertyLiteral} from './HIR';
|
||||
|
||||
export type BuiltInType = PrimitiveType | FunctionType | ObjectType;
|
||||
|
||||
|
|
@ -59,7 +60,7 @@ export type PropType = {
|
|||
kind: 'Property';
|
||||
objectType: Type;
|
||||
objectName: string;
|
||||
propertyName: string;
|
||||
propertyName: PropertyLiteral;
|
||||
};
|
||||
|
||||
export type ObjectMethod = {
|
||||
|
|
|
|||
|
|
@ -145,9 +145,10 @@ function collectTemporaries(
|
|||
}
|
||||
case 'PropertyLoad': {
|
||||
if (sidemap.react.has(value.object.identifier.id)) {
|
||||
if (value.property === 'useMemo' || value.property === 'useCallback') {
|
||||
const property = value.property;
|
||||
if (property === 'useMemo' || property === 'useCallback') {
|
||||
sidemap.manualMemos.set(instr.lvalue.identifier.id, {
|
||||
kind: value.property,
|
||||
kind: property as 'useMemo' | 'useCallback',
|
||||
loadInstr: instr as TInstruction<PropertyLoad>,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import {
|
|||
Primitive,
|
||||
assertConsistentIdentifiers,
|
||||
assertTerminalSuccessorsExist,
|
||||
makePropertyLiteral,
|
||||
markInstructionIds,
|
||||
markPredecessors,
|
||||
mergeConsecutiveBlocks,
|
||||
|
|
@ -238,13 +239,14 @@ function evaluateInstruction(
|
|||
if (
|
||||
property !== null &&
|
||||
property.kind === 'Primitive' &&
|
||||
typeof property.value === 'string' &&
|
||||
isValidIdentifier(property.value)
|
||||
((typeof property.value === 'string' &&
|
||||
isValidIdentifier(property.value)) ||
|
||||
typeof property.value === 'number')
|
||||
) {
|
||||
const nextValue: InstructionValue = {
|
||||
kind: 'PropertyLoad',
|
||||
loc: value.loc,
|
||||
property: property.value,
|
||||
property: makePropertyLiteral(property.value),
|
||||
object: value.object,
|
||||
};
|
||||
instr.value = nextValue;
|
||||
|
|
@ -256,13 +258,14 @@ function evaluateInstruction(
|
|||
if (
|
||||
property !== null &&
|
||||
property.kind === 'Primitive' &&
|
||||
typeof property.value === 'string' &&
|
||||
isValidIdentifier(property.value)
|
||||
((typeof property.value === 'string' &&
|
||||
isValidIdentifier(property.value)) ||
|
||||
typeof property.value === 'number')
|
||||
) {
|
||||
const nextValue: InstructionValue = {
|
||||
kind: 'PropertyStore',
|
||||
loc: value.loc,
|
||||
property: property.value,
|
||||
property: makePropertyLiteral(property.value),
|
||||
object: value.object,
|
||||
value: value.value,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
InstructionKind,
|
||||
JsxAttribute,
|
||||
makeInstructionId,
|
||||
makePropertyLiteral,
|
||||
ObjectProperty,
|
||||
Phi,
|
||||
Place,
|
||||
|
|
@ -446,7 +447,7 @@ function createSymbolProperty(
|
|||
value: {
|
||||
kind: 'PropertyLoad',
|
||||
object: {...symbolInstruction.lvalue},
|
||||
property: 'for',
|
||||
property: makePropertyLiteral('for'),
|
||||
loc: instr.value.loc,
|
||||
},
|
||||
loc: instr.loc,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
isUseContextHookType,
|
||||
makeBlockId,
|
||||
makeInstructionId,
|
||||
makePropertyLiteral,
|
||||
makeType,
|
||||
markInstructionIds,
|
||||
promoteTemporary,
|
||||
|
|
@ -195,7 +196,7 @@ function emitPropertyLoad(
|
|||
const loadProp: PropertyLoad = {
|
||||
kind: 'PropertyLoad',
|
||||
object,
|
||||
property,
|
||||
property: makePropertyLiteral(property),
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
const element: Place = createTemporaryPlace(env, GeneratedSource);
|
||||
|
|
|
|||
|
|
@ -1453,15 +1453,20 @@ function codegenDependency(
|
|||
if (dependency.path.length !== 0) {
|
||||
const hasOptional = dependency.path.some(path => path.optional);
|
||||
for (const path of dependency.path) {
|
||||
const property =
|
||||
typeof path.property === 'string'
|
||||
? t.identifier(path.property)
|
||||
: t.numericLiteral(path.property);
|
||||
const isComputed = typeof path.property !== 'string';
|
||||
if (hasOptional) {
|
||||
object = t.optionalMemberExpression(
|
||||
object,
|
||||
t.identifier(path.property),
|
||||
false,
|
||||
property,
|
||||
isComputed,
|
||||
path.optional,
|
||||
);
|
||||
} else {
|
||||
object = t.memberExpression(object, t.identifier(path.property));
|
||||
object = t.memberExpression(object, property, isComputed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1962,38 +1967,37 @@ function codegenInstructionValue(
|
|||
value = node;
|
||||
break;
|
||||
}
|
||||
case 'PropertyStore': {
|
||||
value = t.assignmentExpression(
|
||||
'=',
|
||||
t.memberExpression(
|
||||
codegenPlaceToExpression(cx, instrValue.object),
|
||||
t.identifier(instrValue.property),
|
||||
),
|
||||
codegenPlaceToExpression(cx, instrValue.value),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'PropertyLoad': {
|
||||
const object = codegenPlaceToExpression(cx, instrValue.object);
|
||||
case 'PropertyStore':
|
||||
case 'PropertyLoad':
|
||||
case 'PropertyDelete': {
|
||||
let memberExpr;
|
||||
/*
|
||||
* We currently only lower single chains of optional memberexpr.
|
||||
* (See BuildHIR.ts for more detail.)
|
||||
*/
|
||||
value = t.memberExpression(
|
||||
object,
|
||||
t.identifier(instrValue.property),
|
||||
undefined,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'PropertyDelete': {
|
||||
value = t.unaryExpression(
|
||||
'delete',
|
||||
t.memberExpression(
|
||||
if (typeof instrValue.property === 'string') {
|
||||
memberExpr = t.memberExpression(
|
||||
codegenPlaceToExpression(cx, instrValue.object),
|
||||
t.identifier(instrValue.property),
|
||||
),
|
||||
);
|
||||
);
|
||||
} else {
|
||||
memberExpr = t.memberExpression(
|
||||
codegenPlaceToExpression(cx, instrValue.object),
|
||||
t.numericLiteral(instrValue.property),
|
||||
true,
|
||||
);
|
||||
}
|
||||
if (instrValue.kind === 'PropertyStore') {
|
||||
value = t.assignmentExpression(
|
||||
'=',
|
||||
memberExpr,
|
||||
codegenPlaceToExpression(cx, instrValue.value),
|
||||
);
|
||||
} else if (instrValue.kind === 'PropertyLoad') {
|
||||
value = memberExpr;
|
||||
} else {
|
||||
value = t.unaryExpression('delete', memberExpr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'ComputedStore': {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
ReactiveStatement,
|
||||
ReactiveTerminalStatement,
|
||||
makeInstructionId,
|
||||
makePropertyLiteral,
|
||||
promoteTemporary,
|
||||
} from '../HIR';
|
||||
import {createTemporaryPlace} from '../HIR/HIRBuilder';
|
||||
|
|
@ -189,7 +190,7 @@ class Transform extends ReactiveFunctionTransform<State> {
|
|||
value: {
|
||||
kind: 'PropertyLoad',
|
||||
object: {...symbolTemp},
|
||||
property: 'for',
|
||||
property: makePropertyLiteral('for'),
|
||||
loc,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
IdentifierId,
|
||||
InstructionId,
|
||||
Place,
|
||||
PropertyLiteral,
|
||||
ReactiveBlock,
|
||||
ReactiveFunction,
|
||||
ReactiveInstruction,
|
||||
|
|
@ -64,13 +65,13 @@ type KindMap = Map<IdentifierId, CreateUpdate>;
|
|||
class Visitor extends ReactiveFunctionVisitor<CreateUpdate> {
|
||||
map: KindMap = new Map();
|
||||
aliases: DisjointSet<IdentifierId>;
|
||||
paths: Map<IdentifierId, Map<string, IdentifierId>>;
|
||||
paths: Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>;
|
||||
env: Environment;
|
||||
|
||||
constructor(
|
||||
env: Environment,
|
||||
aliases: DisjointSet<IdentifierId>,
|
||||
paths: Map<IdentifierId, Map<string, IdentifierId>>,
|
||||
paths: Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>,
|
||||
) {
|
||||
super();
|
||||
this.aliases = aliases;
|
||||
|
|
@ -218,9 +219,9 @@ export default function pruneInitializationDependencies(
|
|||
}
|
||||
|
||||
function update(
|
||||
map: Map<IdentifierId, Map<string, IdentifierId>>,
|
||||
map: Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>,
|
||||
key: IdentifierId,
|
||||
path: string,
|
||||
path: PropertyLiteral,
|
||||
value: IdentifierId,
|
||||
): void {
|
||||
const inner = map.get(key) ?? new Map();
|
||||
|
|
@ -230,7 +231,7 @@ function update(
|
|||
|
||||
class AliasVisitor extends ReactiveFunctionVisitor {
|
||||
scopeIdentifiers: DisjointSet<IdentifierId> = new DisjointSet<IdentifierId>();
|
||||
scopePaths: Map<IdentifierId, Map<string, IdentifierId>> = new Map();
|
||||
scopePaths: Map<IdentifierId, Map<PropertyLiteral, IdentifierId>> = new Map();
|
||||
|
||||
override visitInstruction(instr: ReactiveInstruction): void {
|
||||
if (
|
||||
|
|
@ -271,11 +272,14 @@ class AliasVisitor extends ReactiveFunctionVisitor {
|
|||
|
||||
function getAliases(
|
||||
fn: ReactiveFunction,
|
||||
): [DisjointSet<IdentifierId>, Map<IdentifierId, Map<string, IdentifierId>>] {
|
||||
): [
|
||||
DisjointSet<IdentifierId>,
|
||||
Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>,
|
||||
] {
|
||||
const visitor = new AliasVisitor();
|
||||
visitReactiveFunction(fn, visitor, null);
|
||||
let disjoint = visitor.scopeIdentifiers;
|
||||
let scopePaths = new Map<IdentifierId, Map<string, IdentifierId>>();
|
||||
let scopePaths = new Map<IdentifierId, Map<PropertyLiteral, IdentifierId>>();
|
||||
for (const [key, value] of visitor.scopePaths) {
|
||||
for (const [path, id] of value) {
|
||||
update(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
Identifier,
|
||||
IdentifierId,
|
||||
Instruction,
|
||||
makePropertyLiteral,
|
||||
makeType,
|
||||
PropType,
|
||||
Type,
|
||||
|
|
@ -335,7 +336,7 @@ function* generateInstructionTypes(
|
|||
kind: 'Property',
|
||||
objectType: value.value.identifier.type,
|
||||
objectName: getName(names, value.value.identifier.id),
|
||||
propertyName,
|
||||
propertyName: makePropertyLiteral(propertyName),
|
||||
});
|
||||
} else {
|
||||
break;
|
||||
|
|
@ -352,7 +353,7 @@ function* generateInstructionTypes(
|
|||
kind: 'Property',
|
||||
objectType: value.value.identifier.type,
|
||||
objectName: getName(names, value.value.identifier.id),
|
||||
propertyName: property.key.name,
|
||||
propertyName: makePropertyLiteral(property.key.name),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -453,10 +454,12 @@ class Unifier {
|
|||
return;
|
||||
}
|
||||
const objectType = this.get(tB.objectType);
|
||||
const propertyType = this.env.getPropertyType(
|
||||
objectType,
|
||||
tB.propertyName,
|
||||
);
|
||||
let propertyType;
|
||||
if (typeof tB.propertyName === 'number') {
|
||||
propertyType = null;
|
||||
} else {
|
||||
propertyType = this.env.getPropertyType(objectType, tB.propertyName);
|
||||
}
|
||||
if (propertyType !== null) {
|
||||
this.unify(tA, propertyType);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,7 +257,9 @@ export function validateHooksUsage(fn: HIRFunction): void {
|
|||
}
|
||||
case 'PropertyLoad': {
|
||||
const objectKind = getKindForPlace(instr.value.object);
|
||||
const isHookProperty = isHookName(instr.value.property);
|
||||
const isHookProperty =
|
||||
typeof instr.value.property === 'string' &&
|
||||
isHookName(instr.value.property);
|
||||
let kind: Kind;
|
||||
switch (objectKind) {
|
||||
case Kind.Error: {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,10 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
|
|||
}
|
||||
case 'PropertyLoad': {
|
||||
// Start conservative and disallow all capitalized method calls
|
||||
if (/^[A-Z]/.test(value.property)) {
|
||||
if (
|
||||
typeof value.property === 'string' &&
|
||||
/^[A-Z]/.test(value.property)
|
||||
) {
|
||||
capitalizedProperties.set(lvalue.identifier.id, value.property);
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -285,7 +285,7 @@ function validateNoRefAccessInRenderImpl(
|
|||
}
|
||||
case 'ComputedLoad':
|
||||
case 'PropertyLoad': {
|
||||
if (typeof instr.value.property !== 'string') {
|
||||
if (instr.value.kind === 'ComputedLoad') {
|
||||
validateNoDirectRefValueAccess(errors, instr.value.property, env);
|
||||
}
|
||||
const objType = env.get(instr.value.object.identifier.id);
|
||||
|
|
|
|||
|
|
@ -125,22 +125,21 @@ function Component(t0) {
|
|||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
const t2 = jsx[0];
|
||||
let t3;
|
||||
if ($[3] !== t1 || $[4] !== t2) {
|
||||
t3 = (
|
||||
let t2;
|
||||
if ($[3] !== jsx[0] || $[4] !== t1) {
|
||||
t2 = (
|
||||
<>
|
||||
{t1}
|
||||
{t2}
|
||||
{jsx[0]}
|
||||
</>
|
||||
);
|
||||
$[3] = t1;
|
||||
$[4] = t2;
|
||||
$[5] = t3;
|
||||
$[3] = jsx[0];
|
||||
$[4] = t1;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t3 = $[5];
|
||||
t2 = $[5];
|
||||
}
|
||||
return t3;
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
// We currently only recognize "hoistable" values (e.g. variable reads
|
||||
// and property loads from named variables) in the source depslist.
|
||||
// This makes validation logic simpler and follows the same constraints
|
||||
// from the eslint react-hooks-deps plugin.
|
||||
function Foo(props) {
|
||||
const x = makeArray(props);
|
||||
// react-hooks-deps lint would already fail here
|
||||
return useMemo(() => [x[0]], [x[0]]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{val: 1}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
11 | const x = makeArray(props);
|
||||
12 | // react-hooks-deps lint would already fail here
|
||||
> 13 | return useMemo(() => [x[0]], [x[0]]);
|
||||
| ^^^^ InvalidReact: Expected the dependency list to be an array of simple expressions (e.g. `x`, `x.y.z`, `x?.y?.z`) (13:13)
|
||||
14 | }
|
||||
15 |
|
||||
16 | export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {makeArray} from 'shared-runtime';
|
||||
|
||||
// We currently only recognize "hoistable" values (e.g. variable reads
|
||||
// and property loads from named variables) in the source depslist.
|
||||
// This makes validation logic simpler and follows the same constraints
|
||||
// from the eslint react-hooks-deps plugin.
|
||||
function Foo(props) {
|
||||
const x = makeArray(props);
|
||||
// react-hooks-deps lint would already fail here
|
||||
return useMemo(() => [x[0]], [x[0]]);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{val: 1}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { makeArray } from "shared-runtime";
|
||||
|
||||
// We currently only recognize "hoistable" values (e.g. variable reads
|
||||
// and property loads from named variables) in the source depslist.
|
||||
// This makes validation logic simpler and follows the same constraints
|
||||
// from the eslint react-hooks-deps plugin.
|
||||
function Foo(props) {
|
||||
const $ = _c(4);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
t0 = makeArray(props);
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
const x = t0;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[2] !== x[0]) {
|
||||
t2 = [x[0]];
|
||||
$[2] = x[0];
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
t1 = t2;
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ val: 1 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [{"val":1}]
|
||||
|
|
@ -32,28 +32,20 @@ export const FIXTURE_ENTRYPOINT = {
|
|||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.input) {
|
||||
x = [];
|
||||
const x = [];
|
||||
const y = x;
|
||||
y.push(props.input);
|
||||
$[0] = props.input;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
|
||||
const t0 = x[0];
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = [t0];
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
t0 = [x[0]];
|
||||
$[0] = props.input;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[1];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
|
|
|||
|
|
@ -35,32 +35,24 @@ export const FIXTURE_ENTRYPOINT = {
|
|||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
let x;
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.input) {
|
||||
x = [];
|
||||
const x = [];
|
||||
const f = (arg) => {
|
||||
const y = x;
|
||||
y.push(arg);
|
||||
};
|
||||
|
||||
f(props.input);
|
||||
$[0] = props.input;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
|
||||
const t0 = x[0];
|
||||
let t1;
|
||||
if ($[2] !== t0) {
|
||||
t1 = [t0];
|
||||
$[2] = t0;
|
||||
$[3] = t1;
|
||||
t0 = [x[0]];
|
||||
$[0] = props.input;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
t0 = $[1];
|
||||
}
|
||||
return t1;
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
|
|
|||
|
|
@ -94,69 +94,67 @@ export function Component(t0) {
|
|||
} else {
|
||||
t6 = $[8];
|
||||
}
|
||||
const t7 = items_0[0];
|
||||
let t8;
|
||||
if ($[9] !== t6 || $[10] !== t7) {
|
||||
t8 = <SharedRuntime.ValidateMemoization inputs={t6} output={t7} />;
|
||||
$[9] = t6;
|
||||
$[10] = t7;
|
||||
$[11] = t8;
|
||||
let t7;
|
||||
if ($[9] !== items_0[0] || $[10] !== t6) {
|
||||
t7 = <SharedRuntime.ValidateMemoization inputs={t6} output={items_0[0]} />;
|
||||
$[9] = items_0[0];
|
||||
$[10] = t6;
|
||||
$[11] = t7;
|
||||
} else {
|
||||
t8 = $[11];
|
||||
t7 = $[11];
|
||||
}
|
||||
let t8;
|
||||
if ($[12] !== b) {
|
||||
t8 = [b];
|
||||
$[12] = b;
|
||||
$[13] = t8;
|
||||
} else {
|
||||
t8 = $[13];
|
||||
}
|
||||
let t9;
|
||||
if ($[12] !== b) {
|
||||
t9 = [b];
|
||||
$[12] = b;
|
||||
$[13] = t9;
|
||||
if ($[14] !== items_0[1] || $[15] !== t8) {
|
||||
t9 = <SharedRuntime.ValidateMemoization inputs={t8} output={items_0[1]} />;
|
||||
$[14] = items_0[1];
|
||||
$[15] = t8;
|
||||
$[16] = t9;
|
||||
} else {
|
||||
t9 = $[13];
|
||||
t9 = $[16];
|
||||
}
|
||||
const t10 = items_0[1];
|
||||
let t11;
|
||||
if ($[14] !== t10 || $[15] !== t9) {
|
||||
t11 = <SharedRuntime.ValidateMemoization inputs={t9} output={t10} />;
|
||||
$[14] = t10;
|
||||
$[15] = t9;
|
||||
$[16] = t11;
|
||||
} else {
|
||||
t11 = $[16];
|
||||
}
|
||||
let t12;
|
||||
let t10;
|
||||
if ($[17] !== a || $[18] !== b) {
|
||||
t12 = [a, b];
|
||||
t10 = [a, b];
|
||||
$[17] = a;
|
||||
$[18] = b;
|
||||
$[19] = t12;
|
||||
$[19] = t10;
|
||||
} else {
|
||||
t12 = $[19];
|
||||
t10 = $[19];
|
||||
}
|
||||
let t13;
|
||||
if ($[20] !== items_0 || $[21] !== t12) {
|
||||
t13 = <SharedRuntime.ValidateMemoization inputs={t12} output={items_0} />;
|
||||
let t11;
|
||||
if ($[20] !== items_0 || $[21] !== t10) {
|
||||
t11 = <SharedRuntime.ValidateMemoization inputs={t10} output={items_0} />;
|
||||
$[20] = items_0;
|
||||
$[21] = t12;
|
||||
$[22] = t13;
|
||||
$[21] = t10;
|
||||
$[22] = t11;
|
||||
} else {
|
||||
t13 = $[22];
|
||||
t11 = $[22];
|
||||
}
|
||||
let t14;
|
||||
if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) {
|
||||
t14 = (
|
||||
let t12;
|
||||
if ($[23] !== t11 || $[24] !== t7 || $[25] !== t9) {
|
||||
t12 = (
|
||||
<>
|
||||
{t8}
|
||||
{t7}
|
||||
{t9}
|
||||
{t11}
|
||||
{t13}
|
||||
</>
|
||||
);
|
||||
$[23] = t11;
|
||||
$[24] = t13;
|
||||
$[25] = t8;
|
||||
$[26] = t14;
|
||||
$[24] = t7;
|
||||
$[25] = t9;
|
||||
$[26] = t12;
|
||||
} else {
|
||||
t14 = $[26];
|
||||
t12 = $[26];
|
||||
}
|
||||
return t14;
|
||||
return t12;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
|
|
|||
|
|
@ -94,69 +94,67 @@ export function Component(t0) {
|
|||
} else {
|
||||
t6 = $[8];
|
||||
}
|
||||
const t7 = items_0[0];
|
||||
let t8;
|
||||
if ($[9] !== t6 || $[10] !== t7) {
|
||||
t8 = <ValidateMemoization inputs={t6} output={t7} />;
|
||||
$[9] = t6;
|
||||
$[10] = t7;
|
||||
$[11] = t8;
|
||||
let t7;
|
||||
if ($[9] !== items_0[0] || $[10] !== t6) {
|
||||
t7 = <ValidateMemoization inputs={t6} output={items_0[0]} />;
|
||||
$[9] = items_0[0];
|
||||
$[10] = t6;
|
||||
$[11] = t7;
|
||||
} else {
|
||||
t8 = $[11];
|
||||
t7 = $[11];
|
||||
}
|
||||
let t8;
|
||||
if ($[12] !== b) {
|
||||
t8 = [b];
|
||||
$[12] = b;
|
||||
$[13] = t8;
|
||||
} else {
|
||||
t8 = $[13];
|
||||
}
|
||||
let t9;
|
||||
if ($[12] !== b) {
|
||||
t9 = [b];
|
||||
$[12] = b;
|
||||
$[13] = t9;
|
||||
if ($[14] !== items_0[1] || $[15] !== t8) {
|
||||
t9 = <ValidateMemoization inputs={t8} output={items_0[1]} />;
|
||||
$[14] = items_0[1];
|
||||
$[15] = t8;
|
||||
$[16] = t9;
|
||||
} else {
|
||||
t9 = $[13];
|
||||
t9 = $[16];
|
||||
}
|
||||
const t10 = items_0[1];
|
||||
let t11;
|
||||
if ($[14] !== t10 || $[15] !== t9) {
|
||||
t11 = <ValidateMemoization inputs={t9} output={t10} />;
|
||||
$[14] = t10;
|
||||
$[15] = t9;
|
||||
$[16] = t11;
|
||||
} else {
|
||||
t11 = $[16];
|
||||
}
|
||||
let t12;
|
||||
let t10;
|
||||
if ($[17] !== a || $[18] !== b) {
|
||||
t12 = [a, b];
|
||||
t10 = [a, b];
|
||||
$[17] = a;
|
||||
$[18] = b;
|
||||
$[19] = t12;
|
||||
$[19] = t10;
|
||||
} else {
|
||||
t12 = $[19];
|
||||
t10 = $[19];
|
||||
}
|
||||
let t13;
|
||||
if ($[20] !== items_0 || $[21] !== t12) {
|
||||
t13 = <ValidateMemoization inputs={t12} output={items_0} />;
|
||||
let t11;
|
||||
if ($[20] !== items_0 || $[21] !== t10) {
|
||||
t11 = <ValidateMemoization inputs={t10} output={items_0} />;
|
||||
$[20] = items_0;
|
||||
$[21] = t12;
|
||||
$[22] = t13;
|
||||
$[21] = t10;
|
||||
$[22] = t11;
|
||||
} else {
|
||||
t13 = $[22];
|
||||
t11 = $[22];
|
||||
}
|
||||
let t14;
|
||||
if ($[23] !== t11 || $[24] !== t13 || $[25] !== t8) {
|
||||
t14 = (
|
||||
let t12;
|
||||
if ($[23] !== t11 || $[24] !== t7 || $[25] !== t9) {
|
||||
t12 = (
|
||||
<>
|
||||
{t8}
|
||||
{t7}
|
||||
{t9}
|
||||
{t11}
|
||||
{t13}
|
||||
</>
|
||||
);
|
||||
$[23] = t11;
|
||||
$[24] = t13;
|
||||
$[25] = t8;
|
||||
$[26] = t14;
|
||||
$[24] = t7;
|
||||
$[25] = t9;
|
||||
$[26] = t12;
|
||||
} else {
|
||||
t14 = $[26];
|
||||
t12 = $[26];
|
||||
}
|
||||
return t14;
|
||||
return t12;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user