Skip to content

Commit 415b6f8

Browse files
Andrew Kennedyfacebook-github-bot
authored andcommitted
Disallow override of async by non-async
Summary: There is a consensus on the Hack Pack workchat that it's a mistake for Hack to allow `async` methods to be overridden by non-async methods that return `Awaitable`. This pattern is a problem for Sound Dynamic pessimisation, as it is an example of an override that doesn't preserve enforcement (as an `async` method returning `Awaitable<int>` enforces `int` but a non-async method only enforces `Awaitable`). Let's outlaw this in Hack. A separate diff for www asyncifies the ~220 instances of this pattern in www. Reviewed By: vassilmladenov Differential Revision: D38940712 fbshipit-source-id: 64b0e3d63feeff4a945486307e3ddee15984ef9d
1 parent e3daa15 commit 415b6f8

8 files changed

Lines changed: 77 additions & 1 deletion

File tree

hphp/hack/src/errors/error_codes.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,7 @@ module Typing = struct
744744
| InvalidMethCallerReadonlyReturn [@value 4464]
745745
| AbstractMemberInConcreteClass [@value 4465]
746746
| TraitNotUsed [@value 4466]
747+
| OverrideAsync [@value 4467]
747748
(* Add new Typing codes here! Comment out when deprecating. *)
748749
[@@deriving enum, show { with_path = false }]
749750

hphp/hack/src/errors/typing_error.ml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6566,6 +6566,10 @@ and Secondary : sig
65666566
pos: Pos_or_decl.t;
65676567
parent_pos: Pos_or_decl.t;
65686568
}
6569+
| Override_async of {
6570+
pos: Pos_or_decl.t;
6571+
parent_pos: Pos_or_decl.t;
6572+
}
65696573
| Override_lsb of {
65706574
pos: Pos_or_decl.t;
65716575
member_name: string;
@@ -6840,6 +6844,10 @@ end = struct
68406844
pos: Pos_or_decl.t;
68416845
parent_pos: Pos_or_decl.t;
68426846
}
6847+
| Override_async of {
6848+
pos: Pos_or_decl.t;
6849+
parent_pos: Pos_or_decl.t;
6850+
}
68436851
| Override_lsb of {
68446852
pos: Pos_or_decl.t;
68456853
member_name: string;
@@ -7290,6 +7298,16 @@ end = struct
72907298
in
72917299
(Error_code.OverrideFinal, reasons, [])
72927300

7301+
let override_async pos parent_pos =
7302+
let reasons =
7303+
lazy
7304+
[
7305+
(pos, "You cannot override this method with a non-async method");
7306+
(parent_pos, "It was declared as async");
7307+
]
7308+
in
7309+
(Error_code.OverrideAsync, reasons, [])
7310+
72937311
let override_lsb pos member_name parent_pos =
72947312
let reasons =
72957313
lazy
@@ -7716,6 +7734,8 @@ end = struct
77167734
Eval_result.single (ifc_policy_mismatch pos policy pos_super policy_super)
77177735
| Override_final { pos; parent_pos } ->
77187736
Eval_result.single (override_final pos parent_pos)
7737+
| Override_async { pos; parent_pos } ->
7738+
Eval_result.single (override_async pos parent_pos)
77197739
| Override_lsb { pos; member_name; parent_pos } ->
77207740
Eval_result.single (override_lsb pos member_name parent_pos)
77217741
| Multiple_concrete_defs

hphp/hack/src/errors/typing_error.mli

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,10 @@ module Secondary : sig
15661566
pos: Pos_or_decl.t;
15671567
parent_pos: Pos_or_decl.t;
15681568
}
1569+
| Override_async of {
1570+
pos: Pos_or_decl.t;
1571+
parent_pos: Pos_or_decl.t;
1572+
}
15691573
| Override_lsb of {
15701574
pos: Pos_or_decl.t;
15711575
member_name: string;

hphp/hack/src/oxidized/gen/error_codes.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// This source code is licensed under the MIT license found in the
44
// LICENSE file in the "hack" directory of this source tree.
55
//
6-
// @generated SignedSource<<cc7fdc5368fda8084df29558e67e5303>>
6+
// @generated SignedSource<<36e1333531376fab7153d3cdb7e9b09e>>
77
//
88
// To regenerate this file, run:
99
// hphp/hack/src/oxidized_regen.sh
@@ -577,6 +577,7 @@ pub enum Typing {
577577
InvalidMethCallerReadonlyReturn = 4464,
578578
AbstractMemberInConcreteClass = 4465,
579579
TraitNotUsed = 4466,
580+
OverrideAsync = 4467,
580581
}
581582
impl TrivialDrop for Typing {}
582583
arena_deserializer::impl_deserialize_in_arena!(Typing);

hphp/hack/src/typing/typing_extends.ml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,14 @@ let check_lateinit parent_class_elt class_elt on_error =
430430
parent_is_lateinit = get_ce_lateinit parent_class_elt;
431431
})
432432

433+
let check_async ft_parent ft_child parent_pos pos on_error =
434+
match (get_ft_async ft_parent, get_ft_async ft_child) with
435+
| (true, false) ->
436+
Errors.add_typing_error
437+
Typing_error.(
438+
apply_reasons ~on_error @@ Secondary.Override_async { pos; parent_pos })
439+
| _ -> ()
440+
433441
let check_xhp_attr_required env parent_class_elt class_elt on_error =
434442
if not (TypecheckerOptions.check_xhp_attribute (Env.get_tcopt env)) then
435443
()
@@ -864,6 +872,12 @@ let check_override
864872
class_elt.ce_origin
865873
member_name
866874
member_kind;
875+
check_async
876+
ft_parent
877+
ft_child
878+
(Typing_reason.to_pos r_parent)
879+
(Typing_reason.to_pos r_child)
880+
on_error;
867881
check_ambiguous_inheritance
868882
(check_subtype_methods
869883
env

hphp/hack/test/typecheck/async_function_untyped5.php.exp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,9 @@ The method `f` is not compatible with the overridden method (Typing[4341])
66
Expected `Awaitable<_>` (result of an `async` function)
77
File "async_function_untyped5.php", line 8, characters 24-26:
88
But got `int`
9+
File "async_function_untyped5.php", line 8, characters 19-19:
10+
The method `f` is not compatible with the overridden method (Typing[4341])
11+
File "async_function_untyped5.php", line 8, characters 19-19:
12+
You cannot override this method with a non-async method
13+
File "async_function_untyped5.php", line 4, characters 25-25:
14+
It was declared as async
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?hh
2+
// (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
3+
4+
class C {
5+
public async function foo():Awaitable<int> {
6+
return 3;
7+
}
8+
public function boo():Awaitable<int> {
9+
return $this->foo();
10+
}
11+
}
12+
class D extends C {
13+
public async function bar():Awaitable<int> {
14+
return 4;
15+
}
16+
// Illegal: do not permit non-async methods to override async ones
17+
public function foo():Awaitable<int> {
18+
return $this->bar();
19+
}
20+
// Legal: allow async methods to override non-async ones
21+
public async function boo():Awaitable<int> {
22+
return 5;
23+
}
24+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
File "async_override_bad.php", line 17, characters 19-21:
2+
The method `foo` is not compatible with the overridden method (Typing[4341])
3+
File "async_override_bad.php", line 17, characters 19-21:
4+
You cannot override this method with a non-async method
5+
File "async_override_bad.php", line 5, characters 25-27:
6+
It was declared as async

0 commit comments

Comments
 (0)