Skip to content

Commit abdfbd2

Browse files
viratyosinmeta-codesync[bot]
authored andcommitted
IsUnionOf (support is ?T)
Summary: This diff adds a new kind of predicate `IsUnionOf` that represents a predicate that passes if any of its sub-predicates pass. It is used to produce a predicate for nullable types. Reviewed By: enetsee Differential Revision: D89830544 fbshipit-source-id: 1ef21cd1f8e6358f26f83fede08403cfb95cabfc
1 parent 3d5902b commit abdfbd2

14 files changed

Lines changed: 192 additions & 14 deletions

File tree

hphp/hack/src/oxidized/gen/typing_defs_core.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<<2c3f5c94b0ef6f31c3ed188a23f5f994>>
6+
// @generated SignedSource<<f2cf7bdffd426d5ab4b758ae2a8c1210>>
77
//
88
// To regenerate this file, run:
99
// buck run @fbcode//mode/dev-nosan-lg fbcode//hphp/hack/src:oxidized_regen
@@ -662,6 +662,7 @@ pub enum TypePredicate_ {
662662
IsTag(TypeTag),
663663
IsTupleOf(TuplePredicate),
664664
IsShapeOf(ShapePredicate),
665+
IsUnionOf(Vec<TypePredicate>),
665666
}
666667

667668
#[derive(

hphp/hack/src/typing/type_visitor.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,8 @@ class virtual ['a] locl_type_visitor : ['a] locl_type_visitor_type =
364364
match snd neg_ty with
365365
| IsTag tag -> on_tag acc tag
366366
| IsTupleOf _
367-
| IsShapeOf _ ->
367+
| IsShapeOf _
368+
| IsUnionOf _ ->
368369
acc
369370

370371
method on_tunapplied_alias acc _ _ = acc

hphp/hack/src/typing/typing_case_types.ml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,10 @@ module DataType : sig
458458
right: TagWithReason.t;
459459
}
460460

461+
val empty : t
462+
463+
val union : t -> t -> t
464+
461465
val disjoint : TagWithReason.ctx -> t -> t -> disjoint
462466

463467
val are_disjoint : TagWithReason.ctx -> t -> t -> bool
@@ -817,7 +821,7 @@ end = struct
817821
(env, to_datatypes ~safe_for_are_disjoint ~trail env cls args)
818822
end
819823

820-
let fromPredicate
824+
let rec fromPredicate
821825
~safe_for_are_disjoint ~trail (env : env) (predicate : type_predicate) :
822826
env * t =
823827
let open Tag in
@@ -842,6 +846,12 @@ end = struct
842846
| IsTag tag -> from_tag tag
843847
| IsTupleOf _ -> (env, Set.singleton ~reason VecData)
844848
| IsShapeOf _ -> (env, Set.singleton ~reason DictData)
849+
| IsUnionOf predicates ->
850+
let (env, sets) =
851+
List.fold_map predicates ~init:env ~f:(fun env pred ->
852+
fromPredicate ~safe_for_are_disjoint ~trail env pred)
853+
in
854+
(env, List.fold sets ~init:Set.empty ~f:Set.union)
845855

846856
type context = {
847857
safe_for_are_disjoint: bool;
@@ -1240,8 +1250,12 @@ module AtomicDataTypes = struct
12401250
DataType.Class.to_datatypes ~safe_for_are_disjoint ~trail env name
12411251
@@ Tag.generics_for_class_and_tag_generic_l env name args
12421252

1253+
let empty = DataType.Set.empty
1254+
12431255
let complement dt = DataType.Set.diff mixed dt
12441256

1257+
let union dt1 dt2 = DataType.Set.union dt1 dt2
1258+
12451259
let are_disjoint env dt1 dt2 = DataType.Set.are_disjoint env dt1 dt2
12461260
end
12471261

hphp/hack/src/typing/typing_case_types.mli

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,15 @@ module AtomicDataTypes : sig
118118
val of_tag :
119119
safe_for_are_disjoint:bool -> env -> Typing_defs_core.type_tag -> env * t
120120

121+
(** The empty set of data types *)
122+
val empty : t
123+
121124
(** Computes the complement for the set of values contained in [t] *)
122125
val complement : t -> t
123126

127+
(** Computes the union of the two given sets **)
128+
val union : t -> t -> t
129+
124130
(** Returns true if the given data types are known to have no values in
125131
common, otherwise returns false *)
126132
val are_disjoint : env -> t -> t -> bool

hphp/hack/src/typing/typing_defs_core.ml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ and type_predicate_ =
265265
| IsTag of type_tag
266266
| IsTupleOf of tuple_predicate
267267
| IsShapeOf of shape_predicate
268+
| IsUnionOf of type_predicate list
268269

269270
and type_predicate =
270271
(Reason.t[@hash.ignore] [@transform.opaque]) * type_predicate_
@@ -702,6 +703,10 @@ module Pp = struct
702703
Format.fprintf fmt "(@[<2>IsShapeOf@ ";
703704
pp_shape_predicate fmt shape_predicate;
704705
Format.fprintf fmt "@])"
706+
| IsUnionOf predicates ->
707+
Format.fprintf fmt "(@[<2>IsUnionOf@ ";
708+
pp_list pp_type_predicate fmt predicates;
709+
Format.fprintf fmt "@])"
705710

706711
and pp_tuple_predicate fmt { tp_required } =
707712
Format.fprintf fmt "@[<2>{ ";
@@ -827,6 +832,7 @@ let type_predicate__con_ordinal type_predicate_ =
827832
| IsTag _ -> 0
828833
| IsTupleOf _ -> 1
829834
| IsShapeOf _ -> 2
835+
| IsUnionOf _ -> 3
830836

831837
let type_tag_con_ordinal type_tag =
832838
match type_tag with

hphp/hack/src/typing/typing_defs_core.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ and type_predicate_ =
255255
| IsTag of type_tag
256256
| IsTupleOf of tuple_predicate
257257
| IsShapeOf of shape_predicate
258+
| IsUnionOf of type_predicate list
258259

259260
and type_predicate = (Reason.t[@transform.opaque]) * type_predicate_
260261

hphp/hack/src/typing/typing_json.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,11 @@ let rec from_type : env -> show_like_ty:bool -> locl_ty -> json =
232232
])
233233
(TShapeMap.bindings sp_fields)) );
234234
]
235+
| IsUnionOf predicates ->
236+
let predicates_json =
237+
List.map predicates ~f:(fun p -> obj @@ predicate_json p)
238+
in
239+
name "isunion" @ [("args", JSON_Array predicates_json)]
235240
(* TODO: T196048813 optional, open, fuel? *)
236241
in
237242
obj @@ kind p "negation" @ predicate_json predicate

hphp/hack/src/typing/typing_print.ml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,6 +1321,13 @@ module Full = struct
13211321
([text "shape("]
13221322
@ List.intersperse texts ~sep:(text ", ")
13231323
@ [text ")"]) )
1324+
| IsUnionOf predicates ->
1325+
let (fuel, texts) =
1326+
List.fold_map ~init:fuel predicates ~f:predicate_doc
1327+
in
1328+
( fuel,
1329+
Concat (List.intersperse texts ~sep:(Concat [Space; text "|"; Space]))
1330+
)
13241331
(* TODO: T196048813 optional, open, fuel? *)
13251332
in
13261333
let (fuel, pdoc) = predicate_doc fuel predicate in
@@ -1786,6 +1793,9 @@ module ErrorString = struct
17861793
])
17871794
in
17881795
"shape(" ^ String.concat texts ~sep:", " ^ ")"
1796+
| IsUnionOf predicates ->
1797+
let strings = List.map predicates ~f:(fun (_, pred) -> str pred) in
1798+
String.concat ~sep:" | " strings
17891799
(* TODO: T196048813, dedupe?, optional, open, fuel? *)
17901800
in
17911801
(fuel, "anything but " ^ str @@ snd predicate)

hphp/hack/src/typing/typing_refinement.ml

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,29 @@ module TyPredicate = struct
132132
| Tnonnull -> Result.Error "nonnull"
133133
| Tdynamic -> Result.Error "dynamic"
134134
| Tany _ -> Result.Error "any"
135-
| Toption _ -> Result.Error "option"
135+
| Toption ty_opt -> begin
136+
let open Hh_prelude.Result.Let_syntax in
137+
let* (next_wildcard_id, pred_opt) = of_ty env next_wildcard_id ty_opt in
138+
let pred_null = (get_reason ty, IsTag NullTag) in
139+
return (next_wildcard_id, IsUnionOf [pred_opt; pred_null])
140+
|> Result.map_error ~f:(fun err -> "option-" ^ err)
141+
end
136142
| Tfun _ -> Result.Error "fun"
137143
| Tgeneric _ -> Result.Error "generic"
138-
| Tunion _ -> Result.Error "union"
144+
| Tunion tys -> begin
145+
match
146+
List.fold_result tys ~init:(next_wildcard_id, []) ~f:(fun acc ty ->
147+
let (next_wildcard_id, predicates) = acc in
148+
let open Hh_prelude.Result.Let_syntax in
149+
let* (next_wildcard_id, predicate) =
150+
of_ty env next_wildcard_id ty
151+
in
152+
return (next_wildcard_id, predicate :: predicates))
153+
with
154+
| Result.Error err -> Result.Error ("union-" ^ err)
155+
| Result.Ok (next_wildcard_id, predicates) ->
156+
Result.Ok (next_wildcard_id, IsUnionOf (List.rev predicates))
157+
end
139158
| Tintersection _ -> Result.Error "intersection"
140159
| Tvec_or_dict _ -> Result.Error "vec_or_dict"
141160
| Taccess _ -> Result.Error "access"
@@ -270,6 +289,15 @@ module TyPredicate = struct
270289
IMap.empty
271290
in
272291
(env, new_tparams)
292+
| (_reason, IsUnionOf predicates) ->
293+
let (env, new_tparams) =
294+
List.map_env env predicates ~f:(fun env predicate ->
295+
let (env, new_tparams) =
296+
instantiate_wildcards_for_predicate env predicate p
297+
in
298+
(env, new_tparams))
299+
in
300+
(env, List.fold new_tparams ~init:IMap.empty ~f:IMap.union)
273301

274302
let rec to_ty lookup_wildcard predicate =
275303
let tag_to_ty reason tag =
@@ -312,6 +340,23 @@ module TyPredicate = struct
312340
sp_fields
313341
in
314342
Typing_make_type.shape reason (Typing_make_type.nothing reason) map
343+
| (reason, IsUnionOf predicates) -> begin
344+
match
345+
List.partition_tf predicates ~f:(fun p ->
346+
match p with
347+
| (_, IsTag NullTag) -> true
348+
| _ -> false)
349+
with
350+
| (_ :: _, [other]) ->
351+
Typing_make_type.nullable reason (to_ty lookup_wildcard other)
352+
| (_ :: _, others) ->
353+
Typing_make_type.nullable reason
354+
@@ Typing_make_type.union reason
355+
@@ List.map others ~f:(to_ty lookup_wildcard)
356+
| ([], _) ->
357+
Typing_make_type.union reason
358+
@@ List.map predicates ~f:(to_ty lookup_wildcard)
359+
end
315360

316361
exception FoundWildcard
317362

@@ -564,6 +609,11 @@ module TyPartition = struct
564609
let meet (partition1, t1, f1) (partition2, t2, f2) =
565610
(Partition.meet partition1 partition2, UTL.conj t1 t2, UTL.conj f1 f2)
566611

612+
let union_combine (partition1, t1, f1) (partition2, t2, f2) =
613+
( Partition.union_combine partition1 partition2,
614+
UTL.disj t1 t2,
615+
UTL.conj f1 f2 )
616+
567617
let product ~f sub_splits_and_assumptions =
568618
let sub_splits = List.map sub_splits_and_assumptions ~f:fst3 in
569619
let assumption_pairs =
@@ -770,6 +820,36 @@ and split_ty_by_tag
770820
else
771821
(env, TyPartition.mk_span ~env ~predicate ty)
772822

823+
and split_ty_by_union
824+
~(other_intersected_tys : locl_ty list)
825+
~(expansions : SSet.t)
826+
(env : env)
827+
(ty : locl_ty)
828+
(predicates : type_predicate list) : env * TyPartition.t =
829+
(* Split the type by each predicate in the union.
830+
831+
When splitting T by union predicate (P1 | P2 | ... | Pn):
832+
- Left: parts that pass at least one predicate = L1 ∪ L2 ∪ ... ∪ Ln
833+
- Right: parts that fail all predicates = R1 ∩ R2 ∩ ... ∩ Rn
834+
- Span: parts that may or may not pass = everything else
835+
836+
This is NOT the same as joining the partitions (which would give L1∪L2, S1∪S2, R1∪R2).
837+
*)
838+
let (env, partitions) =
839+
List.fold_map
840+
~init:env
841+
~f:(fun env pred ->
842+
split_ty ~other_intersected_tys ~expansions env ty ~predicate:pred)
843+
predicates
844+
in
845+
(* Combine partitions using the correct algebra for union predicates *)
846+
let partition =
847+
match partitions with
848+
| [] -> TyPartition.mk_bottom
849+
| first :: rest -> List.fold rest ~init:first ~f:TyPartition.union_combine
850+
in
851+
(env, partition)
852+
773853
and split_ty
774854
~(other_intersected_tys : locl_ty list)
775855
~(expansions : SSet.t)
@@ -797,6 +877,8 @@ and split_ty
797877
shape_predicate
798878
predicate
799879
| IsTag tag -> split_ty_by_tag ~ty_datatype env ety tag predicate
880+
| IsUnionOf predicates ->
881+
split_ty_by_union ~other_intersected_tys ~expansions env ety predicates
800882
in
801883
let split_union ~other_intersected_tys ~expansions env (tys : locl_ty list) =
802884
let (env, partitions) =
@@ -855,12 +937,21 @@ and split_ty
855937
partition_f
856938
DataType.(of_ty ~safe_for_are_disjoint:true env @@ Class (name, args))
857939
| Tneg (_, predicate) ->
858-
let (env, dty) =
940+
let rec to_dty env predicate =
859941
match predicate with
860942
| IsTag tag -> DataType.of_tag ~safe_for_are_disjoint:false env tag
861943
| IsTupleOf _ -> DataType.(of_ty ~safe_for_are_disjoint:false env Tuple)
862944
| IsShapeOf _ -> DataType.(of_ty ~safe_for_are_disjoint:false env Shape)
945+
| IsUnionOf preds ->
946+
List.fold_left_env
947+
env
948+
preds
949+
~init:DataType.empty
950+
~f:(fun env dt_acc pred ->
951+
let (env, dt) = to_dty env (snd pred) in
952+
(env, DataType.union dt_acc dt))
863953
in
954+
let (env, dty) = to_dty env predicate in
864955
let dty = DataType.complement dty in
865956
partition_f (env, dty)
866957
| Tprim Aast.Tnoreturn -> (env, TyPartition.mk_bottom)

hphp/hack/src/utils/core/partition/partition.ml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,27 @@ end) : S with type atom := Atom.t = struct
235235
right1 &&& right2 ||| (right1 &&& span2) ||| (right2 &&& span1));
236236
}
237237

238+
(*
239+
Combines two partitions using the algebra for union predicates.
240+
241+
When combining partitions P1 and P2 for union predicates (P1 | P2):
242+
- left: passes at least one predicate = L1 | L2
243+
- right: fails both predicates = R1 & R2
244+
- span: uncertain (not definitely left or right) = (S1 & S2) | (S1 & R2) | (R1 & S2)
245+
246+
This is different from regular join which does component-wise union.
247+
*)
248+
let union_combine
249+
{ left = left1; span = span1; right = right1 }
250+
{ left = left2; span = span2; right = right2 } =
251+
{
252+
left = Lattice.Infix_ops.(left1 ||| left2);
253+
span =
254+
Lattice.Infix_ops.(
255+
span1 &&& span2 ||| (span1 &&& right2) ||| (right1 &&& span2));
256+
right = Lattice.Infix_ops.(right1 &&& right2);
257+
}
258+
238259
(* If the partition is fully within the left, span or right we can
239260
simplify the DNF to be [set], otherwise return [t] unchanged *)
240261
let simplify t atom =

0 commit comments

Comments
 (0)