Naming_provider takes backend not ctx
[hiphop-php.git] / hphp / hack / src / naming / naming_global.ml
blob33aa7c69a865d0285b99ae9ed57a3158a883b1cc
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 (** Module "naming" a program.
12 * The naming phase consists in several things
13 * 1- get all the global names
14 * 2- transform all the local names into a unique identifier
17 open Core_kernel
18 open Utils
19 module SN = Naming_special_names
21 (*****************************************************************************)
22 (* The types *)
23 (*****************************************************************************)
25 let canon_key = String.lowercase
27 module GEnv = struct
28 let get_full_pos ctx (pos, name) =
29 try
30 match pos with
31 | FileInfo.Full p -> (p, name)
32 | FileInfo.File (FileInfo.Class, fn) ->
33 let res = unsafe_opt (Ast_provider.find_class_in_file ctx fn name) in
34 let (p', _) = res.Aast.c_name in
35 (p', name)
36 | FileInfo.File (FileInfo.RecordDef, fn) ->
37 let res =
38 unsafe_opt (Ast_provider.find_record_def_in_file ctx fn name)
40 let (p', _) = res.Aast.rd_name in
41 (p', name)
42 | FileInfo.File (FileInfo.Typedef, fn) ->
43 let res = unsafe_opt (Ast_provider.find_typedef_in_file ctx fn name) in
44 let (p', _) = res.Aast.t_name in
45 (p', name)
46 | FileInfo.File (FileInfo.Const, fn) ->
47 let res = unsafe_opt (Ast_provider.find_gconst_in_file ctx fn name) in
48 let (p', _) = res.Aast.cst_name in
49 (p', name)
50 | FileInfo.File (FileInfo.Fun, fn) ->
51 let res = unsafe_opt (Ast_provider.find_fun_in_file ctx fn name) in
52 let (p', _) = res.Aast.f_name in
53 (p', name)
54 with Invalid_argument _ ->
55 (* We looked for a file in the file heap, but it was deleted
56 before we could get it. This occurs with highest probability when we
57 have multiple large rebases in quick succession, and the typechecker
58 doesn't get updates from watchman while checking. For now, we restart
59 gracefully, but in future versions we'll be restarting the server on
60 large rebases anyhow, so this is sufficient behavior.
62 TODO(jjwu): optimize this. Instead of forcing a server restart,
63 catch the exception in the recheck look and start another recheck cycle
64 by adding more files to the unprocessed/partially-processed set in
65 the previous loop.
67 let fn = FileInfo.get_pos_filename pos in
68 Hh_logger.log "File missing: %s" (Relative_path.to_absolute fn);
69 Hh_logger.log "Name missing: %s" name;
70 raise File_provider.File_provider_stale
72 let type_canon_name ctx name =
73 Naming_provider.get_type_canon_name ctx (canon_key name)
75 let type_pos ctx name =
76 let name = Option.value (type_canon_name ctx name) ~default:name in
77 match Naming_provider.get_type_pos ctx name with
78 | Some pos ->
79 let (p, _) = get_full_pos ctx (pos, name) in
80 Some p
81 | None -> None
83 let type_canon_pos ctx name =
84 let name = Option.value (type_canon_name ctx name) ~default:name in
85 type_pos ctx name
87 let type_info ctx name =
88 match Naming_provider.get_type_pos_and_kind ctx name with
89 | Some
90 ( pos,
91 ( ( Naming_types.TClass | Naming_types.TTypedef
92 | Naming_types.TRecordDef ) as kind ) ) ->
93 let (p, _) = get_full_pos ctx (pos, name) in
94 Some (p, kind)
95 | None -> None
97 let fun_canon_name ctx name =
98 Naming_provider.get_fun_canon_name ctx (canon_key name)
100 let fun_pos ctx name =
101 match Naming_provider.get_fun_pos ctx name with
102 | Some pos ->
103 let (p, _) = get_full_pos ctx (pos, name) in
104 Some p
105 | None -> None
107 let fun_canon_pos ctx name =
108 let name = Option.value (fun_canon_name ctx name) ~default:name in
109 fun_pos ctx name
111 let typedef_pos ctx name =
112 match Naming_provider.get_type_pos_and_kind ctx name with
113 | Some (pos, Naming_types.TTypedef) ->
114 let (p, _) = get_full_pos ctx (pos, name) in
115 Some p
116 | Some (_, Naming_types.TClass)
117 | Some (_, Naming_types.TRecordDef)
118 | None ->
119 None
121 let gconst_pos ctx name =
122 match Naming_provider.get_const_pos ctx name with
123 | Some pos ->
124 let (p, _) = get_full_pos ctx (pos, name) in
125 Some p
126 | None -> None
128 let compare_pos ctx p q name =
129 FileInfo.(
130 match (p, q) with
131 | (Full p', Full q') -> Pos.compare p' q' = 0
132 | (Full q', (File _ as p'))
133 | ((File _ as p'), Full q') ->
134 let p' = fst (get_full_pos ctx (p', name)) in
135 Pos.compare p' q' = 0
136 | (File (x, fn1), File (y, fn2)) -> fn1 = fn2 && x = y)
139 (* The primitives to manipulate the naming environment *)
140 module Env = struct
141 let check_not_typehint ctx (p, name) =
142 let x = canon_key (Utils.strip_all_ns name) in
144 SN.Typehints.is_reserved_hh_name x
145 || SN.Typehints.is_reserved_global_name x
146 then (
147 let (p, name) = GEnv.get_full_pos ctx (p, name) in
148 Errors.name_is_reserved name p;
149 false
150 ) else
151 true
153 (* Dont check for errors, just add to canonical heap *)
154 let new_fun_fast ctx fn name =
155 let name_key = canon_key name in
156 match Naming_provider.get_fun_canon_name ctx name_key with
157 | Some _ -> ()
158 | None ->
159 let backend = Provider_context.get_backend ctx in
160 Naming_provider.add_fun backend name (FileInfo.File (FileInfo.Fun, fn))
162 let new_cid_fast ctx fn name cid_kind =
163 let name_key = canon_key name in
164 let mode =
165 match cid_kind with
166 | Naming_types.TClass -> FileInfo.Class
167 | Naming_types.TTypedef -> FileInfo.Typedef
168 | Naming_types.TRecordDef -> FileInfo.RecordDef
170 match Naming_provider.get_type_canon_name ctx name_key with
171 | Some _ -> ()
172 | None ->
173 let backend = Provider_context.get_backend ctx in
174 (* We store redundant info in this case, but if the position is a *)
175 (* Full position, we don't store the kind, so this is necessary *)
176 Naming_provider.add_type backend name (FileInfo.File (mode, fn)) cid_kind
178 let new_class_fast ctx fn name = new_cid_fast ctx fn name Naming_types.TClass
180 let new_record_decl_fast ctx fn name =
181 new_cid_fast ctx fn name Naming_types.TRecordDef
183 let new_typedef_fast ctx fn name =
184 new_cid_fast ctx fn name Naming_types.TTypedef
186 let new_global_const_fast backend fn name =
187 Naming_provider.add_const backend name (FileInfo.File (FileInfo.Const, fn))
189 let new_fun ctx (p, name) =
190 let name_key = canon_key name in
191 match Naming_provider.get_fun_canon_name ctx name_key with
192 | Some canonical ->
193 let p' = Option.value_exn (Naming_provider.get_fun_pos ctx canonical) in
194 if not @@ GEnv.compare_pos ctx p' p canonical then
195 let (p, name) = GEnv.get_full_pos ctx (p, name) in
196 let (p', canonical) = GEnv.get_full_pos ctx (p', canonical) in
197 Errors.error_name_already_bound name canonical p p'
198 | None ->
199 let backend = Provider_context.get_backend ctx in
200 Naming_provider.add_fun backend name p
202 let (attr_prefix, attr_prefix_len) =
203 let a = "\\__attribute__" in
204 (* lowercase because canon_key call *)
205 (a, String.length a)
207 let new_cid ctx cid_kind (p, name) =
208 let validate canonical error =
209 let p' =
210 match Naming_provider.get_type_pos ctx canonical with
211 | Some x -> x
212 | None ->
213 failwith
214 ( "Failed to get canonical pos for name "
215 ^ name
216 ^ " vs canonical "
217 ^ canonical )
219 if not @@ GEnv.compare_pos ctx p' p canonical then
220 let (p, name) = GEnv.get_full_pos ctx (p, name) in
221 let (p', canonical) = GEnv.get_full_pos ctx (p', canonical) in
222 error name canonical p p'
224 if not (check_not_typehint ctx (p, name)) then
226 else
227 let name_key = canon_key name in
228 match Naming_provider.get_type_canon_name ctx name_key with
229 | Some canonical -> validate canonical Errors.error_name_already_bound
230 | None ->
231 (* Check to prevent collision with attribute classes
232 * If we are checking \A, check \__Attribute__A and vice versa *)
233 let name_len = String.length name_key in
234 let alt_name_key =
236 name_len > attr_prefix_len
237 && String.equal attr_prefix (String.sub name_key 0 attr_prefix_len)
238 then
239 "\\"
240 ^ String.sub name_key attr_prefix_len (name_len - attr_prefix_len)
241 else
242 attr_prefix ^ String.sub name_key 1 (name_len - 1)
244 begin
245 match Naming_provider.get_type_canon_name ctx alt_name_key with
246 | Some alt_canonical ->
247 validate alt_canonical Errors.error_class_attribute_already_bound
248 | None -> ()
249 end;
250 let backend = Provider_context.get_backend ctx in
251 Naming_provider.add_type backend name p cid_kind
253 let new_class ctx = new_cid ctx Naming_types.TClass
255 let new_record_decl ctx = new_cid ctx Naming_types.TRecordDef
257 let new_typedef ctx = new_cid ctx Naming_types.TTypedef
259 let new_global_const ctx (p, x) =
260 match Naming_provider.get_const_pos ctx x with
261 | Some p' ->
262 if not @@ GEnv.compare_pos ctx p' p x then
263 let (p, x) = GEnv.get_full_pos ctx (p, x) in
264 let (p', x) = GEnv.get_full_pos ctx (p', x) in
265 Errors.error_name_already_bound x x p p'
266 | None ->
267 let backend = Provider_context.get_backend ctx in
268 Naming_provider.add_const backend x p
271 (*****************************************************************************)
272 (* Updating the environment *)
273 (*****************************************************************************)
274 let remove_decls ~ctx ~funs ~classes ~record_defs ~typedefs ~consts =
275 let backend = Provider_context.get_backend ctx in
276 let types = SSet.union classes typedefs in
277 let types = SSet.union types record_defs in
278 Naming_provider.remove_type_batch backend types;
279 Naming_provider.remove_fun_batch backend funs;
280 Naming_provider.remove_const_batch backend consts
282 (*****************************************************************************)
283 (* The entry point to build the naming environment *)
284 (*****************************************************************************)
286 let make_env ctx ~funs ~classes ~record_defs ~typedefs ~consts =
287 List.iter funs (Env.new_fun ctx);
288 List.iter classes (Env.new_class ctx);
289 List.iter record_defs (Env.new_record_decl ctx);
290 List.iter typedefs (Env.new_typedef ctx);
291 List.iter consts (Env.new_global_const ctx)
293 let make_env_from_fast ctx fn ~funs ~classes ~record_defs ~typedefs ~consts =
294 SSet.iter (Env.new_fun_fast ctx fn) funs;
295 SSet.iter (Env.new_class_fast ctx fn) classes;
296 SSet.iter (Env.new_record_decl_fast ctx fn) record_defs;
297 SSet.iter (Env.new_typedef_fast ctx fn) typedefs;
298 SSet.iter
299 (Env.new_global_const_fast (Provider_context.get_backend ctx) fn)
300 consts
302 (*****************************************************************************)
303 (* Declaring the names in a list of files *)
304 (*****************************************************************************)
306 let add_files_to_rename failed defl defs_in_env =
307 List.fold_left
309 begin
310 fun failed (_, def) ->
311 match defs_in_env def with
312 | None -> failed
313 | Some previous_definition_position ->
314 let filename = Pos.filename previous_definition_position in
315 Relative_path.Set.add failed filename
317 ~init:failed
318 defl
320 let ndecl_file_fast ctx fn ~funs ~classes ~record_defs ~typedefs ~consts =
321 make_env_from_fast ctx fn ~funs ~classes ~record_defs ~typedefs ~consts
323 let ndecl_file
327 FileInfo.file_mode = _;
328 funs;
329 classes;
330 record_defs;
331 typedefs;
332 consts;
333 comments = _;
334 hash = _;
336 let (errors, ()) =
337 Errors.do_with_context fn Errors.Naming (fun () ->
338 Hh_logger.debug "Naming decl: %s" (Relative_path.to_absolute fn);
339 make_env ctx ~funs ~classes ~record_defs ~typedefs ~consts)
341 if Errors.is_empty errors then
342 (errors, Relative_path.Set.empty)
343 else
344 (* IMPORTANT:
345 * If a file has name collisions, we MUST add the list of files that
346 * were previously defining the type to the set of "failed" files.
347 * If we fail to do so, we will be in a phony state, where a name could
348 * be missing.
350 * Example:
351 * A.php defines class A
352 * B.php defines class B
353 * Save the state, now let's introduce a new file (foo.php):
354 * foo.php defines class A and class B.
356 * 2 things happen (cf serverTypeCheck.ml):
357 * We remove the names A and B from the global environment.
358 * We report the error.
360 * But this is clearly not enough. If the user removes the file foo.php,
361 * both class A and class B are now missing from the naming environment.
362 * If the user has a file using class A (in strict), he now gets the
363 * error "Unbound name class A".
365 * The solution consist in adding all the files that were previously
366 * defining the same things as foo.php to the set of files to recheck.
368 * This way, when the user removes foo.php, A.php and B.php are recomputed
369 * and the naming environment is in a sane state.
371 * XXX (jezng): we can probably be less conservative about this -- instead
372 * of adding all the declarations in the file, why not just add those that
373 * were actually duplicates?
375 let failed = Relative_path.Set.singleton fn in
376 let failed = add_files_to_rename failed funs (GEnv.fun_canon_pos ctx) in
377 let failed = add_files_to_rename failed classes (GEnv.type_canon_pos ctx) in
378 let failed =
379 add_files_to_rename failed record_defs (GEnv.type_canon_pos ctx)
381 let failed =
382 add_files_to_rename failed typedefs (GEnv.type_canon_pos ctx)
384 let failed = add_files_to_rename failed consts (GEnv.gconst_pos ctx) in
385 (errors, failed)