-
Notifications
You must be signed in to change notification settings - Fork 3k
/
typing.ml
9405 lines (9171 loc) · 320 KB
/
typing.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) 2015, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the "hack" directory of this source tree.
*
*)
(* This module implements the typing.
*
* Given an Nast.program, it infers the type of all the local
* variables, and checks that all the types are correct (aka
* consistent) *)
open Hh_prelude
open Common
open Aast
open Tast
open Typing_defs
open Typing_env_types
open Utils
open Typing_helpers
module TFTerm = Typing_func_terminality
module TUtils = Typing_utils
module Reason = Typing_reason
module Type = Typing_ops
module Env = Typing_env
module Inf = Typing_inference_env
module LEnv = Typing_lenv
module Async = Typing_async
module SubType = Typing_subtype
module Union = Typing_union
module Inter = Typing_intersection
module SN = Naming_special_names
module TVis = Typing_visibility
module Phase = Typing_phase
module TOG = Typing_object_get
module Subst = Decl_subst
module ExprDepTy = Typing_dependent_type.ExprDepTy
module TCO = TypecheckerOptions
module C = Typing_continuations
module CMap = C.Map
module Try = Typing_try
module FL = FeatureLogging
module MakeType = Typing_make_type
module Cls = Decl_provider.Class
module Fake = Typing_fake_members
module ExpectedTy = Typing_helpers.ExpectedTy
module ITySet = Internal_type_set
type newable_class_info =
env
* Tast.targ list
* Tast.class_id
* [ `Class of pos_id * Cls.t * locl_ty | `Dynamic ] list
type dyn_func_kind =
| Supportdyn_function
| Like_function
(*****************************************************************************)
(* Debugging *)
(*****************************************************************************)
(* A guess as to the last position we were typechecking, for use in debugging,
* such as figuring out what a runaway hh_server thread is doing. Updated
* only best-effort -- it's an approximation to point debugging in the right
* direction, nothing more. *)
let debug_last_pos = ref Pos.none
let debug_print_last_pos _ =
Hh_logger.info
"Last typecheck pos: %s"
(Pos.string (Pos.to_absolute !debug_last_pos))
(*****************************************************************************)
(* Helpers *)
(*****************************************************************************)
let mk_ty_mismatch_opt ty_have ty_expect = function
| Some _ -> Some (ty_have, ty_expect)
| _ -> None
let mk_ty_mismatch_res ty_have ty_expect = function
| Some _ -> Error (ty_have, ty_expect)
| _ -> Ok ty_have
let mk_hole ?(source = Aast.Typing) ((_, pos, _) as expr) ~ty_have ~ty_expect =
if equal_locl_ty ty_have ty_expect then
expr
else
(* if the hole is generated from typing, we leave the type unchanged,
if it is a call to `[unsafe|enforced]_cast`, we give it the expected type
*)
let ty_hole =
match source with
| Aast.Typing -> ty_have
| UnsafeCast _
| EnforcedCast _ ->
ty_expect
in
make_typed_expr pos ty_hole @@ Aast.Hole (expr, ty_have, ty_expect, source)
let hole_on_ty_mismatch (te : Tast.expr) ~ty_mismatch_opt =
Option.value_map ty_mismatch_opt ~default:te ~f:(fun (ty_have, ty_expect) ->
mk_hole te ~ty_have ~ty_expect)
(* When typing compound assignments we generate a 'fake' expression which
desugars it to the operation on the rhs of the assignment. If there
was a subtyping error, we end up with the Hole on the fake rhs
rather than the original rhs. This function rewrites the
desugared expression with the Hole in the correct place *)
let resugar_binop expr =
match expr with
| ( topt,
p,
Aast.(
Binop
( _,
te1,
(_, _, Hole ((_, _, Binop (op, _, te2)), ty_have, ty_expect, source))
)) ) ->
let hte2 = mk_hole te2 ~ty_have ~ty_expect ~source in
let te = Aast.Binop (Ast_defs.Eq (Some op), te1, hte2) in
Some (topt, p, te)
| (topt, p, Aast.Binop (_, te1, (_, _, Aast.Binop (op, _, te2)))) ->
let te = Aast.Binop (Ast_defs.Eq (Some op), te1, te2) in
Some (topt, p, te)
| _ -> None
(* When recording subtyping or coercion errors for union and intersection types
we need to look at the error for each element and then reconstruct any
errors into a union or intersection. If there were no errors for any
element, the result if also `Ok`; if there was an error for at least
on element we have `Error` with list of actual and expected types *)
let fold_coercion_errs errs =
List.fold_left errs ~init:(Ok []) ~f:(fun acc err ->
match (acc, err) with
| (Ok xs, Ok x) -> Ok (x :: xs)
| (Ok xs, Error (x, y)) -> Error (x :: xs, y :: xs)
| (Error (xs, ys), Ok x) -> Error (x :: xs, x :: ys)
| (Error (xs, ys), Error (x, y)) -> Error (x :: xs, y :: ys))
let union_coercion_errs errs =
Result.fold
~ok:(fun tys -> Ok (MakeType.union Reason.Rnone tys))
~error:(fun (acts, exps) ->
Error (MakeType.union Reason.Rnone acts, MakeType.union Reason.Rnone exps))
@@ fold_coercion_errs errs
let intersect_coercion_errs errs =
Result.fold
~ok:(fun tys -> Ok (MakeType.intersection Reason.Rnone tys))
~error:(fun (acts, exps) ->
Error
( MakeType.intersection Reason.Rnone acts,
MakeType.intersection Reason.Rnone exps ))
@@ fold_coercion_errs errs
(** Given the type of an argument that has been unpacked and typed against
positional and variadic function parameter types, apply the subtyping /
coercion errors back to the original packed type. *)
let pack_errs pos ty subtyping_errs =
let nothing =
MakeType.nothing @@ Reason.Rsolve_fail (Pos_or_decl.of_raw_pos pos)
in
let rec aux ~k = function
(* Case 1: we have a type error at this positional parameter so
replace the type parameter which caused it with the expected type *)
| ((Some (_, ty) :: rest, var_opt), _ :: tys)
(* Case 2: there was no type error here so retain the original type
parameter *)
| ((None :: rest, var_opt), ty :: tys) ->
(* recurse for any remaining positional parameters and add the
corrected (case 1) or original (case 2) type to the front of the
list of type parameters in the continuation *)
aux ((rest, var_opt), tys) ~k:(fun tys -> k (ty :: tys))
(* Case 3: we have a type error at the variadic parameter so replace
the type parameter which cased it with the expected type *)
| ((_, (Some (_, ty) as var_opt)), _ :: tys) ->
(* recurse with the variadic parameter error and add the
corrected type to the front of the list of type parameters in the
continuation *)
aux (([], var_opt), tys) ~k:(fun tys -> k (ty :: tys))
(* Case 4: we have a variadic parameter but no error - we're done so
pass the remaining unchanged type parameters into the contination
to rebuild corrected type params in the right order *)
| ((_, None), tys) -> k tys
(* Case 5: no more type parameters - again we're done so pass empty
list to continuation and rebuild corrected type params in the right
order *)
| (_, []) -> k []
in
(* The only types that _can_ be upacked are tuples and pairs; match on the
type to get the type parameters, pass them to our recursive function
aux to subsitute the expected type where we have a type error
then reconstruct the type in the continuation *)
match deref ty with
| (r, Ttuple tys) ->
aux (subtyping_errs, tys) ~k:(fun tys -> mk (r, Ttuple tys))
| (r, Tclass (pos_id, exact, tys)) ->
aux (subtyping_errs, tys) ~k:(fun tys ->
mk (r, Tclass (pos_id, exact, tys)))
| _ -> nothing
let err_witness env p = TUtils.terr env (Reason.Rwitness p)
let triple_to_pair (env, te, ty) = (env, (te, ty))
let with_special_coeffects env cap_ty unsafe_cap_ty f =
let init =
Option.map (Env.next_cont_opt env) ~f:(fun next_cont ->
let initial_locals = next_cont.Typing_per_cont_env.local_types in
let tpenv = Env.get_tpenv env in
(initial_locals, tpenv))
in
Typing_lenv.stash_and_do env (Env.all_continuations env) (fun env ->
let env =
match init with
| None -> env
| Some (initial_locals, tpenv) ->
let env = Env.reinitialize_locals env in
let env = Env.set_locals env initial_locals in
let env = Env.env_with_tpenv env tpenv in
env
in
let (env, _ty) =
Typing_coeffects.register_capabilities env cap_ty unsafe_cap_ty
in
f env)
(* Set all the types in an expression to the given type. *)
let with_type ty env (e : Nast.expr) : Tast.expr =
let visitor =
object (self)
inherit [_] Aast.map
method! on_expr env ((), p, expr_) = (ty, p, self#on_expr_ env expr_)
method on_'ex _ () = ty
method on_'en _ _ = env
end
in
visitor#on_expr () e
let invalid_expr_ env p : Tast.expr_ =
let expr = ((), p, Naming.invalid_expr_ p) in
let ty = TUtils.terr env Reason.Rnone in
let (_, _, expr_) = with_type ty Tast.dummy_saved_env expr in
expr_
let expr_error env (r : Reason.t) (e : Nast.expr) =
let ty = TUtils.terr env r in
(env, with_type ty Tast.dummy_saved_env e, ty)
let expr_any env p e =
let ty = Typing_utils.mk_tany env p in
(env, with_type ty Tast.dummy_saved_env e, ty)
let unbound_name env (pos, name) e =
let class_exists =
let ctx = Env.get_ctx env in
let decl = Decl_provider.get_class ctx name in
match decl with
| None -> false
| Some dc -> Ast_defs.is_c_class (Cls.kind dc)
in
match Env.get_mode env with
| FileInfo.Mstrict ->
Errors.add_typing_error
Typing_error.(primary @@ Primary.Unbound_name { pos; name; class_exists });
expr_error env (Reason.Rwitness pos) e
| FileInfo.Mhhi -> expr_any env pos e
(* Is this type Traversable<vty> or Container<vty> for some vty? *)
let get_value_collection_inst env ty =
match get_node ty with
| Tclass ((_, c), _, [vty])
when String.equal c SN.Collections.cTraversable
|| String.equal c SN.Collections.cContainer ->
Some vty
(* If we're expecting a mixed or a nonnull then we can just assume
* that the element type is mixed *)
| Tnonnull -> Some (MakeType.mixed (get_reason ty))
| Tany _ -> Some ty
| Tdynamic when env.in_support_dynamic_type_method_check ->
Some ty (* interpret dynamic as Traversable<dynamic> *)
| _ -> None
(* Is this type KeyedTraversable<kty,vty>
* or KeyedContainer<kty,vty>
* for some kty, vty?
*)
let get_key_value_collection_inst env p ty =
match get_node ty with
| Tclass ((_, c), _, [kty; vty])
when String.equal c SN.Collections.cKeyedTraversable
|| String.equal c SN.Collections.cKeyedContainer ->
Some (kty, vty)
(* If we're expecting a mixed or a nonnull then we can just assume
* that the key type is arraykey and the value type is mixed *)
| Tnonnull ->
let arraykey = MakeType.arraykey (Reason.Rkey_value_collection_key p) in
let mixed = MakeType.mixed (Reason.Rwitness p) in
Some (arraykey, mixed)
| Tany _ -> Some (ty, ty)
| Tdynamic when env.in_support_dynamic_type_method_check ->
(* interpret dynamic as KeyedTraversable<arraykey, dynamic> *)
let arraykey = MakeType.arraykey (Reason.Rkey_value_collection_key p) in
Some (arraykey, ty)
| _ -> None
(* Is this type varray<vty> or a supertype for some vty? *)
let vc_kind_to_supers kind =
match kind with
| Vector -> [SN.Collections.cVector; SN.Collections.cMutableVector]
| ImmVector -> [SN.Collections.cImmVector; SN.Collections.cConstVector]
| Vec -> [SN.Collections.cVec]
| Set -> [SN.Collections.cSet; SN.Collections.cMutableSet]
| ImmSet -> [SN.Collections.cImmSet; SN.Collections.cConstSet]
| Keyset -> [SN.Collections.cKeyset]
let kvc_kind_to_supers kind =
match kind with
| Map -> [SN.Collections.cMap; SN.Collections.cMutableMap]
| ImmMap -> [SN.Collections.cImmMap; SN.Collections.cConstMap]
| Dict -> [SN.Collections.cDict]
(* Is this type one of the value collection types with element type vty? *)
let get_vc_inst env vc_kind ty =
let classnames = vc_kind_to_supers vc_kind in
match get_node ty with
| Tclass ((_, c), _, [vty]) when List.exists classnames ~f:(String.equal c) ->
Some vty
| _ -> get_value_collection_inst env ty
(* Is this type one of the three key-value collection types
* e.g. dict<kty,vty> or a supertype for some kty and vty? *)
let get_kvc_inst env p kvc_kind ty =
let classnames = kvc_kind_to_supers kvc_kind in
match get_node ty with
| Tclass ((_, c), _, [kty; vty])
when List.exists classnames ~f:(String.equal c) ->
Some (kty, vty)
| _ -> get_key_value_collection_inst env p ty
(* Check whether this is a function type that (a) either returns a disposable
* or (b) has the <<__ReturnDisposable>> attribute
*)
let is_return_disposable_fun_type env ty =
let (_env, ty) = Env.expand_type env ty in
match get_node ty with
| Tfun ft ->
get_ft_return_disposable ft
|| Option.is_some
(Typing_disposable.is_disposable_type env ft.ft_ret.et_type)
| _ -> false
(* Turn an environment into a local_id_map suitable to be embedded
* into an AssertEnv statement
*)
let annot_map env =
match Env.next_cont_opt env with
| Some { Typing_per_cont_env.local_types; _ } ->
Some (Local_id.Map.map (fun (ty, pos, _expr_id) -> (pos, ty)) local_types)
| None -> None
(* Similar to annot_map above, but filters the map to only contain
* information about locals in lset
*)
let refinement_annot_map env lset =
match annot_map env with
| Some map ->
let map =
Local_id.Map.filter (fun lid _ -> Local_id.Set.mem lid lset) map
in
if Local_id.Map.is_empty map then
None
else
Some map
| None -> None
let assert_env_blk ~pos ~at annotation_kind env_map_opt blk =
let mk_assert map = (pos, Aast.AssertEnv (annotation_kind, map)) in
let annot_blk = Option.to_list (Option.map ~f:mk_assert env_map_opt) in
match at with
| `Start -> annot_blk @ blk
| `End -> blk @ annot_blk
let assert_env_stmt ~pos ~at annotation_kind env_map_opt stmt =
let mk_assert map = (pos, Aast.AssertEnv (annotation_kind, map)) in
match env_map_opt with
| Some env_map ->
let stmt = (pos, stmt) in
let blk =
match at with
| `Start -> [mk_assert env_map; stmt]
| `End -> [stmt; mk_assert env_map]
in
Aast.Block blk
| None -> stmt
let set_tcopt_unstable_features env { fa_user_attributes; _ } =
match
Naming_attributes.find
SN.UserAttributes.uaEnableUnstableFeatures
fa_user_attributes
with
| None -> env
| Some { ua_name = _; ua_params } ->
let ( = ) = String.equal in
List.fold ua_params ~init:env ~f:(fun env (_, _, feature) ->
match feature with
| Aast.String s when s = SN.UnstableFeatures.ifc ->
Env.map_tcopt ~f:TypecheckerOptions.enable_ifc env
| Aast.String s when s = SN.UnstableFeatures.modules ->
Env.map_tcopt ~f:(fun t -> TypecheckerOptions.set_modules t true) env
| Aast.String s when s = SN.UnstableFeatures.expression_trees ->
Env.map_tcopt
~f:(fun t ->
TypecheckerOptions.set_tco_enable_expression_trees t true)
env
| _ -> env)
(** Do a subtype check of inferred type against expected type.
* The optional coerce_for_op parameter controls whether any arguments of type
* dynamic can be coerced to enforceable types because they are arguments to a
* built-in operator.
*)
let check_expected_ty_res
~(coerce_for_op : bool)
(message : string)
(env : env)
(inferred_ty : locl_ty)
(expected : ExpectedTy.t option) : (env, env) result =
match expected with
| None -> Ok env
| Some ExpectedTy.{ pos = p; reason = ur; ty; coerce } ->
Typing_log.(
log_with_level env "typing" ~level:1 (fun () ->
log_types
(Pos_or_decl.of_raw_pos p)
env
[
Log_head
( Printf.sprintf
"Typing.check_expected_ty %s enforced=%s"
message
(match ty.et_enforced with
| Unenforced -> "unenforced"
| Enforced -> "enforced"),
[
Log_type ("inferred_ty", inferred_ty);
Log_type ("expected_ty", ty.et_type);
] );
]));
let (env, ty_err_opt) =
Typing_coercion.coerce_type
~coerce_for_op
~coerce
p
ur
env
inferred_ty
ty
Typing_error.Callback.unify_error
in
Option.iter ty_err_opt ~f:Errors.add_typing_error;
Option.value_map ~default:(Ok env) ~f:(fun _ -> Error env) ty_err_opt
let check_expected_ty message env inferred_ty expected =
Result.fold ~ok:Fn.id ~error:Fn.id
@@ check_expected_ty_res ~coerce_for_op:false message env inferred_ty expected
(* Set a local; must not be already assigned if it is a using variable *)
let set_local ?(is_using_clause = false) env (pos, x) ty =
if Env.is_using_var env x then
Errors.add_typing_error
Typing_error.(
primary
(if is_using_clause then
Primary.Duplicate_using_var pos
else
Primary.Illegal_disposable { pos; verb = `assigned }));
let env = Env.set_local env x ty pos in
if is_using_clause then
Env.set_using_var env x
else
env
(* Require a new construct with disposable *)
let rec enforce_return_disposable _env e =
match e with
| (_, _, New _) -> ()
| (_, _, Call _) -> ()
| (_, _, Await (_, _, Call _)) -> ()
| (_, _, Hole (e, _, _, _)) -> enforce_return_disposable _env e
| (_, p, _) ->
Errors.add_typing_error
Typing_error.(primary @@ Primary.Invalid_return_disposable p)
(* Wrappers around the function with the same name in Typing_lenv, which only
* performs the move/save and merge operation if we are in a try block or in a
* function with return type 'noreturn'.
* This enables significant perf improvement, because this is called at every
* function of method call, when most calls are outside of a try block. *)
let move_and_merge_next_in_catch env =
if env.in_try || TFTerm.is_noreturn env then
LEnv.move_and_merge_next_in_cont env C.Catch
else
LEnv.drop_cont env C.Next
let save_and_merge_next_in_catch env =
if env.in_try || TFTerm.is_noreturn env then
LEnv.save_and_merge_next_in_cont env C.Catch
else
env
let might_throw env = save_and_merge_next_in_catch env
let branch :
type res. env -> (env -> env * res) -> (env -> env * res) -> env * res * res
=
fun env branch1 branch2 ->
let parent_lenv = env.lenv in
let (env, tbr1) = branch1 env in
let lenv1 = env.lenv in
let env = { env with lenv = parent_lenv } in
let (env, tbr2) = branch2 env in
let lenv2 = env.lenv in
let env = LEnv.union_lenvs env parent_lenv lenv1 lenv2 in
(env, tbr1, tbr2)
let as_expr env ty1 pe e =
let env = Env.open_tyvars env pe in
let (env, tv) = Env.fresh_type env pe in
let (env, ct) =
match e with
| As_v _ ->
( env,
{
ct_key = None;
ct_val = tv;
ct_is_await = false;
ct_reason = Reason.Rforeach pe;
} )
| As_kv _ ->
let (env, tk) = Env.fresh_type env pe in
( env,
{
ct_key = Some tk;
ct_val = tv;
ct_is_await = false;
ct_reason = Reason.Rforeach pe;
} )
| Await_as_v _ ->
( env,
{
ct_key = None;
ct_val = tv;
ct_is_await = true;
ct_reason = Reason.Rasyncforeach pe;
} )
| Await_as_kv _ ->
let (env, tk) = Env.fresh_type env pe in
( env,
{
ct_key = Some tk;
ct_val = tv;
ct_is_await = true;
ct_reason = Reason.Rasyncforeach pe;
} )
in
let expected_ty =
ConstraintType (mk_constraint_type (Reason.Rforeach pe, Tcan_traverse ct))
in
let (env, ty_err_opt) =
Type.sub_type_i
pe
Reason.URforeach
env
(LoclType ty1)
expected_ty
Typing_error.Callback.unify_error
in
Option.iter ~f:Errors.add_typing_error ty_err_opt;
let ty_mismatch = mk_ty_mismatch_res ty1 expected_ty ty_err_opt in
let err_opt =
match ty_mismatch with
| Ok _ -> None
| Error (act, LoclType exp) -> Some (act, exp)
| Error (act, ConstraintType _) ->
Some (act, SubType.can_traverse_to_iface ct)
in
let env = Env.set_tyvar_variance_i env expected_ty in
let tk =
match ct.ct_key with
| None -> MakeType.mixed Reason.Rnone
| Some tk -> tk
in
(Typing_solver.close_tyvars_and_solve env, tk, tv, err_opt)
(* These functions invoke special printing functions for Typing_env. They do not
* appear in user code, but we still check top level function calls against their
* names. *)
let typing_env_pseudofunctions =
SN.PseudoFunctions.(
String.Hash_set.of_list
~growth_allowed:false
[
hh_show;
hh_expect;
hh_expect_equivalent;
hh_show_env;
hh_log_level;
hh_force_solve;
hh_loop_forever;
hh_time;
])
let do_hh_expect ~equivalent env use_pos explicit_targs p tys =
match explicit_targs with
| [targ] ->
let ((env, ty_err_opt), (expected_ty, _)) =
Phase.localize_targ ~check_well_kinded:true env (snd targ)
in
Option.iter ~f:Errors.add_typing_error ty_err_opt;
let right_expected_ty =
if TypecheckerOptions.pessimise_builtins (Env.get_tcopt env) then
MakeType.locl_like
(Reason.Renforceable (get_pos expected_ty))
expected_ty
else
expected_ty
in
(match tys with
| [expr_ty] ->
let res =
SubType.sub_type env expr_ty right_expected_ty
@@ Some
Typing_error.(
Reasons_callback.of_primary_error
@@ Primary.Hh_expect { pos = p; equivalent })
in
let (env, ty_err_opt) =
match res with
| (env, None) ->
if equivalent then
SubType.sub_type env expected_ty expr_ty
@@ Some
Typing_error.(
Reasons_callback.of_primary_error
@@ Primary.Hh_expect { pos = p; equivalent })
else
(env, None)
| env_err -> env_err
in
Option.iter ~f:Errors.add_typing_error ty_err_opt;
env
| _ -> env)
| _ ->
(Errors.add_typing_error
@@ Typing_error.(
primary
@@ Primary.Expected_tparam
{ pos = use_pos; decl_pos = Pos_or_decl.none; n = 1 }));
env
let loop_forever env =
(* forever = up to 10 minutes, to avoid accidentally stuck processes *)
for i = 1 to 600 do
(* Look up things in shared memory occasionally to have a chance to be
* interrupted *)
match Env.get_class env "FOR_TEST_ONLY" with
| None -> Unix.sleep 1
| _ -> assert false
done;
Utils.assert_false_log_backtrace
(Some "hh_loop_forever was looping for more than 10 minutes")
let hh_time_start_times = ref SMap.empty
(* Wrap the code you'd like to profile with `hh_time` annotations, e.g.,
hh_time('start');
// Expensive to typecheck code
hh_time('stop');
`hh_time` admits an optional tag parameter to allow for multiple timings in
the same file.
hh_time('start', 'Timing 1');
// Expensive to typecheck code
hh_time('stop', 'Timinig 1');
hh_time('start', 'Timing 2');
// Some more expensive to typecheck code
hh_time('stop', 'Timinig 2');
Limitation: Timings are not scoped, so multiple uses of the same tag
with `hh_time('start', tag)` will overwrite the beginning time of the
previous timing.
*)
let do_hh_time el =
let go command tag =
match command with
| String "start" ->
let start_time = Unix.gettimeofday () in
hh_time_start_times := SMap.add tag start_time !hh_time_start_times
| String "stop" ->
let stop_time = Unix.gettimeofday () in
begin
match SMap.find_opt tag !hh_time_start_times with
| Some start_time ->
let elapsed_time_ms = (stop_time -. start_time) *. 1000. in
Printf.printf "%s: %0.2fms\n" tag elapsed_time_ms
| None -> ()
end
| _ -> ()
in
match el with
| [(_, (_, _, command))] -> go command "_"
| [(_, (_, _, command)); (_, (_, _, String tag))] -> go command tag
| _ -> ()
let is_parameter env x = Local_id.Map.mem x (Env.get_params env)
let check_escaping_var env (pos, x) =
let err_opt =
if Env.is_using_var env x then
let open Typing_error.Primary in
Some
(if Local_id.equal x this then
Escaping_this pos
else if is_parameter env x then
Escaping_disposable_param pos
else
Escaping_disposable pos)
else
None
in
Option.iter err_opt ~f:(fun err ->
Errors.add_typing_error @@ Typing_error.primary err)
let make_result env p te ty =
(* Set the variance of any type variables that were generated according
* to how they appear in the expression type *)
let env = Env.set_tyvar_variance env ty in
(env, Tast.make_typed_expr p ty te, ty)
let localize_targ env ta =
let pos = fst ta in
let (env, targ) = Phase.localize_targ ~check_well_kinded:true env ta in
(env, targ, ExpectedTy.make pos Reason.URhint (fst targ))
let rec set_function_pointer ty =
match get_node ty with
| Tfun ft ->
let ft = set_ft_is_function_pointer ft true in
mk (get_reason ty, Tfun ft)
| Tnewtype (name, [ty1], ty2) when String.equal name SN.Classes.cSupportDyn ->
let ty3 = set_function_pointer ty1 in
mk (get_reason ty, Tnewtype (name, [ty3], ty2))
| _ -> ty
let rec set_readonly_this ty =
match get_node ty with
| Tfun ft ->
let ft = set_ft_readonly_this ft true in
mk (get_reason ty, Tfun ft)
| Tnewtype (name, [ty1], ty2) when String.equal name SN.Classes.cSupportDyn ->
let ty3 = set_readonly_this ty1 in
mk (get_reason ty, Tnewtype (name, [ty3], ty2))
| _ -> ty
let xhp_attribute_decl_ty env sid obj attr =
let (namepstr, valpty) = attr in
let (valp, valty) = valpty in
let ((env, e1), (declty, _tal)) =
TOG.obj_get
~obj_pos:(fst sid)
~is_method:false
~inst_meth:false
~meth_caller:false
~nullsafe:None
~coerce_from_ty:None
~explicit_targs:[]
~class_id:(CI sid)
~member_id:namepstr
~on_error:Typing_error.Callback.unify_error
env
obj
in
let ureason = Reason.URxhp (snd sid, snd namepstr) in
let (env, e2) =
Typing_coercion.coerce_type
valp
ureason
env
valty
(MakeType.unenforced declty)
Typing_error.Callback.xhp_attribute_does_not_match_hint
in
let ty_mismatch_opt = mk_ty_mismatch_opt valty declty e2 in
Option.(iter ~f:Errors.add_typing_error @@ merge e1 e2 ~f:Typing_error.both);
(env, declty, ty_mismatch_opt)
let closure_check_param env param =
match hint_of_type_hint param.param_type_hint with
| None -> env
| Some hty ->
let hint_pos = fst hty in
let ((env, ty_err_opt1), hty) =
Phase.localize_hint_no_subst env ~ignore_errors:false hty
in
Option.iter ~f:Errors.add_typing_error ty_err_opt1;
let paramty = Env.get_local env (Local_id.make_unscoped param.param_name) in
let (env, ty_err_opt2) =
Typing_coercion.coerce_type
hint_pos
Reason.URhint
env
paramty
(MakeType.unenforced hty)
Typing_error.Callback.unify_error
in
Option.iter ty_err_opt2 ~f:Errors.add_typing_error;
env
let stash_conts_for_closure env p is_anon captured f =
let captured =
if is_anon && TypecheckerOptions.any_coeffects (Env.get_tcopt env) then
Typing_coeffects.(
(Pos.none, local_capability_id) :: (Pos.none, capability_id) :: captured)
else
captured
in
let captured =
if Env.is_local_defined env this && not (Env.is_in_expr_tree env) then
(Pos.none, this) :: captured
else
captured
in
let init =
Option.map (Env.next_cont_opt env) ~f:(fun next_cont ->
let initial_locals =
if is_anon then
Env.get_locals env captured
else
next_cont.Typing_per_cont_env.local_types
in
let initial_fakes =
Fake.forget (Env.get_fake_members env) Reason.(Blame (p, BSlambda))
in
let tpenv = Env.get_tpenv env in
(initial_locals, initial_fakes, tpenv))
in
Typing_lenv.stash_and_do env (Env.all_continuations env) (fun env ->
let env =
match init with
| None -> env
| Some (initial_locals, initial_fakes, tpenv) ->
let env = Env.reinitialize_locals env in
let env = Env.set_locals env initial_locals in
let env = Env.set_fake_members env initial_fakes in
let env = Env.env_with_tpenv env tpenv in
env
in
f env)
let requires_consistent_construct = function
| CIstatic -> true
| CIexpr _ -> true
| CIparent -> false
| CIself -> false
| CI _ -> false
(* Caller will be looking for a particular form of expected type
* e.g. a function type (when checking lambdas) or tuple type (when checking
* tuples). First expand the expected type and elide single union; also
* strip nullables, so ?t becomes t, as context will always accept a t if a ?t
* is expected.
*
* If for_lambda is true, then we are expecting a function type for the expected type,
* and we should decompose supportdyn<t>, and like-push, and return true to
* indicate that the type supports dynamic.
*
* Note: we currently do not generally expand ?t into (null | t), so ~?t is (dynamic | Toption t).
*)
let expand_expected_and_get_node
?(for_lambda = false) env (expected : ExpectedTy.t option) =
let rec unbox env ty =
match TUtils.try_strip_dynamic env ty with
| Some stripped_ty ->
if TypecheckerOptions.enable_sound_dynamic env.genv.tcopt then
let (env, opt_ty) =
if
(not for_lambda)
&& TypecheckerOptions.pessimise_builtins (Env.get_tcopt env)
then
(env, None)
else
Typing_dynamic.try_push_like env stripped_ty
in
match opt_ty with
| None -> unbox env stripped_ty
| Some ty ->
if Typing_utils.is_supportdyn env ty then
unbox env ty
else
unbox env stripped_ty
else
unbox env stripped_ty
| None ->
begin
match get_node ty with
| Tunion [ty] -> unbox env ty
| Toption ty -> unbox env ty
| Tnewtype (name, [ty], _) when String.equal name SN.Classes.cSupportDyn
->
let (env, ty, _) = unbox env ty in
(env, ty, true)
| _ -> (env, ty, false)
end
in
match expected with
| None -> (env, None)
| Some ExpectedTy.{ pos = p; reason = ur; ty = { et_type = ty; _ }; _ } ->
let (env, ty) = Env.expand_type env ty in
let (env, uty, supportdyn) = unbox env ty in
if supportdyn && not for_lambda then
(env, None)
else
(env, Some (p, ur, supportdyn, uty, get_node uty))
let uninstantiable_error env reason_pos cid c_tc_pos c_name c_usage_pos c_ty =
let reason_ty_opt =
match cid with
| CIexpr _ -> Some (reason_pos, lazy (Typing_print.error env c_ty))
| _ -> None
in
let err =
Typing_error.(
primary
@@ Primary.Uninstantiable_class
{
pos = c_usage_pos;
class_name = c_name;
reason_ty_opt;
decl_pos = c_tc_pos;
})
in
Errors.add_typing_error err
let coerce_to_throwable pos env exn_ty =
let throwable_ty = MakeType.throwable (Reason.Rthrow pos) in
Typing_coercion.coerce_type
~coerce_for_op:true
pos
Reason.URthrow
env
exn_ty
{ et_type = throwable_ty; et_enforced = Enforced }
Typing_error.Callback.unify_error
let set_valid_rvalue p env x ty =
let env = set_local env (p, x) ty in
(* We are assigning a new value to the local variable, so we need to
* generate a new expression id
*)
Env.set_local_expr_id env x (Ident.tmp ())
let is_hack_collection env ty =
(* TODO(like types) This fails if a collection is used as a parameter under
* pessimization, because ~Vector<int> </: ConstCollection<mixed>. This is the
* test we use to see whether to update the expression id for expressions
* $vector[0] = $x and $vector[] = $x. If this is false, the receiver is assumed
* to be a Hack array which are COW. This approximation breaks down in the presence
* of dynamic. It is unclear whether we should change an expression id if the
* receiver is dynamic. *)
Typing_solver.is_sub_type
env
ty
(MakeType.const_collection Reason.Rnone (MakeType.mixed Reason.Rnone))
let check_class_get env p def_pos cid mid ce (_, _cid_pos, e) function_pointer =
match e with
| CIself when get_ce_abstract ce ->
begin
match Env.get_self_id env with
| Some self ->
(* at runtime, self:: in a trait is a call to whatever
* self:: is in the context of the non-trait "use"-ing
* the trait's code *)
begin
match Env.get_class env self with
| Some cls when Ast_defs.is_c_trait (Cls.kind cls) ->
(* Ban self::some_abstract_method() in a trait, if the
* method is also defined in a trait.