From 5eabe5366b140a90fc607c0950faed9d5532219f Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Thu, 9 Jan 2025 18:43:04 -0800 Subject: [PATCH] add test for wrong use of override with overload --- .../results/mypy/overloads_invalid.toml | 46 +++++++----- .../results/pyre/overloads_invalid.toml | 11 +-- .../results/pyright/overloads_invalid.toml | 9 ++- conformance/results/pyright/version.toml | 2 +- .../results/pytype/overloads_invalid.toml | 74 ++++++++++++++++--- conformance/results/pytype/version.toml | 2 +- conformance/results/results.html | 8 +- conformance/tests/overloads_invalid.py | 23 +++++- 8 files changed, 129 insertions(+), 46 deletions(-) diff --git a/conformance/results/mypy/overloads_invalid.toml b/conformance/results/mypy/overloads_invalid.toml index 33b9360b..09598d02 100644 --- a/conformance/results/mypy/overloads_invalid.toml +++ b/conformance/results/mypy/overloads_invalid.toml @@ -5,7 +5,7 @@ Does not allow an overload with no implementation in an abstract base class. """ errors_diff = """ Line 49: Unexpected errors ['overloads_invalid.py:49: error: An overloaded function outside a stub file must have an implementation [no-overload-impl]'] -Line 199: Unexpected errors ['overloads_invalid.py:199: error: Signature of "good_override" incompatible with supertype "Base" [override]'] +Line 202: Unexpected errors ['overloads_invalid.py:202: error: Signature of "good_override" incompatible with supertype "Base" [override]'] """ output = """ overloads_invalid.py:14: error: Single overload definition, multiple required [misc] @@ -18,22 +18,30 @@ overloads_invalid.py:88: error: Overloaded function implementation does not acce overloads_invalid.py:91: error: Overload does not consistently use the "@classmethod" decorator on all function signatures. [misc] overloads_invalid.py:133: error: @final should be applied only to overload implementation [misc] overloads_invalid.py:148: error: @final should be applied only to overload implementation [misc] -overloads_invalid.py:173: error: Cannot override final attribute "final_method" (previously declared in base class "Base") [misc] -overloads_invalid.py:173: error: Signature of "final_method" incompatible with supertype "Base" [override] -overloads_invalid.py:173: note: Superclass: -overloads_invalid.py:173: note: @overload -overloads_invalid.py:173: note: def final_method(self, x: int) -> int -overloads_invalid.py:173: note: @overload -overloads_invalid.py:173: note: def final_method(self, x: str) -> str -overloads_invalid.py:173: note: Subclass: -overloads_invalid.py:173: note: def final_method(self, x: int | str) -> int | str -overloads_invalid.py:180: error: Method "bad_override" is marked as an override, but no base method was found with this name [misc] -overloads_invalid.py:199: error: Signature of "good_override" incompatible with supertype "Base" [override] -overloads_invalid.py:199: note: Superclass: -overloads_invalid.py:199: note: def good_override(self, x: int | str) -> int | str -overloads_invalid.py:199: note: Subclass: -overloads_invalid.py:199: note: @overload -overloads_invalid.py:199: note: def good_override(self, x: int) -> int -overloads_invalid.py:199: note: @overload -overloads_invalid.py:199: note: def good_override(self, x: str) -> str +overloads_invalid.py:176: error: Cannot override final attribute "final_method" (previously declared in base class "Base") [misc] +overloads_invalid.py:176: error: Signature of "final_method" incompatible with supertype "Base" [override] +overloads_invalid.py:176: note: Superclass: +overloads_invalid.py:176: note: @overload +overloads_invalid.py:176: note: def final_method(self, x: int) -> int +overloads_invalid.py:176: note: @overload +overloads_invalid.py:176: note: def final_method(self, x: str) -> str +overloads_invalid.py:176: note: Subclass: +overloads_invalid.py:176: note: def final_method(self, x: int | str) -> int | str +overloads_invalid.py:183: error: Method "bad_override" is marked as an override, but no base method was found with this name [misc] +overloads_invalid.py:202: error: Signature of "good_override" incompatible with supertype "Base" [override] +overloads_invalid.py:202: note: Superclass: +overloads_invalid.py:202: note: def good_override(self, x: int | str) -> int | str +overloads_invalid.py:202: note: Subclass: +overloads_invalid.py:202: note: @overload +overloads_invalid.py:202: note: def good_override(self, x: int) -> int +overloads_invalid.py:202: note: @overload +overloads_invalid.py:202: note: def good_override(self, x: str) -> str +overloads_invalid.py:217: error: Signature of "to_override" incompatible with supertype "Base" [override] +overloads_invalid.py:217: note: Superclass: +overloads_invalid.py:217: note: def to_override(self, x: int | str) -> int | str +overloads_invalid.py:217: note: Subclass: +overloads_invalid.py:217: note: @overload +overloads_invalid.py:217: note: def to_override(self, x: int) -> int +overloads_invalid.py:217: note: @overload +overloads_invalid.py:217: note: def to_override(self, x: str) -> str """ diff --git a/conformance/results/pyre/overloads_invalid.toml b/conformance/results/pyre/overloads_invalid.toml index 3fb24ed1..faa561d7 100644 --- a/conformance/results/pyre/overloads_invalid.toml +++ b/conformance/results/pyre/overloads_invalid.toml @@ -5,11 +5,12 @@ Does not allow an overload with no implementation in a Protocol or an abstract b Expects @final/@override on all overloads and implementation, instead of implementation only. """ errors_diff = """ +Line 217: Expected 1 errors Lines 148, 150: Expected error (tag 'invalid_final_2') Line 40: Unexpected errors ['overloads_invalid.py:40:4 Missing overload implementation [42]: Overloaded function `MyProto.func3` must have an implementation.'] Line 51: Unexpected errors ['overloads_invalid.py:51:4 Missing overload implementation [42]: Overloaded function `MyAbstractBase.func4` must have an implementation.'] Line 128: Unexpected errors ['overloads_invalid.py:128:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s).'] -Line 208: Unexpected errors ['overloads_invalid.py:208:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s).'] +Line 211: Unexpected errors ['overloads_invalid.py:211:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s).'] """ output = """ overloads_invalid.py:15:0 Incompatible overload [43]: At least two overload signatures must be present. @@ -24,8 +25,8 @@ overloads_invalid.py:97:4 Incompatible overload [43]: The implementation of `C.f overloads_invalid.py:97:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). overloads_invalid.py:128:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). overloads_invalid.py:139:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). -overloads_invalid.py:173:4 Invalid override [40]: `overloads_invalid.Child.final_method` cannot override final method defined in `Base`. -overloads_invalid.py:189:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). -overloads_invalid.py:189:4 Invalid override [40]: `overloads_invalid.Child.bad_override` is decorated with @override, but no method of the same name exists in superclasses of `Child`. -overloads_invalid.py:208:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). +overloads_invalid.py:176:4 Invalid override [40]: `overloads_invalid.Child.final_method` cannot override final method defined in `Base`. +overloads_invalid.py:192:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). +overloads_invalid.py:192:4 Invalid override [40]: `overloads_invalid.Child.bad_override` is decorated with @override, but no method of the same name exists in superclasses of `Child`. +overloads_invalid.py:211:4 Incompatible overload [43]: This definition does not have the same decorators as the preceding overload(s). """ diff --git a/conformance/results/pyright/overloads_invalid.toml b/conformance/results/pyright/overloads_invalid.toml index e2fa4ed8..e650c117 100644 --- a/conformance/results/pyright/overloads_invalid.toml +++ b/conformance/results/pyright/overloads_invalid.toml @@ -1,9 +1,10 @@ conformant = "Partial" notes = """ -Allows @final on all overloads and implementation; should be implementation-only. +Allows @final/@override on all overloads and implementation; should be implementation-only. """ conformance_automated = "Fail" errors_diff = """ +Line 217: Expected 1 errors Lines 148, 150: Expected error (tag 'invalid_final_2') """ output = """ @@ -26,10 +27,10 @@ overloads_invalid.py:88:9 - error: Overloaded implementation is not consistent w overloads_invalid.py:93:9 - error: Overloads for "func6" use @classmethod inconsistently (reportInconsistentOverload) overloads_invalid.py:97:15 - warning: Instance methods should take a "self" parameter (reportSelfClsParameterName) overloads_invalid.py:135:9 - error: Overload for "invalid_final" is marked @final but implementation is not (reportInconsistentOverload) -overloads_invalid.py:173:9 - error: Method "final_method" cannot override final method defined in class "Base" (reportIncompatibleMethodOverride) -overloads_invalid.py:173:9 - error: Method "final_method" overrides class "Base" in an incompatible manner +overloads_invalid.py:176:9 - error: Method "final_method" cannot override final method defined in class "Base" (reportIncompatibleMethodOverride) +overloads_invalid.py:176:9 - error: Method "final_method" overrides class "Base" in an incompatible manner   Return type mismatch: base method returns type "int", override returns type "int | str"     Type "int | str" is not assignable to type "int"       "str" is not assignable to "int" (reportIncompatibleMethodOverride) -overloads_invalid.py:189:9 - error: Method "bad_override" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) +overloads_invalid.py:192:9 - error: Method "bad_override" is marked as override, but no base method of same name is present (reportGeneralTypeIssues) """ diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml index 1ae5a35b..eef86e05 100644 --- a/conformance/results/pyright/version.toml +++ b/conformance/results/pyright/version.toml @@ -1,2 +1,2 @@ version = "pyright 1.1.391" -test_duration = 1.5 +test_duration = 1.6 diff --git a/conformance/results/pytype/overloads_invalid.toml b/conformance/results/pytype/overloads_invalid.toml index c6ecafd6..bab69e93 100644 --- a/conformance/results/pytype/overloads_invalid.toml +++ b/conformance/results/pytype/overloads_invalid.toml @@ -4,10 +4,11 @@ Does not reject a function with a single @overload signature. Does not reject a function with @overload signature but no implementation. Does not allow an overload with no implementation in a Protocol or an abstract base class. Does not error on overloads inconsistently decorated with `@staticmethod` or `@classmethod`. -Does not enforce any rules on location of @final decorator. +Does not enforce any rules on location of @final or @override decorators. """ conformance_automated = "Fail" errors_diff = """ +Line 217: Expected 1 errors Lines 14, 15: Expected error (tag 'func1') Lines 26, 27: Expected error (tag 'func2') Lines 63, 64: Expected error (tag 'not_abstract') @@ -25,9 +26,11 @@ Line 129: Unexpected errors ['overloads_invalid.py:129:9: \\x1b[1m\\x1b[31merror Line 143: Unexpected errors ['overloads_invalid.py:143:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in invalid_final: bad return type [bad-return-type]', 'overloads_invalid.py:143:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in invalid_final: bad return type [bad-return-type]'] Line 160: Unexpected errors ['overloads_invalid.py:160:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in invalid_final_2: bad return type [bad-return-type]', 'overloads_invalid.py:160:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in invalid_final_2: bad return type [bad-return-type]'] Line 165: Unexpected errors ['overloads_invalid.py:165:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in good_override: bad return type [bad-return-type]'] -Line 174: Unexpected errors ['overloads_invalid.py:174:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in final_method: bad return type [bad-return-type]'] -Line 190: Unexpected errors ['overloads_invalid.py:190:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in bad_override: bad return type [bad-return-type]', 'overloads_invalid.py:190:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in bad_override: bad return type [bad-return-type]'] -Line 209: Unexpected errors ['overloads_invalid.py:209:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in good_override: bad return type [bad-return-type]', 'overloads_invalid.py:209:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in good_override: bad return type [bad-return-type]'] +Line 168: Unexpected errors ['overloads_invalid.py:168:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in to_override: bad return type [bad-return-type]'] +Line 177: Unexpected errors ['overloads_invalid.py:177:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in final_method: bad return type [bad-return-type]'] +Line 193: Unexpected errors ['overloads_invalid.py:193:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in bad_override: bad return type [bad-return-type]', 'overloads_invalid.py:193:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in bad_override: bad return type [bad-return-type]'] +Line 212: Unexpected errors ['overloads_invalid.py:212:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in good_override: bad return type [bad-return-type]', 'overloads_invalid.py:212:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in good_override: bad return type [bad-return-type]'] +Line 229: Unexpected errors ['overloads_invalid.py:229:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in to_override: bad return type [bad-return-type]', 'overloads_invalid.py:229:9: \\x1b[1m\\x1b[31merror\\x1b[39m\\x1b[0m: in to_override: bad return type [bad-return-type]'] """ output = """ overloads_invalid.py:6:1: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in : typing.override not supported yet [not-supported-yet] @@ -105,7 +108,12 @@ overloads_invalid.py:165:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in good ... \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m -overloads_invalid.py:168:1: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in : Class Child overrides final method final_method, defined in base class Base [final-error] +overloads_invalid.py:168:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in to_override: bad return type [bad-return-type] + + ... + \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m + +overloads_invalid.py:171:1: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in : Class Child overrides final method final_method, defined in base class Base [final-error] class Child(Base): # E[override-final] \u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m @@ -192,34 +200,78 @@ class Child(Base): # E[override-final] ... \u001b[1m\u001b[31m~~~~~~~~~~~\u001b[39m\u001b[0m -overloads_invalid.py:174:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in final_method: bad return type [bad-return-type] +\u001b[1m\u001b[31m\u001b[39m\u001b[0m + # This is the wrong way to use @override with an overloaded method, and +\u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m + # should emit an error: +\u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m + +\u001b[1m\u001b[31m\u001b[39m\u001b[0m + @overload # E: @override should appear only on implementation +\u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m + @override +\u001b[1m\u001b[31m~~~~~~~~~~~~~\u001b[39m\u001b[0m + def to_override(self, x: int) -> int: +\u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m + ... +\u001b[1m\u001b[31m~~~~~~~~~~~\u001b[39m\u001b[0m + +\u001b[1m\u001b[31m\u001b[39m\u001b[0m + @overload +\u001b[1m\u001b[31m~~~~~~~~~~~~~\u001b[39m\u001b[0m + @override +\u001b[1m\u001b[31m~~~~~~~~~~~~~\u001b[39m\u001b[0m + def to_override(self, x: str) -> str: +\u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m + ... +\u001b[1m\u001b[31m~~~~~~~~~~~\u001b[39m\u001b[0m + +\u001b[1m\u001b[31m\u001b[39m\u001b[0m + @override +\u001b[1m\u001b[31m~~~~~~~~~~~~~\u001b[39m\u001b[0m + def to_override(self, x: int | str) -> int | str: +\u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m + ... +\u001b[1m\u001b[31m~~~~~~~~~~~\u001b[39m\u001b[0m + +overloads_invalid.py:177:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in final_method: bad return type [bad-return-type] ... \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m -overloads_invalid.py:181:5: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in Child: Attribute 'bad_override' not found on any parent class [override-error] +overloads_invalid.py:184:5: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in Child: Attribute 'bad_override' not found on any parent class [override-error] def bad_override(self, x: int) -> int: # E[bad_override] \u001b[1m\u001b[31m~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\u001b[39m\u001b[0m ... \u001b[1m\u001b[31m~~~~~~~~~~~\u001b[39m\u001b[0m -overloads_invalid.py:190:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in bad_override: bad return type [bad-return-type] +overloads_invalid.py:193:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in bad_override: bad return type [bad-return-type] + + ... + \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m + +overloads_invalid.py:193:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in bad_override: bad return type [bad-return-type] + + ... + \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m + +overloads_invalid.py:212:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in good_override: bad return type [bad-return-type] ... \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m -overloads_invalid.py:190:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in bad_override: bad return type [bad-return-type] +overloads_invalid.py:212:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in good_override: bad return type [bad-return-type] ... \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m -overloads_invalid.py:209:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in good_override: bad return type [bad-return-type] +overloads_invalid.py:229:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in to_override: bad return type [bad-return-type] ... \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m -overloads_invalid.py:209:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in good_override: bad return type [bad-return-type] +overloads_invalid.py:229:9: \u001b[1m\u001b[31merror\u001b[39m\u001b[0m: in to_override: bad return type [bad-return-type] ... \u001b[1m\u001b[31m~~~\u001b[39m\u001b[0m diff --git a/conformance/results/pytype/version.toml b/conformance/results/pytype/version.toml index a7bacc6c..add97f41 100644 --- a/conformance/results/pytype/version.toml +++ b/conformance/results/pytype/version.toml @@ -1,2 +1,2 @@ version = "pytype 2024.10.11" -test_duration = 30.6 +test_duration = 30.7 diff --git a/conformance/results/results.html b/conformance/results/results.html index 6ca870b4..2144c28b 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -162,13 +162,13 @@

Python Type System Conformance Test Results

1.7sec
pyright 1.1.391
-
1.5sec
+
1.6sec
pyre 0.9.23
5.9sec
pytype 2024.10.11
-
30.6sec
+
30.7sec
@@ -674,9 +674,9 @@

Python Type System Conformance Test Results

     overloads_invalid
Partial

Does not allow an overload with no implementation in an abstract base class.

-
Partial

Allows @final on all overloads and implementation; should be implementation-only.

+
Partial

Allows @final/@override on all overloads and implementation; should be implementation-only.

Partial

Does not allow an overload with no implementation in a Protocol or an abstract base class.

Expects @final/@override on all overloads and implementation, instead of implementation only.

-
Fail

Does not reject a function with a single @overload signature.

Does not reject a function with @overload signature but no implementation.

Does not allow an overload with no implementation in a Protocol or an abstract base class.

Does not error on overloads inconsistently decorated with `@staticmethod` or `@classmethod`.

Does not enforce any rules on location of @final decorator.

+
Fail

Does not reject a function with a single @overload signature.

Does not reject a function with @overload signature but no implementation.

Does not allow an overload with no implementation in a Protocol or an abstract base class.

Does not error on overloads inconsistently decorated with `@staticmethod` or `@classmethod`.

Does not enforce any rules on location of @final or @override decorators.

Exceptions diff --git a/conformance/tests/overloads_invalid.py b/conformance/tests/overloads_invalid.py index 1af39d13..83ce3873 100644 --- a/conformance/tests/overloads_invalid.py +++ b/conformance/tests/overloads_invalid.py @@ -159,11 +159,14 @@ def invalid_final_2(self, x: str) -> str: def invalid_final_2(self, x: int | str) -> int | str: ... - # This method is just here for the @override test below: + # These methods are just here for the @override test below: def good_override(self, x: int | str) -> int | str: ... + def to_override(self, x: int | str) -> int | str: + ... + class Child(Base): # E[override-final] @@ -207,3 +210,21 @@ def good_override(self, x: str) -> str: @override def good_override(self, x: int | str) -> int | str: ... + + # This is the wrong way to use @override with an overloaded method, and + # should emit an error: + + @overload # E: @override should appear only on implementation + @override + def to_override(self, x: int) -> int: + ... + + @overload + @override + def to_override(self, x: str) -> str: + ... + + @override + def to_override(self, x: int | str) -> int | str: + ... +