Skip to content

Commit

Permalink
Implement GenericAlias.__mro_entries__
Browse files Browse the repository at this point in the history
Reviewed By: pradeep90

Differential Revision: D48421775

fbshipit-source-id: 1cf20d115379659e401b66e91b359a8a9d36b467
  • Loading branch information
grievejia authored and facebook-github-bot committed Aug 19, 2023
1 parent b8b897b commit b755365
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 6 deletions.
28 changes: 28 additions & 0 deletions source/analysis/classHierarchyEnvironment.ml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,33 @@ let get_parents alias_environment name ~dependency =
| [], _ -> simples ["object"]
| _ -> parents
in
(* If `typing.Generic[]` appears in the base list, that entry needs to go through special
handling. This behavior was established in PEP 560 and gets implemented in CPython via
`GenericAlias.__mro_entries__()`. See https://fburl.com/mro_in_pyre for more detailed
explanation. *)
let filter_shadowed_generic_bases name_and_parameters =
let is_protocol =
List.exists name_and_parameters ~f:(fun (name, _) -> String.equal name "typing.Protocol")
in
let process_parent ((name, _) as current) rest =
match name with
| "typing.Generic" ->
(* TODO: type parameters of the `name` class is expected to be non-empty here because
Python forbids inheriting from `typing.Generic` directly. But we currently can't check
for that since we lack the setup to emit errors from this environment. *)
if is_protocol then
(* Hide `Generic` from MRO if the class also extends from `Protocol` *)
rest
else if List.exists rest ~f:(fun (_, parameters) -> not (List.is_empty parameters)) then
(* Hide `Generic` from MRO if there exist other generic aliases down the base class
list *)
rest
else
current :: rest
| _ -> current :: rest
in
List.fold_right name_and_parameters ~init:[] ~f:process_parent
in
let is_not_primitive_cycle (parent, _) = not (String.equal name parent) in
let convert_to_targets =
List.map ~f:(fun (name, parameters) ->
Expand Down Expand Up @@ -168,6 +195,7 @@ let get_parents alias_environment name ~dependency =
List.filter_map base_classes ~f:extract_supertype
|> add_special_parents
|> List.filter ~f:is_not_primitive_cycle
|> filter_shadowed_generic_bases
|> convert_to_targets
|> deduplicate
|> remove_extra_edges_to_object
Expand Down
132 changes: 126 additions & 6 deletions source/analysis/test/classHierarchyEnvironmentTest.ml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ let test_simple_registration context =
()


let test_register_inferred_generic_base context =
let test_parents_and_inferred_generic_base context =
let assert_registers ~expected_parents ?expected_inferred_generic_base source name =
let project = ScratchProject.setup ["test.py", source] ~context ~track_dependencies:true in
let read_only =
Expand Down Expand Up @@ -176,8 +176,7 @@ let test_register_inferred_generic_base context =
pass
|}
"test.C"
~expected_parents:
["typing.Generic", [Type.variable "test._T"]; "test.List", [Type.variable "test._T"]]
~expected_parents:["test.List", [Type.variable "test._T"]]
~expected_inferred_generic_base:("typing.Generic", [Type.variable "test._T"]);
assert_registers
{|
Expand All @@ -190,8 +189,7 @@ let test_register_inferred_generic_base context =
pass
|}
"test.C"
~expected_parents:
["typing.Generic", [Type.variable "test._T"]; "test.List", [Type.variable "test._T"]]
~expected_parents:["test.List", [Type.variable "test._T"]]
~expected_inferred_generic_base:("typing.Generic", [Type.variable "test._T"]);
assert_registers
{|
Expand All @@ -205,6 +203,128 @@ let test_register_inferred_generic_base context =
~expected_parents:
["test.Iterable", [Type.variable "test._T"]; "typing.Generic", [Type.variable "test._T"]]
~expected_inferred_generic_base:("typing.Generic", [Type.variable "test._T"]);
assert_registers
{|
import typing
T1 = typing.TypeVar("T1")
T2 = typing.TypeVar("T2")
class Foo1(typing.Generic[T1]): pass
class Foo2(typing.Generic[T2]): pass
class Bar(Foo1[T1], Foo2[T2]): pass
|}
"test.Bar"
~expected_parents:
["test.Foo1", [Type.variable "test.T1"]; "test.Foo2", [Type.variable "test.T2"]]
~expected_inferred_generic_base:
("typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"]);
assert_registers
{|
import typing
T1 = typing.TypeVar("T1")
T2 = typing.TypeVar("T2")
class Foo1(typing.Generic[T1]): pass
class Foo2(typing.Generic[T2]): pass
class Bar(typing.Generic[T1, T2], Foo1[T1], Foo2[T2]): pass
|}
"test.Bar"
~expected_parents:
["test.Foo1", [Type.variable "test.T1"]; "test.Foo2", [Type.variable "test.T2"]]
~expected_inferred_generic_base:
("typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"]);
assert_registers
{|
import typing
T1 = typing.TypeVar("T1")
T2 = typing.TypeVar("T2")
class Foo1(typing.Generic[T1]): pass
class Foo2(typing.Generic[T2]): pass
class Bar(Foo1[T1], Foo2[T2], typing.Generic[T1, T2]): pass
|}
"test.Bar"
~expected_parents:
[
"test.Foo1", [Type.variable "test.T1"];
"test.Foo2", [Type.variable "test.T2"];
"typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"];
]
~expected_inferred_generic_base:
("typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"]);
assert_registers
{|
import typing
T1 = typing.TypeVar("T1")
T2 = typing.TypeVar("T2")
class Foo1(typing.Generic[T1]): pass
class Foo2(typing.Generic[T2]): pass
# Note that Foo1 doesn't have type parameter here
class Bar(typing.Generic[T1, T2], Foo1, Foo2[T2]): pass
|}
"test.Bar"
~expected_parents:["test.Foo1", []; "test.Foo2", [Type.variable "test.T2"]]
~expected_inferred_generic_base:
("typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"]);
assert_registers
{|
import typing
T1 = typing.TypeVar("T1")
T2 = typing.TypeVar("T2")
class Foo1(typing.Generic[T1]): pass
class Foo2(typing.Generic[T2]): pass
class Bar(Foo1[T1], typing.Generic[T1, T2], Foo2[T2]): pass
|}
"test.Bar"
~expected_parents:
["test.Foo1", [Type.variable "test.T1"]; "test.Foo2", [Type.variable "test.T2"]]
~expected_inferred_generic_base:
("typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"]);
assert_registers
{|
import typing
T1 = typing.TypeVar("T1")
T2 = typing.TypeVar("T2")
class Foo1(typing.Generic[T1]): pass
class Foo2(typing.Generic[T2]): pass
# Note that Foo2 doesn't have type parameter here
class Bar(Foo1[T1], typing.Generic[T1, T2], Foo2): pass
|}
"test.Bar"
~expected_parents:
[
"test.Foo1", [Type.variable "test.T1"];
"typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"];
"test.Foo2", [];
]
~expected_inferred_generic_base:
("typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"]);
assert_registers
{|
import typing
T1 = typing.TypeVar("T1")
T2 = typing.TypeVar("T2")
class Foo1(typing.Generic[T1]): pass
class Foo2(typing.Generic[T2]): pass
class Bar(typing.Generic[T1, T2], Foo1, Foo2): pass
|}
"test.Bar"
~expected_parents:
[
"typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"];
"test.Foo1", [];
"test.Foo2", [];
]
~expected_inferred_generic_base:
("typing.Generic", [Type.variable "test.T1"; Type.variable "test.T2"]);
assert_registers
{|
import typing
T = typing.TypeVar("T")
class Foo(typing.Generic[T]): pass
class Bar(typing.Protocol[T], Generic[T], Foo[T]): pass
|}
"test.Bar"
~expected_parents:
["typing.Protocol", [Type.variable "test.T"]; "test.Foo", [Type.variable "test.T"]]
~expected_inferred_generic_base:("typing.Generic", [Type.variable "test.T"]);
assert_registers
{|
_T1 = typing.TypeVar('_T1')
Expand Down Expand Up @@ -582,7 +702,7 @@ let () =
"environment"
>::: [
"simple_registration" >:: test_simple_registration;
"register_inferred_generic_bases" >:: test_register_inferred_generic_base;
"parents_and_inferred_generic_bases" >:: test_parents_and_inferred_generic_base;
"compute_inferred_generic_base" >:: test_compute_inferred_generic_base;
"updates" >:: test_updates;
]
Expand Down

0 comments on commit b755365

Please sign in to comment.