Skip to content

Commit e68b3a0

Browse files
pradeep90facebook-github-bot
authored andcommitted
Split and match ordered types by elements of known length.
Summary: Problem: To solve `Tuple[int, str, bool, bool] <: Tuple[int, *Ts, int]`, we need to solve `[int, str, bool, bool] <: [int, *Ts, int]`. We do that by removing matching elements of known length from both the prefix and the suffix. In the above example, `int` is in both prefixes, so we extract it. Next, the left side has `str`, which has a known fixed length 1, but the right side has `*Ts`, which doesn't have a known length. So, we don't extract that. Likewise, we can extract the matching suffix of `[bool] vs [int]` since they have known lengths. (We just split by lengths here, without checking for compatibility. That happens in `solve`.) So, after splitting, we get ``` [int] - [str, bool] - [bool] [int] - [ *Ts ] - [int] ``` We solve each part pairwise. We check `int <: int`, `[str, bool] <: [*Ts]`, and `bool <: int`. So, we end up solving it as `Ts = Tuple[str, bool]`. There are other cases like unbounded tuples, bound vs free variables, multiple `*Ts`, and so on, but this is the core idea. This is what TypeScript [does](microsoft/TypeScript#39094) for its variadic tuples. Note that we don't yet support unpacking unbounded tuples or concatenation of multiple variadics (that's in a future PEP). Reviewed By: grievejia Differential Revision: D26520882 fbshipit-source-id: 0df0d74becf3ee2a85caf3819093ccc336a5029d
1 parent 03056b0 commit e68b3a0

File tree

3 files changed

+231
-0
lines changed

3 files changed

+231
-0
lines changed

source/analysis/test/typeTest.ml

+121
Original file line numberDiff line numberDiff line change
@@ -1857,6 +1857,126 @@ let test_parse_type_variable_declarations _ =
18571857
()
18581858
18591859
1860+
let test_split_ordered_types _ =
1861+
let variadic = Type.Variable.Variadic.Tuple.create "Ts" in
1862+
let assert_split ?(split_both_ways = true) left right expected =
1863+
let aliases ?replace_unbound_parameters_with_any:_ = function
1864+
| "Ts" -> Some (Type.VariableAlias (Type.Variable.TupleVariadic variadic))
1865+
| _ -> None
1866+
in
1867+
let left =
1868+
match
1869+
Type.create ~aliases (parse_single_expression ~preprocess:true ("typing.Tuple" ^ left))
1870+
with
1871+
| Type.Tuple (Bounded ordered_type) -> ordered_type
1872+
| _ -> failwith "expected tuple elements"
1873+
in
1874+
let right =
1875+
match
1876+
Type.create ~aliases (parse_single_expression ~preprocess:true ("typing.Tuple" ^ right))
1877+
with
1878+
| Type.Tuple (Bounded ordered_type) -> ordered_type
1879+
| _ -> failwith "expected tuple elements"
1880+
in
1881+
assert_equal
1882+
~printer:[%show: Type.t Type.OrderedTypes.ordered_type_split option]
1883+
expected
1884+
(Type.OrderedTypes.split_matching_elements_by_length left right);
1885+
if split_both_ways then
1886+
let flip_splits { Type.Record.OrderedTypes.prefix_pairs; middle_pair; suffix_pairs } =
1887+
let swap (a, b) = b, a in
1888+
{
1889+
Type.Record.OrderedTypes.prefix_pairs = List.map prefix_pairs ~f:swap;
1890+
middle_pair = swap middle_pair;
1891+
suffix_pairs = List.map suffix_pairs ~f:swap;
1892+
}
1893+
in
1894+
assert_equal
1895+
~printer:[%show: Type.t Type.OrderedTypes.ordered_type_split option]
1896+
(expected >>| flip_splits)
1897+
(Type.OrderedTypes.split_matching_elements_by_length right left)
1898+
in
1899+
let open Type.OrderedTypes in
1900+
assert_split
1901+
"[int, str]"
1902+
"[int, str]"
1903+
(Some
1904+
{
1905+
prefix_pairs = [Type.integer, Type.integer; Type.string, Type.string];
1906+
middle_pair = Concrete [], Concrete [];
1907+
suffix_pairs = [];
1908+
});
1909+
assert_split
1910+
"[int, str, bool, int, str]"
1911+
"[int, pyre_extensions.Unpack[Ts], int, str]"
1912+
(Some
1913+
{
1914+
prefix_pairs = [Type.integer, Type.integer];
1915+
middle_pair =
1916+
( Concrete [Type.string; Type.bool],
1917+
Concatenation (Type.OrderedTypes.Concatenation.create ~prefix:[] ~suffix:[] variadic) );
1918+
suffix_pairs = [Type.integer, Type.integer; Type.string, Type.string];
1919+
});
1920+
(* Not enough elements. *)
1921+
assert_split "[int]" "[int, str, pyre_extensions.Unpack[Ts]]" None;
1922+
assert_split "[str]" "[pyre_extensions.Unpack[Ts], int, str]" None;
1923+
assert_split "[int, int]" "[int, pyre_extensions.Unpack[Ts], int, str]" None;
1924+
assert_split "[int, int]" "[int, pyre_extensions.Unpack[Ts], int, str]" None;
1925+
(* *Ts can match against zero elements. *)
1926+
assert_split
1927+
"[int, int, str]"
1928+
"[int, pyre_extensions.Unpack[Ts], int, str]"
1929+
(Some
1930+
{
1931+
prefix_pairs = [Type.integer, Type.integer];
1932+
middle_pair =
1933+
( Concrete [],
1934+
Concatenation (Type.OrderedTypes.Concatenation.create ~prefix:[] ~suffix:[] variadic) );
1935+
suffix_pairs = [Type.integer, Type.integer; Type.string, Type.string];
1936+
});
1937+
1938+
(* Concatenation vs concatenation. *)
1939+
assert_split
1940+
"[int, pyre_extensions.Unpack[Ts], bool]"
1941+
"[int, pyre_extensions.Unpack[Ts], int]"
1942+
(Some
1943+
{
1944+
prefix_pairs = [Type.integer, Type.integer];
1945+
middle_pair =
1946+
( Concatenation (Type.OrderedTypes.Concatenation.create ~prefix:[] ~suffix:[] variadic),
1947+
Concatenation (Type.OrderedTypes.Concatenation.create ~prefix:[] ~suffix:[] variadic) );
1948+
suffix_pairs = [Type.bool, Type.integer];
1949+
});
1950+
assert_split
1951+
"[int, str, pyre_extensions.Unpack[Ts], bool]"
1952+
"[int, pyre_extensions.Unpack[Ts], str, int]"
1953+
(Some
1954+
{
1955+
prefix_pairs = [Type.integer, Type.integer];
1956+
middle_pair =
1957+
( Concatenation
1958+
(Type.OrderedTypes.Concatenation.create ~prefix:[Type.string] ~suffix:[] variadic),
1959+
Concatenation
1960+
(Type.OrderedTypes.Concatenation.create ~prefix:[] ~suffix:[Type.string] variadic) );
1961+
suffix_pairs = [Type.bool, Type.integer];
1962+
});
1963+
(* There are no matching elements of known length in either the prefix_pairs or the suffix_pairs. *)
1964+
assert_split
1965+
"[pyre_extensions.Unpack[Ts], str]"
1966+
"[int, pyre_extensions.Unpack[Ts]]"
1967+
(Some
1968+
{
1969+
prefix_pairs = [];
1970+
middle_pair =
1971+
( Concatenation
1972+
(Type.OrderedTypes.Concatenation.create ~prefix:[] ~suffix:[Type.string] variadic),
1973+
Concatenation
1974+
(Type.OrderedTypes.Concatenation.create ~prefix:[Type.integer] ~suffix:[] variadic) );
1975+
suffix_pairs = [];
1976+
});
1977+
()
1978+
1979+
18601980
let test_union_upper_bound _ =
18611981
let assert_union_upper_bound map expected =
18621982
assert_equal (Type.OrderedTypes.union_upper_bound map) expected
@@ -2222,6 +2342,7 @@ let () =
22222342
"replace_all" >:: test_replace_all;
22232343
"collect_all" >:: test_collect_all;
22242344
"parse_type_variable_declarations" >:: test_parse_type_variable_declarations;
2345+
"split_ordered_types" >:: test_split_ordered_types;
22252346
"union_upper_bound" >:: test_union_upper_bound;
22262347
"infer_transform" >:: test_infer_transform;
22272348
"fields_from_constructor" >:: test_fields_from_constructor;

source/analysis/type.ml

+98
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,30 @@ module Record = struct
200200
| Concatenation of 'annotation Concatenation.t
201201
[@@deriving compare, eq, sexp, show, hash]
202202

203+
(* This represents the splitting of two ordered types to match each other in length. The prefix
204+
contains the prefix elements of known length that both have, the suffix contains the suffix
205+
elements of known length that both have, and the middle part contains the rest.
206+
207+
[int, bool, str, int, bool] <: [int, int, *Ts, T]
208+
209+
will be represented as:
210+
211+
* prefix_match: [int; bool], [int; int].
212+
213+
* middle_match: [str; int], *Ts
214+
215+
* suffix_match: [bool], T.
216+
217+
We don't include `str` in the prefixes because the corresponding `*Ts` on the right side is
218+
not of known length. Note that this doesn't check for compatibility; it is a purely
219+
length-based operation. *)
220+
type 'annotation ordered_type_split = {
221+
prefix_pairs: ('annotation * 'annotation) list;
222+
middle_pair: 'annotation record * 'annotation record;
223+
suffix_pairs: ('annotation * 'annotation) list;
224+
}
225+
[@@deriving compare, eq, sexp, show, hash]
226+
203227
let pp_concise format variable ~pp_type =
204228
match variable with
205229
| Concrete types -> Format.fprintf format "%s" (show_type_list types ~pp_type)
@@ -217,6 +241,80 @@ module Record = struct
217241
| Concatenation _, Concatenation _ ->
218242
(* TODO(T84854853). *)
219243
None
244+
245+
246+
let split_matching_elements_by_length left right =
247+
let split_concrete_against_concatenation
248+
~is_left_concrete
249+
~concrete
250+
~concatenation:{ Concatenation.prefix; middle; suffix }
251+
=
252+
let concrete_prefix, concrete_rest = List.split_n concrete (List.length prefix) in
253+
let concrete_middle, concrete_suffix =
254+
List.split_n concrete_rest (List.length concrete_rest - List.length suffix)
255+
in
256+
let prefix_pairs, middle_pair, suffix_pairs =
257+
if is_left_concrete then
258+
( List.zip concrete_prefix prefix,
259+
(Concrete concrete_middle, Concatenation { prefix = []; middle; suffix = [] }),
260+
List.zip concrete_suffix suffix )
261+
else
262+
( List.zip prefix concrete_prefix,
263+
(Concatenation { prefix = []; middle; suffix = [] }, Concrete concrete_middle),
264+
List.zip suffix concrete_suffix )
265+
in
266+
match prefix_pairs, middle_pair, suffix_pairs with
267+
| Ok prefix_pairs, middle_pair, Ok suffix_pairs ->
268+
Some { prefix_pairs; middle_pair; suffix_pairs }
269+
| _ -> None
270+
in
271+
match left, right with
272+
| Concrete left, Concrete right -> (
273+
match List.zip left right with
274+
| Ok prefix_pairs ->
275+
Some { prefix_pairs; middle_pair = Concrete [], Concrete []; suffix_pairs = [] }
276+
| Unequal_lengths -> None )
277+
| Concrete left, Concatenation concatenation ->
278+
split_concrete_against_concatenation ~is_left_concrete:true ~concrete:left ~concatenation
279+
| Concatenation concatenation, Concrete right ->
280+
split_concrete_against_concatenation
281+
~is_left_concrete:false
282+
~concrete:right
283+
~concatenation
284+
| ( Concatenation { prefix = left_prefix; middle = left_middle; suffix = left_suffix },
285+
Concatenation { prefix = right_prefix; middle = right_middle; suffix = right_suffix } )
286+
-> (
287+
let prefix_length = Int.min (List.length left_prefix) (List.length right_prefix) in
288+
let suffix_length = Int.min (List.length left_suffix) (List.length right_suffix) in
289+
let left_prefix, left_prefix_rest = List.split_n left_prefix prefix_length in
290+
let right_prefix, right_prefix_rest = List.split_n right_prefix prefix_length in
291+
let left_suffix_rest, left_suffix =
292+
List.split_n left_suffix (List.length left_suffix - suffix_length)
293+
in
294+
let right_suffix_rest, right_suffix =
295+
List.split_n right_suffix (List.length right_suffix - suffix_length)
296+
in
297+
match List.zip left_prefix right_prefix, List.zip left_suffix right_suffix with
298+
| Ok prefix_pairs, Ok suffix_pairs ->
299+
Some
300+
{
301+
prefix_pairs;
302+
middle_pair =
303+
( Concatenation
304+
{
305+
prefix = left_prefix_rest;
306+
middle = left_middle;
307+
suffix = left_suffix_rest;
308+
},
309+
Concatenation
310+
{
311+
prefix = right_prefix_rest;
312+
middle = right_middle;
313+
suffix = right_suffix_rest;
314+
} );
315+
suffix_pairs;
316+
}
317+
| _ -> None )
220318
end
221319

222320
module Callable = struct

source/analysis/type.mli

+12
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,23 @@ module Record : sig
8989
| Concatenation of 'annotation Concatenation.t
9090
[@@deriving compare, eq, sexp, show, hash]
9191

92+
type 'annotation ordered_type_split = {
93+
prefix_pairs: ('annotation * 'annotation) list;
94+
middle_pair: 'annotation record * 'annotation record;
95+
suffix_pairs: ('annotation * 'annotation) list;
96+
}
97+
[@@deriving compare, eq, sexp, show, hash]
98+
9299
val pp_concise
93100
: Format.formatter ->
94101
'a record ->
95102
pp_type:(Format.formatter -> 'a -> unit) ->
96103
unit
104+
105+
val split_matching_elements_by_length
106+
: 'annotation record ->
107+
'annotation record ->
108+
'annotation ordered_type_split option
97109
end
98110

99111
module Callable : sig

0 commit comments

Comments
 (0)