Type idx invocations in TAST
[hiphop-php.git] / hphp / hack / test / integration_ml / test_server_hover.ml
blob07223a3a2d4e52d2d6e1fab9bd87ded4941ef0b9
1 (**
2 * Copyright (c) 2016, Facebook, Inc.
3 * All rights reserved.
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.
9 *)
11 open Hh_core
12 open HoverService
14 module Test = Integration_test_base
16 let pos_at (line1, column1) (line2, column2) =
17 Some (Pos.make_from_file_pos
18 Relative_path.default
19 (File_pos.of_line_column_offset line1 (column1 - 1) 0)
20 (File_pos.of_line_column_offset line2 column2 0))
22 let class_members = "<?hh // strict
23 abstract class ClassMembers {
24 public async function genDoStuff(): Awaitable<void> {}
26 public string $public = 'public';
27 protected string $protected = 'protected';
28 private string $private = 'private';
30 public static string $staticVar = 'staticVar';
32 public abstract function abstractMethod(): string;
34 public final function finalMethod(string $arg): void {}
36 protected final static async function genLotsOfModifiers(): Awaitable<void> {}
38 public async function exerciseClassMembers(): Awaitable<void> {
39 await $this->genDoStuff();
40 // ^18:18
41 $this->public;
42 // ^20:12
43 $this->protected;
44 // ^22:12
45 $this->private;
46 // ^24:12
47 ClassMembers::$staticVar;
48 // ^26:19
49 $this->abstractMethod();
50 // ^28:12
51 $this->finalMethod(\"arg\");
52 // ^30:12
53 await ClassMembers::genLotsOfModifiers();
54 // ^32:11 ^32:25
58 let class_members_cases = [
59 ("class_members.php", 18, 18), [
61 snippet = "public async function genDoStuff(): Awaitable<void>";
62 addendum = ["Full name: `ClassMembers::genDoStuff`"];
63 pos = pos_at (18, 18) (18, 27);
66 ("class_members.php", 20, 12), [
68 snippet = "public string ClassMembers::public";
69 addendum = [];
70 pos = pos_at (20, 12) (20, 17);
73 ("class_members.php", 22, 12), [
75 snippet = "protected string ClassMembers::protected";
76 addendum = [];
77 pos = pos_at (22, 12) (22, 20);
80 ("class_members.php", 24, 12), [
82 snippet = "private string ClassMembers::private";
83 addendum = [];
84 pos = pos_at (24, 12) (24, 18);
87 ("class_members.php", 26, 19), [
89 snippet = "public static string ClassMembers::staticVar";
90 addendum = [];
91 pos = pos_at (26, 19) (26, 28);
94 ("class_members.php", 28, 12), [
96 snippet = "public abstract function abstractMethod(): string";
97 addendum = ["Full name: `ClassMembers::abstractMethod`"];
98 pos = pos_at (28, 12) (28, 25);
101 ("class_members.php", 30, 12), [
103 snippet = "public final function finalMethod(string $arg): void";
104 addendum = ["Full name: `ClassMembers::finalMethod`"];
105 pos = pos_at (30, 12) (30, 22);
108 ("class_members.php", 32, 11), [
110 snippet = "abstract class ClassMembers";
111 addendum = [];
112 pos = pos_at (32, 11) (32, 22);
115 ("class_members.php", 32, 25), [
117 snippet = "protected final static async\n\
118 function genLotsOfModifiers(): Awaitable<void>";
119 addendum = ["Full name: `ClassMembers::genLotsOfModifiers`"];
120 pos = pos_at (32, 25) (32, 42);
125 let classname_call = "<?hh // strict
126 class ClassnameCall {
127 static function foo(): int {
128 return 0;
132 function call_foo(): void {
133 ClassnameCall::foo();
134 // ^9:4 ^9:18
137 let classname_call_cases = [
138 ("classname_call.php", 9, 4), [{
139 snippet = "class ClassnameCall";
140 addendum = [];
141 pos = pos_at (9, 3) (9, 15);
143 ("classname_call.php", 9, 18), [{
144 snippet = "static function foo(): int";
145 addendum = ["Full name: `ClassnameCall::foo`"];
146 pos = pos_at (9, 18) (9, 20);
150 let chained_calls = "<?hh // strict
151 class ChainedCalls {
152 public function foo(): this {
153 return $this;
157 function test(): void {
158 $myItem = new ChainedCalls();
159 $myItem
160 ->foo()
161 ->foo()
162 ->foo();
163 // ^13:8
166 let chained_calls_cases = [
167 ("chained_calls.php", 13, 8), [
169 snippet = "public function foo(): ChainedCalls";
170 addendum = ["Full name: `ChainedCalls::foo`"];
171 pos = pos_at (13, 7) (13, 9);
176 let multiple_potential_types = "<?hh // strict
177 class C1 { public function foo(): int { return 5; } }
178 class C2 { public function foo(): string { return 's'; } }
179 function test_multiple_type(C1 $c1, C2 $c2, bool $cond): arraykey {
180 $x = $cond ? $c1 : $c2;
181 return $x->foo();
182 // ^6:11^6:16
185 let multiple_potential_types_cases = [
186 ("multiple_potential_types.php", 6, 11), [{
187 snippet = "(C1 | C2)";
188 addendum = [];
189 pos = None;
191 ("multiple_potential_types.php", 6, 16), [
193 snippet = "((function(): string) | (function(): int))";
194 addendum = [];
195 pos = None;
198 snippet = "((function(): string) | (function(): int))";
199 addendum = [];
200 pos = None;
205 let classname_variable = "<?hh // strict
206 class ClassnameVariable {
207 public static function foo(): void {}
210 function test_classname(): void {
211 $cls = ClassnameVariable::class;
212 $cls::foo();
213 // ^8:4 ^8:10
216 let classname_variable_cases = [
217 ("classname_variable.php", 8, 4), [{
218 snippet = "classname<ClassnameVariable>";
219 addendum = [];
220 pos = pos_at (8, 3) (8, 6);
223 ("classname_variable.php", 8, 10), [{
224 snippet = "public static function foo(): void";
225 addendum = ["Full name: `ClassnameVariable::foo`"];
226 pos = pos_at (8, 9) (8, 11);
230 let docblock = "<?hh // strict
232 // Multiline
233 // function
234 // doc block.
235 function queryDocBlocks(): void {
236 DocBlock::doStuff();
237 //^7:3 ^7:13
238 queryDocBlocks();
239 //^9:3
240 DocBlock::preserveIndentation();
241 // ^11:13
242 DocBlock::leadingStarsAndMDList();
243 // ^13:13
244 DocBlock::manyLineBreaks();
245 // ^15:13
246 $x = new DocBlockOnClassButNotConstructor();
247 // ^17:12
250 function docblockReturn(): DocBlockBase {
251 // ^21:28
252 $x = new DocBlockBase();
253 // ^23:12
254 return new DocBlockDerived();
255 // ^25:14
258 /* Class doc block.
259 This
261 block
263 multiple
264 lines. */
265 class DocBlock {
266 /** Method doc block with double star. */
267 public static function doStuff(): void {}
269 /** Multiline doc block with
270 a certain amount of
271 indentation
272 we want to preserve. */
273 public static function preserveIndentation(): void {}
275 /** Multiline doc block with
276 * leading stars, as well as
277 * * a Markdown list!
278 * and we'd really like to preserve the Markdown list while getting rid of
279 * the other stars. */
280 public static function leadingStarsAndMDList(): void {}
283 * This method has many line breaks, which
285 * someone might use if they wanted
287 * to have separate paragraphs
289 * in Markdown.
291 public static function manyLineBreaks(): void {}
295 * Class doc block for a class whose constructor doesn't have a doc block.
297 final class DocBlockOnClassButNotConstructor {
298 public function __construct() {}
301 /* DocBlockBase: class doc block. */
302 class DocBlockBase {
303 /* DocBlockBase: constructor doc block. */
304 public function __construct() {}
307 /* DocBlockDerived: extends a class with a constructor, but doesn't have one of
308 its own. */
309 class DocBlockDerived extends DocBlockBase {}
312 let docblockCases = [
313 ("docblock.php", 7, 3), [
315 snippet = "class DocBlock";
316 addendum = ["Class doc block.\n\
317 This\n\
318 doc\n\
319 block\n\
320 has\n\
321 multiple\n\
322 lines."];
323 pos = pos_at (7, 3) (7, 10);
326 ("docblock.php", 7, 13), [
328 snippet = "public static function doStuff(): void";
329 addendum = ["Method doc block with double star."; "Full name: `DocBlock::doStuff`"];
330 pos = pos_at (7, 13) (7, 19);
333 ("docblock.php", 9, 3), [
335 snippet = "function queryDocBlocks(): void";
336 addendum = ["Multiline\n\
337 function\n\
338 doc block."];
339 pos = pos_at (9, 3) (9, 16);
342 ("docblock.php", 11, 13), [
344 snippet = "public static function preserveIndentation(): void";
345 addendum = ["Multiline doc block with
346 a certain amount of
347 indentation
348 we want to preserve."; "Full name: `DocBlock::preserveIndentation`"];
349 pos = pos_at (11, 13) (11, 31);
352 ("docblock.php", 13, 13), [
354 snippet = "public static function leadingStarsAndMDList(): void";
355 addendum = ["Multiline doc block with
356 leading stars, as well as
357 * a Markdown list!
358 and we'd really like to preserve the Markdown list while getting rid of
359 the other stars."; "Full name: `DocBlock::leadingStarsAndMDList`"];
360 pos = pos_at (13, 13) (13, 33);
363 ("docblock.php", 15, 13), [
365 snippet = "public static function manyLineBreaks(): void";
366 addendum = [
367 "\n\
368 This method has many line breaks, which\n\
370 someone might use if they wanted\n\
372 to have separate paragraphs\n\
374 in Markdown.\n";
375 "Full name: `DocBlock::manyLineBreaks`"];
376 pos = pos_at (15, 13) (15, 26);
379 ("docblock.php", 17, 12), [
381 snippet =
382 "public function __construct(): _";
383 (* This is because we set `last_line` to 0 in Docblock_finder, but we
384 can't get a proper `last_line` value without generating a TAST of the
385 file that contains this class. I'll be fixing this in a later diff.
386 -wipi *)
387 addendum = [
388 "\nClass doc block for a class whose constructor doesn't have a doc block.\n";
389 "Full name: `DocBlockOnClassButNotConstructor::__construct`";
391 pos = pos_at (17, 8) (17, 45);
394 ("docblock.php", 21, 28), [
396 snippet = "DocBlockBase";
397 addendum = ["DocBlockBase: class doc block."];
398 pos = pos_at (21, 28) (21, 39);
401 ("docblock.php", 23, 12), [
403 snippet = "public function __construct(): _";
404 addendum = [
405 "DocBlockBase: constructor doc block.";
406 "Full name: `DocBlockBase::__construct`";
408 pos = pos_at (23, 8) (23, 25);
411 ("docblock.php", 25, 14), [
413 snippet = "public function __construct(): _";
414 addendum = [
415 "DocBlockBase: constructor doc block.";
416 "Full name: `DocBlockBase::__construct`";
418 pos = pos_at (25, 10) (25, 30);
423 let special_cases = "<?hh // strict
424 function special_cases(): void {
425 idx(array(1, 2, 3), 1);
426 //^3:3
430 let special_cases_cases = [
431 ("special_cases.php", 3, 3), [
433 snippet = "\
434 function idx(
435 ?KeyedContainer<int, "^"
436 ?int> $collection, "^"
437 ?int $index
438 ): ?int";
439 addendum = [];
440 pos = pos_at (3, 3) (3, 24);
445 let bounded_generic_fun = "<?hh // strict
446 abstract class Base {}
447 final class C extends Base {}
448 function bounded_generic_fun<T as Base>(T $x): void {
450 //^5:3
451 if ($x instanceof C) {
452 // ^7:7
454 // ^9:5
457 //^12:3
461 let bounded_generic_fun_cases = [
462 ("bounded_generic_fun.php", 5, 3), [{
463 snippet = "T\nwhere T as Base";
464 addendum = [];
465 pos = pos_at (5, 3) (5, 4);
467 ("bounded_generic_fun.php", 7, 7), [{
468 snippet = "T\nwhere T as Base";
469 addendum = [];
470 pos = pos_at (7, 7) (7, 8);
472 ("bounded_generic_fun.php", 9, 5), [{
473 snippet = "C";
474 addendum = [];
475 pos = pos_at (9, 5) (9, 6);
477 ("bounded_generic_fun.php", 12, 3), [{
478 snippet = "(C | T)\nwhere T as Base";
479 addendum = [];
480 pos = pos_at (12, 3) (12, 4);
484 let files = [
485 "class_members.php", class_members;
486 "classname_call.php", classname_call;
487 "chained_calls.php", chained_calls;
488 "classname_variable.php", classname_variable;
489 "docblock.php", docblock;
490 "special_cases.php", special_cases;
491 "bounded_generic_fun.php", bounded_generic_fun;
494 let cases =
495 special_cases_cases
496 @ docblockCases
497 @ class_members_cases
498 @ classname_call_cases
499 @ chained_calls_cases
500 @ classname_variable_cases
501 @ bounded_generic_fun_cases
503 let () =
504 let env =
505 Test.setup_server ()
506 ~hhi_files:(Hhi.get_raw_hhi_contents () |> Array.to_list)
508 let env = Test.setup_disk env files in
510 Test.assert_no_errors env;
512 let failed_cases =
513 List.filter_map cases ~f:begin fun ((file, line, col), expectedHover) ->
514 let list_to_string hover_list =
515 let string_list = hover_list |> List.map ~f:HoverService.string_of_result in
516 let inner = match string_list |> List.reduce ~f:(fun a b -> a ^ "; " ^ b) with
517 | None -> ""
518 | Some s -> s
520 Printf.sprintf "%s:%d:%d: [%s]" file line col inner
522 let fn = ServerUtils.FileName ("/" ^ file) in
523 let hover = ServerHover.go env (fn, line, col) in
524 let expected = list_to_string expectedHover in
525 let actual = list_to_string hover in
526 if expected <> actual then Some (expected, actual) else None
527 end in
528 match failed_cases with
529 | [] -> ()
530 | cases ->
531 let display_case (expected, actual) =
532 Printf.sprintf "Expected:\n%s\nGot:\n%s" expected actual
534 Test.fail @@ String.concat "\n\n" (List.map ~f:display_case cases)