Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a5f77c261 | ||
|
|
34179fe344 | ||
|
|
0e7cdebb32 | ||
|
|
81d8115116 |
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {Effect, makeIdentifierId, ValueKind, ValueReason} from './HIR';
|
||||
import {Effect, ValueKind, ValueReason} from './HIR';
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
@@ -34,7 +34,6 @@ import {
|
||||
addFunction,
|
||||
addHook,
|
||||
addObject,
|
||||
signatureArgument,
|
||||
} from './ObjectShape';
|
||||
import {BuiltInType, ObjectType, PolyType} from './Types';
|
||||
import {TypeConfig} from './TypeSchema';
|
||||
@@ -646,35 +645,35 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
|
||||
hookKind: 'useEffect',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: {
|
||||
receiver: makeIdentifierId(0),
|
||||
receiver: '@receiver',
|
||||
params: [],
|
||||
rest: makeIdentifierId(1),
|
||||
returns: makeIdentifierId(2),
|
||||
temporaries: [signatureArgument(3)],
|
||||
rest: '@rest',
|
||||
returns: '@returns',
|
||||
temporaries: ['@effect'],
|
||||
effects: [
|
||||
// Freezes the function and deps
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: signatureArgument(1),
|
||||
value: '@rest',
|
||||
reason: ValueReason.Effect,
|
||||
},
|
||||
// Internally creates an effect object that captures the function and deps
|
||||
{
|
||||
kind: 'Create',
|
||||
into: signatureArgument(3),
|
||||
into: '@effect',
|
||||
value: ValueKind.Frozen,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
// The effect stores the function and dependencies
|
||||
{
|
||||
kind: 'Capture',
|
||||
from: signatureArgument(1),
|
||||
into: signatureArgument(3),
|
||||
from: '@rest',
|
||||
into: '@effect',
|
||||
},
|
||||
// Returns undefined
|
||||
{
|
||||
kind: 'Create',
|
||||
into: signatureArgument(2),
|
||||
into: '@returns',
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
@@ -906,6 +905,7 @@ export function installTypeConfig(
|
||||
noAlias: typeConfig.noAlias === true,
|
||||
mutableOnlyIfOperandsAreMutable:
|
||||
typeConfig.mutableOnlyIfOperandsAreMutable === true,
|
||||
aliasing: typeConfig.aliasing,
|
||||
});
|
||||
}
|
||||
case 'hook': {
|
||||
@@ -923,6 +923,7 @@ export function installTypeConfig(
|
||||
),
|
||||
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
|
||||
noAlias: typeConfig.noAlias === true,
|
||||
aliasing: typeConfig.aliasing,
|
||||
});
|
||||
}
|
||||
case 'object': {
|
||||
|
||||
@@ -1453,6 +1453,20 @@ export const ValueKindSchema = z.enum([
|
||||
ValueKind.Context,
|
||||
]);
|
||||
|
||||
export const ValueReasonSchema = z.enum([
|
||||
ValueReason.Context,
|
||||
ValueReason.Effect,
|
||||
ValueReason.Global,
|
||||
ValueReason.HookCaptured,
|
||||
ValueReason.HookReturn,
|
||||
ValueReason.JsxCaptured,
|
||||
ValueReason.KnownReturnSignature,
|
||||
ValueReason.Other,
|
||||
ValueReason.ReactiveFunctionArgument,
|
||||
ValueReason.ReducerState,
|
||||
ValueReason.State,
|
||||
]);
|
||||
|
||||
// The effect with which a value is modified.
|
||||
export enum Effect {
|
||||
// Default value: not allowed after lifetime inference
|
||||
|
||||
@@ -6,14 +6,18 @@
|
||||
*/
|
||||
|
||||
import {CompilerError} from '../CompilerError';
|
||||
import {AliasingSignature} from '../Inference/AliasingEffects';
|
||||
import {AliasingEffect, AliasingSignature} from '../Inference/AliasingEffects';
|
||||
import {assertExhaustive} from '../Utils/utils';
|
||||
import {
|
||||
Effect,
|
||||
GeneratedSource,
|
||||
Hole,
|
||||
makeDeclarationId,
|
||||
makeIdentifierId,
|
||||
makeInstructionId,
|
||||
Place,
|
||||
SourceLocation,
|
||||
SpreadPattern,
|
||||
ValueKind,
|
||||
ValueReason,
|
||||
} from './HIR';
|
||||
@@ -25,6 +29,7 @@ import {
|
||||
PolyType,
|
||||
PrimitiveType,
|
||||
} from './Types';
|
||||
import {AliasingEffectConfig, AliasingSignatureConfig} from './TypeSchema';
|
||||
|
||||
/*
|
||||
* This file exports types and defaults for JavaScript object shapes. These are
|
||||
@@ -53,13 +58,20 @@ function createAnonId(): string {
|
||||
export function addFunction(
|
||||
registry: ShapeRegistry,
|
||||
properties: Iterable<[string, BuiltInType | PolyType]>,
|
||||
fn: Omit<FunctionSignature, 'hookKind'>,
|
||||
fn: Omit<FunctionSignature, 'hookKind' | 'aliasing'> & {
|
||||
aliasing?: AliasingSignatureConfig | null | undefined;
|
||||
},
|
||||
id: string | null = null,
|
||||
isConstructor: boolean = false,
|
||||
): FunctionType {
|
||||
const shapeId = id ?? createAnonId();
|
||||
const aliasing =
|
||||
fn.aliasing != null
|
||||
? parseAliasingSignatureConfig(fn.aliasing, '<builtin>', GeneratedSource)
|
||||
: null;
|
||||
addShape(registry, shapeId, properties, {
|
||||
...fn,
|
||||
aliasing,
|
||||
hookKind: null,
|
||||
});
|
||||
return {
|
||||
@@ -77,11 +89,18 @@ export function addFunction(
|
||||
*/
|
||||
export function addHook(
|
||||
registry: ShapeRegistry,
|
||||
fn: FunctionSignature & {hookKind: HookKind},
|
||||
fn: Omit<FunctionSignature, 'aliasing'> & {
|
||||
hookKind: HookKind;
|
||||
aliasing?: AliasingSignatureConfig | null | undefined;
|
||||
},
|
||||
id: string | null = null,
|
||||
): FunctionType {
|
||||
const shapeId = id ?? createAnonId();
|
||||
addShape(registry, shapeId, [], fn);
|
||||
const aliasing =
|
||||
fn.aliasing != null
|
||||
? parseAliasingSignatureConfig(fn.aliasing, '<builtin>', GeneratedSource)
|
||||
: null;
|
||||
addShape(registry, shapeId, [], {...fn, aliasing});
|
||||
return {
|
||||
kind: 'Function',
|
||||
return: fn.returnType,
|
||||
@@ -90,6 +109,129 @@ export function addHook(
|
||||
};
|
||||
}
|
||||
|
||||
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 'CreateFrom':
|
||||
case 'Capture':
|
||||
case 'Alias':
|
||||
case 'Assign': {
|
||||
const from = lookup(effect.from);
|
||||
const into = lookup(effect.into);
|
||||
return {
|
||||
kind: effect.kind,
|
||||
from,
|
||||
into,
|
||||
};
|
||||
}
|
||||
case 'Mutate':
|
||||
case 'MutateTransitiveConditionally': {
|
||||
const value = lookup(effect.value);
|
||||
return {kind: effect.kind, value};
|
||||
}
|
||||
case 'Create': {
|
||||
const into = lookup(effect.into);
|
||||
return {
|
||||
kind: 'Create',
|
||||
into,
|
||||
reason: effect.reason,
|
||||
value: effect.value,
|
||||
};
|
||||
}
|
||||
case 'Freeze': {
|
||||
const value = lookup(effect.value);
|
||||
return {
|
||||
kind: 'Freeze',
|
||||
value,
|
||||
reason: effect.reason,
|
||||
};
|
||||
}
|
||||
case 'Impure': {
|
||||
const place = lookup(effect.place);
|
||||
return {
|
||||
kind: 'Impure',
|
||||
place,
|
||||
error: CompilerError.throwTodo({
|
||||
reason: 'Support impure effect declarations',
|
||||
loc: GeneratedSource,
|
||||
}),
|
||||
};
|
||||
}
|
||||
case 'Apply': {
|
||||
const receiver = lookup(effect.receiver);
|
||||
const fn = lookup(effect.function);
|
||||
const args: Array<Place | SpreadPattern | Hole> = effect.args.map(
|
||||
arg => {
|
||||
if (typeof arg === 'string') {
|
||||
return lookup(arg);
|
||||
} else if (arg.kind === 'Spread') {
|
||||
return {kind: 'Spread', place: lookup(arg.place)};
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
},
|
||||
);
|
||||
const into = lookup(effect.into);
|
||||
return {
|
||||
kind: 'Apply',
|
||||
receiver,
|
||||
function: fn,
|
||||
mutatesFunction: effect.mutatesFunction,
|
||||
args,
|
||||
into,
|
||||
loc,
|
||||
signature: null,
|
||||
};
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Add an object to an existing ShapeRegistry.
|
||||
*
|
||||
@@ -192,8 +334,7 @@ export type FunctionSignature = {
|
||||
|
||||
canonicalName?: string;
|
||||
|
||||
aliasing?: AliasingSignature | null;
|
||||
todo_aliasing?: AliasingSignature | null;
|
||||
aliasing?: AliasingSignature | null | undefined;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -320,24 +461,24 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
calleeEffect: Effect.Store,
|
||||
returnValueKind: ValueKind.Primitive,
|
||||
aliasing: {
|
||||
receiver: makeIdentifierId(0),
|
||||
receiver: '@receiver',
|
||||
params: [],
|
||||
rest: makeIdentifierId(1),
|
||||
returns: makeIdentifierId(2),
|
||||
rest: '@rest',
|
||||
returns: '@returns',
|
||||
temporaries: [],
|
||||
effects: [
|
||||
// Push directly mutates the array itself
|
||||
{kind: 'Mutate', value: signatureArgument(0)},
|
||||
{kind: 'Mutate', value: '@receiver'},
|
||||
// The arguments are captured into the array
|
||||
{
|
||||
kind: 'Capture',
|
||||
from: signatureArgument(1),
|
||||
into: signatureArgument(0),
|
||||
from: '@rest',
|
||||
into: '@receiver',
|
||||
},
|
||||
// Returns the new length, a primitive
|
||||
{
|
||||
kind: 'Create',
|
||||
into: signatureArgument(2),
|
||||
into: '@returns',
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
@@ -374,58 +515,56 @@ addObject(BUILTIN_SHAPES, BuiltInArrayId, [
|
||||
noAlias: true,
|
||||
mutableOnlyIfOperandsAreMutable: true,
|
||||
aliasing: {
|
||||
receiver: makeIdentifierId(0),
|
||||
params: [makeIdentifierId(1)],
|
||||
receiver: '@receiver',
|
||||
params: ['@callback'],
|
||||
rest: null,
|
||||
returns: makeIdentifierId(2),
|
||||
returns: '@returns',
|
||||
temporaries: [
|
||||
// Temporary representing captured items of the receiver
|
||||
signatureArgument(3),
|
||||
'@item',
|
||||
// Temporary representing the result of the callback
|
||||
signatureArgument(4),
|
||||
'@callbackReturn',
|
||||
/*
|
||||
* Undefined `this` arg to the callback. Note the signature does not
|
||||
* support passing an explicit thisArg second param
|
||||
*/
|
||||
signatureArgument(5),
|
||||
'@thisArg',
|
||||
],
|
||||
effects: [
|
||||
// Map creates a new mutable array
|
||||
{
|
||||
kind: 'Create',
|
||||
into: signatureArgument(2),
|
||||
into: '@returns',
|
||||
value: ValueKind.Mutable,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
// The first arg to the callback is an item extracted from the receiver array
|
||||
{
|
||||
kind: 'CreateFrom',
|
||||
from: signatureArgument(0),
|
||||
into: signatureArgument(3),
|
||||
from: '@receiver',
|
||||
into: '@item',
|
||||
},
|
||||
// The undefined this for the callback
|
||||
{
|
||||
kind: 'Create',
|
||||
into: signatureArgument(5),
|
||||
into: '@thisArg',
|
||||
value: ValueKind.Primitive,
|
||||
reason: ValueReason.KnownReturnSignature,
|
||||
},
|
||||
// calls the callback, returning the result into a temporary
|
||||
{
|
||||
kind: 'Apply',
|
||||
receiver: signatureArgument(5),
|
||||
args: [signatureArgument(3), {kind: 'Hole'}, signatureArgument(0)],
|
||||
function: signatureArgument(1),
|
||||
into: signatureArgument(4),
|
||||
signature: null,
|
||||
receiver: '@thisArg',
|
||||
args: ['@item', {kind: 'Hole'}, '@receiver'],
|
||||
function: '@callback',
|
||||
into: '@callbackReturn',
|
||||
mutatesFunction: false,
|
||||
loc: GeneratedSource,
|
||||
},
|
||||
// captures the result of the callback into the return array
|
||||
{
|
||||
kind: 'Capture',
|
||||
from: signatureArgument(4),
|
||||
into: signatureArgument(2),
|
||||
from: '@callbackReturn',
|
||||
into: '@returns',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -577,28 +716,28 @@ addObject(BUILTIN_SHAPES, BuiltInSetId, [
|
||||
// returnValueKind is technically dependent on the ValueKind of the set itself
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
aliasing: {
|
||||
receiver: makeIdentifierId(0),
|
||||
receiver: '@receiver',
|
||||
params: [],
|
||||
rest: makeIdentifierId(1),
|
||||
returns: makeIdentifierId(2),
|
||||
rest: '@rest',
|
||||
returns: '@returns',
|
||||
temporaries: [],
|
||||
effects: [
|
||||
// Set.add returns the receiver Set
|
||||
{
|
||||
kind: 'Assign',
|
||||
from: signatureArgument(0),
|
||||
into: signatureArgument(2),
|
||||
from: '@receiver',
|
||||
into: '@returns',
|
||||
},
|
||||
// Set.add mutates the set itself
|
||||
{
|
||||
kind: 'Mutate',
|
||||
value: signatureArgument(0),
|
||||
value: '@receiver',
|
||||
},
|
||||
// Captures the rest params into the set
|
||||
{
|
||||
kind: 'Capture',
|
||||
from: signatureArgument(1),
|
||||
into: signatureArgument(0),
|
||||
from: '@rest',
|
||||
into: '@receiver',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -1303,30 +1442,30 @@ export const DefaultNonmutatingHook = addHook(
|
||||
hookKind: 'Custom',
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
aliasing: {
|
||||
receiver: makeIdentifierId(0),
|
||||
receiver: '@receiver',
|
||||
params: [],
|
||||
rest: makeIdentifierId(1),
|
||||
returns: makeIdentifierId(2),
|
||||
rest: '@rest',
|
||||
returns: '@returns',
|
||||
temporaries: [],
|
||||
effects: [
|
||||
// Freeze the arguments
|
||||
{
|
||||
kind: 'Freeze',
|
||||
value: signatureArgument(1),
|
||||
value: '@rest',
|
||||
reason: ValueReason.HookCaptured,
|
||||
},
|
||||
// Returns a frozen value
|
||||
{
|
||||
kind: 'Create',
|
||||
into: signatureArgument(2),
|
||||
into: '@returns',
|
||||
value: ValueKind.Frozen,
|
||||
reason: ValueReason.HookReturn,
|
||||
},
|
||||
// May alias any arguments into the return
|
||||
{
|
||||
kind: 'Alias',
|
||||
from: signatureArgument(1),
|
||||
into: signatureArgument(2),
|
||||
from: '@rest',
|
||||
into: '@returns',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -8,7 +8,12 @@
|
||||
import {isValidIdentifier} from '@babel/types';
|
||||
import {z} from 'zod';
|
||||
import {Effect, ValueKind} from '..';
|
||||
import {EffectSchema, ValueKindSchema} from './HIR';
|
||||
import {
|
||||
EffectSchema,
|
||||
ValueKindSchema,
|
||||
ValueReason,
|
||||
ValueReasonSchema,
|
||||
} from './HIR';
|
||||
|
||||
export type ObjectPropertiesConfig = {[key: string]: TypeConfig};
|
||||
export const ObjectPropertiesSchema: z.ZodType<ObjectPropertiesConfig> = z
|
||||
@@ -31,6 +36,194 @@ 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;
|
||||
reason: ValueReason;
|
||||
};
|
||||
|
||||
export const FreezeEffectSchema: z.ZodType<FreezeEffectConfig> = z.object({
|
||||
kind: z.literal('Freeze'),
|
||||
value: LifetimeIdSchema,
|
||||
reason: ValueReasonSchema,
|
||||
});
|
||||
|
||||
export type MutateEffectConfig = {
|
||||
kind: 'Mutate';
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const MutateEffectSchema: z.ZodType<MutateEffectConfig> = z.object({
|
||||
kind: z.literal('Mutate'),
|
||||
value: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
export type MutateTransitiveConditionallyConfig = {
|
||||
kind: 'MutateTransitiveConditionally';
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const MutateTransitiveConditionallySchema: z.ZodType<MutateTransitiveConditionallyConfig> =
|
||||
z.object({
|
||||
kind: z.literal('MutateTransitiveConditionally'),
|
||||
value: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
export type CreateEffectConfig = {
|
||||
kind: 'Create';
|
||||
into: string;
|
||||
value: ValueKind;
|
||||
reason: ValueReason;
|
||||
};
|
||||
|
||||
export const CreateEffectSchema: z.ZodType<CreateEffectConfig> = z.object({
|
||||
kind: z.literal('Create'),
|
||||
into: LifetimeIdSchema,
|
||||
value: ValueKindSchema,
|
||||
reason: ValueReasonSchema,
|
||||
});
|
||||
|
||||
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 AliasEffectConfig = {
|
||||
kind: 'Alias';
|
||||
from: string;
|
||||
into: string;
|
||||
};
|
||||
|
||||
export const AliasEffectSchema: z.ZodType<AliasEffectConfig> = z.object({
|
||||
kind: z.literal('Alias'),
|
||||
from: LifetimeIdSchema,
|
||||
into: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
export type CaptureEffectConfig = {
|
||||
kind: 'Capture';
|
||||
from: string;
|
||||
into: string;
|
||||
};
|
||||
|
||||
export const CaptureEffectSchema: z.ZodType<CaptureEffectConfig> = z.object({
|
||||
kind: z.literal('Capture'),
|
||||
from: LifetimeIdSchema,
|
||||
into: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
export type CreateFromEffectConfig = {
|
||||
kind: 'CreateFrom';
|
||||
from: string;
|
||||
into: string;
|
||||
};
|
||||
|
||||
export const CreateFromEffectSchema: z.ZodType<CreateFromEffectConfig> =
|
||||
z.object({
|
||||
kind: z.literal('CreateFrom'),
|
||||
from: LifetimeIdSchema,
|
||||
into: LifetimeIdSchema,
|
||||
});
|
||||
|
||||
export type ApplyArgConfig =
|
||||
| string
|
||||
| {kind: 'Spread'; place: string}
|
||||
| {kind: 'Hole'};
|
||||
|
||||
export const ApplyArgSchema: z.ZodType<ApplyArgConfig> = z.union([
|
||||
LifetimeIdSchema,
|
||||
z.object({
|
||||
kind: z.literal('Spread'),
|
||||
place: LifetimeIdSchema,
|
||||
}),
|
||||
z.object({
|
||||
kind: z.literal('Hole'),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type ApplyEffectConfig = {
|
||||
kind: 'Apply';
|
||||
receiver: string;
|
||||
function: string;
|
||||
mutatesFunction: boolean;
|
||||
args: Array<ApplyArgConfig>;
|
||||
into: string;
|
||||
};
|
||||
|
||||
export const ApplyEffectSchema: z.ZodType<ApplyEffectConfig> = z.object({
|
||||
kind: z.literal('Apply'),
|
||||
receiver: LifetimeIdSchema,
|
||||
function: LifetimeIdSchema,
|
||||
mutatesFunction: z.boolean(),
|
||||
args: z.array(ApplyArgSchema),
|
||||
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
|
||||
| CreateFromEffectConfig
|
||||
| AssignEffectConfig
|
||||
| AliasEffectConfig
|
||||
| CaptureEffectConfig
|
||||
| ImpureEffectConfig
|
||||
| MutateEffectConfig
|
||||
| MutateTransitiveConditionallyConfig
|
||||
| ApplyEffectConfig;
|
||||
|
||||
export const AliasingEffectSchema: z.ZodType<AliasingEffectConfig> = z.union([
|
||||
FreezeEffectSchema,
|
||||
CreateEffectSchema,
|
||||
CreateFromEffectSchema,
|
||||
AssignEffectSchema,
|
||||
AliasEffectSchema,
|
||||
CaptureEffectSchema,
|
||||
ImpureEffectSchema,
|
||||
MutateEffectSchema,
|
||||
MutateTransitiveConditionallySchema,
|
||||
ApplyEffectSchema,
|
||||
]);
|
||||
|
||||
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 +235,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 +248,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 +258,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 +267,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