mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Update
[ghstack-poisoned]
This commit is contained in:
@@ -13,7 +13,7 @@ module.exports = function (api) {
|
||||
[
|
||||
'babel-plugin-react-compiler',
|
||||
{
|
||||
runtimeModule: 'react-compiler-runtime',
|
||||
target: '18',
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
"test": "yarn workspaces run test",
|
||||
"snap": "yarn workspace babel-plugin-react-compiler run snap",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
"postinstall": "perl -p -i -e 's/react\\.element/react.transitional.element/' node_modules/fbt/lib/FbtReactUtil.js && perl -p -i -e 's/didWarnAboutUsingAct = false;/didWarnAboutUsingAct = true;/' node_modules/react-dom/cjs/react-dom-test-utils.development.js",
|
||||
"npm:publish": "node scripts/release/publish"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf dist && rollup --config --bundleConfigAsCjs",
|
||||
"test": "yarn snap:ci",
|
||||
"test": "./scripts/link-react-compiler-runtime.sh && yarn snap:ci",
|
||||
"jest": "yarn build && ts-node node_modules/.bin/jest",
|
||||
"snap": "node ../snap/dist/main.js",
|
||||
"snap:build": "yarn workspace snap run build",
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
yarn --silent workspace react-compiler-runtime link
|
||||
yarn --silent workspace babel-plugin-react-compiler link react-compiler-runtime
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
parseEnvironmentConfig,
|
||||
} from '../HIR/Environment';
|
||||
import {hasOwnProperty} from '../Utils/utils';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
|
||||
const PanicThresholdOptionsSchema = z.enum([
|
||||
/*
|
||||
@@ -86,17 +87,6 @@ export type PluginOptions = {
|
||||
*/
|
||||
compilationMode: CompilationMode;
|
||||
|
||||
/*
|
||||
* If enabled, Forget will import `useMemoCache` from the given module
|
||||
* instead of `react/compiler-runtime`.
|
||||
*
|
||||
* ```
|
||||
* // If set to "react-compiler-runtime"
|
||||
* import {c as useMemoCache} from 'react-compiler-runtime';
|
||||
* ```
|
||||
*/
|
||||
runtimeModule?: string | null | undefined;
|
||||
|
||||
/**
|
||||
* By default React Compiler will skip compilation of code that suppresses the default
|
||||
* React ESLint rules, since this is a strong indication that the code may be breaking React rules
|
||||
@@ -121,8 +111,19 @@ export type PluginOptions = {
|
||||
* Set this flag (on by default) to automatically check for this library and activate the support.
|
||||
*/
|
||||
enableReanimatedCheck: boolean;
|
||||
|
||||
/**
|
||||
* The minimum major version of React that the compiler should emit code for. If the target is 19
|
||||
* or higher, the compiler emits direct imports of React runtime APIs needed by the compiler. On
|
||||
* versions prior to 19, an extra runtime package react-compiler-runtime is necessary to provide
|
||||
* a userspace approximation of runtime APIs.
|
||||
*/
|
||||
target: CompilerReactTarget;
|
||||
};
|
||||
|
||||
const CompilerReactTargetSchema = z.enum(['17', '18', '19']);
|
||||
export type CompilerReactTarget = z.infer<typeof CompilerReactTargetSchema>;
|
||||
|
||||
const CompilationModeSchema = z.enum([
|
||||
/*
|
||||
* Compiles functions annotated with "use forget" or component/hook-like functions.
|
||||
@@ -202,7 +203,6 @@ export const defaultOptions: PluginOptions = {
|
||||
logger: null,
|
||||
gating: null,
|
||||
noEmit: false,
|
||||
runtimeModule: null,
|
||||
eslintSuppressionRules: null,
|
||||
flowSuppressions: true,
|
||||
ignoreUseNoForget: false,
|
||||
@@ -210,6 +210,7 @@ export const defaultOptions: PluginOptions = {
|
||||
return filename.indexOf('node_modules') === -1;
|
||||
},
|
||||
enableReanimatedCheck: true,
|
||||
target: '19',
|
||||
} as const;
|
||||
|
||||
export function parsePluginOptions(obj: unknown): PluginOptions {
|
||||
@@ -222,25 +223,49 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
|
||||
// normalize string configs to be case insensitive
|
||||
value = value.toLowerCase();
|
||||
}
|
||||
if (key === 'environment') {
|
||||
const environmentResult = parseEnvironmentConfig(value);
|
||||
if (environmentResult.isErr()) {
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason:
|
||||
'Error in validating environment config. This is an advanced setting and not meant to be used directly',
|
||||
description: environmentResult.unwrapErr().toString(),
|
||||
suggestions: null,
|
||||
loc: null,
|
||||
});
|
||||
if (isCompilerFlag(key)) {
|
||||
switch (key) {
|
||||
case 'environment': {
|
||||
const environmentResult = parseEnvironmentConfig(value);
|
||||
if (environmentResult.isErr()) {
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason:
|
||||
'Error in validating environment config. This is an advanced setting and not meant to be used directly',
|
||||
description: environmentResult.unwrapErr().toString(),
|
||||
suggestions: null,
|
||||
loc: null,
|
||||
});
|
||||
}
|
||||
parsedOptions[key] = environmentResult.unwrap();
|
||||
break;
|
||||
}
|
||||
case 'target': {
|
||||
parsedOptions[key] = parseTargetConfig(value);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
parsedOptions[key] = value;
|
||||
}
|
||||
}
|
||||
parsedOptions[key] = environmentResult.unwrap();
|
||||
} else if (isCompilerFlag(key)) {
|
||||
parsedOptions[key] = value;
|
||||
}
|
||||
}
|
||||
return {...defaultOptions, ...parsedOptions};
|
||||
}
|
||||
|
||||
export function parseTargetConfig(value: unknown): CompilerReactTarget {
|
||||
const parsed = CompilerReactTargetSchema.safeParse(value);
|
||||
if (parsed.success) {
|
||||
return parsed.data;
|
||||
} else {
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason: 'Not a valid target',
|
||||
description: `${fromZodError(parsed.error)}`,
|
||||
suggestions: null,
|
||||
loc: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function isCompilerFlag(s: string): s is keyof PluginOptions {
|
||||
return hasOwnProperty(defaultOptions, s);
|
||||
}
|
||||
|
||||
@@ -298,7 +298,6 @@ export function compileProgram(
|
||||
return;
|
||||
}
|
||||
const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
|
||||
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
|
||||
|
||||
/*
|
||||
* Record lint errors and critical errors as depending on Forget's config,
|
||||
@@ -605,7 +604,7 @@ export function compileProgram(
|
||||
if (needsMemoCacheFunctionImport) {
|
||||
updateMemoCacheFunctionImport(
|
||||
program,
|
||||
moduleName,
|
||||
getReactCompilerRuntimeModule(pass.opts),
|
||||
useMemoCacheIdentifier.name,
|
||||
);
|
||||
}
|
||||
@@ -638,8 +637,12 @@ function shouldSkipCompilation(
|
||||
}
|
||||
}
|
||||
|
||||
const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';
|
||||
if (hasMemoCacheFunctionImport(program, moduleName)) {
|
||||
if (
|
||||
hasMemoCacheFunctionImport(
|
||||
program,
|
||||
getReactCompilerRuntimeModule(pass.opts),
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -1126,3 +1129,31 @@ function checkFunctionReferencedBeforeDeclarationAtTopLevel(
|
||||
|
||||
return errors.details.length > 0 ? errors : null;
|
||||
}
|
||||
|
||||
type ReactCompilerRuntimeModule =
|
||||
| 'react/compiler-runtime' // from react namespace
|
||||
| 'react-compiler-runtime'; // npm package
|
||||
function getReactCompilerRuntimeModule(
|
||||
opts: PluginOptions,
|
||||
): ReactCompilerRuntimeModule {
|
||||
let moduleName: ReactCompilerRuntimeModule | null = null;
|
||||
switch (opts.target) {
|
||||
case '17':
|
||||
case '18': {
|
||||
moduleName = 'react-compiler-runtime';
|
||||
break;
|
||||
}
|
||||
case '19': {
|
||||
moduleName = 'react/compiler-runtime';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
CompilerError.invariant(moduleName != null, {
|
||||
reason: 'Expected target to already be validated',
|
||||
description: null,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
return moduleName;
|
||||
}
|
||||
|
||||
+271
-105
@@ -1,6 +1,13 @@
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {inRange} from '../ReactiveScopes/InferReactiveScopeVariables';
|
||||
import {Set_intersect, Set_union, getOrInsertDefault} from '../Utils/utils';
|
||||
import {
|
||||
Set_equal,
|
||||
Set_filter,
|
||||
Set_intersect,
|
||||
Set_union,
|
||||
getOrInsertDefault,
|
||||
} from '../Utils/utils';
|
||||
import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies';
|
||||
import {
|
||||
BasicBlock,
|
||||
BlockId,
|
||||
@@ -10,14 +17,16 @@ import {
|
||||
Identifier,
|
||||
IdentifierId,
|
||||
InstructionId,
|
||||
InstructionValue,
|
||||
ReactiveScopeDependency,
|
||||
ScopeId,
|
||||
} from './HIR';
|
||||
import {collectTemporariesSidemap} from './PropagateScopeDependenciesHIR';
|
||||
|
||||
/**
|
||||
* Helper function for `PropagateScopeDependencies`.
|
||||
* Uses control flow graph analysis to determine which `Identifier`s can
|
||||
* be assumed to be non-null objects, on a per-block basis.
|
||||
* Helper function for `PropagateScopeDependencies`. Uses control flow graph
|
||||
* analysis to determine which `Identifier`s can be assumed to be non-null
|
||||
* objects, on a per-block basis.
|
||||
*
|
||||
* Here is an example:
|
||||
* ```js
|
||||
@@ -42,15 +51,16 @@ import {
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Note that we currently do NOT account for mutable / declaration range
|
||||
* when doing the CFG-based traversal, producing results that are technically
|
||||
* Note that we currently do NOT account for mutable / declaration range when
|
||||
* doing the CFG-based traversal, producing results that are technically
|
||||
* incorrect but filtered by PropagateScopeDeps (which only takes dependencies
|
||||
* on constructed value -- i.e. a scope's dependencies must have mutable ranges
|
||||
* ending earlier than the scope start).
|
||||
*
|
||||
* Take this example, this function will infer x.foo.bar as non-nullable for bb0,
|
||||
* via the intersection of bb1 & bb2 which in turn comes from bb3. This is technically
|
||||
* incorrect bb0 is before / during x's mutable range.
|
||||
* Take this example, this function will infer x.foo.bar as non-nullable for
|
||||
* bb0, via the intersection of bb1 & bb2 which in turn comes from bb3. This is
|
||||
* technically incorrect bb0 is before / during x's mutable range.
|
||||
* ```
|
||||
* bb0:
|
||||
* const x = ...;
|
||||
* if cond then bb1 else bb2
|
||||
@@ -62,27 +72,71 @@ import {
|
||||
* goto bb3:
|
||||
* bb3:
|
||||
* x.foo.bar
|
||||
* ```
|
||||
*
|
||||
* @param fn
|
||||
* @param temporaries sidemap of identifier -> baseObject.a.b paths. Does not
|
||||
* contain optional chains.
|
||||
* @param hoistableFromOptionals sidemap of optionalBlock -> baseObject?.a
|
||||
* optional paths for which it's safe to evaluate non-optional loads (see
|
||||
* CollectOptionalChainDependencies).
|
||||
* @returns
|
||||
*/
|
||||
export function collectHoistablePropertyLoads(
|
||||
fn: HIRFunction,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
): ReadonlyMap<ScopeId, BlockInfo> {
|
||||
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>,
|
||||
nestedFnImmutableContext: ReadonlySet<IdentifierId> | null,
|
||||
): ReadonlyMap<BlockId, BlockInfo> {
|
||||
const registry = new PropertyPathRegistry();
|
||||
|
||||
const nodes = collectNonNullsInBlocks(fn, temporaries, registry);
|
||||
propagateNonNull(fn, nodes);
|
||||
const functionExpressionLoads = collectFunctionExpressionFakeLoads(fn);
|
||||
const actuallyEvaluatedTemporaries = new Map(
|
||||
[...temporaries].filter(([id]) => !functionExpressionLoads.has(id)),
|
||||
);
|
||||
|
||||
const nodesKeyedByScopeId = new Map<ScopeId, BlockInfo>();
|
||||
/**
|
||||
* 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
|
||||
* mutable range and scope.
|
||||
* (see `destructure-array-declaration-to-context-var` fixture)
|
||||
* We track known immutable identifiers to reduce regressions (as PropagateScopeDeps
|
||||
* is being rewritten to HIR).
|
||||
*/
|
||||
const knownImmutableIdentifiers = new Set<IdentifierId>();
|
||||
if (fn.fnType === 'Component' || fn.fnType === 'Hook') {
|
||||
for (const p of fn.params) {
|
||||
if (p.kind === 'Identifier') {
|
||||
knownImmutableIdentifiers.add(p.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
const nodes = collectNonNullsInBlocks(fn, {
|
||||
temporaries: actuallyEvaluatedTemporaries,
|
||||
knownImmutableIdentifiers,
|
||||
hoistableFromOptionals,
|
||||
registry,
|
||||
nestedFnImmutableContext,
|
||||
});
|
||||
propagateNonNull(fn, nodes, registry);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
export function keyByScopeId<T>(
|
||||
fn: HIRFunction,
|
||||
source: ReadonlyMap<BlockId, T>,
|
||||
): ReadonlyMap<ScopeId, T> {
|
||||
const keyedByScopeId = new Map<ScopeId, T>();
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
if (block.terminal.kind === 'scope') {
|
||||
nodesKeyedByScopeId.set(
|
||||
keyedByScopeId.set(
|
||||
block.terminal.scope.id,
|
||||
nodes.get(block.terminal.block)!,
|
||||
source.get(block.terminal.block)!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return nodesKeyedByScopeId;
|
||||
return keyedByScopeId;
|
||||
}
|
||||
|
||||
export type BlockInfo = {
|
||||
@@ -96,17 +150,21 @@ export type BlockInfo = {
|
||||
*/
|
||||
type RootNode = {
|
||||
properties: Map<string, PropertyPathNode>;
|
||||
optionalProperties: Map<string, PropertyPathNode>;
|
||||
parent: null;
|
||||
// Recorded to make later computations simpler
|
||||
fullPath: ReactiveScopeDependency;
|
||||
hasOptional: boolean;
|
||||
root: IdentifierId;
|
||||
};
|
||||
|
||||
type PropertyPathNode =
|
||||
| {
|
||||
properties: Map<string, PropertyPathNode>;
|
||||
optionalProperties: Map<string, PropertyPathNode>;
|
||||
parent: PropertyPathNode;
|
||||
fullPath: ReactiveScopeDependency;
|
||||
hasOptional: boolean;
|
||||
}
|
||||
| RootNode;
|
||||
|
||||
@@ -124,10 +182,12 @@ class PropertyPathRegistry {
|
||||
rootNode = {
|
||||
root: identifier.id,
|
||||
properties: new Map(),
|
||||
optionalProperties: new Map(),
|
||||
fullPath: {
|
||||
identifier,
|
||||
path: [],
|
||||
},
|
||||
hasOptional: false,
|
||||
parent: null,
|
||||
};
|
||||
this.roots.set(identifier.id, rootNode);
|
||||
@@ -139,23 +199,20 @@ class PropertyPathRegistry {
|
||||
parent: PropertyPathNode,
|
||||
entry: DependencyPathEntry,
|
||||
): PropertyPathNode {
|
||||
if (entry.optional) {
|
||||
CompilerError.throwTodo({
|
||||
reason: 'handle optional nodes',
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
}
|
||||
let child = parent.properties.get(entry.property);
|
||||
const map = entry.optional ? parent.optionalProperties : parent.properties;
|
||||
let child = map.get(entry.property);
|
||||
if (child == null) {
|
||||
child = {
|
||||
properties: new Map(),
|
||||
optionalProperties: new Map(),
|
||||
parent: parent,
|
||||
fullPath: {
|
||||
identifier: parent.fullPath.identifier,
|
||||
path: parent.fullPath.path.concat(entry),
|
||||
},
|
||||
hasOptional: parent.hasOptional || entry.optional,
|
||||
};
|
||||
parent.properties.set(entry.property, child);
|
||||
map.set(entry.property, child);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
@@ -184,56 +241,77 @@ class PropertyPathRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
function addNonNullPropertyPath(
|
||||
source: Identifier,
|
||||
sourceNode: PropertyPathNode,
|
||||
instrId: InstructionId,
|
||||
knownImmutableIdentifiers: Set<IdentifierId>,
|
||||
result: Set<PropertyPathNode>,
|
||||
): void {
|
||||
/**
|
||||
* Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
|
||||
* are not valid with respect to current instruction id numbering.
|
||||
* We use attached reactive scope ranges as a proxy for mutable range, but this
|
||||
* is an overestimate as (1) scope ranges merge and align to form valid program
|
||||
* blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
|
||||
* non-mutable identifiers.
|
||||
*
|
||||
* See comment at top of function for why we track known immutable identifiers.
|
||||
*/
|
||||
const isMutableAtInstr =
|
||||
source.mutableRange.end > source.mutableRange.start + 1 &&
|
||||
source.scope != null &&
|
||||
inRange({id: instrId}, source.scope.range);
|
||||
if (
|
||||
!isMutableAtInstr ||
|
||||
knownImmutableIdentifiers.has(sourceNode.fullPath.identifier.id)
|
||||
) {
|
||||
result.add(sourceNode);
|
||||
function getMaybeNonNullInInstruction(
|
||||
instr: InstructionValue,
|
||||
context: CollectNonNullsInBlocksContext,
|
||||
): PropertyPathNode | null {
|
||||
let path = null;
|
||||
if (instr.kind === 'PropertyLoad') {
|
||||
path = context.temporaries.get(instr.object.identifier.id) ?? {
|
||||
identifier: instr.object.identifier,
|
||||
path: [],
|
||||
};
|
||||
} else if (instr.kind === 'Destructure') {
|
||||
path = context.temporaries.get(instr.value.identifier.id) ?? null;
|
||||
} else if (instr.kind === 'ComputedLoad') {
|
||||
path = context.temporaries.get(instr.object.identifier.id) ?? null;
|
||||
}
|
||||
return path != null ? context.registry.getOrCreateProperty(path) : null;
|
||||
}
|
||||
|
||||
function isImmutableAtInstr(
|
||||
identifier: Identifier,
|
||||
instr: InstructionId,
|
||||
context: CollectNonNullsInBlocksContext,
|
||||
): boolean {
|
||||
if (context.nestedFnImmutableContext != null) {
|
||||
/**
|
||||
* Comparing instructions ids across inner-outer function bodies is not valid, as they are numbered
|
||||
*/
|
||||
return context.nestedFnImmutableContext.has(identifier.id);
|
||||
} else {
|
||||
/**
|
||||
* Since this runs *after* buildReactiveScopeTerminals, identifier mutable ranges
|
||||
* are not valid with respect to current instruction id numbering.
|
||||
* We use attached reactive scope ranges as a proxy for mutable range, but this
|
||||
* is an overestimate as (1) scope ranges merge and align to form valid program
|
||||
* blocks and (2) passes like MemoizeFbtAndMacroOperands may assign scopes to
|
||||
* non-mutable identifiers.
|
||||
*
|
||||
* See comment in exported function for why we track known immutable identifiers.
|
||||
*/
|
||||
const mutableAtInstr =
|
||||
identifier.mutableRange.end > identifier.mutableRange.start + 1 &&
|
||||
identifier.scope != null &&
|
||||
inRange(
|
||||
{
|
||||
id: instr,
|
||||
},
|
||||
identifier.scope.range,
|
||||
);
|
||||
return (
|
||||
!mutableAtInstr || context.knownImmutableIdentifiers.has(identifier.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type CollectNonNullsInBlocksContext = {
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
|
||||
knownImmutableIdentifiers: ReadonlySet<IdentifierId>;
|
||||
hoistableFromOptionals: ReadonlyMap<BlockId, ReactiveScopeDependency>;
|
||||
registry: PropertyPathRegistry;
|
||||
/**
|
||||
* (For nested / inner function declarations)
|
||||
* Context variables (i.e. captured from an outer scope) that are immutable.
|
||||
* Note that this technically could be merged into `knownImmutableIdentifiers`,
|
||||
* but are currently kept separate for readability.
|
||||
*/
|
||||
nestedFnImmutableContext: ReadonlySet<IdentifierId> | null;
|
||||
};
|
||||
function collectNonNullsInBlocks(
|
||||
fn: HIRFunction,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
registry: PropertyPathRegistry,
|
||||
context: CollectNonNullsInBlocksContext,
|
||||
): ReadonlyMap<BlockId, BlockInfo> {
|
||||
/**
|
||||
* 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
|
||||
* mutable range and scope.
|
||||
* (see `destructure-array-declaration-to-context-var` fixture)
|
||||
* We track known immutable identifiers to reduce regressions (as PropagateScopeDeps
|
||||
* is being rewritten to HIR).
|
||||
*/
|
||||
const knownImmutableIdentifiers = new Set<IdentifierId>();
|
||||
if (fn.fnType === 'Component' || fn.fnType === 'Hook') {
|
||||
for (const p of fn.params) {
|
||||
if (p.kind === 'Identifier') {
|
||||
knownImmutableIdentifiers.add(p.identifier.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Known non-null objects such as functional component props can be safely
|
||||
* read from any block.
|
||||
@@ -245,49 +323,58 @@ function collectNonNullsInBlocks(
|
||||
fn.params[0].kind === 'Identifier'
|
||||
) {
|
||||
const identifier = fn.params[0].identifier;
|
||||
knownNonNullIdentifiers.add(registry.getOrCreateIdentifier(identifier));
|
||||
knownNonNullIdentifiers.add(
|
||||
context.registry.getOrCreateIdentifier(identifier),
|
||||
);
|
||||
}
|
||||
const nodes = new Map<BlockId, BlockInfo>();
|
||||
for (const [_, block] of fn.body.blocks) {
|
||||
const assumedNonNullObjects = new Set<PropertyPathNode>(
|
||||
knownNonNullIdentifiers,
|
||||
);
|
||||
|
||||
const maybeOptionalChain = context.hoistableFromOptionals.get(block.id);
|
||||
if (maybeOptionalChain != null) {
|
||||
assumedNonNullObjects.add(
|
||||
context.registry.getOrCreateProperty(maybeOptionalChain),
|
||||
);
|
||||
}
|
||||
for (const instr of block.instructions) {
|
||||
if (instr.value.kind === 'PropertyLoad') {
|
||||
const source = temporaries.get(instr.value.object.identifier.id) ?? {
|
||||
identifier: instr.value.object.identifier,
|
||||
path: [],
|
||||
};
|
||||
addNonNullPropertyPath(
|
||||
instr.value.object.identifier,
|
||||
registry.getOrCreateProperty(source),
|
||||
instr.id,
|
||||
knownImmutableIdentifiers,
|
||||
assumedNonNullObjects,
|
||||
const maybeNonNull = getMaybeNonNullInInstruction(instr.value, context);
|
||||
if (
|
||||
maybeNonNull != null &&
|
||||
isImmutableAtInstr(maybeNonNull.fullPath.identifier, instr.id, context)
|
||||
) {
|
||||
assumedNonNullObjects.add(maybeNonNull);
|
||||
}
|
||||
if (
|
||||
instr.value.kind === 'FunctionExpression' &&
|
||||
!fn.env.config.enableTreatFunctionDepsAsConditional
|
||||
) {
|
||||
const innerFn = instr.value.loweredFunc;
|
||||
const innerTemporaries = collectTemporariesSidemap(
|
||||
innerFn.func,
|
||||
new Set(),
|
||||
);
|
||||
} else if (instr.value.kind === 'Destructure') {
|
||||
const source = instr.value.value.identifier.id;
|
||||
const sourceNode = temporaries.get(source);
|
||||
if (sourceNode != null) {
|
||||
addNonNullPropertyPath(
|
||||
instr.value.value.identifier,
|
||||
registry.getOrCreateProperty(sourceNode),
|
||||
instr.id,
|
||||
knownImmutableIdentifiers,
|
||||
assumedNonNullObjects,
|
||||
);
|
||||
}
|
||||
} else if (instr.value.kind === 'ComputedLoad') {
|
||||
const source = instr.value.object.identifier.id;
|
||||
const sourceNode = temporaries.get(source);
|
||||
if (sourceNode != null) {
|
||||
addNonNullPropertyPath(
|
||||
instr.value.object.identifier,
|
||||
registry.getOrCreateProperty(sourceNode),
|
||||
instr.id,
|
||||
knownImmutableIdentifiers,
|
||||
assumedNonNullObjects,
|
||||
);
|
||||
const innerOptionals = collectOptionalChainSidemap(innerFn.func);
|
||||
const innerHoistableMap = collectHoistablePropertyLoads(
|
||||
innerFn.func,
|
||||
innerTemporaries,
|
||||
innerOptionals.hoistableObjects,
|
||||
context.nestedFnImmutableContext ??
|
||||
new Set(
|
||||
innerFn.func.context
|
||||
.filter(place =>
|
||||
isImmutableAtInstr(place.identifier, instr.id, context),
|
||||
)
|
||||
.map(place => place.identifier.id),
|
||||
),
|
||||
);
|
||||
const innerHoistables = assertNonNull(
|
||||
innerHoistableMap.get(innerFn.func.body.entry),
|
||||
);
|
||||
for (const entry of innerHoistables.assumedNonNullObjects) {
|
||||
assumedNonNullObjects.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -303,6 +390,7 @@ function collectNonNullsInBlocks(
|
||||
function propagateNonNull(
|
||||
fn: HIRFunction,
|
||||
nodes: ReadonlyMap<BlockId, BlockInfo>,
|
||||
registry: PropertyPathRegistry,
|
||||
): void {
|
||||
const blockSuccessors = new Map<BlockId, Set<BlockId>>();
|
||||
const terminalPreds = new Set<BlockId>();
|
||||
@@ -388,10 +476,17 @@ function propagateNonNull(
|
||||
|
||||
const prevObjects = assertNonNull(nodes.get(nodeId)).assumedNonNullObjects;
|
||||
const mergedObjects = Set_union(prevObjects, neighborAccesses);
|
||||
reduceMaybeOptionalChains(mergedObjects, registry);
|
||||
|
||||
assertNonNull(nodes.get(nodeId)).assumedNonNullObjects = mergedObjects;
|
||||
traversalState.set(nodeId, 'done');
|
||||
changed ||= prevObjects.size !== mergedObjects.size;
|
||||
/**
|
||||
* Note that it's not sufficient to compare set sizes since
|
||||
* reduceMaybeOptionalChains may replace optional-chain loads with
|
||||
* unconditional loads. This could in turn change `assumedNonNullObjects` of
|
||||
* downstream blocks and backedges.
|
||||
*/
|
||||
changed ||= !Set_equal(prevObjects, mergedObjects);
|
||||
return changed;
|
||||
}
|
||||
const traversalState = new Map<BlockId, 'done' | 'active'>();
|
||||
@@ -440,3 +535,74 @@ export function assertNonNull<T extends NonNullable<U>, U>(
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any two optional chains with different operations . vs ?. but the same set of
|
||||
* property strings paths de-duplicates.
|
||||
*
|
||||
* Intuitively: given <base>?.b, we know <base> to be either hoistable or not.
|
||||
* If unconditional reads from <base> are hoistable, we can replace all
|
||||
* <base>?.PROPERTY_STRING subpaths with <base>.PROPERTY_STRING
|
||||
*/
|
||||
function reduceMaybeOptionalChains(
|
||||
nodes: Set<PropertyPathNode>,
|
||||
registry: PropertyPathRegistry,
|
||||
): void {
|
||||
let optionalChainNodes = Set_filter(nodes, n => n.hasOptional);
|
||||
if (optionalChainNodes.size === 0) {
|
||||
return;
|
||||
}
|
||||
let changed: boolean;
|
||||
do {
|
||||
changed = false;
|
||||
|
||||
for (const original of optionalChainNodes) {
|
||||
let {identifier, path: origPath} = original.fullPath;
|
||||
let currNode: PropertyPathNode =
|
||||
registry.getOrCreateIdentifier(identifier);
|
||||
for (let i = 0; i < origPath.length; i++) {
|
||||
const entry = origPath[i];
|
||||
// If the base is known to be non-null, replace with a non-optional load
|
||||
const nextEntry: DependencyPathEntry =
|
||||
entry.optional && nodes.has(currNode)
|
||||
? {property: entry.property, optional: false}
|
||||
: entry;
|
||||
currNode = PropertyPathRegistry.getOrCreatePropertyEntry(
|
||||
currNode,
|
||||
nextEntry,
|
||||
);
|
||||
}
|
||||
if (currNode !== original) {
|
||||
changed = true;
|
||||
optionalChainNodes.delete(original);
|
||||
optionalChainNodes.add(currNode);
|
||||
nodes.delete(original);
|
||||
nodes.add(currNode);
|
||||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
+382
@@ -0,0 +1,382 @@
|
||||
import {CompilerError} from '..';
|
||||
import {assertNonNull} from './CollectHoistablePropertyLoads';
|
||||
import {
|
||||
BlockId,
|
||||
BasicBlock,
|
||||
InstructionId,
|
||||
IdentifierId,
|
||||
ReactiveScopeDependency,
|
||||
BranchTerminal,
|
||||
TInstruction,
|
||||
PropertyLoad,
|
||||
StoreLocal,
|
||||
GotoVariant,
|
||||
TBasicBlock,
|
||||
OptionalTerminal,
|
||||
HIRFunction,
|
||||
DependencyPathEntry,
|
||||
} from './HIR';
|
||||
import {printIdentifier} from './PrintHIR';
|
||||
|
||||
export function collectOptionalChainSidemap(
|
||||
fn: HIRFunction,
|
||||
): OptionalChainSidemap {
|
||||
const context: OptionalTraversalContext = {
|
||||
blocks: fn.body.blocks,
|
||||
seenOptionals: new Set(),
|
||||
processedInstrsInOptional: new Set(),
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
temporariesReadInOptional: context.temporariesReadInOptional,
|
||||
processedInstrsInOptional: context.processedInstrsInOptional,
|
||||
hoistableObjects: context.hoistableObjects,
|
||||
};
|
||||
}
|
||||
export type OptionalChainSidemap = {
|
||||
/**
|
||||
* Stores the correct property mapping (e.g. `a?.b` instead of `a.b`) for
|
||||
* dependency calculation. Note that we currently do not store anything on
|
||||
* outer phi nodes.
|
||||
*/
|
||||
temporariesReadInOptional: ReadonlyMap<IdentifierId, ReactiveScopeDependency>;
|
||||
/**
|
||||
* Records instructions (PropertyLoads, StoreLocals, and test terminals)
|
||||
* processed in this pass. When extracting dependencies in
|
||||
* PropagateScopeDependencies, these instructions are skipped.
|
||||
*
|
||||
* E.g. given a?.b
|
||||
* ```
|
||||
* bb0
|
||||
* $0 = LoadLocal 'a'
|
||||
* test $0 then=bb1 <- Avoid adding dependencies from these instructions, as
|
||||
* bb1 the sidemap produced by readOptionalBlock already maps
|
||||
* $1 = PropertyLoad $0.'b' <- $1 and $2 back to a?.b. Instead, we want to add a?.b
|
||||
* StoreLocal $2 = $1 <- as a dependency when $1 or $2 are later used in either
|
||||
* - an unhoistable expression within an outer optional
|
||||
* block e.g. MethodCall
|
||||
* - a phi node (if the entire optional value is hoistable)
|
||||
* ```
|
||||
*
|
||||
* Note that mapping blockIds to their evaluated dependency path does not
|
||||
* work, since values produced by inner optional chains may be referenced in
|
||||
* outer ones
|
||||
* ```
|
||||
* a?.b.c()
|
||||
* ->
|
||||
* bb0
|
||||
* $0 = LoadLocal 'a'
|
||||
* test $0 then=bb1
|
||||
* bb1
|
||||
* $1 = PropertyLoad $0.'b'
|
||||
* StoreLocal $2 = $1
|
||||
* goto bb2
|
||||
* bb2
|
||||
* test $2 then=bb3
|
||||
* bb3:
|
||||
* $3 = PropertyLoad $2.'c'
|
||||
* StoreLocal $4 = $3
|
||||
* goto bb4
|
||||
* bb4
|
||||
* test $4 then=bb5
|
||||
* bb5:
|
||||
* $5 = MethodCall $2.$4() <--- here, we want to take a dep on $2 and $4!
|
||||
* ```
|
||||
*/
|
||||
processedInstrsInOptional: 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
|
||||
* the optional terminal in bb1.
|
||||
* ```js
|
||||
* bb1:
|
||||
* ...
|
||||
* Optional optional=false test=bb2 fallth=...
|
||||
* bb2:
|
||||
* Optional optional=true test=bb3 fallth=...
|
||||
* ...
|
||||
* ```
|
||||
*/
|
||||
hoistableObjects: ReadonlyMap<BlockId, ReactiveScopeDependency>;
|
||||
};
|
||||
|
||||
type OptionalTraversalContext = {
|
||||
blocks: ReadonlyMap<BlockId, BasicBlock>;
|
||||
|
||||
// Track optional blocks to avoid outer calls into nested optionals
|
||||
seenOptionals: Set<BlockId>;
|
||||
|
||||
processedInstrsInOptional: Set<InstructionId>;
|
||||
temporariesReadInOptional: Map<IdentifierId, ReactiveScopeDependency>;
|
||||
hoistableObjects: Map<BlockId, ReactiveScopeDependency>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Match the consequent and alternate blocks of an optional.
|
||||
* @returns propertyload computed by the consequent block, or null if the
|
||||
* consequent block is not a simple PropertyLoad.
|
||||
*/
|
||||
function matchOptionalTestBlock(
|
||||
terminal: BranchTerminal,
|
||||
blocks: ReadonlyMap<BlockId, BasicBlock>,
|
||||
): {
|
||||
consequentId: IdentifierId;
|
||||
property: string;
|
||||
propertyId: IdentifierId;
|
||||
storeLocalInstrId: InstructionId;
|
||||
consequentGoto: BlockId;
|
||||
} | null {
|
||||
const consequentBlock = assertNonNull(blocks.get(terminal.consequent));
|
||||
if (
|
||||
consequentBlock.instructions.length === 2 &&
|
||||
consequentBlock.instructions[0].value.kind === 'PropertyLoad' &&
|
||||
consequentBlock.instructions[1].value.kind === 'StoreLocal'
|
||||
) {
|
||||
const propertyLoad: TInstruction<PropertyLoad> = consequentBlock
|
||||
.instructions[0] as TInstruction<PropertyLoad>;
|
||||
const storeLocal: StoreLocal = consequentBlock.instructions[1].value;
|
||||
const storeLocalInstrId = consequentBlock.instructions[1].id;
|
||||
CompilerError.invariant(
|
||||
propertyLoad.value.object.identifier.id === terminal.test.identifier.id,
|
||||
{
|
||||
reason:
|
||||
'[OptionalChainDeps] Inconsistent optional chaining property load',
|
||||
description: `Test=${printIdentifier(terminal.test.identifier)} PropertyLoad base=${printIdentifier(propertyLoad.value.object.identifier)}`,
|
||||
loc: propertyLoad.loc,
|
||||
},
|
||||
);
|
||||
|
||||
CompilerError.invariant(
|
||||
storeLocal.value.identifier.id === propertyLoad.lvalue.identifier.id,
|
||||
{
|
||||
reason: '[OptionalChainDeps] Unexpected storeLocal',
|
||||
loc: propertyLoad.loc,
|
||||
},
|
||||
);
|
||||
if (
|
||||
consequentBlock.terminal.kind !== 'goto' ||
|
||||
consequentBlock.terminal.variant !== GotoVariant.Break
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const alternate = assertNonNull(blocks.get(terminal.alternate));
|
||||
|
||||
CompilerError.invariant(
|
||||
alternate.instructions.length === 2 &&
|
||||
alternate.instructions[0].value.kind === 'Primitive' &&
|
||||
alternate.instructions[1].value.kind === 'StoreLocal',
|
||||
{
|
||||
reason: 'Unexpected alternate structure',
|
||||
loc: terminal.loc,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
consequentId: storeLocal.lvalue.place.identifier.id,
|
||||
property: propertyLoad.value.property,
|
||||
propertyId: propertyLoad.lvalue.identifier.id,
|
||||
storeLocalInstrId,
|
||||
consequentGoto: consequentBlock.terminal.block,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse into the optional block and all transitively referenced blocks to
|
||||
* collect sidemaps of optional chain dependencies.
|
||||
*
|
||||
* @returns the IdentifierId representing the optional block if the block and
|
||||
* all transitively referenced optional blocks precisely represent a chain of
|
||||
* property loads. If any part of the optional chain is not hoistable, returns
|
||||
* null.
|
||||
*/
|
||||
function traverseOptionalBlock(
|
||||
optional: TBasicBlock<OptionalTerminal>,
|
||||
context: OptionalTraversalContext,
|
||||
outerAlternate: BlockId | null,
|
||||
): IdentifierId | null {
|
||||
context.seenOptionals.add(optional.id);
|
||||
const maybeTest = context.blocks.get(optional.terminal.test)!;
|
||||
let test: BranchTerminal;
|
||||
let baseObject: ReactiveScopeDependency;
|
||||
if (maybeTest.terminal.kind === 'branch') {
|
||||
CompilerError.invariant(optional.terminal.optional, {
|
||||
reason: '[OptionalChainDeps] Expect base case to be always optional',
|
||||
loc: optional.terminal.loc,
|
||||
});
|
||||
/**
|
||||
* Optional base expressions are currently within value blocks which cannot
|
||||
* be interrupted by scope boundaries. As such, the only dependencies we can
|
||||
* hoist out of optional chains are property load chains with no intervening
|
||||
* instructions.
|
||||
*
|
||||
* Ideally, we would be able to flatten base instructions out of optional
|
||||
* blocks, but this would require changes to HIR.
|
||||
*
|
||||
* For now, only match base expressions that are straightforward
|
||||
* PropertyLoad chains
|
||||
*/
|
||||
if (
|
||||
maybeTest.instructions.length === 0 ||
|
||||
maybeTest.instructions[0].value.kind !== 'LoadLocal'
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const path: Array<DependencyPathEntry> = [];
|
||||
for (let i = 1; i < maybeTest.instructions.length; i++) {
|
||||
const instrVal = maybeTest.instructions[i].value;
|
||||
const prevInstr = maybeTest.instructions[i - 1];
|
||||
if (
|
||||
instrVal.kind === 'PropertyLoad' &&
|
||||
instrVal.object.identifier.id === prevInstr.lvalue.identifier.id
|
||||
) {
|
||||
path.push({property: instrVal.property, optional: false});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
CompilerError.invariant(
|
||||
maybeTest.terminal.test.identifier.id ===
|
||||
maybeTest.instructions.at(-1)!.lvalue.identifier.id,
|
||||
{
|
||||
reason: '[OptionalChainDeps] Unexpected test expression',
|
||||
loc: maybeTest.terminal.loc,
|
||||
},
|
||||
);
|
||||
baseObject = {
|
||||
identifier: maybeTest.instructions[0].value.place.identifier,
|
||||
path,
|
||||
};
|
||||
test = maybeTest.terminal;
|
||||
} else if (maybeTest.terminal.kind === 'optional') {
|
||||
/**
|
||||
* This is either
|
||||
* - <inner_optional>?.property (optional=true)
|
||||
* - <inner_optional>.property (optional=false)
|
||||
* - <inner_optional> <other operation>
|
||||
* - a optional base block with a separate nested optional-chain (e.g. a(c?.d)?.d)
|
||||
*/
|
||||
const testBlock = context.blocks.get(maybeTest.terminal.fallthrough)!;
|
||||
if (testBlock!.terminal.kind !== 'branch') {
|
||||
/**
|
||||
* Fallthrough of the inner optional should be a block with no
|
||||
* instructions, terminating with Test($<temporary written to from
|
||||
* StoreLocal>)
|
||||
*/
|
||||
CompilerError.throwTodo({
|
||||
reason: `Unexpected terminal kind \`${testBlock.terminal.kind}\` for optional fallthrough block`,
|
||||
loc: maybeTest.terminal.loc,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Recurse into inner optional blocks to collect inner optional-chain
|
||||
* expressions, regardless of whether we can match the outer one to a
|
||||
* PropertyLoad.
|
||||
*/
|
||||
const innerOptional = traverseOptionalBlock(
|
||||
maybeTest as TBasicBlock<OptionalTerminal>,
|
||||
context,
|
||||
testBlock.terminal.alternate,
|
||||
);
|
||||
if (innerOptional == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the inner optional is part of the same optional-chain as the
|
||||
* outer one. This is not guaranteed, e.g. given a(c?.d)?.d
|
||||
* ```
|
||||
* bb0:
|
||||
* Optional test=bb1
|
||||
* bb1:
|
||||
* $0 = LoadLocal a <-- part 1 of the outer optional-chaining base
|
||||
* Optional test=bb2 fallth=bb5 <-- start of optional chain for c?.d
|
||||
* bb2:
|
||||
* ... (optional chain for c?.d)
|
||||
* ...
|
||||
* bb5:
|
||||
* $1 = phi(c.d, undefined) <-- part 2 (continuation) of the outer optional-base
|
||||
* $2 = Call $0($1)
|
||||
* Branch $2 ...
|
||||
* ```
|
||||
*/
|
||||
if (testBlock.terminal.test.identifier.id !== innerOptional) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!optional.terminal.optional) {
|
||||
/**
|
||||
* If this is an non-optional load participating in an optional chain
|
||||
* (e.g. loading the `c` property in `a?.b.c`), record that PropertyLoads
|
||||
* from the inner optional value are hoistable.
|
||||
*/
|
||||
context.hoistableObjects.set(
|
||||
optional.id,
|
||||
assertNonNull(context.temporariesReadInOptional.get(innerOptional)),
|
||||
);
|
||||
}
|
||||
baseObject = assertNonNull(
|
||||
context.temporariesReadInOptional.get(innerOptional),
|
||||
);
|
||||
test = testBlock.terminal;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (test.alternate === outerAlternate) {
|
||||
CompilerError.invariant(optional.instructions.length === 0, {
|
||||
reason:
|
||||
'[OptionalChainDeps] Unexpected instructions an inner optional block. ' +
|
||||
'This indicates that the compiler may be incorrectly concatenating two unrelated optional chains',
|
||||
loc: optional.terminal.loc,
|
||||
});
|
||||
}
|
||||
const matchConsequentResult = matchOptionalTestBlock(test, context.blocks);
|
||||
if (!matchConsequentResult) {
|
||||
// Optional chain consequent is not hoistable e.g. a?.[computed()]
|
||||
return null;
|
||||
}
|
||||
CompilerError.invariant(
|
||||
matchConsequentResult.consequentGoto === optional.terminal.fallthrough,
|
||||
{
|
||||
reason: '[OptionalChainDeps] Unexpected optional goto-fallthrough',
|
||||
description: `${matchConsequentResult.consequentGoto} != ${optional.terminal.fallthrough}`,
|
||||
loc: optional.terminal.loc,
|
||||
},
|
||||
);
|
||||
const load = {
|
||||
identifier: baseObject.identifier,
|
||||
path: [
|
||||
...baseObject.path,
|
||||
{
|
||||
property: matchConsequentResult.property,
|
||||
optional: optional.terminal.optional,
|
||||
},
|
||||
],
|
||||
};
|
||||
context.processedInstrsInOptional.add(
|
||||
matchConsequentResult.storeLocalInstrId,
|
||||
);
|
||||
context.processedInstrsInOptional.add(test.id);
|
||||
context.temporariesReadInOptional.set(
|
||||
matchConsequentResult.consequentId,
|
||||
load,
|
||||
);
|
||||
context.temporariesReadInOptional.set(matchConsequentResult.propertyId, load);
|
||||
return matchConsequentResult.consequentId;
|
||||
}
|
||||
+231
-144
@@ -6,97 +6,173 @@
|
||||
*/
|
||||
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {GeneratedSource, Identifier, ReactiveScopeDependency} from '../HIR';
|
||||
import {
|
||||
DependencyPathEntry,
|
||||
GeneratedSource,
|
||||
Identifier,
|
||||
ReactiveScopeDependency,
|
||||
} from '../HIR';
|
||||
import {printIdentifier} from '../HIR/PrintHIR';
|
||||
import {ReactiveScopePropertyDependency} from '../ReactiveScopes/DeriveMinimalDependencies';
|
||||
|
||||
const ENABLE_DEBUG_INVARIANTS = true;
|
||||
|
||||
/**
|
||||
* Simpler fork of DeriveMinimalDependencies, see PropagateScopeDependenciesHIR
|
||||
* for detailed explanation.
|
||||
*/
|
||||
export class ReactiveScopeDependencyTreeHIR {
|
||||
#roots: Map<Identifier, DependencyNode> = new Map();
|
||||
/**
|
||||
* Paths from which we can hoist PropertyLoads. If an `identifier`,
|
||||
* `identifier.path`, or `identifier?.path` is in this map, it is safe to
|
||||
* evaluate (non-optional) PropertyLoads from.
|
||||
*/
|
||||
#hoistableObjects: Map<Identifier, HoistableNode> = new Map();
|
||||
#deps: Map<Identifier, DependencyNode> = new Map();
|
||||
|
||||
#getOrCreateRoot(
|
||||
/**
|
||||
* @param hoistableObjects a set of paths from which we can safely evaluate
|
||||
* PropertyLoads. Note that we expect these to not contain duplicates (e.g.
|
||||
* both `a?.b` and `a.b`) only because CollectHoistablePropertyLoads merges
|
||||
* duplicates when traversing the CFG.
|
||||
*/
|
||||
constructor(hoistableObjects: Iterable<ReactiveScopeDependency>) {
|
||||
for (const {path, identifier} of hoistableObjects) {
|
||||
let currNode = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
|
||||
identifier,
|
||||
this.#hoistableObjects,
|
||||
path.length > 0 && path[0].optional ? 'Optional' : 'NonNull',
|
||||
);
|
||||
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const prevAccessType = currNode.properties.get(
|
||||
path[i].property,
|
||||
)?.accessType;
|
||||
const accessType =
|
||||
i + 1 < path.length && path[i + 1].optional ? 'Optional' : 'NonNull';
|
||||
CompilerError.invariant(
|
||||
prevAccessType == null || prevAccessType === accessType,
|
||||
{
|
||||
reason: 'Conflicting access types',
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
);
|
||||
let nextNode = currNode.properties.get(path[i].property);
|
||||
if (nextNode == null) {
|
||||
nextNode = {
|
||||
properties: new Map(),
|
||||
accessType,
|
||||
};
|
||||
currNode.properties.set(path[i].property, nextNode);
|
||||
}
|
||||
currNode = nextNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static #getOrCreateRoot<T extends string>(
|
||||
identifier: Identifier,
|
||||
accessType: PropertyAccessType,
|
||||
): DependencyNode {
|
||||
roots: Map<Identifier, TreeNode<T>>,
|
||||
defaultAccessType: T,
|
||||
): TreeNode<T> {
|
||||
// roots can always be accessed unconditionally in JS
|
||||
let rootNode = this.#roots.get(identifier);
|
||||
let rootNode = roots.get(identifier);
|
||||
|
||||
if (rootNode === undefined) {
|
||||
rootNode = {
|
||||
properties: new Map(),
|
||||
accessType,
|
||||
accessType: defaultAccessType,
|
||||
};
|
||||
this.#roots.set(identifier, rootNode);
|
||||
roots.set(identifier, rootNode);
|
||||
}
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join a dependency with `#hoistableObjects` to record the hoistable
|
||||
* dependency. This effectively truncates @param dep to its maximal
|
||||
* safe-to-evaluate subpath
|
||||
*/
|
||||
addDependency(dep: ReactiveScopePropertyDependency): void {
|
||||
const {path} = dep;
|
||||
let currNode = this.#getOrCreateRoot(dep.identifier, MIN_ACCESS_TYPE);
|
||||
|
||||
const accessType = PropertyAccessType.Access;
|
||||
|
||||
currNode.accessType = merge(currNode.accessType, accessType);
|
||||
|
||||
for (const property of path) {
|
||||
// all properties read 'on the way' to a dependency are marked as 'access'
|
||||
let currChild = makeOrMergeProperty(
|
||||
currNode,
|
||||
property.property,
|
||||
accessType,
|
||||
);
|
||||
currNode = currChild;
|
||||
}
|
||||
|
||||
/*
|
||||
* If this property does not have a conditional path (i.e. a.b.c), the
|
||||
* final property node should be marked as an conditional/unconditional
|
||||
* `dependency` as based on control flow.
|
||||
const {identifier, path} = dep;
|
||||
let depCursor = ReactiveScopeDependencyTreeHIR.#getOrCreateRoot(
|
||||
identifier,
|
||||
this.#deps,
|
||||
PropertyAccessType.UnconditionalAccess,
|
||||
);
|
||||
/**
|
||||
* hoistableCursor is null if depCursor is not an object we can hoist
|
||||
* property reads from otherwise, it represents the same node in the
|
||||
* hoistable / cfg-informed tree
|
||||
*/
|
||||
currNode.accessType = merge(
|
||||
currNode.accessType,
|
||||
PropertyAccessType.Dependency,
|
||||
let hoistableCursor: HoistableNode | undefined =
|
||||
this.#hoistableObjects.get(identifier);
|
||||
|
||||
// All properties read 'on the way' to a dependency are marked as 'access'
|
||||
for (const entry of path) {
|
||||
let nextHoistableCursor: HoistableNode | undefined;
|
||||
let nextDepCursor: DependencyNode;
|
||||
if (entry.optional) {
|
||||
/**
|
||||
* No need to check the access type since we can match both optional or non-optionals
|
||||
* in the hoistable
|
||||
* e.g. a?.b<rest> is hoistable if a.b<rest> is hoistable
|
||||
*/
|
||||
if (hoistableCursor != null) {
|
||||
nextHoistableCursor = hoistableCursor?.properties.get(entry.property);
|
||||
}
|
||||
|
||||
let accessType;
|
||||
if (
|
||||
hoistableCursor != null &&
|
||||
hoistableCursor.accessType === 'NonNull'
|
||||
) {
|
||||
/**
|
||||
* For an optional chain dep `a?.b`: if the hoistable tree only
|
||||
* contains `a`, we can keep either `a?.b` or 'a.b' as a dependency.
|
||||
* (note that we currently do the latter for perf)
|
||||
*/
|
||||
accessType = PropertyAccessType.UnconditionalAccess;
|
||||
} else {
|
||||
/**
|
||||
* Given that it's safe to evaluate `depCursor` and optional load
|
||||
* never throws, it's also safe to evaluate `depCursor?.entry`
|
||||
*/
|
||||
accessType = PropertyAccessType.OptionalAccess;
|
||||
}
|
||||
nextDepCursor = makeOrMergeProperty(
|
||||
depCursor,
|
||||
entry.property,
|
||||
accessType,
|
||||
);
|
||||
} else if (
|
||||
hoistableCursor != null &&
|
||||
hoistableCursor.accessType === 'NonNull'
|
||||
) {
|
||||
nextHoistableCursor = hoistableCursor.properties.get(entry.property);
|
||||
nextDepCursor = makeOrMergeProperty(
|
||||
depCursor,
|
||||
entry.property,
|
||||
PropertyAccessType.UnconditionalAccess,
|
||||
);
|
||||
} else {
|
||||
/**
|
||||
* Break to truncate the dependency on its first non-optional entry that PropertyLoads are not hoistable from
|
||||
*/
|
||||
break;
|
||||
}
|
||||
depCursor = nextDepCursor;
|
||||
hoistableCursor = nextHoistableCursor;
|
||||
}
|
||||
// mark the final node as a dependency
|
||||
depCursor.accessType = merge(
|
||||
depCursor.accessType,
|
||||
PropertyAccessType.OptionalDependency,
|
||||
);
|
||||
}
|
||||
|
||||
markNodesNonNull(dep: ReactiveScopePropertyDependency): void {
|
||||
const accessType = PropertyAccessType.NonNullAccess;
|
||||
let currNode = this.#roots.get(dep.identifier);
|
||||
|
||||
let cursor = 0;
|
||||
while (currNode != null && cursor < dep.path.length) {
|
||||
currNode.accessType = merge(currNode.accessType, accessType);
|
||||
currNode = currNode.properties.get(dep.path[cursor++].property);
|
||||
}
|
||||
if (currNode != null) {
|
||||
currNode.accessType = merge(currNode.accessType, accessType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a set of minimal dependencies that are safe to
|
||||
* access unconditionally (with respect to nullthrows behavior)
|
||||
*/
|
||||
deriveMinimalDependencies(): Set<ReactiveScopeDependency> {
|
||||
const results = new Set<ReactiveScopeDependency>();
|
||||
for (const [rootId, rootNode] of this.#roots.entries()) {
|
||||
if (ENABLE_DEBUG_INVARIANTS) {
|
||||
assertWellFormedTree(rootNode);
|
||||
}
|
||||
const deps = deriveMinimalDependenciesInSubtree(rootNode, []);
|
||||
|
||||
for (const dep of deps) {
|
||||
results.add({
|
||||
identifier: rootId,
|
||||
path: dep.path.map(s => ({property: s, optional: false})),
|
||||
});
|
||||
}
|
||||
for (const [rootId, rootNode] of this.#deps.entries()) {
|
||||
collectMinimalDependenciesInSubtree(rootNode, rootId, [], results);
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -110,7 +186,7 @@ export class ReactiveScopeDependencyTreeHIR {
|
||||
printDeps(includeAccesses: boolean): string {
|
||||
let res: Array<Array<string>> = [];
|
||||
|
||||
for (const [rootId, rootNode] of this.#roots.entries()) {
|
||||
for (const [rootId, rootNode] of this.#deps.entries()) {
|
||||
const rootResults = printSubtree(rootNode, includeAccesses).map(
|
||||
result => `${printIdentifier(rootId)}.${result}`,
|
||||
);
|
||||
@@ -118,31 +194,64 @@ export class ReactiveScopeDependencyTreeHIR {
|
||||
}
|
||||
return res.flat().join('\n');
|
||||
}
|
||||
|
||||
static debug<T extends string>(roots: Map<Identifier, TreeNode<T>>): string {
|
||||
const buf: Array<string> = [`tree() [`];
|
||||
for (const [rootId, rootNode] of roots) {
|
||||
buf.push(`${printIdentifier(rootId)} (${rootNode.accessType}):`);
|
||||
this.#debugImpl(buf, rootNode, 1);
|
||||
}
|
||||
buf.push(']');
|
||||
return buf.length > 2 ? buf.join('\n') : buf.join('');
|
||||
}
|
||||
|
||||
static #debugImpl<T extends string>(
|
||||
buf: Array<string>,
|
||||
node: TreeNode<T>,
|
||||
depth: number = 0,
|
||||
): void {
|
||||
for (const [property, childNode] of node.properties) {
|
||||
buf.push(`${' '.repeat(depth)}.${property} (${childNode.accessType}):`);
|
||||
this.#debugImpl(buf, childNode, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PropertyAccessType {
|
||||
Access = 'Access',
|
||||
NonNullAccess = 'NonNullAccess',
|
||||
Dependency = 'Dependency',
|
||||
NonNullDependency = 'NonNullDependency',
|
||||
}
|
||||
|
||||
const MIN_ACCESS_TYPE = PropertyAccessType.Access;
|
||||
/**
|
||||
* "NonNull" means that PropertyReads from a node are side-effect free,
|
||||
* as the node is (1) immutable and (2) has unconditional propertyloads
|
||||
* somewhere in the cfg.
|
||||
/*
|
||||
* Enum representing the access type of single property on a parent object.
|
||||
* We distinguish on two independent axes:
|
||||
* Optional / Unconditional:
|
||||
* - whether this property is an optional load (within an optional chain)
|
||||
* Access / Dependency:
|
||||
* - Access: this property is read on the path of a dependency. We do not
|
||||
* need to track change variables for accessed properties. Tracking accesses
|
||||
* helps Forget do more granular dependency tracking.
|
||||
* - Dependency: this property is read as a dependency and we must track changes
|
||||
* to it for correctness.
|
||||
* ```javascript
|
||||
* // props.a is a dependency here and must be tracked
|
||||
* deps: {props.a, props.a.b} ---> minimalDeps: {props.a}
|
||||
* // props.a is just an access here and does not need to be tracked
|
||||
* deps: {props.a.b} ---> minimalDeps: {props.a.b}
|
||||
* ```
|
||||
*/
|
||||
function isNonNull(access: PropertyAccessType): boolean {
|
||||
enum PropertyAccessType {
|
||||
OptionalAccess = 'OptionalAccess',
|
||||
UnconditionalAccess = 'UnconditionalAccess',
|
||||
OptionalDependency = 'OptionalDependency',
|
||||
UnconditionalDependency = 'UnconditionalDependency',
|
||||
}
|
||||
|
||||
function isOptional(access: PropertyAccessType): boolean {
|
||||
return (
|
||||
access === PropertyAccessType.NonNullAccess ||
|
||||
access === PropertyAccessType.NonNullDependency
|
||||
access === PropertyAccessType.OptionalAccess ||
|
||||
access === PropertyAccessType.OptionalDependency
|
||||
);
|
||||
}
|
||||
function isDependency(access: PropertyAccessType): boolean {
|
||||
return (
|
||||
access === PropertyAccessType.Dependency ||
|
||||
access === PropertyAccessType.NonNullDependency
|
||||
access === PropertyAccessType.OptionalDependency ||
|
||||
access === PropertyAccessType.UnconditionalDependency
|
||||
);
|
||||
}
|
||||
|
||||
@@ -150,92 +259,70 @@ function merge(
|
||||
access1: PropertyAccessType,
|
||||
access2: PropertyAccessType,
|
||||
): PropertyAccessType {
|
||||
const resultisNonNull = isNonNull(access1) || isNonNull(access2);
|
||||
const resultIsUnconditional = !(isOptional(access1) && isOptional(access2));
|
||||
const resultIsDependency = isDependency(access1) || isDependency(access2);
|
||||
|
||||
/*
|
||||
* Straightforward merge.
|
||||
* This can be represented as bitwise OR, but is written out for readability
|
||||
*
|
||||
* Observe that `NonNullAccess | Dependency` produces an
|
||||
* Observe that `UnconditionalAccess | ConditionalDependency` produces an
|
||||
* unconditionally accessed conditional dependency. We currently use these
|
||||
* as we use unconditional dependencies. (i.e. to codegen change variables)
|
||||
*/
|
||||
if (resultisNonNull) {
|
||||
if (resultIsUnconditional) {
|
||||
if (resultIsDependency) {
|
||||
return PropertyAccessType.NonNullDependency;
|
||||
return PropertyAccessType.UnconditionalDependency;
|
||||
} else {
|
||||
return PropertyAccessType.NonNullAccess;
|
||||
return PropertyAccessType.UnconditionalAccess;
|
||||
}
|
||||
} else {
|
||||
// result is optional
|
||||
if (resultIsDependency) {
|
||||
return PropertyAccessType.Dependency;
|
||||
return PropertyAccessType.OptionalDependency;
|
||||
} else {
|
||||
return PropertyAccessType.Access;
|
||||
return PropertyAccessType.OptionalAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DependencyNode = {
|
||||
properties: Map<string, DependencyNode>;
|
||||
accessType: PropertyAccessType;
|
||||
type TreeNode<T extends string> = {
|
||||
properties: Map<string, TreeNode<T>>;
|
||||
accessType: T;
|
||||
};
|
||||
type HoistableNode = TreeNode<'Optional' | 'NonNull'>;
|
||||
type DependencyNode = TreeNode<PropertyAccessType>;
|
||||
|
||||
type ReduceResultNode = {
|
||||
path: Array<string>;
|
||||
};
|
||||
|
||||
function assertWellFormedTree(node: DependencyNode): void {
|
||||
let nonNullInChildren = false;
|
||||
for (const childNode of node.properties.values()) {
|
||||
assertWellFormedTree(childNode);
|
||||
nonNullInChildren ||= isNonNull(childNode.accessType);
|
||||
}
|
||||
if (nonNullInChildren) {
|
||||
CompilerError.invariant(isNonNull(node.accessType), {
|
||||
reason:
|
||||
'[DeriveMinimialDependencies] Not well formed tree, unexpected non-null node',
|
||||
description: node.accessType,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function deriveMinimalDependenciesInSubtree(
|
||||
/**
|
||||
* TODO: this is directly pasted from DeriveMinimalDependencies. Since we no
|
||||
* longer have conditionally accessed nodes, we can simplify
|
||||
*
|
||||
* Recursively calculates minimal dependencies in a subtree.
|
||||
* @param node DependencyNode representing a dependency subtree.
|
||||
* @returns a minimal list of dependencies in this subtree.
|
||||
*/
|
||||
function collectMinimalDependenciesInSubtree(
|
||||
node: DependencyNode,
|
||||
path: Array<string>,
|
||||
): Array<ReduceResultNode> {
|
||||
rootIdentifier: Identifier,
|
||||
path: Array<DependencyPathEntry>,
|
||||
results: Set<ReactiveScopeDependency>,
|
||||
): void {
|
||||
if (isDependency(node.accessType)) {
|
||||
/**
|
||||
* If this node is a dependency, we truncate the subtree
|
||||
* and return this node. e.g. deps=[`obj.a`, `obj.a.b`]
|
||||
* reduces to deps=[`obj.a`]
|
||||
*/
|
||||
return [{path}];
|
||||
results.add({identifier: rootIdentifier, path});
|
||||
} else {
|
||||
if (isNonNull(node.accessType)) {
|
||||
/*
|
||||
* Only recurse into subtree dependencies if this node
|
||||
* is known to be non-null.
|
||||
*/
|
||||
const result: Array<ReduceResultNode> = [];
|
||||
for (const [childName, childNode] of node.properties) {
|
||||
result.push(
|
||||
...deriveMinimalDependenciesInSubtree(childNode, [
|
||||
...path,
|
||||
childName,
|
||||
]),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
/*
|
||||
* This only occurs when this subtree contains a dependency,
|
||||
* but this node is potentially nullish. As we currently
|
||||
* don't record optional property paths as scope dependencies,
|
||||
* we truncate and record this node as a dependency.
|
||||
*/
|
||||
return [{path}];
|
||||
for (const [childName, childNode] of node.properties) {
|
||||
collectMinimalDependenciesInSubtree(
|
||||
childNode,
|
||||
rootIdentifier,
|
||||
[
|
||||
...path,
|
||||
{
|
||||
property: childName,
|
||||
optional: isOptional(childNode.accessType),
|
||||
},
|
||||
],
|
||||
results,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
DEFAULT_SHAPES,
|
||||
Global,
|
||||
GlobalRegistry,
|
||||
installReAnimatedTypes,
|
||||
getReanimatedModuleType,
|
||||
installTypeConfig,
|
||||
} from './Globals';
|
||||
import {
|
||||
@@ -688,7 +688,8 @@ export class Environment {
|
||||
}
|
||||
|
||||
if (config.enableCustomTypeDefinitionForReanimated) {
|
||||
installReAnimatedTypes(this.#globals, this.#shapes);
|
||||
const reanimatedModuleType = getReanimatedModuleType(this.#shapes);
|
||||
this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType);
|
||||
}
|
||||
|
||||
this.#contextIdentifiers = contextIdentifiers;
|
||||
@@ -734,11 +735,11 @@ export class Environment {
|
||||
}
|
||||
|
||||
#resolveModuleType(moduleName: string, loc: SourceLocation): Global | null {
|
||||
if (this.config.moduleTypeProvider == null) {
|
||||
return null;
|
||||
}
|
||||
let moduleType = this.#moduleTypes.get(moduleName);
|
||||
if (moduleType === undefined) {
|
||||
if (this.config.moduleTypeProvider == null) {
|
||||
return null;
|
||||
}
|
||||
const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName);
|
||||
if (unparsedModuleConfig != null) {
|
||||
const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
|
||||
@@ -957,6 +958,8 @@ export class Environment {
|
||||
}
|
||||
}
|
||||
|
||||
const REANIMATED_MODULE_NAME = 'react-native-reanimated';
|
||||
|
||||
// From https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js#LL18C1-L23C2
|
||||
export function isHookName(name: string): boolean {
|
||||
return /^use[A-Z0-9]/.test(name);
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
addHook,
|
||||
addObject,
|
||||
} from './ObjectShape';
|
||||
import {BuiltInType, PolyType} from './Types';
|
||||
import {BuiltInType, ObjectType, PolyType} from './Types';
|
||||
import {TypeConfig} from './TypeSchema';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {isHookName} from './Environment';
|
||||
@@ -652,10 +652,7 @@ export function installTypeConfig(
|
||||
}
|
||||
}
|
||||
|
||||
export function installReAnimatedTypes(
|
||||
globals: GlobalRegistry,
|
||||
registry: ShapeRegistry,
|
||||
): void {
|
||||
export function getReanimatedModuleType(registry: ShapeRegistry): ObjectType {
|
||||
// hooks that freeze args and return frozen value
|
||||
const frozenHooks = [
|
||||
'useFrameCallback',
|
||||
@@ -665,8 +662,9 @@ export function installReAnimatedTypes(
|
||||
'useAnimatedReaction',
|
||||
'useWorkletCallback',
|
||||
];
|
||||
const reanimatedType: Array<[string, BuiltInType]> = [];
|
||||
for (const hook of frozenHooks) {
|
||||
globals.set(
|
||||
reanimatedType.push([
|
||||
hook,
|
||||
addHook(registry, {
|
||||
positionalParams: [],
|
||||
@@ -677,7 +675,7 @@ export function installReAnimatedTypes(
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'Custom',
|
||||
}),
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -686,7 +684,7 @@ export function installReAnimatedTypes(
|
||||
*/
|
||||
const mutableHooks = ['useSharedValue', 'useDerivedValue'];
|
||||
for (const hook of mutableHooks) {
|
||||
globals.set(
|
||||
reanimatedType.push([
|
||||
hook,
|
||||
addHook(registry, {
|
||||
positionalParams: [],
|
||||
@@ -697,7 +695,7 @@ export function installReAnimatedTypes(
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: 'Custom',
|
||||
}),
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
// functions that return mutable value
|
||||
@@ -711,7 +709,7 @@ export function installReAnimatedTypes(
|
||||
'executeOnUIRuntimeSync',
|
||||
];
|
||||
for (const fn of funcs) {
|
||||
globals.set(
|
||||
reanimatedType.push([
|
||||
fn,
|
||||
addFunction(registry, [], {
|
||||
positionalParams: [],
|
||||
@@ -721,6 +719,8 @@ export function installReAnimatedTypes(
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
}),
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
return addObject(registry, null, reanimatedType);
|
||||
}
|
||||
|
||||
@@ -367,6 +367,7 @@ export type BasicBlock = {
|
||||
preds: Set<BlockId>;
|
||||
phis: Set<Phi>;
|
||||
};
|
||||
export type TBasicBlock<T extends Terminal> = BasicBlock & {terminal: T};
|
||||
|
||||
/*
|
||||
* Terminal nodes generally represent statements that affect control flow, such as
|
||||
|
||||
+68
-43
@@ -18,8 +18,8 @@ import {
|
||||
IdentifierId,
|
||||
} from './HIR';
|
||||
import {
|
||||
BlockInfo,
|
||||
collectHoistablePropertyLoads,
|
||||
keyByScopeId,
|
||||
} from './CollectHoistablePropertyLoads';
|
||||
import {
|
||||
ScopeBlockTraversal,
|
||||
@@ -32,37 +32,60 @@ import {Stack, empty} from '../Utils/Stack';
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {Iterable_some} from '../Utils/utils';
|
||||
import {ReactiveScopeDependencyTreeHIR} from './DeriveMinimalDependenciesHIR';
|
||||
import {collectOptionalChainSidemap} from './CollectOptionalChainDependencies';
|
||||
|
||||
export function propagateScopeDependenciesHIR(fn: HIRFunction): void {
|
||||
const usedOutsideDeclaringScope =
|
||||
findTemporariesUsedOutsideDeclaringScope(fn);
|
||||
const temporaries = collectTemporariesSidemap(fn, usedOutsideDeclaringScope);
|
||||
const {
|
||||
temporariesReadInOptional,
|
||||
processedInstrsInOptional,
|
||||
hoistableObjects,
|
||||
} = collectOptionalChainSidemap(fn);
|
||||
|
||||
const hoistablePropertyLoads = collectHoistablePropertyLoads(fn, temporaries);
|
||||
const hoistablePropertyLoads = keyByScopeId(
|
||||
fn,
|
||||
collectHoistablePropertyLoads(fn, temporaries, hoistableObjects, null),
|
||||
);
|
||||
|
||||
const scopeDeps = collectDependencies(
|
||||
fn,
|
||||
usedOutsideDeclaringScope,
|
||||
temporaries,
|
||||
new Map([...temporaries, ...temporariesReadInOptional]),
|
||||
processedInstrsInOptional,
|
||||
);
|
||||
|
||||
/**
|
||||
* Derive the minimal set of hoistable dependencies for each scope.
|
||||
*/
|
||||
for (const [scope, deps] of scopeDeps) {
|
||||
const tree = new ReactiveScopeDependencyTreeHIR();
|
||||
if (deps.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: Add every dependency used by this scope (e.g. `a.b.c`)
|
||||
* Step 1: Find hoistable accesses, given the basic block in which the scope
|
||||
* begins.
|
||||
*/
|
||||
const hoistables = hoistablePropertyLoads.get(scope.id);
|
||||
CompilerError.invariant(hoistables != null, {
|
||||
reason: '[PropagateScopeDependencies] Scope not found in tracked blocks',
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
/**
|
||||
* Step 2: Calculate hoistable dependencies.
|
||||
*/
|
||||
const tree = new ReactiveScopeDependencyTreeHIR(
|
||||
[...hoistables.assumedNonNullObjects].map(o => o.fullPath),
|
||||
);
|
||||
for (const dep of deps) {
|
||||
tree.addDependency({...dep});
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Mark hoistable dependencies, given the basic block in
|
||||
* which the scope begins.
|
||||
* Step 3: Reduce dependencies to a minimal set.
|
||||
*/
|
||||
recordHoistablePropertyReads(hoistablePropertyLoads, scope.id, tree);
|
||||
const candidates = tree.deriveMinimalDependencies();
|
||||
for (const candidateDep of candidates) {
|
||||
if (
|
||||
@@ -188,7 +211,7 @@ function findTemporariesUsedOutsideDeclaringScope(
|
||||
* of $1, as the evaluation of `arr.length` changes between instructions $1 and
|
||||
* $3. We do not track $1 -> arr.length in this case.
|
||||
*/
|
||||
function collectTemporariesSidemap(
|
||||
export function collectTemporariesSidemap(
|
||||
fn: HIRFunction,
|
||||
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
|
||||
): ReadonlyMap<IdentifierId, ReactiveScopeDependency> {
|
||||
@@ -201,7 +224,12 @@ function collectTemporariesSidemap(
|
||||
);
|
||||
|
||||
if (value.kind === 'PropertyLoad' && !usedOutside) {
|
||||
const property = getProperty(value.object, value.property, temporaries);
|
||||
const property = getProperty(
|
||||
value.object,
|
||||
value.property,
|
||||
false,
|
||||
temporaries,
|
||||
);
|
||||
temporaries.set(lvalue.identifier.id, property);
|
||||
} else if (
|
||||
value.kind === 'LoadLocal' &&
|
||||
@@ -222,6 +250,7 @@ function collectTemporariesSidemap(
|
||||
function getProperty(
|
||||
object: Place,
|
||||
propertyName: string,
|
||||
optional: boolean,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
): ReactiveScopeDependency {
|
||||
/*
|
||||
@@ -253,15 +282,12 @@ function getProperty(
|
||||
if (resolvedDependency == null) {
|
||||
property = {
|
||||
identifier: object.identifier,
|
||||
path: [{property: propertyName, optional: false}],
|
||||
path: [{property: propertyName, optional}],
|
||||
};
|
||||
} else {
|
||||
property = {
|
||||
identifier: resolvedDependency.identifier,
|
||||
path: [
|
||||
...resolvedDependency.path,
|
||||
{property: propertyName, optional: false},
|
||||
],
|
||||
path: [...resolvedDependency.path, {property: propertyName, optional}],
|
||||
};
|
||||
}
|
||||
return property;
|
||||
@@ -409,8 +435,13 @@ class Context {
|
||||
);
|
||||
}
|
||||
|
||||
visitProperty(object: Place, property: string): void {
|
||||
const nextDependency = getProperty(object, property, this.#temporaries);
|
||||
visitProperty(object: Place, property: string, optional: boolean): void {
|
||||
const nextDependency = getProperty(
|
||||
object,
|
||||
property,
|
||||
optional,
|
||||
this.#temporaries,
|
||||
);
|
||||
this.visitDependency(nextDependency);
|
||||
}
|
||||
|
||||
@@ -489,7 +520,7 @@ function handleInstruction(instr: Instruction, context: Context): void {
|
||||
}
|
||||
} else if (value.kind === 'PropertyLoad') {
|
||||
if (context.isUsedOutsideDeclaringScope(lvalue)) {
|
||||
context.visitProperty(value.object, value.property);
|
||||
context.visitProperty(value.object, value.property, false);
|
||||
}
|
||||
} else if (value.kind === 'StoreLocal') {
|
||||
context.visitOperand(value.value);
|
||||
@@ -544,6 +575,7 @@ function collectDependencies(
|
||||
fn: HIRFunction,
|
||||
usedOutsideDeclaringScope: ReadonlySet<DeclarationId>,
|
||||
temporaries: ReadonlyMap<IdentifierId, ReactiveScopeDependency>,
|
||||
processedInstrsInOptional: ReadonlySet<InstructionId>,
|
||||
): Map<ReactiveScope, Array<ReactiveScopeDependency>> {
|
||||
const context = new Context(usedOutsideDeclaringScope, temporaries);
|
||||
|
||||
@@ -572,33 +604,26 @@ function collectDependencies(
|
||||
context.exitScope(scopeBlockInfo.scope, scopeBlockInfo?.pruned);
|
||||
}
|
||||
|
||||
for (const instr of block.instructions) {
|
||||
handleInstruction(instr, context);
|
||||
// Record referenced optional chains in phis
|
||||
for (const phi of block.phis) {
|
||||
for (const operand of phi.operands) {
|
||||
const maybeOptionalChain = temporaries.get(operand[1].id);
|
||||
if (maybeOptionalChain) {
|
||||
context.visitDependency(maybeOptionalChain);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
context.visitOperand(place);
|
||||
for (const instr of block.instructions) {
|
||||
if (!processedInstrsInOptional.has(instr.id)) {
|
||||
handleInstruction(instr, context);
|
||||
}
|
||||
}
|
||||
|
||||
if (!processedInstrsInOptional.has(block.terminal.id)) {
|
||||
for (const place of eachTerminalOperand(block.terminal)) {
|
||||
context.visitOperand(place);
|
||||
}
|
||||
}
|
||||
}
|
||||
return context.deps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the set of hoistable property reads.
|
||||
*/
|
||||
function recordHoistablePropertyReads(
|
||||
nodes: ReadonlyMap<ScopeId, BlockInfo>,
|
||||
scopeId: ScopeId,
|
||||
tree: ReactiveScopeDependencyTreeHIR,
|
||||
): void {
|
||||
const node = nodes.get(scopeId);
|
||||
CompilerError.invariant(node != null, {
|
||||
reason: '[PropagateScopeDependencies] Scope not found in tracked blocks',
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
|
||||
for (const item of node.assumedNonNullObjects) {
|
||||
tree.markNodesNonNull({
|
||||
...item.fullPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,17 @@ export function getOrInsertDefault<U, V>(
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
export function Set_equal<T>(a: ReadonlySet<T>, b: ReadonlySet<T>): boolean {
|
||||
if (a.size !== b.size) {
|
||||
return false;
|
||||
}
|
||||
for (const item of a) {
|
||||
if (!b.has(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function Set_union<T>(a: ReadonlySet<T>, b: ReadonlySet<T>): Set<T> {
|
||||
const union = new Set<T>(a);
|
||||
@@ -128,6 +139,19 @@ export function nonNull<T extends NonNullable<U>, U>(
|
||||
return value != null;
|
||||
}
|
||||
|
||||
export function Set_filter<T>(
|
||||
source: ReadonlySet<T>,
|
||||
fn: (arg: T) => boolean,
|
||||
): Set<T> {
|
||||
const result = new Set<T>();
|
||||
for (const entry of source) {
|
||||
if (fn(entry)) {
|
||||
result.add(entry);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function hasNode<T>(
|
||||
input: NodePath<T | null | undefined>,
|
||||
): input is NodePath<NonNullable<T>> {
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Not safe to hoist read of maybeNullObject.value.inner outside of the
|
||||
* try-catch block, as that might throw
|
||||
*/
|
||||
function useFoo(maybeNullObject: {value: {inner: number}} | null) {
|
||||
const y = [];
|
||||
try {
|
||||
y.push(identity(maybeNullObject.value.inner));
|
||||
} catch {
|
||||
y.push('null');
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [null],
|
||||
sequentialRenders: [null, {value: 2}, {value: 3}, null],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Not safe to hoist read of maybeNullObject.value.inner outside of the
|
||||
* try-catch block, as that might throw
|
||||
*/
|
||||
function useFoo(maybeNullObject) {
|
||||
const $ = _c(2);
|
||||
let y;
|
||||
if ($[0] !== maybeNullObject.value.inner) {
|
||||
y = [];
|
||||
try {
|
||||
y.push(identity(maybeNullObject.value.inner));
|
||||
} catch {
|
||||
y.push("null");
|
||||
}
|
||||
$[0] = maybeNullObject.value.inner;
|
||||
$[1] = y;
|
||||
} else {
|
||||
y = $[1];
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [null],
|
||||
sequentialRenders: [null, { value: 2 }, { value: 3 }, null],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Not safe to hoist read of maybeNullObject.value.inner outside of the
|
||||
* try-catch block, as that might throw
|
||||
*/
|
||||
function useFoo(maybeNullObject: {value: {inner: number}} | null) {
|
||||
const y = [];
|
||||
try {
|
||||
y.push(identity(maybeNullObject.value.inner));
|
||||
} catch {
|
||||
y.push('null');
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [null],
|
||||
sequentialRenders: [null, {value: 2}, {value: 3}, null],
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableCustomTypeDefinitionForReanimated
|
||||
|
||||
/**
|
||||
* Test that a global (i.e. non-imported) useSharedValue is treated as an
|
||||
* unknown hook.
|
||||
*/
|
||||
function SomeComponent() {
|
||||
const sharedVal = useSharedValue(0);
|
||||
return (
|
||||
<Button
|
||||
onPress={() => (sharedVal.value = Math.random())}
|
||||
title="Randomize"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
9 | return (
|
||||
10 | <Button
|
||||
> 11 | onPress={() => (sharedVal.value = Math.random())}
|
||||
| ^^^^^^^^^ InvalidReact: Mutating a value returned from a function whose return value should not be mutated. Found mutation of `sharedVal` (11:11)
|
||||
12 | title="Randomize"
|
||||
13 | />
|
||||
14 | );
|
||||
```
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// @enableCustomTypeDefinitionForReanimated
|
||||
|
||||
/**
|
||||
* Test that a global (i.e. non-imported) useSharedValue is treated as an
|
||||
* unknown hook.
|
||||
*/
|
||||
function SomeComponent() {
|
||||
const sharedVal = useSharedValue(0);
|
||||
return (
|
||||
<Button
|
||||
onPress={() => (sharedVal.value = Math.random())}
|
||||
title="Randomize"
|
||||
/>
|
||||
);
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function useFoo({a}) {
|
||||
let x = [];
|
||||
x.push(a?.b.c?.d.e);
|
||||
x.push(a.b?.c.d?.e);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [
|
||||
{a: null},
|
||||
{a: null},
|
||||
{a: {}},
|
||||
{a: {b: {c: {d: {e: 42}}}}},
|
||||
{a: {b: {c: {d: {e: 43}}}}},
|
||||
{a: {b: {c: {d: {e: undefined}}}}},
|
||||
{a: {b: undefined}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
function useFoo(t0) {
|
||||
const $ = _c(2);
|
||||
const { a } = t0;
|
||||
let x;
|
||||
if ($[0] !== a.b.c.d) {
|
||||
x = [];
|
||||
x.push(a?.b.c?.d.e);
|
||||
x.push(a.b?.c.d?.e);
|
||||
$[0] = a.b.c.d;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [
|
||||
{ a: null },
|
||||
{ a: null },
|
||||
{ a: {} },
|
||||
{ a: { b: { c: { d: { e: 42 } } } } },
|
||||
{ a: { b: { c: { d: { e: 43 } } } } },
|
||||
{ a: { b: { c: { d: { e: undefined } } } } },
|
||||
{ a: { b: undefined } },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
|
||||
[42,42]
|
||||
[43,43]
|
||||
[null,null]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
function useFoo({a}) {
|
||||
let x = [];
|
||||
x.push(a?.b.c?.d.e);
|
||||
x.push(a.b?.c.d?.e);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [
|
||||
{a: null},
|
||||
{a: null},
|
||||
{a: {}},
|
||||
{a: {b: {c: {d: {e: 42}}}}},
|
||||
{a: {b: {c: {d: {e: 43}}}}},
|
||||
{a: {b: {c: {d: {e: undefined}}}}},
|
||||
{a: {b: undefined}},
|
||||
],
|
||||
};
|
||||
+1
-1
@@ -101,4 +101,4 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [{"Children":{"map":"[[ function params=3 ]]","forEach":"[[ function params=3 ]]","count":"[[ function params=1 ]]","toArray":"[[ function params=1 ]]","only":"[[ function params=1 ]]"},"Component":"[[ function params=3 ]]","PureComponent":"[[ function params=3 ]]","__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE":{"H":{"readContext":"[[ function params=1 ]]","use":"[[ function params=1 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useEffect":"[[ function params=2 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useTransition":"[[ function params=0 ]]","useSyncExternalStore":"[[ function params=3 ]]","useId":"[[ function params=0 ]]","useCacheRefresh":"[[ function params=0 ]]","useMemoCache":"[[ function params=1 ]]","useHostTransitionStatus":"[[ function params=0 ]]","useFormState":"[[ function params=3 ]]","useActionState":"[[ function params=3 ]]","useOptimistic":"[[ function params=2 ]]"},"A":{"getCacheForType":"[[ function params=1 ]]","getOwner":"[[ function params=0 ]]"},"T":null,"actQueue":["[[ function params=0 ]]","[[ function params=1 ]]"],"isBatchingLegacy":false,"didScheduleLegacyUpdate":false,"didUsePromise":false,"thrownErrors":[],"setExtraStackFrame":"[[ function params=1 ]]","getCurrentStack":"[[ function params=0 ]]","getStackAddendum":"[[ function params=0 ]]"},"act":"[[ function params=1 ]]","cache":"[[ function params=1 ]]","cloneElement":"[[ function params=3 ]]","createContext":"[[ function params=1 ]]","createElement":"[[ function params=3 ]]","createRef":"[[ function params=0 ]]","forwardRef":"[[ function params=1 ]]","isValidElement":"[[ function params=1 ]]","lazy":"[[ function params=1 ]]","memo":"[[ function params=2 ]]","startTransition":"[[ function params=2 ]]","unstable_useCacheRefresh":"[[ function params=0 ]]","use":"[[ function params=1 ]]","useActionState":"[[ function params=3 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useEffect":"[[ function params=2 ]]","useId":"[[ function params=0 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useOptimistic":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useSyncExternalStore":"[[ function params=3 ]]","useTransition":"[[ function params=0 ]]","version":"19.0.0-beta-b498834eab-20240506","c":"[[ function params=1 ]]"},"[[ cyclic ref *6 ]]",true,true,true,true,"[[ function params=0 ]]",true,"[[ function params=0 ]]"]
|
||||
(kind: ok) [{"Children":{"map":"[[ function params=3 ]]","forEach":"[[ function params=3 ]]","count":"[[ function params=1 ]]","toArray":"[[ function params=1 ]]","only":"[[ function params=1 ]]"},"Component":"[[ function params=3 ]]","PureComponent":"[[ function params=3 ]]","__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE":{"H":{"readContext":"[[ function params=1 ]]","use":"[[ function params=1 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useEffect":"[[ function params=2 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useTransition":"[[ function params=0 ]]","useSyncExternalStore":"[[ function params=3 ]]","useId":"[[ function params=0 ]]","useCacheRefresh":"[[ function params=0 ]]","useMemoCache":"[[ function params=1 ]]","useHostTransitionStatus":"[[ function params=0 ]]","useFormState":"[[ function params=3 ]]","useActionState":"[[ function params=3 ]]","useOptimistic":"[[ function params=2 ]]"},"A":{"getCacheForType":"[[ function params=1 ]]","getOwner":"[[ function params=0 ]]"},"T":null,"actQueue":["[[ function params=0 ]]","[[ function params=1 ]]"],"isBatchingLegacy":false,"didScheduleLegacyUpdate":false,"didUsePromise":false,"thrownErrors":[],"setExtraStackFrame":"[[ function params=1 ]]","getCurrentStack":"[[ function params=0 ]]","getStackAddendum":"[[ function params=0 ]]"},"act":"[[ function params=1 ]]","cache":"[[ function params=1 ]]","cloneElement":"[[ function params=3 ]]","createContext":"[[ function params=1 ]]","createElement":"[[ function params=3 ]]","createRef":"[[ function params=0 ]]","forwardRef":"[[ function params=1 ]]","isValidElement":"[[ function params=1 ]]","lazy":"[[ function params=1 ]]","memo":"[[ function params=2 ]]","startTransition":"[[ function params=2 ]]","unstable_useCacheRefresh":"[[ function params=0 ]]","use":"[[ function params=1 ]]","useActionState":"[[ function params=3 ]]","useCallback":"[[ function params=2 ]]","useContext":"[[ function params=1 ]]","useDebugValue":"[[ function params=2 ]]","useDeferredValue":"[[ function params=2 ]]","useEffect":"[[ function params=2 ]]","useId":"[[ function params=0 ]]","useImperativeHandle":"[[ function params=3 ]]","useInsertionEffect":"[[ function params=2 ]]","useLayoutEffect":"[[ function params=2 ]]","useMemo":"[[ function params=2 ]]","useOptimistic":"[[ function params=2 ]]","useReducer":"[[ function params=3 ]]","useRef":"[[ function params=1 ]]","useState":"[[ function params=1 ]]","useSyncExternalStore":"[[ function params=3 ]]","useTransition":"[[ function params=0 ]]","version":"19.0.0-beta-b498834eab-20240506"},"[[ cyclic ref *6 ]]",true,true,true,true,"[[ function params=0 ]]",true,"[[ function params=0 ]]"]
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* identity(...)?.toString() is the outer optional, and prop?.value is the inner
|
||||
* one.
|
||||
* Note that prop?.
|
||||
*/
|
||||
function useFoo({
|
||||
prop1,
|
||||
prop2,
|
||||
prop3,
|
||||
prop4,
|
||||
prop5,
|
||||
prop6,
|
||||
}: {
|
||||
prop1: null | {value: number};
|
||||
prop2: null | {inner: {value: number}};
|
||||
prop3: null | {fn: (val: any) => NonNullable<object>};
|
||||
prop4: null | {inner: {value: number}};
|
||||
prop5: null | {fn: (val: any) => NonNullable<object>};
|
||||
prop6: null | {inner: {value: number}};
|
||||
}) {
|
||||
// prop1?.value should be hoisted as the dependency of x
|
||||
const x = identity(prop1?.value)?.toString();
|
||||
|
||||
// prop2?.inner.value should be hoisted as the dependency of y
|
||||
const y = identity(prop2?.inner.value)?.toString();
|
||||
|
||||
// prop3 and prop4?.inner should be hoisted as the dependency of z
|
||||
const z = prop3?.fn(prop4?.inner.value).toString();
|
||||
|
||||
// prop5 and prop6?.inner should be hoisted as the dependency of zz
|
||||
const zz = prop5?.fn(prop6?.inner.value)?.toString();
|
||||
return [x, y, z, zz];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
],
|
||||
sequentialRenders: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: 3}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: 4}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: 4}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: 3}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: 4}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: undefined}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: undefined}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {},
|
||||
prop3: {fn: identity},
|
||||
prop4: {},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* identity(...)?.toString() is the outer optional, and prop?.value is the inner
|
||||
* one.
|
||||
* Note that prop?.
|
||||
*/
|
||||
function useFoo(t0) {
|
||||
const $ = _c(15);
|
||||
const { prop1, prop2, prop3, prop4, prop5, prop6 } = t0;
|
||||
let t1;
|
||||
if ($[0] !== prop1?.value) {
|
||||
t1 = identity(prop1?.value)?.toString();
|
||||
$[0] = prop1?.value;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const x = t1;
|
||||
let t2;
|
||||
if ($[2] !== prop2?.inner) {
|
||||
t2 = identity(prop2?.inner.value)?.toString();
|
||||
$[2] = prop2?.inner;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
const y = t2;
|
||||
let t3;
|
||||
if ($[4] !== prop3 || $[5] !== prop4) {
|
||||
t3 = prop3?.fn(prop4?.inner.value).toString();
|
||||
$[4] = prop3;
|
||||
$[5] = prop4;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
const z = t3;
|
||||
let t4;
|
||||
if ($[7] !== prop5 || $[8] !== prop6) {
|
||||
t4 = prop5?.fn(prop6?.inner.value)?.toString();
|
||||
$[7] = prop5;
|
||||
$[8] = prop6;
|
||||
$[9] = t4;
|
||||
} else {
|
||||
t4 = $[9];
|
||||
}
|
||||
const zz = t4;
|
||||
let t5;
|
||||
if ($[10] !== x || $[11] !== y || $[12] !== z || $[13] !== zz) {
|
||||
t5 = [x, y, z, zz];
|
||||
$[10] = x;
|
||||
$[11] = y;
|
||||
$[12] = z;
|
||||
$[13] = zz;
|
||||
$[14] = t5;
|
||||
} else {
|
||||
t5 = $[14];
|
||||
}
|
||||
return t5;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
],
|
||||
|
||||
sequentialRenders: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
{
|
||||
prop1: { value: 2 },
|
||||
prop2: { inner: { value: 3 } },
|
||||
prop3: { fn: identity },
|
||||
prop4: { inner: { value: 4 } },
|
||||
prop5: { fn: identity },
|
||||
prop6: { inner: { value: 4 } },
|
||||
},
|
||||
{
|
||||
prop1: { value: 2 },
|
||||
prop2: { inner: { value: 3 } },
|
||||
prop3: { fn: identity },
|
||||
prop4: { inner: { value: 4 } },
|
||||
prop5: { fn: identity },
|
||||
prop6: { inner: { value: undefined } },
|
||||
},
|
||||
{
|
||||
prop1: { value: 2 },
|
||||
prop2: { inner: { value: undefined } },
|
||||
prop3: { fn: identity },
|
||||
prop4: { inner: { value: undefined } },
|
||||
prop5: { fn: identity },
|
||||
prop6: { inner: { value: undefined } },
|
||||
},
|
||||
{
|
||||
prop1: { value: 2 },
|
||||
prop2: {},
|
||||
prop3: { fn: identity },
|
||||
prop4: {},
|
||||
prop5: { fn: identity },
|
||||
prop6: { inner: { value: undefined } },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [null,null,null,null]
|
||||
["2","3","4","4"]
|
||||
["2","3","4",null]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'toString') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'value') ]]
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* identity(...)?.toString() is the outer optional, and prop?.value is the inner
|
||||
* one.
|
||||
* Note that prop?.
|
||||
*/
|
||||
function useFoo({
|
||||
prop1,
|
||||
prop2,
|
||||
prop3,
|
||||
prop4,
|
||||
prop5,
|
||||
prop6,
|
||||
}: {
|
||||
prop1: null | {value: number};
|
||||
prop2: null | {inner: {value: number}};
|
||||
prop3: null | {fn: (val: any) => NonNullable<object>};
|
||||
prop4: null | {inner: {value: number}};
|
||||
prop5: null | {fn: (val: any) => NonNullable<object>};
|
||||
prop6: null | {inner: {value: number}};
|
||||
}) {
|
||||
// prop1?.value should be hoisted as the dependency of x
|
||||
const x = identity(prop1?.value)?.toString();
|
||||
|
||||
// prop2?.inner.value should be hoisted as the dependency of y
|
||||
const y = identity(prop2?.inner.value)?.toString();
|
||||
|
||||
// prop3 and prop4?.inner should be hoisted as the dependency of z
|
||||
const z = prop3?.fn(prop4?.inner.value).toString();
|
||||
|
||||
// prop5 and prop6?.inner should be hoisted as the dependency of zz
|
||||
const zz = prop5?.fn(prop6?.inner.value)?.toString();
|
||||
return [x, y, z, zz];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
],
|
||||
sequentialRenders: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: 3}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: 4}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: 4}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: 3}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: 4}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: undefined}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: undefined}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {},
|
||||
prop3: {fn: identity},
|
||||
prop4: {},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
],
|
||||
};
|
||||
+74
-24
@@ -3,12 +3,29 @@
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
|
||||
function Component(props) {
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
import {useMemo} from 'react';
|
||||
|
||||
function Component({arg}) {
|
||||
const data = useMemo(() => {
|
||||
return props?.items.edges?.nodes.map();
|
||||
}, [props?.items.edges?.nodes]);
|
||||
return <Foo data={data} />;
|
||||
return arg?.items.edges?.nodes.map(identity);
|
||||
}, [arg?.items.edges?.nodes]);
|
||||
return (
|
||||
<ValidateMemoization inputs={[arg?.items.edges?.nodes]} output={data} />
|
||||
);
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: null}],
|
||||
sequentialRenders: [
|
||||
{arg: null},
|
||||
{arg: null},
|
||||
{arg: {items: {edges: null}}},
|
||||
{arg: {items: {edges: null}}},
|
||||
{arg: {items: {edges: {nodes: [1, 2, 'hello']}}}},
|
||||
{arg: {items: {edges: {nodes: [1, 2, 'hello']}}}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
@@ -16,33 +33,66 @@ function Component(props) {
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
|
||||
function Component(props) {
|
||||
const $ = _c(4);
|
||||
import { identity, ValidateMemoization } from "shared-runtime";
|
||||
import { useMemo } from "react";
|
||||
|
||||
props?.items.edges?.nodes;
|
||||
let t0;
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { arg } = t0;
|
||||
|
||||
arg?.items.edges?.nodes;
|
||||
let t1;
|
||||
if ($[0] !== props?.items.edges?.nodes) {
|
||||
t1 = props?.items.edges?.nodes.map();
|
||||
$[0] = props?.items.edges?.nodes;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
t0 = t1;
|
||||
const data = t0;
|
||||
let t2;
|
||||
if ($[2] !== data) {
|
||||
t2 = <Foo data={data} />;
|
||||
$[2] = data;
|
||||
$[3] = t2;
|
||||
if ($[0] !== arg?.items.edges?.nodes) {
|
||||
t2 = arg?.items.edges?.nodes.map(identity);
|
||||
$[0] = arg?.items.edges?.nodes;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
t2 = $[1];
|
||||
}
|
||||
return t2;
|
||||
t1 = t2;
|
||||
const data = t1;
|
||||
|
||||
const t3 = arg?.items.edges?.nodes;
|
||||
let t4;
|
||||
if ($[2] !== t3) {
|
||||
t4 = [t3];
|
||||
$[2] = t3;
|
||||
$[3] = t4;
|
||||
} else {
|
||||
t4 = $[3];
|
||||
}
|
||||
let t5;
|
||||
if ($[4] !== t4 || $[5] !== data) {
|
||||
t5 = <ValidateMemoization inputs={t4} output={data} />;
|
||||
$[4] = t4;
|
||||
$[5] = data;
|
||||
$[6] = t5;
|
||||
} else {
|
||||
t5 = $[6];
|
||||
}
|
||||
return t5;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ arg: null }],
|
||||
sequentialRenders: [
|
||||
{ arg: null },
|
||||
{ arg: null },
|
||||
{ arg: { items: { edges: null } } },
|
||||
{ arg: { items: { edges: null } } },
|
||||
{ arg: { items: { edges: { nodes: [1, 2, "hello"] } } } },
|
||||
{ arg: { items: { edges: { nodes: [1, 2, "hello"] } } } },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
(kind: ok) <div>{"inputs":[null]}</div>
|
||||
<div>{"inputs":[null]}</div>
|
||||
<div>{"inputs":[null]}</div>
|
||||
<div>{"inputs":[null]}</div>
|
||||
<div>{"inputs":[[1,2,"hello"]],"output":[1,2,"hello"]}</div>
|
||||
<div>{"inputs":[[1,2,"hello"]],"output":[1,2,"hello"]}</div>
|
||||
+21
-4
@@ -1,7 +1,24 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
|
||||
function Component(props) {
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
import {useMemo} from 'react';
|
||||
|
||||
function Component({arg}) {
|
||||
const data = useMemo(() => {
|
||||
return props?.items.edges?.nodes.map();
|
||||
}, [props?.items.edges?.nodes]);
|
||||
return <Foo data={data} />;
|
||||
return arg?.items.edges?.nodes.map(identity);
|
||||
}, [arg?.items.edges?.nodes]);
|
||||
return (
|
||||
<ValidateMemoization inputs={[arg?.items.edges?.nodes]} output={data} />
|
||||
);
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: null}],
|
||||
sequentialRenders: [
|
||||
{arg: null},
|
||||
{arg: null},
|
||||
{arg: {items: {edges: null}}},
|
||||
{arg: {items: {edges: null}}},
|
||||
{arg: {items: {edges: {nodes: [1, 2, 'hello']}}}},
|
||||
{arg: {items: {edges: {nodes: [1, 2, 'hello']}}}},
|
||||
],
|
||||
};
|
||||
|
||||
+57
-29
@@ -4,15 +4,27 @@
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
function Component(props) {
|
||||
import {useMemo} from 'react';
|
||||
function Component({arg}) {
|
||||
const data = useMemo(() => {
|
||||
const x = [];
|
||||
x.push(props?.items);
|
||||
x.push(arg?.items);
|
||||
return x;
|
||||
}, [props?.items]);
|
||||
return <ValidateMemoization inputs={[props?.items]} output={data} />;
|
||||
}, [arg?.items]);
|
||||
return <ValidateMemoization inputs={[arg?.items]} output={data} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: {items: 2}}],
|
||||
sequentialRenders: [
|
||||
{arg: {items: 2}},
|
||||
{arg: {items: 2}},
|
||||
{arg: null},
|
||||
{arg: null},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
@@ -20,44 +32,60 @@ function Component(props) {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
|
||||
import { ValidateMemoization } from "shared-runtime";
|
||||
function Component(props) {
|
||||
import { useMemo } from "react";
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { arg } = t0;
|
||||
|
||||
props?.items;
|
||||
let t0;
|
||||
arg?.items;
|
||||
let t1;
|
||||
let x;
|
||||
if ($[0] !== props?.items) {
|
||||
if ($[0] !== arg?.items) {
|
||||
x = [];
|
||||
x.push(props?.items);
|
||||
$[0] = props?.items;
|
||||
x.push(arg?.items);
|
||||
$[0] = arg?.items;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
t0 = x;
|
||||
const data = t0;
|
||||
const t1 = props?.items;
|
||||
let t2;
|
||||
if ($[2] !== t1) {
|
||||
t2 = [t1];
|
||||
$[2] = t1;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
t1 = x;
|
||||
const data = t1;
|
||||
const t2 = arg?.items;
|
||||
let t3;
|
||||
if ($[4] !== t2 || $[5] !== data) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={data} />;
|
||||
$[4] = t2;
|
||||
$[5] = data;
|
||||
$[6] = t3;
|
||||
if ($[2] !== t2) {
|
||||
t3 = [t2];
|
||||
$[2] = t2;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
t3 = $[3];
|
||||
}
|
||||
return t3;
|
||||
let t4;
|
||||
if ($[4] !== t3 || $[5] !== data) {
|
||||
t4 = <ValidateMemoization inputs={t3} output={data} />;
|
||||
$[4] = t3;
|
||||
$[5] = data;
|
||||
$[6] = t4;
|
||||
} else {
|
||||
t4 = $[6];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ arg: { items: 2 } }],
|
||||
sequentialRenders: [
|
||||
{ arg: { items: 2 } },
|
||||
{ arg: { items: 2 } },
|
||||
{ arg: null },
|
||||
{ arg: null },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
(kind: ok) <div>{"inputs":[2],"output":[2]}</div>
|
||||
<div>{"inputs":[2],"output":[2]}</div>
|
||||
<div>{"inputs":[null],"output":[null]}</div>
|
||||
<div>{"inputs":[null],"output":[null]}</div>
|
||||
+16
-4
@@ -1,10 +1,22 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
function Component(props) {
|
||||
import {useMemo} from 'react';
|
||||
function Component({arg}) {
|
||||
const data = useMemo(() => {
|
||||
const x = [];
|
||||
x.push(props?.items);
|
||||
x.push(arg?.items);
|
||||
return x;
|
||||
}, [props?.items]);
|
||||
return <ValidateMemoization inputs={[props?.items]} output={data} />;
|
||||
}, [arg?.items]);
|
||||
return <ValidateMemoization inputs={[arg?.items]} output={data} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: {items: 2}}],
|
||||
sequentialRenders: [
|
||||
{arg: {items: 2}},
|
||||
{arg: {items: 2}},
|
||||
{arg: null},
|
||||
{arg: null},
|
||||
],
|
||||
};
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPONT = {
|
||||
2 | function useFoo(props: {value: {x: string; y: string} | null}) {
|
||||
3 | const value = props.value;
|
||||
> 4 | return createArray(value?.x, value?.y)?.join(', ');
|
||||
| ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional test block (4:4)
|
||||
| ^^^^^^^^ Todo: Unexpected terminal kind `optional` for optional fallthrough block (4:4)
|
||||
5 | }
|
||||
6 |
|
||||
7 | function createArray<T>(...args: Array<T>): Array<T> {
|
||||
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
function Component(props) {
|
||||
const data = useMemo(() => {
|
||||
return props?.items.edges?.nodes.map();
|
||||
}, [props?.items.edges?.nodes]);
|
||||
return <Foo data={data} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
1 | // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
2 | function Component(props) {
|
||||
> 3 | const data = useMemo(() => {
|
||||
| ^^^^^^^
|
||||
> 4 | return props?.items.edges?.nodes.map();
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
> 5 | }, [props?.items.edges?.nodes]);
|
||||
| ^^^^ 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 (3:5)
|
||||
6 | return <Foo data={data} />;
|
||||
7 | }
|
||||
8 |
|
||||
```
|
||||
|
||||
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
function Component(props) {
|
||||
const data = useMemo(() => {
|
||||
return props?.items.edges?.nodes.map();
|
||||
}, [props?.items.edges?.nodes]);
|
||||
return <Foo data={data} />;
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
function Component(props) {
|
||||
const data = useMemo(() => {
|
||||
const x = [];
|
||||
x.push(props?.items);
|
||||
x.push(props.items);
|
||||
return x;
|
||||
}, [props.items]);
|
||||
return <ValidateMemoization inputs={[props.items]} 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 | x.push(props.items);
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
> 8 | return x;
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
> 9 | }, [props.items]);
|
||||
| ^^^^ 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:9)
|
||||
10 | return <ValidateMemoization inputs={[props.items]} output={data} />;
|
||||
11 | }
|
||||
12 |
|
||||
```
|
||||
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
function Component(props) {
|
||||
const data = useMemo(() => {
|
||||
const x = [];
|
||||
x.push(props?.items);
|
||||
return x;
|
||||
}, [props?.items]);
|
||||
return <ValidateMemoization inputs={[props?.items]} 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 | return x;
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
> 8 | }, [props?.items]);
|
||||
| ^^^^ 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:8)
|
||||
9 | return <ValidateMemoization inputs={[props?.items]} output={data} />;
|
||||
10 | }
|
||||
11 |
|
||||
```
|
||||
|
||||
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
function Component(props) {
|
||||
const data = useMemo(() => {
|
||||
const x = [];
|
||||
x.push(props?.items);
|
||||
return x;
|
||||
}, [props?.items]);
|
||||
return <ValidateMemoization inputs={[props?.items]} output={data} />;
|
||||
}
|
||||
+20
-11
@@ -12,7 +12,7 @@ function Foo(props) {
|
||||
* as it is arg[0] of a component function
|
||||
*/
|
||||
const arr = [];
|
||||
if (cond) {
|
||||
if (props.cond) {
|
||||
arr.push(identity(props.value));
|
||||
}
|
||||
return <Stringify arr={arr} />;
|
||||
@@ -20,7 +20,7 @@ function Foo(props) {
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{value: 2}],
|
||||
params: [{value: 2, cond: true}],
|
||||
};
|
||||
|
||||
```
|
||||
@@ -32,29 +32,38 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
import { identity, Stringify } from "shared-runtime";
|
||||
|
||||
function Foo(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(5);
|
||||
let t0;
|
||||
if ($[0] !== props.value) {
|
||||
if ($[0] !== props.cond || $[1] !== props.value) {
|
||||
const arr = [];
|
||||
if (cond) {
|
||||
arr.push(identity(props.value));
|
||||
if (props.cond) {
|
||||
let t1;
|
||||
if ($[3] !== props.value) {
|
||||
t1 = identity(props.value);
|
||||
$[3] = props.value;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
arr.push(t1);
|
||||
}
|
||||
|
||||
t0 = <Stringify arr={arr} />;
|
||||
$[0] = props.value;
|
||||
$[1] = t0;
|
||||
$[0] = props.cond;
|
||||
$[1] = props.value;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ value: 2 }],
|
||||
params: [{ value: 2, cond: true }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) cond is not defined
|
||||
(kind: ok) <div>{"arr":[2]}</div>
|
||||
+2
-2
@@ -8,7 +8,7 @@ function Foo(props) {
|
||||
* as it is arg[0] of a component function
|
||||
*/
|
||||
const arr = [];
|
||||
if (cond) {
|
||||
if (props.cond) {
|
||||
arr.push(identity(props.value));
|
||||
}
|
||||
return <Stringify arr={arr} />;
|
||||
@@ -16,5 +16,5 @@ function Foo(props) {
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{value: 2}],
|
||||
params: [{value: 2, cond: true}],
|
||||
};
|
||||
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
function useFoo({a}) {
|
||||
let x = [];
|
||||
x.push(a?.b.c?.d.e);
|
||||
x.push(a.b?.c.d?.e);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [
|
||||
{a: null},
|
||||
{a: null},
|
||||
{a: {}},
|
||||
{a: {b: {c: {d: {e: 42}}}}},
|
||||
{a: {b: {c: {d: {e: 43}}}}},
|
||||
{a: {b: {c: {d: {e: undefined}}}}},
|
||||
{a: {b: undefined}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(2);
|
||||
const { a } = t0;
|
||||
let x;
|
||||
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.e;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [
|
||||
{ a: null },
|
||||
{ a: null },
|
||||
{ a: {} },
|
||||
{ a: { b: { c: { d: { e: 42 } } } } },
|
||||
{ a: { b: { c: { d: { e: 43 } } } } },
|
||||
{ a: { b: { c: { d: { e: undefined } } } } },
|
||||
{ a: { b: undefined } },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
|
||||
[42,42]
|
||||
[43,43]
|
||||
[null,null]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
function useFoo({a}) {
|
||||
let x = [];
|
||||
x.push(a?.b.c?.d.e);
|
||||
x.push(a.b?.c.d?.e);
|
||||
return x;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [
|
||||
{a: null},
|
||||
{a: null},
|
||||
{a: {}},
|
||||
{a: {b: {c: {d: {e: 42}}}}},
|
||||
{a: {b: {c: {d: {e: 43}}}}},
|
||||
{a: {b: {c: {d: {e: undefined}}}}},
|
||||
{a: {b: undefined}},
|
||||
],
|
||||
};
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* identity(...)?.toString() is the outer optional, and prop?.value is the inner
|
||||
* one.
|
||||
* Note that prop?.
|
||||
*/
|
||||
function useFoo({
|
||||
prop1,
|
||||
prop2,
|
||||
prop3,
|
||||
prop4,
|
||||
prop5,
|
||||
prop6,
|
||||
}: {
|
||||
prop1: null | {value: number};
|
||||
prop2: null | {inner: {value: number}};
|
||||
prop3: null | {fn: (val: any) => NonNullable<object>};
|
||||
prop4: null | {inner: {value: number}};
|
||||
prop5: null | {fn: (val: any) => NonNullable<object>};
|
||||
prop6: null | {inner: {value: number}};
|
||||
}) {
|
||||
// prop1?.value should be hoisted as the dependency of x
|
||||
const x = identity(prop1?.value)?.toString();
|
||||
|
||||
// prop2?.inner.value should be hoisted as the dependency of y
|
||||
const y = identity(prop2?.inner.value)?.toString();
|
||||
|
||||
// prop3 and prop4?.inner should be hoisted as the dependency of z
|
||||
const z = prop3?.fn(prop4?.inner.value).toString();
|
||||
|
||||
// prop5 and prop6?.inner should be hoisted as the dependency of zz
|
||||
const zz = prop5?.fn(prop6?.inner.value)?.toString();
|
||||
return [x, y, z, zz];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
],
|
||||
sequentialRenders: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: 3}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: 4}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: 4}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: 3}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: 4}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: undefined}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: undefined}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {},
|
||||
prop3: {fn: identity},
|
||||
prop4: {},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* identity(...)?.toString() is the outer optional, and prop?.value is the inner
|
||||
* one.
|
||||
* Note that prop?.
|
||||
*/
|
||||
function useFoo(t0) {
|
||||
const $ = _c(15);
|
||||
const { prop1, prop2, prop3, prop4, prop5, prop6 } = t0;
|
||||
let t1;
|
||||
if ($[0] !== prop1?.value) {
|
||||
t1 = identity(prop1?.value)?.toString();
|
||||
$[0] = prop1?.value;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const x = t1;
|
||||
let t2;
|
||||
if ($[2] !== prop2?.inner.value) {
|
||||
t2 = identity(prop2?.inner.value)?.toString();
|
||||
$[2] = prop2?.inner.value;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
const y = t2;
|
||||
let t3;
|
||||
if ($[4] !== prop3 || $[5] !== prop4?.inner) {
|
||||
t3 = prop3?.fn(prop4?.inner.value).toString();
|
||||
$[4] = prop3;
|
||||
$[5] = prop4?.inner;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
const z = t3;
|
||||
let t4;
|
||||
if ($[7] !== prop5 || $[8] !== prop6?.inner) {
|
||||
t4 = prop5?.fn(prop6?.inner.value)?.toString();
|
||||
$[7] = prop5;
|
||||
$[8] = prop6?.inner;
|
||||
$[9] = t4;
|
||||
} else {
|
||||
t4 = $[9];
|
||||
}
|
||||
const zz = t4;
|
||||
let t5;
|
||||
if ($[10] !== x || $[11] !== y || $[12] !== z || $[13] !== zz) {
|
||||
t5 = [x, y, z, zz];
|
||||
$[10] = x;
|
||||
$[11] = y;
|
||||
$[12] = z;
|
||||
$[13] = zz;
|
||||
$[14] = t5;
|
||||
} else {
|
||||
t5 = $[14];
|
||||
}
|
||||
return t5;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
],
|
||||
|
||||
sequentialRenders: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
{
|
||||
prop1: { value: 2 },
|
||||
prop2: { inner: { value: 3 } },
|
||||
prop3: { fn: identity },
|
||||
prop4: { inner: { value: 4 } },
|
||||
prop5: { fn: identity },
|
||||
prop6: { inner: { value: 4 } },
|
||||
},
|
||||
{
|
||||
prop1: { value: 2 },
|
||||
prop2: { inner: { value: 3 } },
|
||||
prop3: { fn: identity },
|
||||
prop4: { inner: { value: 4 } },
|
||||
prop5: { fn: identity },
|
||||
prop6: { inner: { value: undefined } },
|
||||
},
|
||||
{
|
||||
prop1: { value: 2 },
|
||||
prop2: { inner: { value: undefined } },
|
||||
prop3: { fn: identity },
|
||||
prop4: { inner: { value: undefined } },
|
||||
prop5: { fn: identity },
|
||||
prop6: { inner: { value: undefined } },
|
||||
},
|
||||
{
|
||||
prop1: { value: 2 },
|
||||
prop2: {},
|
||||
prop3: { fn: identity },
|
||||
prop4: {},
|
||||
prop5: { fn: identity },
|
||||
prop6: { inner: { value: undefined } },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [null,null,null,null]
|
||||
["2","3","4","4"]
|
||||
["2","3","4",null]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'toString') ]]
|
||||
[[ (exception in render) TypeError: Cannot read properties of undefined (reading 'value') ]]
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* identity(...)?.toString() is the outer optional, and prop?.value is the inner
|
||||
* one.
|
||||
* Note that prop?.
|
||||
*/
|
||||
function useFoo({
|
||||
prop1,
|
||||
prop2,
|
||||
prop3,
|
||||
prop4,
|
||||
prop5,
|
||||
prop6,
|
||||
}: {
|
||||
prop1: null | {value: number};
|
||||
prop2: null | {inner: {value: number}};
|
||||
prop3: null | {fn: (val: any) => NonNullable<object>};
|
||||
prop4: null | {inner: {value: number}};
|
||||
prop5: null | {fn: (val: any) => NonNullable<object>};
|
||||
prop6: null | {inner: {value: number}};
|
||||
}) {
|
||||
// prop1?.value should be hoisted as the dependency of x
|
||||
const x = identity(prop1?.value)?.toString();
|
||||
|
||||
// prop2?.inner.value should be hoisted as the dependency of y
|
||||
const y = identity(prop2?.inner.value)?.toString();
|
||||
|
||||
// prop3 and prop4?.inner should be hoisted as the dependency of z
|
||||
const z = prop3?.fn(prop4?.inner.value).toString();
|
||||
|
||||
// prop5 and prop6?.inner should be hoisted as the dependency of zz
|
||||
const zz = prop5?.fn(prop6?.inner.value)?.toString();
|
||||
return [x, y, z, zz];
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
],
|
||||
sequentialRenders: [
|
||||
{
|
||||
prop1: null,
|
||||
prop2: null,
|
||||
prop3: null,
|
||||
prop4: null,
|
||||
prop5: null,
|
||||
prop6: null,
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: 3}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: 4}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: 4}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: 3}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: 4}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {inner: {value: undefined}},
|
||||
prop3: {fn: identity},
|
||||
prop4: {inner: {value: undefined}},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
{
|
||||
prop1: {value: 2},
|
||||
prop2: {},
|
||||
prop3: {fn: identity},
|
||||
prop4: {},
|
||||
prop5: {fn: identity},
|
||||
prop6: {inner: {value: undefined}},
|
||||
},
|
||||
],
|
||||
};
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
import {useMemo} from 'react';
|
||||
|
||||
function Component({arg}) {
|
||||
const data = useMemo(() => {
|
||||
return arg?.items.edges?.nodes.map(identity);
|
||||
}, [arg?.items.edges?.nodes]);
|
||||
return (
|
||||
<ValidateMemoization inputs={[arg?.items.edges?.nodes]} output={data} />
|
||||
);
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: null}],
|
||||
sequentialRenders: [
|
||||
{arg: null},
|
||||
{arg: null},
|
||||
{arg: {items: {edges: null}}},
|
||||
{arg: {items: {edges: null}}},
|
||||
{arg: {items: {edges: {nodes: [1, 2, 'hello']}}}},
|
||||
{arg: {items: {edges: {nodes: [1, 2, 'hello']}}}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import { identity, ValidateMemoization } from "shared-runtime";
|
||||
import { useMemo } from "react";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { arg } = t0;
|
||||
|
||||
arg?.items.edges?.nodes;
|
||||
let t1;
|
||||
let t2;
|
||||
if ($[0] !== arg?.items.edges?.nodes) {
|
||||
t2 = arg?.items.edges?.nodes.map(identity);
|
||||
$[0] = arg?.items.edges?.nodes;
|
||||
$[1] = t2;
|
||||
} else {
|
||||
t2 = $[1];
|
||||
}
|
||||
t1 = t2;
|
||||
const data = t1;
|
||||
|
||||
const t3 = arg?.items.edges?.nodes;
|
||||
let t4;
|
||||
if ($[2] !== t3) {
|
||||
t4 = [t3];
|
||||
$[2] = t3;
|
||||
$[3] = t4;
|
||||
} else {
|
||||
t4 = $[3];
|
||||
}
|
||||
let t5;
|
||||
if ($[4] !== t4 || $[5] !== data) {
|
||||
t5 = <ValidateMemoization inputs={t4} output={data} />;
|
||||
$[4] = t4;
|
||||
$[5] = data;
|
||||
$[6] = t5;
|
||||
} else {
|
||||
t5 = $[6];
|
||||
}
|
||||
return t5;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ arg: null }],
|
||||
sequentialRenders: [
|
||||
{ arg: null },
|
||||
{ arg: null },
|
||||
{ arg: { items: { edges: null } } },
|
||||
{ arg: { items: { edges: null } } },
|
||||
{ arg: { items: { edges: { nodes: [1, 2, "hello"] } } } },
|
||||
{ arg: { items: { edges: { nodes: [1, 2, "hello"] } } } },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[null]}</div>
|
||||
<div>{"inputs":[null]}</div>
|
||||
<div>{"inputs":[null]}</div>
|
||||
<div>{"inputs":[null]}</div>
|
||||
<div>{"inputs":[[1,2,"hello"]],"output":[1,2,"hello"]}</div>
|
||||
<div>{"inputs":[[1,2,"hello"]],"output":[1,2,"hello"]}</div>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import {identity, ValidateMemoization} from 'shared-runtime';
|
||||
import {useMemo} from 'react';
|
||||
|
||||
function Component({arg}) {
|
||||
const data = useMemo(() => {
|
||||
return arg?.items.edges?.nodes.map(identity);
|
||||
}, [arg?.items.edges?.nodes]);
|
||||
return (
|
||||
<ValidateMemoization inputs={[arg?.items.edges?.nodes]} output={data} />
|
||||
);
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: null}],
|
||||
sequentialRenders: [
|
||||
{arg: null},
|
||||
{arg: null},
|
||||
{arg: {items: {edges: null}}},
|
||||
{arg: {items: {edges: null}}},
|
||||
{arg: {items: {edges: {nodes: [1, 2, 'hello']}}}},
|
||||
{arg: {items: {edges: {nodes: [1, 2, 'hello']}}}},
|
||||
],
|
||||
};
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
function Component(props) {
|
||||
const data = useMemo(() => {
|
||||
const x = [];
|
||||
x.push(props?.items);
|
||||
x.push(props.items);
|
||||
return x;
|
||||
}, [props.items]);
|
||||
return <ValidateMemoization inputs={[props.items]} output={data} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import { ValidateMemoization } from "shared-runtime";
|
||||
function Component(props) {
|
||||
const $ = _c(7);
|
||||
let t0;
|
||||
let x;
|
||||
if ($[0] !== props.items) {
|
||||
x = [];
|
||||
x.push(props?.items);
|
||||
x.push(props.items);
|
||||
$[0] = props.items;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
t0 = x;
|
||||
const data = t0;
|
||||
let t1;
|
||||
if ($[2] !== props.items) {
|
||||
t1 = [props.items];
|
||||
$[2] = props.items;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
let t2;
|
||||
if ($[4] !== t1 || $[5] !== data) {
|
||||
t2 = <ValidateMemoization inputs={t1} output={data} />;
|
||||
$[4] = t1;
|
||||
$[5] = data;
|
||||
$[6] = t2;
|
||||
} else {
|
||||
t2 = $[6];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: exception) Fixture not implemented
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
import {useMemo} from 'react';
|
||||
function Component({arg}) {
|
||||
const data = useMemo(() => {
|
||||
const x = [];
|
||||
x.push(arg?.items);
|
||||
return x;
|
||||
}, [arg?.items]);
|
||||
return <ValidateMemoization inputs={[arg?.items]} output={data} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: {items: 2}}],
|
||||
sequentialRenders: [
|
||||
{arg: {items: 2}},
|
||||
{arg: {items: 2}},
|
||||
{arg: null},
|
||||
{arg: null},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import { ValidateMemoization } from "shared-runtime";
|
||||
import { useMemo } from "react";
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { arg } = t0;
|
||||
|
||||
arg?.items;
|
||||
let t1;
|
||||
let x;
|
||||
if ($[0] !== arg?.items) {
|
||||
x = [];
|
||||
x.push(arg?.items);
|
||||
$[0] = arg?.items;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
}
|
||||
t1 = x;
|
||||
const data = t1;
|
||||
const t2 = arg?.items;
|
||||
let t3;
|
||||
if ($[2] !== t2) {
|
||||
t3 = [t2];
|
||||
$[2] = t2;
|
||||
$[3] = t3;
|
||||
} else {
|
||||
t3 = $[3];
|
||||
}
|
||||
let t4;
|
||||
if ($[4] !== t3 || $[5] !== data) {
|
||||
t4 = <ValidateMemoization inputs={t3} output={data} />;
|
||||
$[4] = t3;
|
||||
$[5] = data;
|
||||
$[6] = t4;
|
||||
} else {
|
||||
t4 = $[6];
|
||||
}
|
||||
return t4;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ arg: { items: 2 } }],
|
||||
sequentialRenders: [
|
||||
{ arg: { items: 2 } },
|
||||
{ arg: { items: 2 } },
|
||||
{ arg: null },
|
||||
{ arg: null },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[2],"output":[2]}</div>
|
||||
<div>{"inputs":[2],"output":[2]}</div>
|
||||
<div>{"inputs":[null],"output":[null]}</div>
|
||||
<div>{"inputs":[null],"output":[null]}</div>
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// @validatePreserveExistingMemoizationGuarantees @enableOptionalDependencies @enablePropagateDepsInHIR
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
import {useMemo} from 'react';
|
||||
function Component({arg}) {
|
||||
const data = useMemo(() => {
|
||||
const x = [];
|
||||
x.push(arg?.items);
|
||||
return x;
|
||||
}, [arg?.items]);
|
||||
return <ValidateMemoization inputs={[arg?.items]} output={data} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{arg: {items: 2}}],
|
||||
sequentialRenders: [
|
||||
{arg: {items: 2}},
|
||||
{arg: {items: 2}},
|
||||
{arg: null},
|
||||
{arg: null},
|
||||
],
|
||||
};
|
||||
+2
-2
@@ -16,9 +16,9 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
let t0;
|
||||
if ($[0] !== props.post.feedback.comments) {
|
||||
if ($[0] !== props.post.feedback.comments?.edges) {
|
||||
t0 = props.post.feedback.comments?.edges?.map(render);
|
||||
$[0] = props.post.feedback.comments;
|
||||
$[0] = props.post.feedback.comments?.edges;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
|
||||
+2
-2
@@ -31,10 +31,10 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
let x;
|
||||
if ($[0] !== props.a) {
|
||||
if ($[0] !== props.a?.b) {
|
||||
x = [];
|
||||
x.push(props.a?.b);
|
||||
$[0] = props.a;
|
||||
$[0] = props.a?.b;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {shallowCopy, mutate, Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({
|
||||
a,
|
||||
shouldReadA,
|
||||
}: {
|
||||
a: {b: {c: number}; x: number};
|
||||
shouldReadA: boolean;
|
||||
}) {
|
||||
const local = shallowCopy(a);
|
||||
mutate(local);
|
||||
return (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) return local.b.c;
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null, shouldReadA: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, shouldReadA: true},
|
||||
{a: null, shouldReadA: false},
|
||||
{a: {b: {c: 4}}, shouldReadA: true},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { shallowCopy, mutate, Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(5);
|
||||
const { a, shouldReadA } = t0;
|
||||
let local;
|
||||
if ($[0] !== a) {
|
||||
local = shallowCopy(a);
|
||||
mutate(local);
|
||||
$[0] = a;
|
||||
$[1] = local;
|
||||
} else {
|
||||
local = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] !== shouldReadA || $[3] !== local) {
|
||||
t1 = (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) {
|
||||
return local.b.c;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
$[2] = shouldReadA;
|
||||
$[3] = local;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null, shouldReadA: true }],
|
||||
sequentialRenders: [
|
||||
{ a: null, shouldReadA: true },
|
||||
{ a: null, shouldReadA: false },
|
||||
{ a: { b: { c: 4 } }, shouldReadA: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
|
||||
<div>{"fn":{"kind":"Function","result":null},"shouldInvokeFns":true}</div>
|
||||
<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {shallowCopy, mutate, Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({
|
||||
a,
|
||||
shouldReadA,
|
||||
}: {
|
||||
a: {b: {c: number}; x: number};
|
||||
shouldReadA: boolean;
|
||||
}) {
|
||||
const local = shallowCopy(a);
|
||||
mutate(local);
|
||||
return (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) return local.b.c;
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null, shouldReadA: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, shouldReadA: true},
|
||||
{a: null, shouldReadA: false},
|
||||
{a: {b: {c: 4}}, shouldReadA: true},
|
||||
],
|
||||
};
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function Foo({a, shouldReadA}) {
|
||||
return (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) return a.b.c;
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: null, shouldReadA: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, shouldReadA: true},
|
||||
{a: null, shouldReadA: false},
|
||||
{a: {b: {c: 4}}, shouldReadA: true},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Foo(t0) {
|
||||
const $ = _c(3);
|
||||
const { a, shouldReadA } = t0;
|
||||
let t1;
|
||||
if ($[0] !== shouldReadA || $[1] !== a) {
|
||||
t1 = (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) {
|
||||
return a.b.c;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
$[0] = shouldReadA;
|
||||
$[1] = a;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ a: null, shouldReadA: true }],
|
||||
sequentialRenders: [
|
||||
{ a: null, shouldReadA: true },
|
||||
{ a: null, shouldReadA: false },
|
||||
{ a: { b: { c: 4 } }, shouldReadA: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
<div>{"fn":{"kind":"Function","result":null},"shouldInvokeFns":true}</div>
|
||||
<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function Foo({a, shouldReadA}) {
|
||||
return (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) return a.b.c;
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: null, shouldReadA: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, shouldReadA: true},
|
||||
{a: null, shouldReadA: false},
|
||||
{a: {b: {c: 4}}, shouldReadA: true},
|
||||
],
|
||||
};
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
return <Stringify fn={() => a.b.c} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(2);
|
||||
const { a } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a.b.c) {
|
||||
t1 = <Stringify fn={() => a.b.c} shouldInvokeFns={true} />;
|
||||
$[0] = a.b.c;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
return <Stringify fn={() => a.b.c} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Foo({a, cond}) {
|
||||
// Assume fn will be uncond evaluated, so we can safely evaluate {a.<any>,
|
||||
// a.b.<any}
|
||||
const fn = () => [a, a.b.c];
|
||||
useIdentity(null);
|
||||
const x = makeArray();
|
||||
if (cond) {
|
||||
x.push(identity(a.b.c));
|
||||
}
|
||||
return <Stringify fn={fn} x={x} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: null, cond: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, cond: true},
|
||||
{a: {b: {c: 4}}, cond: true},
|
||||
{a: {b: {c: 4}}, cond: true},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { identity, makeArray, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Foo(t0) {
|
||||
const $ = _c(8);
|
||||
const { a, cond } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a) {
|
||||
t1 = () => [a, a.b.c];
|
||||
$[0] = a;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const fn = t1;
|
||||
useIdentity(null);
|
||||
let x;
|
||||
if ($[2] !== cond || $[3] !== a.b.c) {
|
||||
x = makeArray();
|
||||
if (cond) {
|
||||
x.push(identity(a.b.c));
|
||||
}
|
||||
$[2] = cond;
|
||||
$[3] = a.b.c;
|
||||
$[4] = x;
|
||||
} else {
|
||||
x = $[4];
|
||||
}
|
||||
let t2;
|
||||
if ($[5] !== fn || $[6] !== x) {
|
||||
t2 = <Stringify fn={fn} x={x} shouldInvokeFns={true} />;
|
||||
$[5] = fn;
|
||||
$[6] = x;
|
||||
$[7] = t2;
|
||||
} else {
|
||||
t2 = $[7];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ a: null, cond: true }],
|
||||
sequentialRenders: [
|
||||
{ a: null, cond: true },
|
||||
{ a: { b: { c: 4 } }, cond: true },
|
||||
{ a: { b: { c: 4 } }, cond: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
<div>{"fn":{"kind":"Function","result":[{"b":{"c":4}},4]},"x":[4],"shouldInvokeFns":true}</div>
|
||||
<div>{"fn":{"kind":"Function","result":[{"b":{"c":4}},4]},"x":[4],"shouldInvokeFns":true}</div>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Foo({a, cond}) {
|
||||
// Assume fn will be uncond evaluated, so we can safely evaluate {a.<any>,
|
||||
// a.b.<any}
|
||||
const fn = () => [a, a.b.c];
|
||||
useIdentity(null);
|
||||
const x = makeArray();
|
||||
if (cond) {
|
||||
x.push(identity(a.b.c));
|
||||
}
|
||||
return <Stringify fn={fn} x={x} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: null, cond: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, cond: true},
|
||||
{a: {b: {c: 4}}, cond: true},
|
||||
{a: {b: {c: 4}}, cond: true},
|
||||
],
|
||||
};
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {mutate, shallowCopy, Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}: {a: {b: {c: number}}}) {
|
||||
const local = shallowCopy(a);
|
||||
mutate(local);
|
||||
const fn = () => local.b.c;
|
||||
return <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { mutate, shallowCopy, Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(6);
|
||||
const { a } = t0;
|
||||
let local;
|
||||
if ($[0] !== a) {
|
||||
local = shallowCopy(a);
|
||||
mutate(local);
|
||||
$[0] = a;
|
||||
$[1] = local;
|
||||
} else {
|
||||
local = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] !== local.b.c) {
|
||||
t1 = () => local.b.c;
|
||||
$[2] = local.b.c;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const fn = t1;
|
||||
let t2;
|
||||
if ($[4] !== fn) {
|
||||
t2 = <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
$[4] = fn;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
|
||||
<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {mutate, shallowCopy, Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}: {a: {b: {c: number}}}) {
|
||||
const local = shallowCopy(a);
|
||||
mutate(local);
|
||||
const fn = () => local.b.c;
|
||||
return <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Foo({a, cond}) {
|
||||
// Assume fn can be uncond evaluated, so we can safely evaluate a.b?.c.<any>
|
||||
const fn = () => [a, a.b?.c.d];
|
||||
useIdentity(null);
|
||||
const arr = makeArray();
|
||||
if (cond) {
|
||||
arr.push(identity(a.b?.c.e));
|
||||
}
|
||||
return <Stringify fn={fn} arr={arr} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: null, cond: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, cond: true},
|
||||
{a: {b: {c: {d: 5}}}, cond: true},
|
||||
{a: {b: null}, cond: false},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { identity, makeArray, Stringify, useIdentity } from "shared-runtime";
|
||||
|
||||
function Foo(t0) {
|
||||
const $ = _c(8);
|
||||
const { a, cond } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a) {
|
||||
t1 = () => [a, a.b?.c.d];
|
||||
$[0] = a;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const fn = t1;
|
||||
useIdentity(null);
|
||||
let arr;
|
||||
if ($[2] !== cond || $[3] !== a.b?.c.e) {
|
||||
arr = makeArray();
|
||||
if (cond) {
|
||||
arr.push(identity(a.b?.c.e));
|
||||
}
|
||||
$[2] = cond;
|
||||
$[3] = a.b?.c.e;
|
||||
$[4] = arr;
|
||||
} else {
|
||||
arr = $[4];
|
||||
}
|
||||
let t2;
|
||||
if ($[5] !== fn || $[6] !== arr) {
|
||||
t2 = <Stringify fn={fn} arr={arr} shouldInvokeFns={true} />;
|
||||
$[5] = fn;
|
||||
$[6] = arr;
|
||||
$[7] = t2;
|
||||
} else {
|
||||
t2 = $[7];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ a: null, cond: true }],
|
||||
sequentialRenders: [
|
||||
{ a: null, cond: true },
|
||||
{ a: { b: { c: { d: 5 } } }, cond: true },
|
||||
{ a: { b: null }, cond: false },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
<div>{"fn":{"kind":"Function","result":[{"b":{"c":{"d":5}}},5]},"arr":[null],"shouldInvokeFns":true}</div>
|
||||
<div>{"fn":{"kind":"Function","result":[{"b":null},null]},"arr":[],"shouldInvokeFns":true}</div>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {identity, makeArray, Stringify, useIdentity} from 'shared-runtime';
|
||||
|
||||
function Foo({a, cond}) {
|
||||
// Assume fn can be uncond evaluated, so we can safely evaluate a.b?.c.<any>
|
||||
const fn = () => [a, a.b?.c.d];
|
||||
useIdentity(null);
|
||||
const arr = makeArray();
|
||||
if (cond) {
|
||||
arr.push(identity(a.b?.c.e));
|
||||
}
|
||||
return <Stringify fn={fn} arr={arr} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: null, cond: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, cond: true},
|
||||
{a: {b: {c: {d: 5}}}, cond: true},
|
||||
{a: {b: null}, cond: false},
|
||||
],
|
||||
};
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {shallowCopy, Stringify, mutate} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}: {a: {b: {c: number}}}) {
|
||||
const local = shallowCopy(a);
|
||||
mutate(local);
|
||||
const fn = () => [() => local.b.c];
|
||||
return <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { shallowCopy, Stringify, mutate } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(6);
|
||||
const { a } = t0;
|
||||
let local;
|
||||
if ($[0] !== a) {
|
||||
local = shallowCopy(a);
|
||||
mutate(local);
|
||||
$[0] = a;
|
||||
$[1] = local;
|
||||
} else {
|
||||
local = $[1];
|
||||
}
|
||||
let t1;
|
||||
if ($[2] !== local.b.c) {
|
||||
t1 = () => [() => local.b.c];
|
||||
$[2] = local.b.c;
|
||||
$[3] = t1;
|
||||
} else {
|
||||
t1 = $[3];
|
||||
}
|
||||
const fn = t1;
|
||||
let t2;
|
||||
if ($[4] !== fn) {
|
||||
t2 = <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
$[4] = fn;
|
||||
$[5] = t2;
|
||||
} else {
|
||||
t2 = $[5];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of undefined (reading 'c') ]]
|
||||
<div>{"fn":{"kind":"Function","result":[{"kind":"Function","result":4}]},"shouldInvokeFns":true}</div>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {shallowCopy, Stringify, mutate} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}: {a: {b: {c: number}}}) {
|
||||
const local = shallowCopy(a);
|
||||
mutate(local);
|
||||
const fn = () => [() => local.b.c];
|
||||
return <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
const fn = () => {
|
||||
return () => ({
|
||||
value: a.b.c,
|
||||
});
|
||||
};
|
||||
return <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(4);
|
||||
const { a } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a.b.c) {
|
||||
t1 = () => () => ({ value: a.b.c });
|
||||
$[0] = a.b.c;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const fn = t1;
|
||||
let t2;
|
||||
if ($[2] !== fn) {
|
||||
t2 = <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
$[2] = fn;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
<div>{"fn":{"kind":"Function","result":{"kind":"Function","result":{"value":4}}},"shouldInvokeFns":true}</div>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
const fn = () => {
|
||||
return () => ({
|
||||
value: a.b.c,
|
||||
});
|
||||
};
|
||||
return <Stringify fn={fn} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {identity, Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
const x = {
|
||||
fn() {
|
||||
return identity(a.b.c);
|
||||
},
|
||||
};
|
||||
return <Stringify x={x} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { identity, Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(4);
|
||||
const { a } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a.b.c) {
|
||||
t1 = {
|
||||
fn() {
|
||||
return identity(a.b.c);
|
||||
},
|
||||
};
|
||||
$[0] = a.b.c;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const x = t1;
|
||||
let t2;
|
||||
if ($[2] !== x) {
|
||||
t2 = <Stringify x={x} shouldInvokeFns={true} />;
|
||||
$[2] = x;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [{ a: null }, { a: { b: { c: 4 } } }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
<div>{"x":{"fn":{"kind":"Function","result":4}},"shouldInvokeFns":true}</div>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {identity, Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
const x = {
|
||||
fn() {
|
||||
return identity(a.b.c);
|
||||
},
|
||||
};
|
||||
return <Stringify x={x} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [{a: null}, {a: {b: {c: 4}}}],
|
||||
};
|
||||
+2
-2
@@ -46,11 +46,11 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
let x;
|
||||
if ($[0] !== props.a) {
|
||||
if ($[0] !== props.a.b) {
|
||||
x = [];
|
||||
x.push(props.a?.b);
|
||||
x.push(props.a.b.c);
|
||||
$[0] = props.a;
|
||||
$[0] = props.a.b;
|
||||
$[1] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
|
||||
+15
-6
@@ -22,16 +22,25 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(5);
|
||||
let x;
|
||||
if ($[0] !== props.items) {
|
||||
if ($[0] !== props.items?.length || $[1] !== props.items?.edges) {
|
||||
x = [];
|
||||
x.push(props.items?.length);
|
||||
x.push(props.items?.edges?.map?.(render)?.filter?.(Boolean) ?? []);
|
||||
$[0] = props.items;
|
||||
$[1] = x;
|
||||
let t0;
|
||||
if ($[3] !== props.items?.edges) {
|
||||
t0 = props.items?.edges?.map?.(render)?.filter?.(Boolean) ?? [];
|
||||
$[3] = props.items?.edges;
|
||||
$[4] = t0;
|
||||
} else {
|
||||
t0 = $[4];
|
||||
}
|
||||
x.push(t0);
|
||||
$[0] = props.items?.length;
|
||||
$[1] = props.items?.edges;
|
||||
$[2] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[2];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Very contrived text fixture showing that it's technically incorrect to merge
|
||||
* a conditional dependency (e.g. dep.path in `cond ? dep.path : ...`) and an
|
||||
* unconditionally evaluated optional chain (`dep?.path`).
|
||||
*
|
||||
*
|
||||
* when screen is non-null, useFoo returns { title: null } or "(not null)"
|
||||
* when screen is null, useFoo throws
|
||||
*/
|
||||
function useFoo({screen}: {screen: null | undefined | {title_text: null}}) {
|
||||
return screen?.title_text != null
|
||||
? '(not null)'
|
||||
: identity({title: screen.title_text});
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{screen: null}],
|
||||
sequentialRenders: [{screen: {title_bar: undefined}}, {screen: null}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Very contrived text fixture showing that it's technically incorrect to merge
|
||||
* a conditional dependency (e.g. dep.path in `cond ? dep.path : ...`) and an
|
||||
* unconditionally evaluated optional chain (`dep?.path`).
|
||||
*
|
||||
*
|
||||
* when screen is non-null, useFoo returns { title: null } or "(not null)"
|
||||
* when screen is null, useFoo throws
|
||||
*/
|
||||
function useFoo(t0) {
|
||||
const $ = _c(2);
|
||||
const { screen } = t0;
|
||||
let t1;
|
||||
if ($[0] !== screen) {
|
||||
t1 =
|
||||
screen?.title_text != null
|
||||
? "(not null)"
|
||||
: identity({ title: screen.title_text });
|
||||
$[0] = screen;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ screen: null }],
|
||||
sequentialRenders: [{ screen: { title_bar: undefined } }, { screen: null }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) {}
|
||||
[[ (exception in render) TypeError: Cannot read properties of null (reading 'title_text') ]]
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Very contrived text fixture showing that it's technically incorrect to merge
|
||||
* a conditional dependency (e.g. dep.path in `cond ? dep.path : ...`) and an
|
||||
* unconditionally evaluated optional chain (`dep?.path`).
|
||||
*
|
||||
*
|
||||
* when screen is non-null, useFoo returns { title: null } or "(not null)"
|
||||
* when screen is null, useFoo throws
|
||||
*/
|
||||
function useFoo({screen}: {screen: null | undefined | {title_text: null}}) {
|
||||
return screen?.title_text != null
|
||||
? '(not null)'
|
||||
: identity({title: screen.title_text});
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{screen: null}],
|
||||
sequentialRenders: [{screen: {title_bar: undefined}}, {screen: null}],
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
return <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [
|
||||
{a: null},
|
||||
{a: {b: null}},
|
||||
{a: {b: {c: {d: null}}}},
|
||||
{a: {b: {c: {d: {e: 4}}}}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(2);
|
||||
const { a } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a.b) {
|
||||
t1 = <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
|
||||
$[0] = a.b;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [
|
||||
{ a: null },
|
||||
{ a: { b: null } },
|
||||
{ a: { b: { c: { d: null } } } },
|
||||
{ a: { b: { c: { d: { e: 4 } } } } },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
<div>{"fn":{"kind":"Function"},"shouldInvokeFns":true}</div>
|
||||
<div>{"fn":{"kind":"Function"},"shouldInvokeFns":true}</div>
|
||||
<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
return <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [
|
||||
{a: null},
|
||||
{a: {b: null}},
|
||||
{a: {b: {c: {d: null}}}},
|
||||
{a: {b: {c: {d: {e: 4}}}}},
|
||||
],
|
||||
};
|
||||
+2
-2
@@ -24,14 +24,14 @@ function HomeDiscoStoreItemTileRating(props) {
|
||||
const $ = _c(4);
|
||||
const item = useFragment();
|
||||
let count;
|
||||
if ($[0] !== item) {
|
||||
if ($[0] !== item?.aggregates) {
|
||||
count = 0;
|
||||
const aggregates = item?.aggregates || [];
|
||||
aggregates.forEach((aggregate) => {
|
||||
count = count + (aggregate.count || 0);
|
||||
count;
|
||||
});
|
||||
$[0] = item;
|
||||
$[0] = item?.aggregates;
|
||||
$[1] = count;
|
||||
} else {
|
||||
count = $[1];
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enablePropagateDepsInHIR
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Not safe to hoist read of maybeNullObject.value.inner outside of the
|
||||
* try-catch block, as that might throw
|
||||
*/
|
||||
function useFoo(maybeNullObject: {value: {inner: number}} | null) {
|
||||
const y = [];
|
||||
try {
|
||||
y.push(identity(maybeNullObject.value.inner));
|
||||
} catch {
|
||||
y.push('null');
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [null],
|
||||
sequentialRenders: [null, {value: 2}, {value: 3}, null],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
/**
|
||||
* Not safe to hoist read of maybeNullObject.value.inner outside of the
|
||||
* try-catch block, as that might throw
|
||||
*/
|
||||
function useFoo(maybeNullObject) {
|
||||
const $ = _c(4);
|
||||
let y;
|
||||
if ($[0] !== maybeNullObject) {
|
||||
y = [];
|
||||
try {
|
||||
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;
|
||||
$[1] = y;
|
||||
} else {
|
||||
y = $[1];
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [null],
|
||||
sequentialRenders: [null, { value: 2 }, { value: 3 }, null],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) ["null"]
|
||||
[null]
|
||||
[null]
|
||||
["null"]
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// @enablePropagateDepsInHIR
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
/**
|
||||
* Not safe to hoist read of maybeNullObject.value.inner outside of the
|
||||
* try-catch block, as that might throw
|
||||
*/
|
||||
function useFoo(maybeNullObject: {value: {inner: number}} | null) {
|
||||
const y = [];
|
||||
try {
|
||||
y.push(identity(maybeNullObject.value.inner));
|
||||
} catch {
|
||||
y.push('null');
|
||||
}
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [null],
|
||||
sequentialRenders: [null, {value: 2}, {value: 3}, null],
|
||||
};
|
||||
+6
-5
@@ -32,9 +32,9 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
const { throwInput } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(3);
|
||||
let x;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.y || $[1] !== props.e) {
|
||||
try {
|
||||
const y = [];
|
||||
y.push(props.y);
|
||||
@@ -44,10 +44,11 @@ function Component(props) {
|
||||
e.push(props.e);
|
||||
x = e;
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = x;
|
||||
$[0] = props.y;
|
||||
$[1] = props.e;
|
||||
$[2] = x;
|
||||
} else {
|
||||
x = $[1];
|
||||
x = $[2];
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
+6
-5
@@ -31,9 +31,9 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
|
||||
const { throwInput } = require("shared-runtime");
|
||||
|
||||
function Component(props) {
|
||||
const $ = _c(2);
|
||||
const $ = _c(3);
|
||||
let t0;
|
||||
if ($[0] !== props) {
|
||||
if ($[0] !== props.y || $[1] !== props.e) {
|
||||
t0 = Symbol.for("react.early_return_sentinel");
|
||||
bb0: {
|
||||
try {
|
||||
@@ -47,10 +47,11 @@ function Component(props) {
|
||||
break bb0;
|
||||
}
|
||||
}
|
||||
$[0] = props;
|
||||
$[1] = t0;
|
||||
$[0] = props.y;
|
||||
$[1] = props.e;
|
||||
$[2] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
t0 = $[2];
|
||||
}
|
||||
if (t0 !== Symbol.for("react.early_return_sentinel")) {
|
||||
return t0;
|
||||
|
||||
+2
@@ -3,6 +3,7 @@
|
||||
|
||||
```javascript
|
||||
// @enableCustomTypeDefinitionForReanimated
|
||||
import {useAnimatedProps} from 'react-native-reanimated';
|
||||
function Component() {
|
||||
const radius = useSharedValue(50);
|
||||
|
||||
@@ -38,6 +39,7 @@ export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableCustomTypeDefinitionForReanimated
|
||||
import { useAnimatedProps } from "react-native-reanimated";
|
||||
function Component() {
|
||||
const $ = _c(2);
|
||||
const radius = useSharedValue(50);
|
||||
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
// @enableCustomTypeDefinitionForReanimated
|
||||
import {useAnimatedProps} from 'react-native-reanimated';
|
||||
function Component() {
|
||||
const radius = useSharedValue(50);
|
||||
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableCustomTypeDefinitionForReanimated
|
||||
import {useSharedValue} from 'react-native-reanimated';
|
||||
|
||||
/**
|
||||
* https://docs.swmansion.com/react-native-reanimated/docs/2.x/api/hooks/useSharedValue/
|
||||
*
|
||||
* Test that shared values are treated as ref-like, i.e. allowing writes outside
|
||||
* of render
|
||||
*/
|
||||
function SomeComponent() {
|
||||
const sharedVal = useSharedValue(0);
|
||||
return (
|
||||
<Button
|
||||
onPress={() => (sharedVal.value = Math.random())}
|
||||
title="Randomize"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableCustomTypeDefinitionForReanimated
|
||||
import { useSharedValue } from "react-native-reanimated";
|
||||
|
||||
/**
|
||||
* https://docs.swmansion.com/react-native-reanimated/docs/2.x/api/hooks/useSharedValue/
|
||||
*
|
||||
* Test that shared values are treated as ref-like, i.e. allowing writes outside
|
||||
* of render
|
||||
*/
|
||||
function SomeComponent() {
|
||||
const $ = _c(3);
|
||||
const sharedVal = useSharedValue(0);
|
||||
|
||||
const T0 = Button;
|
||||
const t0 = () => (sharedVal.value = Math.random());
|
||||
let t1;
|
||||
if ($[0] !== T0 || $[1] !== t0) {
|
||||
t1 = <T0 onPress={t0} title="Randomize" />;
|
||||
$[0] = T0;
|
||||
$[1] = t0;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// @enableCustomTypeDefinitionForReanimated
|
||||
import {useSharedValue} from 'react-native-reanimated';
|
||||
|
||||
/**
|
||||
* https://docs.swmansion.com/react-native-reanimated/docs/2.x/api/hooks/useSharedValue/
|
||||
*
|
||||
* Test that shared values are treated as ref-like, i.e. allowing writes outside
|
||||
* of render
|
||||
*/
|
||||
function SomeComponent() {
|
||||
const sharedVal = useSharedValue(0);
|
||||
return (
|
||||
<Button
|
||||
onPress={() => (sharedVal.value = Math.random())}
|
||||
title="Randomize"
|
||||
/>
|
||||
);
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function Foo({a, shouldReadA}) {
|
||||
return (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) return a.b.c;
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: null, shouldReadA: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, shouldReadA: true},
|
||||
{a: null, shouldReadA: false},
|
||||
{a: {b: {c: 4}}, shouldReadA: true},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function Foo(t0) {
|
||||
const $ = _c(3);
|
||||
const { a, shouldReadA } = t0;
|
||||
let t1;
|
||||
if ($[0] !== shouldReadA || $[1] !== a.b.c) {
|
||||
t1 = (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) {
|
||||
return a.b.c;
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
$[0] = shouldReadA;
|
||||
$[1] = a.b.c;
|
||||
$[2] = t1;
|
||||
} else {
|
||||
t1 = $[2];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ a: null, shouldReadA: true }],
|
||||
sequentialRenders: [
|
||||
{ a: null, shouldReadA: true },
|
||||
{ a: null, shouldReadA: false },
|
||||
{ a: { b: { c: 4 } }, shouldReadA: true },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function Foo({a, shouldReadA}) {
|
||||
return (
|
||||
<Stringify
|
||||
fn={() => {
|
||||
if (shouldReadA) return a.b.c;
|
||||
return null;
|
||||
}}
|
||||
shouldInvokeFns={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{a: null, shouldReadA: true}],
|
||||
sequentialRenders: [
|
||||
{a: null, shouldReadA: true},
|
||||
{a: null, shouldReadA: false},
|
||||
{a: {b: {c: 4}}, shouldReadA: true},
|
||||
],
|
||||
};
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
return <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [
|
||||
{a: null},
|
||||
{a: {b: null}},
|
||||
{a: {b: {c: {d: null}}}},
|
||||
{a: {b: {c: {d: {e: 4}}}}},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { Stringify } from "shared-runtime";
|
||||
|
||||
function useFoo(t0) {
|
||||
const $ = _c(2);
|
||||
const { a } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a.b) {
|
||||
t1 = <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
|
||||
$[0] = a.b;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{ a: null }],
|
||||
sequentialRenders: [
|
||||
{ a: null },
|
||||
{ a: { b: null } },
|
||||
{ a: { b: { c: { d: null } } } },
|
||||
{ a: { b: { c: { d: { e: 4 } } } } },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) [[ (exception in render) TypeError: Cannot read properties of null (reading 'b') ]]
|
||||
<div>{"fn":{"kind":"Function"},"shouldInvokeFns":true}</div>
|
||||
<div>{"fn":{"kind":"Function"},"shouldInvokeFns":true}</div>
|
||||
<div>{"fn":{"kind":"Function","result":4},"shouldInvokeFns":true}</div>
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function useFoo({a}) {
|
||||
return <Stringify fn={() => a.b?.c.d?.e} shouldInvokeFns={true} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: useFoo,
|
||||
params: [{a: null}],
|
||||
sequentialRenders: [
|
||||
{a: null},
|
||||
{a: {b: null}},
|
||||
{a: {b: {c: {d: null}}}},
|
||||
{a: {b: {c: {d: {e: 4}}}}},
|
||||
],
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @target="18"
|
||||
|
||||
function Component() {
|
||||
return <div>Hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react-compiler-runtime"; // @target="18"
|
||||
|
||||
function Component() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div>Hello world</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>Hello world</div>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
// @target="18"
|
||||
|
||||
function Component() {
|
||||
return <div>Hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
isComponent: true,
|
||||
};
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @runtimeModule="react-forget-runtime"
|
||||
function Component(props) {
|
||||
const [x, setX] = useState(1);
|
||||
let y;
|
||||
if (props.cond) {
|
||||
y = x * 2;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setX(10 * y);
|
||||
}}></Button>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [true],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react-forget-runtime"; // @runtimeModule="react-forget-runtime"
|
||||
function Component(props) {
|
||||
const $ = _c(5);
|
||||
const [x, setX] = useState(1);
|
||||
let y;
|
||||
if ($[0] !== props.cond || $[1] !== x) {
|
||||
if (props.cond) {
|
||||
y = x * 2;
|
||||
}
|
||||
$[0] = props.cond;
|
||||
$[1] = x;
|
||||
$[2] = y;
|
||||
} else {
|
||||
y = $[2];
|
||||
}
|
||||
|
||||
const t0 = y;
|
||||
let t1;
|
||||
if ($[3] !== t0) {
|
||||
t1 = (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setX(10 * y);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
$[3] = t0;
|
||||
$[4] = t1;
|
||||
} else {
|
||||
t1 = $[4];
|
||||
}
|
||||
return t1;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [true],
|
||||
isComponent: true,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
// @runtimeModule="react-forget-runtime"
|
||||
function Component(props) {
|
||||
const [x, setX] = useState(1);
|
||||
let y;
|
||||
if (props.cond) {
|
||||
y = x * 2;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
setX(10 * y);
|
||||
}}></Button>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [true],
|
||||
isComponent: true,
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
# react-compiler-runtime
|
||||
|
||||
Backwards compatible shim for runtime APIs used by React Compiler. Primarily meant for React versions prior to 19, but it will also work on > 19.
|
||||
|
||||
See also https://github.com/reactwg/react-compiler/discussions/6.
|
||||
@@ -19,30 +19,27 @@ const ReactSecretInternals =
|
||||
type MemoCache = Array<number | typeof $empty>;
|
||||
|
||||
const $empty = Symbol.for('react.memo_cache_sentinel');
|
||||
/**
|
||||
* DANGER: this hook is NEVER meant to be called directly!
|
||||
**/
|
||||
export function c(size: number) {
|
||||
return React.useState(() => {
|
||||
const $ = new Array(size);
|
||||
for (let ii = 0; ii < size; ii++) {
|
||||
$[ii] = $empty;
|
||||
}
|
||||
// This symbol is added to tell the react devtools that this array is from
|
||||
// useMemoCache.
|
||||
// @ts-ignore
|
||||
$[$empty] = true;
|
||||
return $;
|
||||
})[0];
|
||||
}
|
||||
|
||||
export function $read(memoCache: MemoCache, index: number) {
|
||||
const value = memoCache[index];
|
||||
if (value === $empty) {
|
||||
throw new Error('useMemoCache: read before write');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
// Re-export React.c if present, otherwise fallback to the userspace polyfill for versions of React
|
||||
// < 19.
|
||||
export const c =
|
||||
// @ts-expect-error
|
||||
typeof React.__COMPILER_RUNTIME?.c === 'function'
|
||||
? // @ts-expect-error
|
||||
React.__COMPILER_RUNTIME.c
|
||||
: function c(size: number) {
|
||||
return React.useMemo<Array<unknown>>(() => {
|
||||
const $ = new Array(size);
|
||||
for (let ii = 0; ii < size; ii++) {
|
||||
$[ii] = $empty;
|
||||
}
|
||||
// This symbol is added to tell the react devtools that this array is from
|
||||
// useMemoCache.
|
||||
// @ts-ignore
|
||||
$[$empty] = true;
|
||||
return $;
|
||||
}, []);
|
||||
};
|
||||
|
||||
const LazyGuardDispatcher: {[key: string]: (...args: Array<any>) => any} = {};
|
||||
[
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rimraf dist && tsc --build",
|
||||
"postinstall": "./scripts/link-react-compiler-runtime.sh && perl -p -i -e 's/react\\.element/react.transitional.element/' ../../node_modules/fbt/lib/FbtReactUtil.js && perl -p -i -e 's/didWarnAboutUsingAct = false;/didWarnAboutUsingAct = true;/' ../../node_modules/react-dom/cjs/react-dom-test-utils.development.js",
|
||||
"build": "rimraf dist && concurrently -n snap,runtime \"tsc --build\" \"yarn --silent workspace react-compiler-runtime build --silent\"",
|
||||
"test": "echo 'no tests'",
|
||||
"prettier": "prettier --write 'src/**/*.ts'"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
#
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
yarn --silent workspace react-compiler-runtime link
|
||||
yarn --silent workspace snap link react-compiler-runtime
|
||||
@@ -434,6 +434,7 @@ const skipFilter = new Set([
|
||||
'todo.useContext-mutate-context-in-callback',
|
||||
'loop-unused-let',
|
||||
'reanimated-no-memo-arg',
|
||||
'reanimated-shared-value-writes',
|
||||
|
||||
'userspace-use-memo-cache',
|
||||
'transitive-freeze-function-expressions',
|
||||
@@ -478,6 +479,8 @@ const skipFilter = new Set([
|
||||
'fbt/bug-fbt-plural-multiple-function-calls',
|
||||
'fbt/bug-fbt-plural-multiple-mixed-call-tag',
|
||||
'bug-invalid-hoisting-functionexpr',
|
||||
'bug-try-catch-maybe-null-dependency',
|
||||
'reduce-reactive-deps/bug-infer-function-cond-access-not-hoisted',
|
||||
'bug-invalid-phi-as-dependency',
|
||||
'reduce-reactive-deps/bug-merge-uncond-optional-chain-and-cond',
|
||||
'original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block',
|
||||
|
||||
@@ -48,7 +48,6 @@ function makePluginOptions(
|
||||
let enableEmitFreeze = null;
|
||||
let enableEmitHookGuards = null;
|
||||
let compilationMode: CompilationMode = 'all';
|
||||
let runtimeModule = null;
|
||||
let panicThreshold: PanicThresholdOptions = 'all_errors';
|
||||
let hookPattern: string | null = null;
|
||||
// TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false
|
||||
@@ -56,6 +55,7 @@ function makePluginOptions(
|
||||
let enableChangeDetectionForDebugging = null;
|
||||
let customMacros: null | Array<Macro> = null;
|
||||
let validateBlocklistedImports = null;
|
||||
let target = '19' as const;
|
||||
|
||||
if (firstLine.indexOf('@compilationMode(annotation)') !== -1) {
|
||||
assert(
|
||||
@@ -103,10 +103,13 @@ function makePluginOptions(
|
||||
importSpecifierName: '$dispatcherGuard',
|
||||
};
|
||||
}
|
||||
const runtimeModuleMatch = /@runtimeModule="([^"]+)"/.exec(firstLine);
|
||||
if (runtimeModuleMatch) {
|
||||
runtimeModule = runtimeModuleMatch[1];
|
||||
|
||||
const targetMatch = /@target="([^"]+)"/.exec(firstLine);
|
||||
if (targetMatch) {
|
||||
// @ts-ignore
|
||||
target = targetMatch[1];
|
||||
}
|
||||
|
||||
if (firstLine.includes('@panicThreshold(none)')) {
|
||||
panicThreshold = 'none';
|
||||
}
|
||||
@@ -243,11 +246,11 @@ function makePluginOptions(
|
||||
gating,
|
||||
panicThreshold,
|
||||
noEmit: false,
|
||||
runtimeModule,
|
||||
eslintSuppressionRules,
|
||||
flowSuppressions,
|
||||
ignoreUseNoForget,
|
||||
enableReanimatedCheck: false,
|
||||
target,
|
||||
};
|
||||
return [options, logs];
|
||||
}
|
||||
|
||||
@@ -8,16 +8,11 @@
|
||||
import {render} from '@testing-library/react';
|
||||
import {JSDOM} from 'jsdom';
|
||||
import React, {MutableRefObject} from 'react';
|
||||
// @ts-ignore
|
||||
import {c as useMemoCache} from 'react/compiler-runtime';
|
||||
import util from 'util';
|
||||
import {z} from 'zod';
|
||||
import {fromZodError} from 'zod-validation-error';
|
||||
import {initFbt, toJSON} from './shared-runtime';
|
||||
|
||||
// @ts-ignore
|
||||
React.c = useMemoCache;
|
||||
|
||||
/**
|
||||
* Set up the global environment for JSDOM tests.
|
||||
* This is a hack to let us share code and setup between the test
|
||||
|
||||
@@ -2,6 +2,7 @@ const PUBLISHABLE_PACKAGES = [
|
||||
'babel-plugin-react-compiler',
|
||||
'eslint-plugin-react-compiler',
|
||||
'react-compiler-healthcheck',
|
||||
'react-compiler-runtime',
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
|
||||
+16
-1
@@ -185,7 +185,7 @@ export function processReply(
|
||||
temporaryReferences: void | TemporaryReferenceSet,
|
||||
resolve: (string | FormData) => void,
|
||||
reject: (error: mixed) => void,
|
||||
): void {
|
||||
): (reason: mixed) => void {
|
||||
let nextPartId = 1;
|
||||
let pendingParts = 0;
|
||||
let formData: null | FormData = null;
|
||||
@@ -841,6 +841,19 @@ export function processReply(
|
||||
return JSON.stringify(model, resolveToJSON);
|
||||
}
|
||||
|
||||
function abort(reason: mixed): void {
|
||||
if (pendingParts > 0) {
|
||||
pendingParts = 0; // Don't resolve again later.
|
||||
// Resolve with what we have so far, which may have holes at this point.
|
||||
// They'll error when the stream completes on the server.
|
||||
if (formData === null) {
|
||||
resolve(json);
|
||||
} else {
|
||||
resolve(formData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const json = serializeModel(root, 0);
|
||||
|
||||
if (formData === null) {
|
||||
@@ -854,6 +867,8 @@ export function processReply(
|
||||
resolve(formData);
|
||||
}
|
||||
}
|
||||
|
||||
return abort;
|
||||
}
|
||||
|
||||
const boundCache: WeakMap<
|
||||
|
||||
+31
-10
@@ -26,8 +26,7 @@ import type {
|
||||
import type {
|
||||
DevToolsHook,
|
||||
DevToolsHookSettings,
|
||||
ReloadAndProfileConfig,
|
||||
ReloadAndProfileConfigPersistence,
|
||||
ProfilingSettings,
|
||||
} from 'react-devtools-shared/src/backend/types';
|
||||
import type {ResolveNativeStyle} from 'react-devtools-shared/src/backend/NativeStyleEditor/setupNativeStyleEditor';
|
||||
|
||||
@@ -42,7 +41,9 @@ type ConnectOptions = {
|
||||
websocket?: ?WebSocket,
|
||||
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
|
||||
isReloadAndProfileSupported?: boolean,
|
||||
reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence,
|
||||
isProfiling?: boolean,
|
||||
onReloadAndProfile?: (recordChangeDescriptions: boolean) => void,
|
||||
onReloadAndProfileFlagsReset?: () => void,
|
||||
};
|
||||
|
||||
let savedComponentFilters: Array<ComponentFilter> =
|
||||
@@ -63,9 +64,15 @@ export function initialize(
|
||||
maybeSettingsOrSettingsPromise?:
|
||||
| DevToolsHookSettings
|
||||
| Promise<DevToolsHookSettings>,
|
||||
reloadAndProfileConfig?: ReloadAndProfileConfig,
|
||||
shouldStartProfilingNow: boolean = false,
|
||||
profilingSettings?: ProfilingSettings,
|
||||
) {
|
||||
installHook(window, maybeSettingsOrSettingsPromise, reloadAndProfileConfig);
|
||||
installHook(
|
||||
window,
|
||||
maybeSettingsOrSettingsPromise,
|
||||
shouldStartProfilingNow,
|
||||
profilingSettings,
|
||||
);
|
||||
}
|
||||
|
||||
export function connectToDevTools(options: ?ConnectOptions) {
|
||||
@@ -86,7 +93,9 @@ export function connectToDevTools(options: ?ConnectOptions) {
|
||||
isAppActive = () => true,
|
||||
onSettingsUpdated,
|
||||
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
|
||||
reloadAndProfileConfigPersistence,
|
||||
isProfiling,
|
||||
onReloadAndProfile,
|
||||
onReloadAndProfileFlagsReset,
|
||||
} = options || {};
|
||||
|
||||
const protocol = useHttps ? 'wss' : 'ws';
|
||||
@@ -180,7 +189,11 @@ export function connectToDevTools(options: ?ConnectOptions) {
|
||||
|
||||
// TODO (npm-packages) Warn if "isBackendStorageAPISupported"
|
||||
// $FlowFixMe[incompatible-call] found when upgrading Flow
|
||||
const agent = new Agent(bridge, reloadAndProfileConfigPersistence);
|
||||
const agent = new Agent(bridge, isProfiling, onReloadAndProfile);
|
||||
if (typeof onReloadAndProfileFlagsReset === 'function') {
|
||||
onReloadAndProfileFlagsReset();
|
||||
}
|
||||
|
||||
if (onSettingsUpdated != null) {
|
||||
agent.addListener('updateHookSettings', onSettingsUpdated);
|
||||
}
|
||||
@@ -320,7 +333,9 @@ type ConnectWithCustomMessagingOptions = {
|
||||
resolveRNStyle?: ResolveNativeStyle,
|
||||
onSettingsUpdated?: (settings: $ReadOnly<DevToolsHookSettings>) => void,
|
||||
isReloadAndProfileSupported?: boolean,
|
||||
reloadAndProfileConfigPersistence?: ReloadAndProfileConfigPersistence,
|
||||
isProfiling?: boolean,
|
||||
onReloadAndProfile?: (recordChangeDescriptions: boolean) => void,
|
||||
onReloadAndProfileFlagsReset?: () => void,
|
||||
};
|
||||
|
||||
export function connectWithCustomMessagingProtocol({
|
||||
@@ -331,7 +346,9 @@ export function connectWithCustomMessagingProtocol({
|
||||
resolveRNStyle,
|
||||
onSettingsUpdated,
|
||||
isReloadAndProfileSupported = getIsReloadAndProfileSupported(),
|
||||
reloadAndProfileConfigPersistence,
|
||||
isProfiling,
|
||||
onReloadAndProfile,
|
||||
onReloadAndProfileFlagsReset,
|
||||
}: ConnectWithCustomMessagingOptions): Function {
|
||||
const hook: ?DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
if (hook == null) {
|
||||
@@ -368,7 +385,11 @@ export function connectWithCustomMessagingProtocol({
|
||||
bridge.send('overrideComponentFilters', savedComponentFilters);
|
||||
}
|
||||
|
||||
const agent = new Agent(bridge, reloadAndProfileConfigPersistence);
|
||||
const agent = new Agent(bridge, isProfiling, onReloadAndProfile);
|
||||
if (typeof onReloadAndProfileFlagsReset === 'function') {
|
||||
onReloadAndProfileFlagsReset();
|
||||
}
|
||||
|
||||
if (onSettingsUpdated != null) {
|
||||
agent.addListener('updateHookSettings', onSettingsUpdated);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,14 @@ import type {
|
||||
import {hasAssignedBackend} from 'react-devtools-shared/src/backend/utils';
|
||||
import {COMPACT_VERSION_NAME} from 'react-devtools-extensions/src/utils';
|
||||
import {getIsReloadAndProfileSupported} from 'react-devtools-shared/src/utils';
|
||||
import {
|
||||
getIfReloadedAndProfiling,
|
||||
onReloadAndProfile,
|
||||
onReloadAndProfileFlagsReset,
|
||||
} from 'react-devtools-shared/src/utils';
|
||||
|
||||
let welcomeHasInitialized = false;
|
||||
const requiredBackends = new Set<string>();
|
||||
|
||||
function welcome(event: $FlowFixMe) {
|
||||
if (
|
||||
@@ -49,8 +55,6 @@ function welcome(event: $FlowFixMe) {
|
||||
setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__);
|
||||
}
|
||||
|
||||
window.addEventListener('message', welcome);
|
||||
|
||||
function setup(hook: ?DevToolsHook) {
|
||||
// this should not happen, but Chrome can be weird sometimes
|
||||
if (hook == null) {
|
||||
@@ -71,20 +75,27 @@ function setup(hook: ?DevToolsHook) {
|
||||
updateRequiredBackends();
|
||||
|
||||
// register renderers that inject themselves later.
|
||||
hook.sub('renderer', ({renderer}) => {
|
||||
const unsubscribeRendererListener = hook.sub('renderer', ({renderer}) => {
|
||||
registerRenderer(renderer, hook);
|
||||
updateRequiredBackends();
|
||||
});
|
||||
|
||||
// listen for backend installations.
|
||||
hook.sub('devtools-backend-installed', version => {
|
||||
activateBackend(version, hook);
|
||||
updateRequiredBackends();
|
||||
const unsubscribeBackendInstallationListener = hook.sub(
|
||||
'devtools-backend-installed',
|
||||
version => {
|
||||
activateBackend(version, hook);
|
||||
updateRequiredBackends();
|
||||
},
|
||||
);
|
||||
|
||||
const unsubscribeShutdownListener: () => void = hook.sub('shutdown', () => {
|
||||
unsubscribeRendererListener();
|
||||
unsubscribeBackendInstallationListener();
|
||||
unsubscribeShutdownListener();
|
||||
});
|
||||
}
|
||||
|
||||
const requiredBackends = new Set<string>();
|
||||
|
||||
function registerRenderer(renderer: ReactRenderer, hook: DevToolsHook) {
|
||||
let version = renderer.reconcilerVersion || renderer.version;
|
||||
if (!hasAssignedBackend(version)) {
|
||||
@@ -134,11 +145,20 @@ function activateBackend(version: string, hook: DevToolsHook) {
|
||||
},
|
||||
});
|
||||
|
||||
const agent = new Agent(bridge);
|
||||
const agent = new Agent(
|
||||
bridge,
|
||||
getIfReloadedAndProfiling(),
|
||||
onReloadAndProfile,
|
||||
);
|
||||
// Agent read flags successfully, we can count it as successful launch
|
||||
// Clean up flags, so that next reload won't start profiling
|
||||
onReloadAndProfileFlagsReset();
|
||||
|
||||
agent.addListener('shutdown', () => {
|
||||
// If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down,
|
||||
// and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here.
|
||||
hook.emit('shutdown');
|
||||
delete window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__;
|
||||
});
|
||||
|
||||
initBackend(hook, agent, window, getIsReloadAndProfileSupported());
|
||||
@@ -178,3 +198,13 @@ function updateRequiredBackends() {
|
||||
'*',
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make sure this is executed only once in case Frontend is reloaded multiple times while Backend is initializing
|
||||
* We can't use `reactDevToolsAgent` field on a global Hook object, because it only cleaned up after both Frontend and Backend initialized
|
||||
*/
|
||||
if (!window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__) {
|
||||
window.__REACT_DEVTOOLS_BACKEND_MANAGER_INJECTED__ = true;
|
||||
|
||||
window.addEventListener('message', welcome);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import {installHook} from 'react-devtools-shared/src/hook';
|
||||
import {
|
||||
getIfReloadedAndProfiling,
|
||||
getProfilingSettings,
|
||||
} from 'react-devtools-shared/src/utils';
|
||||
|
||||
let resolveHookSettingsInjection;
|
||||
|
||||
@@ -34,8 +38,15 @@ if (!window.hasOwnProperty('__REACT_DEVTOOLS_GLOBAL_HOOK__')) {
|
||||
payload: {handshake: true},
|
||||
});
|
||||
|
||||
const shouldStartProfiling = getIfReloadedAndProfiling();
|
||||
const profilingSettings = getProfilingSettings();
|
||||
// Can't delay hook installation, inject settings lazily
|
||||
installHook(window, hookSettingsPromise);
|
||||
installHook(
|
||||
window,
|
||||
hookSettingsPromise,
|
||||
shouldStartProfiling,
|
||||
profilingSettings,
|
||||
);
|
||||
|
||||
// Detect React
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user