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.
14 module Result_set
= Caml.Set.Make
(struct
15 type t
= Relative_path.t
SymbolOccurrence.t
16 let compare = Pervasives.compare
19 let is_target target_line target_char
{ pos
; _
} =
20 let l, start
, end_
= Pos.info_pos pos
in
21 l = target_line
&& start
<= target_char
&& target_char
- 1 <= end_
23 let process_class_id ?
(is_declaration
=false) (pos
, cid
) =
24 Result_set.singleton
{
31 let clean_member_name name
= String_utils.lstrip name
"$"
33 let process_member ?
(is_declaration
=false) c_name id ~is_method ~is_const
=
34 let member_name = (snd id
) in
36 if is_const
then ClassConst
(c_name
, member_name)
37 else if is_method
then Method
(c_name
, member_name)
38 else Property
(c_name
, member_name)
40 Result_set.singleton
{
41 name
= (c_name ^
"::" ^
(clean_member_name member_name));
47 let process_fun_id ?
(is_declaration
=false) id
=
48 Result_set.singleton
{
55 let process_global_const ?
(is_declaration
=false) id
=
56 Result_set.singleton
{
63 let process_lvar_id ?
(is_declaration
=false) id
=
64 Result_set.singleton
{
71 let process_typeconst ?
(is_declaration
=false) (class_name
, tconst_name
, pos
) =
72 Result_set.singleton
{
73 name
= class_name ^
"::" ^ tconst_name
;
74 type_ = Typeconst
(class_name
, tconst_name
);
79 let process_class class_
=
80 let acc = process_class_id ~is_declaration
:true class_
.Tast.c_name
in
81 let c_name = snd class_
.Tast.c_name in
82 let constructor, static_methods
, methods
= Tast.split_methods class_
in
83 let all_methods = static_methods
@ methods
in
84 let acc = List.fold
all_methods ~init
:acc ~f
:begin fun acc method_
->
85 Result_set.union
acc @@
86 process_member c_name method_
.Tast.m_name
87 ~is_declaration
:true ~is_method
:true ~is_const
:false
89 let all_props = class_
.Tast.c_vars
in
90 let acc = List.fold
all_props ~init
:acc ~f
:begin fun acc prop
->
91 Result_set.union
acc @@
92 process_member c_name prop
.Tast.cv_id
93 ~is_declaration
:true ~is_method
:false ~is_const
:false
95 let acc = List.fold class_
.Tast.c_consts ~init
:acc ~f
:begin fun acc (_
, const_id
, _
) ->
96 Result_set.union
acc @@
97 process_member c_name const_id
98 ~is_declaration
:true ~is_method
:false ~is_const
:true
100 let acc = List.fold class_
.Tast.c_typeconsts ~init
:acc ~f
:begin fun acc typeconst
->
101 let pos, tconst_name
= typeconst
.Tast.c_tconst_name
in
102 Result_set.union
acc @@
103 process_typeconst ~is_declaration
:true (c_name, tconst_name
, pos)
105 (* We don't check anything about xhp attributes, so the hooks won't fire when
106 typechecking the class. Need to look at them individually. *)
107 let acc = List.fold class_
.Tast.c_xhp_attr_uses ~init
:acc ~f
:begin fun acc attr
->
109 | _
, Aast.Happly
(cid
, _
) ->
110 Result_set.union
acc @@
114 match constructor with
116 let id = fst method_
.Tast.m_name
, SN.Members.__construct
in
117 Result_set.union
acc @@
118 process_member c_name id
119 ~is_declaration
:true ~is_method
:true ~is_const
:false
122 let typed_member_id env receiver_ty mid ~is_method ~is_const
=
123 Tast_env.get_class_ids env receiver_ty
124 |> List.map ~f
:(fun cid
-> process_member cid mid ~is_method ~is_const
)
125 |> List.fold ~init
:Result_set.empty ~f
:Result_set.union
127 let typed_method = typed_member_id ~is_method
:true ~is_const
:false
128 let typed_const = typed_member_id ~is_method
:false ~is_const
:true
129 let typed_property = typed_member_id ~is_method
:false ~is_const
:false
130 let typed_constructor env ty
pos =
131 typed_method env ty
(pos, SN.Members.__construct
)
133 let typed_class_id env ty
pos =
134 Tast_env.get_class_ids env ty
135 |> List.map ~f
:(fun cid
-> process_class_id (pos, cid
))
136 |> List.fold ~init
:Result_set.empty ~f
:Result_set.union
138 (* When we detect a function reference encapsulated in a string,
139 * we want to update the function reference without removing the apostrophes.
141 * Example: class_meth(myclass::class, 'myfunc');
143 * In this case, we only want to replace the text 'myfunc' - so we need
144 * to shrink our positional data by the apostrophes. *)
145 let remove_apostrophes_from_function_eval (mid
: Ast_defs.pstring
): Ast_defs.pstring
=
146 let pos, member_name = mid
in
147 let new_pos = (Pos.shrink_by_one_char_both_sides
pos) in
148 (new_pos, member_name)
150 let visitor = object (self
)
151 inherit [_
] Tast_visitor.reduce
as super
153 method zero
= Result_set.empty
154 method plus
= Result_set.union
156 method! on_expr env expr
=
157 let pos = fst
(fst expr
) in
158 let (+) = self#plus
in
161 | Tast.New
(((p
, ty
), _
), _
, _
, _
, _
) ->
162 typed_constructor env ty p
163 | Tast.Obj_get
(((_
, ty
), _
), (_
, Tast.Id mid
), _
) ->
164 typed_property env ty mid
165 | Tast.Class_const
(((_
, ty
), _
), mid
) ->
166 typed_const env ty mid
167 | Tast.Class_get
(((_
, ty
), _
), Tast.CGstring mid
) ->
168 typed_property env ty mid
169 | Tast.Xml
(cid
, _
, _
) ->
172 process_fun_id (pos, "\\HH\\"^
SN.SpecialFunctions.fun_
) +
173 process_fun_id (remove_apostrophes_from_function_eval id)
174 | Tast.Method_id
(((_
, ty
), _
), mid
) ->
175 process_fun_id (pos, "\\HH\\"^
SN.SpecialFunctions.inst_meth
) +
176 typed_method env ty
(remove_apostrophes_from_function_eval mid
)
177 | Tast.Smethod_id
((_
, cid
) as pcid
, mid
) ->
178 process_fun_id (pos, "\\HH\\"^
SN.SpecialFunctions.class_meth
) +
179 process_class_id pcid
+
180 process_member cid
(remove_apostrophes_from_function_eval mid
)
181 ~is_method
:true ~is_const
:false
182 | Tast.Method_caller
((_
, cid
) as pcid
, mid
) ->
183 process_fun_id (pos, "\\HH\\"^
SN.SpecialFunctions.meth_caller
) +
184 process_class_id pcid
+
185 process_member cid
(remove_apostrophes_from_function_eval mid
)
186 ~is_method
:true ~is_const
:false
189 acc + super#on_expr env expr
191 method! on_class_id env
((p
, ty
), cid
) =
193 | Tast.CIexpr expr
->
194 (* We want to special case this because we want to get the type of the
195 inner expression, which will have a type like `classname<Foo>`, rather
196 than the resolved type of the class ID, which will have a type like
197 `Foo`. Since the class ID and the inner expression have the same span,
198 it is not easy to distinguish them later. *)
199 self#on_expr env expr
200 | _
-> typed_class_id env ty p
202 method! on_Call env ct e tal el uel
=
203 (* For Id, Obj_get (with an Id member), and Class_const, we don't want to
204 * use the result of `self#on_expr env e`, since it would record a
205 * property, class const, or global const access instead of a method call.
206 * So instead of invoking super#on_Call, we reimplement it here, omitting
207 * `self#on_expr env e` when necessary. *)
208 let (+) = self#plus
in
209 let cta = self#on_call_type env ct
in
214 | Tast.Obj_get
(((_
, ty
), _
) as obj
, (_
, Tast.Id mid
), _
) ->
215 self#on_expr env obj
+ typed_method env ty mid
216 | Tast.Class_const
(((_
, ty
), _
) as cid
, mid
) ->
217 self#on_class_id env cid
+ typed_method env ty mid
218 | _
-> self#on_expr env e
220 let tala = self#on_list self#on_targ env tal
in
221 let ela = self#on_list self#on_expr env el
in
222 let uela = self#on_list self#on_expr env uel
in
223 cta + ea + tala + ela + uela
225 method! on_Haccess env root ids
=
227 Tast_env.referenced_typeconsts env root ids
228 |> List.map ~f
:process_typeconst
229 |> List.fold ~init
:self#zero ~f
:self#plus
231 self#plus
acc (super#on_Haccess env root ids
)
233 method! on_Lvar env
(pos, id) =
234 let acc = process_lvar_id (pos, Local_id.get_name
id) in
235 self#plus
acc (super#on_Lvar env
(pos, id))
237 method! on_fun_param env param
=
238 let acc = process_lvar_id (param
.Tast.param_pos
, param
.Tast.param_name
) in
239 self#plus
acc (super#on_fun_param env param
)
241 method! on_Happly env sid hl
=
242 let acc = process_class_id sid
in
243 self#plus
acc (super#on_Happly env sid hl
)
245 method! on_catch env
(sid
, lid
, block
) =
246 let acc = process_class_id sid
in
247 self#plus
acc (super#on_catch env
(sid
, lid
, block
))
249 method! on_class_ env class_
=
252 let acc = process_class class_
in
254 Enums implicitly extend BuiltinEnum. However, BuiltinEnums also extend
255 the same Enum as a type parameter.
257 Ex: enum Size extends BuiltinEnum<Size> { ... }
259 This will return the definition of the enum twice when finding references
260 on it. As a result, we set the extends property of an enum's tast to an empty list.
262 let class_ = match class_.c_extends
with
264 Happly
((_
, builtin_enum
), [(_
,
265 Happly
(c_name, []))]
268 when c_name = class_.c_name && builtin_enum
= Naming_special_names.Classes.cHH_BuiltinEnum
269 -> { class_ with c_extends
= [] }
272 self#plus
acc (super#on_class_ env
class_)
274 method! on_fun_ env fun_
=
275 let acc = process_fun_id ~is_declaration
:true fun_
.Tast.f_name
in
276 self#plus
acc (super#on_fun_ env fun_
)
278 method! on_typedef env typedef
=
279 let acc = process_class_id ~is_declaration
:true typedef
.Tast.t_name
in
280 self#plus
acc (super#on_typedef env typedef
)
282 method! on_gconst env cst
=
283 let acc = process_global_const ~is_declaration
:true cst
.Tast.cst_name
in
284 self#plus
acc (super#on_gconst env cst
)
286 method! on_Id env
id =
287 let acc = process_global_const id in
288 self#plus
acc (super#on_Id env
id)
290 method! on_Obj_get env obj member ognf
=
291 match snd member
with
293 (* Don't visit this Id, since we would record it as a gconst access. *)
294 let obja = self#on_expr env obj
in
295 let ognfa = self#on_og_null_flavor env ognf
in
297 | _
-> super#on_Obj_get env obj member ognf
299 method! on_SFclass_const env cid mid
=
300 let (+) = Result_set.union
in
301 process_class_id cid
+
302 process_member (snd cid
) mid ~is_method
:false ~is_const
:true +
303 super#on_SFclass_const env cid mid
306 let all_symbols tast
=
308 |> Result_set.elements
310 let go tast line char
=
312 |> List.filter ~f
:(is_target line char
)