Compare commits

...

1 Commits

Author SHA1 Message Date
Joe Savona
2a5f77c261 [compiler] More readable alias signature declarations
Now that we have support for defining aliasing signatures in moduleTypeProvider, which uses string names for receiver/args/returns/etc, we can reuse that same form for builtin declarations. The declarations are written in the unparsed form and than parsed/validated when registered (in the addFunction/addHook call).

This also required flushing out configs/schemas for more effect types.
2025-06-18 15:46:12 -07:00
4 changed files with 327 additions and 166 deletions

View File

@@ -5,14 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {
Effect,
GeneratedSource,
makeIdentifierId,
Place,
ValueKind,
ValueReason,
} from './HIR';
import {Effect, ValueKind, ValueReason} from './HIR';
import {
BUILTIN_SHAPES,
BuiltInArrayId,
@@ -41,18 +34,12 @@ import {
addFunction,
addHook,
addObject,
signatureArgument,
} from './ObjectShape';
import {BuiltInType, ObjectType, PolyType} from './Types';
import {
AliasingEffectConfig,
AliasingSignatureConfig,
TypeConfig,
} from './TypeSchema';
import {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.
@@ -658,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,
},
@@ -903,10 +890,6 @@ 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,
@@ -922,14 +905,10 @@ export function installTypeConfig(
noAlias: typeConfig.noAlias === true,
mutableOnlyIfOperandsAreMutable:
typeConfig.mutableOnlyIfOperandsAreMutable === true,
aliasing,
aliasing: typeConfig.aliasing,
});
}
case 'hook': {
const aliasing =
typeConfig.aliasing != null
? parseAliasingSignatureConfig(typeConfig.aliasing, moduleName, loc)
: null;
return addHook(shapes, {
hookKind: 'Custom',
positionalParams: typeConfig.positionalParams ?? [],
@@ -944,7 +923,7 @@ export function installTypeConfig(
),
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
noAlias: typeConfig.noAlias === true,
aliasing,
aliasing: typeConfig.aliasing,
});
}
case 'object': {
@@ -987,90 +966,6 @@ 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 = [

View File

@@ -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

View File

@@ -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',
},
],
},

View File

@@ -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
@@ -38,23 +43,48 @@ export const LifetimeIdSchema = z.string().refine(id => id.startsWith('@'), {
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 = {
@@ -69,6 +99,77 @@ export const AssignEffectSchema: z.ZodType<AssignEffectConfig> = z.object({
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;
@@ -82,14 +183,26 @@ export const ImpureEffectSchema: z.ZodType<ImpureEffectConfig> = z.object({
export type AliasingEffectConfig =
| FreezeEffectConfig
| CreateEffectConfig
| CreateFromEffectConfig
| AssignEffectConfig
| ImpureEffectConfig;
| 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 = {