Skip to content

Commit 0a6eafc

Browse files
andrewjkennedyfacebook-github-bot
authored andcommitted
Simplify and improve type accesses through intersections and unions
Summary: Improve the expressivity of type constant access through intersections. Currently, the following code is rejected: ``` interface IBoundedJ { abstract const type TP as J; public function get():this::TP; } interface IBoundedK { abstract const type TP as K; } function test3<T as (IBoundedJ & IBoundedK)>(T $x):(J & K) { return $x->get(); } ``` It's safe to accept, if we merge the bounds on `TP` when projecting from `(IBoundedJ & IBoundedK)`. Although this example uses the experimental intersection type syntax, the same effect will happen for multiple bounds on generics, and type refinements. We also tighten up the code a bit, rejecting all kinds of projections through unions, unless accessing non-abstract type constants. Reviewed By: vsiles Differential Revision: D25615483 fbshipit-source-id: e3540807529a6ba22cc90763bef53c554a40af7e
1 parent f7fc6d7 commit 0a6eafc

2 files changed

Lines changed: 17 additions & 54 deletions

File tree

hphp/hack/src/typing/typing_taccess.ml

Lines changed: 15 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ type context = {
5858
type result =
5959
| Missing of (unit -> unit)
6060
| Exact of locl_ty
61-
| Abstract of string * string list * locl_ty option
61+
| Abstract of string * string list * TySet.t
6262

6363
let make_reason env id root r =
6464
Reason.Rtypeconst (r, id, Typing_print.error env root, get_reason root)
@@ -158,13 +158,13 @@ let create_root_from_type_constant ctx env root (_class_pos, class_name) class_
158158
if Cls.final class_ || Option.is_none ctx.base then
159159
(env, Exact ty)
160160
else
161-
(env, make_abstract env (Some ty))
161+
(env, make_abstract env (TySet.singleton ty))
162162
(* Abstract type constants with constraint *)
163163
| { ttc_constraint = Some cstr; _ } ->
164164
let (env, cstr) = Phase.localize ~ety_env env cstr in
165-
(env, make_abstract env (Some cstr))
165+
(env, make_abstract env (TySet.singleton cstr))
166166
(* Abstract type constant without constraint. *)
167-
| _ -> (env, make_abstract env None)))
167+
| _ -> (env, make_abstract env TySet.empty)))
168168

169169
(* Cheap intersection operation. Do not call Typing_intersection.intersect
170170
* as this calls into subtype which in turn calls into expand_with_env below
@@ -196,34 +196,15 @@ let rec union_results err rl =
196196
| r1 :: rl ->
197197
let r2 = union_results err rl in
198198

199-
(* Union is defined iff both are defined *)
199+
(* Union is defined iff both are defined concretely *)
200200
(match (r1, r2) with
201201
| (Missing err, _)
202202
| (_, Missing err) ->
203203
Missing err
204204
(* In essence, this says (C | D)::TP = (C::TP) | (D::TP) *)
205205
| (Exact ty1, Exact ty2) -> Exact (union ty1 ty2)
206-
| (Abstract (id1, ids1, tyopt1), Abstract (id2, ids2, tyopt2))
207-
when String.equal id1 id2 && List.equal String.equal ids1 ids2 ->
208-
(* Take the union of the bounds on abstract type constants *)
209-
let tyopt =
210-
match (tyopt1, tyopt2) with
211-
| (None, _)
212-
| (_, None) ->
213-
None
214-
| (Some ty1, Some ty2) -> Some (union ty1 ty2)
215-
in
216-
Abstract (id1, ids1, tyopt)
217-
(* If paths don't match, regard the type constant as missing *)
218-
| (Abstract _, Abstract _) -> Missing err
219-
| (Abstract (id, ids, tyopt), Exact ty)
220-
| (Exact ty, Abstract (id, ids, tyopt)) ->
221-
Abstract
222-
( id,
223-
ids,
224-
match tyopt with
225-
| None -> None
226-
| Some bound -> Some (union ty bound) ))
206+
(* We don't support projecting through any other kind of union *)
207+
| _ -> Missing err)
227208

228209
(* Given the results of projecting a type constant from types t1, ..., tn,
229210
* determine the result of projecting a type constant from type (t1 & ... & tn).
@@ -241,27 +222,9 @@ let rec intersect_results err rl =
241222
r
242223
(* In essence, we're saying (C & D)::TP = (C::TP) & (D::TP) *)
243224
| (Exact ty1, Exact ty2) -> Exact (intersect ty1 ty2)
244-
| (Abstract (id1, ids1, tyopt1), Abstract (id2, ids2, tyopt2))
245-
when String.equal id1 id2 && List.equal String.equal ids1 ids2 ->
246-
(* For abstract type constants, take the intersection of the bounds *)
247-
let tyopt =
248-
match (tyopt1, tyopt2) with
249-
| (None, None) -> None
250-
| (Some ty, None)
251-
| (None, Some ty) ->
252-
Some ty
253-
| (Some ty1, Some ty2) -> Some (intersect ty1 ty2)
254-
in
255-
Abstract (id1, ids1, tyopt)
256-
(* The strategy here is to take the last result. It is necessary for
257-
poor reasons, unfortunately. Because `type_of_result` bogusly uses
258-
`Env.add_upper_bound_global`, local type refinement information can
259-
leak outside its scope. To remain consistent with the previous
260-
version of the type access algorithm wrt this bug, we pick the last
261-
result. See T59317869.
262-
The test test/typecheck/tconst/type_refinement_stress.php monitors
263-
the situation here. *)
264-
| (Abstract _, Abstract _) -> r2
225+
(* Here, we merge the bounds on abstract type constants. Effectively this is intersection. *)
226+
| (Abstract (id1, ids1, tyl1), Abstract (_id2, _ids2, tyl2)) ->
227+
Abstract (id1, ids1, TySet.union tyl1 tyl2)
265228
(* Exact type overrides abstract type: the bound on abstract type will be checked
266229
* against the exact type at implementation site. *)
267230
| (Abstract _, Exact ty)
@@ -280,9 +243,12 @@ let rec type_of_result ~ignore_errors ctx env root res =
280243
let reason = make_reason env id root Reason.Rnone in
281244
let ty = MakeType.generic reason generic_name in
282245
let env =
283-
Option.fold bnd ~init:env ~f:(fun env bnd ->
246+
TySet.fold
247+
(fun bnd env ->
284248
(* TODO(T59317869): play well with flow sensitivity *)
285249
Env.add_upper_bound_global env generic_name bnd)
250+
bnd
251+
env
286252
in
287253
(env, ty)
288254
in
@@ -291,7 +257,7 @@ let rec type_of_result ~ignore_errors ctx env root res =
291257
| Abstract (name, name' :: namel, bnd) ->
292258
let res' = Abstract (name', namel, bnd) in
293259
let (env, ty) = type_of_result ~ignore_errors ctx env root res' in
294-
type_with_bound env false name (Some ty)
260+
type_with_bound env false name (TySet.singleton ty)
295261
| Abstract (name, [], bnd) ->
296262
type_with_bound env ctx.abstract_as_tyvar name bnd
297263
| Missing err ->

hphp/hack/test/typecheck/denotable_unions/tconst_union_intersection.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,11 @@ function test1<T as (IMissing & IBoundedJ)>(T $x):J {
4141
return $x->get();
4242
}
4343

44-
// Currently we use the last conjunct, to be sound
45-
// Future recasting of type constant projects will avoid this issue
46-
function test2<T as (IBoundedJ & IUnbounded)>(T $x):mixed {
44+
function test2<T as (IBoundedJ & IUnbounded)>(T $x):J {
4745
return $x->get();
4846
}
4947

50-
// Ditto
51-
function test3<T as (IBoundedJ & IBoundedK)>(T $x):K {
48+
function test3<T as (IBoundedJ & IBoundedK)>(T $x):(J & K) {
5249
return $x->get();
5350
}
5451

0 commit comments

Comments
 (0)