import {BindingKind} from '@babel/traverse';
import * as t from '@babel/types';
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {assertExhaustive} from '../Utils/utils';
import {Environment, ReactFunctionType} from './Environment';
import {HookKind} from './ObjectShape';
import {Type, makeType} from './Types';
import {z} from 'zod';
export const GeneratedSource = Symbol();
export type SourceLocation = t.SourceLocation | typeof GeneratedSource;
export type ReactiveFunction = {
loc: SourceLocation;
id: string | null;
params: Array<Place | SpreadPattern>;
generator: boolean;
async: boolean;
body: ReactiveBlock;
env: Environment;
directives: Array<string>;
};
export type ReactiveScopeBlock = {
kind: 'scope';
scope: ReactiveScope;
instructions: ReactiveBlock;
};
export type PrunedReactiveScopeBlock = {
kind: 'pruned-scope';
scope: ReactiveScope;
instructions: ReactiveBlock;
};
export type ReactiveBlock = Array<ReactiveStatement>;
export type ReactiveStatement =
| ReactiveInstructionStatement
| ReactiveTerminalStatement
| ReactiveScopeBlock
| PrunedReactiveScopeBlock;
export type ReactiveInstructionStatement = {
kind: 'instruction';
instruction: ReactiveInstruction;
};
export type ReactiveTerminalStatement<
Tterminal extends ReactiveTerminal = ReactiveTerminal,
> = {
kind: 'terminal';
terminal: Tterminal;
label: {
id: BlockId;
implicit: boolean;
} | null;
};
export type ReactiveInstruction = {
id: InstructionId;
lvalue: Place | null;
value: ReactiveValue;
loc: SourceLocation;
};
export type ReactiveValue =
| InstructionValue
| ReactiveLogicalValue
| ReactiveSequenceValue
| ReactiveTernaryValue
| ReactiveOptionalCallValue
| ReactiveFunctionValue;
export type ReactiveFunctionValue = {
kind: 'ReactiveFunctionValue';
fn: ReactiveFunction;
dependencies: Array<Place>;
returnType: t.FlowType | t.TSType | null;
loc: SourceLocation;
};
export type ReactiveLogicalValue = {
kind: 'LogicalExpression';
operator: t.LogicalExpression['operator'];
left: ReactiveValue;
right: ReactiveValue;
loc: SourceLocation;
};
export type ReactiveTernaryValue = {
kind: 'ConditionalExpression';
test: ReactiveValue;
consequent: ReactiveValue;
alternate: ReactiveValue;
loc: SourceLocation;
};
export type ReactiveSequenceValue = {
kind: 'SequenceExpression';
instructions: Array<ReactiveInstruction>;
id: InstructionId;
value: ReactiveValue;
loc: SourceLocation;
};
export type ReactiveOptionalCallValue = {
kind: 'OptionalExpression';
id: InstructionId;
value: ReactiveValue;
optional: boolean;
loc: SourceLocation;
};
export type ReactiveTerminal =
| ReactiveBreakTerminal
| ReactiveContinueTerminal
| ReactiveReturnTerminal
| ReactiveThrowTerminal
| ReactiveSwitchTerminal
| ReactiveDoWhileTerminal
| ReactiveWhileTerminal
| ReactiveForTerminal
| ReactiveForOfTerminal
| ReactiveForInTerminal
| ReactiveIfTerminal
| ReactiveLabelTerminal
| ReactiveTryTerminal;
function _staticInvariantReactiveTerminalHasLocation(
terminal: ReactiveTerminal,
): SourceLocation {
return terminal.loc;
}
function _staticInvariantReactiveTerminalHasInstructionId(
terminal: ReactiveTerminal,
): InstructionId {
return terminal.id;
}
export type ReactiveTerminalTargetKind = 'implicit' | 'labeled' | 'unlabeled';
export type ReactiveBreakTerminal = {
kind: 'break';
target: BlockId;
id: InstructionId;
targetKind: ReactiveTerminalTargetKind;
loc: SourceLocation;
};
export type ReactiveContinueTerminal = {
kind: 'continue';
target: BlockId;
id: InstructionId;
targetKind: ReactiveTerminalTargetKind;
loc: SourceLocation;
};
export type ReactiveReturnTerminal = {
kind: 'return';
value: Place;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveThrowTerminal = {
kind: 'throw';
value: Place;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveSwitchTerminal = {
kind: 'switch';
test: Place;
cases: Array<{
test: Place | null;
block: ReactiveBlock | void;
}>;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveDoWhileTerminal = {
kind: 'do-while';
loop: ReactiveBlock;
test: ReactiveValue;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveWhileTerminal = {
kind: 'while';
test: ReactiveValue;
loop: ReactiveBlock;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveForTerminal = {
kind: 'for';
init: ReactiveValue;
test: ReactiveValue;
update: ReactiveValue | null;
loop: ReactiveBlock;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveForOfTerminal = {
kind: 'for-of';
init: ReactiveValue;
test: ReactiveValue;
loop: ReactiveBlock;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveForInTerminal = {
kind: 'for-in';
init: ReactiveValue;
loop: ReactiveBlock;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveIfTerminal = {
kind: 'if';
test: Place;
consequent: ReactiveBlock;
alternate: ReactiveBlock | null;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveLabelTerminal = {
kind: 'label';
block: ReactiveBlock;
id: InstructionId;
loc: SourceLocation;
};
export type ReactiveTryTerminal = {
kind: 'try';
block: ReactiveBlock;
handlerBinding: Place | null;
handler: ReactiveBlock;
id: InstructionId;
loc: SourceLocation;
};
export type HIRFunction = {
loc: SourceLocation;
id: string | null;
fnType: ReactFunctionType;
env: Environment;
params: Array<Place | SpreadPattern>;
returnTypeAnnotation: t.FlowType | t.TSType | null;
returnType: Type;
context: Array<Place>;
effects: Array<FunctionEffect> | null;
body: HIR;
generator: boolean;
async: boolean;
directives: Array<string>;
};
export type FunctionEffect =
| {
kind: 'GlobalMutation';
error: CompilerErrorDetailOptions;
}
| {
kind: 'ReactMutation';
error: CompilerErrorDetailOptions;
}
| {
kind: 'ContextMutation';
places: ReadonlySet<Place>;
effect: Effect;
loc: SourceLocation;
};
export type HIR = {
entry: BlockId;
blocks: Map<BlockId, BasicBlock>;
};
export type BlockKind = 'block' | 'value' | 'loop' | 'sequence' | 'catch';
export function isStatementBlockKind(kind: BlockKind): boolean {
return kind === 'block' || kind === 'catch';
}
export function isExpressionBlockKind(kind: BlockKind): boolean {
return !isStatementBlockKind(kind);
}
export type BasicBlock = {
kind: BlockKind;
id: BlockId;
instructions: Array<Instruction>;
terminal: Terminal;
preds: Set<BlockId>;
phis: Set<Phi>;
};
export type TBasicBlock<T extends Terminal> = BasicBlock & {terminal: T};
export type Terminal =
| UnsupportedTerminal
| UnreachableTerminal
| ThrowTerminal
| ReturnTerminal
| GotoTerminal
| IfTerminal
| BranchTerminal
| SwitchTerminal
| ForTerminal
| ForOfTerminal
| ForInTerminal
| DoWhileTerminal
| WhileTerminal
| LogicalTerminal
| TernaryTerminal
| OptionalTerminal
| LabelTerminal
| SequenceTerminal
| MaybeThrowTerminal
| TryTerminal
| ReactiveScopeTerminal
| PrunedScopeTerminal;
export type TerminalWithFallthrough = Terminal & {fallthrough: BlockId};
function _staticInvariantTerminalHasLocation(
terminal: Terminal,
): SourceLocation {
return terminal.loc;
}
function _staticInvariantTerminalHasInstructionId(
terminal: Terminal,
): InstructionId {
return terminal.id;
}
function _staticInvariantTerminalHasFallthrough(
terminal: Terminal,
): BlockId | never | undefined {
return terminal.fallthrough;
}
export type UnsupportedTerminal = {
kind: 'unsupported';
id: InstructionId;
loc: SourceLocation;
fallthrough?: never;
};
export type UnreachableTerminal = {
kind: 'unreachable';
id: InstructionId;
loc: SourceLocation;
fallthrough?: never;
};
export type ThrowTerminal = {
kind: 'throw';
value: Place;
id: InstructionId;
loc: SourceLocation;
fallthrough?: never;
};
export type Case = {test: Place | null; block: BlockId};
export type ReturnTerminal = {
kind: 'return';
loc: SourceLocation;
value: Place;
id: InstructionId;
fallthrough?: never;
};
export type GotoTerminal = {
kind: 'goto';
block: BlockId;
variant: GotoVariant;
id: InstructionId;
loc: SourceLocation;
fallthrough?: never;
};
export enum GotoVariant {
Break = 'Break',
Continue = 'Continue',
Try = 'Try',
}
export type IfTerminal = {
kind: 'if';
test: Place;
consequent: BlockId;
alternate: BlockId;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type BranchTerminal = {
kind: 'branch';
test: Place;
consequent: BlockId;
alternate: BlockId;
id: InstructionId;
loc: SourceLocation;
fallthrough: BlockId;
};
export type SwitchTerminal = {
kind: 'switch';
test: Place;
cases: Array<Case>;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type DoWhileTerminal = {
kind: 'do-while';
loop: BlockId;
test: BlockId;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type WhileTerminal = {
kind: 'while';
loc: SourceLocation;
test: BlockId;
loop: BlockId;
fallthrough: BlockId;
id: InstructionId;
};
export type ForTerminal = {
kind: 'for';
loc: SourceLocation;
init: BlockId;
test: BlockId;
update: BlockId | null;
loop: BlockId;
fallthrough: BlockId;
id: InstructionId;
};
export type ForOfTerminal = {
kind: 'for-of';
loc: SourceLocation;
init: BlockId;
test: BlockId;
loop: BlockId;
fallthrough: BlockId;
id: InstructionId;
};
export type ForInTerminal = {
kind: 'for-in';
loc: SourceLocation;
init: BlockId;
loop: BlockId;
fallthrough: BlockId;
id: InstructionId;
};
export type LogicalTerminal = {
kind: 'logical';
operator: t.LogicalExpression['operator'];
test: BlockId;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type TernaryTerminal = {
kind: 'ternary';
test: BlockId;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type LabelTerminal = {
kind: 'label';
block: BlockId;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type OptionalTerminal = {
kind: 'optional';
optional: boolean;
test: BlockId;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type SequenceTerminal = {
kind: 'sequence';
block: BlockId;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type TryTerminal = {
kind: 'try';
block: BlockId;
handlerBinding: Place | null;
handler: BlockId;
fallthrough: BlockId;
id: InstructionId;
loc: SourceLocation;
};
export type MaybeThrowTerminal = {
kind: 'maybe-throw';
continuation: BlockId;
handler: BlockId;
id: InstructionId;
loc: SourceLocation;
fallthrough?: never;
};
export type ReactiveScopeTerminal = {
kind: 'scope';
fallthrough: BlockId;
block: BlockId;
scope: ReactiveScope;
id: InstructionId;
loc: SourceLocation;
};
export type PrunedScopeTerminal = {
kind: 'pruned-scope';
fallthrough: BlockId;
block: BlockId;
scope: ReactiveScope;
id: InstructionId;
loc: SourceLocation;
};
export type Instruction = {
id: InstructionId;
lvalue: Place;
value: InstructionValue;
loc: SourceLocation;
};
export type TInstruction<T extends InstructionValue> = {
id: InstructionId;
lvalue: Place;
value: T;
loc: SourceLocation;
};
export type LValue = {
place: Place;
kind: InstructionKind;
};
export type LValuePattern = {
pattern: Pattern;
kind: InstructionKind;
};
export type ArrayExpression = {
kind: 'ArrayExpression';
elements: Array<Place | SpreadPattern | Hole>;
loc: SourceLocation;
};
export type Pattern = ArrayPattern | ObjectPattern;
export type Hole = {
kind: 'Hole';
};
export type SpreadPattern = {
kind: 'Spread';
place: Place;
};
export type ArrayPattern = {
kind: 'ArrayPattern';
items: Array<Place | SpreadPattern | Hole>;
};
export type ObjectPattern = {
kind: 'ObjectPattern';
properties: Array<ObjectProperty | SpreadPattern>;
};
export type ObjectPropertyKey =
| {
kind: 'string';
name: string;
}
| {
kind: 'identifier';
name: string;
}
| {
kind: 'computed';
name: Place;
};
export type ObjectProperty = {
kind: 'ObjectProperty';
key: ObjectPropertyKey;
type: 'property' | 'method';
place: Place;
};
export type LoweredFunction = {
dependencies: Array<Place>;
func: HIRFunction;
};
export type ObjectMethod = {
kind: 'ObjectMethod';
loc: SourceLocation;
loweredFunc: LoweredFunction;
};
export enum InstructionKind {
Const = 'Const',
Let = 'Let',
Reassign = 'Reassign',
Catch = 'Catch',
HoistedConst = 'HoistedConst',
HoistedLet = 'HoistedLet',
HoistedFunction = 'HoistedFunction',
Function = 'Function',
}
function _staticInvariantInstructionValueHasLocation(
value: InstructionValue,
): SourceLocation {
return value.loc;
}
export type Phi = {
kind: 'Phi';
id: Identifier;
operands: Map<BlockId, Identifier>;
};
export type ManualMemoDependency = {
root:
| {
kind: 'NamedLocal';
value: Place;
}
| {kind: 'Global'; identifierName: string};
path: DependencyPath;
};
export type StartMemoize = {
kind: 'StartMemoize';
manualMemoId: number;
deps: Array<ManualMemoDependency> | null;
loc: SourceLocation;
};
export type FinishMemoize = {
kind: 'FinishMemoize';
manualMemoId: number;
decl: Place;
pruned?: true;
loc: SourceLocation;
};
export type MethodCall = {
kind: 'MethodCall';
receiver: Place;
property: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
};
export type CallExpression = {
kind: 'CallExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
typeArguments?: Array<t.FlowType>;
};
export type LoadLocal = {
kind: 'LoadLocal';
place: Place;
loc: SourceLocation;
};
export type InstructionValue =
| LoadLocal
| {
kind: 'LoadContext';
place: Place;
loc: SourceLocation;
}
| {
kind: 'DeclareLocal';
lvalue: LValue;
type: t.FlowType | t.TSType | null;
loc: SourceLocation;
}
| {
kind: 'DeclareContext';
lvalue: {
kind:
| InstructionKind.Let
| InstructionKind.HoistedConst
| InstructionKind.HoistedLet
| InstructionKind.HoistedFunction;
place: Place;
};
loc: SourceLocation;
}
| StoreLocal
| {
kind: 'StoreContext';
lvalue: {
kind: InstructionKind.Reassign;
place: Place;
};
value: Place;
loc: SourceLocation;
}
| Destructure
| {
kind: 'Primitive';
value: number | boolean | string | null | undefined;
loc: SourceLocation;
}
| JSXText
| {
kind: 'BinaryExpression';
operator: Exclude<t.BinaryExpression['operator'], '|>'>;
left: Place;
right: Place;
loc: SourceLocation;
}
| {
kind: 'NewExpression';
callee: Place;
args: Array<Place | SpreadPattern>;
loc: SourceLocation;
}
| CallExpression
| MethodCall
| {
kind: 'UnaryExpression';
operator: Exclude<t.UnaryExpression['operator'], 'throw' | 'delete'>;
value: Place;
loc: SourceLocation;
}
| {
kind: 'TypeCastExpression';
value: Place;
typeAnnotation: t.FlowType | t.TSType;
type: Type;
loc: SourceLocation;
}
| {
kind: 'JsxExpression';
tag: Place | BuiltinTag;
props: Array<JsxAttribute>;
children: Array<Place> | null;
loc: SourceLocation;
openingLoc: SourceLocation;
closingLoc: SourceLocation;
}
| {
kind: 'ObjectExpression';
properties: Array<ObjectProperty | SpreadPattern>;
loc: SourceLocation;
}
| ObjectMethod
| ArrayExpression
| {kind: 'JsxFragment'; children: Array<Place>; loc: SourceLocation}
| {
kind: 'RegExpLiteral';
pattern: string;
flags: string;
loc: SourceLocation;
}
| {
kind: 'MetaProperty';
meta: string;
property: string;
loc: SourceLocation;
}
| {
kind: 'PropertyStore';
object: Place;
property: string;
value: Place;
loc: SourceLocation;
}
| PropertyLoad
| {
kind: 'PropertyDelete';
object: Place;
property: string;
loc: SourceLocation;
}
| {
kind: 'ComputedStore';
object: Place;
property: Place;
value: Place;
loc: SourceLocation;
}
| {
kind: 'ComputedLoad';
object: Place;
property: Place;
loc: SourceLocation;
}
| {
kind: 'ComputedDelete';
object: Place;
property: Place;
loc: SourceLocation;
}
| LoadGlobal
| StoreGlobal
| FunctionExpression
| {
kind: 'TaggedTemplateExpression';
tag: Place;
value: {raw: string; cooked?: string};
loc: SourceLocation;
}
| {
kind: 'TemplateLiteral';
subexprs: Array<Place>;
quasis: Array<{raw: string; cooked?: string}>;
loc: SourceLocation;
}
| {
kind: 'Await';
value: Place;
loc: SourceLocation;
}
| {
kind: 'GetIterator';
collection: Place;
loc: SourceLocation;
}
| {
kind: 'IteratorNext';
iterator: Place;
collection: Place;
loc: SourceLocation;
}
| {
kind: 'NextPropertyOf';
value: Place;
loc: SourceLocation;
}
| {
kind: 'PrefixUpdate';
lvalue: Place;
operation: t.UpdateExpression['operator'];
value: Place;
loc: SourceLocation;
}
| {
kind: 'PostfixUpdate';
lvalue: Place;
operation: t.UpdateExpression['operator'];
value: Place;
loc: SourceLocation;
}
| {kind: 'Debugger'; loc: SourceLocation}
| StartMemoize
| FinishMemoize
| {
kind: 'UnsupportedNode';
node: t.Node;
loc: SourceLocation;
};
export type JsxAttribute =
| {kind: 'JsxSpreadAttribute'; argument: Place}
| {kind: 'JsxAttribute'; name: string; place: Place};
export type FunctionExpression = {
kind: 'FunctionExpression';
name: string | null;
loweredFunc: LoweredFunction;
type:
| 'ArrowFunctionExpression'
| 'FunctionExpression'
| 'FunctionDeclaration';
loc: SourceLocation;
};
export type Destructure = {
kind: 'Destructure';
lvalue: LValuePattern;
value: Place;
loc: SourceLocation;
};
export type Place = {
kind: 'Identifier';
identifier: Identifier;
effect: Effect;
reactive: boolean;
loc: SourceLocation;
};
export type Primitive = {
kind: 'Primitive';
value: number | boolean | string | null | undefined;
loc: SourceLocation;
};
export type JSXText = {kind: 'JSXText'; value: string; loc: SourceLocation};
export type StoreLocal = {
kind: 'StoreLocal';
lvalue: LValue;
value: Place;
type: t.FlowType | t.TSType | null;
loc: SourceLocation;
};
export type PropertyLoad = {
kind: 'PropertyLoad';
object: Place;
property: string;
loc: SourceLocation;
};
export type LoadGlobal = {
kind: 'LoadGlobal';
binding: NonLocalBinding;
loc: SourceLocation;
};
export type StoreGlobal = {
kind: 'StoreGlobal';
name: string;
value: Place;
loc: SourceLocation;
};
export type BuiltinTag = {
kind: 'BuiltinTag';
name: string;
loc: SourceLocation;
};
export type MutableRange = {
start: InstructionId;
end: InstructionId;
};
export type VariableBinding =
| {kind: 'Identifier'; identifier: Identifier; bindingKind: BindingKind}
| NonLocalBinding;
export type NonLocalBinding =
| {kind: 'ImportDefault'; name: string; module: string}
| {kind: 'ImportNamespace'; name: string; module: string}
| {
kind: 'ImportSpecifier';
name: string;
module: string;
imported: string;
}
| {kind: 'ModuleLocal'; name: string}
| {kind: 'Global'; name: string};
export type Identifier = {
id: IdentifierId;
declarationId: DeclarationId;
name: IdentifierName | null;
mutableRange: MutableRange;
scope: ReactiveScope | null;
type: Type;
loc: SourceLocation;
};
export type IdentifierName = ValidatedIdentifier | PromotedIdentifier;
export type ValidatedIdentifier = {kind: 'named'; value: ValidIdentifierName};
export type PromotedIdentifier = {kind: 'promoted'; value: string};
const opaqueValidIdentifierName = Symbol();
export type ValidIdentifierName = string & {
[opaqueValidIdentifierName]: 'ValidIdentifierName';
};
export function makeTemporaryIdentifier(
id: IdentifierId,
loc: SourceLocation,
): Identifier {
return {
id,
name: null,
declarationId: makeDeclarationId(id),
mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
scope: null,
type: makeType(),
loc,
};
}
export function makeIdentifierName(name: string): ValidatedIdentifier {
CompilerError.invariant(t.isValidIdentifier(name), {
reason: `Expected a valid identifier name`,
loc: GeneratedSource,
description: `\`${name}\` is not a valid JavaScript identifier`,
suggestions: null,
});
return {
kind: 'named',
value: name as ValidIdentifierName,
};
}
export function promoteTemporary(identifier: Identifier): void {
CompilerError.invariant(identifier.name === null, {
reason: `Expected a temporary (unnamed) identifier`,
loc: GeneratedSource,
description: `Identifier already has a name, \`${identifier.name}\``,
suggestions: null,
});
identifier.name = {
kind: 'promoted',
value: `#t${identifier.declarationId}`,
};
}
export function isPromotedTemporary(name: string): boolean {
return name.startsWith('#t');
}
export function promoteTemporaryJsxTag(identifier: Identifier): void {
CompilerError.invariant(identifier.name === null, {
reason: `Expected a temporary (unnamed) identifier`,
loc: GeneratedSource,
description: `Identifier already has a name, \`${identifier.name}\``,
suggestions: null,
});
identifier.name = {
kind: 'promoted',
value: `#T${identifier.declarationId}`,
};
}
export function isPromotedJsxTemporary(name: string): boolean {
return name.startsWith('#T');
}
export type AbstractValue = {
kind: ValueKind;
reason: ReadonlySet<ValueReason>;
context: ReadonlySet<Place>;
};
export enum ValueReason {
Global = 'global',
JsxCaptured = 'jsx-captured',
KnownReturnSignature = 'known-return-signature',
Context = 'context',
State = 'state',
ReducerState = 'reducer-state',
ReactiveFunctionArgument = 'reactive-function-argument',
Other = 'other',
}
export enum ValueKind {
MaybeFrozen = 'maybefrozen',
Frozen = 'frozen',
Primitive = 'primitive',
Global = 'global',
Mutable = 'mutable',
Context = 'context',
}
export const ValueKindSchema = z.enum([
ValueKind.MaybeFrozen,
ValueKind.Frozen,
ValueKind.Primitive,
ValueKind.Global,
ValueKind.Mutable,
ValueKind.Context,
]);
export enum Effect {
Unknown = '<unknown>',
Freeze = 'freeze',
Read = 'read',
Capture = 'capture',
ConditionallyMutate = 'mutate?',
Mutate = 'mutate',
Store = 'store',
}
export const EffectSchema = z.enum([
Effect.Read,
Effect.Mutate,
Effect.ConditionallyMutate,
Effect.Capture,
Effect.Store,
Effect.Freeze,
]);
export function isMutableEffect(
effect: Effect,
location: SourceLocation,
): boolean {
switch (effect) {
case Effect.Capture:
case Effect.Store:
case Effect.ConditionallyMutate:
case Effect.Mutate: {
return true;
}
case Effect.Unknown: {
CompilerError.invariant(false, {
reason: 'Unexpected unknown effect',
description: null,
loc: location,
suggestions: null,
});
}
case Effect.Read:
case Effect.Freeze: {
return false;
}
default: {
assertExhaustive(effect, `Unexpected effect \`${effect}\``);
}
}
}
export type ReactiveScope = {
id: ScopeId;
range: MutableRange;
dependencies: ReactiveScopeDependencies;
declarations: Map<IdentifierId, ReactiveScopeDeclaration>;
reassignments: Set<Identifier>;
earlyReturnValue: {
value: Identifier;
loc: SourceLocation;
label: BlockId;
} | null;
merged: Set<ScopeId>;
loc: SourceLocation;
};
export type ReactiveScopeDependencies = Set<ReactiveScopeDependency>;
export type ReactiveScopeDeclaration = {
identifier: Identifier;
scope: ReactiveScope;
};
export type DependencyPathEntry = {property: string; optional: boolean};
export type DependencyPath = Array<DependencyPathEntry>;
export type ReactiveScopeDependency = {
identifier: Identifier;
path: DependencyPath;
};
export function areEqualPaths(a: DependencyPath, b: DependencyPath): boolean {
return (
a.length === b.length &&
a.every(
(item, ix) =>
item.property === b[ix].property && item.optional === b[ix].optional,
)
);
}
export function getPlaceScope(
id: InstructionId,
place: Place,
): ReactiveScope | null {
const scope = place.identifier.scope;
if (scope !== null && isScopeActive(scope, id)) {
return scope;
}
return null;
}
function isScopeActive(scope: ReactiveScope, id: InstructionId): boolean {
return id >= scope.range.start && id < scope.range.end;
}
const opaqueBlockId = Symbol();
export type BlockId = number & {[opaqueBlockId]: 'BlockId'};
export function makeBlockId(id: number): BlockId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected block id to be a non-negative integer',
description: null,
loc: null,
suggestions: null,
});
return id as BlockId;
}
const opaqueScopeId = Symbol();
export type ScopeId = number & {[opaqueScopeId]: 'ScopeId'};
export function makeScopeId(id: number): ScopeId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected block id to be a non-negative integer',
description: null,
loc: null,
suggestions: null,
});
return id as ScopeId;
}
const opaqueIdentifierId = Symbol();
export type IdentifierId = number & {[opaqueIdentifierId]: 'IdentifierId'};
export function makeIdentifierId(id: number): IdentifierId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected identifier id to be a non-negative integer',
description: null,
loc: null,
suggestions: null,
});
return id as IdentifierId;
}
const opageDeclarationId = Symbol();
export type DeclarationId = number & {[opageDeclarationId]: 'DeclarationId'};
export function makeDeclarationId(id: number): DeclarationId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected declaration id to be a non-negative integer',
description: null,
loc: null,
suggestions: null,
});
return id as DeclarationId;
}
const opaqueInstructionId = Symbol();
export type InstructionId = number & {[opaqueInstructionId]: 'IdentifierId'};
export function makeInstructionId(id: number): InstructionId {
CompilerError.invariant(id >= 0 && Number.isInteger(id), {
reason: 'Expected instruction id to be a non-negative integer',
description: null,
loc: null,
suggestions: null,
});
return id as InstructionId;
}
export function isObjectMethodType(id: Identifier): boolean {
return id.type.kind == 'ObjectMethod';
}
export function isObjectType(id: Identifier): boolean {
return id.type.kind === 'Object';
}
export function isPrimitiveType(id: Identifier): boolean {
return id.type.kind === 'Primitive';
}
export function isArrayType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
}
export function isRefValueType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInRefValue';
}
export function isUseRefType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseRefId';
}
export function isUseStateType(id: Identifier): boolean {
return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState';
}
export function isRefOrRefValue(id: Identifier): boolean {
return isUseRefType(id) || isRefValueType(id);
}
export function isSetStateType(id: Identifier): boolean {
return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetState';
}
export function isUseActionStateType(id: Identifier): boolean {
return (
id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseActionState'
);
}
export function isStartTransitionType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInStartTransition'
);
}
export function isSetActionStateType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetActionState'
);
}
export function isUseReducerType(id: Identifier): boolean {
return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseReducer';
}
export function isDispatcherType(id: Identifier): boolean {
return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInDispatch';
}
export function isStableType(id: Identifier): boolean {
return (
isSetStateType(id) ||
isSetActionStateType(id) ||
isDispatcherType(id) ||
isUseRefType(id) ||
isStartTransitionType(id)
);
}
export function isUseEffectHookType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectHook'
);
}
export function isUseLayoutEffectHookType(id: Identifier): boolean {
return (
id.type.kind === 'Function' &&
id.type.shapeId === 'BuiltInUseLayoutEffectHook'
);
}
export function isUseInsertionEffectHookType(id: Identifier): boolean {
return (
id.type.kind === 'Function' &&
id.type.shapeId === 'BuiltInUseInsertionEffectHook'
);
}
export function isUseContextHookType(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseContextHook'
);
}
export function getHookKind(env: Environment, id: Identifier): HookKind | null {
return getHookKindForType(env, id.type);
}
export function isUseOperator(id: Identifier): boolean {
return (
id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseOperator'
);
}
export function getHookKindForType(
env: Environment,
type: Type,
): HookKind | null {
if (type.kind === 'Function') {
const signature = env.getFunctionSignature(type);
return signature?.hookKind ?? null;
}
return null;
}
export * from './Types';