solved some TODOs about Tgeneric type arguments (2)
[hiphop-php.git] / hphp / hack / src / typing / nast_check / unbound_name_check.ml
blobfdf7c9da95863b414a345e7de290934394f574fc
1 (*
2 * Copyright (c) Facebook, Inc. and its affiliates.
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the "hack" directory of this source tree.
7 *)
9 (**
10 * Checks to determine whether names referenced in a file are defined globally.
12 * NOTE: Unlike other nast checks, this one depends on and also
13 * modifies global naming table state. We would rather have this done in typing
14 * but currently there are multiple scenarios when the typechecker
15 * does not comprehensively check expressions. We are then left with
16 * an unrecorded dependency. This should be fixed on some more basic level.
19 open Hh_prelude
21 type env = {
22 droot: Typing_deps.Dep.dependent Typing_deps.Dep.variant;
23 mode: FileInfo.mode;
24 ctx: Provider_context.t;
25 type_params: Aast.reify_kind SMap.t;
26 in_ppl: bool;
27 (* Need some context to differentiate global consts and other Id's *)
28 seen_names: Pos.t SMap.t;
29 (* Special context for pocket universes *)
30 pu_case_types: Aast.reify_kind SMap.t;
31 pu_member_types: Aast.sid list SMap.t;
32 (* Contexts where typedefs are valid typenames *)
33 class_id_allow_typedef: bool;
34 hint_allow_typedef: bool;
35 hint_context: Errors.name_context;
38 let handle_unbound_name env (pos, name) kind =
39 (* We've already errored in naming if we get *Unknown* class *)
40 if String.equal name Naming_special_names.Classes.cUnknown then
42 else
43 match env.mode with
44 | FileInfo.Mphp -> ()
45 | FileInfo.Mstrict
46 | FileInfo.Mpartial
47 | FileInfo.Mdecl ->
48 Errors.unbound_name pos name kind;
49 (* In addition to reporting errors, we also add to the global dependency table *)
50 let dep =
51 match kind with
52 | Errors.FunctionNamespace -> Typing_deps.Dep.Fun name
53 | Errors.TypeNamespace -> Typing_deps.Dep.Class name
54 | Errors.ConstantNamespace -> Typing_deps.Dep.GConst name
55 | Errors.TraitContext -> Typing_deps.Dep.Class name
56 | Errors.RecordContext -> Typing_deps.Dep.RecordDef name
57 | Errors.ClassContext -> Typing_deps.Dep.Class name
59 Typing_deps.add_idep env.droot dep
61 let has_canon_name env get_name get_pos (pos, name) =
62 match get_name env.ctx name with
63 | None -> false
64 | Some canon_name ->
65 begin
66 match get_pos env.ctx canon_name with
67 | None -> false
68 | Some canon_pos ->
69 Errors.did_you_mean_naming pos name canon_pos canon_name;
70 true
71 end
73 let check_fun_name env ((_, name) as id) =
74 if Naming_special_names.SpecialFunctions.is_special_function name then
76 else if Naming_provider.fun_exists env.ctx name then
78 else if
79 has_canon_name
80 env
81 Naming_global.GEnv.fun_canon_name
82 Naming_global.GEnv.fun_pos
84 then
86 else
87 handle_unbound_name env id Errors.FunctionNamespace
89 let check_const_name env ((_, name) as id) =
90 if Naming_provider.const_exists env.ctx name then
92 else
93 handle_unbound_name env id Errors.ConstantNamespace
95 let check_type_name
96 ?(kind = Errors.TypeNamespace)
97 env
98 ((pos, name) as id)
99 ~allow_typedef
100 ~allow_generics =
101 if String.equal name Naming_special_names.Classes.cHH_BuiltinEnum then
103 else
104 match SMap.find_opt name env.type_params with
105 | Some reified ->
106 (* TODO: These throw typing errors instead of naming errors *)
107 if not allow_generics then Errors.generics_not_allowed pos;
108 begin
109 match reified with
110 | Aast.Erased -> Errors.generic_at_runtime pos "Erased"
111 | Aast.SoftReified -> Errors.generic_at_runtime pos "Soft reified"
112 | Aast.Reified -> ()
114 | None ->
115 begin
116 match Naming_provider.get_type_pos_and_kind env.ctx name with
117 | Some (def_pos, Naming_types.TTypedef) when not allow_typedef ->
118 let (full_pos, _) =
119 Naming_global.GEnv.get_full_pos env.ctx (def_pos, name)
121 Errors.unexpected_typedef pos full_pos kind
122 | Some _ -> ()
123 | None ->
125 has_canon_name
127 Naming_global.GEnv.type_canon_name
128 Naming_global.GEnv.type_pos
130 then
132 else
133 handle_unbound_name env id kind
136 let check_type_hint
137 ?(kind = Errors.TypeNamespace)
139 ((_, name) as id)
140 ~allow_typedef
141 ~allow_generics =
142 if String.equal name Naming_special_names.Typehints.wildcard then
144 else
145 check_type_name ~kind env id ~allow_typedef ~allow_generics
147 let extend_type_params init paraml =
148 List.fold_right
149 ~init
150 ~f:(fun { Aast.tp_name = (_, name); tp_reified; _ } acc ->
151 SMap.add name tp_reified acc)
152 paraml
154 let handler ctx =
155 object
156 inherit [env] Stateful_aast_visitor.default_nast_visitor_with_state
158 (* The following are all setting the environments / context correctly *)
159 method initial_state =
161 mode = FileInfo.Mpartial;
162 droot = Typing_deps.Dep.Fun "";
163 ctx;
164 type_params = SMap.empty;
165 in_ppl = false;
166 seen_names = SMap.empty;
167 pu_case_types = SMap.empty;
168 pu_member_types = SMap.empty;
169 class_id_allow_typedef = false;
170 hint_allow_typedef = true;
171 hint_context = Errors.TypeNamespace;
174 method! at_class_ env c =
175 let is_ppl =
176 List.exists c.Aast.c_user_attributes ~f:(fun { Aast.ua_name; _ } ->
177 String.equal
178 (snd ua_name)
179 Naming_special_names.UserAttributes.uaProbabilisticModel)
181 let new_env =
183 env with
184 droot = Typing_deps.Dep.Class (snd c.Aast.c_name);
185 mode = c.Aast.c_mode;
186 type_params =
187 extend_type_params SMap.empty c.Aast.c_tparams.Aast.c_tparam_list;
188 in_ppl = is_ppl;
191 new_env
193 method! at_typedef env td =
194 let new_env =
196 env with
197 droot = Typing_deps.Dep.Class (snd td.Aast.t_name);
198 mode = FileInfo.Mstrict;
199 type_params = extend_type_params SMap.empty td.Aast.t_tparams;
202 new_env
204 method! at_fun_ env f =
205 let new_env =
207 env with
208 droot = Typing_deps.Dep.Fun (snd f.Aast.f_name);
209 mode = f.Aast.f_mode;
210 type_params = extend_type_params env.type_params f.Aast.f_tparams;
213 new_env
215 method! at_gconst env gconst =
216 let new_env =
218 env with
219 droot = Typing_deps.Dep.GConst (snd gconst.Aast.cst_name);
220 mode = gconst.Aast.cst_mode;
223 new_env
225 method! at_file_attribute env _ =
226 let new_env =
227 { env with droot = Typing_deps.Dep.Fun ""; type_params = SMap.empty }
229 new_env
231 method! at_method_ env m =
233 env with
234 type_params = extend_type_params env.type_params m.Aast.m_tparams;
237 method! at_method_redeclaration env mt =
239 env with
240 type_params = extend_type_params env.type_params mt.Aast.mt_tparams;
243 method! at_pu_enum env pu_enum =
244 let pu_case_types =
245 List.fold_left
246 ~init:SMap.empty
247 ~f:(fun acc Aast.{ tp_name = (_, name); tp_reified = reified; _ } ->
248 SMap.add name reified acc)
249 pu_enum.Aast.pu_case_types
251 let pu_member_types =
252 List.fold_left
253 pu_enum.Aast.pu_members
254 ~init:SMap.empty
255 ~f:(fun acc Aast.{ pum_atom = (_, name); pum_types; _ } ->
256 let pum_type_param_names = List.map ~f:fst pum_types in
257 match SMap.find_opt name acc with
258 | None -> SMap.add name pum_type_param_names acc
259 | Some types ->
260 SMap.add name (List.append pum_type_param_names types) acc)
262 { env with pu_case_types; pu_member_types }
264 method! at_pu_case_value env _ =
265 { env with type_params = SMap.union env.type_params env.pu_case_types }
267 method! at_pu_member env pu_member =
268 let pu_name = snd pu_member.Aast.pum_atom in
269 let member_types = SMap.find pu_name env.pu_member_types in
270 let type_params =
271 List.fold_left
272 member_types
273 ~init:env.type_params
274 ~f:(fun acc type_param -> SMap.add (snd type_param) Aast.Erased acc)
276 { env with type_params }
278 method! at_targ env _ = { env with hint_allow_typedef = true }
280 method! at_class_hint env _ =
282 env with
283 hint_context = Errors.ClassContext;
284 hint_allow_typedef = false;
287 method! at_trait_hint env _ =
289 env with
290 hint_context = Errors.TraitContext;
291 hint_allow_typedef = false;
294 method! at_record_hint env _ =
296 env with
297 hint_context = Errors.RecordContext;
298 hint_allow_typedef = false;
301 method! at_xhp_attr_hint env _ = { env with hint_allow_typedef = false }
303 (* Below are the methods where we check for unbound names *)
304 method! at_expr env e =
305 match snd e with
306 | Aast.Call (_, (_, Aast.Id (p, name)), _, _, _)
307 when env.in_ppl && Naming_special_names.PPLFunctions.is_reserved name ->
308 { env with seen_names = SMap.add name p env.seen_names }
309 | Aast.FunctionPointer (Aast.FP_id ((p, name) as id), _)
310 | Aast.Call (_, (_, Aast.Id ((p, name) as id)), _, _, _) ->
311 let () = check_fun_name env id in
312 { env with seen_names = SMap.add name p env.seen_names }
313 | Aast.Id ((p, name) as id) ->
314 let () =
315 match SMap.find_opt name env.seen_names with
316 | None -> check_const_name env id
317 | Some pos when not @@ Pos.equal p pos -> check_const_name env id
318 | _ -> ()
321 | Aast.Fun_id id ->
322 let () = check_fun_name env id in
324 | Aast.Method_caller (id, _)
325 | Aast.Smethod_id (id, _)
326 | Aast.Xml (id, _, _) ->
327 let () =
328 check_type_name
330 ~allow_typedef:false
331 ~allow_generics:false
332 ~kind:Errors.ClassContext
336 | Aast.Record (id, _) ->
337 let () =
338 check_type_name
340 ~allow_typedef:false
341 ~allow_generics:false
342 ~kind:Errors.RecordContext
346 | Aast.Class_const ((_, Aast.CI _), (_, s)) when String.equal s "class" ->
347 { env with class_id_allow_typedef = true }
348 | Aast.Obj_get (_, (_, Aast.Id (p, name)), _) ->
349 { env with seen_names = SMap.add name p env.seen_names }
350 | _ -> env
352 method! at_shape_field_name env sfn =
353 let () =
354 match sfn with
355 | Ast_defs.SFclass_const (id, _) ->
356 check_type_name
358 ~allow_typedef:false
359 ~allow_generics:false
360 ~kind:Errors.ClassContext
362 | _ -> ()
366 method! at_user_attribute env { Aast.ua_name; _ } =
367 let () =
368 if not @@ Naming_special_names.UserAttributes.is_reserved (snd ua_name)
369 then
370 check_type_name
372 ~allow_typedef:false
373 ~allow_generics:false
374 ~kind:Errors.ClassContext
375 ua_name
379 method! at_class_id env ci =
380 match snd ci with
381 | Aast.CI id ->
382 let () =
383 check_type_name
385 ~allow_typedef:env.class_id_allow_typedef
386 ~allow_generics:true
387 ~kind:Errors.ClassContext
391 | _ -> env
393 method! at_catch env (id, _, _) =
394 let () =
395 check_type_name
397 ~allow_typedef:true
398 ~allow_generics:false
399 ~kind:Errors.ClassContext
404 method! at_hint env h =
405 match snd h with
406 | Aast.Happly (id, _) ->
407 let () =
408 check_type_hint
410 ~allow_typedef:env.hint_allow_typedef
411 ~allow_generics:false
412 ~kind:env.hint_context
415 (* Intentionally set allow_typedef to true for a hint's type parameters *
416 * because there are no runtime restrictions *)
417 { env with hint_allow_typedef = true }
418 | _ -> env