forked from facebook/flow
-
Notifications
You must be signed in to change notification settings - Fork 0
/
flow_js.ml
6090 lines (5107 loc) · 209 KB
/
flow_js.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the "flow" directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*)
(* This module describes the subtyping algorithm that forms the core of
typechecking. The algorithm (in its basic form) is described in Francois
Pottier's thesis. The main data structures maintained by the algorithm are:
(1) for every type variable, which type variables form its lower and upper
bounds (i.e., flow in and out of the type variable); and (2) for every type
variable, which concrete types form its lower and upper bounds. Every new
subtyping constraint added to the system is deconstructed into its subparts,
until basic flows between type variables and other type variables or concrete
types remain; these flows are then viewed as links in a chain, bringing
together further concrete types and type variables to participate in
subtyping. This process continues till a fixpoint is reached---which itself
is guaranteed to exist, and is usually reached in very few steps. *)
open Utils
open Utils_js
open Modes_js
open Reason_js
open Constraint_js
open Type
(* The following functions are used as constructors for function types and
object types, which unfortunately have many fields, not all of which are
meaningful in all contexts. This part of the design should be revisited:
perhaps the data types can be refactored to make them more specialized. *)
(* Methods may use a dummy statics object type to carry properties. We do not
want to encourage this pattern, but we also don't want to block uses of this
pattern. Thus, we compromise by not tracking the property types. *)
let dummy_static =
AnyObjT (reason_of_string "object type for statics")
let dummy_prototype =
MixedT (reason_of_string "empty prototype object")
let dummy_this =
MixedT (reason_of_string "global object")
let mk_methodtype this tins ?params_names tout = {
this_t = this;
params_tlist = tins;
params_names;
return_t = tout;
closure_t = 0;
changeset = Changeset.empty
}
let mk_methodtype2 this tins ?params_names tout j = {
this_t = this;
params_tlist = tins;
params_names;
return_t = tout;
closure_t = j;
changeset = Changeset.empty
}
let mk_functiontype tins ?params_names tout = {
this_t = dummy_this;
params_tlist = tins;
params_names;
return_t = tout;
closure_t = 0;
changeset = Changeset.empty
}
let mk_functiontype2 tins ?params_names tout j = {
this_t = dummy_this;
params_tlist = tins;
params_names;
return_t = tout;
closure_t = j;
changeset = Changeset.empty
}
(* An object type has two flags, sealed and exact. A sealed object type cannot
be extended. An exact object type accurately describes objects without
"forgeting" any properties: so to extend an object type with optional
properties, the object type must be exact. Thus, as an invariant, "not exact"
logically implies "sealed" (and by contrapositive, "not sealed" implies
"exact"; in other words, exact and sealed cannot both be false).
Types of object literals are exact, but can be sealed or unsealed. Object
type annotations are sealed but not exact. *)
let default_flags = {
sealed = UnsealedInFile None;
exact = true;
frozen = false;
}
let mk_objecttype ?(flags=default_flags) dict map proto = {
flags;
dict_t = dict;
props_tmap = map;
proto_t = proto
}
(**************************************************************)
(* for now, we do speculative matching by setting this global.
it's set to true when trying the arms of intersection or
union types.
when it's true, the error-logging function throws instead
of logging, and the speculative harness catches.
TODO
*)
let throw_on_error = ref false
(* we keep a stack of reasons representing the operations
taking place when flows are performed. the top op reason
is used in messages for errors that take place during its
residence.
*)
module Ops : sig
val push : reason -> unit
val pop : unit -> unit
val peek : unit -> reason option
val get : unit -> reason list
val set : reason list -> unit
end = struct
let ops = ref []
let push r = ops := r :: !ops
let pop () = ops := List.tl !ops
let peek () = match !ops with r :: rs -> Some r | [] -> None
let get () = !ops
let set _ops = ops := _ops
end
let silent_warnings = false
exception FlowError of Errors_js.message list
let add_output cx error_kind ?op ?(trace_reasons=[]) message_list =
if !throw_on_error
then (
raise (FlowError message_list)
) else (
(if Context.is_verbose cx then
prerr_endlinef "\nadd_output cx.file = %S\n%s"
(string_of_filename (Context.file cx))
(message_list
|> List.map (fun message ->
let loc, s = Errors_js.to_pp message in
spf "%s: s = %S" (string_of_loc loc) s
)
|> String.concat "\n"));
let error = Errors_js.({
kind = error_kind;
messages = message_list;
op;
trace = trace_reasons
}) in
(* catch no-loc errors early, before they get into error map *)
Errors_js.(
if Loc.source (loc_of_error error) = None
then assert_false (spf "add_output: no source for error: %s"
(Hh_json.json_to_multiline (json_of_errors [error])))
);
if error_kind = Errors_js.ParseError ||
error_kind = Errors_js.InferError ||
not silent_warnings
then Context.add_error cx error
)
(* tvars *)
let mk_tvar cx reason =
let tvar = mk_id () in
let graph = Context.graph cx in
Context.add_tvar cx tvar (Constraint_js.new_unresolved_root ());
(if Context.is_verbose cx then prerr_endlinef
"TVAR %d (%d): %s" tvar (IMap.cardinal graph)
(string_of_reason reason));
OpenT (reason, tvar)
let mk_tvar_where cx reason f =
let tvar = mk_tvar cx reason in
f tvar;
tvar
(* This function is used in lieu of mk_tvar_where or mk_tvar when the reason
must be marked internal. This has the effect of not forcing annotations where
this type variable appears. See `assume_ground` and `assert_ground`. *)
let mk_tvar_derivable_where cx reason f =
let reason = derivable_reason reason in
mk_tvar_where cx reason f
(* Find the constraints of a type variable in the graph.
Recall that type variables are either roots or goto nodes. (See
constraint_js.ml for details.) If the type variable is a root, the
constraints are stored with the type variable. Otherwise, the type variable
is a goto node, and it points to another type variable: a linked list of such
type variables must be traversed until a root is reached. *)
let rec find_graph cx id =
let _, constraints = find_constraints cx id in
constraints
and find_constraints cx id =
let root_id, root = find_root cx id in
root_id, root.constraints
(* Find the root of a type variable, potentially traversing a chain of type
variables, while short-circuiting all the type variables in the chain to the
root during traversal to speed up future traversals. *)
and find_root cx id =
match IMap.get id (Context.graph cx) with
| Some (Goto next_id) ->
let root_id, root = find_root cx next_id in
if root_id != next_id then replace_node cx id (Goto root_id) else ();
root_id, root
| Some (Root root) ->
id, root
| None ->
let msg = spf "find_root: tvar %d not found in file %s" id
(string_of_filename (Context.file cx))
in
failwith msg
(* Replace the node associated with a type variable in the graph. *)
and replace_node cx id node = Context.set_tvar cx id node
(* Check that id1 is not linked to id2. *)
let not_linked (id1, bounds1) (id2, bounds2) =
(* It suffices to check that id1 is not already in the lower bounds of
id2. Equivalently, we could check that id2 is not already in the upper
bounds of id1. *)
not (IMap.mem id1 bounds2.lowertvars)
(**********)
(* frames *)
(**********)
(* note: this is here instead of Env_js because of circular deps:
Env_js is downstream of Flow_js due general utility funcs such as
Flow_js.mk_tvar and builtins services. If the flow algorithm can
be split away from these, then Env_js can be moved upstream and
this code can be merged into it. *)
(* background:
- each scope has an id. scope ids are unique, mod cloning
for path-dependent analysis.
- an environment is a scope list
- each context holds a map of environment snapshots, keyed
by their topmost scope ids
- every function type contains a frame id, which maps to
the environment in which it was defined; as well as a
changeset containing its reads/writes/refinements on
closed-over variables
Given frame ids for calling function and called function and
the changeset of the called function, here we retrieve the
environment snapshots for the two functions, find the prefix
of scopes they share, and havoc the variables in the called
function's write set which live in those scopes.
*)
let havoc_call_env = Scope.(
let overlapped_call_scopes func_env call_env =
let rec loop = Scope.(function
| func_scope :: func_scopes, call_scope :: call_scopes
when func_scope.id = call_scope.id ->
call_scope :: loop (func_scopes, call_scopes)
| _ -> []
) in
loop (List.rev func_env, List.rev call_env)
in
let havoc_entry cx scope ((_, name, _) as entry_ref) =
(if Context.is_verbose cx then
prerr_endlinef "%d havoc_entry %s %s" (Unix.getpid ())
(Changeset.string_of_entry_ref entry_ref)
(Debug.string_of_scope cx scope)
);
match get_entry name scope with
| Some _ ->
havoc_entry (fun gen -> gen) name scope;
Changeset.add_var_change entry_ref
| None ->
(* global scopes may lack entries, if function closes over
path-refined global vars (artifact of deferred lookup) *)
if is_global scope then ()
else assert_false (spf "missing entry %S in scope %d: { %s }"
name scope.id (String.concat ", "
(SMap.fold (fun n _ acc -> n :: acc) scope.entries [])))
in
let havoc_refi cx scope ((_, key, _) as refi_ref) =
(if Context.is_verbose cx then
prerr_endlinef "%d havoc_refi %s" (Unix.getpid ())
(Changeset.string_of_refi_ref refi_ref));
match get_refi key scope with
| Some _ ->
havoc_refi key scope;
Changeset.add_refi_change refi_ref
| None ->
(* global scopes may lack entries, if function closes over
path-refined global vars (artifact of deferred lookup) *)
if is_global scope then ()
else assert_false (spf "missing refi %S in scope %d: { %s }"
(Key.string_of_key key) scope.id
(String.concat ", " (KeyMap.fold (
fun k _ acc -> (Key.string_of_key k) :: acc) scope.refis [])))
in
fun cx func_frame call_frame changeset ->
if func_frame = 0 || call_frame = 0 || Changeset.is_empty changeset
then ()
else
let func_env = IMap.find_unsafe func_frame (Context.envs cx) in
let call_env = IMap.find_unsafe call_frame (Context.envs cx) in
overlapped_call_scopes func_env call_env |>
List.iter (fun ({ id; _ } as scope) ->
Changeset.include_scopes [id] changeset |>
Changeset.iter_writes
(havoc_entry cx scope)
(havoc_refi cx scope)
)
)
(***************)
(* print utils *)
(***************)
let lib_reason r =
let loc = loc_of_reason r in
Loc.(match loc.source with
| Some LibFile _ -> true
| Some Builtins -> true
| Some SourceFile _ -> false
| None -> false)
let ordered_reasons l u =
let rl = reason_of_t l in
let ru = reason_of_t u in
if is_use u ||
loc_of_t l = Loc.none ||
(lib_reason rl && not (lib_reason ru))
then ru, rl
else rl, ru
(* format an error or warning and add it to flow's output.
here preformatted trace output is passed directly as an argument *)
let prmsg_flow_trace_reasons cx level trace_reasons msg (r1, r2) = Errors_js.(
let op, info = match Ops.peek () with
| Some r when r != r1 && r != r2 ->
(* NOTE: We include the operation's reason in the error message only if it
is distinct from the reasons of the endpoints. *)
Some (message_of_reason r), []
| _ ->
if lib_reason r1 && lib_reason r2
then
(* Since pointing to endpoints in the library without any information on
the code that uses those endpoints inconsistently is useless, we point
to the file containing that code instead. Ideally, improvements in
error reporting would cause this case to never arise. *)
let loc = Loc.({ none with source = Some (Context.file cx) }) in
None, [BlameM (loc, "inconsistent use of library definitions")]
else
None, []
in
let message_list = [
message_of_reason r1;
message_of_string msg;
message_of_reason r2
]
in
add_output cx level ?op ~trace_reasons (info @ message_list)
)
(* format a trace into list of (reason, desc) pairs used
downstream for obscure reasons *)
let make_trace_reasons trace =
if modes.traces = 0 then [] else
Trace.reasons_of_trace ~level:modes.traces trace
|> List.map Errors_js.message_of_reason
(* format an error or warning and add it to flow's output.
here we gate trace output on global settings *)
let prmsg_flow cx level trace msg (r1, r2) =
let trace_reasons = make_trace_reasons trace in
prmsg_flow_trace_reasons cx level trace_reasons msg (r1, r2)
(* format an error and add it to flow's output.
here we print the full trace, ignoring global settings.
Notes
1. since traces can be gigantic, we set the per-level
indentation to 0 when printing them here.
2. as an optimization, we never accumulate traces beyond
a single level if the global setting disables them.
Being downstream of that throttle, we are subject to that
restriction here.
*)
let prerr_flow_full_trace cx trace msg l u =
let trace_reasons =
Trace.reasons_of_trace ~level:(Trace.trace_depth trace + 1) trace
|> List.map Errors_js.message_of_reason
in
prmsg_flow_trace_reasons cx
Errors_js.InferError
trace_reasons
msg
(ordered_reasons l u)
(* format an error and add it to flow's output *)
let prerr_flow cx trace msg l u =
prmsg_flow cx
Errors_js.InferError
trace
msg
(ordered_reasons l u)
(* format a warning and add it to flow's output *)
let prwarn_flow cx trace msg l u =
prmsg_flow cx
Errors_js.InferWarning
trace
msg
(ordered_reasons l u)
let tweak_output list =
List.concat (
List.map (fun (reason, msg) -> Errors_js.(
[message_of_reason reason]
@ (if msg = "" then [] else [message_of_string msg])
)) list
)
let add_msg cx ?trace level list =
let trace_reasons = match trace with
| Some trace -> Some (make_trace_reasons trace)
| None -> None
in
add_output cx level ?trace_reasons (tweak_output list)
(* for outside calls *)
let new_warning list = Errors_js.({
kind = InferWarning;
messages = tweak_output list;
op = None;
trace = []
})
let add_warning cx ?trace list =
add_msg cx ?trace Errors_js.InferWarning list
let new_error list = Errors_js.({
kind = InferError;
messages = tweak_output list;
op = None;
trace = []
})
let add_error cx ?trace list =
add_msg cx ?trace Errors_js.InferError list
(********************************************************************)
(* Since type maps use the built-in compare function to compare types,
we need to be careful to keep the shape of types within the boundaries
of that function. In particular, comparison behaves in unexpected ways
for references. To get around these issues, we denote references with
indices in types, and maintain side tables of those indices to the
denoted references. *)
let mk_propmap cx pmap =
let id = mk_id () in
Context.add_property_map cx id pmap;
id
let find_props cx id = Context.find_props cx id
let has_prop cx id x =
find_props cx id |> SMap.mem x
let read_prop cx id x =
find_props cx id |> SMap.find_unsafe x
(* suitable replacement for has_prop; read_prop *)
let read_prop_opt cx id x =
find_props cx id |> SMap.get x
let read_and_delete_prop cx id x =
find_props cx id |> (fun pmap ->
let t = SMap.find_unsafe x pmap in
let pmap = SMap.remove x pmap in
Context.add_property_map cx id pmap;
t
)
let write_prop cx id x t =
let pmap = find_props cx id in
let pmap = SMap.add x t pmap in
Context.add_property_map cx id pmap
let iter_props cx id f =
find_props cx id
|> SMap.iter f
let iter_props_ cx id f =
find_props cx id
|> SMap.filter (fun x _ -> not (is_internal_name x))
|> SMap.iter f
(***************)
(* strict mode *)
(***************)
(* For any constraints, return a list of def types that form either the lower
bounds of the solution, or a singleton containing the solution itself. *)
let types_of constraints =
match constraints with
| Unresolved { lower; _ } -> TypeMap.keys lower
| Resolved t -> [t]
(* Def types that describe the solution of a type variable. *)
let possible_types cx id = types_of (find_graph cx id)
let possible_types_of_type cx = function
| OpenT (_, id) -> possible_types cx id
| _ -> []
(* Gather the possible types of the tvar associated with the given id, and group
them by their reason's description. Return a map from those descriptions to
one such matching type and a count.
NOTE: here we rely on the unchecked invariant that types are reliably tagged
by their reason descriptions, i.e., that any two nontrivially different types
will always have different reasons. Given the number of places where reasons
are freely created in code, this is a vulnerability.
TODO: find a way to guarantee, or collate types on some other basis.
*)
let distinct_possible_types cx id =
let types = possible_types cx id in
List.fold_left (fun map t ->
let desc = desc_of_reason (reason_of_t t) in
let info = match SMap.get desc map with
| Some (t0, count) -> (t0, count + 1)
| None -> (t, 1)
in
SMap.add desc info map
) SMap.empty types
let suggested_type_cache = ref IMap.empty
let rec list_map2 f ts1 ts2 = match (ts1,ts2) with
| ([],_) | (_,[]) -> []
| (t1::ts1,t2::ts2) -> (f (t1,t2)):: (list_map2 f ts1 ts2)
module TypeSet = Set.Make(Type)
let create_union ts =
UnionT (reason_of_string "union", ts)
let rec merge_type cx = function
| (NumT _, (NumT _ as t))
| (StrT _, (StrT _ as t))
| (BoolT _, (BoolT _ as t))
| (NullT _, (NullT _ as t))
| (VoidT _, (VoidT _ as t))
-> t
| (AnyT _, t) | (t, AnyT _) -> t
| (UndefT _, t) | (t, UndefT _) -> t
| (_, (MixedT _ as t)) | ((MixedT _ as t), _) -> t
| (NullT _, (MaybeT _ as t)) | ((MaybeT _ as t), NullT _)
| (VoidT _, (MaybeT _ as t)) | ((MaybeT _ as t), VoidT _) ->
t
| (FunT (_,_,_,ft1), FunT (_,_,_,ft2)) ->
let tins =
try
List.map2 (fun t1 t2 -> merge_type cx (t1,t2))
ft1.params_tlist ft2.params_tlist
with _ ->
[AnyT.t] (* TODO *)
in
let tout = merge_type cx (ft1.return_t, ft2.return_t) in
(* TODO: How to merge parameter names? *)
FunT (
reason_of_string "function",
dummy_static, dummy_prototype,
mk_functiontype tins tout
)
| (ObjT (_,ot1), ObjT (_,ot2)) ->
(* TODO: How to merge indexer names? *)
let dict = match ot1.dict_t, ot2.dict_t with
| None, None -> None
| Some dict, None | None, Some dict -> Some dict
| Some dict1, Some dict2 ->
Some {
dict_name = None;
key = merge_type cx (dict1.key, dict2.key);
value = merge_type cx (dict1.value, dict2.value);
}
in
let pmap =
let map1 = find_props cx ot1.props_tmap in
let map2 = find_props cx ot2.props_tmap in
let map =
SMap.merge
(fun x t1_opt t2_opt -> match (t1_opt,t2_opt) with
| (None,None) -> None
| (Some t, None) | (None, Some t) -> Some t
| (Some t1, Some t2) -> Some (merge_type cx (t1, t2))
) map1 map2 in
mk_propmap cx map
in
let proto = AnyT.t in
ObjT (
reason_of_string "object",
mk_objecttype dict pmap proto
)
| (ArrT (_,t1,ts1), ArrT (_,t2,ts2)) ->
ArrT (
reason_of_string "array",
merge_type cx (t1, t2),
list_map2 (merge_type cx) ts1 ts2
)
| (MaybeT t1, MaybeT t2) ->
MaybeT (merge_type cx (t1, t2))
| (MaybeT t1, t2)
| (t1, MaybeT t2) ->
MaybeT (merge_type cx (t1, t2))
| ((TaintedT (_,t1)) as lt, ((TaintedT (_,t2)) as rt)) ->
let (r,_) = ordered_reasons lt rt in
TaintedT (r, merge_type cx (t1, t2))
| ((TaintedT (r,t1)), t2) | (t2, (TaintedT (r,t1))) ->
TaintedT (r,merge_type cx (t1,t2))
| (UnionT (_, ts1), UnionT (_, ts2)) ->
create_union (List.rev_append ts1 ts2)
| (UnionT (_, ts), t)
| (t, UnionT (_, ts)) ->
create_union (t :: ts)
(* TODO: do we need to do anything special for merging Null with Void,
Optional with other types, etc.? *)
| (t1, t2) ->
create_union [t1; t2]
(* This function does not only resolve every OpenT recursively, but also
replaces the reasons of types with a uniform ones. It is a left-over bit
from the old ground_type behavior. *)
and ground_type_impl cx ids t = match t with
| BoundT _ -> t
| OpenT (reason, id) ->
lookup_type cx ids id
| NumT _ -> NumT.t
| StrT _ -> StrT.t
| BoolT _ -> BoolT.t
| UndefT _ -> UndefT.t
| NullT _ -> NullT.t
| VoidT _ -> VoidT.t
| MixedT _ -> MixedT.t
| AnyT _ -> AnyT.t
| SingletonStrT (_, s) ->
SingletonStrT (reason_of_string "string singleton", s)
| SingletonNumT (_, n) ->
SingletonNumT (reason_of_string "number singleton", n)
| SingletonBoolT (_, b) ->
SingletonBoolT (reason_of_string "boolean singleton", b)
| FunT (_, _, _, ft) ->
let tins = List.map (ground_type_impl cx ids) ft.params_tlist in
let params_names = ft.params_names in
let tout = ground_type_impl cx ids ft.return_t in
FunT (
reason_of_string "function",
dummy_static, dummy_prototype,
mk_functiontype tins ?params_names tout
)
| ObjT (_, ot) ->
let dict = match ot.dict_t with
| None -> None
| Some dict ->
Some { dict with
key = (ground_type_impl cx ids dict.key);
value = (ground_type_impl cx ids dict.value);
}
in
let pmap =
find_props cx ot.props_tmap
|> SMap.map (ground_type_impl cx ids)
|> mk_propmap cx
in
let proto = AnyT.t in
ObjT (
reason_of_string "object",
mk_objecttype dict pmap proto
)
| ArrT (_, t, ts) ->
ArrT (reason_of_string "array",
ground_type_impl cx ids t,
ts |> List.map (ground_type_impl cx ids))
| MaybeT t ->
MaybeT (ground_type_impl cx ids t)
| TaintedT (r,t) ->
TaintedT (r,ground_type_impl cx ids t)
| PolyT (xs, t) ->
PolyT (xs, ground_type_impl cx ids t)
| ClassT t ->
ClassT (ground_type_impl cx ids t)
| TypeT (_, t) ->
TypeT (reason_of_string "type",
ground_type_impl cx ids t)
| InstanceT (_, static, super, it) ->
t (* nominal type *)
| RestT t ->
RestT (ground_type_impl cx ids t)
| OptionalT t ->
OptionalT (ground_type_impl cx ids t)
| TypeAppT (c, ts) ->
let c = ground_type_impl cx ids c in
let ts = List.map (ground_type_impl cx ids) ts in
TypeAppT (c, ts)
| IntersectionT (_, ts) ->
IntersectionT (
reason_of_string "intersection",
List.map (ground_type_impl cx ids) ts
)
| UnionT (_, ts) ->
create_union (List.map (ground_type_impl cx ids) ts)
| LowerBoundT t ->
LowerBoundT (ground_type_impl cx ids t)
| UpperBoundT t ->
UpperBoundT (ground_type_impl cx ids t)
| AnyObjT _ -> AnyObjT (reason_of_string "any object")
| AnyFunT _ -> AnyFunT (reason_of_string "any function")
| ShapeT t ->
ShapeT (ground_type_impl cx ids t)
| DiffT (t1, t2) ->
DiffT (ground_type_impl cx ids t1, ground_type_impl cx ids t2)
| AnnotT (t1, t2) ->
AnnotT (ground_type_impl cx ids t1, ground_type_impl cx ids t2)
| KeysT (_, t) ->
KeysT (reason_of_string "key set", ground_type_impl cx ids t)
| _ ->
(** TODO **)
failwith (spf "Unsupported type in ground_type_impl: %s" (string_of_ctor t))
and lookup_type_ cx ids id =
if ISet.mem id ids then assert false
else
let ids = ISet.add id ids in
let types = possible_types cx id in
try
List.fold_left
(fun u t -> merge_type cx (ground_type_impl cx ids t, u))
UndefT.t types
with _ ->
AnyT.t
and lookup_type cx ids id =
match IMap.get id !suggested_type_cache with
| None ->
let t = lookup_type_ cx ids id in
suggested_type_cache := !suggested_type_cache |> IMap.add id t;
t
| Some t -> t
and resolve_type cx = function
| OpenT (_, id) ->
let ts = possible_types cx id in
List.fold_left (fun u t ->
merge_type cx (t, u)
) UndefT.t ts
| t -> t
let rec normalize_type cx t =
match t with
| FunT (r, static, proto, ft) ->
let ft =
{ ft with
return_t = normalize_type cx ft.return_t;
params_tlist = List.map (normalize_type cx) ft.params_tlist; } in
FunT (r,
normalize_type cx static,
normalize_type cx proto,
ft)
| ObjT (r, ot) ->
let pmap =
find_props cx ot.props_tmap
|> SMap.map (normalize_type cx)
|> mk_propmap cx
in
ObjT (r,
{ ot with
dict_t = (match ot.dict_t with
| None -> None
| Some dict ->
Some { dict with
key = normalize_type cx dict.key;
value = normalize_type cx dict.value;
});
proto_t = normalize_type cx ot.proto_t;
props_tmap = pmap; })
| UnionT (r, ts) ->
normalize_union cx r ts
| IntersectionT (r, ts) ->
normalize_intersection cx r ts
| MaybeT t ->
let t = normalize_type cx t in
(match t with
| MaybeT _ -> t
| _ -> MaybeT t)
| TaintedT (r,t) ->
let t = normalize_type cx t in
(match t with
| TaintedT _ -> t
| _ -> TaintedT (r,t))
| OptionalT t ->
OptionalT (normalize_type cx t)
| RestT t ->
RestT (normalize_type cx t)
| ArrT (r, t, ts) ->
ArrT (r, normalize_type cx t, List.map (normalize_type cx) ts)
| PolyT (xs, t) ->
PolyT (xs, normalize_type cx t)
| TypeAppT (c, ts) ->
TypeAppT (
normalize_type cx c,
List.map (normalize_type cx) ts
)
| LowerBoundT t ->
LowerBoundT (normalize_type cx t)
| UpperBoundT t ->
UpperBoundT (normalize_type cx t)
| ShapeT t ->
ShapeT (normalize_type cx t)
| DiffT (t1, t2) ->
DiffT (normalize_type cx t1, normalize_type cx t2)
(* TODO: Normalize all types? *)
| t -> t
(* TODO: This is not an exhaustive list of normalization steps for unions.
For example, we might want to get rid of AnyT in the union similar to how
merge_type gets rid of AnyT. Decide on rules like these and implement them
if required. *)
and normalize_union cx r ts =
let ts = List.map (normalize_type cx) ts in
let ts = collect_union_members ts in
let (ts, has_void, has_null) =
TypeSet.fold (fun t (ts, has_void, has_null) ->
match t with
| MaybeT (UnionT (_, tlist)) ->
let ts = List.fold_left (fun acc t -> TypeSet.add t acc) ts tlist in
(ts, true, true)
| MaybeT t -> (TypeSet.add t ts, true, true)
| VoidT _ -> (ts, true, has_null)
| NullT _ -> (ts, has_void, true)
(* TODO: We should only get UndefT here when a completely open type
variable has been in the union before grounding it. This happens when
"null" is passed to a function parameter. We throw this out because
it gives no information at all. merge_type also ignores UndefT. *)
| UndefT _ -> (ts, has_void, has_null)
| _ -> (TypeSet.add t ts, has_void, has_null)
) ts (TypeSet.empty, false, false) in
let ts =
match (has_void, has_null) with
| (true, false) -> TypeSet.add VoidT.t ts
| (false, true) -> TypeSet.add NullT.t ts
| _ ->
(* We should never get an empty set at this point but better safe than
sorry. Stripping out UndefT above might be unsafe. *)
if TypeSet.is_empty ts
then TypeSet.singleton UndefT.t
else ts
in
let ts = TypeSet.elements ts in
let t =
match ts with
| [t] -> t
| _ -> UnionT (r, ts)
in
if has_void && has_null
then MaybeT t
else t
and collect_union_members ts =
List.fold_left (fun acc x ->
match x with
| UnionT (_, ts) -> TypeSet.union (collect_union_members ts) acc
| _ -> TypeSet.add x acc
) TypeSet.empty ts
(* TODO: This does not do any real normalization yet, it only flattens the
intesection. Think about normalization rules and implement them when there
is need for that. *)
and normalize_intersection cx r ts =
let ts = List.map (normalize_type cx) ts in
let ts = collect_intersection_members ts in
let ts = TypeSet.elements ts in
match ts with
| [t] -> t
| _ -> IntersectionT (r, ts)
and collect_intersection_members ts =
List.fold_left (fun acc x ->
match x with
| IntersectionT (_, ts) ->
TypeSet.union acc (collect_intersection_members ts)
| _ ->
TypeSet.add x acc
) TypeSet.empty ts
let ground_type cx type_ =
ground_type_impl cx ISet.empty type_
let rec printify_type cx t =
match t with
| FunT (r, static, proto, ft) ->
let ft =
{ ft with
return_t = printify_type cx ft.return_t;
params_tlist = List.map (printify_type cx) ft.params_tlist; } in
FunT (r,
printify_type cx static,
printify_type cx proto,
ft)
| ObjT (r, ot) ->
let pmap =
find_props cx ot.props_tmap
|> SMap.map (printify_type cx)
|> mk_propmap cx
in
ObjT (r,
{ ot with
dict_t = (match ot.dict_t with
| None -> None
| Some dict ->
Some { dict with
key = printify_type cx dict.key;
value = printify_type cx dict.value;
});
proto_t = printify_type cx ot.proto_t;
props_tmap = pmap; })
| UnionT (r, ts) ->
let (ts, add_maybe) =