-
Notifications
You must be signed in to change notification settings - Fork 464
/
Copy pathlocation.ml
296 lines (254 loc) · 11.6 KB
/
location.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
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy, projet Cristal, INRIA Rocquencourt *)
(* *)
(* Copyright 1996 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
open Lexing
type t = Warnings.loc = {
loc_start: position;
loc_end: position;
loc_ghost: bool;
}
let in_file name =
let loc = {pos_fname = name; pos_lnum = 1; pos_bol = 0; pos_cnum = -1} in
{loc_start = loc; loc_end = loc; loc_ghost = true}
let none = in_file "_none_"
let input_name = ref "_none_"
let set_input_name name = if name <> "" then input_name := name
(* Terminal info *)
(* Print the location in some way or another *)
open Format
let show_filename file = if file = "_none_" then !input_name else file
let print_filename ppf file = Format.fprintf ppf "%s" (show_filename file)
(* return file, line, char from the given position *)
let get_pos_info pos = (pos.pos_fname, pos.pos_lnum, pos.pos_cnum - pos.pos_bol)
let setup_colors () =
Misc.Color.setup !Clflags.color;
Code_frame.setup !Clflags.color
(* ocaml's reported line/col numbering is horrible and super error-prone
when being handled programmatically (or humanly for that matter. If you're
an ocaml contributor reading this: who the heck reads the character count
starting from the first erroring character?) *)
let normalize_range loc =
(* TODO: lots of the handlings here aren't needed anymore because the new
rescript syntax has much stronger invariants regarding positions, e.g.
no -1 *)
let _, start_line, start_char = get_pos_info loc.loc_start in
let _, end_line, end_char = get_pos_info loc.loc_end in
(* line is 1-indexed, column is 0-indexed. We convert all of them to 1-indexed to avoid confusion *)
(* start_char is inclusive, end_char is exclusive *)
if start_char == -1 || end_char == -1 then
(* happens sometimes. Syntax error for example *)
None
else if start_line = end_line && start_char >= end_char then
(* in some errors, starting char and ending char can be the same. But
since ending char was supposed to be exclusive, here it might end up
smaller than the starting char if we naively did start_char + 1 to
just the starting char and forget ending char *)
let same_char = start_char + 1 in
Some ((start_line, same_char), (end_line, same_char))
else
(* again: end_char is exclusive, so +1-1=0 *)
Some ((start_line, start_char + 1), (end_line, end_char))
let print_loc ppf (loc : t) =
setup_colors ();
let normalized_range = normalize_range loc in
let dim_loc ppf = function
| None -> ()
| Some ((start_line, start_line_start_char), (end_line, end_line_end_char))
->
if start_line = end_line then
if start_line_start_char = end_line_end_char then
fprintf ppf ":@{<dim>%i:%i@}" start_line start_line_start_char
else
fprintf ppf ":@{<dim>%i:%i-%i@}" start_line start_line_start_char
end_line_end_char
else
fprintf ppf ":@{<dim>%i:%i-%i:%i@}" start_line start_line_start_char
end_line end_line_end_char
in
fprintf ppf "@{<filename>%a@}%a" print_filename loc.loc_start.pos_fname
dim_loc normalized_range
let print ?(src = None) ~message_kind intro ppf (loc : t) =
(match message_kind with
| `warning -> fprintf ppf "@[@{<info>%s@}@]@," intro
| `warning_as_error ->
fprintf ppf "@[@{<error>%s@} (configured as error) @]@," intro
| `error -> fprintf ppf "@[@{<error>%s@}@]@," intro);
(* ocaml's reported line/col numbering is horrible and super error-prone
when being handled programmatically (or humanly for that matter. If you're
an ocaml contributor reading this: who the heck reads the character count
starting from the first erroring character?) *)
let file, start_line, start_char = get_pos_info loc.loc_start in
let _, end_line, end_char = get_pos_info loc.loc_end in
(* line is 1-indexed, column is 0-indexed. We convert all of them to 1-indexed to avoid confusion *)
(* start_char is inclusive, end_char is exclusive *)
let normalized_range =
(* TODO: lots of the handlings here aren't needed anymore because the new
rescript syntax has much stronger invariants regarding positions, e.g.
no -1 *)
if start_char == -1 || end_char == -1 then
(* happens sometimes. Syntax error for example *)
None
else if start_line = end_line && start_char >= end_char then
(* in some errors, starting char and ending char can be the same. But
since ending char was supposed to be exclusive, here it might end up
smaller than the starting char if we naively did start_char + 1 to
just the starting char and forget ending char *)
let same_char = start_char + 1 in
Some ((start_line, same_char), (end_line, same_char))
else
(* again: end_char is exclusive, so +1-1=0 *)
Some ((start_line, start_char + 1), (end_line, end_char))
in
fprintf ppf " @[%a@]@," print_loc loc;
match normalized_range with
| None -> ()
| Some _ -> (
try
(* Print a syntax error that is a list of Res_diagnostics.t.
Instead of reading file for every error, it uses the source that the parser already has. *)
let src =
match src with
| Some src -> src
| None -> Ext_io.load_file file
in
(* we're putting the line break `@,` here rather than above, because this
branch might not be reached (aka no inline file content display) so
we don't wanna end up with two line breaks in the the consequent *)
fprintf ppf "@,%s"
(Code_frame.print ~is_warning:(message_kind = `warning) ~src
~start_pos:loc.loc_start ~end_pos:loc.loc_end)
with
(* this might happen if the file is e.g. "", "_none_" or any of the fake file name placeholders.
we've already printed the location above, so nothing more to do here. *)
| Sys_error _ ->
())
let default_warning_printer loc ppf w =
match Warnings.report w with
| `Inactive -> ()
| `Active {Warnings.number = _; message = _; is_error; sub_locs = _} ->
setup_colors ();
let message_kind = if is_error then `warning_as_error else `warning in
Format.fprintf ppf "@[<v>@, %a@, %s@,@]@."
(print ~message_kind
("Warning number " ^ (Warnings.number w |> string_of_int)))
loc (Warnings.message w)
(* at this point, you can display sub_locs too, from e.g. https://github.com/ocaml/ocaml/commit/f6d53cc38f87c67fbf49109f5fb79a0334bab17a
but we won't bother for now *)
let warning_printer = ref default_warning_printer
let print_warning loc ppf w = !warning_printer loc ppf w
let formatter_for_warnings = ref err_formatter
let prerr_warning loc w = print_warning loc !formatter_for_warnings w
type 'a loc = {txt: 'a; loc: t}
let mkloc txt loc = {txt; loc}
let mknoloc txt = mkloc txt none
type error = {
loc: t;
msg: string;
sub: error list;
if_highlight: string; (* alternative message if locations are highlighted *)
}
let pp_ksprintf ?before k fmt =
let buf = Buffer.create 64 in
let ppf = Format.formatter_of_buffer buf in
Misc.Color.set_color_tag_handling ppf;
(match before with
| None -> ()
| Some f -> f ppf);
kfprintf
(fun _ ->
pp_print_flush ppf ();
let msg = Buffer.contents buf in
k msg)
ppf fmt
(* taken from https://github.com/rescript-lang/ocaml/blob/d4144647d1bf9bc7dc3aadc24c25a7efa3a67915/parsing/location.ml#L354 *)
(* Shift the formatter's offset by the length of the error prefix, which
is always added by the compiler after the message has been formatted *)
let print_phanton_error_prefix ppf =
(* modified from the original. We use only 2 indentations for error report
(see super_error_reporter above) *)
Format.pp_print_as ppf 2 ""
let errorf ?(loc = none) ?(sub = []) ?(if_highlight = "") fmt =
pp_ksprintf ~before:print_phanton_error_prefix
(fun msg -> {loc; msg; sub; if_highlight})
fmt
let error ?(loc = none) ?(sub = []) ?(if_highlight = "") msg =
{loc; msg; sub; if_highlight}
let error_of_exn : (exn -> error option) list ref = ref []
let register_error_of_exn f = error_of_exn := f :: !error_of_exn
exception Already_displayed_error = Warnings.Errors
let error_of_exn exn =
match exn with
| Already_displayed_error -> Some `Already_displayed
| _ ->
let rec loop = function
| [] -> None
| f :: rest -> (
match f exn with
| Some error -> Some (`Ok error)
| None -> loop rest)
in
loop !error_of_exn
(* taken from https://github.com/rescript-lang/ocaml/blob/d4144647d1bf9bc7dc3aadc24c25a7efa3a67915/parsing/location.ml#L380 *)
(* This is the error report entry point. We'll replace the default reporter with this one. *)
let rec default_error_reporter ?(src = None) ppf {loc; msg; sub} =
setup_colors ();
(* open a vertical box. Everything in our message is indented 2 spaces *)
(* If src is given, it will display a syntax error after parsing. *)
let intro =
match src with
| Some _ -> "Syntax error!"
| None -> "We've found a bug for you!"
in
Format.fprintf ppf "@[<v>@, %a@, %s@,@]"
(print ~src ~message_kind:`error intro)
loc msg;
List.iter (Format.fprintf ppf "@,@[%a@]" (default_error_reporter ~src)) sub
(* no need to flush here; location's report_exception (which uses this ultimately) flushes *)
let error_reporter = ref default_error_reporter
let report_error ?(src = None) ppf err = !error_reporter ~src ppf err
let error_of_printer loc print x = errorf ~loc "%a@?" print x
let error_of_printer_file print x =
error_of_printer (in_file !input_name) print x
let () =
register_error_of_exn (function
| Sys_error msg ->
Some (errorf ~loc:(in_file !input_name) "I/O error: %s" msg)
| Misc.HookExnWrapper {error = e; hook_name; hook_info = {Misc.sourcefile}}
->
let sub =
match error_of_exn e with
| None | Some `Already_displayed -> error (Printexc.to_string e)
| Some (`Ok err) -> err
in
Some (errorf ~loc:(in_file sourcefile) "In hook %S:" hook_name ~sub:[sub])
| _ -> None)
external reraise : exn -> 'a = "%reraise"
let rec report_exception_rec n ppf exn =
try
match error_of_exn exn with
| None -> reraise exn
| Some `Already_displayed -> ()
| Some (`Ok err) -> fprintf ppf "@[%a@]@." (report_error ~src:None) err
with exn when n > 0 -> report_exception_rec (n - 1) ppf exn
let report_exception ppf exn = report_exception_rec 5 ppf exn
exception Error of error
let () =
register_error_of_exn (function
| Error e -> Some e
| _ -> None)
let raise_errorf ?(loc = none) ?(sub = []) ?(if_highlight = "") =
pp_ksprintf ~before:print_phanton_error_prefix (fun msg ->
raise (Error {loc; msg; sub; if_highlight}))
let deprecated ?(def = none) ?(use = none) loc msg =
prerr_warning loc (Warnings.Deprecated (msg, def, use))