PageRenderTime 41ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/hack/src/server/serverTypeCheck.ml

https://gitlab.com/0072016/0072016-PHP.LLC
OCaml | 296 lines | 168 code | 44 blank | 84 comment | 2 complexity | 85a0ac6c6eac718fec43016609108e42 MD5 | raw file
  1. (**
  2. * Copyright (c) 2014, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the "hack" directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. *
  9. *)
  10. (*****************************************************************************)
  11. (*****************************************************************************)
  12. open Utils
  13. open ServerEnv
  14. (*****************************************************************************)
  15. (* Debugging *)
  16. (*****************************************************************************)
  17. let print_defs prefix defs =
  18. List.iter begin fun (_, fname) ->
  19. Printf.printf " %s %s\n" prefix fname;
  20. end defs
  21. let print_fast_pos fast_pos =
  22. SMap.iter begin fun x (funs, classes) ->
  23. Printf.printf "File: %s\n" x;
  24. print_defs "Fun" funs;
  25. print_defs "Class" classes;
  26. end fast_pos;
  27. Printf.printf "\n";
  28. flush stdout;
  29. ()
  30. let print_fast fast =
  31. SMap.iter begin fun x (funs, classes) ->
  32. Printf.printf "File: %s\n" x;
  33. SSet.iter (Printf.printf " Fun %s\n") funs;
  34. SSet.iter (Printf.printf " Class %s\n") classes;
  35. end fast;
  36. Printf.printf "\n";
  37. flush stdout;
  38. ()
  39. (*****************************************************************************)
  40. (* Given a set of Ast.id list produce a SSet.t (got rid of the positions) *)
  41. (*****************************************************************************)
  42. let set_of_idl l =
  43. List.fold_left (fun acc (_, x) -> SSet.add x acc) SSet.empty l
  44. (*****************************************************************************)
  45. (* We want add all the declarations that were present in a file *before* the
  46. * current modification. The scenario:
  47. * File foo.php was defining the class A.
  48. * The user gets rid of class A (in foo.php)
  49. * In general, the type-checker determines what must be re-declared or
  50. * re-typechecked, by comparing the old and the new type-definitions.
  51. * That's why we are adding the 'old' definitions to the file.
  52. * In this case, the redecl phase (typing/typing_redecl_service.ml) is going
  53. * to compare the 'old' definition of A with the new one. It will realize that
  54. * the new one is missing, and go ahead and retype everything that depends
  55. * on A.
  56. * Without a call to add_old_decls, the class A wouldn't appear anywhere,
  57. * and we wouldn't realize that we have to re-check the types that depend
  58. * on A.
  59. *)
  60. (*****************************************************************************)
  61. let add_old_decls old_files_info fast =
  62. Relative_path.Map.fold begin fun filename info_names acc ->
  63. match Relative_path.Map.get filename old_files_info with
  64. | Some {FileInfo.consider_names_just_for_autoload = true; _}
  65. | None -> acc
  66. | Some old_info ->
  67. let old_info_names = FileInfo.simplify old_info in
  68. let info_names = FileInfo.merge_names old_info_names info_names in
  69. Relative_path.Map.add filename info_names acc
  70. end fast fast
  71. (*****************************************************************************)
  72. (* Reparsing helpers.
  73. * It's called reparse (as opposed to parse) because it retrieves the tree
  74. * from the datanodes where the Asts are stored in a serialized format.
  75. * Important: we never ever want to reparse a file that was already parsed.
  76. * If that was the case, an older version of the file would come and replace
  77. * a newer one (the one we just parsed). It would lead to very
  78. * subtle/terrible bugs.
  79. * This is why reparse takes a file->ast (fast). If we try to reparse a file
  80. * that we already parsed, the data is left unchanged.
  81. *)
  82. (*****************************************************************************)
  83. let reparse fast files_info additional_files =
  84. Relative_path.Set.fold begin fun x acc ->
  85. match Relative_path.Map.get x fast with
  86. | None ->
  87. (try
  88. let info = Relative_path.Map.find_unsafe x files_info in
  89. if info.FileInfo.consider_names_just_for_autoload then acc else
  90. let info_names = FileInfo.simplify info in
  91. Relative_path.Map.add x info_names acc
  92. with Not_found ->
  93. acc)
  94. | Some _ -> acc
  95. end additional_files fast
  96. (*****************************************************************************)
  97. (* Removes the names that were defined in the files *)
  98. (*****************************************************************************)
  99. let remove_decls env fast_parsed =
  100. let nenv = env.nenv in
  101. let nenv =
  102. Relative_path.Map.fold begin fun fn _ nenv ->
  103. match Relative_path.Map.get fn env.files_info with
  104. | Some {FileInfo.consider_names_just_for_autoload = true; _}
  105. | None -> nenv
  106. | Some {FileInfo.
  107. funs = funl;
  108. classes = classel;
  109. typedefs = typel;
  110. consts = constl;
  111. file_mode;
  112. comments;
  113. consider_names_just_for_autoload} ->
  114. let funs = set_of_idl funl in
  115. let classes = set_of_idl classel in
  116. let typedefs = set_of_idl typel in
  117. let consts = set_of_idl constl in
  118. let nenv = Naming.remove_decls nenv
  119. (funs, classes, typedefs, consts) in
  120. nenv
  121. end fast_parsed nenv
  122. in
  123. { env with nenv = nenv }
  124. (*****************************************************************************)
  125. (* Removes the files that failed *)
  126. (*****************************************************************************)
  127. let remove_failed fast failed =
  128. Relative_path.Set.fold Relative_path.Map.remove failed fast
  129. (*****************************************************************************)
  130. (* Parses the set of modified files *)
  131. (*****************************************************************************)
  132. let parsing genv env =
  133. Parser_heap.ParserHeap.remove_batch env.failed_parsing;
  134. Parser_heap.HH_FIXMES.remove_batch env.failed_parsing;
  135. HackSearchService.MasterApi.clear_shared_memory env.failed_parsing;
  136. SharedMem.collect();
  137. let get_next = Bucket.make (Relative_path.Set.elements env.failed_parsing) in
  138. Parsing_service.go genv.workers ~get_next
  139. (*****************************************************************************)
  140. (* At any given point in time, we want to know what each file defines.
  141. * The datastructure that maintains this information is called file_info.
  142. * This code updates the file information.
  143. *)
  144. (*****************************************************************************)
  145. let update_file_info env fast_parsed =
  146. Typing_deps.update_files fast_parsed;
  147. let files_info =
  148. Relative_path.Map.fold Relative_path.Map.add fast_parsed env.files_info in
  149. files_info
  150. (*****************************************************************************)
  151. (* Defining the global naming environment.
  152. * Defines an environment with the names of all the globals (classes/funs).
  153. *)
  154. (*****************************************************************************)
  155. let declare_names env files_info fast_parsed =
  156. let env = remove_decls env fast_parsed in
  157. let errorl, failed_naming, nenv =
  158. Relative_path.Map.fold
  159. Naming.ndecl_file fast_parsed ([], Relative_path.Set.empty, env.nenv) in
  160. let fast = remove_failed fast_parsed failed_naming in
  161. let fast = FileInfo.simplify_fast fast in
  162. let env = { env with nenv = nenv } in
  163. env, errorl, failed_naming, fast
  164. (*****************************************************************************)
  165. (* Function called after parsing, does nothing by default. *)
  166. (*****************************************************************************)
  167. let hook_after_parsing = ref (fun _ _ _ _ -> ())
  168. (*****************************************************************************)
  169. (* Where the action is! *)
  170. (*****************************************************************************)
  171. let type_check genv env =
  172. Printf.eprintf "******************************************\n";
  173. Hh_logger.log "Files to recompute: %d"
  174. (Relative_path.Set.cardinal env.failed_parsing);
  175. (* PARSING *)
  176. let start_t = Unix.gettimeofday() in
  177. let fast_parsed, errorl, failed_parsing =
  178. Hh_logger.measure "Parsing" begin fun () ->
  179. parsing genv env
  180. end in
  181. (* UPDATE FILE INFO *)
  182. let old_env = env in
  183. let updates = old_env.failed_parsing in
  184. let files_info = update_file_info env fast_parsed in
  185. (* BUILDING AUTOLOADMAP *)
  186. !hook_after_parsing genv old_env { env with files_info } updates;
  187. (* NAMING *)
  188. let env, errorl', failed_naming, fast =
  189. Hh_logger.measure "Naming" begin fun () ->
  190. let env, errorl', failed_naming, fast =
  191. declare_names env files_info fast_parsed in
  192. (* COMPUTES WHAT MUST BE REDECLARED *)
  193. let fast = reparse fast files_info env.failed_decl in
  194. let fast = add_old_decls env.files_info fast in
  195. env, errorl', failed_naming, fast
  196. end in
  197. let errorl = List.rev_append errorl' errorl in
  198. let to_redecl_phase2, to_recheck1 =
  199. Hh_logger.measure "Determining changes" begin fun () ->
  200. let _, _, to_redecl_phase2, to_recheck1 =
  201. Typing_redecl_service.redo_type_decl genv.workers env.nenv fast
  202. in
  203. let to_redecl_phase2 = Typing_deps.get_files to_redecl_phase2 in
  204. let to_recheck1 = Typing_deps.get_files to_recheck1 in
  205. to_redecl_phase2, to_recheck1
  206. end in
  207. let fast_redecl_phase2 = reparse fast files_info to_redecl_phase2 in
  208. (* DECLARING TYPES: Phase2 *)
  209. let errorl', failed_decl, to_recheck2 =
  210. Hh_logger.measure "Type-decl" begin fun () ->
  211. let errorl', failed_decl, _to_redecl2, to_recheck2 =
  212. Typing_redecl_service.redo_type_decl
  213. genv.workers env.nenv fast_redecl_phase2 in
  214. let to_recheck2 = Typing_deps.get_files to_recheck2 in
  215. errorl', failed_decl, to_recheck2
  216. end in
  217. let errorl = List.rev_append errorl' errorl in
  218. (* DECLARING TYPES: merging results of the 2 phases *)
  219. let fast =
  220. Relative_path.Map.fold Relative_path.Map.add fast fast_redecl_phase2 in
  221. let to_recheck = Relative_path.Set.union env.failed_decl to_redecl_phase2 in
  222. let to_recheck = Relative_path.Set.union to_recheck1 to_recheck in
  223. let to_recheck = Relative_path.Set.union to_recheck2 to_recheck in
  224. (* TYPE CHECKING *)
  225. let errorl', failed_check = Hh_logger.measure "Type-check" begin fun () ->
  226. let to_recheck = Relative_path.Set.union to_recheck env.failed_check in
  227. let fast = reparse fast files_info to_recheck in
  228. ServerCheckpoint.process_updates fast;
  229. Typing_check_service.go genv.workers env.nenv fast
  230. end in
  231. let errorl = List.rev (List.rev_append errorl' errorl) in
  232. Hh_logger.log "Total: %f\n%!" ((Unix.gettimeofday ()) -. start_t);
  233. let total_rechecked_count = Relative_path.Set.cardinal to_recheck in
  234. HackEventLogger.recheck_once_end start_t total_rechecked_count;
  235. (* Done, that's the new environment *)
  236. { files_info = files_info;
  237. nenv = env.nenv;
  238. errorl = errorl;
  239. failed_parsing = Relative_path.Set.union failed_naming failed_parsing;
  240. failed_decl = failed_decl;
  241. failed_check = failed_check;
  242. }
  243. (*****************************************************************************)
  244. (* Checks that the working directory is clean *)
  245. (*****************************************************************************)
  246. let check genv env =
  247. if !debug then begin
  248. Printf.printf "****************************************\n";
  249. Printf.printf "Start Check\n";
  250. flush stdout;
  251. end;
  252. type_check genv env