mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
[compiler] moduleTypeProvider support for aliasing signatures (#33526)
This allows us to type things like `nullthrows()` or `identity()` functions where the return type is polymorphic on the input. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33526). * #33571 * #33558 * #33547 * #33543 * #33533 * #33532 * #33530 * __->__ #33526 * #33522 * #33518
This commit is contained in:
@@ -5,7 +5,14 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Effect, makeIdentifierId, ValueKind, ValueReason} from './HIR';
|
||||
import {
|
||||
Effect,
|
||||
GeneratedSource,
|
||||
makeIdentifierId,
|
||||
Place,
|
||||
ValueKind,
|
||||
ValueReason,
|
||||
} from './HIR';
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
@@ -37,10 +44,15 @@ import {
|
||||
signatureArgument,
|
||||
} from './ObjectShape';
|
||||
import {BuiltInType, ObjectType, PolyType} from './Types';
|
||||
import {TypeConfig} from './TypeSchema';
|
||||
import {
|
||||
AliasingEffectConfig,
|
||||
AliasingSignatureConfig,
|
||||
TypeConfig,
|
||||
} from './TypeSchema';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {isHookName} from './Environment';
|
||||
import {CompilerError, SourceLocation} from '..';
|
||||
import {AliasingEffect, AliasingSignature} from '../Inference/AliasingEffects';
|
||||
|
||||
/*
|
||||
* This file exports types and defaults for JavaScript global objects.
|
||||
@@ -891,6 +903,10 @@ export function installTypeConfig(
|
||||
}
|
||||
}
|
||||
case 'function': {
|
||||
const aliasing =
|
||||
typeConfig.aliasing != null
|
||||
? parseAliasingSignatureConfig(typeConfig.aliasing, moduleName, loc)
|
||||
: null;
|
||||
return addFunction(shapes, [], {
|
||||
positionalParams: typeConfig.positionalParams,
|
||||
restParam: typeConfig.restParam,
|
||||
@@ -906,9 +922,14 @@ export function installTypeConfig(
|
||||
noAlias: typeConfig.noAlias === true,
|
||||
mutableOnlyIfOperandsAreMutable:
|
||||
typeConfig.mutableOnlyIfOperandsAreMutable === true,
|
||||
aliasing,
|
||||
});
|
||||
}
|
||||
case 'hook': {
|
||||
const aliasing =
|
||||
typeConfig.aliasing != null
|
||||
? parseAliasingSignatureConfig(typeConfig.aliasing, moduleName, loc)
|
||||
: null;
|
||||
return addHook(shapes, {
|
||||
hookKind: 'Custom',
|
||||
positionalParams: typeConfig.positionalParams ?? [],
|
||||
@@ -923,6 +944,7 @@ export function installTypeConfig(
|
||||
),
|
||||
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
|
||||
noAlias: typeConfig.noAlias === true,
|
||||
aliasing,
|
||||
});
|
||||
}
|
||||
case 'object': {
|
||||
@@ -965,6 +987,90 @@ export function installTypeConfig(
|
||||
}
|
||||
}
|
||||
|
||||
function parseAliasingSignatureConfig(
|
||||
typeConfig: AliasingSignatureConfig,
|
||||
moduleName: string,
|
||||
loc: SourceLocation,
|
||||
): AliasingSignature {
|
||||
const lifetimes = new Map<string, Place>();
|
||||
function define(temp: string): Place {
|
||||
CompilerError.invariant(!lifetimes.has(temp), {
|
||||
reason: `Invalid type configuration for module`,
|
||||
description: `Expected aliasing signature to have unique names for receiver, params, rest, returns, and temporaries in module '${moduleName}'`,
|
||||
loc,
|
||||
});
|
||||
const place = signatureArgument(lifetimes.size);
|
||||
lifetimes.set(temp, place);
|
||||
return place;
|
||||
}
|
||||
function lookup(temp: string): Place {
|
||||
const place = lifetimes.get(temp);
|
||||
CompilerError.invariant(place != null, {
|
||||
reason: `Invalid type configuration for module`,
|
||||
description: `Expected aliasing signature effects to reference known names from receiver/params/rest/returns/temporaries, but '${temp}' is not a known name in '${moduleName}'`,
|
||||
loc,
|
||||
});
|
||||
return place;
|
||||
}
|
||||
const receiver = define(typeConfig.receiver);
|
||||
const params = typeConfig.params.map(define);
|
||||
const rest = typeConfig.rest != null ? define(typeConfig.rest) : null;
|
||||
const returns = define(typeConfig.returns);
|
||||
const temporaries = typeConfig.temporaries.map(define);
|
||||
const effects = typeConfig.effects.map(
|
||||
(effect: AliasingEffectConfig): AliasingEffect => {
|
||||
switch (effect.kind) {
|
||||
case 'Assign': {
|
||||
return {
|
||||
kind: 'Assign',
|
||||
from: lookup(effect.from),
|
||||
into: lookup(effect.into),
|
||||
};
|
||||
}
|
||||
case 'Create': {
|
||||
return {
|
||||
kind: 'Create',
|
||||
into: lookup(effect.into),
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
value: effect.value,
|
||||
};
|
||||
}
|
||||
case 'Freeze': {
|
||||
return {
|
||||
kind: 'Freeze',
|
||||
value: lookup(effect.value),
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
};
|
||||
}
|
||||
case 'Impure': {
|
||||
return {
|
||||
kind: 'Impure',
|
||||
place: lookup(effect.place),
|
||||
error: CompilerError.throwTodo({
|
||||
reason: 'Support impure effect declarations',
|
||||
loc: GeneratedSource,
|
||||
}),
|
||||
};
|
||||
}
|
||||
default: {
|
||||
assertExhaustive(
|
||||
effect,
|
||||
`Unexpected effect kind '${(effect as any).kind}'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
return {
|
||||
receiver: receiver.identifier.id,
|
||||
params: params.map(p => p.identifier.id),
|
||||
rest: rest != null ? rest.identifier.id : null,
|
||||
returns: returns.identifier.id,
|
||||
temporaries,
|
||||
effects,
|
||||
};
|
||||
}
|
||||
|
||||
export function getReanimatedModuleType(registry: ShapeRegistry): ObjectType {
|
||||
// hooks that freeze args and return frozen value
|
||||
const frozenHooks = [
|
||||
|
||||
@@ -31,6 +31,86 @@ export const ObjectTypeSchema: z.ZodType<ObjectTypeConfig> = z.object({
|
||||
properties: ObjectPropertiesSchema.nullable(),
|
||||
});
|
||||
|
||||
export const LifetimeIdSchema = z.string().refine(id => id.startsWith('@'), {
|
||||
message: "Placeholder names must start with '@'",
|
||||
});
|
||||
|
||||
export type FreezeEffectConfig = {
|
||||
kind: 'Freeze';
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const FreezeEffectSchema: z.ZodType<FreezeEffectConfig> = z.object({
|
||||
kind: z.literal('Freeze'),
|
||||
value: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
export type CreateEffectConfig = {
|
||||
kind: 'Create';
|
||||
into: string;
|
||||
value: ValueKind;
|
||||
};
|
||||
|
||||
export const CreateEffectSchema: z.ZodType<CreateEffectConfig> = z.object({
|
||||
kind: z.literal('Create'),
|
||||
into: LifetimeIdSchema,
|
||||
value: ValueKindSchema,
|
||||
});
|
||||
|
||||
export type AssignEffectConfig = {
|
||||
kind: 'Assign';
|
||||
from: string;
|
||||
into: string;
|
||||
};
|
||||
|
||||
export const AssignEffectSchema: z.ZodType<AssignEffectConfig> = z.object({
|
||||
kind: z.literal('Assign'),
|
||||
from: LifetimeIdSchema,
|
||||
into: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
export type ImpureEffectConfig = {
|
||||
kind: 'Impure';
|
||||
place: string;
|
||||
};
|
||||
|
||||
export const ImpureEffectSchema: z.ZodType<ImpureEffectConfig> = z.object({
|
||||
kind: z.literal('Impure'),
|
||||
place: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
export type AliasingEffectConfig =
|
||||
| FreezeEffectConfig
|
||||
| CreateEffectConfig
|
||||
| AssignEffectConfig
|
||||
| ImpureEffectConfig;
|
||||
|
||||
export const AliasingEffectSchema: z.ZodType<AliasingEffectConfig> = z.union([
|
||||
FreezeEffectSchema,
|
||||
CreateEffectSchema,
|
||||
AssignEffectSchema,
|
||||
ImpureEffectSchema,
|
||||
]);
|
||||
|
||||
export type AliasingSignatureConfig = {
|
||||
receiver: string;
|
||||
params: Array<string>;
|
||||
rest: string | null;
|
||||
returns: string;
|
||||
effects: Array<AliasingEffectConfig>;
|
||||
temporaries: Array<string>;
|
||||
};
|
||||
|
||||
export const AliasingSignatureSchema: z.ZodType<AliasingSignatureConfig> =
|
||||
z.object({
|
||||
receiver: LifetimeIdSchema,
|
||||
params: z.array(LifetimeIdSchema),
|
||||
rest: LifetimeIdSchema.nullable(),
|
||||
returns: LifetimeIdSchema,
|
||||
effects: z.array(AliasingEffectSchema),
|
||||
temporaries: z.array(LifetimeIdSchema),
|
||||
});
|
||||
|
||||
export type FunctionTypeConfig = {
|
||||
kind: 'function';
|
||||
positionalParams: Array<Effect>;
|
||||
@@ -42,6 +122,7 @@ export type FunctionTypeConfig = {
|
||||
mutableOnlyIfOperandsAreMutable?: boolean | null | undefined;
|
||||
impure?: boolean | null | undefined;
|
||||
canonicalName?: string | null | undefined;
|
||||
aliasing?: AliasingSignatureConfig | null | undefined;
|
||||
};
|
||||
export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
|
||||
kind: z.literal('function'),
|
||||
@@ -54,6 +135,7 @@ export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
|
||||
mutableOnlyIfOperandsAreMutable: z.boolean().nullable().optional(),
|
||||
impure: z.boolean().nullable().optional(),
|
||||
canonicalName: z.string().nullable().optional(),
|
||||
aliasing: AliasingSignatureSchema.nullable().optional(),
|
||||
});
|
||||
|
||||
export type HookTypeConfig = {
|
||||
@@ -63,6 +145,7 @@ export type HookTypeConfig = {
|
||||
returnType: TypeConfig;
|
||||
returnValueKind?: ValueKind | null | undefined;
|
||||
noAlias?: boolean | null | undefined;
|
||||
aliasing?: AliasingSignatureConfig | null | undefined;
|
||||
};
|
||||
export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
|
||||
kind: z.literal('hook'),
|
||||
@@ -71,6 +154,7 @@ export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
|
||||
returnType: z.lazy(() => TypeSchema),
|
||||
returnValueKind: ValueKindSchema.nullable().optional(),
|
||||
noAlias: z.boolean().nullable().optional(),
|
||||
aliasing: AliasingSignatureSchema.nullable().optional(),
|
||||
});
|
||||
|
||||
export type BuiltInTypeConfig =
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableNewMutationAliasingModel
|
||||
|
||||
import {
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
typedIdentity,
|
||||
useIdentity,
|
||||
ValidateMemoization,
|
||||
} from 'shared-runtime';
|
||||
|
||||
function Component({a, b}) {
|
||||
// create a mutable value with input `a`
|
||||
const x = makeObject_Primitives(a);
|
||||
|
||||
// freeze the value
|
||||
useIdentity(x);
|
||||
|
||||
// known to pass-through via aliasing signature
|
||||
const x2 = typedIdentity(x);
|
||||
|
||||
// Unknown function so we assume it conditionally mutates,
|
||||
// but x2 is frozen so this downgrades to a read.
|
||||
// x should *not* take b as a dependency
|
||||
identity(x2, b);
|
||||
|
||||
return <ValidateMemoization inputs={[a]} output={x} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 0, b: 0}],
|
||||
sequentialRenders: [
|
||||
{a: 0, b: 0},
|
||||
{a: 1, b: 0},
|
||||
{a: 1, b: 1},
|
||||
{a: 0, b: 1},
|
||||
{a: 0, b: 0},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
|
||||
|
||||
import {
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
typedIdentity,
|
||||
useIdentity,
|
||||
ValidateMemoization,
|
||||
} from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(7);
|
||||
const { a, b } = t0;
|
||||
let t1;
|
||||
if ($[0] !== a) {
|
||||
t1 = makeObject_Primitives(a);
|
||||
$[0] = a;
|
||||
$[1] = t1;
|
||||
} else {
|
||||
t1 = $[1];
|
||||
}
|
||||
const x = t1;
|
||||
|
||||
useIdentity(x);
|
||||
|
||||
const x2 = typedIdentity(x);
|
||||
|
||||
identity(x2, b);
|
||||
let t2;
|
||||
if ($[2] !== a) {
|
||||
t2 = [a];
|
||||
$[2] = a;
|
||||
$[3] = t2;
|
||||
} else {
|
||||
t2 = $[3];
|
||||
}
|
||||
let t3;
|
||||
if ($[4] !== t2 || $[5] !== x) {
|
||||
t3 = <ValidateMemoization inputs={t2} output={x} />;
|
||||
$[4] = t2;
|
||||
$[5] = x;
|
||||
$[6] = t3;
|
||||
} else {
|
||||
t3 = $[6];
|
||||
}
|
||||
return t3;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: 0, b: 0 }],
|
||||
sequentialRenders: [
|
||||
{ a: 0, b: 0 },
|
||||
{ a: 1, b: 0 },
|
||||
{ a: 1, b: 1 },
|
||||
{ a: 0, b: 1 },
|
||||
{ a: 0, b: 0 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[0],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[1],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[1],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[0],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[0],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
// @enableNewMutationAliasingModel
|
||||
|
||||
import {
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
typedIdentity,
|
||||
useIdentity,
|
||||
ValidateMemoization,
|
||||
} from 'shared-runtime';
|
||||
|
||||
function Component({a, b}) {
|
||||
// create a mutable value with input `a`
|
||||
const x = makeObject_Primitives(a);
|
||||
|
||||
// freeze the value
|
||||
useIdentity(x);
|
||||
|
||||
// known to pass-through via aliasing signature
|
||||
const x2 = typedIdentity(x);
|
||||
|
||||
// Unknown function so we assume it conditionally mutates,
|
||||
// but x2 is frozen so this downgrades to a read.
|
||||
// x should *not* take b as a dependency
|
||||
identity(x2, b);
|
||||
|
||||
return <ValidateMemoization inputs={[a]} output={x} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 0, b: 0}],
|
||||
sequentialRenders: [
|
||||
{a: 0, b: 0},
|
||||
{a: 1, b: 0},
|
||||
{a: 1, b: 1},
|
||||
{a: 0, b: 1},
|
||||
{a: 0, b: 0},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableNewMutationAliasingModel
|
||||
|
||||
import {
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
typedIdentity,
|
||||
useIdentity,
|
||||
ValidateMemoization,
|
||||
} from 'shared-runtime';
|
||||
|
||||
function Component({a, b}) {
|
||||
// create a mutable value with input `a`
|
||||
const x = makeObject_Primitives(a);
|
||||
|
||||
// known to pass-through via aliasing signature
|
||||
const x2 = typedIdentity(x);
|
||||
|
||||
// Unknown function so we assume it conditionally mutates,
|
||||
// and x is still mutable so
|
||||
identity(x2, b);
|
||||
|
||||
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 0, b: 0}],
|
||||
sequentialRenders: [
|
||||
{a: 0, b: 0},
|
||||
{a: 1, b: 0},
|
||||
{a: 1, b: 1},
|
||||
{a: 0, b: 1},
|
||||
{a: 0, b: 0},
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime"; // @enableNewMutationAliasingModel
|
||||
|
||||
import {
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
typedIdentity,
|
||||
useIdentity,
|
||||
ValidateMemoization,
|
||||
} from "shared-runtime";
|
||||
|
||||
function Component(t0) {
|
||||
const $ = _c(9);
|
||||
const { a, b } = t0;
|
||||
let x;
|
||||
if ($[0] !== a || $[1] !== b) {
|
||||
x = makeObject_Primitives(a);
|
||||
|
||||
const x2 = typedIdentity(x);
|
||||
|
||||
identity(x2, b);
|
||||
$[0] = a;
|
||||
$[1] = b;
|
||||
$[2] = x;
|
||||
} else {
|
||||
x = $[2];
|
||||
}
|
||||
let t1;
|
||||
if ($[3] !== a || $[4] !== b) {
|
||||
t1 = [a, b];
|
||||
$[3] = a;
|
||||
$[4] = b;
|
||||
$[5] = t1;
|
||||
} else {
|
||||
t1 = $[5];
|
||||
}
|
||||
let t2;
|
||||
if ($[6] !== t1 || $[7] !== x) {
|
||||
t2 = <ValidateMemoization inputs={t1} output={x} />;
|
||||
$[6] = t1;
|
||||
$[7] = x;
|
||||
$[8] = t2;
|
||||
} else {
|
||||
t2 = $[8];
|
||||
}
|
||||
return t2;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{ a: 0, b: 0 }],
|
||||
sequentialRenders: [
|
||||
{ a: 0, b: 0 },
|
||||
{ a: 1, b: 0 },
|
||||
{ a: 1, b: 1 },
|
||||
{ a: 0, b: 1 },
|
||||
{ a: 0, b: 0 },
|
||||
],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>{"inputs":[0,0],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[1,0],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[1,1],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[0,1],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
<div>{"inputs":[0,0],"output":{"a":0,"b":"value1","c":true}}</div>
|
||||
@@ -0,0 +1,35 @@
|
||||
// @enableNewMutationAliasingModel
|
||||
|
||||
import {
|
||||
identity,
|
||||
makeObject_Primitives,
|
||||
typedIdentity,
|
||||
useIdentity,
|
||||
ValidateMemoization,
|
||||
} from 'shared-runtime';
|
||||
|
||||
function Component({a, b}) {
|
||||
// create a mutable value with input `a`
|
||||
const x = makeObject_Primitives(a);
|
||||
|
||||
// known to pass-through via aliasing signature
|
||||
const x2 = typedIdentity(x);
|
||||
|
||||
// Unknown function so we assume it conditionally mutates,
|
||||
// and x is still mutable so
|
||||
identity(x2, b);
|
||||
|
||||
return <ValidateMemoization inputs={[a, b]} output={x} />;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{a: 0, b: 0}],
|
||||
sequentialRenders: [
|
||||
{a: 0, b: 0},
|
||||
{a: 1, b: 0},
|
||||
{a: 1, b: 1},
|
||||
{a: 0, b: 1},
|
||||
{a: 0, b: 0},
|
||||
],
|
||||
};
|
||||
@@ -69,6 +69,22 @@ export function makeSharedRuntimeTypeProvider({
|
||||
returnValueKind: ValueKindEnum.Mutable,
|
||||
noAlias: true,
|
||||
},
|
||||
typedIdentity: {
|
||||
kind: 'function',
|
||||
positionalParams: [EffectEnum.Read],
|
||||
restParam: null,
|
||||
calleeEffect: EffectEnum.Read,
|
||||
returnType: {kind: 'type', name: 'Any'},
|
||||
returnValueKind: ValueKindEnum.Mutable,
|
||||
aliasing: {
|
||||
receiver: '@receiver',
|
||||
params: ['@value'],
|
||||
rest: null,
|
||||
returns: '@return',
|
||||
temporaries: [],
|
||||
effects: [{kind: 'Assign', from: '@value', into: '@return'}],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
} else if (moduleName === 'ReactCompilerTest') {
|
||||
|
||||
@@ -396,4 +396,8 @@ export function typedLog(...values: Array<any>): void {
|
||||
console.log(...values);
|
||||
}
|
||||
|
||||
export function typedIdentity<T>(value: T): T {
|
||||
return value;
|
||||
}
|
||||
|
||||
export default typedLog;
|
||||
|
||||
Reference in New Issue
Block a user