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.
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
19 module SN
= Naming_special_names
21 (*****************************************************************************)
23 (*****************************************************************************)
25 let canon_key = String.lowercase
28 let get_full_pos ctx
(pos
, name
) =
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
36 | FileInfo.File
(FileInfo.RecordDef
, fn
) ->
38 unsafe_opt
(Ast_provider.find_record_def_in_file ctx fn name
)
40 let (p'
, _
) = res.Aast.rd_name
in
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
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
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
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
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
79 let (p
, _
) = get_full_pos ctx
(pos
, name) in
83 let type_canon_pos ctx
name =
84 let name = Option.value (type_canon_name ctx
name) ~default
:name in
87 let type_info ctx
name =
88 match Naming_provider.get_type_pos_and_kind ctx
name with
91 ( ( Naming_types.TClass
| Naming_types.TTypedef
92 | Naming_types.TRecordDef
) as kind
) ) ->
93 let (p
, _
) = get_full_pos ctx
(pos
, name) in
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
103 let (p
, _
) = get_full_pos ctx
(pos
, name) in
107 let fun_canon_pos ctx
name =
108 let name = Option.value (fun_canon_name ctx
name) ~default
:name in
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
116 | Some
(_
, Naming_types.TClass
)
117 | Some
(_
, Naming_types.TRecordDef
)
121 let gconst_pos ctx
name =
122 match Naming_provider.get_const_pos ctx
name with
124 let (p
, _
) = get_full_pos ctx
(pos
, name) in
128 let compare_pos ctx p q
name =
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 *)
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
147 let (p, name) = GEnv.get_full_pos ctx
(p, name) in
148 Errors.name_is_reserved
name p;
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
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
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
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
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'
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 *)
207 let new_cid ctx cid_kind
(p, name) =
208 let validate canonical error
=
210 match Naming_provider.get_type_pos ctx canonical
with
214 ( "Failed to get canonical pos for name "
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
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
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
236 name_len > attr_prefix_len
237 && String.equal attr_prefix
(String.sub
name_key 0 attr_prefix_len
)
240 ^
String.sub
name_key attr_prefix_len
(name_len - attr_prefix_len
)
242 attr_prefix ^
String.sub
name_key 1 (name_len - 1)
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
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
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'
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
;
299 (Env.new_global_const_fast (Provider_context.get_backend ctx
) fn)
302 (*****************************************************************************)
303 (* Declaring the names in a list of files *)
304 (*****************************************************************************)
306 let add_files_to_rename failed defl defs_in_env
=
310 fun failed
(_
, def
) ->
311 match defs_in_env def
with
313 | Some previous_definition_position
->
314 let filename = Pos.filename previous_definition_position
in
315 Relative_path.Set.add failed
filename
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
327 FileInfo.file_mode
= _
;
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
)
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
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
379 add_files_to_rename failed record_defs
(GEnv.type_canon_pos ctx
)
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