From 84bed8b51f66a6bd759e596712d4e134154f7125 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Tue, 4 Jun 2024 10:17:07 -0700 Subject: [PATCH] Implemented conformance tests for Enums chapter. (#1764) --- conformance/results/mypy/enums_behaviors.toml | 7 + .../results/mypy/enums_definition.toml | 6 + conformance/results/mypy/enums_expansion.toml | 12 ++ .../results/mypy/enums_member_names.toml | 11 ++ .../results/mypy/enums_member_values.toml | 16 ++ conformance/results/mypy/enums_members.toml | 42 +++++ conformance/results/mypy/version.toml | 2 +- conformance/results/pyre/enums_behaviors.toml | 10 ++ .../results/pyre/enums_definition.toml | 19 +++ conformance/results/pyre/enums_expansion.toml | 12 ++ .../results/pyre/enums_member_names.toml | 13 ++ .../results/pyre/enums_member_values.toml | 23 +++ conformance/results/pyre/enums_members.toml | 43 ++++++ conformance/results/pyre/version.toml | 2 +- .../results/pyright/enums_behaviors.toml | 7 + .../results/pyright/enums_definition.toml | 6 + .../results/pyright/enums_expansion.toml | 16 ++ .../results/pyright/enums_member_names.toml | 6 + .../results/pyright/enums_member_values.toml | 14 ++ .../results/pyright/enums_members.toml | 31 ++++ .../results/pytype/enums_behaviors.toml | 11 ++ .../results/pytype/enums_definition.toml | 10 ++ .../results/pytype/enums_expansion.toml | 11 ++ .../results/pytype/enums_member_names.toml | 7 + .../results/pytype/enums_member_values.toml | 18 +++ conformance/results/pytype/enums_members.toml | 39 +++++ conformance/results/pytype/version.toml | 2 +- conformance/results/results.html | 45 +++++- conformance/src/test_groups.toml | 4 + conformance/tests/_enums_member_values.py | 3 + conformance/tests/_enums_member_values.pyi | 15 ++ conformance/tests/_enums_members.py | 3 + conformance/tests/_enums_members.pyi | 15 ++ conformance/tests/enums_behaviors.py | 40 +++++ conformance/tests/enums_definition.py | 75 +++++++++ conformance/tests/enums_expansion.py | 78 ++++++++++ conformance/tests/enums_member_names.py | 30 ++++ conformance/tests/enums_member_values.py | 96 ++++++++++++ conformance/tests/enums_members.py | 143 ++++++++++++++++++ 39 files changed, 937 insertions(+), 6 deletions(-) create mode 100644 conformance/results/mypy/enums_behaviors.toml create mode 100644 conformance/results/mypy/enums_definition.toml create mode 100644 conformance/results/mypy/enums_expansion.toml create mode 100644 conformance/results/mypy/enums_member_names.toml create mode 100644 conformance/results/mypy/enums_member_values.toml create mode 100644 conformance/results/mypy/enums_members.toml create mode 100644 conformance/results/pyre/enums_behaviors.toml create mode 100644 conformance/results/pyre/enums_definition.toml create mode 100644 conformance/results/pyre/enums_expansion.toml create mode 100644 conformance/results/pyre/enums_member_names.toml create mode 100644 conformance/results/pyre/enums_member_values.toml create mode 100644 conformance/results/pyre/enums_members.toml create mode 100644 conformance/results/pyright/enums_behaviors.toml create mode 100644 conformance/results/pyright/enums_definition.toml create mode 100644 conformance/results/pyright/enums_expansion.toml create mode 100644 conformance/results/pyright/enums_member_names.toml create mode 100644 conformance/results/pyright/enums_member_values.toml create mode 100644 conformance/results/pyright/enums_members.toml create mode 100644 conformance/results/pytype/enums_behaviors.toml create mode 100644 conformance/results/pytype/enums_definition.toml create mode 100644 conformance/results/pytype/enums_expansion.toml create mode 100644 conformance/results/pytype/enums_member_names.toml create mode 100644 conformance/results/pytype/enums_member_values.toml create mode 100644 conformance/results/pytype/enums_members.toml create mode 100644 conformance/tests/_enums_member_values.py create mode 100644 conformance/tests/_enums_member_values.pyi create mode 100644 conformance/tests/_enums_members.py create mode 100644 conformance/tests/_enums_members.pyi create mode 100644 conformance/tests/enums_behaviors.py create mode 100644 conformance/tests/enums_definition.py create mode 100644 conformance/tests/enums_expansion.py create mode 100644 conformance/tests/enums_member_names.py create mode 100644 conformance/tests/enums_member_values.py create mode 100644 conformance/tests/enums_members.py diff --git a/conformance/results/mypy/enums_behaviors.toml b/conformance/results/mypy/enums_behaviors.toml new file mode 100644 index 000000000..80498e652 --- /dev/null +++ b/conformance/results/mypy/enums_behaviors.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +errors_diff = """ +""" +output = """ +enums_behaviors.py:39: error: Cannot extend enum with existing members: "Shape" [misc] +""" +conformance_automated = "Pass" diff --git a/conformance/results/mypy/enums_definition.toml b/conformance/results/mypy/enums_definition.toml new file mode 100644 index 000000000..513dbe853 --- /dev/null +++ b/conformance/results/mypy/enums_definition.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +errors_diff = """ +""" +output = """ +""" +conformance_automated = "Pass" diff --git a/conformance/results/mypy/enums_expansion.toml b/conformance/results/mypy/enums_expansion.toml new file mode 100644 index 000000000..9858edfea --- /dev/null +++ b/conformance/results/mypy/enums_expansion.toml @@ -0,0 +1,12 @@ +conformant = "Partial" +notes = """ +Improperly applies narrowing to Flag subclass. +""" +conformance_automated = "Fail" +errors_diff = """ +Line 53: Expected 1 errors +Line 52: Unexpected errors ['enums_expansion.py:52: error: Expression is of type "Literal[CustomFlags.FLAG3]", not "CustomFlags" [assert-type]'] +""" +output = """ +enums_expansion.py:52: error: Expression is of type "Literal[CustomFlags.FLAG3]", not "CustomFlags" [assert-type] +""" diff --git a/conformance/results/mypy/enums_member_names.toml b/conformance/results/mypy/enums_member_names.toml new file mode 100644 index 000000000..fc838dc78 --- /dev/null +++ b/conformance/results/mypy/enums_member_names.toml @@ -0,0 +1,11 @@ +conformant = "Pass" +notes = """ +Does not support special-cased handling of member name literal types in some cases (optional). +""" +conformance_automated = "Pass" +errors_diff = """ +""" +output = """ +enums_member_names.py:26: error: Expression is of type "str", not "Literal['RED', 'BLUE']" [assert-type] +enums_member_names.py:30: error: Expression is of type "str", not "Literal['RED', 'BLUE', 'GREEN']" [assert-type] +""" diff --git a/conformance/results/mypy/enums_member_values.toml b/conformance/results/mypy/enums_member_values.toml new file mode 100644 index 000000000..87ec4a09c --- /dev/null +++ b/conformance/results/mypy/enums_member_values.toml @@ -0,0 +1,16 @@ +conformant = "Partial" +notes = """ +Does not enforce declared type of `_value_`. +Does not enforce assigned tuple types for enum members (optional). +""" +conformance_automated = "Fail" +errors_diff = """ +Line 78: Expected 1 errors +""" +output = """ +enums_member_values.py:26: error: Expression is of type "Any", not "Literal[1, 3]" [assert-type] +enums_member_values.py:54: error: Expression is of type "tuple[int, float, float]", not "Literal[1]" [assert-type] +enums_member_values.py:68: error: Expression is of type "int", not "Literal[1]" [assert-type] +enums_member_values.py:85: error: Incompatible types in assignment (expression has type "int", variable has type "str") [assignment] +enums_member_values.py:96: error: Expression is of type "EllipsisType", not "int" [assert-type] +""" diff --git a/conformance/results/mypy/enums_members.toml b/conformance/results/mypy/enums_members.toml new file mode 100644 index 000000000..1c6b709ba --- /dev/null +++ b/conformance/results/mypy/enums_members.toml @@ -0,0 +1,42 @@ +conformant = "Partial" +notes = """ +Does not treat attribute with annotation and no assignment as non-member. +Does not reject use of annotation with enum member. +Does not treat callables as non-members. +Does not honor `enum.nonmember` to define non-member attribute. +Does not honor `enum.member` as method decorator. +Does not properly handle aliased enum members. +Does not support `_ignore_` mechanism (optional). +""" +conformance_automated = "Fail" +errors_diff = """ +Line 46: Expected 1 errors +Line 78: Expected 1 errors +Line 79: Expected 1 errors +Line 112: Expected 1 errors +Line 23: Unexpected errors ['enums_members.py:23: error: Expression is of type "Literal[Pet.genus]", not "str" [assert-type]'] +Line 24: Unexpected errors ['enums_members.py:24: error: Expression is of type "Literal[Pet.species]", not "str" [assert-type]'] +Line 31: Unexpected errors ['enums_members.py:31: error: Expression is of type "Literal[Pet2.genus]", not "str" [assert-type]'] +Line 32: Unexpected errors ['enums_members.py:32: error: Expression is of type "Literal[Pet2.species]", not "str" [assert-type]'] +Line 96: Unexpected errors ['enums_members.py:96: error: Expression is of type "Literal[TrafficLight.AMBER]", not "Literal[TrafficLight.YELLOW]" [assert-type]'] +Line 113: Unexpected errors ['enums_members.py:113: error: Expression is of type "member[Callable[[Example], None]]", not "Any" [assert-type]', 'enums_members.py:113: error: Parameter 1 of Literal[...] is invalid [valid-type]'] +""" +output = """ +enums_members.py:23: error: Expression is of type "Literal[Pet.genus]", not "str" [assert-type] +enums_members.py:24: error: Expression is of type "Literal[Pet.species]", not "str" [assert-type] +enums_members.py:31: error: Expression is of type "Literal[Pet2.genus]", not "str" [assert-type] +enums_members.py:32: error: Expression is of type "Literal[Pet2.species]", not "str" [assert-type] +enums_members.py:80: error: Expression is of type "Callable[[Pet4], str]", not "Any" [assert-type] +enums_members.py:80: error: Parameter 1 of Literal[...] is invalid [valid-type] +enums_members.py:81: error: Expression is of type "Callable[[Pet4], None]", not "Any" [assert-type] +enums_members.py:81: error: Parameter 1 of Literal[...] is invalid [valid-type] +enums_members.py:96: error: Expression is of type "Literal[TrafficLight.AMBER]", not "Literal[TrafficLight.YELLOW]" [assert-type] +enums_members.py:113: error: Expression is of type "member[Callable[[Example], None]]", not "Any" [assert-type] +enums_members.py:113: error: Parameter 1 of Literal[...] is invalid [valid-type] +enums_members.py:124: note: Revealed type is "Any" +enums_members.py:124: note: 'reveal_type' always outputs 'Any' in unchecked functions +enums_members.py:125: error: Expression is of type "Any", not "Literal[Example2.__B]" [assert-type] +enums_members.py:125: note: "assert_type" expects everything to be "Any" in unchecked functions +enums_members.py:142: error: Expression is of type "Literal[Pet5.DOG]", not "int" [assert-type] +enums_members.py:143: error: Expression is of type "Literal[Pet5.FISH]", not "int" [assert-type] +""" diff --git a/conformance/results/mypy/version.toml b/conformance/results/mypy/version.toml index dc9b55ed5..2d942f878 100644 --- a/conformance/results/mypy/version.toml +++ b/conformance/results/mypy/version.toml @@ -1,2 +1,2 @@ version = "mypy 1.10.0" -test_duration = 1.5 +test_duration = 0.9 diff --git a/conformance/results/pyre/enums_behaviors.toml b/conformance/results/pyre/enums_behaviors.toml new file mode 100644 index 000000000..42e562319 --- /dev/null +++ b/conformance/results/pyre/enums_behaviors.toml @@ -0,0 +1,10 @@ +conformant = "Partial" +notes = """ +Does not enforce that Enum classes are implicitly final. +""" +conformance_automated = "Fail" +errors_diff = """ +Line 39: Expected 1 errors +""" +output = """ +""" diff --git a/conformance/results/pyre/enums_definition.toml b/conformance/results/pyre/enums_definition.toml new file mode 100644 index 000000000..832a9d48d --- /dev/null +++ b/conformance/results/pyre/enums_definition.toml @@ -0,0 +1,19 @@ +conformant = "Partial" +notes = """ +Does not support custom Enum classes based on EnumType metaclass. +Does not support some functional forms for Enum class definitions (optional). +""" +conformance_automated = "Fail" +errors_diff = """ +Line 75: Unexpected errors ['enums_definition.py:75:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[Color11.RED]` but got `int`.'] +""" +output = """ +enums_definition.py:24:9 Too many arguments [19]: Call `Enum.__new__` expects 1 positional argument, 4 were provided. +enums_definition.py:27:9 Too many arguments [19]: Call `Enum.__new__` expects 1 positional argument, 2 were provided. +enums_definition.py:28:9 Too many arguments [19]: Call `Enum.__new__` expects 1 positional argument, 2 were provided. +enums_definition.py:31:9 Too many arguments [19]: Call `Enum.__new__` expects 1 positional argument, 2 were provided. +enums_definition.py:33:12 Undefined attribute [16]: `Enum` has no attribute `RED`. +enums_definition.py:38:12 Undefined attribute [16]: `Color7` has no attribute `RED`. +enums_definition.py:39:12 Undefined attribute [16]: `Color8` has no attribute `RED`. +enums_definition.py:75:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[Color11.RED]` but got `int`. +""" diff --git a/conformance/results/pyre/enums_expansion.toml b/conformance/results/pyre/enums_expansion.toml new file mode 100644 index 000000000..8b75a929a --- /dev/null +++ b/conformance/results/pyre/enums_expansion.toml @@ -0,0 +1,12 @@ +conformant = "Pass" +notes = """ +Does not perform type narrowing based on enum literal expansion (optional). +""" +conformance_automated = "Pass" +errors_diff = """ +""" +output = """ +enums_expansion.py:25:8 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[Color.GREEN]` but got `Color`. +enums_expansion.py:35:12 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `Never` but got `Color`. +enums_expansion.py:53:8 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[CustomFlags.FLAG3]` but got `CustomFlags`. +""" diff --git a/conformance/results/pyre/enums_member_names.toml b/conformance/results/pyre/enums_member_names.toml new file mode 100644 index 000000000..109185b53 --- /dev/null +++ b/conformance/results/pyre/enums_member_names.toml @@ -0,0 +1,13 @@ +conformant = "Pass" +notes = """ +Does not support special-cased handling of member name literal types (optional). +""" +conformance_automated = "Pass" +errors_diff = """ +""" +output = """ +enums_member_names.py:21:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal['RED']` but got `str`. +enums_member_names.py:22:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal['RED']` but got `typing.Any`. +enums_member_names.py:26:4 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `Union[typing_extensions.Literal['BLUE'], typing_extensions.Literal['RED']]` but got `typing.Any`. +enums_member_names.py:30:4 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `Union[typing_extensions.Literal['BLUE'], typing_extensions.Literal['GREEN'], typing_extensions.Literal['RED']]` but got `typing.Any`. +""" diff --git a/conformance/results/pyre/enums_member_values.toml b/conformance/results/pyre/enums_member_values.toml new file mode 100644 index 000000000..2cac7f64f --- /dev/null +++ b/conformance/results/pyre/enums_member_values.toml @@ -0,0 +1,23 @@ +conformant = "Partial" +notes = """ +Does not enforce declared type of `_value_`. +Does not enforce assigned tuple types for enum members (optional). +Does not evaluate literal types for enum member values (optional). +Does not evaluate literal types for auto values (optional). +""" +conformance_automated = "Fail" +errors_diff = """ +Line 78: Expected 1 errors +Line 85: Expected 1 errors +Line 75: Unexpected errors ['enums_member_values.py:75:0 Uninitialized attribute [13]: Attribute `_value_` is declared in class `Color3` to have type `Color3` but is never initialized.'] +""" +output = """ +enums_member_values.py:21:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[1]` but got `typing.Any`. +enums_member_values.py:22:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[1]` but got `typing.Any`. +enums_member_values.py:26:4 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `Union[typing_extensions.Literal[1], typing_extensions.Literal[3]]` but got `typing.Any`. +enums_member_values.py:30:4 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `Union[typing_extensions.Literal[1], typing_extensions.Literal[2], typing_extensions.Literal[3]]` but got `typing.Any`. +enums_member_values.py:54:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[1]` but got `typing.Any`. +enums_member_values.py:68:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[1]` but got `typing.Any`. +enums_member_values.py:75:0 Uninitialized attribute [13]: Attribute `_value_` is declared in class `Color3` to have type `Color3` but is never initialized. +enums_member_values.py:96:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `int` but got `typing.Any`. +""" diff --git a/conformance/results/pyre/enums_members.toml b/conformance/results/pyre/enums_members.toml new file mode 100644 index 000000000..3e4ef99c1 --- /dev/null +++ b/conformance/results/pyre/enums_members.toml @@ -0,0 +1,43 @@ +conformant = "Partial" +notes = """ +Does not reject use of annotation with enum member. +Does not treat callables as non-members. +Does not treat annotated attributes as non-members. +Does not honor `enum.nonmember` to define non-member attribute. +Does not honor `enum.member` as method decorator. +Does not properly handle aliased enum members. +Rejects use of `_ignore_`. +Does not support `_ignore_` mechanism (optional). +""" +conformance_automated = "Fail" +errors_diff = """ +Line 46: Expected 1 errors +Line 78: Expected 1 errors +Line 79: Expected 1 errors +Line 112: Expected 1 errors +Line 125: Expected 1 errors +Line 23: Unexpected errors ['enums_members.py:23:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `str` but got `Pet`.'] +Line 24: Unexpected errors ['enums_members.py:24:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `str` but got `Pet`.'] +Line 31: Unexpected errors ['enums_members.py:31:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `str` but got `Pet2`.'] +Line 32: Unexpected errors ['enums_members.py:32:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `str` but got `Pet2`.'] +Line 96: Unexpected errors ['enums_members.py:96:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[TrafficLight.YELLOW]` but got `typing_extensions.Literal[TrafficLight.AMBER]`.'] +Line 113: Unexpected errors ['enums_members.py:113:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[Example.c]` but got `member[typing.Callable(Example.c)[[Named(self, Example)], None]]`.'] +Line 124: Unexpected errors ['enums_members.py:124:8 Revealed type [-1]: Revealed type for `enums_members.Example2._Example2__B` is `typing_extensions.Literal[Example2._Example2__B]` (final).'] +Line 135: Unexpected errors ['enums_members.py:135:4 Inconsistent override [15]: `_ignore_` overrides attribute defined in `Enum` inconsistently. Type `Pet5` is not a subtype of the overridden attribute `typing.Union[typing.List[str], str]`.'] +""" +output = """ +enums_members.py:15:0 Uninitialized attribute [13]: Attribute `genus` is declared in class `Pet` to have type `Pet` but is never initialized. +enums_members.py:15:0 Uninitialized attribute [13]: Attribute `species` is declared in class `Pet` to have type `Pet` but is never initialized. +enums_members.py:23:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `str` but got `Pet`. +enums_members.py:24:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `str` but got `Pet`. +enums_members.py:31:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `str` but got `Pet2`. +enums_members.py:32:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `str` but got `Pet2`. +enums_members.py:80:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[Pet4.species]` but got `str`. +enums_members.py:81:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[Pet4.speak]` but got `typing.Callable(Pet4.speak)[[Named(self, Pet4)], None]`. +enums_members.py:96:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[TrafficLight.YELLOW]` but got `typing_extensions.Literal[TrafficLight.AMBER]`. +enums_members.py:113:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `typing_extensions.Literal[Example.c]` but got `member[typing.Callable(Example.c)[[Named(self, Example)], None]]`. +enums_members.py:124:8 Revealed type [-1]: Revealed type for `enums_members.Example2._Example2__B` is `typing_extensions.Literal[Example2._Example2__B]` (final). +enums_members.py:135:4 Inconsistent override [15]: `_ignore_` overrides attribute defined in `Enum` inconsistently. Type `Pet5` is not a subtype of the overridden attribute `typing.Union[typing.List[str], str]`. +enums_members.py:142:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `int` but got `Pet5`. +enums_members.py:143:0 Incompatible parameter type [6]: In call `assert_type`, for 1st positional argument, expected `int` but got `Pet5`. +""" diff --git a/conformance/results/pyre/version.toml b/conformance/results/pyre/version.toml index 737b7ea3a..47c7ebccd 100644 --- a/conformance/results/pyre/version.toml +++ b/conformance/results/pyre/version.toml @@ -1,2 +1,2 @@ version = "pyre 0.9.21" -test_duration = 4.1 +test_duration = 2.6 diff --git a/conformance/results/pyright/enums_behaviors.toml b/conformance/results/pyright/enums_behaviors.toml new file mode 100644 index 000000000..8a9ff77e3 --- /dev/null +++ b/conformance/results/pyright/enums_behaviors.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +errors_diff = """ +""" +output = """ +enums_behaviors.py:39:21 - error: Enum class "Shape" is final and cannot be subclassed (reportGeneralTypeIssues) +""" +conformance_automated = "Pass" diff --git a/conformance/results/pyright/enums_definition.toml b/conformance/results/pyright/enums_definition.toml new file mode 100644 index 000000000..513dbe853 --- /dev/null +++ b/conformance/results/pyright/enums_definition.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +errors_diff = """ +""" +output = """ +""" +conformance_automated = "Pass" diff --git a/conformance/results/pyright/enums_expansion.toml b/conformance/results/pyright/enums_expansion.toml new file mode 100644 index 000000000..c0150f0cc --- /dev/null +++ b/conformance/results/pyright/enums_expansion.toml @@ -0,0 +1,16 @@ +conformant = "Partial" +notes = """ +Applies type narrowing incorrectly for `a is E` pattern where `E` is an enum member. + +""" +conformance_automated = "Fail" +errors_diff = """ +Line 78: Unexpected errors ['enums_expansion.py:78:12 - error: Expression of type "list[Never]" is incompatible with return type "list[Answer]"'] +""" +output = """ +enums_expansion.py:53:21 - error: "assert_type" mismatch: expected "Literal[CustomFlags.FLAG3]" but received "CustomFlags" (reportAssertTypeFailure) +enums_expansion.py:78:12 - error: Expression of type "list[Never]" is incompatible with return type "list[Answer]" +  "list[Never]" is incompatible with "list[Answer]" +    Type parameter "_T@list" is invariant, but "Never" is not the same as "Answer" +    Consider switching from "list" to "Sequence" which is covariant (reportReturnType) +""" diff --git a/conformance/results/pyright/enums_member_names.toml b/conformance/results/pyright/enums_member_names.toml new file mode 100644 index 000000000..be211bd5b --- /dev/null +++ b/conformance/results/pyright/enums_member_names.toml @@ -0,0 +1,6 @@ +conformant = "Pass" +conformance_automated = "Pass" +errors_diff = """ +""" +output = """ +""" diff --git a/conformance/results/pyright/enums_member_values.toml b/conformance/results/pyright/enums_member_values.toml new file mode 100644 index 000000000..566fcd4f5 --- /dev/null +++ b/conformance/results/pyright/enums_member_values.toml @@ -0,0 +1,14 @@ +conformant = "Pass" +conformance_automated = "Pass" +errors_diff = """ +""" +output = """ +enums_member_values.py:50:12 - error: Argument missing for parameter "radius" (reportCallIssue) +enums_member_values.py:51:15 - error: Arguments missing for parameters "mass", "radius" (reportCallIssue) +enums_member_values.py:54:13 - error: "assert_type" mismatch: expected "Literal[1]" but received "Any" (reportAssertTypeFailure) +enums_member_values.py:68:13 - error: "assert_type" mismatch: expected "Literal[1]" but received "int" (reportAssertTypeFailure) +enums_member_values.py:78:13 - error: Expression of type "Literal['green']" is incompatible with declared type "int" +  "Literal['green']" is incompatible with "int" (reportAssignmentType) +enums_member_values.py:85:24 - error: Cannot assign to attribute "_value_" for class "Planet2*" +  "int" is incompatible with "str" (reportAttributeAccessIssue) +""" diff --git a/conformance/results/pyright/enums_members.toml b/conformance/results/pyright/enums_members.toml new file mode 100644 index 000000000..ed18e64b3 --- /dev/null +++ b/conformance/results/pyright/enums_members.toml @@ -0,0 +1,31 @@ +conformant = "Partial" +notes = """ +Does not reject use of annotation with enum member. +Does not treat annotated attributes as non-members in stub. +Does not support `_ignore_` mechanism (optional). +""" +conformance_automated = "Fail" +errors_diff = """ +Line 46: Expected 1 errors +Line 31: Unexpected errors ['enums_members.py:31:13 - error: "assert_type" mismatch: expected "str" but received "Literal[Pet2.genus]" (reportAssertTypeFailure)'] +Line 32: Unexpected errors ['enums_members.py:32:13 - error: "assert_type" mismatch: expected "str" but received "Literal[Pet2.species]" (reportAssertTypeFailure)'] +""" +output = """ +enums_members.py:31:13 - error: "assert_type" mismatch: expected "str" but received "Literal[Pet2.genus]" (reportAssertTypeFailure) +enums_members.py:32:13 - error: "assert_type" mismatch: expected "str" but received "Literal[Pet2.species]" (reportAssertTypeFailure) +enums_members.py:78:13 - error: "assert_type" mismatch: expected "Unknown" but received "(x: Unknown) -> str" (reportAssertTypeFailure) +enums_members.py:78:37 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +enums_members.py:79:13 - error: "assert_type" mismatch: expected "Unknown" but received "(x: int) -> int" (reportAssertTypeFailure) +enums_members.py:79:37 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +enums_members.py:80:13 - error: "assert_type" mismatch: expected "Unknown" but received "property" (reportAssertTypeFailure) +enums_members.py:80:35 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +enums_members.py:81:13 - error: "assert_type" mismatch: expected "Unknown" but received "(self: Pet4) -> None" (reportAssertTypeFailure) +enums_members.py:81:33 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +enums_members.py:112:13 - error: "assert_type" mismatch: expected "Unknown" but received "int" (reportAssertTypeFailure) +enums_members.py:112:32 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +enums_members.py:124:21 - information: Type of "Example2.__B" is "Literal[2]" +enums_members.py:125:21 - error: "assert_type" mismatch: expected "Unknown" but received "Literal[2]" (reportAssertTypeFailure) +enums_members.py:125:43 - error: Type arguments for "Literal" must be None, a literal value (int, bool, str, or bytes), or an enum value +enums_members.py:142:13 - error: "assert_type" mismatch: expected "int" but received "Literal[Pet5.DOG]" (reportAssertTypeFailure) +enums_members.py:143:13 - error: "assert_type" mismatch: expected "int" but received "Literal[Pet5.FISH]" (reportAssertTypeFailure) +""" diff --git a/conformance/results/pytype/enums_behaviors.toml b/conformance/results/pytype/enums_behaviors.toml new file mode 100644 index 000000000..ab9118cfb --- /dev/null +++ b/conformance/results/pytype/enums_behaviors.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Does not enforce that Enum classes are implicitly final. +""" +conformance_automated = "Fail" +errors_diff = """ +Line 39: Expected 1 errors +""" +output = """ + +""" diff --git a/conformance/results/pytype/enums_definition.toml b/conformance/results/pytype/enums_definition.toml new file mode 100644 index 000000000..29c66d614 --- /dev/null +++ b/conformance/results/pytype/enums_definition.toml @@ -0,0 +1,10 @@ +conformant = "Pass" +notes = """ +Does not support some functional forms for Enum class definitions (optional). +""" +errors_diff = """ +""" +output = """ +File "enums_definition.py", line 24, in : Function unsolveable.__new__ was called with the wrong arguments [wrong-arg-types] +""" +conformance_automated = "Pass" diff --git a/conformance/results/pytype/enums_expansion.toml b/conformance/results/pytype/enums_expansion.toml new file mode 100644 index 000000000..9f168ca05 --- /dev/null +++ b/conformance/results/pytype/enums_expansion.toml @@ -0,0 +1,11 @@ +conformant = "Partial" +notes = """ +Improperly applies narrowing to Flag subclass. +""" +conformance_automated = "Fail" +errors_diff = """ +Line 53: Expected 1 errors +""" +output = """ + +""" diff --git a/conformance/results/pytype/enums_member_names.toml b/conformance/results/pytype/enums_member_names.toml new file mode 100644 index 000000000..fb0bc9609 --- /dev/null +++ b/conformance/results/pytype/enums_member_names.toml @@ -0,0 +1,7 @@ +conformant = "Pass" +conformance_automated = "Pass" +errors_diff = """ +""" +output = """ + +""" diff --git a/conformance/results/pytype/enums_member_values.toml b/conformance/results/pytype/enums_member_values.toml new file mode 100644 index 000000000..c562dce2a --- /dev/null +++ b/conformance/results/pytype/enums_member_values.toml @@ -0,0 +1,18 @@ +conformant = "Partial" +notes = """ +Does not enforce declared type of `_value_`. +Does not correctly enforce assigned tuple types for enum members (optional). +Does not evaluate literal types for enum member values (optional). +Does not evaluate literal types for auto values (optional). +""" +conformance_automated = "Fail" +errors_diff = """ +Line 78: Expected 1 errors +Line 41: Unexpected errors ['File "enums_member_values.py", line 41, in : Missing parameter \\'mass\\' in call to function Planet.__init__ [missing-parameter]', 'File "enums_member_values.py", line 41, in : Function Planet.__init__ was called with the wrong arguments [wrong-arg-types]'] +""" +output = """ +File "enums_member_values.py", line 41, in : Missing parameter 'mass' in call to function Planet.__init__ [missing-parameter] +File "enums_member_values.py", line 41, in : Function Planet.__init__ was called with the wrong arguments [wrong-arg-types] +File "enums_member_values.py", line 85, in __init__: Type annotation for _value_ does not match type of assignment [annotation-type-mismatch] +File "enums_member_values.py", line 96, in : Any [assert-type] +""" diff --git a/conformance/results/pytype/enums_members.toml b/conformance/results/pytype/enums_members.toml new file mode 100644 index 000000000..3d3967c88 --- /dev/null +++ b/conformance/results/pytype/enums_members.toml @@ -0,0 +1,39 @@ +conformant = "Partial" +notes = """ +Does not reject use of annotation with enum member. +Does not support `enum.member` and `enum.nonmember`. +Does not support `_ignore_` mechanism (optional). +""" +conformance_automated = "Fail" +errors_diff = """ +Line 46: Expected 1 errors +Line 112: Expected 1 errors +Line 125: Expected 1 errors +Line 7: Unexpected errors ['File "enums_members.py", line 7, in : enum.nonmember not supported yet [not-supported-yet]', 'File "enums_members.py", line 7, in : enum.member not supported yet [not-supported-yet]'] +Line 31: Unexpected errors ['File "enums_members.py", line 31, in : _enums_members.Pet2 [assert-type]'] +Line 32: Unexpected errors ['File "enums_members.py", line 32, in : _enums_members.Pet2 [assert-type]'] +Line 103: Unexpected errors ['File "enums_members.py", line 103, in Example: Function enum.member expects 0 arg(s), got 1 [wrong-arg-count]'] +Line 104: Unexpected errors ['File "enums_members.py", line 104, in Example: Function enum.nonmember expects 0 arg(s), got 1 [wrong-arg-count]'] +Line 106: Unexpected errors ['File "enums_members.py", line 106, in Example: Function enum.member expects 0 arg(s), got 1 [wrong-arg-count]'] +Line 124: Unexpected errors ['File "enums_members.py", line 124, in method: Example2 [reveal-type]'] +""" +output = """ +File "enums_members.py", line 7, in : enum.nonmember not supported yet [not-supported-yet] +File "enums_members.py", line 7, in : enum.member not supported yet [not-supported-yet] +File "enums_members.py", line 31, in : _enums_members.Pet2 [assert-type] +File "enums_members.py", line 32, in : _enums_members.Pet2 [assert-type] +File "enums_members.py", line 78, in : Invalid type annotation 'Literal' [invalid-annotation] +File "enums_members.py", line 78, in : Callable[[Any], Any] [assert-type] +File "enums_members.py", line 79, in : Invalid type annotation 'Literal' [invalid-annotation] +File "enums_members.py", line 79, in : Callable[[int], int] [assert-type] +File "enums_members.py", line 80, in : property [assert-type] +File "enums_members.py", line 80, in : Invalid type annotation 'Literal' [invalid-annotation] +File "enums_members.py", line 81, in : Invalid type annotation 'Literal' [invalid-annotation] +File "enums_members.py", line 81, in : Callable[[Any], None] [assert-type] +File "enums_members.py", line 103, in Example: Function enum.member expects 0 arg(s), got 1 [wrong-arg-count] +File "enums_members.py", line 104, in Example: Function enum.nonmember expects 0 arg(s), got 1 [wrong-arg-count] +File "enums_members.py", line 106, in Example: Function enum.member expects 0 arg(s), got 1 [wrong-arg-count] +File "enums_members.py", line 124, in method: Example2 [reveal-type] +File "enums_members.py", line 142, in : Pet5 [assert-type] +File "enums_members.py", line 143, in : Pet5 [assert-type] +""" diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml index 3d4735eb9..e1fc04b63 100644 --- a/conformance/results/pytype/version.toml +++ b/conformance/results/pytype/version.toml @@ -1,2 +1,2 @@ version = "pytype 2024.04.11" -test_duration = 29.7 +test_duration = 29.9 diff --git a/conformance/results/results.html b/conformance/results/results.html index 268c79091..7f2380997 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -159,16 +159,16 @@

Python Type System Conformance Test Results

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conformance/src/test_groups.toml b/conformance/src/test_groups.toml index 155287f48..f6588f561 100644 --- a/conformance/src/test_groups.toml +++ b/conformance/src/test_groups.toml @@ -67,6 +67,10 @@ href = "https://typing.readthedocs.io/en/latest/spec/tuples.html" name = "Named tuples" href = "https://typing.readthedocs.io/en/latest/spec/namedtuples.html" +[enums] +name = "Enumerations" +href = "https://typing.readthedocs.io/en/latest/spec/enums.html" + [narrowing] name = "Type narrowing" href = "https://typing.readthedocs.io/en/latest/spec/narrowing.html" diff --git a/conformance/tests/_enums_member_values.py b/conformance/tests/_enums_member_values.py new file mode 100644 index 000000000..c51259fe9 --- /dev/null +++ b/conformance/tests/_enums_member_values.py @@ -0,0 +1,3 @@ +""" +Dummy implementation _enums_member_values stub. +""" diff --git a/conformance/tests/_enums_member_values.pyi b/conformance/tests/_enums_member_values.pyi new file mode 100644 index 000000000..4778f266f --- /dev/null +++ b/conformance/tests/_enums_member_values.pyi @@ -0,0 +1,15 @@ +""" +Support stub file for enums_member_values test. +""" + +from enum import Enum + +# > If the literal values for enum members are not supplied, as they sometimes +# > are not within a type stub file, a type checker can use the type of the +# > _value_ attribute. + +class ColumnType(Enum): + _value_: int + DORIC = ... + IONIC = ... + CORINTHIAN = ... diff --git a/conformance/tests/_enums_members.py b/conformance/tests/_enums_members.py new file mode 100644 index 000000000..3a6d43227 --- /dev/null +++ b/conformance/tests/_enums_members.py @@ -0,0 +1,3 @@ +""" +Dummy implementation for stub file for enums_members test. +""" diff --git a/conformance/tests/_enums_members.pyi b/conformance/tests/_enums_members.pyi new file mode 100644 index 000000000..73f3cdc56 --- /dev/null +++ b/conformance/tests/_enums_members.pyi @@ -0,0 +1,15 @@ +""" +Support stub file for enums_members test. +""" + +from enum import Enum + +# > Within a type stub, members can be defined using the actual runtime values, +# > or a placeholder of ... can be used + +class Pet2(Enum): + genus: str # Non-member attribute + species: str # Non-member attribute + + CAT = ... # Member attribute + DOG = ... # Member attribute diff --git a/conformance/tests/enums_behaviors.py b/conformance/tests/enums_behaviors.py new file mode 100644 index 000000000..fa3aeec7d --- /dev/null +++ b/conformance/tests/enums_behaviors.py @@ -0,0 +1,40 @@ +""" +Tests basic behaviors of of Enum classes. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/enums.html#enum-definition + +from enum import Enum +from typing import assert_type + +# > Enum classes are iterable and indexable, and they can be called with a +# > value to look up the enum member with that value. Type checkers should +# > support these behaviors + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + +for color in Color: + assert_type(color, Color) + +# > Unlike most Python classes, Calling an enum class does not invoke its +# > constructor. Instead, the call performs a value-based lookup of an +# > enum member. + +assert_type(Color["RED"], Color) # 'Literal[Color.RED]' is also acceptable +assert_type(Color(3), Color) # 'Literal[Color.BLUE]' is also acceptable + + +# > An Enum class with one or more defined members cannot be subclassed. + +class EnumWithNoMembers(Enum): + pass + +class Shape(EnumWithNoMembers): # OK (because no members are defined) + SQUARE = 1 + CIRCLE = 2 + +class ExtendedShape(Shape): # E: Shape is implicitly final + TRIANGLE = 3 diff --git a/conformance/tests/enums_definition.py b/conformance/tests/enums_definition.py new file mode 100644 index 000000000..31a137c40 --- /dev/null +++ b/conformance/tests/enums_definition.py @@ -0,0 +1,75 @@ +""" +Tests handling of Enum class definitions using the class syntax. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/enums.html#enum-definition + +from enum import Enum, EnumType +from typing import Literal, assert_type + +# > Type checkers should support the class syntax + + +class Color1(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +assert_type(Color1.RED, Literal[Color1.RED]) + + +# > The function syntax (in its various forms) is optional + +Color2 = Enum("Color2", "RED", "GREEN", "BLUE") # E? +Color3 = Enum("Color3", ["RED", "GREEN", "BLUE"]) # E? +Color4 = Enum("Color4", ("RED", "GREEN", "BLUE")) # E? +Color5 = Enum("Color5", "RED, GREEN, BLUE") # E? +Color6 = Enum("Color6", "RED GREEN BLUE") # E? +Color7 = Enum("Color7", [("RED", 1), ("GREEN", 2), ("BLUE", 3)]) # E? +Color8 = Enum("Color8", (("RED", 1), ("GREEN", 2), ("BLUE", 3))) # E? +Color9 = Enum("Color9", {"RED": 1, "GREEN": 2, "BLUE": 3}) # E? + +assert_type(Color2.RED, Literal[Color2.RED]) # E? +assert_type(Color3.RED, Literal[Color3.RED]) # E? +assert_type(Color4.RED, Literal[Color4.RED]) # E? +assert_type(Color5.RED, Literal[Color5.RED]) # E? +assert_type(Color6.RED, Literal[Color6.RED]) # E? +assert_type(Color7.RED, Literal[Color7.RED]) # E? +assert_type(Color8.RED, Literal[Color8.RED]) # E? +assert_type(Color9.RED, Literal[Color9.RED]) # E? + + +# > Enum classes can also be defined using a subclass of enum.Enum or any class +# > that uses enum.EnumType (or a subclass thereof) as a metaclass. +# > Type checkers should treat such classes as enums + + +class CustomEnum1(Enum): + pass + + +class Color10(CustomEnum1): + RED = 1 + GREEN = 2 + BLUE = 3 + + +assert_type(Color10.RED, Literal[Color10.RED]) + + +class CustomEnumType(EnumType): + pass + + +class CustomEnum2(metaclass=CustomEnumType): + pass + + +class Color11(CustomEnum2): + RED = 1 + GREEN = 2 + BLUE = 3 + + +assert_type(Color11.RED, Literal[Color11.RED]) diff --git a/conformance/tests/enums_expansion.py b/conformance/tests/enums_expansion.py new file mode 100644 index 000000000..57f701f10 --- /dev/null +++ b/conformance/tests/enums_expansion.py @@ -0,0 +1,78 @@ +""" +Tests that the type checker handles literal expansion of enum classes. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/enums.html#enum-literal-expansion + +from enum import Enum, Flag +from typing import Literal, Never, assert_type + +# > From the perspective of the type system, most enum classes are equivalent +# > to the union of the literal members within that enum. Type checkers may +# > therefore expand an enum type + + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +def print_color1(c: Color): + if c is Color.RED or c is Color.BLUE: + print("red or blue") + else: + assert_type(c, Literal[Color.GREEN]) # E? + + +def print_color2(c: Color): + match c: + case Color.RED | Color.BLUE: + print("red or blue") + case Color.GREEN: + print("green") + case _: + assert_type(c, Never) # E? + + +# > This rule does not apply to classes that derive from enum. Flag because +# > these enums allow flags to be combined in arbitrary ways. + + +class CustomFlags(Flag): + FLAG1 = 1 + FLAG2 = 2 + FLAG3 = 4 + + +def test1(f: CustomFlags): + if f is CustomFlags.FLAG1 or f is CustomFlags.FLAG2: + print("flag1 and flag2") + else: + assert_type(f, CustomFlags) + assert_type(f, Literal[CustomFlags.FLAG3]) # E + + +def test2(f: CustomFlags): + match f: + case CustomFlags.FLAG1 | CustomFlags.FLAG2: + pass + case CustomFlags.FLAG3: + pass + case _: + assert_type(f, CustomFlags) + + +# > A type checker should treat a complete union of all literal members as +# > compatible with the enum type. + + +class Answer(Enum): + Yes = 1 + No = 2 + + +def test3(val: object) -> list[Answer]: + assert val is Answer.Yes or val is Answer.No + x = [val] + return x diff --git a/conformance/tests/enums_member_names.py b/conformance/tests/enums_member_names.py new file mode 100644 index 000000000..dc9ee0aae --- /dev/null +++ b/conformance/tests/enums_member_names.py @@ -0,0 +1,30 @@ +""" +Tests that the type checker handles the `_name_` and `name` attributes correctly. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/enums.html#member-names + +from enum import Enum +from typing import Literal, assert_type + +# > All enum member objects have an attribute _name_ that contains the member’s +# > name. They also have a property name that returns the same name. Type +# > checkers may infer a literal type for the name of a member + + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +assert_type(Color.RED._name_, Literal["RED"]) # E? +assert_type(Color.RED.name, Literal["RED"]) # E? + + +def func1(red_or_blue: Literal[Color.RED, Color.BLUE]): + assert_type(red_or_blue.name, Literal["RED", "BLUE"]) # E? + + +def func2(any_color: Color): + assert_type(any_color.name, Literal["RED", "BLUE", "GREEN"]) # E? diff --git a/conformance/tests/enums_member_values.py b/conformance/tests/enums_member_values.py new file mode 100644 index 000000000..741099596 --- /dev/null +++ b/conformance/tests/enums_member_values.py @@ -0,0 +1,96 @@ +""" +Tests that the type checker handles the `_value_` and `value` attributes correctly. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/enums.html#member-values + +# > All enum member objects have an attribute _value_ that contains the +# > member’s value. They also have a property value that returns the same value. +# > Type checkers may infer the type of a member’s value. + +from enum import Enum, auto +from typing import Literal, assert_type + + +class Color(Enum): + RED = 1 + GREEN = 2 + BLUE = 3 + + +assert_type(Color.RED._value_, Literal[1]) # E? +assert_type(Color.RED.value, Literal[1]) # E? + + +def func1(red_or_blue: Literal[Color.RED, Color.BLUE]): + assert_type(red_or_blue.value, Literal[1, 3]) # E? + + +def func2(any_color: Color): + assert_type(any_color.value, Literal[1, 2, 3]) # E? + + +# > The value of _value_ can be assigned in a constructor method. This +# > technique is sometimes used to initialize both the member value and +# > non-member attributes. If the value assigned in the class body is a tuple, +# > the unpacked tuple value is passed to the constructor. Type checkers may +# > validate consistency between assigned tuple values and the constructor +# > signature. + + +class Planet(Enum): + def __init__(self, value: int, mass: float, radius: float): + self._value_ = value + self.mass = mass + self.radius = radius + + MERCURY = (1, 3.303e23, 2.4397e6) + VENUS = (2, 4.869e24, 6.0518e6) + EARTH = (3, 5.976e24, 6.37814e6) + MARS = (6.421e23, 3.3972e6) # E?: Type checker error (optional) + JUPITER = 5 # E?: Type checker error (optional) + + +assert_type(Planet.MERCURY.value, Literal[1]) # E? + + +# > The class enum.auto and method _generate_next_value_ can be used within +# > an enum class to automatically generate values for enum members. +# > Type checkers may support these to infer literal types for member values. + + +class Color2(Enum): + RED = auto() + GREEN = auto() + BLUE = auto() + + +assert_type(Color2.RED.value, Literal[1]) # E? + +# > If an enum class provides an explicit type annotation for _value_, type +# > checkers should enforce this declared type when values are assigned to +# > _value_. + + +class Color3(Enum): + _value_: int + RED = 1 # OK + GREEN = "green" # E + + +class Planet2(Enum): + _value_: str + + def __init__(self, value: int, mass: float, radius: float): + self._value_ = value # E + + MERCURY = (1, 3.303e23, 2.4397e6) + + +from _enums_member_values import ColumnType + +# > If the literal values for enum members are not supplied, as they sometimes +# > are not within a type stub file, a type checker can use the type of the +# > _value_ attribute. + +assert_type(ColumnType.DORIC.value, int) # E? diff --git a/conformance/tests/enums_members.py b/conformance/tests/enums_members.py new file mode 100644 index 000000000..505cacfcc --- /dev/null +++ b/conformance/tests/enums_members.py @@ -0,0 +1,143 @@ +""" +Tests that the type checker can distinguish enum members from non-members. +""" + +# Specification: https://typing.readthedocs.io/en/latest/spec/enums.html#defining-members + +from enum import Enum, member, nonmember +from typing import Literal, assert_type + +# > If an attribute is defined in the class body with a type annotation but +# > with no assigned value, a type checker should assume this is a non-member +# > attribute + + +class Pet(Enum): # E?: Uninitialized attributes (pyre) + genus: str # Non-member attribute + species: str # Non-member attribute + + CAT = 1 # Member attribute + DOG = 2 # Member attribute + + +assert_type(Pet.genus, str) +assert_type(Pet.species, str) +assert_type(Pet.CAT, Literal[Pet.CAT]) +assert_type(Pet.DOG, Literal[Pet.DOG]) + + +from _enums_members import Pet2 + +assert_type(Pet2.genus, str) +assert_type(Pet2.species, str) +assert_type(Pet2.CAT, Literal[Pet2.CAT]) +assert_type(Pet2.DOG, Literal[Pet2.DOG]) + + +# > Members defined within an enum class should not include explicit type +# > annotations. Type checkers should infer a literal type for all members. +# > A type checker should report an error if a type annotation is used for +# > an enum member because this type will be incorrect and misleading to +# > readers of the code + + +class Pet3(Enum): + CAT = 1 + DOG: int = 2 # E + + +# > Methods, callables, descriptors (including properties), and nested classes +# > that are defined in the class are not treated as enum members by the +# > EnumType metaclass and should likewise not be treated as enum members by a +# > type checker + + +def identity(x: int) -> int: + return x + + +class Pet4(Enum): + CAT = 1 # Member attribute + DOG = 2 # Member attribute + + converter = lambda x: str(x) # Non-member attribute + transform = staticmethod(identity) # Non-member attribute + + @property + def species(self) -> str: # Non-member property + return "mammal" + + def speak(self) -> None: # Non-member method + print("meow" if self is Pet.CAT else "woof") + + class Nested: ... # Non-member nested class + + +assert_type(Pet4.CAT, Literal[Pet4.CAT]) +assert_type(Pet4.DOG, Literal[Pet4.DOG]) +assert_type(Pet4.converter, Literal[Pet4.converter]) # E +assert_type(Pet4.transform, Literal[Pet4.transform]) # E +assert_type(Pet4.species, Literal[Pet4.species]) # E +assert_type(Pet4.speak, Literal[Pet4.speak]) # E + + +# > An attribute that is assigned the value of another member of the same +# > enum is not a member itself. Instead, it is an alias for the first member + + +class TrafficLight(Enum): + RED = 1 + GREEN = 2 + YELLOW = 3 + + AMBER = YELLOW # Alias for YELLOW + + +assert_type(TrafficLight.AMBER, Literal[TrafficLight.YELLOW]) + +# > If using Python 3.11 or newer, the enum.member and enum.nonmember classes +# > can be used to unambiguously distinguish members from non-members. + + +class Example(Enum): + a = member(1) # Member attribute + b = nonmember(2) # Non-member attribute + + @member + def c(self) -> None: # Member method + pass + + +assert_type(Example.a, Literal[Example.a]) +assert_type(Example.b, Literal[Example.b]) # E +assert_type(Example.c, Literal[Example.c]) + + +# > An attribute with a private name (beginning with, but not ending in, +# > a double underscore) is treated as a non-member. + + +class Example2(Enum): + __B = 2 # Non-member attribute + + def method(self): + reveal_type(Example2.__B) + assert_type(Example2.__B, Literal[Example2.__B]) # E + + +# > An enum class can define a class symbol named _ignore_. This can be +# > a list of names or a string containing a space-delimited list of names +# > that are deleted from the enum class at runtime. Type checkers may +# > support this mechanism + + +class Pet5(Enum): + _ignore_ = "DOG FISH" + CAT = 1 # Member attribute + DOG = 2 # temporary variable, will be removed from the final enum class + FISH = 3 # temporary variable, will be removed from the final enum class + + +assert_type(Pet5.CAT, Literal[Pet5.CAT]) +assert_type(Pet5.DOG, int) # E?: Literal[2] is also acceptable +assert_type(Pet5.FISH, int) # E?: Literal[3] is also acceptable
 
mypy 1.10.0
-
1.5sec
+
0.9sec
pyright 1.1.365
1.4sec
pyre 0.9.21
-
4.1sec
+
2.6sec
pytype 2024.04.11
-
29.7sec
+
29.9sec
@@ -898,6 +898,45 @@

Python Type System Conformance Test Results

Partial

Incorrectly rejects valid index of named tuple instance when using a negative index.

Does not report out-of-range index access with named tuple instance.

Does not reject attempt to overwrite named tuple entry by name.

Does not reject attempt to delete named tuple entry by name.

+Enumerations +
     enums_behaviorsPassPass
Partial

Does not enforce that Enum classes are implicitly final.

Partial

Does not enforce that Enum classes are implicitly final.

     enums_definitionPassPass
Partial

Does not support custom Enum classes based on EnumType metaclass.

Does not support some functional forms for Enum class definitions (optional).

Pass*

Does not support some functional forms for Enum class definitions (optional).

     enums_expansion
Partial

Improperly applies narrowing to Flag subclass.

Partial

Applies type narrowing incorrectly for `a is E` pattern where `E` is an enum member.

Pass*

Does not perform type narrowing based on enum literal expansion (optional).

Partial

Improperly applies narrowing to Flag subclass.

     enums_member_names
Pass*

Does not support special-cased handling of member name literal types in some cases (optional).

Pass
Pass*

Does not support special-cased handling of member name literal types (optional).

Pass
     enums_member_values
Partial

Does not enforce declared type of `_value_`.

Does not enforce assigned tuple types for enum members (optional).

Pass
Partial

Does not enforce declared type of `_value_`.

Does not enforce assigned tuple types for enum members (optional).

Does not evaluate literal types for enum member values (optional).

Does not evaluate literal types for auto values (optional).

Partial

Does not enforce declared type of `_value_`.

Does not correctly enforce assigned tuple types for enum members (optional).

Does not evaluate literal types for enum member values (optional).

Does not evaluate literal types for auto values (optional).

     enums_members
Partial

Does not treat attribute with annotation and no assignment as non-member.

Does not reject use of annotation with enum member.

Does not treat callables as non-members.

Does not honor `enum.nonmember` to define non-member attribute.

Does not honor `enum.member` as method decorator.

Does not properly handle aliased enum members.

Does not support `_ignore_` mechanism (optional).

Partial

Does not reject use of annotation with enum member.

Does not treat annotated attributes as non-members in stub.

Does not support `_ignore_` mechanism (optional).

Partial

Does not reject use of annotation with enum member.

Does not treat callables as non-members.

Does not treat annotated attributes as non-members.

Does not honor `enum.nonmember` to define non-member attribute.

Does not honor `enum.member` as method decorator.

Does not properly handle aliased enum members.

Rejects use of `_ignore_`.

Does not support `_ignore_` mechanism (optional).

Partial

Does not reject use of annotation with enum member.

Does not support `enum.member` and `enum.nonmember`.

Does not support `_ignore_` mechanism (optional).

Type narrowing
     narrowing_typeguard