Skip to content

Commit d168b1d

Browse files
committed
Conformance: test @OverRide checks on __init__ and __new__
The @OverRide spec requires the overriding method to be assignable to the overridden one, with no exemption for __init__/__new__. Add cases to classes_override.py covering compatible overrides (no error), incompatible overrides decorated with @OverRide (error), and incompatible overrides without @OverRide (allowed). Score results: pyrefly conforms; mypy, pyright, pycroscope, ty, and zuban do not currently honor the check for constructors. Closes #2222
1 parent 94514b1 commit d168b1d

8 files changed

Lines changed: 95 additions & 12 deletions

File tree

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
conformant = "Pass"
1+
conformant = "Partial"
2+
notes = """
3+
Does not honor `@override` compatibility checks for `__init__` and `__new__`:
4+
the incompatible constructor overrides in `ChildC2` are not flagged.
5+
"""
26
output = """
37
classes_override.py:53: error: Method "method3" is marked as an override, but no base method was found with this name [misc]
48
classes_override.py:56: error: Method "method4" is marked as an override, but no base method was found with this name [misc]
59
classes_override.py:79: error: Method "static_method1" is marked as an override, but no base method was found with this name [misc]
610
classes_override.py:84: error: Method "class_method1" is marked as an override, but no base method was found with this name [misc]
711
classes_override.py:89: error: Method "property1" is marked as an override, but no base method was found with this name [misc]
812
"""
9-
conformance_automated = "Pass"
13+
conformance_automated = "Fail"
1014
errors_diff = """
15+
Lines 134, 135: Expected error (tag 'init')
16+
Lines 137, 138: Expected error (tag 'new')
1117
"""

‎conformance/results/pycroscope/classes_override.toml‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
conformance_automated = "Pass"
1+
conformant = "Partial"
2+
notes = """
3+
Does not honor `@override` compatibility checks for `__init__` and `__new__`:
4+
the incompatible constructor overrides in `ChildC2` are not flagged.
5+
"""
6+
conformance_automated = "Fail"
27
errors_diff = """
8+
Lines 134, 135: Expected error (tag 'init')
9+
Lines 137, 138: Expected error (tag 'new')
310
"""
411
output = """
512
./classes_override.py:53:4: Method does not override any base method [override_does_not_override]

‎conformance/results/pyrefly/classes_override.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ ERROR classes_override.py:57:9-16: Class member `ChildA.method4` is marked as an
88
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]
99
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]
1010
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]
11+
ERROR classes_override.py:135:9-17: Class member `ChildC2.__init__` overrides parent class `ParentC` in an inconsistent manner [bad-override]
12+
ERROR classes_override.py:138:9-16: Class member `ChildC2.__new__` overrides parent class `ParentC` in an inconsistent manner [bad-override]
1113
"""
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1-
conformant = "Pass"
1+
conformant = "Partial"
2+
notes = """
3+
Does not honor `@override` compatibility checks for `__init__` and `__new__`:
4+
the incompatible constructor overrides in `ChildC2` are not flagged.
5+
"""
26
output = """
37
classes_override.py:53:9 - error: Method "method3" is marked as override, but no base method of same name is present (reportGeneralTypeIssues)
48
classes_override.py:65:9 - error: Method "method4" is marked as override, but no base method of same name is present (reportGeneralTypeIssues)
59
classes_override.py:79:9 - error: Method "static_method1" is marked as override, but no base method of same name is present (reportGeneralTypeIssues)
610
classes_override.py:84:9 - error: Method "class_method1" is marked as override, but no base method of same name is present (reportGeneralTypeIssues)
711
classes_override.py:89:9 - error: Method "property1" is marked as override, but no base method of same name is present (reportGeneralTypeIssues)
812
"""
9-
conformance_automated = "Pass"
13+
conformance_automated = "Fail"
1014
errors_diff = """
15+
Lines 134, 135: Expected error (tag 'init')
16+
Lines 137, 138: Expected error (tag 'new')
1117
"""

‎conformance/results/results.html‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -564,12 +564,12 @@ <h3>Python Type System Conformance Test Results</h3>
564564
<th class="column col2 conformant">Pass</th>
565565
</tr>
566566
<tr><th class="column col1">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;classes_override</th>
567+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not honor `@override` compatibility checks for `__init__` and `__new__`:</p><p>the incompatible constructor overrides in `ChildC2` are not flagged.</p></span></div></th>
568+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not honor `@override` compatibility checks for `__init__` and `__new__`:</p><p>the incompatible constructor overrides in `ChildC2` are not flagged.</p></span></div></th>
569+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not honor `@override` compatibility checks for `__init__` and `__new__`:</p><p>the incompatible constructor overrides in `ChildC2` are not flagged.</p></span></div></th>
567570
<th class="column col2 conformant">Pass</th>
568-
<th class="column col2 conformant">Pass</th>
569-
<th class="column col2 conformant">Pass</th>
570-
<th class="column col2 conformant">Pass</th>
571-
<th class="column col2 conformant">Pass</th>
572-
<th class="column col2 conformant">Pass</th>
571+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not honor `@override` compatibility checks for `__init__` and `__new__`:</p><p>the incompatible constructor overrides in `ChildC2` are not flagged.</p></span></div></th>
572+
<th class="column col2 partially-conformant"><div class="hover-text">Partial<span class="tooltip-text" id="bottom"><p>Does not honor `@override` compatibility checks for `__init__` and `__new__`:</p><p>the incompatible constructor overrides in `ChildC2` are not flagged.</p></span></div></th>
573573
</tr>
574574
<tr><th class="column" colspan="7">
575575
<a class="test_group" href="https://typing.readthedocs.io/en/latest/spec/aliases.html">Type aliases</a>

‎conformance/results/ty/classes_override.toml‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
conformance_automated = "Pass"
1+
conformant = "Partial"
2+
notes = """
3+
Does not honor `@override` compatibility checks for `__init__` and `__new__`:
4+
the incompatible constructor overrides in `ChildC2` are not flagged.
5+
"""
6+
conformance_automated = "Fail"
27
errors_diff = """
8+
Lines 134, 135: Expected error (tag 'init')
9+
Lines 137, 138: Expected error (tag 'new')
310
"""
411
output = """
512
classes_override.py:53:9: error[invalid-explicit-override] Method `method3` is decorated with `@override` but does not override anything

‎conformance/results/zuban/classes_override.toml‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
conformance_automated = "Pass"
1+
conformant = "Partial"
2+
notes = """
3+
Does not honor `@override` compatibility checks for `__init__` and `__new__`:
4+
the incompatible constructor overrides in `ChildC2` are not flagged.
5+
"""
6+
conformance_automated = "Fail"
27
errors_diff = """
8+
Lines 134, 135: Expected error (tag 'init')
9+
Lines 137, 138: Expected error (tag 'new')
310
"""
411
output = """
512
classes_override.py:53: error: Method "method3" is marked as an override, but no base method was found with this name [misc]

‎conformance/tests/classes_override.py‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,51 @@ class ChildB(ParentB):
100100
@override
101101
def method1(self) -> None: # OK
102102
pass
103+
104+
105+
# > When type checkers encounter a method decorated with @typing.override they
106+
# > should treat it as a type error unless that method is overriding a method or
107+
# > attribute in some ancestor class, and the type of the overriding method is
108+
# > assignable to the type of the overridden method.
109+
110+
# ``__init__`` and ``__new__`` are normally exempt from override compatibility
111+
# checks, since constructors are not subject to the Liskov substitution
112+
# principle. However, when they are explicitly decorated with ``@override`` the
113+
# decorator's assignability check should still be honored.
114+
# See https://github.com/python/typing/issues/2222
115+
116+
117+
class ParentC:
118+
def __init__(self, x: int) -> None: ...
119+
120+
def __new__(cls, x: int) -> "ParentC":
121+
raise NotImplementedError
122+
123+
124+
class ChildC1(ParentC):
125+
@override
126+
def __init__(self, x: int) -> None: ... # OK
127+
128+
@override
129+
def __new__(cls, x: int) -> "ChildC1": # OK
130+
raise NotImplementedError
131+
132+
133+
class ChildC2(ParentC):
134+
@override # E[init]
135+
def __init__(self, x: str) -> None: ... # E[init]: not assignable to "ParentC.__init__"
136+
137+
@override # E[new]
138+
def __new__(cls, x: str) -> "ChildC2": # E[new]: not assignable to "ParentC.__new__"
139+
raise NotImplementedError
140+
141+
142+
# Without ``@override`` an incompatible constructor signature is allowed, since
143+
# ``__init__`` and ``__new__`` are exempt from the usual override checks.
144+
145+
146+
class ChildC3(ParentC):
147+
def __init__(self, x: str) -> None: ... # OK
148+
149+
def __new__(cls, x: str) -> "ChildC3": # OK
150+
raise NotImplementedError

0 commit comments

Comments
 (0)