diff --git a/conformance/results/mypy/classes_override.toml b/conformance/results/mypy/classes_override.toml index 45cceb4c..860203f3 100644 --- a/conformance/results/mypy/classes_override.toml +++ b/conformance/results/mypy/classes_override.toml @@ -1,4 +1,8 @@ -conformant = "Pass" +conformant = "Partial" +notes = """ +Does not honor `@override` compatibility checks for `__init__` and `__new__`: +the incompatible constructor overrides in `ChildC2` are not flagged. +""" output = """ classes_override.py:53: error: Method "method3" is marked as an override, but no base method was found with this name [misc] classes_override.py:56: error: Method "method4" is marked as an override, but no base method was found with this name [misc] @@ -6,6 +10,8 @@ classes_override.py:79: error: Method "static_method1" is marked as an override, classes_override.py:84: error: Method "class_method1" is marked as an override, but no base method was found with this name [misc] classes_override.py:89: error: Method "property1" is marked as an override, but no base method was found with this name [misc] """ -conformance_automated = "Pass" +conformance_automated = "Fail" errors_diff = """ +Lines 134, 135: Expected error (tag 'init') +Lines 137, 138: Expected error (tag 'new') """ diff --git a/conformance/results/pycroscope/classes_override.toml b/conformance/results/pycroscope/classes_override.toml index b73910e7..b5c0ee4f 100644 --- a/conformance/results/pycroscope/classes_override.toml +++ b/conformance/results/pycroscope/classes_override.toml @@ -1,5 +1,12 @@ -conformance_automated = "Pass" +conformant = "Partial" +notes = """ +Does not honor `@override` compatibility checks for `__init__` and `__new__`: +the incompatible constructor overrides in `ChildC2` are not flagged. +""" +conformance_automated = "Fail" errors_diff = """ +Lines 134, 135: Expected error (tag 'init') +Lines 137, 138: Expected error (tag 'new') """ output = """ ./classes_override.py:53:4: Method does not override any base method [override_does_not_override] diff --git a/conformance/results/pyrefly/classes_override.toml b/conformance/results/pyrefly/classes_override.toml index cb376ba0..bfb18ccf 100644 --- a/conformance/results/pyrefly/classes_override.toml +++ b/conformance/results/pyrefly/classes_override.toml @@ -8,4 +8,6 @@ ERROR classes_override.py:57:9-16: Class member `ChildA.method4` is marked as an ERROR classes_override.py:79:9-23: Class member `ChildA.static_method1` is marked as an override, but no parent class has a matching attribute [bad-override] ERROR classes_override.py:84:9-22: Class member `ChildA.class_method1` is marked as an override, but no parent class has a matching attribute [bad-override] ERROR classes_override.py:89:9-18: Class member `ChildA.property1` is marked as an override, but no parent class has a matching attribute [bad-override] +ERROR classes_override.py:135:9-17: Class member `ChildC2.__init__` overrides parent class `ParentC` in an inconsistent manner [bad-override] +ERROR classes_override.py:138:9-16: Class member `ChildC2.__new__` overrides parent class `ParentC` in an inconsistent manner [bad-override] """ diff --git a/conformance/results/pyright/classes_override.toml b/conformance/results/pyright/classes_override.toml index d27bd43d..42cda7e3 100644 --- a/conformance/results/pyright/classes_override.toml +++ b/conformance/results/pyright/classes_override.toml @@ -1,4 +1,8 @@ -conformant = "Pass" +conformant = "Partial" +notes = """ +Does not honor `@override` compatibility checks for `__init__` and `__new__`: +the incompatible constructor overrides in `ChildC2` are not flagged. +""" output = """ classes_override.py:53:9 - error: Method "method3" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) classes_override.py:65:9 - error: Method "method4" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) @@ -6,6 +10,8 @@ classes_override.py:79:9 - error: Method "static_method1" is marked as override, classes_override.py:84:9 - error: Method "class_method1" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) classes_override.py:89:9 - error: Method "property1" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) """ -conformance_automated = "Pass" +conformance_automated = "Fail" errors_diff = """ +Lines 134, 135: Expected error (tag 'init') +Lines 137, 138: Expected error (tag 'new') """ diff --git a/conformance/results/results.html b/conformance/results/results.html index 0edd52cc..e04ea49c 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -564,12 +564,12 @@

Python Type System Conformance Test Results

Pass      classes_override +
Partial

Does not honor `@override` compatibility checks for `__init__` and `__new__`:

the incompatible constructor overrides in `ChildC2` are not flagged.

+
Partial

Does not honor `@override` compatibility checks for `__init__` and `__new__`:

the incompatible constructor overrides in `ChildC2` are not flagged.

+
Partial

Does not honor `@override` compatibility checks for `__init__` and `__new__`:

the incompatible constructor overrides in `ChildC2` are not flagged.

Pass -Pass -Pass -Pass -Pass -Pass +
Partial

Does not honor `@override` compatibility checks for `__init__` and `__new__`:

the incompatible constructor overrides in `ChildC2` are not flagged.

+
Partial

Does not honor `@override` compatibility checks for `__init__` and `__new__`:

the incompatible constructor overrides in `ChildC2` are not flagged.

Type aliases diff --git a/conformance/results/ty/classes_override.toml b/conformance/results/ty/classes_override.toml index 52cef47d..90cb245d 100644 --- a/conformance/results/ty/classes_override.toml +++ b/conformance/results/ty/classes_override.toml @@ -1,5 +1,12 @@ -conformance_automated = "Pass" +conformant = "Partial" +notes = """ +Does not honor `@override` compatibility checks for `__init__` and `__new__`: +the incompatible constructor overrides in `ChildC2` are not flagged. +""" +conformance_automated = "Fail" errors_diff = """ +Lines 134, 135: Expected error (tag 'init') +Lines 137, 138: Expected error (tag 'new') """ output = """ classes_override.py:53:9: error[invalid-explicit-override] Method `method3` is decorated with `@override` but does not override anything diff --git a/conformance/results/zuban/classes_override.toml b/conformance/results/zuban/classes_override.toml index 6bc2112c..b33df3dd 100644 --- a/conformance/results/zuban/classes_override.toml +++ b/conformance/results/zuban/classes_override.toml @@ -1,5 +1,12 @@ -conformance_automated = "Pass" +conformant = "Partial" +notes = """ +Does not honor `@override` compatibility checks for `__init__` and `__new__`: +the incompatible constructor overrides in `ChildC2` are not flagged. +""" +conformance_automated = "Fail" errors_diff = """ +Lines 134, 135: Expected error (tag 'init') +Lines 137, 138: Expected error (tag 'new') """ output = """ classes_override.py:53: error: Method "method3" is marked as an override, but no base method was found with this name [misc] diff --git a/conformance/tests/classes_override.py b/conformance/tests/classes_override.py index 831e37d0..0bbf5eac 100644 --- a/conformance/tests/classes_override.py +++ b/conformance/tests/classes_override.py @@ -100,3 +100,51 @@ class ChildB(ParentB): @override def method1(self) -> None: # OK pass + + +# > When type checkers encounter a method decorated with @typing.override they +# > should treat it as a type error unless that method is overriding a method or +# > attribute in some ancestor class, and the type of the overriding method is +# > assignable to the type of the overridden method. + +# ``__init__`` and ``__new__`` are normally exempt from override compatibility +# checks, since constructors are not subject to the Liskov substitution +# principle. However, when they are explicitly decorated with ``@override`` the +# decorator's assignability check should still be honored. +# See https://github.com/python/typing/issues/2222 + + +class ParentC: + def __init__(self, x: int) -> None: ... + + def __new__(cls, x: int) -> "ParentC": + raise NotImplementedError + + +class ChildC1(ParentC): + @override + def __init__(self, x: int) -> None: ... # OK + + @override + def __new__(cls, x: int) -> "ChildC1": # OK + raise NotImplementedError + + +class ChildC2(ParentC): + @override # E[init] + def __init__(self, x: str) -> None: ... # E[init]: not assignable to "ParentC.__init__" + + @override # E[new] + def __new__(cls, x: str) -> "ChildC2": # E[new]: not assignable to "ParentC.__new__" + raise NotImplementedError + + +# Without ``@override`` an incompatible constructor signature is allowed, since +# ``__init__`` and ``__new__`` are exempt from the usual override checks. + + +class ChildC3(ParentC): + def __init__(self, x: str) -> None: ... # OK + + def __new__(cls, x: str) -> "ChildC3": # OK + raise NotImplementedError