2 * Copyright (c) 2015, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
12 (*****************************************************************************)
13 (* Types, constants *)
14 (*****************************************************************************)
16 type mode
= Ai
of Ai_options.t
20 extra_builtins
: string list
;
21 ai_options
: Ai_options.t
;
22 error_format
: Errors.format
;
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 *)
41 ( "hh_single_type_check_magic.hhi",
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 () {}"
52 ^
"namespace HH\\Lib\\Tuple{\n"
54 ^
"function from_async();\n"
58 (*****************************************************************************)
60 (*****************************************************************************)
64 Out_channel.output_string
oc str
;
68 let print_error format ?
(oc = stderr
) l
=
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
76 if use_canonical_filenames () then
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
106 Arg.String
(fun f
-> extra_builtins := f
:: !extra_builtins),
107 " HHI file to parse and declare" );
109 Arg.String
set_ai_options,
110 " Run the abstract interpreter (Zoncolan)" );
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",
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",
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
154 | (x
, Some
ai_options) -> (x
, ai_options)
157 let root = Path.make
"/" (* if none specified, we use this dummy *) in
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;
182 extra_builtins = !extra_builtins;
184 error_format = !error_format;
185 no_builtins = !no_builtins;
190 SharedMem.default_config
)
192 let parse_and_name ctx files_contents
=
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
198 Full_fidelity_ast.defensive_program
popt fn contents
201 let { Parser_return.ast; _
} = parsed_file in
202 if ParserOptions.deregister_php_stdlib
popt then
203 Nast.deregister_ignored_attributes
ast
207 Ast_provider.provide_ast_hint fn
ast Ast_provider.Full
;
208 { parsed_file with Parser_return.ast }))
211 Relative_path.Map.mapi
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
) =
228 comments
= Some comments
;
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
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
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
)
261 (naming_table_path
: string option) : unit =
262 Ident.track_names
:= true;
265 Relative_path.Map.empty
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
276 |> List.fold ~f
:add_file_content ~init
:[]
279 let magic_builtins = Array.append
magic_builtins extra_builtins in
280 (* Check that magic_builtin filenames are unique *)
282 let n_of_builtins = Array.length
magic_builtins in
283 let n_of_unique_builtins =
284 Array.to_list
magic_builtins
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
296 (Sys_utils.Touch_existing
{ follow_symlinks
= true })
298 Sys_utils.write_file ~
file file_contents
);
300 (* Take the builtins (file, contents) array and create relative paths *)
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)
312 if use_canonical_filenames () then
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
318 files |> List.map ~
f:(Relative_path.create
Relative_path.Dummy
)
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
335 Relative_path.Map.add acc ~key
:k ~data
:src
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 *)
345 Provider_context.empty_for_test
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
364 (Errors.get_sorted_error_list errors
)
368 ({ tcopt; _
} as opts
)
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 *)
389 if !Sys.interactive
then
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