@samitouri / QOS-React-2 / commits / c403a7c548

[compiler] Upstream experimental flow integration (#34121)

all credit on the Flood/ code goes to @mvitousek and @jbrown215, i'm just the one upstreaming it

Joseph Savona committed Aug 6, 2025 at 15:58 UTC c403a7c54805e1198bfdad7fc97f33a792359a77
8 files changed +2232 -3
compiler/packages/babel-plugin-react-compiler/src/Flood/FlowTypes.ts new
+752
@@ -0,0 +1,752 @@
1 +/**
2 + * TypeScript definitions for Flow type JSON representations
3 + * Based on the output of /data/sandcastle/boxes/fbsource/fbcode/flow/src/typing/convertTypes.ml
4 + */
5 +
6 +// Base type for all Flow types with a kind field
7 +export interface BaseFlowType {
8 + kind: string;
9 +}
10 +
11 +// Type for representing polarity
12 +export type Polarity = 'positive' | 'negative' | 'neutral';
13 +
14 +// Type for representing a name that might be null
15 +export type OptionalName = string | null;
16 +
17 +// Open type
18 +export interface OpenType extends BaseFlowType {
19 + kind: 'Open';
20 +}
21 +
22 +// Def type
23 +export interface DefType extends BaseFlowType {
24 + kind: 'Def';
25 + def: DefT;
26 +}
27 +
28 +// Eval type
29 +export interface EvalType extends BaseFlowType {
30 + kind: 'Eval';
31 + type: FlowType;
32 + destructor: Destructor;
33 +}
34 +
35 +// Generic type
36 +export interface GenericType extends BaseFlowType {
37 + kind: 'Generic';
38 + name: string;
39 + bound: FlowType;
40 + no_infer: boolean;
41 +}
42 +
43 +// ThisInstance type
44 +export interface ThisInstanceType extends BaseFlowType {
45 + kind: 'ThisInstance';
46 + instance: InstanceT;
47 + is_this: boolean;
48 + name: string;
49 +}
50 +
51 +// ThisTypeApp type
52 +export interface ThisTypeAppType extends BaseFlowType {
53 + kind: 'ThisTypeApp';
54 + t1: FlowType;
55 + t2: FlowType;
56 + t_list?: Array<FlowType>;
57 +}
58 +
59 +// TypeApp type
60 +export interface TypeAppType extends BaseFlowType {
61 + kind: 'TypeApp';
62 + type: FlowType;
63 + targs: Array<FlowType>;
64 + from_value: boolean;
65 + use_desc: boolean;
66 +}
67 +
68 +// FunProto type
69 +export interface FunProtoType extends BaseFlowType {
70 + kind: 'FunProto';
71 +}
72 +
73 +// ObjProto type
74 +export interface ObjProtoType extends BaseFlowType {
75 + kind: 'ObjProto';
76 +}
77 +
78 +// NullProto type
79 +export interface NullProtoType extends BaseFlowType {
80 + kind: 'NullProto';
81 +}
82 +
83 +// FunProtoBind type
84 +export interface FunProtoBindType extends BaseFlowType {
85 + kind: 'FunProtoBind';
86 +}
87 +
88 +// Intersection type
89 +export interface IntersectionType extends BaseFlowType {
90 + kind: 'Intersection';
91 + members: Array<FlowType>;
92 +}
93 +
94 +// Union type
95 +export interface UnionType extends BaseFlowType {
96 + kind: 'Union';
97 + members: Array<FlowType>;
98 +}
99 +
100 +// Maybe type
101 +export interface MaybeType extends BaseFlowType {
102 + kind: 'Maybe';
103 + type: FlowType;
104 +}
105 +
106 +// Optional type
107 +export interface OptionalType extends BaseFlowType {
108 + kind: 'Optional';
109 + type: FlowType;
110 + use_desc: boolean;
111 +}
112 +
113 +// Keys type
114 +export interface KeysType extends BaseFlowType {
115 + kind: 'Keys';
116 + type: FlowType;
117 +}
118 +
119 +// Annot type
120 +export interface AnnotType extends BaseFlowType {
121 + kind: 'Annot';
122 + type: FlowType;
123 + use_desc: boolean;
124 +}
125 +
126 +// Opaque type
127 +export interface OpaqueType extends BaseFlowType {
128 + kind: 'Opaque';
129 + opaquetype: {
130 + opaque_id: string;
131 + underlying_t: FlowType | null;
132 + super_t: FlowType | null;
133 + opaque_type_args: Array<{
134 + name: string;
135 + type: FlowType;
136 + polarity: Polarity;
137 + }>;
138 + opaque_name: string;
139 + };
140 +}
141 +
142 +// Namespace type
143 +export interface NamespaceType extends BaseFlowType {
144 + kind: 'Namespace';
145 + namespace_symbol: {
146 + symbol: string;
147 + };
148 + values_type: FlowType;
149 + types_tmap: PropertyMap;
150 +}
151 +
152 +// Any type
153 +export interface AnyType extends BaseFlowType {
154 + kind: 'Any';
155 +}
156 +
157 +// StrUtil type
158 +export interface StrUtilType extends BaseFlowType {
159 + kind: 'StrUtil';
160 + op: 'StrPrefix' | 'StrSuffix';
161 + prefix?: string;
162 + suffix?: string;
163 + remainder?: FlowType;
164 +}
165 +
166 +// TypeParam definition
167 +export interface TypeParam {
168 + name: string;
169 + bound: FlowType;
170 + polarity: Polarity;
171 + default: FlowType | null;
172 +}
173 +
174 +// EnumInfo types
175 +export type EnumInfo = ConcreteEnum | AbstractEnum;
176 +
177 +export interface ConcreteEnum {
178 + kind: 'ConcreteEnum';
179 + enum_name: string;
180 + enum_id: string;
181 + members: Array<string>;
182 + representation_t: FlowType;
183 + has_unknown_members: boolean;
184 +}
185 +
186 +export interface AbstractEnum {
187 + kind: 'AbstractEnum';
188 + representation_t: FlowType;
189 +}
190 +
191 +// CanonicalRendersForm types
192 +export type CanonicalRendersForm =
193 + | InstrinsicRenders
194 + | NominalRenders
195 + | StructuralRenders
196 + | DefaultRenders;
197 +
198 +export interface InstrinsicRenders {
199 + kind: 'InstrinsicRenders';
200 + name: string;
201 +}
202 +
203 +export interface NominalRenders {
204 + kind: 'NominalRenders';
205 + renders_id: string;
206 + renders_name: string;
207 + renders_super: FlowType;
208 +}
209 +
210 +export interface StructuralRenders {
211 + kind: 'StructuralRenders';
212 + renders_variant: 'RendersNormal' | 'RendersMaybe' | 'RendersStar';
213 + renders_structural_type: FlowType;
214 +}
215 +
216 +export interface DefaultRenders {
217 + kind: 'DefaultRenders';
218 +}
219 +
220 +// InstanceT definition
221 +export interface InstanceT {
222 + inst: InstType;
223 + static: FlowType;
224 + super: FlowType;
225 + implements: Array<FlowType>;
226 +}
227 +
228 +// InstType definition
229 +export interface InstType {
230 + class_name: string | null;
231 + class_id: string;
232 + type_args: Array<{
233 + name: string;
234 + type: FlowType;
235 + polarity: Polarity;
236 + }>;
237 + own_props: PropertyMap;
238 + proto_props: PropertyMap;
239 + call_t: null | {
240 + id: number;
241 + call: FlowType;
242 + };
243 +}
244 +
245 +// DefT types
246 +export type DefT =
247 + | NumGeneralType
248 + | StrGeneralType
249 + | BoolGeneralType
250 + | BigIntGeneralType
251 + | EmptyType
252 + | MixedType
253 + | NullType
254 + | VoidType
255 + | SymbolType
256 + | FunType
257 + | ObjType
258 + | ArrType
259 + | ClassType
260 + | InstanceType
261 + | SingletonStrType
262 + | NumericStrKeyType
263 + | SingletonNumType
264 + | SingletonBoolType
265 + | SingletonBigIntType
266 + | TypeType
267 + | PolyType
268 + | ReactAbstractComponentType
269 + | RendersType
270 + | EnumValueType
271 + | EnumObjectType;
272 +
273 +export interface NumGeneralType extends BaseFlowType {
274 + kind: 'NumGeneral';
275 +}
276 +
277 +export interface StrGeneralType extends BaseFlowType {
278 + kind: 'StrGeneral';
279 +}
280 +
281 +export interface BoolGeneralType extends BaseFlowType {
282 + kind: 'BoolGeneral';
283 +}
284 +
285 +export interface BigIntGeneralType extends BaseFlowType {
286 + kind: 'BigIntGeneral';
287 +}
288 +
289 +export interface EmptyType extends BaseFlowType {
290 + kind: 'Empty';
291 +}
292 +
293 +export interface MixedType extends BaseFlowType {
294 + kind: 'Mixed';
295 +}
296 +
297 +export interface NullType extends BaseFlowType {
298 + kind: 'Null';
299 +}
300 +
301 +export interface VoidType extends BaseFlowType {
302 + kind: 'Void';
303 +}
304 +
305 +export interface SymbolType extends BaseFlowType {
306 + kind: 'Symbol';
307 +}
308 +
309 +export interface FunType extends BaseFlowType {
310 + kind: 'Fun';
311 + static: FlowType;
312 + funtype: FunTypeObj;
313 +}
314 +
315 +export interface ObjType extends BaseFlowType {
316 + kind: 'Obj';
317 + objtype: ObjTypeObj;
318 +}
319 +
320 +export interface ArrType extends BaseFlowType {
321 + kind: 'Arr';
322 + arrtype: ArrTypeObj;
323 +}
324 +
325 +export interface ClassType extends BaseFlowType {
326 + kind: 'Class';
327 + type: FlowType;
328 +}
329 +
330 +export interface InstanceType extends BaseFlowType {
331 + kind: 'Instance';
332 + instance: InstanceT;
333 +}
334 +
335 +export interface SingletonStrType extends BaseFlowType {
336 + kind: 'SingletonStr';
337 + from_annot: boolean;
338 + value: string;
339 +}
340 +
341 +export interface NumericStrKeyType extends BaseFlowType {
342 + kind: 'NumericStrKey';
343 + number: string;
344 + string: string;
345 +}
346 +
347 +export interface SingletonNumType extends BaseFlowType {
348 + kind: 'SingletonNum';
349 + from_annot: boolean;
350 + number: string;
351 + string: string;
352 +}
353 +
354 +export interface SingletonBoolType extends BaseFlowType {
355 + kind: 'SingletonBool';
356 + from_annot: boolean;
357 + value: boolean;
358 +}
359 +
360 +export interface SingletonBigIntType extends BaseFlowType {
361 + kind: 'SingletonBigInt';
362 + from_annot: boolean;
363 + value: string;
364 +}
365 +
366 +export interface TypeType extends BaseFlowType {
367 + kind: 'Type';
368 + type_kind: TypeTKind;
369 + type: FlowType;
370 +}
371 +
372 +export type TypeTKind =
373 + | 'TypeAliasKind'
374 + | 'TypeParamKind'
375 + | 'OpaqueKind'
376 + | 'ImportTypeofKind'
377 + | 'ImportClassKind'
378 + | 'ImportEnumKind'
379 + | 'InstanceKind'
380 + | 'RenderTypeKind';
381 +
382 +export interface PolyType extends BaseFlowType {
383 + kind: 'Poly';
384 + tparams: Array<TypeParam>;
385 + t_out: FlowType;
386 + id: string;
387 +}
388 +
389 +export interface ReactAbstractComponentType extends BaseFlowType {
390 + kind: 'ReactAbstractComponent';
391 + config: FlowType;
392 + renders: FlowType;
393 + instance: ComponentInstance;
394 + component_kind: ComponentKind;
395 +}
396 +
397 +export type ComponentInstance =
398 + | {kind: 'RefSetterProp'; type: FlowType}
399 + | {kind: 'Omitted'};
400 +
401 +export type ComponentKind =
402 + | {kind: 'Structural'}
403 + | {kind: 'Nominal'; id: string; name: string; types: Array<FlowType> | null};
404 +
405 +export interface RendersType extends BaseFlowType {
406 + kind: 'Renders';
407 + form: CanonicalRendersForm;
408 +}
409 +
410 +export interface EnumValueType extends BaseFlowType {
411 + kind: 'EnumValue';
412 + enum_info: EnumInfo;
413 +}
414 +
415 +export interface EnumObjectType extends BaseFlowType {
416 + kind: 'EnumObject';
417 + enum_value_t: FlowType;
418 + enum_info: EnumInfo;
419 +}
420 +
421 +// ObjKind types
422 +export type ObjKind =
423 + | {kind: 'Exact'}
424 + | {kind: 'Inexact'}
425 + | {kind: 'Indexed'; dicttype: DictType};
426 +
427 +// DictType definition
428 +export interface DictType {
429 + dict_name: string | null;
430 + key: FlowType;
431 + value: FlowType;
432 + dict_polarity: Polarity;
433 +}
434 +
435 +// ArrType types
436 +export type ArrTypeObj = ArrayAT | TupleAT | ROArrayAT;
437 +
438 +export interface ArrayAT {
439 + kind: 'ArrayAT';
440 + elem_t: FlowType;
441 +}
442 +
443 +export interface TupleAT {
444 + kind: 'TupleAT';
445 + elem_t: FlowType;
446 + elements: Array<TupleElement>;
447 + min_arity: number;
448 + max_arity: number;
449 + inexact: boolean;
450 +}
451 +
452 +export interface ROArrayAT {
453 + kind: 'ROArrayAT';
454 + elem_t: FlowType;
455 +}
456 +
457 +// TupleElement definition
458 +export interface TupleElement {
459 + name: string | null;
460 + t: FlowType;
461 + polarity: Polarity;
462 + optional: boolean;
463 +}
464 +
465 +// Flags definition
466 +export interface Flags {
467 + obj_kind: ObjKind;
468 +}
469 +
470 +// Property types
471 +export type Property =
472 + | FieldProperty
473 + | GetProperty
474 + | SetProperty
475 + | GetSetProperty
476 + | MethodProperty;
477 +
478 +export interface FieldProperty {
479 + kind: 'Field';
480 + type: FlowType;
481 + polarity: Polarity;
482 +}
483 +
484 +export interface GetProperty {
485 + kind: 'Get';
486 + type: FlowType;
487 +}
488 +
489 +export interface SetProperty {
490 + kind: 'Set';
491 + type: FlowType;
492 +}
493 +
494 +export interface GetSetProperty {
495 + kind: 'GetSet';
496 + get_type: FlowType;
497 + set_type: FlowType;
498 +}
499 +
500 +export interface MethodProperty {
501 + kind: 'Method';
502 + type: FlowType;
503 +}
504 +
505 +// PropertyMap definition
506 +export interface PropertyMap {
507 + [key: string]: Property; // For other properties in the map
508 +}
509 +
510 +// ObjType definition
511 +export interface ObjTypeObj {
512 + flags: Flags;
513 + props: PropertyMap;
514 + proto_t: FlowType;
515 + call_t: number | null;
516 +}
517 +
518 +// FunType definition
519 +export interface FunTypeObj {
520 + this_t: {
521 + type: FlowType;
522 + status: ThisStatus;
523 + };
524 + params: Array<{
525 + name: string | null;
526 + type: FlowType;
527 + }>;
528 + rest_param: null | {
529 + name: string | null;
530 + type: FlowType;
531 + };
532 + return_t: FlowType;
533 + type_guard: null | {
534 + inferred: boolean;
535 + param_name: string;
536 + type_guard: FlowType;
537 + one_sided: boolean;
538 + };
539 + effect: Effect;
540 +}
541 +
542 +// ThisStatus types
543 +export type ThisStatus =
544 + | {kind: 'This_Method'; unbound: boolean}
545 + | {kind: 'This_Function'};
546 +
547 +// Effect types
548 +export type Effect =
549 + | {kind: 'HookDecl'; id: string}
550 + | {kind: 'HookAnnot'}
551 + | {kind: 'ArbitraryEffect'}
552 + | {kind: 'AnyEffect'};
553 +
554 +// Destructor types
555 +export type Destructor =
556 + | NonMaybeTypeDestructor
557 + | PropertyTypeDestructor
558 + | ElementTypeDestructor
559 + | OptionalIndexedAccessNonMaybeTypeDestructor
560 + | OptionalIndexedAccessResultTypeDestructor
561 + | ExactTypeDestructor
562 + | ReadOnlyTypeDestructor
563 + | PartialTypeDestructor
564 + | RequiredTypeDestructor
565 + | SpreadTypeDestructor
566 + | SpreadTupleTypeDestructor
567 + | RestTypeDestructor
568 + | ValuesTypeDestructor
569 + | ConditionalTypeDestructor
570 + | TypeMapDestructor
571 + | ReactElementPropsTypeDestructor
572 + | ReactElementConfigTypeDestructor
573 + | ReactCheckComponentConfigDestructor
574 + | ReactDRODestructor
575 + | MakeHooklikeDestructor
576 + | MappedTypeDestructor
577 + | EnumTypeDestructor;
578 +
579 +export interface NonMaybeTypeDestructor {
580 + kind: 'NonMaybeType';
581 +}
582 +
583 +export interface PropertyTypeDestructor {
584 + kind: 'PropertyType';
585 + name: string;
586 +}
587 +
588 +export interface ElementTypeDestructor {
589 + kind: 'ElementType';
590 + index_type: FlowType;
591 +}
592 +
593 +export interface OptionalIndexedAccessNonMaybeTypeDestructor {
594 + kind: 'OptionalIndexedAccessNonMaybeType';
595 + index: OptionalIndexedAccessIndex;
596 +}
597 +
598 +export type OptionalIndexedAccessIndex =
599 + | {kind: 'StrLitIndex'; name: string}
600 + | {kind: 'TypeIndex'; type: FlowType};
601 +
602 +export interface OptionalIndexedAccessResultTypeDestructor {
603 + kind: 'OptionalIndexedAccessResultType';
604 +}
605 +
606 +export interface ExactTypeDestructor {
607 + kind: 'ExactType';
608 +}
609 +
610 +export interface ReadOnlyTypeDestructor {
611 + kind: 'ReadOnlyType';
612 +}
613 +
614 +export interface PartialTypeDestructor {
615 + kind: 'PartialType';
616 +}
617 +
618 +export interface RequiredTypeDestructor {
619 + kind: 'RequiredType';
620 +}
621 +
622 +export interface SpreadTypeDestructor {
623 + kind: 'SpreadType';
624 + target: SpreadTarget;
625 + operands: Array<SpreadOperand>;
626 + operand_slice: Slice | null;
627 +}
628 +
629 +export type SpreadTarget =
630 + | {kind: 'Value'; make_seal: 'Sealed' | 'Frozen' | 'As_Const'}
631 + | {kind: 'Annot'; make_exact: boolean};
632 +
633 +export type SpreadOperand = {kind: 'Type'; type: FlowType} | Slice;
634 +
635 +export interface Slice {
636 + kind: 'Slice';
637 + prop_map: PropertyMap;
638 + generics: Array<string>;
639 + dict: DictType | null;
640 + reachable_targs: Array<{
641 + type: FlowType;
642 + polarity: Polarity;
643 + }>;
644 +}
645 +
646 +export interface SpreadTupleTypeDestructor {
647 + kind: 'SpreadTupleType';
648 + inexact: boolean;
649 + resolved_rev: string;
650 + unresolved: string;
651 +}
652 +
653 +export interface RestTypeDestructor {
654 + kind: 'RestType';
655 + merge_mode: RestMergeMode;
656 + type: FlowType;
657 +}
658 +
659 +export type RestMergeMode =
660 + | {kind: 'SpreadReversal'}
661 + | {kind: 'ReactConfigMerge'; polarity: Polarity}
662 + | {kind: 'Omit'};
663 +
664 +export interface ValuesTypeDestructor {
665 + kind: 'ValuesType';
666 +}
667 +
668 +export interface ConditionalTypeDestructor {
669 + kind: 'ConditionalType';
670 + distributive_tparam_name: string | null;
671 + infer_tparams: string;
672 + extends_t: FlowType;
673 + true_t: FlowType;
674 + false_t: FlowType;
675 +}
676 +
677 +export interface TypeMapDestructor {
678 + kind: 'ObjectKeyMirror';
679 +}
680 +
681 +export interface ReactElementPropsTypeDestructor {
682 + kind: 'ReactElementPropsType';
683 +}
684 +
685 +export interface ReactElementConfigTypeDestructor {
686 + kind: 'ReactElementConfigType';
687 +}
688 +
689 +export interface ReactCheckComponentConfigDestructor {
690 + kind: 'ReactCheckComponentConfig';
691 + props: {
692 + [key: string]: Property;
693 + };
694 +}
695 +
696 +export interface ReactDRODestructor {
697 + kind: 'ReactDRO';
698 + dro_type:
699 + | 'HookReturn'
700 + | 'HookArg'
701 + | 'Props'
702 + | 'ImmutableAnnot'
703 + | 'DebugAnnot';
704 +}
705 +
706 +export interface MakeHooklikeDestructor {
707 + kind: 'MakeHooklike';
708 +}
709 +
710 +export interface MappedTypeDestructor {
711 + kind: 'MappedType';
712 + homomorphic: Homomorphic;
713 + distributive_tparam_name: string | null;
714 + property_type: FlowType;
715 + mapped_type_flags: {
716 + variance: Polarity;
717 + optional: 'MakeOptional' | 'RemoveOptional' | 'KeepOptionality';
718 + };
719 +}
720 +
721 +export type Homomorphic =
722 + | {kind: 'Homomorphic'}
723 + | {kind: 'Unspecialized'}
724 + | {kind: 'SemiHomomorphic'; type: FlowType};
725 +
726 +export interface EnumTypeDestructor {
727 + kind: 'EnumType';
728 +}
729 +
730 +// Union of all possible Flow types
731 +export type FlowType =
732 + | OpenType
733 + | DefType
734 + | EvalType
735 + | GenericType
736 + | ThisInstanceType
737 + | ThisTypeAppType
738 + | TypeAppType
739 + | FunProtoType
740 + | ObjProtoType
741 + | NullProtoType
742 + | FunProtoBindType
743 + | IntersectionType
744 + | UnionType
745 + | MaybeType
746 + | OptionalType
747 + | KeysType
748 + | AnnotType
749 + | OpaqueType
750 + | NamespaceType
751 + | AnyType
752 + | StrUtilType;
compiler/packages/babel-plugin-react-compiler/src/Flood/TypeErrors.ts new
+131
@@ -0,0 +1,131 @@
1 +import {CompilerError, SourceLocation} from '..';
2 +import {
3 + ConcreteType,
4 + printConcrete,
5 + printType,
6 + StructuralValue,
7 + Type,
8 + VariableId,
9 +} from './Types';
10 +
11 +export function unsupportedLanguageFeature(
12 + desc: string,
13 + loc: SourceLocation,
14 +): never {
15 + CompilerError.throwInvalidJS({
16 + reason: `Typedchecker does not currently support language feature: ${desc}`,
17 + loc,
18 + });
19 +}
20 +
21 +export type UnificationError =
22 + | {
23 + kind: 'TypeUnification';
24 + left: ConcreteType<Type>;
25 + right: ConcreteType<Type>;
26 + }
27 + | {
28 + kind: 'StructuralUnification';
29 + left: StructuralValue;
30 + right: ConcreteType<Type>;
31 + };
32 +
33 +function printUnificationError(err: UnificationError): string {
34 + if (err.kind === 'TypeUnification') {
35 + return `${printConcrete(err.left, printType)} is incompatible with ${printConcrete(err.right, printType)}`;
36 + } else {
37 + return `structural ${err.left.kind} is incompatible with ${printConcrete(err.right, printType)}`;
38 + }
39 +}
40 +
41 +export function raiseUnificationErrors(
42 + errs: null | Array<UnificationError>,
43 + loc: SourceLocation,
44 +): void {
45 + if (errs != null) {
46 + if (errs.length === 0) {
47 + CompilerError.invariant(false, {
48 + reason: 'Should not have array of zero errors',
49 + loc,
50 + });
51 + } else if (errs.length === 1) {
52 + CompilerError.throwInvalidJS({
53 + reason: `Unable to unify types because ${printUnificationError(errs[0])}`,
54 + loc,
55 + });
56 + } else {
57 + const messages = errs
58 + .map(err => `\t* ${printUnificationError(err)}`)
59 + .join('\n');
60 + CompilerError.throwInvalidJS({
61 + reason: `Unable to unify types because:\n${messages}`,
62 + loc,
63 + });
64 + }
65 + }
66 +}
67 +
68 +export function unresolvableTypeVariable(
69 + id: VariableId,
70 + loc: SourceLocation,
71 +): never {
72 + CompilerError.throwInvalidJS({
73 + reason: `Unable to resolve free variable ${id} to a concrete type`,
74 + loc,
75 + });
76 +}
77 +
78 +export function cannotAddVoid(explicit: boolean, loc: SourceLocation): never {
79 + if (explicit) {
80 + CompilerError.throwInvalidJS({
81 + reason: `Undefined is not a valid operand of \`+\``,
82 + loc,
83 + });
84 + } else {
85 + CompilerError.throwInvalidJS({
86 + reason: `Value may be undefined, which is not a valid operand of \`+\``,
87 + loc,
88 + });
89 + }
90 +}
91 +
92 +export function unsupportedTypeAnnotation(
93 + desc: string,
94 + loc: SourceLocation,
95 +): never {
96 + CompilerError.throwInvalidJS({
97 + reason: `Typedchecker does not currently support type annotation: ${desc}`,
98 + loc,
99 + });
100 +}
101 +
102 +export function checkTypeArgumentArity(
103 + desc: string,
104 + expected: number,
105 + actual: number,
106 + loc: SourceLocation,
107 +): void {
108 + if (expected !== actual) {
109 + CompilerError.throwInvalidJS({
110 + reason: `Expected ${desc} to have ${expected} type parameters, got ${actual}`,
111 + loc,
112 + });
113 + }
114 +}
115 +
116 +export function notAFunction(desc: string, loc: SourceLocation): void {
117 + CompilerError.throwInvalidJS({
118 + reason: `Cannot call ${desc} because it is not a function`,
119 + loc,
120 + });
121 +}
122 +
123 +export function notAPolymorphicFunction(
124 + desc: string,
125 + loc: SourceLocation,
126 +): void {
127 + CompilerError.throwInvalidJS({
128 + reason: `Cannot call ${desc} with type arguments because it is not a polymorphic function`,
129 + loc,
130 + });
131 +}
compiler/packages/babel-plugin-react-compiler/src/Flood/TypeUtils.ts new
+312
@@ -0,0 +1,312 @@
1 +import {GeneratedSource} from '../HIR';
2 +import {assertExhaustive} from '../Utils/utils';
3 +import {unsupportedLanguageFeature} from './TypeErrors';
4 +import {
5 + ConcreteType,
6 + ResolvedType,
7 + TypeParameter,
8 + TypeParameterId,
9 + DEBUG,
10 + printConcrete,
11 + printType,
12 +} from './Types';
13 +
14 +export function substitute(
15 + type: ConcreteType<ResolvedType>,
16 + typeParameters: Array<TypeParameter<ResolvedType>>,
17 + typeArguments: Array<ResolvedType>,
18 +): ResolvedType {
19 + const substMap = new Map<TypeParameterId, ResolvedType>();
20 + for (let i = 0; i < typeParameters.length; i++) {
21 + // TODO: Length checks to make sure type params match up with args
22 + const typeParameter = typeParameters[i];
23 + const typeArgument = typeArguments[i];
24 + substMap.set(typeParameter.id, typeArgument);
25 + }
26 + const substitutionFunction = (t: ResolvedType): ResolvedType => {
27 + // TODO: We really want a stateful mapper or visitor here so that we can model nested polymorphic types
28 + if (t.type.kind === 'Generic' && substMap.has(t.type.id)) {
29 + const substitutedType = substMap.get(t.type.id)!;
30 + return substitutedType;
31 + }
32 +
33 + return {
34 + kind: 'Concrete',
35 + type: mapType(substitutionFunction, t.type),
36 + platform: t.platform,
37 + };
38 + };
39 +
40 + const substituted = mapType(substitutionFunction, type);
41 +
42 + if (DEBUG) {
43 + let substs = '';
44 + for (let i = 0; i < typeParameters.length; i++) {
45 + const typeParameter = typeParameters[i];
46 + const typeArgument = typeArguments[i];
47 + substs += `[${typeParameter.name}${typeParameter.id} := ${printType(typeArgument)}]`;
48 + }
49 + console.log(
50 + `${printConcrete(type, printType)}${substs} = ${printConcrete(substituted, printType)}`,
51 + );
52 + }
53 +
54 + return {kind: 'Concrete', type: substituted, platform: /* TODO */ 'shared'};
55 +}
56 +
57 +export function mapType<T, U>(
58 + f: (t: T) => U,
59 + type: ConcreteType<T>,
60 +): ConcreteType<U> {
61 + switch (type.kind) {
62 + case 'Mixed':
63 + case 'Number':
64 + case 'String':
65 + case 'Boolean':
66 + case 'Void':
67 + return type;
68 +
69 + case 'Nullable':
70 + return {
71 + kind: 'Nullable',
72 + type: f(type.type),
73 + };
74 +
75 + case 'Array':
76 + return {
77 + kind: 'Array',
78 + element: f(type.element),
79 + };
80 +
81 + case 'Set':
82 + return {
83 + kind: 'Set',
84 + element: f(type.element),
85 + };
86 +
87 + case 'Map':
88 + return {
89 + kind: 'Map',
90 + key: f(type.key),
91 + value: f(type.value),
92 + };
93 +
94 + case 'Function':
95 + return {
96 + kind: 'Function',
97 + typeParameters:
98 + type.typeParameters?.map(param => ({
99 + id: param.id,
100 + name: param.name,
101 + bound: f(param.bound),
102 + })) ?? null,
103 + params: type.params.map(f),
104 + returnType: f(type.returnType),
105 + };
106 +
107 + case 'Component': {
108 + return {
109 + kind: 'Component',
110 + children: type.children != null ? f(type.children) : null,
111 + props: new Map([...type.props.entries()].map(([k, v]) => [k, f(v)])),
112 + };
113 + }
114 +
115 + case 'Generic':
116 + return {
117 + kind: 'Generic',
118 + id: type.id,
119 + bound: f(type.bound),
120 + };
121 +
122 + case 'Object':
123 + return type;
124 +
125 + case 'Tuple':
126 + return {
127 + kind: 'Tuple',
128 + id: type.id,
129 + members: type.members.map(f),
130 + };
131 +
132 + case 'Structural':
133 + return type;
134 +
135 + case 'Enum':
136 + case 'Union':
137 + case 'Instance':
138 + unsupportedLanguageFeature(type.kind, GeneratedSource);
139 +
140 + default:
141 + assertExhaustive(type, 'Unknown type kind');
142 + }
143 +}
144 +
145 +export function diff<R, T>(
146 + a: ConcreteType<T>,
147 + b: ConcreteType<T>,
148 + onChild: (a: T, b: T) => R,
149 + onChildMismatch: (child: R, cur: R) => R,
150 + onMismatch: (a: ConcreteType<T>, b: ConcreteType<T>, cur: R) => R,
151 + init: R,
152 +): R {
153 + let errors = init;
154 +
155 + // Check if kinds match
156 + if (a.kind !== b.kind) {
157 + errors = onMismatch(a, b, errors);
158 + return errors;
159 + }
160 +
161 + // Based on kind, check other properties
162 + switch (a.kind) {
163 + case 'Mixed':
164 + case 'Number':
165 + case 'String':
166 + case 'Boolean':
167 + case 'Void':
168 + // Simple types, no further checks needed
169 + break;
170 +
171 + case 'Nullable':
172 + // Check the nested type
173 + errors = onChildMismatch(onChild(a.type, (b as typeof a).type), errors);
174 + break;
175 +
176 + case 'Array':
177 + case 'Set':
178 + // Check the element type
179 + errors = onChildMismatch(
180 + onChild(a.element, (b as typeof a).element),
181 + errors,
182 + );
183 + break;
184 +
185 + case 'Map':
186 + // Check both key and value types
187 + errors = onChildMismatch(onChild(a.key, (b as typeof a).key), errors);
188 + errors = onChildMismatch(onChild(a.value, (b as typeof a).value), errors);
189 + break;
190 +
191 + case 'Function': {
192 + const bFunc = b as typeof a;
193 +
194 + // Check type parameters
195 + if ((a.typeParameters == null) !== (bFunc.typeParameters == null)) {
196 + errors = onMismatch(a, b, errors);
197 + }
198 +
199 + if (a.typeParameters != null && bFunc.typeParameters != null) {
200 + if (a.typeParameters.length !== bFunc.typeParameters.length) {
201 + errors = onMismatch(a, b, errors);
202 + }
203 +
204 + // Type parameters are just numbers, so we can compare them directly
205 + for (let i = 0; i < a.typeParameters.length; i++) {
206 + if (a.typeParameters[i] !== bFunc.typeParameters[i]) {
207 + errors = onMismatch(a, b, errors);
208 + }
209 + }
210 + }
211 +
212 + // Check parameters
213 + if (a.params.length !== bFunc.params.length) {
214 + errors = onMismatch(a, b, errors);
215 + }
216 +
217 + for (let i = 0; i < a.params.length; i++) {
218 + errors = onChildMismatch(onChild(a.params[i], bFunc.params[i]), errors);
219 + }
220 +
221 + // Check return type
222 + errors = onChildMismatch(onChild(a.returnType, bFunc.returnType), errors);
223 + break;
224 + }
225 +
226 + case 'Component': {
227 + const bComp = b as typeof a;
228 +
229 + // Check children
230 + if (a.children !== bComp.children) {
231 + errors = onMismatch(a, b, errors);
232 + }
233 +
234 + // Check props
235 + if (a.props.size !== bComp.props.size) {
236 + errors = onMismatch(a, b, errors);
237 + }
238 +
239 + for (const [k, v] of a.props) {
240 + const bProp = bComp.props.get(k);
241 + if (bProp == null) {
242 + errors = onMismatch(a, b, errors);
243 + } else {
244 + errors = onChildMismatch(onChild(v, bProp), errors);
245 + }
246 + }
247 +
248 + break;
249 + }
250 +
251 + case 'Generic': {
252 + // Check that the type parameter IDs match
253 + if (a.id !== (b as typeof a).id) {
254 + errors = onMismatch(a, b, errors);
255 + }
256 + break;
257 + }
258 + case 'Structural': {
259 + const bStruct = b as typeof a;
260 +
261 + // Check that the structural IDs match
262 + if (a.id !== bStruct.id) {
263 + errors = onMismatch(a, b, errors);
264 + }
265 + break;
266 + }
267 + case 'Object': {
268 + const bNom = b as typeof a;
269 +
270 + // Check that the nominal IDs match
271 + if (a.id !== bNom.id) {
272 + errors = onMismatch(a, b, errors);
273 + }
274 + break;
275 + }
276 +
277 + case 'Tuple': {
278 + const bTuple = b as typeof a;
279 +
280 + // Check that the tuple IDs match
281 + if (a.id !== bTuple.id) {
282 + errors = onMismatch(a, b, errors);
283 + }
284 + for (let i = 0; i < a.members.length; i++) {
285 + errors = onChildMismatch(
286 + onChild(a.members[i], bTuple.members[i]),
287 + errors,
288 + );
289 + }
290 +
291 + break;
292 + }
293 +
294 + case 'Enum':
295 + case 'Instance':
296 + case 'Union': {
297 + unsupportedLanguageFeature(a.kind, GeneratedSource);
298 + }
299 +
300 + default:
301 + assertExhaustive(a, 'Unknown type kind');
302 + }
303 +
304 + return errors;
305 +}
306 +
307 +export function filterOptional(t: ResolvedType): ResolvedType {
308 + if (t.kind === 'Concrete' && t.type.kind === 'Nullable') {
309 + return t.type.type;
310 + }
311 + return t;
312 +}
compiler/packages/babel-plugin-react-compiler/src/Flood/Types.ts new
+1001
@@ -0,0 +1,1001 @@
1 +import {CompilerError, SourceLocation} from '..';
2 +import {
3 + Environment,
4 + GeneratedSource,
5 + HIRFunction,
6 + Identifier,
7 + IdentifierId,
8 +} from '../HIR';
9 +import * as t from '@babel/types';
10 +import * as TypeErrors from './TypeErrors';
11 +import {assertExhaustive} from '../Utils/utils';
12 +
13 +export const DEBUG = false;
14 +
15 +export type Type =
16 + | {kind: 'Concrete'; type: ConcreteType<Type>; platform: Platform}
17 + | {kind: 'Variable'; id: VariableId};
18 +
19 +export type ResolvedType = {
20 + kind: 'Concrete';
21 + type: ConcreteType<ResolvedType>;
22 + platform: Platform;
23 +};
24 +
25 +export type ComponentType<T> = {
26 + kind: 'Component';
27 + props: Map<string, T>;
28 + children: null | T;
29 +};
30 +export type ConcreteType<T> =
31 + | {kind: 'Enum'}
32 + | {kind: 'Mixed'}
33 + | {kind: 'Number'}
34 + | {kind: 'String'}
35 + | {kind: 'Boolean'}
36 + | {kind: 'Void'}
37 + | {kind: 'Nullable'; type: T}
38 + | {kind: 'Array'; element: T}
39 + | {kind: 'Set'; element: T}
40 + | {kind: 'Map'; key: T; value: T}
41 + | {
42 + kind: 'Function';
43 + typeParameters: null | Array<TypeParameter<T>>;
44 + params: Array<T>;
45 + returnType: T;
46 + }
47 + | ComponentType<T>
48 + | {kind: 'Generic'; id: TypeParameterId; bound: T}
49 + | {
50 + kind: 'Object';
51 + id: NominalId;
52 + members: Map<string, ResolvedType>;
53 + }
54 + | {
55 + kind: 'Tuple';
56 + id: NominalId;
57 + members: Array<T>;
58 + }
59 + | {kind: 'Structural'; id: LinearId}
60 + | {kind: 'Union'; members: Array<T>}
61 + | {kind: 'Instance'; name: string; members: Map<string, ResolvedType>};
62 +
63 +export type StructuralValue =
64 + | {
65 + kind: 'Function';
66 + fn: HIRFunction;
67 + }
68 + | {
69 + kind: 'Object';
70 + members: Map<string, ResolvedType>;
71 + }
72 + | {
73 + kind: 'Array';
74 + elementType: ResolvedType;
75 + };
76 +
77 +export type Structural = {
78 + type: StructuralValue;
79 + consumed: boolean;
80 +};
81 +// TODO: create a kind: "Alias"
82 +
83 +// type T<X> = { foo: X}
84 +
85 +/**
86 + *
87 + * function apply<A, B>(x: A, f: A => B): B { }
88 + *
89 + * apply(42, x => String(x));
90 + *
91 + * f({foo: 42})
92 + *
93 + * f([HOLE]) -----> {foo: 42} with context NominalType
94 + *
95 + * $0 = Object {foo: 42}
96 + * $1 = LoadLocal "f"
97 + * $2 = Call $1, [$0]
98 + *
99 + * ContextMap:
100 + * $2 => ??
101 + * $1 => [HOLE]($0)
102 + * $0 => $1([HOLE])
103 + */
104 +
105 +/*
106 + *const g = {foo: 42} as NominalType // ok
107 + *
108 + *
109 + *function f(x: NominalType) { ... }
110 + *f()
111 + *
112 + *const y: NominalType = {foo: 42}
113 + *
114 + *
115 + */
116 +
117 +/**
118 + * // Mike: maybe this could be the ideal?
119 + *type X = nominal('registryNameX', {
120 + *value: number,
121 + *});
122 + *
123 + * // For now:
124 + *opaque type X = { // creates a new nominal type
125 + *value: number,
126 + *};
127 + *
128 + *type Y = X; // creates a type alias
129 + *
130 + *type Z = number; // creates a type alias
131 + *
132 + *
133 + * // (todo: disallowed)
134 + *type X' = {
135 + *value: number,
136 + *}
137 + */
138 +
139 +export type TypeParameter<T> = {
140 + name: string;
141 + id: TypeParameterId;
142 + bound: T;
143 +};
144 +
145 +const opaqueLinearId = Symbol();
146 +export type LinearId = number & {
147 + [opaqueLinearId]: 'LinearId';
148 +};
149 +
150 +export function makeLinearId(id: number): LinearId {
151 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
152 + reason: 'Expected LinearId id to be a non-negative integer',
153 + description: null,
154 + loc: null,
155 + suggestions: null,
156 + });
157 + return id as LinearId;
158 +}
159 +
160 +const opaqueTypeParameterId = Symbol();
161 +export type TypeParameterId = number & {
162 + [opaqueTypeParameterId]: 'TypeParameterId';
163 +};
164 +
165 +export function makeTypeParameterId(id: number): TypeParameterId {
166 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
167 + reason: 'Expected TypeParameterId to be a non-negative integer',
168 + description: null,
169 + loc: null,
170 + suggestions: null,
171 + });
172 + return id as TypeParameterId;
173 +}
174 +
175 +const opaqueNominalId = Symbol();
176 +export type NominalId = number & {
177 + [opaqueNominalId]: 'NominalId';
178 +};
179 +
180 +export function makeNominalId(id: number): NominalId {
181 + return id as NominalId;
182 +}
183 +
184 +const opaqueVariableId = Symbol();
185 +export type VariableId = number & {
186 + [opaqueVariableId]: 'VariableId';
187 +};
188 +
189 +export function makeVariableId(id: number): VariableId {
190 + CompilerError.invariant(id >= 0 && Number.isInteger(id), {
191 + reason: 'Expected VariableId id to be a non-negative integer',
192 + description: null,
193 + loc: null,
194 + suggestions: null,
195 + });
196 + return id as VariableId;
197 +}
198 +
199 +import {inspect} from 'util';
200 +import {FlowType} from './FlowTypes';
201 +export function printConcrete<T>(
202 + type: ConcreteType<T>,
203 + printType: (_: T) => string,
204 +): string {
205 + switch (type.kind) {
206 + case 'Mixed':
207 + return 'mixed';
208 + case 'Number':
209 + return 'number';
210 + case 'String':
211 + return 'string';
212 + case 'Boolean':
213 + return 'boolean';
214 + case 'Void':
215 + return 'void';
216 + case 'Nullable':
217 + return `${printType(type.type)} | void`;
218 + case 'Array':
219 + return `Array<${printType(type.element)}>`;
220 + case 'Set':
221 + return `Set<${printType(type.element)}>`;
222 + case 'Map':
223 + return `Map<${printType(type.key)}, ${printType(type.value)}>`;
224 + case 'Function': {
225 + const typeParams = type.typeParameters
226 + ? `<${type.typeParameters.map(tp => `T${tp}`).join(', ')}>`
227 + : '';
228 + const params = type.params.map(printType).join(', ');
229 + const returnType = printType(type.returnType);
230 + return `${typeParams}(${params}) => ${returnType}`;
231 + }
232 + case 'Component': {
233 + const params = [...type.props.entries()]
234 + .map(([k, v]) => `${k}: ${printType(v)}`)
235 + .join(', ');
236 + const comma = type.children != null && type.props.size > 0 ? ', ' : '';
237 + const children =
238 + type.children != null ? `children: ${printType(type.children)}` : '';
239 + return `component (${params}${comma}${children})`;
240 + }
241 + case 'Generic':
242 + return `T${type.id}`;
243 + case 'Object': {
244 + const name = `Object ${inspect([...type.members.keys()])}`;
245 + return `${name}`;
246 + }
247 + case 'Tuple': {
248 + const name = `Tuple ${type.members}`;
249 + return `${name}`;
250 + }
251 + case 'Structural': {
252 + const name = `Structural ${type.id}`;
253 + return `${name}`;
254 + }
255 + case 'Enum': {
256 + return 'TODO enum printing';
257 + }
258 + case 'Union': {
259 + return type.members.map(printType).join(' | ');
260 + }
261 + case 'Instance': {
262 + return type.name;
263 + }
264 + default:
265 + assertExhaustive(type, `Unknown type: ${JSON.stringify(type)}`);
266 + }
267 +}
268 +
269 +export function printType(type: Type): string {
270 + switch (type.kind) {
271 + case 'Concrete':
272 + return printConcrete(type.type, printType);
273 + case 'Variable':
274 + return `$${type.id}`;
275 + default:
276 + assertExhaustive(type, `Unknown type: ${JSON.stringify(type)}`);
277 + }
278 +}
279 +
280 +export function printResolved(type: ResolvedType): string {
281 + return printConcrete(type.type, printResolved);
282 +}
283 +
284 +type Platform = 'client' | 'server' | 'shared';
285 +
286 +const DUMMY_NOMINAL = makeNominalId(0);
287 +
288 +function convertFlowType(flowType: FlowType, loc: string): ResolvedType {
289 + let nextGenericId = 0;
290 + function convertFlowTypeImpl(
291 + flowType: FlowType,
292 + loc: string,
293 + genericEnv: Map<string, TypeParameterId>,
294 + platform: Platform,
295 + poly: null | Array<TypeParameter<ResolvedType>> = null,
296 + ): ResolvedType {
297 + switch (flowType.kind) {
298 + case 'TypeApp': {
299 + if (
300 + flowType.type.kind === 'Def' &&
301 + flowType.type.def.kind === 'Poly' &&
302 + flowType.type.def.t_out.kind === 'Def' &&
303 + flowType.type.def.t_out.def.kind === 'Type' &&
304 + flowType.type.def.t_out.def.type.kind === 'Opaque' &&
305 + flowType.type.def.t_out.def.type.opaquetype.opaque_name ===
306 + 'Client' &&
307 + flowType.targs.length === 1
308 + ) {
309 + return convertFlowTypeImpl(
310 + flowType.targs[0],
311 + loc,
312 + genericEnv,
313 + 'client',
314 + );
315 + } else if (
316 + flowType.type.kind === 'Def' &&
317 + flowType.type.def.kind === 'Poly' &&
318 + flowType.type.def.t_out.kind === 'Def' &&
319 + flowType.type.def.t_out.def.kind === 'Type' &&
320 + flowType.type.def.t_out.def.type.kind === 'Opaque' &&
321 + flowType.type.def.t_out.def.type.opaquetype.opaque_name ===
322 + 'Server' &&
323 + flowType.targs.length === 1
324 + ) {
325 + return convertFlowTypeImpl(
326 + flowType.targs[0],
327 + loc,
328 + genericEnv,
329 + 'server',
330 + );
331 + }
332 + return Resolved.todo(platform);
333 + }
334 + case 'Open':
335 + return Resolved.mixed(platform);
336 + case 'Any':
337 + return Resolved.todo(platform);
338 + case 'Annot':
339 + return convertFlowTypeImpl(
340 + flowType.type,
341 + loc,
342 + genericEnv,
343 + platform,
344 + poly,
345 + );
346 + case 'Opaque': {
347 + if (
348 + flowType.opaquetype.opaque_name === 'Client' &&
349 + flowType.opaquetype.super_t != null
350 + ) {
351 + return convertFlowTypeImpl(
352 + flowType.opaquetype.super_t,
353 + loc,
354 + genericEnv,
355 + 'client',
356 + );
357 + }
358 + if (
359 + flowType.opaquetype.opaque_name === 'Server' &&
360 + flowType.opaquetype.super_t != null
361 + ) {
362 + return convertFlowTypeImpl(
363 + flowType.opaquetype.super_t,
364 + loc,
365 + genericEnv,
366 + 'server',
367 + );
368 + }
369 + const t =
370 + flowType.opaquetype.underlying_t ?? flowType.opaquetype.super_t;
371 + if (t != null) {
372 + return convertFlowTypeImpl(t, loc, genericEnv, platform, poly);
373 + } else {
374 + return Resolved.todo(platform);
375 + }
376 + }
377 + case 'Def': {
378 + switch (flowType.def.kind) {
379 + case 'EnumValue':
380 + return convertFlowTypeImpl(
381 + flowType.def.enum_info.representation_t,
382 + loc,
383 + genericEnv,
384 + platform,
385 + poly,
386 + );
387 + case 'EnumObject':
388 + return Resolved.enum(platform);
389 + case 'Empty':
390 + return Resolved.todo(platform);
391 + case 'Instance': {
392 + const members = new Map<string, ResolvedType>();
393 + for (const key in flowType.def.instance.inst.own_props) {
394 + const prop = flowType.def.instance.inst.own_props[key];
395 + if (prop.kind === 'Field') {
396 + members.set(
397 + key,
398 + convertFlowTypeImpl(prop.type, loc, genericEnv, platform),
399 + );
400 + } else {
401 + CompilerError.invariant(false, {
402 + reason: `Unsupported property kind ${prop.kind}`,
403 + loc: GeneratedSource,
404 + });
405 + }
406 + }
407 + return Resolved.class(
408 + flowType.def.instance.inst.class_name ?? '[anonymous class]',
409 + members,
410 + platform,
411 + );
412 + }
413 + case 'Type':
414 + return convertFlowTypeImpl(
415 + flowType.def.type,
416 + loc,
417 + genericEnv,
418 + platform,
419 + poly,
420 + );
421 + case 'NumGeneral':
422 + case 'SingletonNum':
423 + return Resolved.number(platform);
424 + case 'StrGeneral':
425 + case 'SingletonStr':
426 + return Resolved.string(platform);
427 + case 'BoolGeneral':
428 + case 'SingletonBool':
429 + return Resolved.boolean(platform);
430 + case 'Void':
431 + return Resolved.void(platform);
432 + case 'Null':
433 + return Resolved.void(platform);
434 + case 'Mixed':
435 + return Resolved.mixed(platform);
436 + case 'Arr': {
437 + if (
438 + flowType.def.arrtype.kind === 'ArrayAT' ||
439 + flowType.def.arrtype.kind === 'ROArrayAT'
440 + ) {
441 + return Resolved.array(
442 + convertFlowTypeImpl(
443 + flowType.def.arrtype.elem_t,
444 + loc,
445 + genericEnv,
446 + platform,
447 + ),
448 + platform,
449 + );
450 + } else {
451 + return Resolved.tuple(
452 + DUMMY_NOMINAL,
453 + flowType.def.arrtype.elements.map(t =>
454 + convertFlowTypeImpl(t.t, loc, genericEnv, platform),
455 + ),
456 + platform,
457 + );
458 + }
459 + }
460 + case 'Obj': {
461 + const members = new Map<string, ResolvedType>();
462 + for (const key in flowType.def.objtype.props) {
463 + const prop = flowType.def.objtype.props[key];
464 + if (prop.kind === 'Field') {
465 + members.set(
466 + key,
467 + convertFlowTypeImpl(prop.type, loc, genericEnv, platform),
468 + );
469 + } else {
470 + CompilerError.invariant(false, {
471 + reason: `Unsupported property kind ${prop.kind}`,
472 + loc: GeneratedSource,
473 + });
474 + }
475 + }
476 + return Resolved.object(DUMMY_NOMINAL, members, platform);
477 + }
478 + case 'Class': {
479 + if (flowType.def.type.kind === 'ThisInstance') {
480 + const members = new Map<string, ResolvedType>();
481 + for (const key in flowType.def.type.instance.inst.own_props) {
482 + const prop = flowType.def.type.instance.inst.own_props[key];
483 + if (prop.kind === 'Field') {
484 + members.set(
485 + key,
486 + convertFlowTypeImpl(prop.type, loc, genericEnv, platform),
487 + );
488 + } else {
489 + CompilerError.invariant(false, {
490 + reason: `Unsupported property kind ${prop.kind}`,
491 + loc: GeneratedSource,
492 + });
493 + }
494 + }
495 + return Resolved.class(
496 + flowType.def.type.instance.inst.class_name ??
497 + '[anonymous class]',
498 + members,
499 + platform,
500 + );
501 + }
502 + CompilerError.invariant(false, {
503 + reason: `Unsupported class instance type ${flowType.def.type.kind}`,
504 + loc: GeneratedSource,
505 + });
506 + }
507 + case 'Fun':
508 + return Resolved.function(
509 + poly,
510 + flowType.def.funtype.params.map(p =>
511 + convertFlowTypeImpl(p.type, loc, genericEnv, platform),
512 + ),
513 + convertFlowTypeImpl(
514 + flowType.def.funtype.return_t,
515 + loc,
516 + genericEnv,
517 + platform,
518 + ),
519 + platform,
520 + );
521 + case 'Poly': {
522 + let newEnv = genericEnv;
523 + const poly = flowType.def.tparams.map(p => {
524 + const id = makeTypeParameterId(nextGenericId++);
525 + const bound = convertFlowTypeImpl(p.bound, loc, newEnv, platform);
526 + newEnv = new Map(newEnv);
527 + newEnv.set(p.name, id);
528 + return {
529 + name: p.name,
530 + id,
531 + bound,
532 + };
533 + });
534 + return convertFlowTypeImpl(
535 + flowType.def.t_out,
536 + loc,
537 + newEnv,
538 + platform,
539 + poly,
540 + );
541 + }
542 + case 'ReactAbstractComponent': {
543 + const props = new Map<string, ResolvedType>();
544 + let children: ResolvedType | null = null;
545 + const propsType = convertFlowTypeImpl(
546 + flowType.def.config,
547 + loc,
548 + genericEnv,
549 + platform,
550 + );
551 +
552 + if (propsType.type.kind === 'Object') {
553 + propsType.type.members.forEach((v, k) => {
554 + if (k === 'children') {
555 + children = v;
556 + } else {
557 + props.set(k, v);
558 + }
559 + });
560 + } else {
561 + CompilerError.invariant(false, {
562 + reason: `Unsupported component props type ${propsType.type.kind}`,
563 + loc: GeneratedSource,
564 + });
565 + }
566 +
567 + return Resolved.component(props, children, platform);
568 + }
569 + case 'Renders':
570 + return Resolved.todo(platform);
571 + default:
572 + TypeErrors.unsupportedTypeAnnotation('Renders', GeneratedSource);
573 + }
574 + }
575 + case 'Generic': {
576 + const id = genericEnv.get(flowType.name);
577 + if (id == null) {
578 + TypeErrors.unsupportedTypeAnnotation(flowType.name, GeneratedSource);
579 + }
580 + return Resolved.generic(
581 + id,
582 + platform,
583 + convertFlowTypeImpl(flowType.bound, loc, genericEnv, platform),
584 + );
585 + }
586 + case 'Union': {
587 + const members = flowType.members.map(t =>
588 + convertFlowTypeImpl(t, loc, genericEnv, platform),
589 + );
590 + if (members.length === 1) {
591 + return members[0];
592 + }
593 + if (
594 + members[0].type.kind === 'Number' ||
595 + members[0].type.kind === 'String' ||
596 + members[0].type.kind === 'Boolean'
597 + ) {
598 + const dupes = members.filter(
599 + t => t.type.kind === members[0].type.kind,
600 + );
601 + if (dupes.length === members.length) {
602 + return members[0];
603 + }
604 + }
605 + if (
606 + members[0].type.kind === 'Array' &&
607 + (members[0].type.element.type.kind === 'Number' ||
608 + members[0].type.element.type.kind === 'String' ||
609 + members[0].type.element.type.kind === 'Boolean')
610 + ) {
611 + const first = members[0].type.element;
612 + const dupes = members.filter(
613 + t =>
614 + t.type.kind === 'Array' &&
615 + t.type.element.type.kind === first.type.kind,
616 + );
617 + if (dupes.length === members.length) {
618 + return members[0];
619 + }
620 + }
621 + return Resolved.union(members, platform);
622 + }
623 + case 'Eval': {
624 + if (
625 + flowType.destructor.kind === 'ReactDRO' ||
626 + flowType.destructor.kind === 'ReactCheckComponentConfig'
627 + ) {
628 + return convertFlowTypeImpl(
629 + flowType.type,
630 + loc,
631 + genericEnv,
632 + platform,
633 + poly,
634 + );
635 + }
636 + TypeErrors.unsupportedTypeAnnotation(
637 + `EvalT(${flowType.destructor.kind})`,
638 + GeneratedSource,
639 + );
640 + }
641 + case 'Optional': {
642 + return Resolved.union(
643 + [
644 + convertFlowTypeImpl(flowType.type, loc, genericEnv, platform),
645 + Resolved.void(platform),
646 + ],
647 + platform,
648 + );
649 + }
650 + default:
651 + TypeErrors.unsupportedTypeAnnotation(flowType.kind, GeneratedSource);
652 + }
653 + }
654 + return convertFlowTypeImpl(flowType, loc, new Map(), 'shared');
655 +}
656 +
657 +export interface ITypeEnv {
658 + popGeneric(name: string): void;
659 + getGeneric(name: string): null | TypeParameter<ResolvedType>;
660 + pushGeneric(
661 + name: string,
662 + binding: {name: string; id: TypeParameterId; bound: ResolvedType},
663 + ): void;
664 + getType(id: Identifier): ResolvedType;
665 + getTypeOrNull(id: Identifier): ResolvedType | null;
666 + setType(id: Identifier, type: ResolvedType): void;
667 + nextNominalId(): NominalId;
668 + nextTypeParameterId(): TypeParameterId;
669 + moduleEnv: Map<string, ResolvedType>;
670 + addBinding(bindingIdentifier: t.Identifier, type: ResolvedType): void;
671 + resolveBinding(bindingIdentifier: t.Identifier): ResolvedType | null;
672 +}
673 +
674 +function serializeLoc(location: t.SourceLocation): string {
675 + return `${location.start.line}:${location.start.column}-${location.end.line}:${location.end.column}`;
676 +}
677 +
678 +function buildTypeEnvironment(
679 + flowOutput: Array<{loc: t.SourceLocation; type: string}>,
680 +): Map<string, string> {
681 + const result: Map<string, string> = new Map();
682 + for (const item of flowOutput) {
683 + const loc: t.SourceLocation = {
684 + start: {
685 + line: item.loc.start.line,
686 + column: item.loc.start.column - 1,
687 + index: item.loc.start.index,
688 + },
689 + end: item.loc.end,
690 + filename: item.loc.filename,
691 + identifierName: item.loc.identifierName,
692 + };
693 +
694 + result.set(serializeLoc(loc), item.type);
695 + }
696 + return result;
697 +}
698 +
699 +let lastFlowSource: string | null = null;
700 +let lastFlowResult: any = null;
701 +
702 +export class FlowTypeEnv implements ITypeEnv {
703 + moduleEnv: Map<string, ResolvedType> = new Map();
704 + #nextNominalId: number = 0;
705 + #nextTypeParameterId: number = 0;
706 +
707 + #types: Map<IdentifierId, ResolvedType> = new Map();
708 + #bindings: Map<t.Identifier, ResolvedType> = new Map();
709 + #generics: Array<[string, TypeParameter<ResolvedType>]> = [];
710 + #flowTypes: Map<string, ResolvedType> = new Map();
711 +
712 + init(env: Environment, source: string): void {
713 + // TODO: use flow-js only for web environments (e.g. playground)
714 + CompilerError.invariant(env.config.flowTypeProvider != null, {
715 + reason: 'Expected flowDumpTypes to be defined in environment config',
716 + loc: GeneratedSource,
717 + });
718 + let stdout: any;
719 + if (source === lastFlowSource) {
720 + stdout = lastFlowResult;
721 + } else {
722 + lastFlowSource = source;
723 + lastFlowResult = env.config.flowTypeProvider(source);
724 + stdout = lastFlowResult;
725 + }
726 + const flowTypes = buildTypeEnvironment(stdout);
727 + const resolvedFlowTypes = new Map<string, ResolvedType>();
728 + for (const [loc, type] of flowTypes) {
729 + if (typeof loc === 'symbol') continue;
730 + resolvedFlowTypes.set(loc, convertFlowType(JSON.parse(type), loc));
731 + }
732 + // =console.log(resolvedFlowTypes);
733 + this.#flowTypes = resolvedFlowTypes;
734 + }
735 +
736 + setType(identifier: Identifier, type: ResolvedType): void {
737 + if (
738 + typeof identifier.loc !== 'symbol' &&
739 + this.#flowTypes.has(serializeLoc(identifier.loc))
740 + ) {
741 + return;
742 + }
743 + this.#types.set(identifier.id, type);
744 + }
745 +
746 + getType(identifier: Identifier): ResolvedType {
747 + const result = this.getTypeOrNull(identifier);
748 + if (result == null) {
749 + throw new Error(
750 + `Type not found for ${identifier.id}, ${typeof identifier.loc === 'symbol' ? 'generated loc' : serializeLoc(identifier.loc)}`,
751 + );
752 + }
753 + return result;
754 + }
755 +
756 + getTypeOrNull(identifier: Identifier): ResolvedType | null {
757 + const result = this.#types.get(identifier.id) ?? null;
758 + if (result == null && typeof identifier.loc !== 'symbol') {
759 + const flowType = this.#flowTypes.get(serializeLoc(identifier.loc));
760 + return flowType ?? null;
761 + }
762 + return result;
763 + }
764 +
765 + getTypeByLoc(loc: SourceLocation): ResolvedType | null {
766 + if (typeof loc === 'symbol') {
767 + return null;
768 + }
769 + const flowType = this.#flowTypes.get(serializeLoc(loc));
770 + return flowType ?? null;
771 + }
772 +
773 + nextNominalId(): NominalId {
774 + return makeNominalId(this.#nextNominalId++);
775 + }
776 +
777 + nextTypeParameterId(): TypeParameterId {
778 + return makeTypeParameterId(this.#nextTypeParameterId++);
779 + }
780 +
781 + addBinding(bindingIdentifier: t.Identifier, type: ResolvedType): void {
782 + this.#bindings.set(bindingIdentifier, type);
783 + }
784 +
785 + resolveBinding(bindingIdentifier: t.Identifier): ResolvedType | null {
786 + return this.#bindings.get(bindingIdentifier) ?? null;
787 + }
788 +
789 + pushGeneric(name: string, generic: TypeParameter<ResolvedType>): void {
790 + this.#generics.unshift([name, generic]);
791 + }
792 +
793 + popGeneric(name: string): void {
794 + for (let i = 0; i < this.#generics.length; i++) {
795 + if (this.#generics[i][0] === name) {
796 + this.#generics.splice(i, 1);
797 + return;
798 + }
799 + }
800 + }
801 +
802 + /**
803 + * Look up bound polymorphic types
804 + * @param name
805 + * @returns
806 + */
807 + getGeneric(name: string): null | TypeParameter<ResolvedType> {
808 + for (const [eltName, param] of this.#generics) {
809 + if (name === eltName) {
810 + return param;
811 + }
812 + }
813 + return null;
814 + }
815 +}
816 +const Primitives = {
817 + number(platform: Platform): Type & ResolvedType {
818 + return {kind: 'Concrete', type: {kind: 'Number'}, platform};
819 + },
820 + string(platform: Platform): Type & ResolvedType {
821 + return {kind: 'Concrete', type: {kind: 'String'}, platform};
822 + },
823 + boolean(platform: Platform): Type & ResolvedType {
824 + return {kind: 'Concrete', type: {kind: 'Boolean'}, platform};
825 + },
826 + void(platform: Platform): Type & ResolvedType {
827 + return {kind: 'Concrete', type: {kind: 'Void'}, platform};
828 + },
829 + mixed(platform: Platform): Type & ResolvedType {
830 + return {kind: 'Concrete', type: {kind: 'Mixed'}, platform};
831 + },
832 + enum(platform: Platform): Type & ResolvedType {
833 + return {kind: 'Concrete', type: {kind: 'Enum'}, platform};
834 + },
835 + todo(platform: Platform): Type & ResolvedType {
836 + return {kind: 'Concrete', type: {kind: 'Mixed'}, platform};
837 + },
838 +};
839 +
840 +export const Resolved = {
841 + ...Primitives,
842 + nullable(type: ResolvedType, platform: Platform): ResolvedType {
843 + return {kind: 'Concrete', type: {kind: 'Nullable', type}, platform};
844 + },
845 + array(element: ResolvedType, platform: Platform): ResolvedType {
846 + return {kind: 'Concrete', type: {kind: 'Array', element}, platform};
847 + },
848 + set(element: ResolvedType, platform: Platform): ResolvedType {
849 + return {kind: 'Concrete', type: {kind: 'Set', element}, platform};
850 + },
851 + map(
852 + key: ResolvedType,
853 + value: ResolvedType,
854 + platform: Platform,
855 + ): ResolvedType {
856 + return {kind: 'Concrete', type: {kind: 'Map', key, value}, platform};
857 + },
858 + function(
859 + typeParameters: null | Array<TypeParameter<ResolvedType>>,
860 + params: Array<ResolvedType>,
861 + returnType: ResolvedType,
862 + platform: Platform,
863 + ): ResolvedType {
864 + return {
865 + kind: 'Concrete',
866 + type: {kind: 'Function', typeParameters, params, returnType},
867 + platform,
868 + };
869 + },
870 + component(
871 + props: Map<string, ResolvedType>,
872 + children: ResolvedType | null,
873 + platform: Platform,
874 + ): ResolvedType {
875 + return {
876 + kind: 'Concrete',
877 + type: {kind: 'Component', props, children},
878 + platform,
879 + };
880 + },
881 + object(
882 + id: NominalId,
883 + members: Map<string, ResolvedType>,
884 + platform: Platform,
885 + ): ResolvedType {
886 + return {
887 + kind: 'Concrete',
888 + type: {
889 + kind: 'Object',
890 + id,
891 + members,
892 + },
893 + platform,
894 + };
895 + },
896 + class(
897 + name: string,
898 + members: Map<string, ResolvedType>,
899 + platform: Platform,
900 + ): ResolvedType {
901 + return {
902 + kind: 'Concrete',
903 + type: {
904 + kind: 'Instance',
905 + name,
906 + members,
907 + },
908 + platform,
909 + };
910 + },
911 + tuple(
912 + id: NominalId,
913 + members: Array<ResolvedType>,
914 + platform: Platform,
915 + ): ResolvedType {
916 + return {
917 + kind: 'Concrete',
918 + type: {
919 + kind: 'Tuple',
920 + id,
921 + members,
922 + },
923 + platform,
924 + };
925 + },
926 + generic(
927 + id: TypeParameterId,
928 + platform: Platform,
929 + bound = Primitives.mixed(platform),
930 + ): ResolvedType {
931 + return {
932 + kind: 'Concrete',
933 + type: {
934 + kind: 'Generic',
935 + id,
936 + bound,
937 + },
938 + platform,
939 + };
940 + },
941 + union(members: Array<ResolvedType>, platform: Platform): ResolvedType {
942 + return {
943 + kind: 'Concrete',
944 + type: {
945 + kind: 'Union',
946 + members,
947 + },
948 + platform,
949 + };
950 + },
951 +};
952 +
953 +/*
954 + * export const Types = {
955 + * ...Primitives,
956 + * variable(env: TypeEnv): Type {
957 + * return env.nextTypeVariable();
958 + * },
959 + * nullable(type: Type): Type {
960 + * return {kind: 'Concrete', type: {kind: 'Nullable', type}};
961 + * },
962 + * array(element: Type): Type {
963 + * return {kind: 'Concrete', type: {kind: 'Array', element}};
964 + * },
965 + * set(element: Type): Type {
966 + * return {kind: 'Concrete', type: {kind: 'Set', element}};
967 + * },
968 + * map(key: Type, value: Type): Type {
969 + * return {kind: 'Concrete', type: {kind: 'Map', key, value}};
970 + * },
971 + * function(
972 + * typeParameters: null | Array<TypeParameter<Type>>,
973 + * params: Array<Type>,
974 + * returnType: Type,
975 + * ): Type {
976 + * return {
977 + * kind: 'Concrete',
978 + * type: {kind: 'Function', typeParameters, params, returnType},
979 + * };
980 + * },
981 + * component(
982 + * props: Map<string, ResolvedType>,
983 + * children: Type | null,
984 + * ): Type {
985 + * return {
986 + * kind: 'Concrete',
987 + * type: {kind: 'Component', props, children},
988 + * };
989 + * },
990 + * object(id: NominalId, members: Map<string, ResolvedType>): Type {
991 + * return {
992 + * kind: 'Concrete',
993 + * type: {
994 + * kind: 'Object',
995 + * id,
996 + * members,
997 + * },
998 + * };
999 + * },
1000 + * };
1001 + */
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+29
@@ -49,6 +49,7 @@ import {
49 } from './ObjectShape';
50 import {Scope as BabelScope, NodePath} from '@babel/traverse';
51 import {TypeSchema} from './TypeSchema';
52 +import {FlowTypeEnv} from '../Flood/Types';
53
54 export const ReactElementSymbolSchema = z.object({
55 elementSymbol: z.union([
@@ -243,6 +244,12 @@ export const EnvironmentConfigSchema = z.object({
244 */
245 enableUseTypeAnnotations: z.boolean().default(false),
246
247 + /**
248 + * Allows specifying a function that can populate HIR with type information from
249 + * Flow
250 + */
251 + flowTypeProvider: z.nullable(z.function().args(z.string())).default(null),
252 +
253 /**
254 * Enable a new model for mutability and aliasing inference
255 */
@@ -697,6 +704,8 @@ export class Environment {
704 #hoistedIdentifiers: Set<t.Identifier>;
705 parentFunction: NodePath<t.Function>;
706
707 + #flowTypeEnvironment: FlowTypeEnv | null;
708 +
709 constructor(
710 scope: BabelScope,
711 fnType: ReactFunctionType,
@@ -765,6 +774,26 @@ export class Environment {
774 this.parentFunction = parentFunction;
775 this.#contextIdentifiers = contextIdentifiers;
776 this.#hoistedIdentifiers = new Set();
777 +
778 + if (config.flowTypeProvider != null) {
779 + this.#flowTypeEnvironment = new FlowTypeEnv();
780 + CompilerError.invariant(code != null, {
781 + reason:
782 + 'Expected Environment to be initialized with source code when a Flow type provider is specified',
783 + loc: null,
784 + });
785 + this.#flowTypeEnvironment.init(this, code);
786 + } else {
787 + this.#flowTypeEnvironment = null;
788 + }
789 + }
790 +
791 + get typeContext(): FlowTypeEnv {
792 + CompilerError.invariant(this.#flowTypeEnvironment != null, {
793 + reason: 'Flow type environment not initialized',
794 + loc: null,
795 + });
796 + return this.#flowTypeEnvironment;
797 }
798
799 get isInferredMemoEnabled(): boolean {
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/MergeReactiveScopesThatInvalidateTogether.ts
+1 -1
@@ -504,7 +504,7 @@ function canMergeScopes(
504 return false;
505 }
506
507 -function isAlwaysInvalidatingType(type: Type): boolean {
507 +export function isAlwaysInvalidatingType(type: Type): boolean {
508 switch (type.kind) {
509 case 'Object': {
510 switch (type.shapeId) {
compiler/packages/babel-plugin-react-compiler/src/Utils/DisjointSet.ts
+4
@@ -78,6 +78,10 @@ export default class DisjointSet<T> {
78 return root;
79 }
80
81 + has(item: T): boolean {
82 + return this.#entries.has(item);
83 + }
84 +
85 /*
86 * Forces the set into canonical form, ie with all items pointing directly to
87 * their root, and returns a Map representing the mapping of items to their roots.
compiler/packages/babel-plugin-react-compiler/src/Utils/utils.ts
+2 -2
@@ -33,12 +33,12 @@ export function assertExhaustive(_: never, errorMsg: string): never {
33 // Modifies @param array in place, retaining only the items where the predicate returns true.
34 export function retainWhere<T>(
35 array: Array<T>,
36 - predicate: (item: T) => boolean,
36 + predicate: (item: T, index: number) => boolean,
37 ): void {
38 let writeIndex = 0;
39 for (let readIndex = 0; readIndex < array.length; readIndex++) {
40 const item = array[readIndex];
41 - if (predicate(item) === true) {
41 + if (predicate(item, readIndex) === true) {
42 array[writeIndex++] = item;
43 }
44 }