2 * Copyright (c) 2015, Facebook, Inc.
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.
12 open Option.Monad_infix
13 open Reordered_argument_collections
16 type member
= Ai.ServerFindRefs.member
=
19 | Class_const
of string
22 type action
= Ai.ServerFindRefs.action
=
24 | Member
of string * member
28 (* The class containing the member can be specified in two ways:
29 * - Class_set - as an explicit, pre-computed set of names, which are then
30 * compared using string comparison
31 * - Subclasses_of - the class's name, in which comparison will use the
36 | Subclasses_of
of string
38 type action_internal
=
40 | IMember
of member_class
* member
44 type result
= (string * Pos.absolute
) list
45 type ide_result
= (string * Pos.absolute list
) option
47 let process_fun_id results_acc target_fun id
=
48 if target_fun
= (snd id
)
49 then results_acc
:= Pos.Map.add
(fst id
) (snd id
) !results_acc
51 let check_if_extends_class tcopt target_class_name class_name
=
52 let class_ = Typing_lazy_heap.get_class tcopt class_name
in
54 | Some
{ Typing_defs.tc_ancestors
= imps
; _
}
55 when SMap.mem imps target_class_name
-> true
58 let is_target_class tcopt target_classes class_name
=
59 match target_classes
with
60 | Class_set s
-> SSet.mem s class_name
62 s
= class_name
|| check_if_extends_class tcopt s class_name
64 let process_member_id tcopt results_acc target_classes target_member
65 class_ ~targs
:_ id _ _ ~is_method ~is_const
=
66 let member_name = snd id
in
67 let is_target = match target_member
with
68 | Method target_name
-> is_method
&& (member_name = target_name
)
69 | Property target_name
->
70 (not is_method
) && (not is_const
) &&
71 ((String_utils.lstrip
member_name "$") = target_name
)
72 | Class_const target_name
-> is_const
&& (member_name = target_name
)
73 | Typeconst _
-> false
75 if not
is_target then () else
76 let class_name = class_.Typing_defs.tc_name
in
77 if is_target_class tcopt target_classes
class_name then
79 Pos.Map.add
(fst id
) (class_name ^
"::" ^
(snd id
)) !results_acc
81 let process_constructor tcopt results_acc
82 target_classes target_member
class_ ~targs _ p
=
84 tcopt results_acc target_classes target_member
class_ ~targs
85 (p
, "__construct") () () ~is_method
:true ~is_const
:false
87 let process_class_id results_acc target_classes cid mid_option
=
88 if (SSet.mem target_classes
(snd cid
))
90 let class_name = match mid_option
with
92 | Some n
-> (snd cid
)^
"::"^
(snd n
) in
93 results_acc
:= Pos.Map.add
(fst cid
) class_name !results_acc
96 let process_taccess tcopt results_acc target_classes target_typeconst
98 let class_name = class_.tc_name
in
99 let tconst_name = (snd typeconst
.ttc_name
) in
100 if (is_target_class tcopt target_classes
class_name) &&
101 (target_typeconst
= tconst_name) then
103 Pos.Map.add p
(class_name ^
"::" ^
tconst_name) !results_acc
105 let process_gconst_id results_acc target_gconst id
=
106 if target_gconst
= (snd id
)
107 then results_acc
:= Pos.Map.add
(fst id
) (snd id
) !results_acc
109 let attach_hooks tcopt results_acc
= function
110 | IMember
(classes
, ((Method _
| Property _
| Class_const _
) as member
)) ->
111 let process_member_id =
112 process_member_id tcopt results_acc classes member
in
113 Typing_hooks.attach_cmethod_hook
process_member_id;
114 Typing_hooks.attach_smethod_hook
process_member_id;
115 Typing_hooks.attach_constructor_hook
116 (process_constructor tcopt results_acc classes member
);
117 | IMember
(classes
, Typeconst t
) ->
118 Typing_hooks.attach_taccess_hook
119 (process_taccess tcopt results_acc classes t
)
120 | IFunction fun_name
->
121 Typing_hooks.attach_fun_id_hook
(process_fun_id results_acc fun_name
)
123 let classes = SSet.singleton c
in
124 Decl_hooks.attach_class_id_hook
(process_class_id results_acc
classes)
125 | IGConst cst_name
->
126 Typing_hooks.attach_global_const_hook
127 (process_gconst_id results_acc cst_name
)
129 let detach_hooks () =
130 Decl_hooks.remove_all_hooks
();
131 Typing_hooks.remove_all_hooks
()
133 let add_if_extends_class tcopt target_class_name
class_name acc
=
134 if check_if_extends_class tcopt target_class_name
class_name
135 then SSet.add acc
class_name else acc
137 let find_child_classes tcopt target_class_name files_info files
=
138 SharedMem.invalidate_caches
();
139 Relative_path.Set.fold files ~init
:SSet.empty ~f
:begin fun fn acc
->
141 let { FileInfo.classes; _
} =
142 Relative_path.Map.find_unsafe files_info fn
in
143 List.fold_left
classes ~init
:acc ~f
:begin fun acc cid
->
144 add_if_extends_class tcopt target_class_name
(snd cid
) acc
150 let get_child_classes_files class_name =
151 match Naming_heap.TypeIdHeap.get
class_name with
152 | Some
(_
, `Class
) ->
153 (* Find the files that contain classes that extend class_ *)
155 Typing_deps.Dep.make
(Typing_deps.Dep.Class
class_name) in
157 Decl_compare.get_extend_deps
cid_hash
158 (Typing_deps.DepSet.singleton
cid_hash)
160 Typing_deps.get_files
extend_deps
162 Relative_path.Set.empty
164 let get_deps_set classes =
165 let get_filename class_name =
166 Naming_heap.TypeIdHeap.get
class_name >>= fun (pos
, _
) ->
167 Some
(FileInfo.get_pos_filename pos
)
169 SSet.fold
classes ~f
:begin fun class_name acc
->
170 match get_filename class_name with
173 let dep = Typing_deps.Dep.Class
class_name in
174 let bazooka = Typing_deps.get_bazooka
dep in
175 let files = Typing_deps.get_files
bazooka in
176 let files = Relative_path.Set.add
files fn
in
177 Relative_path.Set.union
files acc
178 end ~init
:Relative_path.Set.empty
180 let get_deps_set_function f_name
=
181 match Naming_heap.FunPosHeap.get f_name
with
183 let fn = FileInfo.get_pos_filename pos
in
184 let dep = Typing_deps.Dep.Fun f_name
in
185 let bazooka = Typing_deps.get_bazooka
dep in
186 let files = Typing_deps.get_files
bazooka in
187 Relative_path.Set.add
files fn
189 Relative_path.Set.empty
191 let get_deps_set_gconst cst_name
=
192 match Naming_heap.ConstPosHeap.get cst_name
with
194 let fn = FileInfo.get_pos_filename pos
in
195 let dep = Typing_deps.Dep.GConst cst_name
in
196 let bazooka = Typing_deps.get_bazooka
dep in
197 let files = Typing_deps.get_files
bazooka in
198 Relative_path.Set.add
files fn
200 Relative_path.Set.empty
202 let find_refs tcopt target acc fileinfo_l
=
203 let results_acc = ref Pos.Map.empty
in
204 attach_hooks tcopt
results_acc target
;
205 let tcopt = TypecheckerOptions.make_permissive
tcopt in
206 ServerIdeUtils.recheck
tcopt fileinfo_l
;
208 Pos.Map.fold
begin fun p str acc
->
212 let parallel_find_refs workers fileinfo_l target
tcopt =
215 ~job
:(find_refs tcopt target
)
217 ~merge
:(List.rev_append
)
218 ~next
:(MultiWorker.next workers fileinfo_l
)
220 let get_definitions tcopt = function
221 | IMember
(Class_set
classes, Method method_name
) ->
222 SSet.fold
classes ~init
:[] ~f
:begin fun class_name acc
->
223 match Typing_lazy_heap.get_class
tcopt class_name with
225 let add_meth meths acc
= match SMap.get meths method_name
with
226 | Some meth
when meth
.ce_origin
= class_.tc_name
->
227 let pos = Reason.to_pos
(fst
@@ Lazy.force meth
.ce_type
) in
228 (method_name
, pos) :: acc
231 let acc = add_meth class_.tc_methods
acc in
232 let acc = add_meth class_.tc_smethods
acc in
236 | IClass
class_name ->
237 Option.value ~default
:[] begin Naming_heap.TypeIdHeap.get
class_name >>=
238 function (_
, `Class
) -> Typing_lazy_heap.get_class
tcopt class_name >>=
239 fun class_ -> Some
([(class_name, class_.tc_pos
)])
240 | (_
, `Typedef
) -> Typing_lazy_heap.get_typedef
tcopt class_name >>=
241 fun type_
-> Some
([class_name, type_
.td_pos
])
243 | IFunction fun_name
->
244 begin match Typing_lazy_heap.get_fun
tcopt fun_name
with
245 | Some fun_
-> [fun_name
, fun_
.ft_pos
]
249 | IMember
(Subclasses_of _
, _
)
250 | IMember
(_
, (Property _
| Class_const _
| Typeconst _
)) ->
251 (* this code path is used only in ServerRefactor, we can update it at some
255 let find_references tcopt workers target include_defs
257 let fileinfo_l = Relative_path.Set.fold
files ~f
:begin fun fn acc ->
258 match Relative_path.Map.get files_info
fn with
259 | Some fi
-> (fn, fi
) :: acc
263 if List.length
fileinfo_l < 10 then
264 find_refs tcopt target
[] fileinfo_l
266 parallel_find_refs workers
fileinfo_l target
tcopt
269 let defs = get_definitions tcopt target
in
270 List.rev_append
defs results
274 let get_dependent_files_function _workers f_name
=
275 (* This is performant enough to not need to go parallel for now *)
276 get_deps_set_function f_name
278 let get_dependent_files_gconst _workers cst_name
=
279 (* This is performant enough to not need to go parallel for now *)
280 get_deps_set_gconst cst_name
282 let get_dependent_files _workers input_set
=
283 (* This is performant enough to not need to go parallel for now *)
284 get_deps_set input_set
286 let result_to_ide_message x
=
287 let open Ide_message
in
288 Find_references_response
(
289 Option.map x ~f
:begin fun (symbol_name
, references
) ->
291 List.map
references ~f
:Ide_api_types.pos_to_file_range
in
292 {symbol_name
; references}