/
signatureSearchService.ml
256 lines (232 loc) · 8.67 KB
/
signatureSearchService.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
(**
* Copyright (c) 2018, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the "hack" directory of this source tree.
*
*)
open Core_kernel
module Index = SignatureSearchIndex
module Parser = SignatureSearchParser
type indexable_type =
| ITprim of Nast.tprim
| ITapply of string
| IToption of indexable_type
| ITdarray
| ITvarray
| ITvarray_or_darray
| ITmixed
| ITnonnull
| ITdynamic
type search_term =
| Arity of int
| Parameter of {position: int; type_: indexable_type}
| Return_type of indexable_type
let index: Index.t = Index.make ()
let add_backslash type_name =
if type_name <> "" && type_name.[0] = '\\'
then type_name
else "\\" ^ type_name
let add_question_mark type_name =
if type_name <> "" && type_name.[0] = '?'
then type_name
else "?" ^ type_name
let rec indexable_type_to_string = function
| IToption it_type -> add_question_mark (indexable_type_to_string it_type)
| ITapply type_name -> type_name
| ITdarray -> "darray"
| ITvarray -> "varray"
| ITvarray_or_darray -> "varray_or_darray"
| ITmixed -> "mixed"
| ITnonnull -> "nonnull"
| ITdynamic -> "dynamic"
| ITprim Nast.Tnull -> "null"
| ITprim Nast.Tvoid -> "void"
| ITprim Nast.Tint -> "int"
| ITprim Nast.Tbool -> "bool"
| ITprim Nast.Tfloat -> "float"
| ITprim Nast.Tstring -> "string"
| ITprim Nast.Tnum -> "num"
| ITprim Nast.Tresource -> "resource"
| ITprim Nast.Tarraykey -> "arraykey"
| ITprim Nast.Tnoreturn -> "noreturn"
(** Convert a type specifier parsed by the signature search query parser to our
indexable_type representation. *)
let rec type_specifier_to_indexable_type ty_spec =
match ty_spec with
| Parser.TSoption type_ -> IToption (type_specifier_to_indexable_type type_)
| Parser.TSsimple type_ ->
match type_ with
| "darray" -> ITdarray
| "varray" -> ITvarray
| "varray_or_darray" -> ITvarray_or_darray
| "mixed" -> ITmixed
| "nonnull" -> ITnonnull
| "dynamic" -> ITdynamic
| "void" -> ITprim Nast.Tvoid
| "int" -> ITprim Nast.Tint
| "bool" -> ITprim Nast.Tbool
| "float" -> ITprim Nast.Tfloat
| "string" -> ITprim Nast.Tstring
| "num" -> ITprim Nast.Tnum
| "resource" -> ITprim Nast.Tresource
| "arraykey" -> ITprim Nast.Tarraykey
| "noreturn" -> ITprim Nast.Tnoreturn
| _ -> ITapply (add_backslash type_) (* Class or interface type *)
(** Convert a decl ty to the simplified representation of types used by the
signature search service. If the type is not one that the service can index,
return None. *)
let rec decl_ty_to_indexable_type ty =
let open Typing_defs in
match snd ty with
| Tapply ((_, "\\Awaitable"), [ty]) -> decl_ty_to_indexable_type ty
| Tapply ((_, type_name), _) -> Some (ITapply type_name)
| Tprim prim -> Some (ITprim prim)
| Tdarray _ -> Some ITdarray
| Tvarray _ -> Some ITvarray
| Tvarray_or_darray _ -> Some ITvarray_or_darray
| Tmixed -> Some ITmixed
| Tnonnull -> Some ITnonnull
| Tdynamic -> Some ITdynamic
| Toption ty ->
begin match decl_ty_to_indexable_type ty with
| Some ty -> Some (IToption ty)
| None -> None
end
(* All other types are types that we currently don't handle in the index. *)
| _ -> None
let expand_to_optional_types type_list =
let opt_ty_list = List.map type_list ~f:(fun type_ -> IToption type_) in
type_list @ opt_ty_list
let expand_to_supertypes (tcopt:TypecheckerOptions.t) type_ =
let open Nast in
let type_list =
match type_ with
| ITprim Tint -> [ITprim Tnum; ITprim Tarraykey; ITprim Tint]
| ITprim Tfloat -> [ITprim Tnum; ITprim Tfloat]
| ITprim Tstring -> [ITprim Tstring; ITprim Tarraykey]
| ITapply type_ ->
begin match Typing_lazy_heap.get_class tcopt type_ with
| Some cls ->
let super_types = Sequence.to_list (Typing_classes_heap.all_ancestors cls) in
let super_types = List.map super_types ~f:(fun (key, _) -> ITapply key) in
ITapply type_ :: super_types
| None -> [ITapply type_]
end
| type_ -> [type_]
in
type_list @ (expand_to_optional_types type_list)
let search_term_to_string search_term =
match search_term with
| Arity arity -> Printf.sprintf "arity=%d" arity
| Parameter { position; type_ } ->
Printf.sprintf "arg%d<:%s" position (indexable_type_to_string type_)
| Return_type return_type ->
Printf.sprintf "ret<:%s" (indexable_type_to_string return_type)
let create_search_terms param_types return_types =
let param_types = List.mapi param_types ~f:(fun i type_ ->
Parameter {position = i + 1; type_}
) in
let return_types = List.map return_types ~f:(fun type_ -> Return_type type_) in
let arity = Arity (List.length param_types) in
arity :: param_types @ return_types
let fun_to_search_terms tcopt fun_name =
match Typing_lazy_heap.get_fun tcopt fun_name with
| None -> None
| Some funs_t ->
let params = funs_t.Typing_defs.ft_params in
let param_types = List.map params ~f:(fun fun_param ->
decl_ty_to_indexable_type fun_param.Typing_defs.fp_type
) in
(* If any param_type is not a type we can index, decl_ty_to_indexable_type will return
None, so here Option.all will return None. *)
let param_types = Option.all param_types in
let return_type = decl_ty_to_indexable_type funs_t.Typing_defs.ft_ret in
(* Transform return_types into a list, append super_types if applicable
Append subtypes of primitives num and arraykey *)
let return_types = Option.map return_type (expand_to_supertypes tcopt) in
(* If the return type and all param_types are valid (that is, they are all
types that we can index), we have a valid signature *)
match param_types, return_types with
| Some param_types, Some return_type ->
Some (create_search_terms param_types return_type)
| _ -> None
let add_function tcopt fun_name =
Errors.ignore_ (fun () ->
fun_to_search_terms tcopt fun_name
|> Option.iter ~f:begin fun search_terms ->
search_terms
|> List.map ~f:search_term_to_string
|> Index.update index fun_name
end
)
let build tcopt naming_table =
Hh_logger.log "Building Search Index";
Naming_table.iter naming_table (fun _ value ->
let {FileInfo.funs; _ } = value in
List.iter funs ~f:(fun (pos, fun_name) ->
let path =
match pos with
| FileInfo.Full pos -> Pos.filename pos
| FileInfo.File (_, path) -> path
in
let prefix = Relative_path.prefix path in
(* Functions that do not belong to the Hack Standard Library are filtered out of
SignatureSearchService *)
if prefix = Relative_path.Hhi
|| String_utils.string_starts_with fun_name "\\HH\\Lib\\"
then add_function tcopt fun_name
else ()
)
);
Hh_logger.log "Search index is ready"
let query_to_search_terms (tcopt:TypecheckerOptions.t) query =
let open Parser in
let open Index in
let {function_params = params; function_output = ret} = query in
let query_list = List.filter_mapi params ~f:(fun i parameter ->
match parameter with
| QTtype ty_spec ->
let type_ = type_specifier_to_indexable_type ty_spec in
let types = expand_to_supertypes tcopt type_ in
Some (Or (List.map types ~f:(fun type_ ->
let term = Parameter {position = i + 1; type_} in
Term (search_term_to_string term)
)))
| QTwildcard -> None
) in
let query_list = match ret with
| QTwildcard -> query_list
| QTtype ty_spec ->
let type_ = type_specifier_to_indexable_type ty_spec in
Term (search_term_to_string (Return_type type_)) :: query_list
in
let arity = Arity (List.length params) in
let query_list = Term (search_term_to_string arity) :: query_list in
And query_list
let go tcopt query =
Errors.ignore_ (fun () ->
let keys = query_to_search_terms tcopt query in
let results = Index.get index keys in
List.filter_map results ~f:(fun fun_name ->
let open Option.Monad_infix in
Naming_heap.FunPosHeap.get fun_name
>>= function
| FileInfo.File (_, fn) ->
Parser_heap.find_fun_in_file tcopt fn fun_name
>>| fun fun_ ->
let pos = fst fun_.Ast.f_name in
{
SearchUtils.name = fun_name;
pos;
result_type = HackSearchService.Function;
}
| FileInfo.Full pos ->
Some {
SearchUtils.name = fun_name;
pos;
result_type = HackSearchService.Function;
}
)
)