server_specific_files
[hiphop-php.git] / hphp / hack / src / hh_single_ai.ml
blobcaf62df2938c677163832d9d4f90bbdeee688447
1 (*
2 * Copyright (c) 2015, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
10 open Hh_prelude
12 (*****************************************************************************)
13 (* Types, constants *)
14 (*****************************************************************************)
16 type mode = Ai of Ai_options.t
18 type options = {
19 files: string list;
20 extra_builtins: string list;
21 ai_options: Ai_options.t;
22 error_format: Errors.format;
23 no_builtins: bool;
24 tcopt: GlobalOptions.t;
27 (** If the user passed --root, then all pathnames have to be canonicalized.
28 The fact of whether they passed --root is kind of stored inside Relative_path
29 global variables: the Relative_path.(path_of_prefix Root) is either "/"
30 if they failed to pass something, or the thing that they passed. *)
31 let use_canonical_filenames () =
32 not (String.equal "/" (Relative_path.path_of_prefix Relative_path.Root))
34 (* Canonical builtins from our hhi library *)
35 let hhi_builtins = Hhi.get_raw_hhi_contents ()
37 (* All of the stuff that hh_single_type_check relies on is sadly not contained
38 * in the hhi library, so we include a very small number of magic builtins *)
39 let magic_builtins =
41 ( "hh_single_type_check_magic.hhi",
42 "<?hh\n"
43 ^ "namespace {\n"
44 ^ "async function gena<Tk as arraykey, Tv>(
45 KeyedTraversable<Tk, Awaitable<Tv>> $awaitables,
46 ): Awaitable<darray<Tk, Tv>>;\n"
47 ^ "function hh_show(<<__AcceptDisposable>> $val) {}\n"
48 ^ "function hh_show_env() {}\n"
49 ^ "function hh_log_level($key, $level) {}\n"
50 ^ "function hh_force_solve () {}"
51 ^ "}\n"
52 ^ "namespace HH\\Lib\\Tuple{\n"
53 ^ "function gen();\n"
54 ^ "function from_async();\n"
55 ^ "}\n" );
58 (*****************************************************************************)
59 (* Helpers *)
60 (*****************************************************************************)
62 let die str =
63 let oc = stderr in
64 Out_channel.output_string oc str;
65 Out_channel.close oc;
66 exit 2
68 let print_error format ?(oc = stderr) l =
69 let formatter =
70 match format with
71 | Errors.Context -> (fun e -> Contextual_error_formatter.to_string e)
72 | Errors.Raw -> (fun e -> Errors.to_string e)
73 | Errors.Highlighted -> Highlighted_error_formatter.to_string
75 let absolute_errors =
76 if use_canonical_filenames () then
77 Errors.to_absolute l
78 else
79 Errors.to_absolute_for_test l
81 Out_channel.output_string oc (formatter absolute_errors)
83 let comma_string_to_iset (s : string) : ISet.t =
84 Str.split (Str.regexp ", *") s |> List.map ~f:int_of_string |> ISet.of_list
86 let parse_options () =
87 let fn_ref = ref [] in
88 let extra_builtins = ref [] in
89 let usage = Printf.sprintf "Usage: %s filename\n" Sys.argv.(0) in
90 let no_builtins = ref false in
91 let ai_options = ref None in
92 let set_ai_options x =
93 ai_options := Some (Ai_options.prepare ~server:false x)
95 let error_format = ref Errors.Highlighted in
96 let check_xhp_attribute = ref false in
97 let disable_xhp_element_mangling = ref false in
98 let disable_xhp_children_declarations = ref false in
99 let enable_xhp_class_modifier = ref false in
100 let allowed_fixme_codes_strict = ref None in
101 let allowed_decl_fixme_codes = ref None in
102 let enable_enum_supertyping = ref false in
103 let options =
105 ( "--extra-builtin",
106 Arg.String (fun f -> extra_builtins := f :: !extra_builtins),
107 " HHI file to parse and declare" );
108 ( "--ai",
109 Arg.String set_ai_options,
110 " Run the abstract interpreter (Zoncolan)" );
111 ( "--error-format",
112 Arg.String
113 (fun s ->
114 match s with
115 | "raw" -> error_format := Errors.Raw
116 | "context" -> error_format := Errors.Context
117 | "highlighted" -> error_format := Errors.Highlighted
118 | _ -> print_string "Warning: unrecognized error format.\n"),
119 "<raw|context|highlighted> Error formatting style" );
120 ( "--check-xhp-attribute",
121 Arg.Set check_xhp_attribute,
122 " Typechecks xhp required attributes" );
123 ( "--disable-xhp-element-mangling",
124 Arg.Set disable_xhp_element_mangling,
125 "Disable mangling of XHP elements :foo. That is, :foo:bar is now \\foo\\bar, not xhp_foo__bar"
127 ( "--disable-xhp-children-declarations",
128 Arg.Set disable_xhp_children_declarations,
129 "Disable XHP children declarations, e.g. children (foo, bar+)" );
130 ( "--enable-xhp-class-modifier",
131 Arg.Set enable_xhp_class_modifier,
132 "Enable the XHP class modifier, xhp class name {} will define an xhp class."
134 ( "--allowed-fixme-codes-strict",
135 Arg.String
136 (fun s -> allowed_fixme_codes_strict := Some (comma_string_to_iset s)),
137 "List of fixmes that are allowed in strict mode." );
138 ( "--allowed-decl-fixme-codes",
139 Arg.String
140 (fun s -> allowed_decl_fixme_codes := Some (comma_string_to_iset s)),
141 "List of fixmes that are allowed in declarations." );
142 ( "--enable-enum-supertyping",
143 Arg.Set enable_enum_supertyping,
144 "Enable the enum supertyping extension." );
147 let options = Arg.align ~limit:25 options in
148 Arg.parse options (fun fn -> fn_ref := fn :: !fn_ref) usage;
149 let (fns, ai_options) =
150 match (!fn_ref, !ai_options) with
151 | ([], _)
152 | (_, None) ->
153 die usage
154 | (x, Some ai_options) -> (x, ai_options)
157 let root = Path.make "/" (* if none specified, we use this dummy *) in
158 let tcopt =
159 GlobalOptions.make
160 ?po_disable_array_typehint:(Some false)
161 ~allowed_fixme_codes_strict:
162 (Option.value !allowed_fixme_codes_strict ~default:ISet.empty)
163 ~tco_check_xhp_attribute:!check_xhp_attribute
164 ~po_disable_xhp_element_mangling:!disable_xhp_element_mangling
165 ~po_disable_xhp_children_declarations:!disable_xhp_children_declarations
166 ~po_enable_xhp_class_modifier:!enable_xhp_class_modifier
167 ~po_allowed_decl_fixme_codes:
168 (Option.value !allowed_decl_fixme_codes ~default:ISet.empty)
169 ~po_enable_enum_supertyping:!enable_enum_supertyping
172 Errors.allowed_fixme_codes_strict :=
173 GlobalOptions.allowed_fixme_codes_strict tcopt;
174 Errors.allowed_fixme_codes_partial :=
175 GlobalOptions.allowed_fixme_codes_partial tcopt;
176 Errors.codes_not_raised_partial :=
177 GlobalOptions.codes_not_raised_partial tcopt;
178 Errors.report_pos_from_reason :=
179 GlobalOptions.tco_report_pos_from_reason tcopt;
181 files = fns;
182 extra_builtins = !extra_builtins;
183 ai_options;
184 error_format = !error_format;
185 no_builtins = !no_builtins;
186 tcopt;
188 root,
189 None,
190 SharedMem.default_config )
192 let parse_and_name ctx files_contents =
193 let parsed_files =
194 Relative_path.Map.mapi files_contents ~f:(fun fn contents ->
195 Errors.run_in_context fn Errors.Parsing (fun () ->
196 let popt = Provider_context.get_tcopt ctx in
197 let parsed_file =
198 Full_fidelity_ast.defensive_program popt fn contents
200 let ast =
201 let { Parser_return.ast; _ } = parsed_file in
202 if ParserOptions.deregister_php_stdlib popt then
203 Nast.deregister_ignored_attributes ast
204 else
207 Ast_provider.provide_ast_hint fn ast Ast_provider.Full;
208 { parsed_file with Parser_return.ast }))
210 let files_info =
211 Relative_path.Map.mapi
213 begin
214 fun _fn parsed_file ->
215 let { Parser_return.file_mode; comments; ast; _ } = parsed_file in
216 (* If the feature is turned on, deregister functions with attribute
217 __PHPStdLib. This does it for all functions, not just hhi files *)
218 let (funs, classes, record_defs, typedefs, consts) =
219 Nast.get_defs ast
222 FileInfo.file_mode;
223 funs;
224 classes;
225 record_defs;
226 typedefs;
227 consts;
228 comments = Some comments;
229 hash = None;
232 parsed_files
234 Relative_path.Map.iter files_info (fun fn fileinfo ->
235 let (errors, _failed_naming_fns) =
236 Naming_global.ndecl_file_error_if_already_bound ctx fn fileinfo
238 Errors.merge_into_current errors);
239 (parsed_files, files_info)
241 let parse_name_and_skip_decl ctx files_contents =
242 Errors.do_ (fun () ->
243 let (_parsed_files, files_info) = parse_and_name ctx files_contents in
244 files_info)
246 let handle_mode ai_options ctx files_info parse_errors error_format =
247 if not (List.is_empty parse_errors) then
248 List.iter ~f:(print_error error_format) parse_errors
249 else
250 (* No type check *)
251 Ai.do_ files_info ai_options ctx
253 (*****************************************************************************)
254 (* Main entry point *)
255 (*****************************************************************************)
257 let decl_and_run_mode
258 { files; extra_builtins; ai_options; error_format; no_builtins; tcopt }
259 (popt : TypecheckerOptions.t)
260 (hhi_root : Path.t)
261 (naming_table_path : string option) : unit =
262 Ident.track_names := true;
263 let builtins =
264 if no_builtins then
265 Relative_path.Map.empty
266 else
267 let extra_builtins =
268 let add_file_content map filename =
269 Relative_path.create Relative_path.Dummy filename
270 |> Multifile.file_to_file_list
271 |> List.map ~f:(fun (path, contents) ->
272 (Filename.basename (Relative_path.suffix path), contents))
273 |> List.unordered_append map
275 extra_builtins
276 |> List.fold ~f:add_file_content ~init:[]
277 |> Array.of_list
279 let magic_builtins = Array.append magic_builtins extra_builtins in
280 (* Check that magic_builtin filenames are unique *)
281 let () =
282 let n_of_builtins = Array.length magic_builtins in
283 let n_of_unique_builtins =
284 Array.to_list magic_builtins
285 |> List.map ~f:fst
286 |> SSet.of_list
287 |> SSet.cardinal
289 if n_of_builtins <> n_of_unique_builtins then
290 die "Multiple magic builtins share the same base name.\n"
292 Array.iter magic_builtins ~f:(fun (file_name, file_contents) ->
293 let file_path = Path.concat hhi_root file_name in
294 let file = Path.to_string file_path in
295 Sys_utils.try_touch
296 (Sys_utils.Touch_existing { follow_symlinks = true })
297 file;
298 Sys_utils.write_file ~file file_contents);
300 (* Take the builtins (file, contents) array and create relative paths *)
301 Array.fold
302 (Array.append magic_builtins hhi_builtins)
303 ~init:Relative_path.Map.empty
304 ~f:(fun acc (f, src) ->
305 let f = Path.concat hhi_root f |> Path.to_string in
306 Relative_path.Map.add
308 ~key:(Relative_path.create Relative_path.Hhi f)
309 ~data:src)
311 let files =
312 if use_canonical_filenames () then
313 files
314 |> List.map ~f:Sys_utils.realpath
315 |> List.map ~f:(fun s -> Option.value_exn s)
316 |> List.map ~f:Relative_path.create_detect_prefix
317 else
318 files |> List.map ~f:(Relative_path.create Relative_path.Dummy)
320 let files_contents =
321 List.fold
322 files
323 ~f:(fun acc filename ->
324 let files_contents = Multifile.file_to_files filename in
325 Relative_path.Map.union acc files_contents)
326 ~init:Relative_path.Map.empty
328 (* Merge in builtins *)
329 let files_contents_with_builtins =
330 Relative_path.Map.fold
331 builtins
333 begin
334 fun k src acc ->
335 Relative_path.Map.add acc ~key:k ~data:src
337 ~init:files_contents
339 Relative_path.Map.iter files_contents ~f:(fun filename contents ->
340 File_provider.(provide_file filename (Disk contents)));
341 (* Don't declare all the filenames in batch_errors mode *)
342 let to_decl = files_contents_with_builtins in
343 (* TODO(hverr): Should we switch this to 64-bit *)
344 let ctx =
345 Provider_context.empty_for_test
346 ~popt
347 ~tcopt
348 ~deps_mode:Typing_deps_mode.SQLiteMode
350 (* We make the following call for the side-effect of updating ctx's "naming-table fallback"
351 so it will look in the sqlite database for names it doesn't know.
352 This function returns the forward naming table, but we don't care about that;
353 it's only needed for tools that process file changes, to know in the event
354 of a file-change which old symbols used to be defined in the file. *)
355 let _naming_table_for_root : Naming_table.t option =
356 Option.map naming_table_path ~f:(fun path ->
357 Naming_table.load_from_sqlite ctx path)
359 let (errors, files_info) = parse_name_and_skip_decl ctx to_decl in
360 handle_mode
361 ai_options
363 files_info
364 (Errors.get_sorted_error_list errors)
365 error_format
367 let main_hack
368 ({ tcopt; _ } as opts)
369 (root : Path.t)
370 (naming_table : string option)
371 (sharedmem_config : SharedMem.config) : unit =
372 (* TODO: We should have a per file config *)
373 Sys_utils.signal Sys.sigusr1 (Sys.Signal_handle Typing.debug_print_last_pos);
374 EventLogger.init_fake ();
376 let (_handle : SharedMem.handle) =
377 SharedMem.init ~num_workers:0 sharedmem_config
379 Tempfile.with_tempdir (fun hhi_root ->
380 Hhi.set_hhi_root_for_unit_test hhi_root;
381 Relative_path.set_path_prefix Relative_path.Root root;
382 Relative_path.set_path_prefix Relative_path.Hhi hhi_root;
383 Relative_path.set_path_prefix Relative_path.Tmp (Path.make "tmp");
384 decl_and_run_mode opts tcopt hhi_root naming_table;
385 TypingLogger.flush_buffers ())
387 (* command line driver *)
388 let () =
389 if !Sys.interactive then
391 else
392 (* On windows, setting 'binary mode' avoids to output CRLF on
393 stdout. The 'text mode' would not hurt the user in general, but
394 it breaks the testsuite where the output is compared to the
395 expected one (i.e. in given file without CRLF). *)
396 Out_channel.set_binary_mode stdout true;
397 let (options, root, naming_table, sharedmem_config) = parse_options () in
398 Unix.handle_unix_error main_hack options root naming_table sharedmem_config