-
Notifications
You must be signed in to change notification settings - Fork 3k
/
errors.ml
3494 lines (2894 loc) · 127 KB
/
errors.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.
*
*)
open Hh_core
open Utils
open Reordered_argument_collections
open String_utils
type error_code = int
(* We use `Pos.t message` on the server and convert to `Pos.absolute message`
* before sending it to the client *)
type 'a message = 'a * string
type error_phase = Init | Parsing | Naming | Decl | Typing
type error_severity = Warning | Error
(* For callers that don't care about tracking error origins *)
let default_context = (Relative_path.default, Typing)
(* The file and phase of analysis being currently performed *)
let current_context: (Relative_path.t * error_phase) ref = ref default_context
let allow_errors_in_default_path = ref true
module PhaseMap = Reordered_argument_map(MyMap.Make(struct
type t = error_phase
let rank = function
| Init -> 0
| Parsing -> 1
| Naming -> 2
| Decl -> 3
| Typing -> 4
let compare x y = (rank x) - (rank y)
end))
(* Results of single file analysis. *)
type 'a file_t = 'a list PhaseMap.t
(* Results of multi-file analysis. *)
type 'a files_t = ('a file_t) Relative_path.Map.t
let files_t_fold v ~f ~init =
Relative_path.Map.fold v ~init ~f:begin fun path v acc ->
PhaseMap.fold v ~init:acc ~f:begin fun phase v acc ->
f path phase v acc
end
end
let files_t_map v ~f =
Relative_path.Map.map v ~f:begin fun v ->
PhaseMap.map v ~f
end
let files_t_merge ~f x y =
(* Using fold instead of merge to make the runtime proportional to the size
* of first argument (like List.rev_append ) *)
Relative_path.Map.fold x ~init:y ~f:begin fun k x acc ->
let y = Option.value (Relative_path.Map.get y k) ~default:PhaseMap.empty in
Relative_path.Map.add acc k (
PhaseMap.merge x y ~f:(fun phase x y -> f phase k x y)
)
end
let files_t_to_list x =
files_t_fold x ~f:(fun _ _ x acc -> List.rev_append x acc) ~init:[] |>
List.rev
let list_to_files_t = function
| [] -> Relative_path.Map.empty
| x ->
(* Values constructed here should not be used with incremental mode.
* See assert in incremental_update. *)
Relative_path.Map.singleton Relative_path.default
(PhaseMap.singleton Typing x)
let get_code_severity code =
if code = Error_codes.Init.err_code Error_codes.Init.ForwardCompatibilityNotCurrent
then Warning
else Error
module Common = struct
(* Get most recently-ish added error. *)
let get_last error_map =
(* If this map has more than one element, we pick an arbitrary file. Because
* of that, we might not end up with the most recent error and generate a
* less-specific error message. This should be rare. *)
match Relative_path.Map.max_binding error_map with
| None -> None
| Some (_, phase_map) -> begin
let error_list = PhaseMap.max_binding phase_map
|> Option.value_map ~f:snd ~default:[]
in
match List.rev error_list with
| [] -> None
| e::_ -> Some e
end
let try_with_result f1 f2 error_map accumulate_errors =
let error_map_copy = !error_map in
let accumulate_errors_copy = !accumulate_errors in
error_map := Relative_path.Map.empty;
accumulate_errors := true;
let result, errors = Utils.try_finally
~f:begin fun () ->
let result = f1 () in
result, !error_map
end
~finally:begin fun () ->
error_map := error_map_copy;
accumulate_errors := accumulate_errors_copy;
end
in
match get_last errors with
| None -> result
| Some l -> f2 result l
let do_ f error_map accumulate_errors applied_fixmes =
let error_map_copy = !error_map in
let accumulate_errors_copy = !accumulate_errors in
let applied_fixmes_copy = !applied_fixmes in
error_map := Relative_path.Map.empty;
applied_fixmes := Relative_path.Map.empty;
accumulate_errors := true;
let result, out_errors, out_applied_fixmes = Utils.try_finally
~f:begin fun () ->
let result = f () in
result, !error_map, !applied_fixmes
end
~finally:begin fun () ->
error_map := error_map_copy;
applied_fixmes := applied_fixmes_copy;
accumulate_errors := accumulate_errors_copy;
end
in
let out_errors = files_t_map ~f:(List.rev) out_errors in
(out_errors, out_applied_fixmes), result
let run_in_context path phase f =
let context_copy = !current_context in
current_context := (path, phase);
Utils.try_finally ~f ~finally:begin fun () ->
current_context := context_copy;
end
(* Log important data if lazy_decl triggers a crash *)
let lazy_decl_error_logging error error_map to_absolute to_string =
let error_list = files_t_to_list !error_map in
(* Print the current error list, which should be empty *)
Printf.eprintf "%s" "Error list(should be empty):\n";
List.iter error_list ~f:(fun err ->
let msg = err |> to_absolute |> to_string in Printf.eprintf "%s\n" msg);
Printf.eprintf "%s" "Offending error:\n";
Printf.eprintf "%s" error;
(* Print out a larger stack trace *)
Printf.eprintf "%s" "Callstack:\n";
Printf.eprintf "%s" (Printexc.raw_backtrace_to_string
(Printexc.get_callstack 500));
(* Exit with special error code so we can see the log after *)
Exit_status.exit Exit_status.Lazy_decl_bug
(*****************************************************************************)
(* Error code printing. *)
(*****************************************************************************)
let error_kind error_code =
match error_code / 1000 with
| 1 -> "Parsing"
| 2 -> "Naming"
| 3 -> "NastCheck"
| 4 -> "Typing"
| 5 -> "Lint"
| 8 -> "Init"
| _ -> "Other"
let error_code_to_string error_code =
let error_kind = error_kind error_code in
let error_number = Printf.sprintf "%04d" error_code in
error_kind^"["^error_number^"]"
let phase_to_string (phase: error_phase) : string =
match phase with
| Init -> "Init"
| Parsing -> "Parsing"
| Naming -> "Naming"
| Decl -> "Decl"
| Typing -> "Typing"
let sort get_pos err =
List.sort ~cmp:begin fun x y ->
Pos.compare (get_pos x) (get_pos y)
end err
|> List.remove_consecutive_duplicates ~equal:(=)
let get_sorted_error_list get_pos (err,_) =
sort get_pos (files_t_to_list err)
(* Getters and setter for passed-in map, based on current context *)
let get_current_file_t file_t_map =
let current_file = fst !current_context in
Relative_path.Map.get file_t_map current_file |>
Option.value ~default:PhaseMap.empty
let get_current_list file_t_map =
let current_phase = snd !current_context in
get_current_file_t file_t_map |> fun x ->
PhaseMap.get x current_phase |>
Option.value ~default:[]
let set_current_list file_t_map new_list =
let current_file, current_phase = !current_context in
file_t_map := Relative_path.Map.add
!file_t_map
current_file
(PhaseMap.add
(get_current_file_t !file_t_map)
current_phase
new_list
)
end
(** The mode abstracts away the underlying errors type so errors can be
* stored either with backtraces (TracingErrors) or without
* (NonTracingErrors). *)
module type Errors_modes = sig
type 'a error_
type error = Pos.t error_
type applied_fixme = Pos.t * int
val applied_fixmes: applied_fixme files_t ref
val error_map: error files_t ref
val try_with_result: (unit -> 'a) -> ('a -> error -> 'a) -> 'a
val do_: (unit -> 'a) -> (error files_t * applied_fixme files_t) * 'a
val do_with_context: Relative_path.t -> error_phase ->
(unit -> 'a) -> (error files_t * applied_fixme files_t) * 'a
val run_in_context: Relative_path.t -> error_phase -> (unit -> 'a) -> 'a
val run_in_decl_mode: Relative_path.t -> (unit -> 'a) -> 'a
val add_error: error -> unit
val make_error: error_code -> (Pos.t message) list -> error
val get_code: 'a error_ -> error_code
val get_pos: error -> Pos.t
val get_severity: 'a error_ -> error_severity
val to_list: 'a error_ -> 'a message list
val to_absolute : error -> Pos.absolute error_
val to_string : ?indent:bool -> Pos.absolute error_ -> string
val get_sorted_error_list: error files_t * applied_fixme files_t -> error list
val sort: error list -> error list
val currently_has_errors : unit -> bool
end
(** Errors don't have backtraces embedded. *)
module NonTracingErrors: Errors_modes = struct
type 'a error_ = error_code * 'a message list
type error = Pos.t error_
type applied_fixme = Pos.t * int
let applied_fixmes: applied_fixme files_t ref = ref Relative_path.Map.empty
let (error_map: error files_t ref) = ref Relative_path.Map.empty
let accumulate_errors = ref false
(* Some filename when declaring *)
let in_lazy_decl = ref None
let try_with_result f1 f2 =
Common.try_with_result f1 f2 error_map accumulate_errors
let do_ f =
Common.do_ f error_map accumulate_errors applied_fixmes
let do_with_context path phase f =
Common.run_in_context path phase (fun () -> do_ f)
let run_in_context = Common.run_in_context
(* Turn on lazy decl mode for the duration of the closure.
This runs without returning the original state,
since we collect it later in do_with_lazy_decls_
*)
let run_in_decl_mode filename f =
let old_in_lazy_decl = !in_lazy_decl in
in_lazy_decl := Some filename;
Utils.try_finally ~f ~finally:begin fun () ->
in_lazy_decl := old_in_lazy_decl;
end
and make_error code (x: (Pos.t * string) list) = ((code, x): error)
(*****************************************************************************)
(* Accessors. *)
(*****************************************************************************)
and get_code (error: 'a error_) = ((fst error): error_code)
let get_pos (error : error) = fst (List.hd_exn (snd error))
let get_severity (error: 'a error_) = get_code_severity (get_code error)
let to_list (error : 'a error_) = snd error
let to_absolute error =
let code, msg_l = (get_code error), (to_list error) in
let msg_l = List.map msg_l (fun (p, s) -> Pos.to_absolute p, s) in
code, msg_l
let to_string ?(indent=false) (error : Pos.absolute error_) : string =
let error_code, msgl = (get_code error), (to_list error) in
let buf = Buffer.create 50 in
(match msgl with
| [] -> assert false
| (pos1, msg1) :: rest_of_error ->
Buffer.add_string buf begin
let error_code = Common.error_code_to_string error_code in
Printf.sprintf "%s\n%s (%s)\n"
(Pos.string pos1) msg1 error_code
end;
let indentstr = if indent then " " else "" in
List.iter rest_of_error begin fun (p, w) ->
let msg = Printf.sprintf "%s%s\n%s%s\n"
indentstr (Pos.string p) indentstr w in
Buffer.add_string buf msg
end
);
Buffer.contents buf
let sort = Common.sort get_pos
let get_sorted_error_list = Common.get_sorted_error_list get_pos
let add_error error =
if !accumulate_errors then
let () = match !current_context with
| (path, _) when path = Relative_path.default &&
(not !allow_errors_in_default_path) ->
Hh_logger.log "WARNING: adding an error in default path\n%s\n"
(Printexc.raw_backtrace_to_string (Printexc.get_callstack 100))
| _ -> ()
in
(* Cheap test to avoid duplicating most recent error *)
let error_list = Common.get_current_list !error_map in
match error_list with
| old_error :: _ when error = old_error -> ()
| _ -> Common.set_current_list error_map (error :: error_list)
else
(* We have an error, but haven't handled it in any way *)
let msg = error |> to_absolute |> to_string in
match !in_lazy_decl with
| Some _ ->
Common.lazy_decl_error_logging msg error_map to_absolute to_string
| None -> assert_false_log_backtrace (Some msg)
let currently_has_errors () =
Common.get_current_list !error_map <> []
end
(** Errors with backtraces embedded. They are revealed with to_string. *)
module TracingErrors: Errors_modes = struct
type 'a error_ = (Printexc.raw_backtrace * error_code * 'a message list)
type error = Pos.t error_
type applied_fixme = Pos.t * int
let applied_fixmes: applied_fixme files_t ref = ref Relative_path.Map.empty
let (error_map: error files_t ref) = ref Relative_path.Map.empty
let accumulate_errors = ref false
let in_lazy_decl = ref None
let try_with_result f1 f2 =
Common.try_with_result f1 f2 error_map accumulate_errors
let do_ f =
Common.do_ f error_map accumulate_errors applied_fixmes
let run_in_context = Common.run_in_context
let do_with_context path phase f =
run_in_context path phase (fun () -> do_ f)
(* Turn on lazy decl mode for the duration of the closure.
This runs without returning the original state,
since we collect it later in do_with_lazy_decls_
*)
let run_in_decl_mode filename f =
in_lazy_decl := Some filename;
Utils.try_finally ~f ~finally:begin fun () ->
in_lazy_decl := None;
end
let make_error code (x: (Pos.t * string) list) =
let bt = Printexc.get_callstack 25 in
((bt, code, x): error)
let get_code ((_, c, _): 'a error_) = c
let get_pos ((_, _, msg_l): error) =
fst (List.hd_exn msg_l)
let get_severity (error: 'a error_) = get_code_severity (get_code error)
let get_bt ((bt, _, _): 'a error_) = bt
let to_list ((_, _, l): 'a error_) = l
let to_absolute (error: error) =
let bt, code, msg_l = (get_bt error), (get_code error), (to_list error) in
let msg_l = List.map msg_l (fun (p, s) -> Pos.to_absolute p, s) in
bt, code, msg_l
(** TODO: Much of this is copy-pasta. *)
let to_string ?(indent=false) (error : Pos.absolute error_) : string =
let bt, error_code, msgl = (get_bt error),
(get_code error), (to_list error) in
let buf = Buffer.create 50 in
(match msgl with
| [] -> assert false
| (pos1, msg1) :: rest_of_error ->
Buffer.add_string buf begin
let error_code = Common.error_code_to_string error_code in
Printf.sprintf "%s\n%s%s (%s)\n"
(Pos.string pos1) (Printexc.raw_backtrace_to_string bt)
msg1 error_code
end;
let indentstr = if indent then " " else "" in
List.iter rest_of_error begin fun (p, w) ->
let msg = Printf.sprintf "%s%s\n%s%s\n"
indentstr (Pos.string p) indentstr w in
Buffer.add_string buf msg
end
);
Buffer.contents buf
let add_error error =
if !accumulate_errors then
begin
let error_list = Common.get_current_list !error_map in
Common.set_current_list error_map (error :: error_list)
end
else
(* We have an error, but haven't handled it in any way *)
let msg = error |> to_absolute |> to_string in
match !in_lazy_decl with
| Some _ ->
Common.lazy_decl_error_logging msg error_map to_absolute to_string
| None -> assert_false_log_backtrace (Some msg)
let sort = Common.sort get_pos
let get_sorted_error_list = Common.get_sorted_error_list get_pos
let currently_has_errors () =
Common.get_current_list !error_map <> []
end
(** The Errors functor which produces the Errors module.
* Omitting gratuitous indentation. *)
module Errors_with_mode(M: Errors_modes) = struct
module Temporary = Error_codes.Temporary
module Parsing = Error_codes.Parsing
module Naming = Error_codes.Naming
module NastCheck = Error_codes.NastCheck
module Typing = Error_codes.Typing
module Init = Error_codes.Init
(*****************************************************************************)
(* Types *)
(*****************************************************************************)
type 'a error_ = 'a M.error_
type error = Pos.t error_
type applied_fixme = M.applied_fixme
type t = error files_t * applied_fixme files_t
type phase = error_phase = Init | Parsing | Naming | Decl | Typing
type severity = error_severity = Warning | Error
module type Error_category = sig
type t
val min : int
val max : int
val of_enum : int -> t option
val show : t -> string
val err_code : t -> int
end
(*****************************************************************************)
(* HH_FIXMEs hook *)
(*****************************************************************************)
let applied_fixmes = M.applied_fixmes
let error_map = M.error_map
let default_ignored_fixme_codes = ISet.of_list [
Typing.err_code Typing.InvalidIsAsExpressionHint;
]
let ignored_fixme_codes = ref default_ignored_fixme_codes
let set_allow_errors_in_default_path x = allow_errors_in_default_path := x
let is_ignored_code code = ISet.mem code !ignored_fixme_codes
let is_ignored_fixme code = is_ignored_code code
let (is_hh_fixme: (Pos.t -> error_code -> bool) ref) = ref (fun _ _ -> false)
let (get_hh_fixme_pos: (Pos.t -> error_code -> Pos.t option) ref) =
ref (fun _ _ -> None)
let add_ignored_fixme_code_error pos code =
if !is_hh_fixme pos code && is_ignored_code code then
let pos = Option.value (!get_hh_fixme_pos pos code) ~default:pos in
M.add_error (M.make_error code
[pos, Printf.sprintf "HH_FIXME cannot be used for error %d" code])
(*****************************************************************************)
(* Errors accumulator. *)
(*****************************************************************************)
let add_applied_fixme code pos =
if ServerLoadFlag.get_no_load () then
let applied_fixmes_list = Common.get_current_list !applied_fixmes in
Common.set_current_list applied_fixmes ((pos, code) :: applied_fixmes_list)
else ()
let rec add_error = M.add_error
and add code pos msg =
if not (is_ignored_fixme code) && !is_hh_fixme pos code
then add_applied_fixme code pos
else add_error (M.make_error code [pos, msg]);
add_ignored_fixme_code_error pos code
and add_list code pos_msg_l =
let pos = fst (List.hd_exn pos_msg_l) in
if not (is_ignored_fixme code) && !is_hh_fixme pos code
then add_applied_fixme code pos
else add_error (make_error code pos_msg_l);
add_ignored_fixme_code_error pos code;
and merge (err',fixmes') (err,fixmes) =
let append = fun _ _ x y ->
let x = Option.value x ~default: [] in
let y = Option.value y ~default: [] in
Some (List.rev_append x y)
in
files_t_merge ~f:append err' err,
files_t_merge ~f:append fixmes' fixmes
and merge_into_current errors =
let merged = merge errors (!error_map, !applied_fixmes) in
error_map := fst merged;
applied_fixmes := snd merged
and incremental_update :
(* Need to write out the entire ugly type to convince OCaml it's polymorphic
* and can update both error_map as well as applied_fixmes map *)
type a .
a files_t ->
a files_t ->
(* function folding over paths of rechecked files *)
(a files_t -> (Relative_path.t -> a files_t -> a files_t) -> a files_t) ->
phase ->
a files_t
= fun old new_ fold phase ->
(* Helper to remove acc[path][phase]. If acc[path] becomes empty afterwards,
* remove it too (i.e do not store empty maps or lists ever). *)
let remove path phase acc =
let new_phase_map = match Relative_path.Map.get acc path with
| None -> None
| Some phase_map ->
let new_phase_map = PhaseMap.remove phase_map phase in
if PhaseMap.is_empty new_phase_map then None else Some new_phase_map
in
match new_phase_map with
| None -> Relative_path.Map.remove acc path
| Some x -> Relative_path.Map.add acc path x
in
(* Replace old errors with new *)
let res = files_t_merge new_ old ~f:begin fun phase path new_ old ->
if path = Relative_path.default then begin
let phase = match phase with
| Init -> "Init"
| Parsing -> "Parsing"
| Naming -> "Naming"
| Decl -> "Decl"
| Typing -> "Typing"
in
Utils.assert_false_log_backtrace (Some(
"Default (untracked) error sources should not get into incremental " ^
"mode. There might be a missing call to Errors.do_with_context/" ^
"run_in_context somwhere or incorrectly used Errors.from_error_list." ^
"Phase: " ^ phase
))
end;
match new_ with
| Some new_ -> Some (List.rev new_)
| None -> old
end in
(* For files that were rechecked, but had no errors - remove them from maps *)
fold res begin fun path acc ->
let has_errors =
match Relative_path.Map.get new_ path with
| None -> false
| Some phase_map -> PhaseMap.mem phase_map phase
in
if has_errors then acc
else remove path phase acc
end
and incremental_update_set ~old ~new_ ~rechecked phase =
let fold = fun init g -> Relative_path.Set.fold ~f:begin fun path acc ->
g path acc
end ~init rechecked in
(incremental_update (fst old) (fst new_) fold phase),
(incremental_update (snd old) (snd new_) fold phase)
and incremental_update_map ~old ~new_ ~rechecked phase =
let fold = fun init g -> Relative_path.Map.fold ~f:begin fun path _ acc ->
g path acc
end ~init rechecked in
(incremental_update (fst old) (fst new_) fold phase),
(incremental_update (snd old) (snd new_) fold phase)
and empty = (Relative_path.Map.empty, Relative_path.Map.empty)
and is_empty (err, _fixmes) = Relative_path.Map.is_empty err
and count (err, _fixmes) = files_t_fold err ~f:(fun _ _ x acc -> acc + List.length x) ~init:0
and get_error_list (err, _fixmes) = files_t_to_list err
and get_applied_fixmes (_err, fixmes) = files_t_to_list fixmes
and from_error_list err = (list_to_files_t err, Relative_path.Map.empty)
(*****************************************************************************)
(* Accessors. (All methods delegated to the parameterized module.) *)
(*****************************************************************************)
and get_code = M.get_code
and get_pos = M.get_pos
and get_severity = M.get_severity
and to_list = M.to_list
and make_error = M.make_error
let sort = M.sort
let get_sorted_error_list = M.get_sorted_error_list
let iter_error_list f err = List.iter ~f:f (get_sorted_error_list err)
let fold_errors ?phase err ~init ~f =
match phase with
| None ->
files_t_fold (fst err)
~init
~f:begin fun source _ errors acc ->
List.fold_right errors ~init:acc ~f:(f source)
end
| Some phase ->
Relative_path.Map.fold (fst err) ~init ~f:begin fun source phases acc ->
match PhaseMap.get phases phase with
| None -> acc
| Some errors -> List.fold_right errors ~init:acc ~f:(f source)
end
let fold_errors_in ?phase err ~source ~init ~f =
Relative_path.Map.get (fst err) source |>
Option.value ~default:PhaseMap.empty |>
PhaseMap.fold ~init ~f:begin fun p errors acc ->
if phase <> None && phase <> Some p then acc
else List.fold_right errors ~init:acc ~f
end
let get_failed_files err phase =
files_t_fold (fst err)
~init:Relative_path.Set.empty
~f:begin fun source p _ acc ->
if phase <> p then acc else Relative_path.Set.add acc source
end
(*****************************************************************************)
(* Error code printing. *)
(*****************************************************************************)
let error_code_to_string = Common.error_code_to_string
let phase_to_string = Common.phase_to_string
let internal_error pos msg =
add 0 pos ("Internal error: "^msg)
let unimplemented_feature pos msg =
add 0 pos ("Feature not implemented: " ^ msg)
let experimental_feature pos msg =
add 0 pos ("Cannot use experimental feature: " ^ msg)
(*****************************************************************************)
(* Temporary errors. *)
(*****************************************************************************)
let darray_not_supported pos =
add Temporary.darray_not_supported pos "darray is not supported."
let varray_not_supported pos =
add Temporary.varray_not_supported pos "varray is not supported."
let varray_or_darray_not_supported pos =
add
Temporary.varray_or_darray_not_supported
pos
"varray_or_darray is not supported."
let nonnull_not_supported pos =
add Temporary.nonnull_not_supported pos "nonnull is not supported."
(*****************************************************************************)
(* Parsing errors. *)
(*****************************************************************************)
let fixme_format pos =
add (Parsing.err_code Parsing.FixmeFormat) pos
"HH_FIXME wrong format, expected '/* HH_FIXME[ERROR_NUMBER] */'"
let unexpected_eof pos =
add (Parsing.err_code Parsing.UnexpectedEof) pos "Unexpected end of file"
let unterminated_comment pos =
add (Parsing.err_code Parsing.UnterminatedComment) pos "unterminated comment"
let unterminated_xhp_comment pos =
add (Parsing.err_code Parsing.UnterminatedXhpComment) pos "unterminated xhp comment"
let parsing_error (p, msg) =
add (Parsing.err_code Parsing.ParsingError) p msg
(*****************************************************************************)
(* Naming errors *)
(*****************************************************************************)
let trait_interface_constructor_promo pos =
add (Naming.err_code Naming.TraitInterfaceConstructorPromo) pos
"Constructor parameter promotion not allowed on traits or interfaces"
let typeparam_alok (pos, x) =
add (Naming.err_code Naming.TypeparamAlok) pos (
"You probably forgot to bind this type parameter right?\nAdd <"^x^
"> somewhere (after the function name definition, \
or after the class name)\nExamples: "^"function foo<T> or class A<T>")
let unexpected_arrow pos cname =
add (Naming.err_code Naming.UnexpectedArrow) pos (
"Keys may not be specified for "^cname^" initialization"
)
let missing_arrow pos cname =
add (Naming.err_code Naming.MissingArrow) pos (
"Keys must be specified for "^cname^" initialization"
)
let disallowed_xhp_type pos name =
add (Naming.err_code Naming.DisallowedXhpType) pos (
name^" is not a valid type. Use :xhp or XHPChild."
)
let name_already_bound name pos1 pos2 =
let name = Utils.strip_ns name in
add_list (Naming.err_code Naming.NameAlreadyBound) [
pos1, "Name already bound: "^name;
pos2, "Previous definition is here"
]
let name_is_reserved name pos =
let name = Utils.strip_all_ns name in
add (Naming.err_code Naming.NameIsReserved) pos (
name^" cannot be used as it is reserved."
)
let dollardollar_unused pos =
add (Naming.err_code Naming.DollardollarUnused) pos ("This expression does not contain a "^
"usage of the special pipe variable. Did you forget to use the ($$) "^
"variable?")
let method_name_already_bound pos name =
add (Naming.err_code Naming.MethodNameAlreadyBound) pos (
"Method name already bound: "^name
)
let reference_in_rx pos =
add (Naming.err_code Naming.ReferenceInRx) pos (
"References are not allowed in reactive code."
)
let error_name_already_bound name name_prev p p_prev =
let name = Utils.strip_ns name in
let name_prev = Utils.strip_ns name_prev in
let errs = [
p, "Name already bound: "^name;
p_prev, (if String.compare name name_prev == 0
then "Previous definition is here"
else "Previous definition "^name_prev^" differs only in capitalization ")
] in
let hhi_msg =
"This appears to be defined in an hhi file included in your project "^
"root. The hhi files for the standard library are now a part of the "^
"typechecker and must be removed from your project. Typically, you can "^
"do this by deleting the \"hhi\" directory you copied into your "^
"project when first starting with Hack." in
let errs =
if (Relative_path.prefix (Pos.filename p)) = Relative_path.Hhi
then errs @ [p_prev, hhi_msg]
else if (Relative_path.prefix (Pos.filename p_prev)) = Relative_path.Hhi
then errs @ [p, hhi_msg]
else errs in
add_list (Naming.err_code Naming.ErrorNameAlreadyBound) errs
let error_class_attribute_already_bound name name_prev p p_prev =
let name = Utils.strip_ns name in
let name_prev = Utils.strip_ns name_prev in
let errs = [
p, "A class and an attribute class cannot share the same name. Conflicting class: "^name;
p_prev, "Previous definition: "^name_prev
] in
add_list (Naming.err_code Naming.AttributeClassNameConflict) errs
let unbound_name pos name kind =
let kind_str = match kind with
| `cls -> "an object type"
| `func -> "a global function"
| `const -> "a global constant"
in
add (Naming.err_code Naming.UnboundName) pos
("Unbound name: "^(strip_ns name)^" ("^kind_str^")")
let different_scope pos var_name pos' =
add_list (Naming.err_code Naming.DifferentScope) [
pos, ("The variable "^ var_name ^" is defined");
pos', ("But in a different scope")
]
let undefined pos var_name =
add (Naming.err_code Naming.Undefined) pos ("Undefined variable: "^var_name)
let this_reserved pos =
add (Naming.err_code Naming.ThisReserved) pos
"The type parameter \"this\" is reserved"
let start_with_T pos =
add (Naming.err_code Naming.StartWith_T) pos
"Please make your type parameter start with the letter T (capital)"
let already_bound pos name =
add (Naming.err_code Naming.NameAlreadyBound) pos ("Argument already bound: "^name)
let unexpected_typedef pos def_pos =
add_list (Naming.err_code Naming.UnexpectedTypedef) [
pos, "Unexpected typedef";
def_pos, "Definition is here";
]
let fd_name_already_bound pos =
add (Naming.err_code Naming.FdNameAlreadyBound) pos
"Field name already bound"
let primitive_toplevel pos =
add (Naming.err_code Naming.PrimitiveToplevel) pos (
"Primitive type annotations are always available and may no \
longer be referred to in the toplevel namespace."
)
let primitive_invalid_alias pos used valid =
add (Naming.err_code Naming.PrimitiveInvalidAlias) pos
("Invalid Hack type. Using '"^used^"' in Hack is considered \
an error. Use '"^valid^"' instead, to keep the codebase \
consistent.")
let dynamic_new_in_strict_mode pos =
add (Naming.err_code Naming.DynamicNewInStrictMode) pos
"Cannot use dynamic new in strict mode"
let invalid_type_access_root (pos, id) =
add (Naming.err_code Naming.InvalidTypeAccessRoot) pos
(id^" must be an identifier for a class, \"self\", or \"this\"")
let duplicate_user_attribute (pos, name) existing_attr_pos =
add_list (Naming.err_code Naming.DuplicateUserAttribute) [
pos, "You cannot reuse the attribute "^name;
existing_attr_pos, name^" was already used here";
]
let misplaced_rx_of_scope pos =
add (Naming.err_code Naming.MisplacedRxOfScope) pos (
"<<__RxOfScope>> attribute is only allowed on lambdas."
)
let rx_of_scope_and_explicit_rx pos =
add (Naming.err_code Naming.RxOfScopeAndExplicitRx) pos (
"<<__RxOfScope>> attribute cannot be used with explicit reactivity annotations."
)
let unbound_attribute_name pos name =
let reason = if (string_starts_with name "__")
then "starts with __ but is not a standard attribute"
else "does not have a class and is not listed in .hhconfig"
in add (Naming.err_code Naming.UnboundName) pos
("Unrecognized user attribute: "^(Utils.strip_ns name)^" "^reason)
let this_no_argument pos =
add (Naming.err_code Naming.ThisNoArgument) pos "\"this\" expects no arguments"
let void_cast pos =
add (Naming.err_code Naming.VoidCast) pos "Cannot cast to void."
let unset_cast pos =
add (Naming.err_code Naming.UnsetCast) pos "Don't use (unset), just assign null!"
let object_cast pos cls_opt =
let msg1 = "Object casts are unsupported." in
let msg2 =
match cls_opt with
| Some c ->
" Try 'if ($var instanceof "^c^")' or 'invariant($var instanceof "^c^", ...)'."
| None -> ""
in
add (Naming.err_code Naming.ObjectCast) pos (msg1 ^ msg2)
let this_hint_outside_class pos =
add (Naming.err_code Naming.ThisHintOutsideClass) pos
"Cannot use \"this\" outside of a class"
let this_type_forbidden pos =
add (Naming.err_code Naming.ThisMustBeReturn) pos
"The type \"this\" cannot be used as a constraint on a class' generic, \
or as the type of a static member variable"
let nonstatic_property_with_lsb pos =
add (Naming.err_code Naming.NonstaticPropertyWithLSB) pos
"__LSB attribute may only be used on static properties"
let lowercase_this pos type_ =
add (Naming.err_code Naming.LowercaseThis) pos (
"Invalid Hack type \""^type_^"\". Use \"this\" instead"
)
let classname_param pos =
add (Naming.err_code Naming.ClassnameParam) pos
("Missing type parameter to classname; classname is entirely"
^" meaningless without one")
let invalid_instanceof pos =
add (Naming.err_code Naming.InvalidInstanceof) pos
"This instanceof has an invalid right operand. Only class identifiers, \
local variables, accesses of objects / classes / arrays, and function / \
method calls are allowed."
let tparam_with_tparam pos x =
add (Naming.err_code Naming.TparamWithTparam) pos (
Printf.sprintf "%s is a type parameter. Type parameters cannot \
themselves take type parameters (e.g. %s<int> doesn't make sense)" x x
)
let shadowed_type_param p pos name =
add_list (Naming.err_code Naming.ShadowedTypeParam) [
p, Printf.sprintf "You cannot re-bind the type parameter %s" name;
pos, Printf.sprintf "%s is already bound here" name
]
let missing_typehint pos =
add (Naming.err_code Naming.MissingTypehint) pos
"Please add a type hint"
let expected_variable pos =
add (Naming.err_code Naming.ExpectedVariable) pos
"Was expecting a variable name"
let clone_too_many_arguments pos =
add (Naming.err_code Naming.NamingTooManyArguments) pos
"__clone method cannot take arguments"
let naming_too_few_arguments pos =
add (Naming.err_code Naming.NamingTooFewArguments) pos
"Too few arguments"
let naming_too_many_arguments pos =
add (Naming.err_code Naming.NamingTooManyArguments) pos
"Too many arguments"
let expected_collection pos cn =
add (Naming.err_code Naming.ExpectedCollection) pos (
"Unexpected collection type " ^ (Utils.strip_ns cn)
)
let illegal_CLASS pos =
add (Naming.err_code Naming.IllegalClass) pos
"Using __CLASS__ outside a class or trait"
let illegal_TRAIT pos =
add (Naming.err_code Naming.IllegalTrait) pos
"Using __TRAIT__ outside a trait"
let dynamic_method_call pos =