main
ts 1,996 lines 51.1 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import {BindingKind} from '@babel/traverse';
9 import * as t from '@babel/types';
10 import {
11 CompilerDiagnostic,
12 CompilerError,
13 ErrorCategory,
14 } from '../CompilerError';
15 import {assertExhaustive} from '../Utils/utils';
16 import {Environment, ReactFunctionType} from './Environment';
17 import type {HookKind} from './ObjectShape';
18 import {Type, makeType} from './Types';
19 import {z} from 'zod/v4';
20 import type {AliasingEffect} from '../Inference/AliasingEffects';
21 import {isReservedWord} from '../Utils/Keyword';
22 import {Err, Ok, Result} from '../Utils/Result';
23
24 /*
25 * *******************************************************************************************
26 * *******************************************************************************************
27 * ************************************* Core Data Model *************************************
28 * *******************************************************************************************
29 * *******************************************************************************************
30 */
31
32 // AST -> (lowering) -> HIR -> (analysis) -> Reactive Scopes -> (codegen) -> AST
33
34 /*
35 * A location in a source file, intended to be used for providing diagnostic information and
36 * transforming code while preserving source information (ie to emit source maps).
37 *
38 * `GeneratedSource` indicates that there is no single source location from which the code derives.
39 */
40 export const GeneratedSource = Symbol();
41 export type SourceLocation = t.SourceLocation | typeof GeneratedSource;
42
43 /*
44 * A React function defines a computation that takes some set of reactive inputs
45 * (props, hook arguments) and return a result (JSX, hook return value). Unlike
46 * HIR, the data model is tree-shaped:
47 *
48 * ReactFunction
49 * ReactiveBlock
50 * ReactiveBlockScope*
51 * Place* (dependencies)
52 * (ReactiveInstruction | ReactiveTerminal)*
53 *
54 * Where ReactiveTerminal may recursively contain zero or more ReactiveBlocks.
55 *
56 * Each ReactiveBlockScope describes a set of dependencies as well as the instructions (and terminals)
57 * within that scope.
58 */
59 export type ReactiveFunction = {
60 loc: SourceLocation;
61 id: ValidIdentifierName | null;
62 nameHint: string | null;
63 params: Array<Place | SpreadPattern>;
64 generator: boolean;
65 async: boolean;
66 body: ReactiveBlock;
67 env: Environment;
68 directives: Array<string>;
69 };
70
71 export type ReactiveScopeBlock = {
72 kind: 'scope';
73 scope: ReactiveScope;
74 instructions: ReactiveBlock;
75 };
76
77 export type PrunedReactiveScopeBlock = {
78 kind: 'pruned-scope';
79 scope: ReactiveScope;
80 instructions: ReactiveBlock;
81 };
82
83 export type ReactiveBlock = Array<ReactiveStatement>;
84
85 export type ReactiveStatement =
86 | ReactiveInstructionStatement
87 | ReactiveTerminalStatement
88 | ReactiveScopeBlock
89 | PrunedReactiveScopeBlock;
90
91 export type ReactiveInstructionStatement = {
92 kind: 'instruction';
93 instruction: ReactiveInstruction;
94 };
95
96 export type ReactiveTerminalStatement<
97 Tterminal extends ReactiveTerminal = ReactiveTerminal,
98 > = {
99 kind: 'terminal';
100 terminal: Tterminal;
101 label: {
102 id: BlockId;
103 implicit: boolean;
104 } | null;
105 };
106
107 export type ReactiveInstruction = {
108 id: InstructionId;
109 lvalue: Place | null;
110 value: ReactiveValue;
111 effects?: Array<AliasingEffect> | null; // TODO make non-optional
112 loc: SourceLocation;
113 };
114
115 export type ReactiveValue =
116 | InstructionValue
117 | ReactiveLogicalValue
118 | ReactiveSequenceValue
119 | ReactiveTernaryValue
120 | ReactiveOptionalCallValue;
121
122 export type ReactiveLogicalValue = {
123 kind: 'LogicalExpression';
124 operator: t.LogicalExpression['operator'];
125 left: ReactiveValue;
126 right: ReactiveValue;
127 loc: SourceLocation;
128 };
129
130 export type ReactiveTernaryValue = {
131 kind: 'ConditionalExpression';
132 test: ReactiveValue;
133 consequent: ReactiveValue;
134 alternate: ReactiveValue;
135 loc: SourceLocation;
136 };
137
138 export type ReactiveSequenceValue = {
139 kind: 'SequenceExpression';
140 instructions: Array<ReactiveInstruction>;
141 id: InstructionId;
142 value: ReactiveValue;
143 loc: SourceLocation;
144 };
145
146 export type ReactiveOptionalCallValue = {
147 kind: 'OptionalExpression';
148 id: InstructionId;
149 value: ReactiveValue;
150 optional: boolean;
151 loc: SourceLocation;
152 };
153
154 export type ReactiveTerminal =
155 | ReactiveBreakTerminal
156 | ReactiveContinueTerminal
157 | ReactiveReturnTerminal
158 | ReactiveThrowTerminal
159 | ReactiveSwitchTerminal
160 | ReactiveDoWhileTerminal
161 | ReactiveWhileTerminal
162 | ReactiveForTerminal
163 | ReactiveForOfTerminal
164 | ReactiveForInTerminal
165 | ReactiveIfTerminal
166 | ReactiveLabelTerminal
167 | ReactiveTryTerminal;
168
169 function _staticInvariantReactiveTerminalHasLocation(
170 terminal: ReactiveTerminal,
171 ): SourceLocation {
172 // If this fails, it is because a variant of ReactiveTerminal is missing a .loc - add it!
173 return terminal.loc;
174 }
175
176 function _staticInvariantReactiveTerminalHasInstructionId(
177 terminal: ReactiveTerminal,
178 ): InstructionId {
179 // If this fails, it is because a variant of ReactiveTerminal is missing a .id - add it!
180 return terminal.id;
181 }
182
183 export type ReactiveTerminalTargetKind = 'implicit' | 'labeled' | 'unlabeled';
184 export type ReactiveBreakTerminal = {
185 kind: 'break';
186 target: BlockId;
187 id: InstructionId;
188 targetKind: ReactiveTerminalTargetKind;
189 loc: SourceLocation;
190 };
191 export type ReactiveContinueTerminal = {
192 kind: 'continue';
193 target: BlockId;
194 id: InstructionId;
195 targetKind: ReactiveTerminalTargetKind;
196 loc: SourceLocation;
197 };
198 export type ReactiveReturnTerminal = {
199 kind: 'return';
200 value: Place;
201 id: InstructionId;
202 loc: SourceLocation;
203 };
204 export type ReactiveThrowTerminal = {
205 kind: 'throw';
206 value: Place;
207 id: InstructionId;
208 loc: SourceLocation;
209 };
210 export type ReactiveSwitchTerminal = {
211 kind: 'switch';
212 test: Place;
213 cases: Array<{
214 test: Place | null;
215 block: ReactiveBlock | void;
216 }>;
217 id: InstructionId;
218 loc: SourceLocation;
219 };
220 export type ReactiveDoWhileTerminal = {
221 kind: 'do-while';
222 loop: ReactiveBlock;
223 test: ReactiveValue;
224 id: InstructionId;
225 loc: SourceLocation;
226 };
227 export type ReactiveWhileTerminal = {
228 kind: 'while';
229 test: ReactiveValue;
230 loop: ReactiveBlock;
231 id: InstructionId;
232 loc: SourceLocation;
233 };
234 export type ReactiveForTerminal = {
235 kind: 'for';
236 init: ReactiveValue;
237 test: ReactiveValue;
238 update: ReactiveValue | null;
239 loop: ReactiveBlock;
240 id: InstructionId;
241 loc: SourceLocation;
242 };
243 export type ReactiveForOfTerminal = {
244 kind: 'for-of';
245 init: ReactiveValue;
246 test: ReactiveValue;
247 loop: ReactiveBlock;
248 id: InstructionId;
249 loc: SourceLocation;
250 };
251 export type ReactiveForInTerminal = {
252 kind: 'for-in';
253 init: ReactiveValue;
254 loop: ReactiveBlock;
255 id: InstructionId;
256 loc: SourceLocation;
257 };
258 export type ReactiveIfTerminal = {
259 kind: 'if';
260 test: Place;
261 consequent: ReactiveBlock;
262 alternate: ReactiveBlock | null;
263 id: InstructionId;
264 loc: SourceLocation;
265 };
266 export type ReactiveLabelTerminal = {
267 kind: 'label';
268 block: ReactiveBlock;
269 id: InstructionId;
270 loc: SourceLocation;
271 };
272 export type ReactiveTryTerminal = {
273 kind: 'try';
274 block: ReactiveBlock;
275 handlerBinding: Place | null;
276 handler: ReactiveBlock;
277 id: InstructionId;
278 loc: SourceLocation;
279 };
280
281 // A function lowered to HIR form, ie where its body is lowered to an HIR control-flow graph
282 export type HIRFunction = {
283 loc: SourceLocation;
284 id: ValidIdentifierName | null;
285 nameHint: string | null;
286 fnType: ReactFunctionType;
287 env: Environment;
288 params: Array<Place | SpreadPattern>;
289 returnTypeAnnotation: t.FlowType | t.TSType | null;
290 returns: Place;
291 context: Array<Place>;
292 body: HIR;
293 generator: boolean;
294 async: boolean;
295 directives: Array<string>;
296 aliasingEffects: Array<AliasingEffect> | null;
297 };
298
299 /*
300 * Each reactive scope may have its own control-flow, so the instructions form
301 * a control-flow graph. The graph comprises a set of basic blocks which reference
302 * each other via terminal statements, as well as a reference to the entry block.
303 */
304 export type HIR = {
305 entry: BlockId;
306
307 /*
308 * Basic blocks are stored as a map to aid certain operations that need to
309 * lookup blocks by their id. However, the order of the items in the map is
310 * reverse postorder, that is, barring cycles, predecessors appear before
311 * successors. This is designed to facilitate forward data flow analysis.
312 */
313 blocks: Map<BlockId, BasicBlock>;
314 };
315
316 /*
317 * Each basic block within an instruction graph contains zero or more instructions
318 * followed by a terminal node. Note that basic blocks always execute consecutively,
319 * there can be no branching within a block other than for an exception. Exceptions
320 * can occur pervasively and React runtime is responsible for resetting state when
321 * an exception occurs, therefore the block model only represents explicit throw
322 * statements and not implicit exceptions which may occur.
323 */
324 export type BlockKind = 'block' | 'value' | 'loop' | 'sequence' | 'catch';
325
326 /**
327 * Returns true for "block" and "catch" block kinds which correspond to statements
328 * in the source, including BlockStatement, CatchStatement.
329 *
330 * Inverse of isExpressionBlockKind()
331 */
332 export function isStatementBlockKind(kind: BlockKind): boolean {
333 return kind === 'block' || kind === 'catch';
334 }
335
336 /**
337 * Returns true for "value", "loop", and "sequence" block kinds which correspond to
338 * expressions in the source, such as ConditionalExpression, LogicalExpression, loop
339 * initializer/test/updaters, etc
340 *
341 * Inverse of isStatementBlockKind()
342 */
343 export function isExpressionBlockKind(kind: BlockKind): boolean {
344 return !isStatementBlockKind(kind);
345 }
346
347 export type BasicBlock = {
348 kind: BlockKind;
349 id: BlockId;
350 instructions: Array<Instruction>;
351 terminal: Terminal;
352 preds: Set<BlockId>;
353 phis: Set<Phi>;
354 };
355 export type TBasicBlock<T extends Terminal> = BasicBlock & {terminal: T};
356
357 /*
358 * Terminal nodes generally represent statements that affect control flow, such as
359 * for-of, if-else, return, etc.
360 */
361 export type Terminal =
362 | UnsupportedTerminal
363 | UnreachableTerminal
364 | ThrowTerminal
365 | ReturnTerminal
366 | GotoTerminal
367 | IfTerminal
368 | BranchTerminal
369 | SwitchTerminal
370 | ForTerminal
371 | ForOfTerminal
372 | ForInTerminal
373 | DoWhileTerminal
374 | WhileTerminal
375 | LogicalTerminal
376 | TernaryTerminal
377 | OptionalTerminal
378 | LabelTerminal
379 | SequenceTerminal
380 | MaybeThrowTerminal
381 | TryTerminal
382 | ReactiveScopeTerminal
383 | PrunedScopeTerminal;
384
385 export type TerminalWithFallthrough = Terminal & {fallthrough: BlockId};
386
387 function _staticInvariantTerminalHasLocation(
388 terminal: Terminal,
389 ): SourceLocation {
390 // If this fails, it is because a variant of Terminal is missing a .loc - add it!
391 return terminal.loc;
392 }
393
394 function _staticInvariantTerminalHasInstructionId(
395 terminal: Terminal,
396 ): InstructionId {
397 // If this fails, it is because a variant of Terminal is missing a .id - add it!
398 return terminal.id;
399 }
400
401 function _staticInvariantTerminalHasFallthrough(
402 terminal: Terminal,
403 ): BlockId | never | undefined {
404 // If this fails, it is because a variant of Terminal is missing a fallthrough annotation
405 return terminal.fallthrough;
406 }
407
408 /*
409 * Terminal nodes allowed for a value block
410 * A terminal that couldn't be lowered correctly.
411 */
412 export type UnsupportedTerminal = {
413 kind: 'unsupported';
414 id: InstructionId;
415 loc: SourceLocation;
416 fallthrough?: never;
417 };
418
419 /**
420 * Terminal for an unreachable block.
421 * Unreachable blocks are emitted when all control flow paths of a if/switch/try block diverge
422 * before reaching the fallthrough.
423 */
424 export type UnreachableTerminal = {
425 kind: 'unreachable';
426 id: InstructionId;
427 loc: SourceLocation;
428 fallthrough?: never;
429 };
430
431 export type ThrowTerminal = {
432 kind: 'throw';
433 value: Place;
434 id: InstructionId;
435 loc: SourceLocation;
436 fallthrough?: never;
437 };
438 export type Case = {test: Place | null; block: BlockId};
439
440 export type ReturnVariant = 'Void' | 'Implicit' | 'Explicit';
441 export type ReturnTerminal = {
442 kind: 'return';
443 /**
444 * Void:
445 * () => { ... }
446 * function() { ... }
447 * Implicit (ArrowFunctionExpression only):
448 * () => foo
449 * Explicit:
450 * () => { return ... }
451 * function () { return ... }
452 */
453 returnVariant: ReturnVariant;
454 loc: SourceLocation;
455 value: Place;
456 id: InstructionId;
457 fallthrough?: never;
458 effects: Array<AliasingEffect> | null;
459 };
460
461 export type GotoTerminal = {
462 kind: 'goto';
463 block: BlockId;
464 variant: GotoVariant;
465 id: InstructionId;
466 loc: SourceLocation;
467 fallthrough?: never;
468 };
469
470 export enum GotoVariant {
471 Break = 'Break',
472 Continue = 'Continue',
473 Try = 'Try',
474 }
475
476 export type IfTerminal = {
477 kind: 'if';
478 test: Place;
479 consequent: BlockId;
480 alternate: BlockId;
481 fallthrough: BlockId;
482 id: InstructionId;
483 loc: SourceLocation;
484 };
485
486 export type BranchTerminal = {
487 kind: 'branch';
488 test: Place;
489 consequent: BlockId;
490 alternate: BlockId;
491 id: InstructionId;
492 loc: SourceLocation;
493 fallthrough: BlockId;
494 };
495
496 export type SwitchTerminal = {
497 kind: 'switch';
498 test: Place;
499 cases: Array<Case>;
500 fallthrough: BlockId;
501 id: InstructionId;
502 loc: SourceLocation;
503 };
504
505 export type DoWhileTerminal = {
506 kind: 'do-while';
507 loop: BlockId;
508 test: BlockId;
509 fallthrough: BlockId;
510 id: InstructionId;
511 loc: SourceLocation;
512 };
513
514 export type WhileTerminal = {
515 kind: 'while';
516 loc: SourceLocation;
517 test: BlockId;
518 loop: BlockId;
519 fallthrough: BlockId;
520 id: InstructionId;
521 };
522
523 export type ForTerminal = {
524 kind: 'for';
525 loc: SourceLocation;
526 init: BlockId;
527 test: BlockId;
528 update: BlockId | null;
529 loop: BlockId;
530 fallthrough: BlockId;
531 id: InstructionId;
532 };
533
534 export type ForOfTerminal = {
535 kind: 'for-of';
536 loc: SourceLocation;
537 init: BlockId;
538 test: BlockId;
539 loop: BlockId;
540 fallthrough: BlockId;
541 id: InstructionId;
542 };
543
544 export type ForInTerminal = {
545 kind: 'for-in';
546 loc: SourceLocation;
547 init: BlockId;
548 loop: BlockId;
549 fallthrough: BlockId;
550 id: InstructionId;
551 };
552
553 export type LogicalTerminal = {
554 kind: 'logical';
555 operator: t.LogicalExpression['operator'];
556 test: BlockId;
557 fallthrough: BlockId;
558 id: InstructionId;
559 loc: SourceLocation;
560 };
561
562 export type TernaryTerminal = {
563 kind: 'ternary';
564 test: BlockId;
565 fallthrough: BlockId;
566 id: InstructionId;
567 loc: SourceLocation;
568 };
569
570 export type LabelTerminal = {
571 kind: 'label';
572 block: BlockId;
573 fallthrough: BlockId;
574 id: InstructionId;
575 loc: SourceLocation;
576 };
577
578 export type OptionalTerminal = {
579 kind: 'optional';
580 /*
581 * Specifies whether this node was optional. If false, it means that the original
582 * node was part of an optional chain but this specific item was non-optional.
583 * For example, in `a?.b.c?.()`, the `.b` access is non-optional but appears within
584 * an optional chain.
585 */
586 optional: boolean;
587 test: BlockId;
588 fallthrough: BlockId;
589 id: InstructionId;
590 loc: SourceLocation;
591 };
592
593 export type SequenceTerminal = {
594 kind: 'sequence';
595 block: BlockId;
596 fallthrough: BlockId;
597 id: InstructionId;
598 loc: SourceLocation;
599 };
600
601 export type TryTerminal = {
602 kind: 'try';
603 block: BlockId;
604 handlerBinding: Place | null;
605 handler: BlockId;
606 // TODO: support `finally`
607 fallthrough: BlockId;
608 id: InstructionId;
609 loc: SourceLocation;
610 };
611
612 export type MaybeThrowTerminal = {
613 kind: 'maybe-throw';
614 continuation: BlockId;
615 handler: BlockId | null;
616 id: InstructionId;
617 loc: SourceLocation;
618 fallthrough?: never;
619 effects: Array<AliasingEffect> | null;
620 };
621
622 export type ReactiveScopeTerminal = {
623 kind: 'scope';
624 fallthrough: BlockId;
625 block: BlockId;
626 scope: ReactiveScope;
627 id: InstructionId;
628 loc: SourceLocation;
629 };
630
631 export type PrunedScopeTerminal = {
632 kind: 'pruned-scope';
633 fallthrough: BlockId;
634 block: BlockId;
635 scope: ReactiveScope;
636 id: InstructionId;
637 loc: SourceLocation;
638 };
639
640 /*
641 * Instructions generally represent expressions but with all nesting flattened away,
642 * such that all operands to each instruction are either primitive values OR are
643 * references to a place, which may be a temporary that holds the results of a
644 * previous instruction. So `foo(bar(a))` would decompose into two instructions,
645 * one to store `tmp0 = bar(a)`, one for `foo(tmp0)`.
646 *
647 * Instructions generally store their value into a Place, though some instructions
648 * may not produce a value that is necessary to track (for example, class definitions)
649 * or may occur only for side-effects (many expression statements).
650 */
651 export type Instruction = {
652 id: InstructionId;
653 lvalue: Place;
654 value: InstructionValue;
655 loc: SourceLocation;
656 effects: Array<AliasingEffect> | null;
657 };
658
659 export type TInstruction<T extends InstructionValue> = {
660 id: InstructionId;
661 lvalue: Place;
662 value: T;
663 effects: Array<AliasingEffect> | null;
664 loc: SourceLocation;
665 };
666
667 export type LValue = {
668 place: Place;
669 kind: InstructionKind;
670 };
671
672 export type LValuePattern = {
673 pattern: Pattern;
674 kind: InstructionKind;
675 };
676
677 export type ArrayExpression = {
678 kind: 'ArrayExpression';
679 elements: Array<Place | SpreadPattern | Hole>;
680 loc: SourceLocation;
681 };
682
683 export type Pattern = ArrayPattern | ObjectPattern;
684
685 export type Hole = {
686 kind: 'Hole';
687 };
688
689 export type SpreadPattern = {
690 kind: 'Spread';
691 place: Place;
692 };
693
694 export type ArrayPattern = {
695 kind: 'ArrayPattern';
696 items: Array<Place | SpreadPattern | Hole>;
697 loc: SourceLocation;
698 };
699
700 export type ObjectPattern = {
701 kind: 'ObjectPattern';
702 properties: Array<ObjectProperty | SpreadPattern>;
703 loc: SourceLocation;
704 };
705
706 export type ObjectPropertyKey =
707 | {
708 kind: 'string';
709 name: string;
710 }
711 | {
712 kind: 'identifier';
713 name: string;
714 }
715 | {
716 kind: 'computed';
717 name: Place;
718 }
719 | {
720 kind: 'number';
721 name: number;
722 };
723
724 export type ObjectProperty = {
725 kind: 'ObjectProperty';
726 key: ObjectPropertyKey;
727 type: 'property' | 'method';
728 place: Place;
729 };
730
731 export type LoweredFunction = {
732 func: HIRFunction;
733 };
734
735 export type ObjectMethod = {
736 kind: 'ObjectMethod';
737 loc: SourceLocation;
738 loweredFunc: LoweredFunction;
739 };
740
741 export enum InstructionKind {
742 // const declaration
743 Const = 'Const',
744 // let declaration
745 Let = 'Let',
746 // assing a new value to a let binding
747 Reassign = 'Reassign',
748 // catch clause binding
749 Catch = 'Catch',
750
751 // hoisted const declarations
752 HoistedConst = 'HoistedConst',
753
754 // hoisted const declarations
755 HoistedLet = 'HoistedLet',
756
757 HoistedFunction = 'HoistedFunction',
758 Function = 'Function',
759 }
760
761 export function convertHoistedLValueKind(
762 kind: InstructionKind,
763 ): InstructionKind | null {
764 switch (kind) {
765 case InstructionKind.HoistedLet:
766 return InstructionKind.Let;
767 case InstructionKind.HoistedConst:
768 return InstructionKind.Const;
769 case InstructionKind.HoistedFunction:
770 return InstructionKind.Function;
771 case InstructionKind.Let:
772 case InstructionKind.Const:
773 case InstructionKind.Function:
774 case InstructionKind.Reassign:
775 case InstructionKind.Catch:
776 return null;
777 default:
778 assertExhaustive(kind, 'Unexpected lvalue kind');
779 }
780 }
781
782 function _staticInvariantInstructionValueHasLocation(
783 value: InstructionValue,
784 ): SourceLocation {
785 // If this fails, it is because a variant of InstructionValue is missing a .loc - add it!
786 return value.loc;
787 }
788
789 export type Phi = {
790 kind: 'Phi';
791 place: Place;
792 operands: Map<BlockId, Place>;
793 };
794
795 /**
796 * Valid ManualMemoDependencies are always of the form
797 * `sourceDeclaredVariable.a.b?.c`, since this is documented
798 * and enforced by the `react-hooks/exhaustive-deps` rule.
799 *
800 * `root` must either reference a ValidatedIdentifier or a global
801 * variable.
802 */
803 export type ManualMemoDependency = {
804 root:
805 | {
806 kind: 'NamedLocal';
807 value: Place;
808 constant: boolean;
809 }
810 | {kind: 'Global'; identifierName: string};
811 path: DependencyPath;
812 loc: SourceLocation;
813 };
814
815 export type StartMemoize = {
816 kind: 'StartMemoize';
817 // Start/FinishMemoize markers should have matching ids
818 manualMemoId: number;
819 /**
820 * deps-list from source code, or null if one was not provided
821 * (e.g. useMemo without a second arg)
822 */
823 deps: Array<ManualMemoDependency> | null;
824 /**
825 * The source location of the dependencies argument. Used for
826 * emitting diagnostics with a suggested replacement
827 */
828 depsLoc: SourceLocation | null;
829 hasInvalidDeps?: true;
830 loc: SourceLocation;
831 };
832 export type FinishMemoize = {
833 kind: 'FinishMemoize';
834 // Start/FinishMemoize markers should have matching ids
835 manualMemoId: number;
836 decl: Place;
837 pruned?: true;
838 loc: SourceLocation;
839 };
840
841 /*
842 * Forget currently does not handle MethodCall correctly in
843 * all cases. Specifically, we do not bind the receiver and method property
844 * before calling to args. Until we add a SequenceExpression to inline all
845 * instructions generated when lowering args, we have a limited representation
846 * with some constraints.
847 *
848 * Forget currently makes these assumptions (checked in codegen):
849 * - {@link MethodCall.property} is a temporary produced by a PropertyLoad or ComputedLoad
850 * on {@link MethodCall.receiver}
851 * - {@link MethodCall.property} remains an rval (i.e. never promoted to a
852 * named identifier). We currently rely on this for codegen.
853 *
854 * Type inference does not currently guarantee that {@link MethodCall.property}
855 * is a FunctionType.
856 */
857 export type MethodCall = {
858 kind: 'MethodCall';
859 receiver: Place;
860 property: Place;
861 args: Array<Place | SpreadPattern>;
862 loc: SourceLocation;
863 };
864
865 export type CallExpression = {
866 kind: 'CallExpression';
867 callee: Place;
868 args: Array<Place | SpreadPattern>;
869 loc: SourceLocation;
870 typeArguments?: Array<t.FlowType>;
871 };
872
873 export type NewExpression = {
874 kind: 'NewExpression';
875 callee: Place;
876 args: Array<Place | SpreadPattern>;
877 loc: SourceLocation;
878 };
879
880 export type LoadLocal = {
881 kind: 'LoadLocal';
882 place: Place;
883 loc: SourceLocation;
884 };
885 export type LoadContext = {
886 kind: 'LoadContext';
887 place: Place;
888 loc: SourceLocation;
889 };
890
891 /*
892 * The value of a given instruction. Note that values are not recursive: complex
893 * values such as objects or arrays are always defined by instructions to define
894 * their operands (saving to a temporary), then passing those temporaries as
895 * the operands to the final instruction (ObjectExpression, ArrayExpression, etc).
896 *
897 * Operands are therefore always a Place.
898 */
899
900 export type InstructionValue =
901 | LoadLocal
902 | LoadContext
903 | {
904 kind: 'DeclareLocal';
905 lvalue: LValue;
906 type: t.FlowType | t.TSType | null;
907 loc: SourceLocation;
908 }
909 | {
910 kind: 'DeclareContext';
911 lvalue: {
912 kind:
913 | InstructionKind.Let
914 | InstructionKind.HoistedConst
915 | InstructionKind.HoistedLet
916 | InstructionKind.HoistedFunction;
917 place: Place;
918 };
919 loc: SourceLocation;
920 }
921 | StoreLocal
922 | {
923 kind: 'StoreContext';
924 /**
925 * StoreContext kinds:
926 * Reassign: context variable reassignment in source
927 * Const: const declaration + assignment in source
928 * ('const' context vars are ones whose declarations are hoisted)
929 * Let: let declaration + assignment in source
930 * Function: function declaration in source (similar to `const`)
931 */
932 lvalue: {
933 kind:
934 | InstructionKind.Reassign
935 | InstructionKind.Const
936 | InstructionKind.Let
937 | InstructionKind.Function;
938 place: Place;
939 };
940 value: Place;
941 loc: SourceLocation;
942 }
943 | Destructure
944 | {
945 kind: 'Primitive';
946 value: number | boolean | string | null | undefined;
947 loc: SourceLocation;
948 }
949 | JSXText
950 | {
951 kind: 'BinaryExpression';
952 operator: Exclude<t.BinaryExpression['operator'], '|>'>;
953 left: Place;
954 right: Place;
955 loc: SourceLocation;
956 }
957 | NewExpression
958 | CallExpression
959 | MethodCall
960 | {
961 kind: 'UnaryExpression';
962 operator: Exclude<t.UnaryExpression['operator'], 'throw' | 'delete'>;
963 value: Place;
964 loc: SourceLocation;
965 }
966 | ({
967 kind: 'TypeCastExpression';
968 value: Place;
969 type: Type;
970 loc: SourceLocation;
971 } & (
972 | {
973 typeAnnotation: t.FlowType;
974 typeAnnotationKind: 'cast';
975 }
976 | {
977 typeAnnotation: t.TSType;
978 typeAnnotationKind: 'as' | 'satisfies';
979 }
980 ))
981 | JsxExpression
982 | {
983 kind: 'ObjectExpression';
984 properties: Array<ObjectProperty | SpreadPattern>;
985 loc: SourceLocation;
986 }
987 | ObjectMethod
988 | ArrayExpression
989 | {kind: 'JsxFragment'; children: Array<Place>; loc: SourceLocation}
990 | {
991 kind: 'RegExpLiteral';
992 pattern: string;
993 flags: string;
994 loc: SourceLocation;
995 }
996 | {
997 kind: 'MetaProperty';
998 meta: string;
999 property: string;
1000 loc: SourceLocation;
1001 }
1002
1003 // store `object.property = value`
1004 | {
1005 kind: 'PropertyStore';
1006 object: Place;
1007 property: PropertyLiteral;
1008 value: Place;
1009 loc: SourceLocation;
1010 }
1011 // load `object.property`
1012 | PropertyLoad
1013 // `delete object.property`
1014 | {
1015 kind: 'PropertyDelete';
1016 object: Place;
1017 property: PropertyLiteral;
1018 loc: SourceLocation;
1019 }
1020
1021 // store `object[index] = value` - like PropertyStore but with a dynamic property
1022 | {
1023 kind: 'ComputedStore';
1024 object: Place;
1025 property: Place;
1026 value: Place;
1027 loc: SourceLocation;
1028 }
1029 // load `object[index]` - like PropertyLoad but with a dynamic property
1030 | {
1031 kind: 'ComputedLoad';
1032 object: Place;
1033 property: Place;
1034 loc: SourceLocation;
1035 }
1036 // `delete object[property]`
1037 | {
1038 kind: 'ComputedDelete';
1039 object: Place;
1040 property: Place;
1041 loc: SourceLocation;
1042 }
1043 | LoadGlobal
1044 | StoreGlobal
1045 | FunctionExpression
1046 | {
1047 kind: 'TaggedTemplateExpression';
1048 tag: Place;
1049 value: {raw: string; cooked?: string};
1050 loc: SourceLocation;
1051 }
1052 | {
1053 kind: 'TemplateLiteral';
1054 subexprs: Array<Place>;
1055 quasis: Array<{raw: string; cooked?: string}>;
1056 loc: SourceLocation;
1057 }
1058 | {
1059 kind: 'Await';
1060 value: Place;
1061 loc: SourceLocation;
1062 }
1063 | {
1064 kind: 'GetIterator';
1065 collection: Place; // the collection
1066 loc: SourceLocation;
1067 }
1068 | {
1069 kind: 'IteratorNext';
1070 iterator: Place; // the iterator created with GetIterator
1071 collection: Place; // the collection being iterated over (which may be an iterable or iterator)
1072 loc: SourceLocation;
1073 }
1074 | {
1075 kind: 'NextPropertyOf';
1076 value: Place; // the collection
1077 loc: SourceLocation;
1078 }
1079 /*
1080 * Models a prefix update expression such as --x or ++y
1081 * This instructions increments or decrements the <lvalue>
1082 * but evaluates to the value of <value> prior to the update.
1083 */
1084 | {
1085 kind: 'PrefixUpdate';
1086 lvalue: Place;
1087 operation: t.UpdateExpression['operator'];
1088 value: Place;
1089 loc: SourceLocation;
1090 }
1091 /*
1092 * Models a postfix update expression such as x-- or y++
1093 * This instructions increments or decrements the <lvalue>
1094 * and evaluates to the value after the update
1095 */
1096 | {
1097 kind: 'PostfixUpdate';
1098 lvalue: Place;
1099 operation: t.UpdateExpression['operator'];
1100 value: Place;
1101 loc: SourceLocation;
1102 }
1103 // `debugger` statement
1104 | {kind: 'Debugger'; loc: SourceLocation}
1105 /*
1106 * Represents semantic information from useMemo/useCallback that the developer
1107 * has indicated a particular value should be memoized. This value is ignored
1108 * unless the TODO flag is enabled.
1109 *
1110 * NOTE: the Memoize instruction is intended for side-effects only, and is pruned
1111 * during codegen. It can't be pruned during DCE because we need to preserve the
1112 * instruction so it can be visible in InferReferenceEffects.
1113 */
1114 | StartMemoize
1115 | FinishMemoize
1116 /*
1117 * Catch-all for statements such as type imports, nested class declarations, etc
1118 * which are not directly represented, but included for completeness and to allow
1119 * passing through in codegen.
1120 */
1121 | {
1122 kind: 'UnsupportedNode';
1123 node: t.Node;
1124 loc: SourceLocation;
1125 };
1126
1127 export type JsxExpression = {
1128 kind: 'JsxExpression';
1129 tag: Place | BuiltinTag;
1130 props: Array<JsxAttribute>;
1131 children: Array<Place> | null; // null === no children
1132 loc: SourceLocation;
1133 openingLoc: SourceLocation;
1134 closingLoc: SourceLocation;
1135 };
1136
1137 export type JsxAttribute =
1138 | {kind: 'JsxSpreadAttribute'; argument: Place}
1139 | {kind: 'JsxAttribute'; name: string; place: Place};
1140
1141 export type FunctionExpression = {
1142 kind: 'FunctionExpression';
1143 name: ValidIdentifierName | null;
1144 nameHint: string | null;
1145 loweredFunc: LoweredFunction;
1146 type:
1147 | 'ArrowFunctionExpression'
1148 | 'FunctionExpression'
1149 | 'FunctionDeclaration';
1150 loc: SourceLocation;
1151 };
1152
1153 export type Destructure = {
1154 kind: 'Destructure';
1155 lvalue: LValuePattern;
1156 value: Place;
1157 loc: SourceLocation;
1158 };
1159
1160 /*
1161 * A place where data may be read from / written to:
1162 * - a variable (identifier)
1163 * - a path into an identifier
1164 */
1165 export type Place = {
1166 kind: 'Identifier';
1167 identifier: Identifier;
1168 effect: Effect;
1169 reactive: boolean;
1170 loc: SourceLocation;
1171 };
1172
1173 // A primitive value with a specific (constant) value.
1174 export type Primitive = {
1175 kind: 'Primitive';
1176 value: number | boolean | string | null | undefined;
1177 loc: SourceLocation;
1178 };
1179
1180 export type JSXText = {kind: 'JSXText'; value: string; loc: SourceLocation};
1181
1182 export type StoreLocal = {
1183 kind: 'StoreLocal';
1184 lvalue: LValue;
1185 value: Place;
1186 type: t.FlowType | t.TSType | null;
1187 loc: SourceLocation;
1188 };
1189 export type PropertyLoad = {
1190 kind: 'PropertyLoad';
1191 object: Place;
1192 property: PropertyLiteral;
1193 loc: SourceLocation;
1194 };
1195
1196 export type LoadGlobal = {
1197 kind: 'LoadGlobal';
1198 binding: NonLocalBinding;
1199 loc: SourceLocation;
1200 };
1201
1202 export type StoreGlobal = {
1203 kind: 'StoreGlobal';
1204 name: string;
1205 value: Place;
1206 loc: SourceLocation;
1207 };
1208
1209 export type BuiltinTag = {
1210 kind: 'BuiltinTag';
1211 name: string;
1212 loc: SourceLocation;
1213 };
1214
1215 /*
1216 * Range in which an identifier is mutable. Start and End refer to Instruction.id.
1217 *
1218 * Start is inclusive, End is exclusive (ie, end is the "first" instruction for which
1219 * the value is not mutable).
1220 */
1221 export type MutableRange = {
1222 start: InstructionId;
1223 end: InstructionId;
1224 };
1225
1226 export type VariableBinding =
1227 // let, const, etc declared within the current component/hook
1228 | {kind: 'Identifier'; identifier: Identifier; bindingKind: BindingKind}
1229 // bindings declard outside the current component/hook
1230 | NonLocalBinding;
1231
1232 // `import {bar as baz} from 'foo'`: name=baz, module=foo, imported=bar
1233 export type NonLocalImportSpecifier = {
1234 kind: 'ImportSpecifier';
1235 name: string;
1236 module: string;
1237 imported: string;
1238 };
1239
1240 export type NonLocalBinding =
1241 // `import Foo from 'foo'`: name=Foo, module=foo
1242 | {kind: 'ImportDefault'; name: string; module: string}
1243 // `import * as Foo from 'foo'`: name=Foo, module=foo
1244 | {kind: 'ImportNamespace'; name: string; module: string}
1245 // `import {bar as baz} from 'foo'`
1246 | NonLocalImportSpecifier
1247 // let, const, function, etc declared in the module but outside the current component/hook
1248 | {kind: 'ModuleLocal'; name: string}
1249 // an unresolved binding
1250 | {kind: 'Global'; name: string};
1251
1252 // Represents a user-defined variable (has a name) or a temporary variable (no name).
1253 export type Identifier = {
1254 /**
1255 * After EnterSSA, `id` uniquely identifies an SSA instance of a variable.
1256 * Before EnterSSA, `id` matches `declarationId`.
1257 */
1258 id: IdentifierId;
1259
1260 /**
1261 * Uniquely identifies a given variable in the original program. If a value is
1262 * reassigned in the original program each reassigned value will have a distinct
1263 * `id` (after EnterSSA), but they will still have the same `declarationId`.
1264 */
1265 declarationId: DeclarationId;
1266
1267 // null for temporaries. name is primarily used for debugging.
1268 name: IdentifierName | null;
1269 // The range for which this variable is mutable
1270 mutableRange: MutableRange;
1271 /*
1272 * The ID of the reactive scope which will compute this value. Multiple
1273 * variables may have the same scope id.
1274 */
1275 scope: ReactiveScope | null;
1276 type: Type;
1277 loc: SourceLocation;
1278 };
1279
1280 export type IdentifierName = ValidatedIdentifier | PromotedIdentifier;
1281 export type ValidatedIdentifier = {kind: 'named'; value: ValidIdentifierName};
1282 export type PromotedIdentifier = {kind: 'promoted'; value: string};
1283
1284 /**
1285 * Simulated opaque type for identifier names to ensure values can only be created
1286 * through the below helpers.
1287 */
1288 const opaqueValidIdentifierName = Symbol();
1289 export type ValidIdentifierName = string & {
1290 [opaqueValidIdentifierName]: 'ValidIdentifierName';
1291 };
1292
1293 export function makeTemporaryIdentifier(
1294 id: IdentifierId,
1295 loc: SourceLocation,
1296 ): Identifier {
1297 return {
1298 id,
1299 name: null,
1300 declarationId: makeDeclarationId(id),
1301 mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
1302 scope: null,
1303 type: makeType(),
1304 loc,
1305 };
1306 }
1307
1308 export function forkTemporaryIdentifier(
1309 id: IdentifierId,
1310 source: Identifier,
1311 ): Identifier {
1312 return {
1313 ...source,
1314 mutableRange: {start: makeInstructionId(0), end: makeInstructionId(0)},
1315 id,
1316 };
1317 }
1318
1319 export function validateIdentifierName(
1320 name: string,
1321 ): Result<ValidatedIdentifier, CompilerError> {
1322 if (isReservedWord(name)) {
1323 const error = new CompilerError();
1324 error.pushDiagnostic(
1325 CompilerDiagnostic.create({
1326 category: ErrorCategory.Syntax,
1327 reason: 'Expected a non-reserved identifier name',
1328 description: `\`${name}\` is a reserved word in JavaScript and cannot be used as an identifier name`,
1329 suggestions: null,
1330 }).withDetails({
1331 kind: 'error',
1332 loc: GeneratedSource,
1333 message: 'reserved word',
1334 }),
1335 );
1336 return Err(error);
1337 } else if (!t.isValidIdentifier(name)) {
1338 const error = new CompilerError();
1339 error.pushDiagnostic(
1340 CompilerDiagnostic.create({
1341 category: ErrorCategory.Syntax,
1342 reason: `Expected a valid identifier name`,
1343 description: `\`${name}\` is not a valid JavaScript identifier`,
1344 suggestions: null,
1345 }).withDetails({
1346 kind: 'error',
1347 loc: GeneratedSource,
1348 message: 'reserved word',
1349 }),
1350 );
1351 }
1352 return Ok({
1353 kind: 'named',
1354 value: name as ValidIdentifierName,
1355 });
1356 }
1357
1358 /**
1359 * Creates a valid identifier name. This should *not* be used for synthesizing
1360 * identifier names: only call this method for identifier names that appear in the
1361 * original source code.
1362 */
1363 export function makeIdentifierName(name: string): ValidatedIdentifier {
1364 return validateIdentifierName(name).unwrap();
1365 }
1366
1367 /**
1368 * Given an unnamed identifier, promote it to a named identifier.
1369 *
1370 * Note: this uses the identifier's DeclarationId to ensure that all
1371 * instances of the same declaration will have the same name.
1372 */
1373 export function promoteTemporary(identifier: Identifier): void {
1374 CompilerError.invariant(identifier.name === null, {
1375 reason: `Expected a temporary (unnamed) identifier`,
1376 description: `Identifier already has a name, \`${identifier.name}\``,
1377 loc: GeneratedSource,
1378 });
1379 identifier.name = {
1380 kind: 'promoted',
1381 value: `#t${identifier.declarationId}`,
1382 };
1383 }
1384
1385 export function isPromotedTemporary(name: string): boolean {
1386 return name.startsWith('#t');
1387 }
1388
1389 /**
1390 * Given an unnamed identifier, promote it to a named identifier, distinguishing
1391 * it as a value that needs to be capitalized since it appears in JSX element tag position
1392 *
1393 * Note: this uses the identifier's DeclarationId to ensure that all
1394 * instances of the same declaration will have the same name.
1395 */
1396 export function promoteTemporaryJsxTag(identifier: Identifier): void {
1397 CompilerError.invariant(identifier.name === null, {
1398 reason: `Expected a temporary (unnamed) identifier`,
1399 description: `Identifier already has a name, \`${identifier.name}\``,
1400 loc: GeneratedSource,
1401 });
1402 identifier.name = {
1403 kind: 'promoted',
1404 value: `#T${identifier.declarationId}`,
1405 };
1406 }
1407
1408 export function isPromotedJsxTemporary(name: string): boolean {
1409 return name.startsWith('#T');
1410 }
1411
1412 export type AbstractValue = {
1413 kind: ValueKind;
1414 reason: ReadonlySet<ValueReason>;
1415 context: ReadonlySet<Place>;
1416 };
1417
1418 /**
1419 * The reason for the kind of a value.
1420 */
1421 export enum ValueReason {
1422 /**
1423 * Defined outside the React function.
1424 */
1425 Global = 'global',
1426
1427 /**
1428 * Used in a JSX expression.
1429 */
1430 JsxCaptured = 'jsx-captured',
1431
1432 /**
1433 * Argument to a hook
1434 */
1435 HookCaptured = 'hook-captured',
1436
1437 /**
1438 * Return value of a hook
1439 */
1440 HookReturn = 'hook-return',
1441
1442 /**
1443 * Passed to an effect
1444 */
1445 Effect = 'effect',
1446
1447 /**
1448 * Return value of a function with known frozen return value, e.g. `useState`.
1449 */
1450 KnownReturnSignature = 'known-return-signature',
1451
1452 /**
1453 * A value returned from `useContext`
1454 */
1455 Context = 'context',
1456
1457 /**
1458 * A value returned from `useState`
1459 */
1460 State = 'state',
1461
1462 /**
1463 * A value returned from `useReducer`
1464 */
1465 ReducerState = 'reducer-state',
1466
1467 /**
1468 * Props of a component or arguments of a hook.
1469 */
1470 ReactiveFunctionArgument = 'reactive-function-argument',
1471
1472 Other = 'other',
1473 }
1474
1475 /*
1476 * Distinguish between different kinds of values relevant to inference purposes:
1477 * see the main docblock for the module for details.
1478 */
1479 export enum ValueKind {
1480 MaybeFrozen = 'maybefrozen',
1481 Frozen = 'frozen',
1482 Primitive = 'primitive',
1483 Global = 'global',
1484 Mutable = 'mutable',
1485 Context = 'context',
1486 }
1487
1488 export const ValueKindSchema = z.enum([
1489 ValueKind.MaybeFrozen,
1490 ValueKind.Frozen,
1491 ValueKind.Primitive,
1492 ValueKind.Global,
1493 ValueKind.Mutable,
1494 ValueKind.Context,
1495 ]);
1496
1497 export const ValueReasonSchema = z.enum([
1498 ValueReason.Context,
1499 ValueReason.Effect,
1500 ValueReason.Global,
1501 ValueReason.HookCaptured,
1502 ValueReason.HookReturn,
1503 ValueReason.JsxCaptured,
1504 ValueReason.KnownReturnSignature,
1505 ValueReason.Other,
1506 ValueReason.ReactiveFunctionArgument,
1507 ValueReason.ReducerState,
1508 ValueReason.State,
1509 ]);
1510
1511 // The effect with which a value is modified.
1512 export enum Effect {
1513 // Default value: not allowed after lifetime inference
1514 Unknown = '<unknown>',
1515 // This reference freezes the value (corresponds to a place where codegen should emit a freeze instruction)
1516 Freeze = 'freeze',
1517 // This reference reads the value
1518 Read = 'read',
1519 // This reference reads and stores the value
1520 Capture = 'capture',
1521 ConditionallyMutateIterator = 'mutate-iterator?',
1522 /*
1523 * This reference *may* write to (mutate) the value. This covers two similar cases:
1524 * - The compiler is being conservative and assuming that a value *may* be mutated
1525 * - The effect is polymorphic: mutable values may be mutated, non-mutable values
1526 * will not be mutated.
1527 * In both cases, we conservatively assume that mutable values will be mutated.
1528 * But we do not error if the value is known to be immutable.
1529 */
1530 ConditionallyMutate = 'mutate?',
1531
1532 /*
1533 * This reference *does* write to (mutate) the value. It is an error (invalid input)
1534 * if an immutable value flows into a location with this effect.
1535 */
1536 Mutate = 'mutate',
1537 // This reference may alias to (mutate) the value
1538 Store = 'store',
1539 }
1540 export const EffectSchema = z.enum([
1541 Effect.Read,
1542 Effect.Mutate,
1543 Effect.ConditionallyMutate,
1544 Effect.ConditionallyMutateIterator,
1545 Effect.Capture,
1546 Effect.Store,
1547 Effect.Freeze,
1548 ]);
1549
1550 export function isMutableEffect(
1551 effect: Effect,
1552 location: SourceLocation,
1553 ): boolean {
1554 switch (effect) {
1555 case Effect.Capture:
1556 case Effect.Store:
1557 case Effect.ConditionallyMutate:
1558 case Effect.ConditionallyMutateIterator:
1559 case Effect.Mutate: {
1560 return true;
1561 }
1562
1563 case Effect.Unknown: {
1564 CompilerError.invariant(false, {
1565 reason: 'Unexpected unknown effect',
1566 loc: location,
1567 });
1568 }
1569 case Effect.Read:
1570 case Effect.Freeze: {
1571 return false;
1572 }
1573 default: {
1574 assertExhaustive(effect, `Unexpected effect \`${effect}\``);
1575 }
1576 }
1577 }
1578
1579 export type ReactiveScope = {
1580 id: ScopeId;
1581 range: MutableRange;
1582
1583 /**
1584 * The inputs to this reactive scope
1585 */
1586 dependencies: ReactiveScopeDependencies;
1587
1588 /**
1589 * The set of values produced by this scope. This may be empty
1590 * for scopes that produce reassignments only.
1591 */
1592 declarations: Map<IdentifierId, ReactiveScopeDeclaration>;
1593
1594 /**
1595 * A mutable range may sometimes include a reassignment of some variable.
1596 * This is the set of identifiers which are reassigned by this scope.
1597 */
1598 reassignments: Set<Identifier>;
1599
1600 /**
1601 * Reactive scopes may contain a return statement, which needs to be replayed
1602 * whenever the inputs to the scope have not changed since the previous execution.
1603 * If the reactive scope has an early return, this variable stores the temporary
1604 * identifier to which the return value will be assigned. See PropagateEarlyReturns
1605 * for more about how early returns in reactive scopes are compiled and represented.
1606 *
1607 * This value is null for scopes that do not contain early returns.
1608 */
1609 earlyReturnValue: {
1610 value: Identifier;
1611 loc: SourceLocation;
1612 label: BlockId;
1613 } | null;
1614
1615 /*
1616 * Some passes may merge scopes together. The merged set contains the
1617 * ids of scopes that were merged into this one, for passes that need
1618 * to track which scopes are still present (in some form) vs scopes that
1619 * no longer exist due to being pruned.
1620 */
1621 merged: Set<ScopeId>;
1622
1623 loc: SourceLocation;
1624 };
1625
1626 export type ReactiveScopeDependencies = Set<ReactiveScopeDependency>;
1627
1628 export type ReactiveScopeDeclaration = {
1629 identifier: Identifier;
1630 scope: ReactiveScope; // the scope in which the variable was originally declared
1631 };
1632
1633 const opaquePropertyLiteral = Symbol();
1634 export type PropertyLiteral = (string | number) & {
1635 [opaquePropertyLiteral]: 'PropertyLiteral';
1636 };
1637 export function makePropertyLiteral(value: string | number): PropertyLiteral {
1638 return value as PropertyLiteral;
1639 }
1640 export type DependencyPathEntry = {
1641 property: PropertyLiteral;
1642 optional: boolean;
1643 loc: SourceLocation;
1644 };
1645 export type DependencyPath = Array<DependencyPathEntry>;
1646 export type ReactiveScopeDependency = {
1647 identifier: Identifier;
1648 /**
1649 * Reflects whether the base identifier is reactive. Note that some reactive
1650 * objects may have non-reactive properties, but we do not currently track
1651 * this.
1652 *
1653 * ```js
1654 * // Technically, result[0] is reactive and result[1] is not.
1655 * // Currently, both dependencies would be marked as reactive.
1656 * const result = useState();
1657 * ```
1658 */
1659 reactive: boolean;
1660 path: DependencyPath;
1661 loc: SourceLocation;
1662 };
1663
1664 export function areEqualPaths(a: DependencyPath, b: DependencyPath): boolean {
1665 return (
1666 a.length === b.length &&
1667 a.every(
1668 (item, ix) =>
1669 item.property === b[ix].property && item.optional === b[ix].optional,
1670 )
1671 );
1672 }
1673 export function isSubPath(
1674 subpath: DependencyPath,
1675 path: DependencyPath,
1676 ): boolean {
1677 return (
1678 subpath.length <= path.length &&
1679 subpath.every(
1680 (item, ix) =>
1681 item.property === path[ix].property &&
1682 item.optional === path[ix].optional,
1683 )
1684 );
1685 }
1686 export function isSubPathIgnoringOptionals(
1687 subpath: DependencyPath,
1688 path: DependencyPath,
1689 ): boolean {
1690 return (
1691 subpath.length <= path.length &&
1692 subpath.every((item, ix) => item.property === path[ix].property)
1693 );
1694 }
1695
1696 export function getPlaceScope(
1697 id: InstructionId,
1698 place: Place,
1699 ): ReactiveScope | null {
1700 const scope = place.identifier.scope;
1701 if (scope !== null && isScopeActive(scope, id)) {
1702 return scope;
1703 }
1704 return null;
1705 }
1706
1707 function isScopeActive(scope: ReactiveScope, id: InstructionId): boolean {
1708 return id >= scope.range.start && id < scope.range.end;
1709 }
1710
1711 /*
1712 * Simulated opaque type for BlockIds to prevent using normal numbers as block ids
1713 * accidentally.
1714 */
1715 const opaqueBlockId = Symbol();
1716 export type BlockId = number & {[opaqueBlockId]: 'BlockId'};
1717
1718 export function makeBlockId(id: number): BlockId {
1719 CompilerError.invariant(id >= 0 && Number.isInteger(id), {
1720 reason: 'Expected block id to be a non-negative integer',
1721 loc: GeneratedSource,
1722 });
1723 return id as BlockId;
1724 }
1725
1726 /*
1727 * Simulated opaque type for ScopeIds to prevent using normal numbers as scope ids
1728 * accidentally.
1729 */
1730 const opaqueScopeId = Symbol();
1731 export type ScopeId = number & {[opaqueScopeId]: 'ScopeId'};
1732
1733 export function makeScopeId(id: number): ScopeId {
1734 CompilerError.invariant(id >= 0 && Number.isInteger(id), {
1735 reason: 'Expected block id to be a non-negative integer',
1736 loc: GeneratedSource,
1737 });
1738 return id as ScopeId;
1739 }
1740
1741 /*
1742 * Simulated opaque type for IdentifierId to prevent using normal numbers as ids
1743 * accidentally.
1744 */
1745 const opaqueIdentifierId = Symbol();
1746 export type IdentifierId = number & {[opaqueIdentifierId]: 'IdentifierId'};
1747
1748 export function makeIdentifierId(id: number): IdentifierId {
1749 CompilerError.invariant(id >= 0 && Number.isInteger(id), {
1750 reason: 'Expected identifier id to be a non-negative integer',
1751 loc: GeneratedSource,
1752 });
1753 return id as IdentifierId;
1754 }
1755
1756 /*
1757 * Simulated opaque type for IdentifierId to prevent using normal numbers as ids
1758 * accidentally.
1759 */
1760 const opageDeclarationId = Symbol();
1761 export type DeclarationId = number & {[opageDeclarationId]: 'DeclarationId'};
1762
1763 export function makeDeclarationId(id: number): DeclarationId {
1764 CompilerError.invariant(id >= 0 && Number.isInteger(id), {
1765 reason: 'Expected declaration id to be a non-negative integer',
1766 loc: GeneratedSource,
1767 });
1768 return id as DeclarationId;
1769 }
1770
1771 /*
1772 * Simulated opaque type for InstructionId to prevent using normal numbers as ids
1773 * accidentally.
1774 */
1775 const opaqueInstructionId = Symbol();
1776 export type InstructionId = number & {[opaqueInstructionId]: 'IdentifierId'};
1777
1778 export function makeInstructionId(id: number): InstructionId {
1779 CompilerError.invariant(id >= 0 && Number.isInteger(id), {
1780 reason: 'Expected instruction id to be a non-negative integer',
1781 loc: GeneratedSource,
1782 });
1783 return id as InstructionId;
1784 }
1785
1786 export function isObjectMethodType(id: Identifier): boolean {
1787 return id.type.kind == 'ObjectMethod';
1788 }
1789
1790 export function isObjectType(id: Identifier): boolean {
1791 return id.type.kind === 'Object';
1792 }
1793
1794 export function isPrimitiveType(id: Identifier): boolean {
1795 return id.type.kind === 'Primitive';
1796 }
1797
1798 export function isPlainObjectType(id: Identifier): boolean {
1799 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInObject';
1800 }
1801
1802 export function isArrayType(id: Identifier): boolean {
1803 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInArray';
1804 }
1805
1806 export function isMapType(id: Identifier): boolean {
1807 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInMap';
1808 }
1809
1810 export function isSetType(id: Identifier): boolean {
1811 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInSet';
1812 }
1813
1814 export function isPropsType(id: Identifier): boolean {
1815 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInProps';
1816 }
1817
1818 export function isRefValueType(id: Identifier): boolean {
1819 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInRefValue';
1820 }
1821
1822 export function isUseRefType(id: Identifier): boolean {
1823 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseRefId';
1824 }
1825
1826 export function isUseStateType(id: Identifier): boolean {
1827 return id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseState';
1828 }
1829
1830 export function isJsxType(type: Type): boolean {
1831 return type.kind === 'Object' && type.shapeId === 'BuiltInJsx';
1832 }
1833
1834 export function isRefOrRefValue(id: Identifier): boolean {
1835 return isUseRefType(id) || isRefValueType(id);
1836 }
1837
1838 /*
1839 * Returns true if the type is a Ref or a custom user type that acts like a ref when it
1840 * shouldn't. For now the only other case of this is Reanimated's shared values.
1841 */
1842 export function isRefOrRefLikeMutableType(type: Type): boolean {
1843 return (
1844 type.kind === 'Object' &&
1845 (type.shapeId === 'BuiltInUseRefId' ||
1846 type.shapeId == 'ReanimatedSharedValueId')
1847 );
1848 }
1849
1850 export function isSetStateType(id: Identifier): boolean {
1851 return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetState';
1852 }
1853
1854 export function isUseActionStateType(id: Identifier): boolean {
1855 return (
1856 id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseActionState'
1857 );
1858 }
1859
1860 export function isStartTransitionType(id: Identifier): boolean {
1861 return (
1862 id.type.kind === 'Function' && id.type.shapeId === 'BuiltInStartTransition'
1863 );
1864 }
1865
1866 export function isUseOptimisticType(id: Identifier): boolean {
1867 return (
1868 id.type.kind === 'Object' && id.type.shapeId === 'BuiltInUseOptimistic'
1869 );
1870 }
1871
1872 export function isSetOptimisticType(id: Identifier): boolean {
1873 return (
1874 id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetOptimistic'
1875 );
1876 }
1877
1878 export function isSetActionStateType(id: Identifier): boolean {
1879 return (
1880 id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetActionState'
1881 );
1882 }
1883
1884 export function isUseReducerType(id: Identifier): boolean {
1885 return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseReducer';
1886 }
1887
1888 export function isDispatcherType(id: Identifier): boolean {
1889 return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInDispatch';
1890 }
1891
1892 export function isEffectEventFunctionType(id: Identifier): boolean {
1893 return (
1894 id.type.kind === 'Function' &&
1895 id.type.shapeId === 'BuiltInEffectEventFunction'
1896 );
1897 }
1898
1899 export function isStableType(id: Identifier): boolean {
1900 return (
1901 isSetStateType(id) ||
1902 isSetActionStateType(id) ||
1903 isDispatcherType(id) ||
1904 isUseRefType(id) ||
1905 isStartTransitionType(id) ||
1906 isSetOptimisticType(id)
1907 );
1908 }
1909
1910 export function isStableTypeContainer(id: Identifier): boolean {
1911 const type_ = id.type;
1912 if (type_.kind !== 'Object') {
1913 return false;
1914 }
1915 return (
1916 isUseStateType(id) || // setState
1917 isUseActionStateType(id) || // setActionState
1918 isUseReducerType(id) || // dispatcher
1919 isUseOptimisticType(id) || // setOptimistic
1920 type_.shapeId === 'BuiltInUseTransition' // startTransition
1921 );
1922 }
1923
1924 export function evaluatesToStableTypeOrContainer(
1925 env: Environment,
1926 {value}: Instruction,
1927 ): boolean {
1928 if (value.kind === 'CallExpression' || value.kind === 'MethodCall') {
1929 const callee =
1930 value.kind === 'CallExpression' ? value.callee : value.property;
1931
1932 const calleeHookKind = getHookKind(env, callee.identifier);
1933 switch (calleeHookKind) {
1934 case 'useState':
1935 case 'useReducer':
1936 case 'useActionState':
1937 case 'useRef':
1938 case 'useTransition':
1939 case 'useOptimistic':
1940 return true;
1941 }
1942 }
1943 return false;
1944 }
1945
1946 export function isUseEffectHookType(id: Identifier): boolean {
1947 return (
1948 id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectHook'
1949 );
1950 }
1951 export function isUseLayoutEffectHookType(id: Identifier): boolean {
1952 return (
1953 id.type.kind === 'Function' &&
1954 id.type.shapeId === 'BuiltInUseLayoutEffectHook'
1955 );
1956 }
1957 export function isUseInsertionEffectHookType(id: Identifier): boolean {
1958 return (
1959 id.type.kind === 'Function' &&
1960 id.type.shapeId === 'BuiltInUseInsertionEffectHook'
1961 );
1962 }
1963 export function isUseEffectEventType(id: Identifier): boolean {
1964 return (
1965 id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseEffectEvent'
1966 );
1967 }
1968
1969 export function isUseContextHookType(id: Identifier): boolean {
1970 return (
1971 id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseContextHook'
1972 );
1973 }
1974
1975 export function getHookKind(env: Environment, id: Identifier): HookKind | null {
1976 return getHookKindForType(env, id.type);
1977 }
1978
1979 export function isUseOperator(id: Identifier): boolean {
1980 return (
1981 id.type.kind === 'Function' && id.type.shapeId === 'BuiltInUseOperator'
1982 );
1983 }
1984
1985 export function getHookKindForType(
1986 env: Environment,
1987 type: Type,
1988 ): HookKind | null {
1989 if (type.kind === 'Function') {
1990 const signature = env.getFunctionSignature(type);
1991 return signature?.hookKind ?? null;
1992 }
1993 return null;
1994 }
1995
1996 export * from './Types';