Compare commits

...

8 Commits

131 changed files with 1511 additions and 2479 deletions

View File

@@ -57,7 +57,6 @@ import {
mergeReactiveScopesThatInvalidateTogether,
promoteUsedTemporaries,
propagateEarlyReturns,
propagateScopeDependencies,
pruneHoistedContexts,
pruneNonEscapingScopes,
pruneNonReactiveDependencies,
@@ -343,14 +342,12 @@ function* runWithEnvironment(
});
assertTerminalSuccessorsExist(hir);
assertTerminalPredsExist(hir);
if (env.config.enablePropagateDepsInHIR) {
propagateScopeDependenciesHIR(hir);
yield log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
value: hir,
});
}
propagateScopeDependenciesHIR(hir);
yield log({
kind: 'hir',
name: 'PropagateScopeDependenciesHIR',
value: hir,
});
if (env.config.inlineJsxTransform) {
inlineJsxTransform(hir, env.config.inlineJsxTransform);
@@ -378,15 +375,6 @@ function* runWithEnvironment(
});
assertScopeInstructionsWithinScopes(reactiveFunction);
if (!env.config.enablePropagateDepsInHIR) {
propagateScopeDependencies(reactiveFunction);
yield log({
kind: 'reactive',
name: 'PropagateScopeDependencies',
value: reactiveFunction,
});
}
pruneNonEscapingScopes(reactiveFunction);
yield log({
kind: 'reactive',

View File

@@ -7,7 +7,6 @@
import {NodePath, Scope} from '@babel/traverse';
import * as t from '@babel/types';
import {Expression} from '@babel/types';
import invariant from 'invariant';
import {
CompilerError,
@@ -3186,7 +3185,13 @@ function lowerJsxMemberExpression(
loc: object.node.loc ?? null,
suggestions: null,
});
objectPlace = lowerIdentifier(builder, object);
const kind = getLoadKind(builder, object);
objectPlace = lowerValueToTemporary(builder, {
kind: kind,
place: lowerIdentifier(builder, object),
loc: exprPath.node.loc ?? GeneratedSource,
});
}
const property = exprPath.get('property').node.name;
return lowerValueToTemporary(builder, {
@@ -3359,7 +3364,7 @@ function lowerFunction(
>,
): LoweredFunction | null {
const componentScope: Scope = builder.parentFunction.scope;
const captured = gatherCapturedDeps(builder, expr, componentScope);
const capturedContext = gatherCapturedContext(expr, componentScope);
/*
* TODO(gsn): In the future, we could only pass in the context identifiers
@@ -3373,7 +3378,7 @@ function lowerFunction(
expr,
builder.environment,
builder.bindings,
[...builder.context, ...captured.identifiers],
[...builder.context, ...capturedContext],
builder.parentFunction,
);
let loweredFunc: HIRFunction;
@@ -3386,7 +3391,6 @@ function lowerFunction(
loweredFunc = lowering.unwrap();
return {
func: loweredFunc,
dependencies: captured.refs,
};
}
@@ -4060,14 +4064,6 @@ function lowerAssignment(
}
}
function isValidDependency(path: NodePath<t.MemberExpression>): boolean {
const parent: NodePath<t.Node> = path.parentPath;
return (
!path.node.computed &&
!(parent.isCallExpression() && parent.get('callee') === path)
);
}
function captureScopes({from, to}: {from: Scope; to: Scope}): Set<Scope> {
let scopes: Set<Scope> = new Set();
while (from) {
@@ -4082,8 +4078,7 @@ function captureScopes({from, to}: {from: Scope; to: Scope}): Set<Scope> {
return scopes;
}
function gatherCapturedDeps(
builder: HIRBuilder,
function gatherCapturedContext(
fn: NodePath<
| t.FunctionExpression
| t.ArrowFunctionExpression
@@ -4091,10 +4086,8 @@ function gatherCapturedDeps(
| t.ObjectMethod
>,
componentScope: Scope,
): {identifiers: Array<t.Identifier>; refs: Array<Place>} {
const capturedIds: Map<t.Identifier, number> = new Map();
const capturedRefs: Set<Place> = new Set();
const seenPaths: Set<string> = new Set();
): Array<t.Identifier> {
const capturedIds = new Set<t.Identifier>();
/*
* Capture all the scopes from the parent of this function up to and including
@@ -4105,33 +4098,11 @@ function gatherCapturedDeps(
to: componentScope,
});
function addCapturedId(bindingIdentifier: t.Identifier): number {
if (!capturedIds.has(bindingIdentifier)) {
const index = capturedIds.size;
capturedIds.set(bindingIdentifier, index);
return index;
} else {
return capturedIds.get(bindingIdentifier)!;
}
}
function handleMaybeDependency(
path:
| NodePath<t.MemberExpression>
| NodePath<t.Identifier>
| NodePath<t.JSXOpeningElement>,
path: NodePath<t.Identifier> | NodePath<t.JSXOpeningElement>,
): void {
// Base context variable to depend on
let baseIdentifier: NodePath<t.Identifier> | NodePath<t.JSXIdentifier>;
/*
* Base expression to depend on, which (for now) may contain non side-effectful
* member expressions
*/
let dependency:
| NodePath<t.MemberExpression>
| NodePath<t.JSXMemberExpression>
| NodePath<t.Identifier>
| NodePath<t.JSXIdentifier>;
if (path.isJSXOpeningElement()) {
const name = path.get('name');
if (!(name.isJSXMemberExpression() || name.isJSXIdentifier())) {
@@ -4147,115 +4118,20 @@ function gatherCapturedDeps(
'Invalid logic in gatherCapturedDeps',
);
baseIdentifier = current;
/*
* Get the expression to depend on, which may involve PropertyLoads
* for member expressions
*/
let currentDep:
| NodePath<t.JSXMemberExpression>
| NodePath<t.Identifier>
| NodePath<t.JSXIdentifier> = baseIdentifier;
while (true) {
const nextDep: null | NodePath<t.Node> = currentDep.parentPath;
if (nextDep && nextDep.isJSXMemberExpression()) {
currentDep = nextDep;
} else {
break;
}
}
dependency = currentDep;
} else if (path.isMemberExpression()) {
// Calculate baseIdentifier
let currentId: NodePath<Expression> = path;
while (currentId.isMemberExpression()) {
currentId = currentId.get('object');
}
if (!currentId.isIdentifier()) {
return;
}
baseIdentifier = currentId;
/*
* Get the expression to depend on, which may involve PropertyLoads
* for member expressions
*/
let currentDep:
| NodePath<t.MemberExpression>
| NodePath<t.Identifier>
| NodePath<t.JSXIdentifier> = baseIdentifier;
while (true) {
const nextDep: null | NodePath<t.Node> = currentDep.parentPath;
if (
nextDep &&
nextDep.isMemberExpression() &&
isValidDependency(nextDep)
) {
currentDep = nextDep;
} else {
break;
}
}
dependency = currentDep;
} else {
baseIdentifier = path;
dependency = path;
}
/*
* Skip dependency path, as we already tried to recursively add it (+ all subexpressions)
* as a dependency.
*/
dependency.skip();
path.skip();
// Add the base identifier binding as a dependency.
const binding = baseIdentifier.scope.getBinding(baseIdentifier.node.name);
if (binding === undefined || !pureScopes.has(binding.scope)) {
return;
}
const idKey = String(addCapturedId(binding.identifier));
// Add the expression (potentially a memberexpr path) as a dependency.
let exprKey = idKey;
if (dependency.isMemberExpression()) {
let pathTokens = [];
let current: NodePath<Expression> = dependency;
while (current.isMemberExpression()) {
const property = current.get('property') as NodePath<t.Identifier>;
pathTokens.push(property.node.name);
current = current.get('object');
}
exprKey += '.' + pathTokens.reverse().join('.');
} else if (dependency.isJSXMemberExpression()) {
let pathTokens = [];
let current: NodePath<t.JSXMemberExpression | t.JSXIdentifier> =
dependency;
while (current.isJSXMemberExpression()) {
const property = current.get('property');
pathTokens.push(property.node.name);
current = current.get('object');
}
}
if (!seenPaths.has(exprKey)) {
let loweredDep: Place;
if (dependency.isJSXIdentifier()) {
loweredDep = lowerValueToTemporary(builder, {
kind: 'LoadLocal',
place: lowerIdentifier(builder, dependency),
loc: path.node.loc ?? GeneratedSource,
});
} else if (dependency.isJSXMemberExpression()) {
loweredDep = lowerJsxMemberExpression(builder, dependency);
} else {
loweredDep = lowerExpressionToTemporary(builder, dependency);
}
capturedRefs.add(loweredDep);
seenPaths.add(exprKey);
if (binding !== undefined && pureScopes.has(binding.scope)) {
capturedIds.add(binding.identifier);
}
}
@@ -4286,13 +4162,13 @@ function gatherCapturedDeps(
return;
} else if (path.isJSXElement()) {
handleMaybeDependency(path.get('openingElement'));
} else if (path.isMemberExpression() || path.isIdentifier()) {
} else if (path.isIdentifier()) {
handleMaybeDependency(path);
}
},
});
return {identifiers: [...capturedIds.keys()], refs: [...capturedRefs]};
return [...capturedIds.keys()];
}
function notNull<T>(value: T | null): value is T {

View File

@@ -7,7 +7,6 @@ import {
Set_union,
getOrInsertDefault,
} from '../Utils/utils';
import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies';
import {
BasicBlock,
BlockId,
@@ -21,7 +20,6 @@ import {
ReactiveScopeDependency,
ScopeId,
} from './HIR';
import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR';
/**
* Helper function for `PropagateScopeDependencies`. Uses control flow graph
@@ -90,11 +88,6 @@ export function collectHoistablePropertyLoads(
): ReadonlyMap<BlockId, BlockInfo> {
const registry = new PropertyPathRegistry();
const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn);
const actuallyEvaluatedTemporaries = new Map(
[...temporaries].filter(([id]) => !functionExpressionLoads.has(id)),
);
/**
* Due to current limitations of mutable range inference, there are edge cases in
* which we infer known-immutable values (e.g. props or hook params) to have a
@@ -112,7 +105,7 @@ export function collectHoistablePropertyLoads(
}
}
const nodes = collectNonNullsInBlocks(fn, {
temporaries: actuallyEvaluatedTemporaries,
temporaries,
knownImmutableIdentifiers,
hoistableFromOptionals,
registry,
@@ -348,19 +341,15 @@ function collectNonNullsInBlocks(
assumedNonNullObjects.add(maybeNonNull);
}
if (
instr.value.kind === 'FunctionExpression' &&
(instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod') &&
!fn.env.config.enableTreatFunctionDepsAsConditional
) {
const innerFn = instr.value.loweredFunc;
const innerTemporaries = collectTemporariesSidemap(
innerFn.func,
new Set(),
);
const innerOptionals = collectOptionalChainSidemap(innerFn.func);
const innerHoistableMap = collectHoistablePropertyLoads(
innerFn.func,
innerTemporaries,
innerOptionals.hoistableObjects,
context.temporaries,
context.hoistableFromOptionals,
context.nestedFnImmutableContext ??
new Set(
innerFn.func.context
@@ -582,27 +571,3 @@ function reduceMaybeOptionalChains(
}
} while (changed);
}
function collectFunctionExpressionFakeLoads(
fn: HIRFunction,
): Set<IdentifierId> {
const sources = new Map<IdentifierId, IdentifierId>();
const functionExpressionReferences = new Set<IdentifierId>();
for (const [_, block] of fn.body.blocks) {
for (const {lvalue, value} of block.instructions) {
if (value.kind === 'FunctionExpression') {
for (const reference of value.loweredFunc.dependencies) {
let curr: IdentifierId | undefined = reference.identifier.id;
while (curr != null) {
functionExpressionReferences.add(curr);
curr = sources.get(curr);
}
}
} else if (value.kind === 'PropertyLoad') {
sources.set(lvalue.identifier.id, value.object.identifier.id);
}
}
}
return functionExpressionReferences;
}

View File

@@ -1,4 +1,5 @@
import {CompilerError} from '..';
import {getOrInsertDefault} from '../Utils/utils';
import {assertNonNull} from './CollectHoistablePropertyLoads';
import {
BlockId,
@@ -22,25 +23,14 @@ export function collectOptionalChainSidemap(
fn: HIRFunction,
): OptionalChainSidemap {
const context: OptionalTraversalContext = {
currFn: fn,
blocks: fn.body.blocks,
seenOptionals: new Set(),
processedInstrsInOptional: new Set(),
processedInstrsInOptional: new Map(),
temporariesReadInOptional: new Map(),
hoistableObjects: new Map(),
};
for (const [_, block] of fn.body.blocks) {
if (
block.terminal.kind === 'optional' &&
!context.seenOptionals.has(block.id)
) {
traverseOptionalBlock(
block as TBasicBlock<OptionalTerminal>,
context,
null,
);
}
}
traverseFunction(fn, context);
return {
temporariesReadInOptional: context.temporariesReadInOptional,
processedInstrsInOptional: context.processedInstrsInOptional,
@@ -97,7 +87,10 @@ export type OptionalChainSidemap = {
* $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4!
* ```
*/
processedInstrsInOptional: ReadonlySet<InstructionId>;
processedInstrsInOptional: ReadonlyMap<
HIRFunction,
ReadonlySet<InstructionId>
>;
/**
* Records optional chains for which we can safely evaluate non-optional
* PropertyLoads. e.g. given `a?.b.c`, we can evaluate any load from `a?.b` at
@@ -115,16 +108,47 @@ export type OptionalChainSidemap = {
};
type OptionalTraversalContext = {
currFn: HIRFunction;
blocks: ReadonlyMap<BlockId, BasicBlock>;
// Track optional blocks to avoid outer calls into nested optionals
seenOptionals: Set<BlockId>;
processedInstrsInOptional: Set<InstructionId>;
processedInstrsInOptional: Map<HIRFunction, Set<InstructionId>>;
temporariesReadInOptional: Map<IdentifierId, ReactiveScopeDependency>;
hoistableObjects: Map<BlockId, ReactiveScopeDependency>;
};
function traverseFunction(
fn: HIRFunction,
context: OptionalTraversalContext,
): void {
for (const [_, block] of fn.body.blocks) {
for (const instr of block.instructions) {
if (
instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod'
) {
traverseFunction(instr.value.loweredFunc.func, {
...context,
currFn: instr.value.loweredFunc.func,
blocks: instr.value.loweredFunc.func.body.blocks,
seenOptionals: new Set(),
});
}
}
if (
block.terminal.kind === 'optional' &&
!context.seenOptionals.has(block.id)
) {
traverseOptionalBlock(
block as TBasicBlock<OptionalTerminal>,
context,
null,
);
}
}
}
/**
* Match the consequent and alternate blocks of an optional.
* @returns propertyload computed by the consequent block, or null if the
@@ -369,10 +393,13 @@ function traverseOptionalBlock(
},
],
};
context.processedInstrsInOptional.add(
matchConsequentResult.storeLocalInstrId,
const processedInstrsInOptional = getOrInsertDefault(
context.processedInstrsInOptional,
context.currFn,
new Set(),
);
context.processedInstrsInOptional.add(test.id);
processedInstrsInOptional.add(matchConsequentResult.storeLocalInstrId);
processedInstrsInOptional.add(test.id);
context.temporariesReadInOptional.set(
matchConsequentResult.consequentId,
load,

View File

@@ -230,8 +230,6 @@ const EnvironmentConfigSchema = z.object({
*/
enableUseTypeAnnotations: z.boolean().default(false),
enablePropagateDepsInHIR: z.boolean().default(false),
/**
* Enables inference of optional dependency chains. Without this flag
* a property chain such as `props?.items?.foo` will infer as a dep on

View File

@@ -722,7 +722,6 @@ export type ObjectProperty = {
};
export type LoweredFunction = {
dependencies: Array<Place>;
func: HIRFunction;
};

View File

@@ -538,9 +538,6 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
.split('\n')
.map(line => ` ${line}`)
.join('\n');
const deps = instrValue.loweredFunc.dependencies
.map(dep => printPlace(dep))
.join(',');
const context = instrValue.loweredFunc.func.context
.map(dep => printPlace(dep))
.join(',');
@@ -557,7 +554,7 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
})
.join(', ') ?? '';
const type = printType(instrValue.loweredFunc.func.returnType).trim();
value = `${kind} ${name} @deps[${deps}] @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`;
value = `${kind} ${name} @context[${context}] @effects[${effects}]${type !== '' ? ` return${type}` : ''}:\n${fn}`;
break;
}
case 'TaggedTemplateExpression': {

View File

@@ -16,6 +16,8 @@ import {
DeclarationId,
areEqualPaths,
IdentifierId,
BasicBlock,
BlockId,
} from './HIR';
import {
collectHoistablePropertyLoads,
@@ -37,7 +39,11 @@ import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies';
export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
const usedOutsideDeclaringScope =
findTemporariesUsedOutsideDeclaringScope(fn);
const temporaries = collectTemporariesSidemap(fn, usedOutsideDeclaringScope);
const temporaries = collectTemporariesSidemap(
fn,
usedOutsideDeclaringScope,
new Map(),
);
const {
temporariesReadInOptional,
processedInstrsInOptional,
@@ -214,8 +220,9 @@ function findTemporariesUsedOutsideDeclaringScope(
export function collectTemporariesSidemap(
fn: HIRFunction,
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
temporaries: Map<IdentifierId, ReactiveScopeDependency>,
isInnerFn: boolean = false,
): ReadonlyMap<IdentifierId, ReactiveScopeDependency> {
const temporaries = new Map<IdentifierId, ReactiveScopeDependency>();
for (const [_, block] of fn.body.blocks) {
for (const instr of block.instructions) {
const {value, lvalue} = instr;
@@ -224,23 +231,54 @@ export function collectTemporariesSidemap(
);
if (value.kind === 'PropertyLoad' && !usedOutside) {
const property = getProperty(
value.object,
value.property,
false,
temporaries,
);
temporaries.set(lvalue.identifier.id, property);
if (isInnerFn) {
const source = temporaries.get(value.object.identifier.id);
if (source) {
// only add inner function loads if their source is valid
const property = getProperty(
value.object,
value.property,
false,
temporaries,
);
temporaries.set(lvalue.identifier.id, property);
}
} else {
const property = getProperty(
value.object,
value.property,
false,
temporaries,
);
temporaries.set(lvalue.identifier.id, property);
}
} else if (
value.kind === 'LoadLocal' &&
lvalue.identifier.name == null &&
value.place.identifier.name !== null &&
!usedOutside
) {
temporaries.set(lvalue.identifier.id, {
identifier: value.place.identifier,
path: [],
});
if (
!isInnerFn ||
fn.context.some(
context => context.identifier.id === value.place.identifier.id,
)
) {
temporaries.set(lvalue.identifier.id, {
identifier: value.place.identifier,
path: [],
});
}
} else if (
value.kind === 'FunctionExpression' ||
value.kind === 'ObjectMethod'
) {
collectTemporariesSidemap(
value.loweredFunc.func,
usedOutsideDeclaringScope,
temporaries,
true,
);
}
}
}
@@ -310,6 +348,8 @@ class Context {
#temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
#temporariesUsedOutsideScope: ReadonlySet<DeclarationId>;
innerFnContext: HIRFunction | null = null;
constructor(
temporariesUsedOutsideScope: ReadonlySet<DeclarationId>,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
@@ -374,6 +414,14 @@ class Context {
// Checks if identifier is a valid dependency in the current scope
#checkValidDependency(maybeDependency: ReactiveScopeDependency): boolean {
if (
this.innerFnContext != null &&
!this.innerFnContext.context.some(
context => context.identifier.id === maybeDependency.identifier.id,
)
) {
return false;
}
// ref.current access is not a valid dep
if (
isUseRefType(maybeDependency.identifier) &&
@@ -575,7 +623,10 @@ function collectDependencies(
fn: HIRFunction,
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
processedInstrsInOptional: ReadonlySet<InstructionId>,
processedInstrsInOptional: ReadonlyMap<
HIRFunction,
ReadonlySet<InstructionId>
>,
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
const context = new Context(usedOutsideDeclaringScope, temporaries);
@@ -594,8 +645,13 @@ function collectDependencies(
}
const scopeTraversal = new ScopeBlockTraversal();
// TODO: make this less hacky
const st: Array<[HIRFunction, BlockId, BasicBlock]> = [...fn.body.blocks].map(
([id, block]) => [fn, id, block],
);
for (const [blockId, block] of fn.body.blocks) {
while (st.length != 0) {
const [currFn, blockId, block] = st.shift()!;
scopeTraversal.recordScopes(block);
const scopeBlockInfo = scopeTraversal.blockInfos.get(blockId);
if (scopeBlockInfo?.kind === 'begin') {
@@ -604,6 +660,9 @@ function collectDependencies(
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned);
}
// TODO: make this less hacky
context.innerFnContext = currFn === fn ? null : currFn;
// Record referenced optional chains in phis
for (const phi of block.phis) {
for (const operand of phi.operands) {
@@ -614,16 +673,37 @@ function collectDependencies(
}
}
for (const instr of block.instructions) {
if (!processedInstrsInOptional.has(instr.id)) {
if (
instr.value.kind === 'FunctionExpression' ||
instr.value.kind === 'ObjectMethod'
) {
/**
* Push the outermost nested fn context, as these are guaranteed to reference
* component / hook identifiers (i.e. eligible dependencies)
*/
const innerFn = instr.value.loweredFunc.func;
const outermostNestedFnContext = currFn === fn ? innerFn : currFn;
const x: Array<[HIRFunction, BlockId, BasicBlock]> = [
...innerFn.body.blocks.entries(),
].map(([id, block]) => [outermostNestedFnContext, id, block]);
st.unshift(...x);
context.declare(instr.lvalue.identifier, {
id: instr.id,
scope: context.currentScope,
});
} else if (!processedInstrsInOptional.get(currFn)?.has(instr.id)) {
// instruction ids are function-relative
handleInstruction(instr, context);
}
}
if (!processedInstrsInOptional.has(block.terminal.id)) {
if (!processedInstrsInOptional.get(currFn)?.has(block.terminal.id)) {
for (const place of eachTerminalOperand(block.terminal)) {
context.visitOperand(place);
}
}
}
return context.deps;
}

View File

@@ -193,7 +193,7 @@ export function* eachInstructionValueOperand(
}
case 'ObjectMethod':
case 'FunctionExpression': {
yield* instrValue.loweredFunc.dependencies;
yield* instrValue.loweredFunc.func.context;
break;
}
case 'TaggedTemplateExpression': {
@@ -517,8 +517,9 @@ export function mapInstructionValueOperands(
}
case 'ObjectMethod':
case 'FunctionExpression': {
instrValue.loweredFunc.dependencies =
instrValue.loweredFunc.dependencies.map(d => fn(d));
instrValue.loweredFunc.func.context =
instrValue.loweredFunc.func.context.map(d => fn(d));
break;
}
case 'TaggedTemplateExpression': {

View File

@@ -10,7 +10,6 @@ import {
Effect,
HIRFunction,
Identifier,
IdentifierName,
LoweredFunction,
Place,
isRefOrRefValue,
@@ -41,20 +40,6 @@ export class IdentifierState {
return identifier;
}
declareProperty(lvalue: Place, object: Place, property: string): void {
const objectDependency = this.properties.get(object.identifier);
let nextDependency: Dependency;
if (objectDependency === undefined) {
nextDependency = {identifier: object.identifier, path: [property]};
} else {
nextDependency = {
identifier: objectDependency.identifier,
path: [...objectDependency.path, property],
};
}
this.properties.set(lvalue.identifier, nextDependency);
}
declareTemporary(lvalue: Place, value: Place): void {
const resolved: Dependency = this.properties.get(value.identifier) ?? {
identifier: value.identifier,
@@ -73,25 +58,10 @@ export default function analyseFunctions(func: HIRFunction): void {
case 'ObjectMethod':
case 'FunctionExpression': {
lower(instr.value.loweredFunc.func);
infer(instr.value.loweredFunc, state, func.context);
break;
}
case 'PropertyLoad': {
state.declareProperty(
instr.lvalue,
instr.value.object,
instr.value.property,
);
break;
}
case 'ComputedLoad': {
/*
* The path is set to an empty string as the path doesn't really
* matter for a computed load.
*/
state.declareProperty(instr.lvalue, instr.value.object, '');
infer(instr.value.loweredFunc, func.context);
break;
}
case 'LoadLocal':
case 'LoadContext': {
if (instr.lvalue.identifier.name === null) {
@@ -115,11 +85,8 @@ function lower(func: HIRFunction): void {
logHIRFunction('AnalyseFunction (inner)', func);
}
function infer(
loweredFunc: LoweredFunction,
state: IdentifierState,
context: Array<Place>,
): void {
// infer loweredFunc (inner) with outer function context
function infer(loweredFunc: LoweredFunction, context: Array<Place>): void {
const mutations = new Map<string, Effect>();
for (const operand of loweredFunc.func.context) {
if (
@@ -130,15 +97,13 @@ function infer(
}
}
for (const dep of loweredFunc.dependencies) {
let name: IdentifierName | null = null;
if (state.properties.has(dep.identifier)) {
const receiver = state.properties.get(dep.identifier)!;
name = receiver.identifier.name;
} else {
name = dep.identifier.name;
}
for (const dep of loweredFunc.func.context) {
CompilerError.invariant(dep.identifier.name !== null, {
reason: 'context refs should always have a name',
description: null,
loc: dep.loc,
suggestions: null,
});
if (isRefOrRefValue(dep.identifier)) {
/*
@@ -149,8 +114,8 @@ function infer(
* render
*/
dep.effect = Effect.Capture;
} else if (name !== null) {
const effect = mutations.get(name.value);
} else {
const effect = mutations.get(dep.identifier.name.value);
if (effect !== undefined) {
dep.effect = effect === Effect.Unknown ? Effect.Capture : effect;
}
@@ -176,7 +141,6 @@ function infer(
const effect = mutations.get(place.identifier.name.value);
if (effect !== undefined) {
place.effect = effect === Effect.Unknown ? Effect.Capture : effect;
loweredFunc.dependencies.push(place);
}
}

View File

@@ -61,22 +61,6 @@ export function inferMutableContextVariables(fn: HIRFunction): void {
for (const [, block] of fn.body.blocks) {
for (const instr of block.instructions) {
switch (instr.value.kind) {
case 'PropertyLoad': {
state.declareProperty(
instr.lvalue,
instr.value.object,
instr.value.property,
);
break;
}
case 'ComputedLoad': {
/*
* The path is set to an empty string as the path doesn't really
* matter for a computed load.
*/
state.declareProperty(instr.lvalue, instr.value.object, '');
break;
}
case 'LoadLocal':
case 'LoadContext': {
if (instr.lvalue.identifier.name === null) {

View File

@@ -58,6 +58,14 @@ export function deadCodeElimination(fn: HIRFunction): void {
}
}
}
/**
* Constant propagation and DCE may have deleted or rewritten instructions
* that reference context variables.
*/
retainWhere(fn.context, contextVar =>
state.isIdOrNameUsed(contextVar.identifier),
);
}
class State {

View File

@@ -270,7 +270,6 @@ function emitSelectorFn(env: Environment, keys: Array<string>): Instruction {
name: null,
loweredFunc: {
func: fn,
dependencies: [],
},
type: 'ArrowFunctionExpression',
loc: GeneratedSource,

View File

@@ -24,7 +24,6 @@ export function outlineFunctions(
}
if (
value.kind === 'FunctionExpression' &&
value.loweredFunc.dependencies.length === 0 &&
value.loweredFunc.func.context.length === 0 &&
// TODO: handle outlining named functions
value.loweredFunc.func.id === null &&

View File

@@ -17,7 +17,6 @@ export {mergeReactiveScopesThatInvalidateTogether} from './MergeReactiveScopesTh
export {printReactiveFunction} from './PrintReactiveFunction';
export {promoteUsedTemporaries} from './PromoteUsedTemporaries';
export {propagateEarlyReturns} from './PropagateEarlyReturns';
export {propagateScopeDependencies} from './PropagateScopeDependencies';
export {pruneAllReactiveScopes} from './PruneAllReactiveScopes';
export {pruneHoistedContexts} from './PruneHoistedContexts';
export {pruneNonEscapingScopes} from './PruneNonEscapingScopes';

View File

@@ -44,48 +44,44 @@ import { c as _c } from "react/compiler-runtime"; // @validateRefAccessDuringRen
import { useEffect, useRef, useState } from "react";
function Component() {
const $ = _c(6);
const $ = _c(5);
const ref = useRef(null);
const [state, setState] = useState(false);
let t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {};
t1 = [];
t0 = [];
$[0] = t0;
$[1] = t1;
} else {
t0 = $[0];
t1 = $[1];
}
useEffect(t0, t1);
useEffect(_temp, t0);
let t1;
let t2;
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = () => {
setState(true);
};
t3 = [];
t2 = [];
$[1] = t1;
$[2] = t2;
$[3] = t3;
} else {
t1 = $[1];
t2 = $[2];
t3 = $[3];
}
useEffect(t2, t3);
useEffect(t1, t2);
const t4 = String(state);
let t5;
if ($[4] !== t4) {
t5 = <Child key={t4} ref={ref} />;
const t3 = String(state);
let t4;
if ($[3] !== t3) {
t4 = <Child key={t3} ref={ref} />;
$[3] = t3;
$[4] = t4;
$[5] = t5;
} else {
t5 = $[5];
t4 = $[4];
}
return t5;
return t4;
}
function _temp() {}
function Child(t0) {
const { ref } = t0;

View File

@@ -28,7 +28,7 @@ function Component() {
t0 = () => {
"worklet";
setCount((count_0) => count_0 + 1);
setCount(_temp);
};
$[0] = t0;
} else {
@@ -45,6 +45,9 @@ function Component() {
}
return t1;
}
function _temp(count_0) {
return count_0 + 1;
}
```

View File

@@ -58,7 +58,7 @@ function Component(t0) {
const $ = _c(5);
const { obj, isObjNull } = t0;
let t1;
if ($[0] !== isObjNull || $[1] !== obj.prop) {
if ($[0] !== isObjNull || $[1] !== obj) {
t1 = () => {
if (!isObjNull) {
return obj.prop;
@@ -67,7 +67,7 @@ function Component(t0) {
}
};
$[0] = isObjNull;
$[1] = obj.prop;
$[1] = obj;
$[2] = t1;
} else {
t1 = $[2];

View File

@@ -38,16 +38,24 @@ import { identity } from "shared-runtime";
* try-catch block, as that might throw
*/
function useFoo(maybeNullObject) {
const $ = _c(2);
const $ = _c(4);
let y;
if ($[0] !== maybeNullObject.value.inner) {
if ($[0] !== maybeNullObject) {
y = [];
try {
y.push(identity(maybeNullObject.value.inner));
let t0;
if ($[2] !== maybeNullObject.value.inner) {
t0 = identity(maybeNullObject.value.inner);
$[2] = maybeNullObject.value.inner;
$[3] = t0;
} else {
t0 = $[3];
}
y.push(t0);
} catch {
y.push("null");
}
$[0] = maybeNullObject.value.inner;
$[0] = maybeNullObject;
$[1] = y;
} else {
y = $[1];

View File

@@ -55,11 +55,7 @@ function getNativeLogFunction(level) {
if (arguments.length === 1 && typeof arguments[0] === "string") {
str = arguments[0];
} else {
str = Array.prototype.map
.call(arguments, function (arg) {
return inspect(arg, { depth: 10 });
})
.join(", ");
str = Array.prototype.map.call(arguments, _temp).join(", ");
}
const firstArg = arguments[0];
@@ -92,6 +88,9 @@ function getNativeLogFunction(level) {
}
return t0;
}
function _temp(arg) {
return inspect(arg, { depth: 10 });
}
```

View File

@@ -2,39 +2,55 @@
## Input
```javascript
function component(foo, bar) {
import {mutate} from 'shared-runtime';
function Component({foo, bar}) {
let x = {foo};
let y = {bar};
const f0 = function () {
let a = {y};
let a = [y];
let b = x;
a.x = b;
// this writes y.x = x
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 3, bar: 4}],
sequentialRenders: [
{foo: 3, bar: 4},
{foo: 3, bar: 5},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function component(foo, bar) {
import { mutate } from "shared-runtime";
function Component(t0) {
const $ = _c(3);
const { foo, bar } = t0;
let y;
if ($[0] !== foo || $[1] !== bar) {
const x = { foo };
y = { bar };
const f0 = function () {
const a = { y };
const a = [y];
const b = x;
a.x = b;
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
$[0] = foo;
$[1] = bar;
$[2] = y;
@@ -44,5 +60,17 @@ function component(foo, bar) {
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ foo: 3, bar: 4 }],
sequentialRenders: [
{ foo: 3, bar: 4 },
{ foo: 3, bar: 5 },
],
};
```
### Eval output
(kind: ok) {"bar":4,"x":{"foo":3,"wat0":"joe"}}
{"bar":5,"x":{"foo":3,"wat0":"joe"}}

View File

@@ -1,12 +1,24 @@
function component(foo, bar) {
import {mutate} from 'shared-runtime';
function Component({foo, bar}) {
let x = {foo};
let y = {bar};
const f0 = function () {
let a = {y};
let a = [y];
let b = x;
a.x = b;
// this writes y.x = x
a[0].x = b;
};
f0();
mutate(y);
mutate(y.x);
return y;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{foo: 3, bar: 4}],
sequentialRenders: [
{foo: 3, bar: 4},
{foo: 3, bar: 5},
],
};

View File

@@ -26,29 +26,19 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function component(a, b) {
const $ = _c(5);
let t0;
if ($[0] !== b) {
t0 = { b };
$[0] = b;
$[1] = t0;
} else {
t0 = $[1];
}
const y = t0;
const $ = _c(2);
let z;
if ($[2] !== a || $[3] !== y.b) {
if ($[0] !== a) {
z = { a };
const x = function () {
z.a = 2;
};
x();
$[2] = a;
$[3] = y.b;
$[4] = z;
$[0] = a;
$[1] = z;
} else {
z = $[4];
z = $[1];
}
return z;
}

View File

@@ -2,21 +2,28 @@
## Input
```javascript
function component(a, b) {
import {mutate} from 'shared-runtime';
function Component({a, b}) {
let z = {a};
let y = {b};
let y = {b: {b}};
let x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
return z;
return [y, z];
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ['TodoAdd'],
isComponent: 'TodoAdd',
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};
```
@@ -25,32 +32,46 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function component(a, b) {
import { mutate } from "shared-runtime";
function Component(t0) {
const $ = _c(3);
let z;
const { a, b } = t0;
let t1;
if ($[0] !== a || $[1] !== b) {
z = { a };
const y = { b };
const z = { a };
const y = { b: { b } };
const x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
t1 = [y, z];
$[0] = a;
$[1] = b;
$[2] = z;
$[2] = t1;
} else {
z = $[2];
t1 = $[2];
}
return z;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ["TodoAdd"],
isComponent: "TodoAdd",
fn: Component,
params: [{ a: 2, b: 3 }],
sequentialRenders: [
{ a: 2, b: 3 },
{ a: 2, b: 3 },
{ a: 4, b: 3 },
{ a: 4, b: 5 },
],
};
```
### Eval output
(kind: ok) [{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":3,"wat0":"joe"}},{"a":2}]
[{"b":{"b":5,"wat0":"joe"}},{"a":2}]

View File

@@ -1,16 +1,23 @@
function component(a, b) {
import {mutate} from 'shared-runtime';
function Component({a, b}) {
let z = {a};
let y = {b};
let y = {b: {b}};
let x = function () {
z.a = 2;
console.log(y.b);
mutate(y.b);
};
x();
return z;
return [y, z];
}
export const FIXTURE_ENTRYPOINT = {
fn: component,
params: ['TodoAdd'],
isComponent: 'TodoAdd',
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};

View File

@@ -0,0 +1,80 @@
## Input
```javascript
function Component({a, b}) {
let z = {a};
let y = {b};
let x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(t0) {
const $ = _c(5);
const { a, b } = t0;
let z;
if ($[0] !== a || $[1] !== b) {
z = { a };
let t1;
if ($[3] !== b) {
t1 = { b };
$[3] = b;
$[4] = t1;
} else {
t1 = $[4];
}
const y = t1;
const x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
$[0] = a;
$[1] = b;
$[2] = z;
} else {
z = $[2];
}
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 2, b: 3 }],
sequentialRenders: [
{ a: 2, b: 3 },
{ a: 2, b: 3 },
{ a: 4, b: 3 },
{ a: 4, b: 5 },
],
};
```
### Eval output
(kind: ok) {"a":2}
{"a":2}
{"a":2}
{"a":2}

View File

@@ -0,0 +1,21 @@
function Component({a, b}) {
let z = {a};
let y = {b};
let x = function () {
z.a = 2;
return Math.max(y.b, 0);
};
x();
return z;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 2, b: 3}],
sequentialRenders: [
{a: 2, b: 3},
{a: 2, b: 3},
{a: 4, b: 3},
{a: 4, b: 5},
],
};

View File

@@ -34,7 +34,6 @@ function component(a) {
const x = { a };
y = {};
y;
y = x;
mutate(y);

View File

@@ -31,7 +31,6 @@ function bar(a) {
const x = [a];
y = {};
y;
y = x[0][1];
$[0] = a;
$[1] = y;

View File

@@ -37,8 +37,6 @@ function bar(a, b) {
let t;
t = {};
y;
t;
y = x[0][1];
t = x[1][0];
$[0] = a;

View File

@@ -31,7 +31,6 @@ function bar(a) {
const x = [a];
y = {};
y;
y = x[0].a[1];
$[0] = a;
$[1] = y;

View File

@@ -30,7 +30,6 @@ function bar(a) {
const x = [a];
y = {};
y;
y = x[0];
$[0] = a;
$[1] = y;

View File

@@ -25,7 +25,6 @@ function component(a) {
const x = { a };
y = 1;
y;
y = x;
mutate(y);

View File

@@ -38,9 +38,8 @@ function useTest() {
const t1 = (w = 42);
const t2 = w;
w;
let t3;
w = 999;
t3 = 2;
t0 = makeArray(t1, t2, t3);

View File

@@ -33,9 +33,14 @@ import { c as _c } from "react/compiler-runtime"; /**
* props.b *does* influence `a`
*/
function Component(props) {
const $ = _c(2);
const $ = _c(5);
let a;
if ($[0] !== props) {
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.c ||
$[3] !== props.d
) {
a = [];
a.push(props.a);
bb0: {
@@ -47,10 +52,13 @@ function Component(props) {
}
a.push(props.d);
$[0] = props;
$[1] = a;
$[0] = props.a;
$[1] = props.b;
$[2] = props.c;
$[3] = props.d;
$[4] = a;
} else {
a = $[1];
a = $[4];
}
return a;
}

View File

@@ -70,10 +70,10 @@ import { c as _c } from "react/compiler-runtime"; /**
* props.b does *not* influence `a`
*/
function ComponentA(props) {
const $ = _c(3);
const $ = _c(5);
let a_DEBUG;
let t0;
if ($[0] !== props) {
if ($[0] !== props.a || $[1] !== props.b || $[2] !== props.d) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
a_DEBUG = [];
@@ -85,12 +85,14 @@ function ComponentA(props) {
a_DEBUG.push(props.d);
}
$[0] = props;
$[1] = a_DEBUG;
$[2] = t0;
$[0] = props.a;
$[1] = props.b;
$[2] = props.d;
$[3] = a_DEBUG;
$[4] = t0;
} else {
a_DEBUG = $[1];
t0 = $[2];
a_DEBUG = $[3];
t0 = $[4];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;
@@ -102,9 +104,14 @@ function ComponentA(props) {
* props.b *does* influence `a`
*/
function ComponentB(props) {
const $ = _c(2);
const $ = _c(5);
let a;
if ($[0] !== props) {
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.c ||
$[3] !== props.d
) {
a = [];
a.push(props.a);
if (props.b) {
@@ -112,10 +119,13 @@ function ComponentB(props) {
}
a.push(props.d);
$[0] = props;
$[1] = a;
$[0] = props.a;
$[1] = props.b;
$[2] = props.c;
$[3] = props.d;
$[4] = a;
} else {
a = $[1];
a = $[4];
}
return a;
}
@@ -124,10 +134,15 @@ function ComponentB(props) {
* props.b *does* influence `a`, but only in a way that is never observable
*/
function ComponentC(props) {
const $ = _c(3);
const $ = _c(6);
let a;
let t0;
if ($[0] !== props) {
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.c ||
$[3] !== props.d
) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
a = [];
@@ -140,12 +155,15 @@ function ComponentC(props) {
a.push(props.d);
}
$[0] = props;
$[1] = a;
$[2] = t0;
$[0] = props.a;
$[1] = props.b;
$[2] = props.c;
$[3] = props.d;
$[4] = a;
$[5] = t0;
} else {
a = $[1];
t0 = $[2];
a = $[4];
t0 = $[5];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;
@@ -157,10 +175,15 @@ function ComponentC(props) {
* props.b *does* influence `a`
*/
function ComponentD(props) {
const $ = _c(3);
const $ = _c(6);
let a;
let t0;
if ($[0] !== props) {
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.c ||
$[3] !== props.d
) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
a = [];
@@ -173,12 +196,15 @@ function ComponentD(props) {
a.push(props.d);
}
$[0] = props;
$[1] = a;
$[2] = t0;
$[0] = props.a;
$[1] = props.b;
$[2] = props.c;
$[3] = props.d;
$[4] = a;
$[5] = t0;
} else {
a = $[1];
t0 = $[2];
a = $[4];
t0 = $[5];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;

View File

@@ -36,9 +36,9 @@ function mayMutate() {}
```javascript
import { c as _c } from "react/compiler-runtime";
function ComponentA(props) {
const $ = _c(2);
const $ = _c(4);
let t0;
if ($[0] !== props) {
if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) {
const a = [];
const b = [];
if (b) {
@@ -49,18 +49,20 @@ function ComponentA(props) {
}
t0 = <Foo a={a} b={b} />;
$[0] = props;
$[1] = t0;
$[0] = props.p0;
$[1] = props.p1;
$[2] = props.p2;
$[3] = t0;
} else {
t0 = $[1];
t0 = $[3];
}
return t0;
}
function ComponentB(props) {
const $ = _c(2);
const $ = _c(4);
let t0;
if ($[0] !== props) {
if ($[0] !== props.p0 || $[1] !== props.p1 || $[2] !== props.p2) {
const a = [];
const b = [];
if (mayMutate(b)) {
@@ -71,10 +73,12 @@ function ComponentB(props) {
}
t0 = <Foo a={a} b={b} />;
$[0] = props;
$[1] = t0;
$[0] = props.p0;
$[1] = props.p1;
$[2] = props.p2;
$[3] = t0;
} else {
t0 = $[1];
t0 = $[3];
}
return t0;
}

View File

@@ -19,10 +19,10 @@ function foo() {
import { c as _c } from "react/compiler-runtime";
function foo() {
const $ = _c(1);
const getJSX = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const getJSX = () => <Child x={GLOBAL_IS_X} />;
t0 = getJSX();
$[0] = t0;
} else {
@@ -31,6 +31,9 @@ function foo() {
const result = t0;
return result;
}
function _temp() {
return <Child x={GLOBAL_IS_X} />;
}
```

View File

@@ -23,13 +23,14 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
function foo() {
const f = () => {
console.log(42);
};
const f = _temp;
f();
return 42;
}
function _temp() {
console.log(42);
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,

View File

@@ -18,12 +18,10 @@ function Component(props) {
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(1);
const onEvent = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const onEvent = () => {
console.log(42);
};
t0 = <Foo onEvent={onEvent} />;
$[0] = t0;
} else {
@@ -31,6 +29,9 @@ function Component(props) {
}
return t0;
}
function _temp() {
console.log(42);
}
```

View File

@@ -34,9 +34,8 @@ function Component(props) {
let Component;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
Component = Stringify;
Component;
let t0;
t0 = Component;
Component = t0;
$[0] = Component;

View File

@@ -31,9 +31,9 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(5);
const $ = _c(7);
let t0;
if ($[0] !== props) {
if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
const x = [];
@@ -41,12 +41,12 @@ function Component(props) {
x.push(props.a);
if (props.b) {
let t1;
if ($[2] !== props.b) {
if ($[4] !== props.b) {
t1 = [props.b];
$[2] = props.b;
$[3] = t1;
$[4] = props.b;
$[5] = t1;
} else {
t1 = $[3];
t1 = $[5];
}
const y = t1;
x.push(y);
@@ -58,20 +58,22 @@ function Component(props) {
break bb0;
} else {
let t1;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t1 = foo();
$[4] = t1;
$[6] = t1;
} else {
t1 = $[4];
t1 = $[6];
}
t0 = t1;
break bb0;
}
}
$[0] = props;
$[1] = t0;
$[0] = props.cond;
$[1] = props.a;
$[2] = props.b;
$[3] = t0;
} else {
t0 = $[1];
t0 = $[3];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;

View File

@@ -45,9 +45,9 @@ import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function Component(props) {
const $ = _c(4);
const $ = _c(6);
let t0;
if ($[0] !== props) {
if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
const x = [];
@@ -57,21 +57,23 @@ function Component(props) {
break bb0;
} else {
let t1;
if ($[2] !== props.b) {
if ($[4] !== props.b) {
t1 = makeArray(props.b);
$[2] = props.b;
$[3] = t1;
$[4] = props.b;
$[5] = t1;
} else {
t1 = $[3];
t1 = $[5];
}
t0 = t1;
break bb0;
}
}
$[0] = props;
$[1] = t0;
$[0] = props.cond;
$[1] = props.a;
$[2] = props.b;
$[3] = t0;
} else {
t0 = $[1];
t0 = $[3];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;

View File

@@ -0,0 +1,50 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
import {ValidateMemoization} from 'shared-runtime';
function Component(props) {
const data = useMemo(() => {
const x = [];
x.push(props?.items);
if (props.cond) {
x.push(props?.items);
}
return x;
}, [props?.items, props.cond]);
return (
<ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
);
}
```
## Error
```
2 | import {ValidateMemoization} from 'shared-runtime';
3 | function Component(props) {
> 4 | const data = useMemo(() => {
| ^^^^^^^
> 5 | const x = [];
| ^^^^^^^^^^^^^^^^^
> 6 | x.push(props?.items);
| ^^^^^^^^^^^^^^^^^
> 7 | if (props.cond) {
| ^^^^^^^^^^^^^^^^^
> 8 | x.push(props?.items);
| ^^^^^^^^^^^^^^^^^
> 9 | }
| ^^^^^^^^^^^^^^^^^
> 10 | return x;
| ^^^^^^^^^^^^^^^^^
> 11 | }, [props?.items, props.cond]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11)
12 | return (
13 | <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
14 | );
```

View File

@@ -0,0 +1,50 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
import {ValidateMemoization} from 'shared-runtime';
function Component(props) {
const data = useMemo(() => {
const x = [];
x.push(props?.items);
if (props.cond) {
x.push(props.items);
}
return x;
}, [props?.items, props.cond]);
return (
<ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
);
}
```
## Error
```
2 | import {ValidateMemoization} from 'shared-runtime';
3 | function Component(props) {
> 4 | const data = useMemo(() => {
| ^^^^^^^
> 5 | const x = [];
| ^^^^^^^^^^^^^^^^^
> 6 | x.push(props?.items);
| ^^^^^^^^^^^^^^^^^
> 7 | if (props.cond) {
| ^^^^^^^^^^^^^^^^^
> 8 | x.push(props.items);
| ^^^^^^^^^^^^^^^^^
> 9 | }
| ^^^^^^^^^^^^^^^^^
> 10 | return x;
| ^^^^^^^^^^^^^^^^^
> 11 | }, [props?.items, props.cond]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11)
12 | return (
13 | <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
14 | );
```

View File

@@ -24,13 +24,17 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
4 | }
5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting
6 | function baz() {
> 6 | function baz() {
| ^^^^^^^^^^^^^^^^
> 7 | return bar();
| ^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (7:7)
8 | }
| ^^^^^^^^^^^^^^^^^
> 8 | }
| ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8)
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -22,7 +22,7 @@ function Component() {
4 | // NOTE: `i` is a context variable because it's reassigned and also referenced
5 | // within a closure, the `onClick` handler of each item
> 6 | for (let i = MIN; i <= MAX; i += INCREMENT) {
| ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6)
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (6:6)
7 | items.push(<Stringify key={i} onClick={() => data.set(i)} />);
8 | }
9 | return items;

View File

@@ -25,7 +25,7 @@ export const FIXTURE_ENTRYPONT = {
1 | function useFoo(props: {value: {x: string; y: string} | null}) {
2 | const value = props.value;
> 3 | return createArray(value?.x, value?.y)?.join(', ');
| ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (3:3)
| ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (3:3)
4 | }
5 |
6 | function createArray<T>(...args: Array<T>): Array<T> {

View File

@@ -22,7 +22,7 @@ function Component(props) {
7 | return hasErrors;
8 | }
> 9 | return hasErrors();
| ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$16 (9:9)
| ^^^^^^^^^ Invariant: [hoisting] Expected value for identifier to be initialized. hasErrors_0$14 (9:9)
10 | }
11 |
```

View File

@@ -34,7 +34,7 @@ function Component() {
t0 = function update() {
"worklet";
setCount((count_0) => count_0 + 1);
setCount(_temp);
};
$[0] = t0;
} else {
@@ -51,6 +51,9 @@ function Component() {
}
return t1;
}
function _temp(count_0) {
return count_0 + 1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
// @enableTreatFunctionDepsAsConditional
import {Stringify} from 'shared-runtime';
function Component({props}) {
@@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional
import { Stringify } from "shared-runtime";
function Component(t0) {

View File

@@ -1,4 +1,4 @@
// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
// @enableTreatFunctionDepsAsConditional
import {Stringify} from 'shared-runtime';
function Component({props}) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
// @enableTreatFunctionDepsAsConditional
function Component(props) {
function getLength() {
return props.bar.length;
@@ -21,15 +21,15 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
import { c as _c } from "react/compiler-runtime"; // @enableTreatFunctionDepsAsConditional
function Component(props) {
const $ = _c(5);
let t0;
if ($[0] !== props) {
if ($[0] !== props.bar) {
t0 = function getLength() {
return props.bar.length;
};
$[0] = props;
$[0] = props.bar;
$[1] = t0;
} else {
t0 = $[1];

View File

@@ -1,4 +1,4 @@
// @enableTreatFunctionDepsAsConditional @enablePropagateDepsInHIR:false
// @enableTreatFunctionDepsAsConditional
function Component(props) {
function getLength() {
return props.bar.length;

View File

@@ -26,9 +26,9 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(3);
let items;
if ($[0] !== props) {
if ($[0] !== props.cond || $[1] !== props.a) {
let t0;
if (props.cond) {
t0 = [];
@@ -38,10 +38,11 @@ function Component(props) {
items = t0;
items?.push(props.a);
$[0] = props;
$[1] = items;
$[0] = props.cond;
$[1] = props.a;
$[2] = items;
} else {
items = $[1];
items = $[2];
}
return items;
}

View File

@@ -33,11 +33,11 @@ function useFoo(t0) {
const $ = _c(2);
const { a } = t0;
let x;
if ($[0] !== a.b.c.d) {
if ($[0] !== a.b.c.d.e) {
x = [];
x.push(a?.b.c?.d.e);
x.push(a.b?.c.d?.e);
$[0] = a.b.c.d;
$[0] = a.b.c.d.e;
$[1] = x;
} else {
x = $[1];

View File

@@ -0,0 +1,75 @@
## Input
```javascript
import {Throw} from 'shared-runtime';
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const invalidTag = Throw;
/**
* Need to be careful to not parse `invalidTag` as a localVar (i.e. render
* Throw). Note that the jsx transform turns this into a string tag:
* `jsx("invalidTag"...
*/
return <invalidTag val={{val: 2}} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Throw } from "shared-runtime";
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const $ = _c(1);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <invalidTag val={{ val: 2 }} />;
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};
```

View File

@@ -0,0 +1,29 @@
import {Throw} from 'shared-runtime';
/**
* Note: this is disabled in the evaluator due to different devmode errors.
* Found differences in evaluator results
* Non-forget (expected):
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: ['Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag']
*
* Forget:
* (kind: ok) <invalidtag val="[object Object]"></invalidtag>
* logs: [
* 'Warning: <%s /> is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.%s','invalidTag',
* 'Warning: The tag <%s> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.%s','invalidTag',
* ]
*/
function useFoo() {
const invalidTag = Throw;
/**
* Need to be careful to not parse `invalidTag` as a localVar (i.e. render
* Throw). Note that the jsx transform turns this into a string tag:
* `jsx("invalidTag"...
*/
return <invalidTag val={{val: 2}} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,
params: [],
};

View File

@@ -27,11 +27,10 @@ import * as SharedRuntime from "shared-runtime";
function useFoo(t0) {
const $ = _c(1);
const { cond } = t0;
const MyLocal = SharedRuntime;
if (cond) {
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <MyLocal.Text value={4} />;
t1 = <SharedRuntime.Text value={4} />;
$[0] = t1;
} else {
t1 = $[0];

View File

@@ -22,10 +22,9 @@ import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function useFoo() {
const $ = _c(1);
const MyLocal = SharedRuntime;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = <MyLocal.Text value={4} />;
t0 = <SharedRuntime.Text value={4} />;
$[0] = t0;
} else {
t0 = $[0];

View File

@@ -25,10 +25,10 @@ import { c as _c } from "react/compiler-runtime";
import { Stringify } from "shared-runtime";
function useFoo() {
const $ = _c(1);
const callback = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const callback = () => <Stringify value={4} />;
t0 = callback();
$[0] = t0;
} else {
@@ -36,6 +36,9 @@ function useFoo() {
}
return t0;
}
function _temp() {
return <Stringify value={4} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,

View File

@@ -0,0 +1,59 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
import {invoke} from 'shared-runtime';
function useComponentFactory({name}) {
const localVar = SharedRuntime;
const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
return invoke(cb);
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
import { invoke } from "shared-runtime";
function useComponentFactory(t0) {
const $ = _c(4);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = () => (
<SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>
);
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
const cb = t1;
let t2;
if ($[2] !== cb) {
t2 = invoke(cb);
$[2] = cb;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,12 @@
import * as SharedRuntime from 'shared-runtime';
import {invoke} from 'shared-runtime';
function useComponentFactory({name}) {
const localVar = SharedRuntime;
const cb = () => <localVar.Stringify>hello world {name}</localVar.Stringify>;
return invoke(cb);
}
export const FIXTURE_ENTRYPOINT = {
fn: useComponentFactory,
params: [{name: 'sathya'}],
};

View File

@@ -0,0 +1,45 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
const localVar = SharedRuntime;
return <localVar.Stringify>hello world {name}</localVar.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function Component(t0) {
const $ = _c(2);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,10 @@
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
const localVar = SharedRuntime;
return <localVar.Stringify>hello world {name}</localVar.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};

View File

@@ -0,0 +1,44 @@
## Input
```javascript
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
return <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function Component(t0) {
const $ = _c(2);
const { name } = t0;
let t1;
if ($[0] !== name) {
t1 = <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
$[0] = name;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ name: "sathya" }],
};
```
### Eval output
(kind: ok) <div>{"children":["hello world ","sathya"]}</div>

View File

@@ -0,0 +1,9 @@
import * as SharedRuntime from 'shared-runtime';
function Component({name}) {
return <SharedRuntime.Stringify>hello world {name}</SharedRuntime.Stringify>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{name: 'sathya'}],
};

View File

@@ -25,11 +25,10 @@ import { c as _c } from "react/compiler-runtime";
import * as SharedRuntime from "shared-runtime";
function useFoo() {
const $ = _c(1);
const MyLocal = SharedRuntime;
const callback = _temp;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
const callback = () => <MyLocal.Text value={4} />;
t0 = callback();
$[0] = t0;
} else {
@@ -37,6 +36,9 @@ function useFoo() {
}
return t0;
}
function _temp() {
return <SharedRuntime.Text value={4} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: useFoo,

View File

@@ -26,7 +26,6 @@ function f(a) {
const $ = _c(4);
let x;
if ($[0] !== a) {
x;
x = { a };
$[0] = a;
$[1] = x;

View File

@@ -27,7 +27,6 @@ function f(a) {
const $ = _c(2);
let x;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
x;
x = {};
$[0] = x;
} else {

View File

@@ -32,7 +32,7 @@ function Component() {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => {
setState((s) => s + 1);
setState(_temp);
};
$[0] = t0;
} else {
@@ -61,6 +61,9 @@ function Component() {
}
return t2;
}
function _temp(s) {
return s + 1;
}
```

View File

@@ -31,7 +31,7 @@ function Component(props) {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (e) => {
setX((currentX) => currentX + null);
setX(_temp);
};
$[0] = t0;
} else {
@@ -48,6 +48,9 @@ function Component(props) {
}
return t1;
}
function _temp(currentX) {
return currentX + null;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,

View File

@@ -120,29 +120,29 @@ function useFoo(t0) {
}
const x = t1;
let t2;
if ($[2] !== prop2?.inner) {
if ($[2] !== prop2?.inner.value) {
t2 = identity(prop2?.inner.value)?.toString();
$[2] = prop2?.inner;
$[2] = prop2?.inner.value;
$[3] = t2;
} else {
t2 = $[3];
}
const y = t2;
let t3;
if ($[4] !== prop3 || $[5] !== prop4) {
if ($[4] !== prop3 || $[5] !== prop4?.inner) {
t3 = prop3?.fn(prop4?.inner.value).toString();
$[4] = prop3;
$[5] = prop4;
$[5] = prop4?.inner;
$[6] = t3;
} else {
t3 = $[6];
}
const z = t3;
let t4;
if ($[7] !== prop5 || $[8] !== prop6) {
if ($[7] !== prop5 || $[8] !== prop6?.inner) {
t4 = prop5?.fn(prop6?.inner.value)?.toString();
$[7] = prop5;
$[8] = prop6;
$[8] = prop6?.inner;
$[9] = t4;
} else {
t4 = $[9];

View File

@@ -29,9 +29,9 @@ import { c as _c } from "react/compiler-runtime";
import { makeObject_Primitives } from "shared-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(3);
let t0;
if ($[0] !== props) {
if ($[0] !== props.cond || $[1] !== props.value) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
const object = makeObject_Primitives();
@@ -45,10 +45,11 @@ function Component(props) {
break bb0;
}
}
$[0] = props;
$[1] = t0;
$[0] = props.cond;
$[1] = props.value;
$[2] = t0;
} else {
t0 = $[1];
t0 = $[2];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;

View File

@@ -40,15 +40,15 @@ function useHook(t0) {
t1 = $[1];
}
let t2;
if ($[2] !== b || $[3] !== t1) {
if ($[2] !== t1 || $[3] !== b) {
t2 = {
x: t1,
y() {
return [b];
},
};
$[2] = b;
$[3] = t1;
$[2] = t1;
$[3] = b;
$[4] = t2;
} else {
t2 = $[4];

View File

@@ -40,7 +40,7 @@ function useHook(t0) {
const { value } = t0;
const [state] = useState(false);
let t1;
if ($[0] !== value || $[1] !== state) {
if ($[0] !== state || $[1] !== value) {
t1 = {
getX() {
return {
@@ -52,8 +52,8 @@ function useHook(t0) {
};
},
};
$[0] = value;
$[1] = state;
$[0] = state;
$[1] = value;
$[2] = t1;
} else {
t1 = $[2];

View File

@@ -1,74 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
import {ValidateMemoization} from 'shared-runtime';
function Component(props) {
const data = useMemo(() => {
const x = [];
x.push(props?.items);
if (props.cond) {
x.push(props?.items);
}
return x;
}, [props?.items, props.cond]);
return (
<ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
);
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
import { ValidateMemoization } from "shared-runtime";
function Component(props) {
const $ = _c(9);
props?.items;
let t0;
let x;
if ($[0] !== props?.items || $[1] !== props.cond) {
x = [];
x.push(props?.items);
if (props.cond) {
x.push(props?.items);
}
$[0] = props?.items;
$[1] = props.cond;
$[2] = x;
} else {
x = $[2];
}
t0 = x;
const data = t0;
const t1 = props?.items;
let t2;
if ($[3] !== t1 || $[4] !== props.cond) {
t2 = [t1, props.cond];
$[3] = t1;
$[4] = props.cond;
$[5] = t2;
} else {
t2 = $[5];
}
let t3;
if ($[6] !== t2 || $[7] !== data) {
t3 = <ValidateMemoization inputs={t2} output={data} />;
$[6] = t2;
$[7] = data;
$[8] = t3;
} else {
t3 = $[8];
}
return t3;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,74 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
import {ValidateMemoization} from 'shared-runtime';
function Component(props) {
const data = useMemo(() => {
const x = [];
x.push(props?.items);
if (props.cond) {
x.push(props.items);
}
return x;
}, [props?.items, props.cond]);
return (
<ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
);
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
import { ValidateMemoization } from "shared-runtime";
function Component(props) {
const $ = _c(9);
props?.items;
let t0;
let x;
if ($[0] !== props?.items || $[1] !== props.cond) {
x = [];
x.push(props?.items);
if (props.cond) {
x.push(props.items);
}
$[0] = props?.items;
$[1] = props.cond;
$[2] = x;
} else {
x = $[2];
}
t0 = x;
const data = t0;
const t1 = props?.items;
let t2;
if ($[3] !== t1 || $[4] !== props.cond) {
t2 = [t1, props.cond];
$[3] = t1;
$[4] = props.cond;
$[5] = t2;
} else {
t2 = $[5];
}
let t3;
if ($[6] !== t2 || $[7] !== data) {
t3 = <ValidateMemoization inputs={t2} output={data} />;
$[6] = t2;
$[7] = data;
$[8] = t3;
} else {
t3 = $[8];
}
return t3;
}
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -30,10 +30,10 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(4);
const $ = _c(6);
let y;
let t0;
if ($[0] !== props) {
if ($[0] !== props.cond || $[1] !== props.a || $[2] !== props.b) {
t0 = Symbol.for("react.early_return_sentinel");
bb0: {
const x = [];
@@ -43,11 +43,11 @@ function Component(props) {
break bb0;
} else {
let t1;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
t1 = foo();
$[3] = t1;
$[5] = t1;
} else {
t1 = $[3];
t1 = $[5];
}
y = t1;
if (props.b) {
@@ -56,12 +56,14 @@ function Component(props) {
}
}
}
$[0] = props;
$[1] = y;
$[2] = t0;
$[0] = props.cond;
$[1] = props.a;
$[2] = props.b;
$[3] = y;
$[4] = t0;
} else {
y = $[1];
t0 = $[2];
y = $[3];
t0 = $[4];
}
if (t0 !== Symbol.for("react.early_return_sentinel")) {
return t0;

View File

@@ -49,7 +49,7 @@ import { c as _c } from "react/compiler-runtime";
import { makeArray } from "shared-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(6);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {};
@@ -59,7 +59,12 @@ function Component(props) {
}
const x = t0;
let t1;
if ($[1] !== props) {
if (
$[1] !== props.cond ||
$[2] !== props.cond2 ||
$[3] !== props.value ||
$[4] !== props.value2
) {
let y;
if (props.cond) {
if (props.cond2) {
@@ -74,10 +79,13 @@ function Component(props) {
y.push(x);
t1 = [x, y];
$[1] = props;
$[2] = t1;
$[1] = props.cond;
$[2] = props.cond2;
$[3] = props.value;
$[4] = props.value2;
$[5] = t1;
} else {
t1 = $[2];
t1 = $[5];
}
return t1;
}

View File

@@ -36,7 +36,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(3);
const $ = _c(4);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {};
@@ -46,7 +46,7 @@ function Component(props) {
}
const x = t0;
let t1;
if ($[1] !== props) {
if ($[1] !== props.cond || $[2] !== props.value) {
let y;
if (props.cond) {
y = [props.value];
@@ -57,10 +57,11 @@ function Component(props) {
y.push(x);
t1 = [x, y];
$[1] = props;
$[2] = t1;
$[1] = props.cond;
$[2] = props.value;
$[3] = t1;
} else {
t1 = $[2];
t1 = $[3];
}
return t1;
}

View File

@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime"; // @debug
function Component(props) {
const $ = _c(3);
const $ = _c(4);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {};
@@ -42,7 +42,7 @@ function Component(props) {
}
const x = t0;
let t1;
if ($[1] !== props) {
if ($[1] !== props.cond || $[2] !== props.a) {
let y;
if (props.cond) {
y = {};
@@ -53,10 +53,11 @@ function Component(props) {
y.x = x;
t1 = [x, y];
$[1] = props;
$[2] = t1;
$[1] = props.cond;
$[2] = props.a;
$[3] = t1;
} else {
t1 = $[2];
t1 = $[3];
}
return t1;
}

View File

@@ -0,0 +1,50 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
function Component({propA, propB}) {
return useCallback(() => {
if (propA) {
return {
value: propB.x.y,
};
}
}, [propA, propB.x.y]);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{propA: 1, propB: {x: {y: []}}}],
};
```
## Error
```
3 |
4 | function Component({propA, propB}) {
> 5 | return useCallback(() => {
| ^^^^^^^
> 6 | if (propA) {
| ^^^^^^^^^^^^^^^^
> 7 | return {
| ^^^^^^^^^^^^^^^^
> 8 | value: propB.x.y,
| ^^^^^^^^^^^^^^^^
> 9 | };
| ^^^^^^^^^^^^^^^^
> 10 | }
| ^^^^^^^^^^^^^^^^
> 11 | }, [propA, propB.x.y]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (5:11)
12 | }
13 |
14 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -0,0 +1,59 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {identity, mutate} from 'shared-runtime';
function useHook(propA, propB) {
return useCallback(() => {
const x = {};
if (identity(null) ?? propA.a) {
mutate(x);
return {
value: propB.x.y,
};
}
}, [propA.a, propB.x.y]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{a: 1}, {x: {y: 3}}],
};
```
## Error
```
4 |
5 | function useHook(propA, propB) {
> 6 | return useCallback(() => {
| ^^^^^^^
> 7 | const x = {};
| ^^^^^^^^^^^^^^^^^
> 8 | if (identity(null) ?? propA.a) {
| ^^^^^^^^^^^^^^^^^
> 9 | mutate(x);
| ^^^^^^^^^^^^^^^^^
> 10 | return {
| ^^^^^^^^^^^^^^^^^
> 11 | value: propB.x.y,
| ^^^^^^^^^^^^^^^^^
> 12 | };
| ^^^^^^^^^^^^^^^^^
> 13 | }
| ^^^^^^^^^^^^^^^^^
> 14 | }, [propA.a, propB.x.y]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (6:14)
15 | }
16 |
17 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -0,0 +1,53 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
/**
* TODO: we're currently bailing out because `contextVar` is a context variable
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
* we took as eligible dependencies.
*
* One solution is to simply record `LoadContext` identifiers into the
* temporaries sidemap when the instruction occurs *after* the context
* variable's mutable range.
*/
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};
```
## Error
```
22 | }
23 |
> 24 | const cb = useCallback(() => [contextVar.val], [contextVar.val]);
| ^^^^^^^^^^^^^^^^^^^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (24:24)
25 |
26 | return <Stringify cb={cb} shouldInvokeFns={true} />;
27 | }
```

View File

@@ -0,0 +1,32 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
/**
* TODO: we're currently bailing out because `contextVar` is a context variable
* and not recorded into the PropagateScopeDeps LoadLocal / PropertyLoad
* sidemap. Previously, we were able to avoid this as `BuildHIR` hoisted
* `LoadContext` and `PropertyLoad` instructions into the outer function, which
* we took as eligible dependencies.
*
* One solution is to simply record `LoadContext` identifiers into the
* temporaries sidemap when the instruction occurs *after* the context
* variable's mutable range.
*/
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};

View File

@@ -1,81 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useCallback } from "react";
import { Stringify } from "shared-runtime";
function Foo(props) {
const $ = _c(6);
let contextVar;
if ($[0] !== props.cond) {
if (props.cond) {
contextVar = { val: 2 };
} else {
contextVar = {};
}
$[0] = props.cond;
$[1] = contextVar;
} else {
contextVar = $[1];
}
const t0 = contextVar;
let t1;
if ($[2] !== t0.val) {
t1 = () => [contextVar.val];
$[2] = t0.val;
$[3] = t1;
} else {
t1 = $[3];
}
contextVar;
const cb = t1;
let t2;
if ($[4] !== cb) {
t2 = <Stringify cb={cb} shouldInvokeFns={true} />;
$[4] = cb;
$[5] = t2;
} else {
t2 = $[5];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{ cond: true }],
};
```
### Eval output
(kind: ok) <div>{"cb":{"kind":"Function","result":[2]},"shouldInvokeFns":true}</div>

View File

@@ -1,21 +0,0 @@
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {Stringify} from 'shared-runtime';
function Foo(props) {
let contextVar;
if (props.cond) {
contextVar = {val: 2};
} else {
contextVar = {};
}
const cb = useCallback(() => [contextVar.val], [contextVar.val]);
return <Stringify cb={cb} shouldInvokeFns={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{cond: true}],
};

View File

@@ -45,18 +45,16 @@ function Foo(props) {
} else {
x = $[1];
}
const t0 = x;
let t1;
if ($[2] !== t0) {
t1 = () => [x];
$[2] = t0;
$[3] = t1;
let t0;
if ($[2] !== x) {
t0 = () => [x];
$[2] = x;
$[3] = t0;
} else {
t1 = $[3];
t0 = $[3];
}
x;
const cb = t1;
const cb = t0;
return cb;
}

View File

@@ -1,58 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
function Component({propA, propB}) {
return useCallback(() => {
if (propA) {
return {
value: propB.x.y,
};
}
}, [propA, propB.x.y]);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{propA: 1, propB: {x: {y: []}}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useCallback } from "react";
function Component(t0) {
const $ = _c(3);
const { propA, propB } = t0;
let t1;
if ($[0] !== propA || $[1] !== propB.x.y) {
t1 = () => {
if (propA) {
return { value: propB.x.y };
}
};
$[0] = propA;
$[1] = propB.x.y;
$[2] = t1;
} else {
t1 = $[2];
}
return t1;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ propA: 1, propB: { x: { y: [] } } }],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"

View File

@@ -70,28 +70,26 @@ function useBar(t0, cond) {
if (cond) {
x = b;
}
const t2 = x;
let t3;
if ($[1] !== a || $[2] !== t2) {
t3 = () => [a, x];
$[1] = a;
$[2] = t2;
$[3] = t3;
let t2;
if ($[1] !== x || $[2] !== a) {
t2 = () => [a, x];
$[1] = x;
$[2] = a;
$[3] = t2;
} else {
t3 = $[3];
t2 = $[3];
}
x;
const cb = t3;
let t4;
const cb = t2;
let t3;
if ($[4] !== cb) {
t4 = <Stringify cb={cb} shouldInvoke={true} />;
t3 = <Stringify cb={cb} shouldInvoke={true} />;
$[4] = cb;
$[5] = t4;
$[5] = t3;
} else {
t4 = $[5];
t3 = $[5];
}
return t4;
return t3;
}
export const FIXTURE_ENTRYPOINT = {

View File

@@ -1,63 +0,0 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useCallback} from 'react';
import {identity, mutate} from 'shared-runtime';
function useHook(propA, propB) {
return useCallback(() => {
const x = {};
if (identity(null) ?? propA.a) {
mutate(x);
return {
value: propB.x.y,
};
}
}, [propA.a, propB.x.y]);
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{a: 1}, {x: {y: 3}}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useCallback } from "react";
import { identity, mutate } from "shared-runtime";
function useHook(propA, propB) {
const $ = _c(3);
let t0;
if ($[0] !== propA.a || $[1] !== propB.x.y) {
t0 = () => {
const x = {};
if (identity(null) ?? propA.a) {
mutate(x);
return { value: propB.x.y };
}
};
$[0] = propA.a;
$[1] = propB.x.y;
$[2] = t0;
} else {
t0 = $[2];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: useHook,
params: [{ a: 1 }, { x: { y: 3 } }],
};
```
### Eval output
(kind: ok) "[[ function params=0 ]]"

View File

@@ -35,7 +35,6 @@ function useFoo(arr1, arr2) {
if ($[0] !== arr1 || $[1] !== arr2) {
const x = [arr1];
y;
(y = x.concat(arr2)), y;
$[0] = arr1;
$[1] = arr2;

View File

@@ -34,9 +34,9 @@ function useFoo(t0) {
const $ = _c(2);
const { a } = t0;
let t1;
if ($[0] !== a.b) {
if ($[0] !== a.b?.c.d?.e) {
t1 = <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
$[0] = a.b;
$[0] = a.b?.c.d?.e;
$[1] = t1;
} else {
t1 = $[1];

View File

@@ -29,36 +29,38 @@ import { c as _c } from "react/compiler-runtime";
const FooContext = React.createContext({ current: null });
function Component(props) {
const $ = _c(5);
const $ = _c(7);
React.useContext(FooContext);
const ref = React.useRef();
const [x, setX] = React.useState(false);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
if ($[0] !== ref) {
t0 = () => {
setX(true);
ref.current = true;
};
$[0] = t0;
$[0] = ref;
$[1] = t0;
} else {
t0 = $[0];
t0 = $[1];
}
const onClick = t0;
let t1;
if ($[1] !== props.children) {
if ($[2] !== props.children) {
t1 = React.cloneElement(props.children);
$[1] = props.children;
$[2] = t1;
$[2] = props.children;
$[3] = t1;
} else {
t1 = $[2];
t1 = $[3];
}
let t2;
if ($[3] !== t1) {
if ($[4] !== onClick || $[5] !== t1) {
t2 = <div onClick={onClick}>{t1}</div>;
$[3] = t1;
$[4] = t2;
$[4] = onClick;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[4];
t2 = $[6];
}
return t2;
}

Some files were not shown because too many files have changed in this diff Show More