Fix special case of alternate if statements
[hiphop-php.git] / hphp / hack / src / server / autocompleteService.ml
blob24a53a38bff9cd6bc76d6fc69f20db2671f3fc7f
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 open Hh_core
11 open Reordered_argument_collections
12 open Typing_defs
13 open Utils
14 open String_utils
15 include AutocompleteTypes
17 module Phase = Typing_phase
18 module TUtils = Typing_utils
20 let ac_env = ref None
21 let autocomplete_results : autocomplete_result list ref = ref []
22 let autocomplete_is_complete : bool ref = ref true
24 (* The position we're autocompleting at. This is used when computing completions
25 * for global identifiers. *)
26 let autocomplete_identifier: (Pos.t * string) option ref = ref None
28 type autocomplete_type =
29 | Acid
30 | Acnew
31 | Actype
32 | Acclass_get
33 | Acprop
35 let (argument_global_type: autocomplete_type option ref) = ref None
36 let auto_complete_for_global = ref ""
38 let auto_complete_suffix = "AUTO332"
39 let suffix_len = String.length auto_complete_suffix
40 let strip_suffix s = String.sub s 0 (String.length s - suffix_len)
42 let matches_auto_complete_suffix x =
43 String.length x >= suffix_len &&
44 let suffix = String.sub x (String.length x - suffix_len) suffix_len in
45 suffix = auto_complete_suffix
47 let is_auto_complete x =
48 if !autocomplete_results = []
49 then matches_auto_complete_suffix x
50 else false
52 let get_replace_pos_exn ~delimit_on_namespaces =
53 match !autocomplete_identifier with
54 | None -> failwith "No autocomplete position was set."
55 | Some (pos, text) ->
56 if Pos.length pos < suffix_len
57 then failwith "Matched position is shorter than autocomplete suffix."
58 else
59 let open Ide_api_types in
60 let range = pos_to_range pos in
61 let st = if delimit_on_namespaces
62 then match String.rindex_opt text '\\' with
63 | Some index ->
64 { range.st with column = range.st.column + index }
65 | None -> range.st
66 else range.st in
67 let ed = { range.ed with column = range.ed.column - suffix_len } in
68 { st; ed }
71 let autocomplete_result_to_json res =
72 let func_param_to_json param =
73 Hh_json.JSON_Object [ "name", Hh_json.JSON_String param.param_name;
74 "type", Hh_json.JSON_String param.param_ty;
75 "variadic", Hh_json.JSON_Bool param.param_variadic;
78 let func_details_to_json details =
79 match details with
80 | Some fd -> Hh_json.JSON_Object [
81 "min_arity", Hh_json.int_ fd.min_arity;
82 "return_type", Hh_json.JSON_String fd.return_ty;
83 "params", Hh_json.JSON_Array (List.map fd.params func_param_to_json);
85 | None -> Hh_json.JSON_Null
87 let name = res.res_name in
88 let pos = res.res_pos in
89 let ty = res.res_ty in
90 Hh_json.JSON_Object [
91 "name", Hh_json.JSON_String name;
92 "type", Hh_json.JSON_String ty;
93 "pos", Pos.json pos;
94 "func_details", func_details_to_json res.func_details;
95 "expected_ty", Hh_json.JSON_Bool false; (* legacy field, left here in case clients need it *)
98 let get_partial_result name ty kind class_opt =
99 let base_class = Option.map ~f:(fun class_ -> class_.Typing_defs.tc_name) class_opt in
100 Partial { ty; name; kind_=kind; base_class; }
102 let add_res (res: autocomplete_result) : unit =
103 autocomplete_results := res :: !autocomplete_results
105 let add_partial_result name ty kind class_opt =
106 add_res (get_partial_result name ty kind class_opt)
108 let autocomplete_token ac_type env x =
109 if is_auto_complete (snd x)
110 then begin
111 ac_env := env;
112 autocomplete_identifier := Some x;
113 argument_global_type := Some ac_type;
114 auto_complete_for_global := snd x
117 let autocomplete_id id env = autocomplete_token Acid (Some env) id
119 let autocomplete_hint = autocomplete_token Actype None
121 let autocomplete_new cid env =
122 match cid with
123 | Nast.CI (sid, _) -> autocomplete_token Acnew (Some env) sid
124 | _ -> ()
126 let get_class_elt_types env class_ cid elts =
127 let elts = SMap.filter elts begin fun _ x ->
128 Tast_env.is_visible env x.ce_visibility cid class_
129 end in
130 SMap.map elts (fun { ce_type = lazy ty; _ } -> ty)
132 let autocomplete_member ~is_static env class_ cid id =
133 (* This is used for instance "$x->|" and static "Class1::|" members. *)
134 (* It's also used for "<nt:fb:text |" XHP attributes, in which case *)
135 (* class_ is ":nt:fb:text" and its attributes are in tc_props. *)
136 if is_auto_complete (snd id)
137 then begin
138 ac_env := Some env;
139 autocomplete_identifier := Some id;
140 argument_global_type := Some Acclass_get;
141 let add kind name ty = add_partial_result name (Phase.decl ty) kind (Some class_) in
142 if is_static then begin
143 SMap.iter (get_class_elt_types env class_ cid class_.tc_smethods) ~f:(add Method_kind);
144 SMap.iter (get_class_elt_types env class_ cid class_.tc_sprops) ~f:(add Property_kind);
145 SMap.iter (class_.tc_consts) ~f:(fun name cc -> add Class_constant_kind name cc.cc_type);
146 end else begin
147 SMap.iter (get_class_elt_types env class_ cid class_.tc_methods) ~f:(add Method_kind);
148 SMap.iter (get_class_elt_types env class_ cid class_.tc_props) ~f:(add Property_kind);
152 let autocomplete_lvar id env =
153 (* This is used for "$|" and "$x = $|" local variables. *)
154 let text = Local_id.get_name (snd id) in
155 if is_auto_complete text
156 then begin
157 argument_global_type := Some Acprop;
158 ac_env := Some env;
159 autocomplete_identifier := Some (fst id, text);
162 let should_complete_class completion_type class_kind =
163 match completion_type, class_kind with
164 | Some Acid, Some Ast.Cnormal
165 | Some Acid, Some Ast.Cabstract
166 | Some Acnew, Some Ast.Cnormal
167 | Some Actype, Some _ -> true
168 | _ -> false
170 let should_complete_fun completion_type =
171 completion_type=Some Acid
173 let get_constructor_ty c =
174 let pos = c.Typing_defs.tc_pos in
175 let reason = Typing_reason.Rwitness pos in
176 let return_ty = reason, Typing_defs.Tapply ((pos, c.Typing_defs.tc_name), []) in
177 match (fst c.Typing_defs.tc_construct) with
178 | Some elt ->
179 begin match elt.ce_type with
180 | lazy (_ as r, Tfun fun_) ->
181 (* We have a constructor defined, but the return type is void
182 * make it the object *)
183 let fun_ = { fun_ with Typing_defs.ft_ret = return_ty } in
184 r, Tfun fun_
185 | _ -> (* how can a constructor not be a function? *) assert false
187 | None ->
188 (* Nothing defined, so we need to fake the entire constructor *)
189 reason,
190 Typing_defs.Tfun
191 (Typing_env.make_ft pos Nonreactive (*is_coroutine*)false [] return_ty)
193 (* Global identifier autocomplete uses search service to find matching names *)
194 let search_funs_and_classes input ~limit ~on_class ~on_function =
195 HackSearchService.MasterApi.query_autocomplete input ~limit
196 ~filter_map:begin fun _ _ res ->
197 let name = res.SearchUtils.name in
198 match res.SearchUtils.result_type with
199 | HackSearchService.Class _-> on_class name
200 | HackSearchService.Function -> on_function name
201 | _ -> None
204 (* compute_complete_global: given the sets content_funs and content_classes *)
205 (* of function names and classes in the current file, returns a list of all *)
206 (* possible identifier autocompletions at the autocomplete position (which *)
207 (* is stored in a global mutable reference). The results are stored in the *)
208 (* global mutable reference 'autocomplete_results'. *)
209 (* This function has two modes of dealing with namespaces... *)
210 (* delimit_on_namespaces=true delimit_on_namespaces=false *)
211 (* St| => Str Str\compare, ... *)
212 (* Str\\c| => compare Str\compare *)
213 (* Essentially, 'delimit_on_namespaces=true' means that autocomplete treats *)
214 (* namespaces as first class entities; 'false' means that it treats them *)
215 (* purely as part of long identifier names where the symbol '\\' is not *)
216 (* really any different from the symbol '_' for example. *)
217 (* *)
218 (* XHP note: *)
219 (* This function is also called for "<foo|", with gname="foo". *)
220 (* This is a Hack shorthand for referring to the global classname ":foo". *)
221 (* Our global dictionary of classnames stores the authoritative name ":foo", *)
222 (* and inside autocompleteService we do the job of inserting a leading ":" *)
223 (* from the user's prefix, and stripping the leading ":" when we emit. *)
224 let compute_complete_global
225 ~(tcopt: TypecheckerOptions.t)
226 ~(delimit_on_namespaces: bool)
227 ~(autocomplete_context: AutocompleteTypes.legacy_autocomplete_context)
228 ~(content_funs: Reordered_argument_collections.SSet.t)
229 ~(content_classes: Reordered_argument_collections.SSet.t)
230 : unit =
231 let completion_type = !argument_global_type in
232 let gname = Utils.strip_ns !auto_complete_for_global in
233 let gname = strip_suffix gname in
234 let gname = if autocomplete_context.is_xhp_classname then (":" ^ gname) else gname in
235 (* Colon hack: in "case Foo::Bar:", we wouldn't want to show autocomplete *)
236 (* here, but we would in "<nt:" and "$a->:" and "function f():". We can *)
237 (* recognize this case by whether the prefix is empty. *)
238 if autocomplete_context.is_after_single_colon && gname = "" then
240 else begin
242 (* Objective: given "fo|", we should suggest functions in both the current *)
243 (* namespace and also in the global namespace. We'll use gname_gns to *)
244 (* indicate what prefix to look for (if any) in the global namespace... *)
246 (* "$z = S|" => gname = "S", gname_gns = Some "S" *)
247 (* "$z = Str|" => gname = "Str", gname_gns = Some "Str" *)
248 (* "$z = Str\\|" => gname = "Str\\", gname_gns = None *)
249 (* "$z = Str\\s|" => gname = "Str\\s", gname_gns = None *)
250 (* "$z = \\s|" => gname = "\\s", gname_gns = None *)
251 (* "...; |" => gname = "", gname_gns = Some "" *)
252 let gname_gns = if should_complete_fun completion_type then
253 (* Disgusting hack alert!
255 * In PHP/Hack, namespaced function lookup falls back into the global
256 * namespace if no function in the current namespace exists. The
257 * typechecker knows everything that exists, and resolves all of this
258 * during naming -- meaning that by the time that we get to typing, not
259 * only has "gname" been fully qualified, but we've lost whatever it
260 * might have looked like originally. This makes it tough to do the full
261 * namespace fallback behavior here -- we'd like to know if whatever
262 * "gname" corresponds to in the source code has a '\' to qualify it, but
263 * since it's already fully qualified here, we can't know.
265 * [NOTE(ljw): is this really even true? if user wrote "foo|" then name
266 * lookup of "fooAUTO332" is guaranteed to fail in the current namespace,
267 * and guaranteed to fail in the global namespace, so how would the
268 * typechecker fully qualify it? to what? Experimentally I've only
269 * ever seen gname to be the exact string that the user typed.]
271 * Except, we can kinda reverse engineer and figure it out. We have the
272 * positional information, which we can use to figure out how long the
273 * original source code token was, and then figure out what portion of
274 * "gname" that corresponds to, and see if it has a '\'. Since fully
275 * qualifying a name will always prepend, this all works.
277 match !autocomplete_identifier with
278 | None -> None
279 | Some (p, _) ->
280 let len = (Pos.length p) - suffix_len in
281 let start = String.length gname - len in
282 if start < 0 || String.contains_from gname start '\\'
283 then None else Some (strip_all_ns gname)
284 else None in
286 let does_fully_qualified_name_match_prefix ?(funky_gns_rules=false) name =
287 let stripped_name = strip_ns name in
288 if delimit_on_namespaces then
289 (* name must match gname, and have no additional namespace slashes, e.g. *)
290 (* name="Str\\co" gname="S" -> false *)
291 (* name="Str\\co" gname="Str\\co" -> true *)
292 string_starts_with stripped_name gname &&
293 not (String.contains_from stripped_name (String.length gname) '\\')
294 else
295 match gname_gns with
296 | _ when string_starts_with stripped_name gname -> true
297 | Some gns when funky_gns_rules -> string_starts_with stripped_name gns
298 | _ -> false
301 let string_to_replace_prefix name =
302 let stripped_name = strip_ns name in
303 if delimit_on_namespaces then
304 (* returns the part of 'name' after its rightmost slash *)
306 let len = String.length stripped_name in
307 let i = (String.rindex stripped_name '\\') + 1 in
308 String.sub stripped_name i (len - i)
309 with _ ->
310 stripped_name
311 else
312 stripped_name
315 let result_count = ref 0 in
317 let on_class name ~seen =
318 if SSet.mem seen name then None else
319 if not (does_fully_qualified_name_match_prefix name) then None else
320 let target = Typing_lazy_heap.get_class tcopt name in
321 let target_kind = Option.map target ~f:(fun c -> c.Typing_defs.tc_kind) in
322 if not (should_complete_class completion_type target_kind) then None else
323 Option.map target ~f:(fun c ->
324 incr result_count;
325 if completion_type = Some Acnew then
326 get_partial_result
327 (string_to_replace_prefix name)
328 (Phase.decl (get_constructor_ty c))
329 Constructor_kind
330 (* Only do doc block fallback on constructors if they're consistent. *)
331 (if snd c.Typing_defs.tc_construct then Some c else None)
332 else
333 let kind = match c.Typing_defs.tc_kind with
334 | Ast.Cabstract -> Abstract_class_kind
335 | Ast.Cnormal -> Class_kind
336 | Ast.Cinterface -> Interface_kind
337 | Ast.Ctrait -> Trait_kind
338 | Ast.Cenum -> Enum_kind
340 let ty =
341 Typing_reason.Rwitness c.Typing_defs.tc_pos,
342 Typing_defs.Tapply ((c.Typing_defs.tc_pos, name), []) in
343 get_partial_result (string_to_replace_prefix name) (Phase.decl ty) kind None
347 let on_function name ~seen =
348 if autocomplete_context.is_xhp_classname then None else
349 if SSet.mem seen name then None else
350 if not (should_complete_fun completion_type) then None else
351 if not (does_fully_qualified_name_match_prefix ~funky_gns_rules:true name) then None else
352 Option.map (Typing_lazy_heap.get_fun tcopt name) ~f:(fun fun_ ->
353 incr result_count;
354 let ty = Typing_reason.Rwitness fun_.Typing_defs.ft_pos, Typing_defs.Tfun fun_ in
355 get_partial_result (string_to_replace_prefix name) (Phase.decl ty) Function_kind None
359 let on_namespace name : autocomplete_result option =
360 (* name will have the form "Str" or "HH\\Lib\\Str" *)
361 (* Our autocomplete will show up in the list as "Str". *)
362 if autocomplete_context.is_xhp_classname then None else
363 if not delimit_on_namespaces then None else
364 if not (does_fully_qualified_name_match_prefix name) then None else
365 Some (Complete {
366 res_pos = Pos.none |> Pos.to_absolute;
367 res_replace_pos = get_replace_pos_exn ~delimit_on_namespaces;
368 res_base_class = None;
369 res_ty = "namespace";
370 res_name = string_to_replace_prefix name;
371 res_kind = Namespace_kind;
372 func_details = None;
376 (* Try using the names in local content buffer first *)
377 List.iter
378 (List.filter_map (SSet.elements content_classes) (on_class ~seen:SSet.empty))
379 add_res;
380 List.iter
381 (List.filter_map (SSet.elements content_funs) (on_function ~seen:SSet.empty))
382 add_res;
384 (* Add namespaces. The hack server doesn't index namespaces themselves; it *)
385 (* only stores names of functions and classes in fully qualified form, e.g. *)
386 (* \\HH\\Lib\\Str\\length *)
387 (* If the project's .hhconfig has auto_namesspace_map "Str": "HH\Lib\\Str" *)
388 (* then the hack server will index the function just as *)
389 (* \\Str\\length *)
390 (* The main index, having only a global list if functions/classes, doesn't *)
391 (* actually offer any way for us to iterate over namespaces. And changing *)
392 (* its trie-indexer to do so is kind of ugly. So as a temporary workaround, *)
393 (* to give an okay user-experience at least for the Hack standard library, *)
394 (* we're just going to list all the possible standard namespaces right here *)
395 (* and see if any of them really exist in the current codebase/hhconfig! *)
396 (* This will give a good experience only for codebases where users rarely *)
397 (* define their own namespaces... *)
398 let standard_namespaces =
399 ["C"; "Vec"; "Dict"; "Str"; "Keyset"; "Math"; "Regex"; "SecureRandom"; "PHP"; "JS"] in
400 let namespace_permutations ns = [
401 Printf.sprintf "%s" ns;
402 Printf.sprintf "%s\\fb" ns;
403 Printf.sprintf "HH\\Lib\\%s" ns;
404 Printf.sprintf "HH\\Lib\\%s\\fb" ns;
405 ] in
406 let all_possible_namespaces =
407 List.map standard_namespaces ~f:namespace_permutations |> List.concat
409 List.iter all_possible_namespaces ~f:(fun ns ->
410 let ns_results = search_funs_and_classes (ns ^ "\\") ~limit:(Some 1)
411 ~on_class:(fun _className -> on_namespace ns)
412 ~on_function:(fun _functionName -> on_namespace ns)
414 List.iter ns_results.With_complete_flag.value add_res
417 (* Use search results to look for matches, while excluding names we have
418 * already seen in local content buffer *)
419 let gname_results = search_funs_and_classes gname ~limit:(Some 100)
420 ~on_class:(on_class ~seen:content_classes)
421 ~on_function:(on_function ~seen:content_funs)
423 autocomplete_is_complete :=
424 !autocomplete_is_complete && gname_results.With_complete_flag.is_complete;
425 List.iter gname_results.With_complete_flag.value add_res;
427 (* Compute global namespace fallback results for functions, if applicable *)
428 match gname_gns with
429 | Some gname_gns when gname <> gname_gns ->
430 let gname_gns_results = search_funs_and_classes gname_gns ~limit:(Some 100)
431 ~on_class:(fun _ -> None)
432 ~on_function:(on_function ~seen:content_funs)
434 autocomplete_is_complete :=
435 !autocomplete_is_complete && gname_gns_results.With_complete_flag.is_complete;
436 List.iter gname_gns_results.With_complete_flag.value add_res;
437 | _ -> ()
440 (* Here we turn partial_autocomplete_results into complete_autocomplete_results *)
441 (* by using typing environment to convert ty information into strings. *)
442 let resolve_ty
443 (env: Tast_env.t)
444 (autocomplete_context: legacy_autocomplete_context)
445 (x: partial_autocomplete_result)
446 ~(delimit_on_namespaces: bool)
447 : complete_autocomplete_result =
448 let env, ty = match x.ty with
449 | DeclTy ty -> Tast_env.localize_with_self env ty
450 | LoclTy ty -> env, ty
452 let desc_string = match x.kind_ with
453 | Method_kind
454 | Function_kind
455 | Variable_kind
456 | Property_kind
457 | Class_constant_kind
458 | Constructor_kind -> Tast_env.print_ty env ty
459 | Abstract_class_kind -> "abstract class"
460 | Class_kind -> "class"
461 | Interface_kind -> "interface"
462 | Trait_kind -> "trait"
463 | Enum_kind -> "enum"
464 | Namespace_kind -> "namespace"
465 | Keyword_kind -> "keyword"
467 let func_details = match ty with
468 | (_, Tfun ft) ->
469 let param_to_record ?(is_variadic=false) param =
471 param_name = (match param.fp_name with
472 | Some n -> n
473 | None -> "");
474 param_ty = Tast_env.print_ty env param.fp_type;
475 param_variadic = is_variadic;
478 Some {
479 return_ty = Tast_env.print_ty env ft.ft_ret;
480 min_arity = arity_min ft.ft_arity;
481 params = List.map ft.ft_params param_to_record @
482 (match ft.ft_arity with
483 | Fellipsis _ ->
484 let empty = TUtils.default_fun_param (Reason.none, Tany) in
485 [param_to_record ~is_variadic:true empty]
486 | Fvariadic (_, p) -> [param_to_record ~is_variadic:true p]
487 | Fstandard _ -> [])
489 | _ -> None
491 (* XHP class+attribute names are stored internally with a leading colon. *)
492 (* We'll render them without it if and only if we're in an XHP context. *)
493 (* $x = new :class1() -- do strip the colon in front of :class1 *)
494 (* $x = <:class1 -- don't strip it *)
495 (* $x->:attr1 -- don't strip the colon in front of :attr1 *)
496 (* <class1 :attr="a" -- do strip it *)
497 (* The logic is thorny here because we're relying upon regexes to figure *)
498 (* out the context. Once we switch autocomplete to FFP, it'll be cleaner. *)
499 let name = match x.kind_, autocomplete_context with
500 | Property_kind, { AutocompleteTypes.is_instance_member = false; _ } -> lstrip x.name ":"
501 | Abstract_class_kind, { AutocompleteTypes.is_xhp_classname = true; _ }
502 | Class_kind, { AutocompleteTypes.is_xhp_classname = true; _ } -> lstrip x.name ":"
503 | _ -> x.name
506 res_pos = (fst ty) |> Typing_reason.to_pos |> Pos.to_absolute;
507 res_replace_pos = get_replace_pos_exn ~delimit_on_namespaces;
508 res_base_class = x.base_class;
509 res_ty = desc_string;
510 res_name = name;
511 res_kind = x.kind_;
512 func_details = func_details;
515 let tast_cid_to_nast_cid env cid =
516 let nmenv = Tast.nast_mapping_env (Tast_env.save env) in
517 Tast.NastMapper.map_class_id_ nmenv cid
519 let autocomplete_typed_member ~is_static env class_ty cid mid =
520 Tast_env.get_class_ids env class_ty
521 |> List.iter ~f:begin fun cname ->
522 Typing_lazy_heap.get_class (Tast_env.get_tcopt env) cname
523 |> Option.iter ~f:begin fun class_ ->
524 let cid = Option.map cid (tast_cid_to_nast_cid env) in
525 autocomplete_member ~is_static env class_ cid mid
529 let autocomplete_static_member env (ty, cid) mid =
530 autocomplete_typed_member ~is_static:true env ty (Some cid) mid
532 let visitor = object
533 inherit Tast_visitor.iter as super
535 method! on_Id env id =
536 autocomplete_id id env;
537 super#on_Id env id
539 method! on_Fun_id env id =
540 autocomplete_id id env;
541 super#on_Fun_id env id
543 method! on_New env cid el uel =
544 autocomplete_new (tast_cid_to_nast_cid env (snd cid)) env;
545 super#on_New env cid el uel
547 method! on_Happly env sid hl =
548 autocomplete_hint sid;
549 super#on_Happly env sid hl
551 method! on_Lvar env lid =
552 autocomplete_lvar lid env;
553 super#on_Lvar env lid
555 method! on_Class_get env cid mid =
556 autocomplete_static_member env cid mid;
557 super#on_Class_get env cid mid
559 method! on_Class_const env cid mid =
560 autocomplete_static_member env cid mid;
561 super#on_Class_const env cid mid
563 method! on_Obj_get env obj mid ognf =
564 (match mid with
565 | _, Tast.Id mid ->
566 autocomplete_typed_member ~is_static:false env (Tast.get_type obj) None mid
567 | _ -> ()
569 super#on_Obj_get env obj mid ognf
571 method! on_Xml env sid attrs el =
572 let cid = Nast.CI (sid, []) in
573 Typing_lazy_heap.get_class (Tast_env.get_tcopt env) (snd sid)
574 |> Option.iter ~f:begin fun c ->
575 List.iter attrs ~f:begin function
576 | Tast.Xhp_simple (id, _) ->
577 autocomplete_member ~is_static:false env c (Some cid) id
578 | Tast.Xhp_spread _ -> ()
580 end;
581 super#on_Xml env sid attrs el
584 let auto_complete_suffix_finder = object
585 inherit [_] Tast.reduce
586 method zero = false
587 method plus = (||)
588 method! on_Lvar () (_, id) =
589 matches_auto_complete_suffix (Local_id.get_name id)
592 let method_contains_cursor = auto_complete_suffix_finder#on_method_ ()
593 let fun_contains_cursor = auto_complete_suffix_finder#on_fun_ ()
595 class local_types = object (self)
596 inherit Tast_visitor.iter as super
598 val mutable results = Local_id.Map.empty;
599 val mutable after_cursor = false;
601 method get_types tast =
602 self#go tast;
603 results
605 method add id ty =
606 (* If we already have a type for this identifier, don't overwrite it with
607 results from after the cursor position. *)
608 if not (Local_id.Map.mem id results && after_cursor) then
609 results <- Local_id.Map.add id ty results
611 method! on_fun_ env f =
612 if fun_contains_cursor f then
613 super#on_fun_ env f
615 method! on_method_ env m =
616 if method_contains_cursor m then begin
617 if not (Tast_env.is_static env) then
618 self#add Typing_defs.this (Tast_env.get_self env);
619 super#on_method_ env m
622 method! on_expr env e =
623 let (_, ty), e_ = e in
624 match e_ with
625 | Tast.Lvar (_, id) ->
626 if matches_auto_complete_suffix (Local_id.get_name id)
627 then after_cursor <- true
628 else self#add id ty
629 | Tast.Binop (Ast.Eq _, e1, e2) ->
630 (* Process the rvalue before the lvalue, since the lvalue is annotated
631 with its type after the assignment. *)
632 self#on_expr env e2;
633 self#on_expr env e1;
634 | _ -> super#on_expr env e
636 method! on_fun_param _ fp =
637 let id = Local_id.get fp.Tast.param_name in
638 let _, ty = fp.Tast.param_annotation in
639 self#add id ty
642 let compute_complete_local tast =
643 new local_types#get_types tast
644 |> Local_id.Map.iter begin fun x ty ->
645 add_partial_result (Local_id.get_name x) (Phase.locl ty) Variable_kind None
648 let reset () =
649 auto_complete_for_global := "";
650 argument_global_type := None;
651 autocomplete_identifier := None;
652 ac_env := None;
653 autocomplete_results := [];
654 autocomplete_is_complete := true
656 let go
657 ~tcopt
658 ~delimit_on_namespaces
659 ~content_funs
660 ~content_classes
661 ~autocomplete_context
662 tast
664 reset ();
665 visitor#go tast;
666 Errors.ignore_ begin fun () ->
667 let completion_type = !argument_global_type in
668 if completion_type = Some Acid ||
669 completion_type = Some Acnew ||
670 completion_type = Some Actype
671 then compute_complete_global
672 ~tcopt ~delimit_on_namespaces ~autocomplete_context ~content_funs ~content_classes;
673 if completion_type = Some Acprop then compute_complete_local tast;
674 let env = match !ac_env with
675 | Some e -> e
676 | None -> Tast_env.empty tcopt
678 let resolve (result: autocomplete_result) : complete_autocomplete_result =
679 match result with
680 | Partial res -> resolve_ty env autocomplete_context res ~delimit_on_namespaces
681 | Complete res -> res
684 With_complete_flag.is_complete = !autocomplete_is_complete;
685 value = !autocomplete_results |> List.map ~f:resolve;