← Back to index

overloads_definitions.py

True Positive
False Positive
False Negative
Optional (detected)
Warning or Info
TP: 12
FP: 0
FN: 0
Optional: 0 / 0
1"""
2Tests valid/invalid definition of overloaded functions.
3"""
4
5from abc import ABC, abstractmethod
6from typing import (
7 final,
8 Protocol,
9 overload,
10 override,
11)
14# > At least two @overload-decorated definitions must be present.
15@overload # E[func1]
16def func1() -> None: # E[func1]: At least two overloads must be present
Tag 'func1' [invalid-overload] Overloaded function `func1` requires at least two overloads: Only one overload defined here
17 ...
20def func1() -> None:
21 pass
24# > The ``@overload``-decorated definitions must be followed by an overload
25# > implementation, which does not include an ``@overload`` decorator. Type
26# > checkers should report an error or warning if an implementation is missing.
27@overload # E[func2]
28def func2(x: int) -> int: # E[func2]: no implementation
Tag 'func2' [invalid-overload] Overloads for function `func2` must be followed by a non-`@overload`-decorated implementation function
29 ...
32@overload
33def func2(x: str) -> str: ...
36# > Overload definitions within stub files, protocols, and on abstract methods
37# > within abstract base classes are exempt from this check.
38class MyProto(Protocol):
39 @overload
40 def func3(self, x: int) -> int: ...
42 @overload
43 def func3(self, x: str) -> str: ...
46class MyAbstractBase(ABC):
47 @overload
48 @abstractmethod
49 def func4(self, x: int) -> int: ...
51 @overload
52 @abstractmethod
53 def func4(self, x: str) -> str: ...
55 # A non-abstract method in an abstract base class still requires an
56 # implementation:
58 @overload # E[not_abstract]
59 def not_abstract(self, x: int) -> int: # E[not_abstract] no implementation
Tag 'not_abstract' [invalid-overload] Overloads for function `not_abstract` must be followed by a non-`@overload`-decorated implementation function
60 ...
62 @overload
63 def not_abstract(self, x: str) -> str: ...
66# > If one overload signature is decorated with ``@staticmethod`` or
67# > ``@classmethod``, all overload signatures must be similarly decorated. The
68# > implementation, if present, must also have a consistent decorator. Type
69# > checkers should report an error if these conditions are not met.
70class C:
71 @overload # E[func5]
72 @staticmethod
73 def func5(x: int, /) -> int: # E[func5]
74 ...
76 @overload
77 @staticmethod
78 def func5(x: str, /) -> str: # E[func5]
79 ...
81 def func5(*args: object) -> int | str: # E[func5]
Tag 'func5' [invalid-overload] Overloaded function `func5` does not use the `@staticmethod` decorator consistently
82 return 1
84 @overload # E[func6+]
85 @classmethod
86 def func6(cls, x: int, /) -> int: # E[func6+]
87 ...
89 @overload
90 def func6(self, x: str, /) -> str: # E[func6+]
91 ...
93 @classmethod # E[func6+]
94 def func6(cls, *args: int | str) -> int | str: # E[func6+]
Tag 'func6' [invalid-overload] Overloaded function `func6` does not use the `@classmethod` decorator consistently
95 return 1
98# > If a ``@final`` or ``@override`` decorator is supplied for a function with
99# > overloads, the decorator should be applied only to the overload
100# > implementation if it is present. If an overload implementation isn't present
101# > (for example, in a stub file), the ``@final`` or ``@override`` decorator
102# > should be applied only to the first overload. Type checkers should enforce
103# > these rules and generate an error when they are violated. If a ``@final`` or
104# > ``@override`` decorator follows these rules, a type checker should treat the
105# > decorator as if it is present on all overloads.
106class Base:
107 # This is a good definition of an overloaded final method (@final decorator
108 # on implementation only):
110 @overload
111 def final_method(self, x: int) -> int: ...
113 @overload
114 def final_method(self, x: str) -> str: ...
116 @final
117 def final_method(self, x: int | str) -> int | str:
118 raise NotImplementedError
120 # The @final decorator should not be on one of the overloads:
122 @overload # E[invalid_final] @final should be on implementation only
123 @final # E[invalid_final]
124 def invalid_final(self, x: int) -> int: # E[invalid_final]
Tag 'invalid_final' [invalid-overload] `@final` decorator should be applied only to the overload implementation
125 ...
127 @overload
128 def invalid_final(self, x: str) -> str: # E[invalid_final]
129 ...
131 def invalid_final(self, x: int | str) -> int | str:
132 raise NotImplementedError
134 # The @final decorator should not be on multiple overloads and
135 # implementation:
137 @overload # E[invalid_final_2+]: @final should be on implementation only
138 @final # E[invalid_final_2+]
139 def invalid_final_2(self, x: int) -> int: # E[invalid_final_2+]
Tag 'invalid_final_2' [invalid-overload] `@final` decorator should be applied only to the overload implementation
140 ...
142 @overload
143 @final # E[invalid_final_2+]
144 def invalid_final_2(self, x: str) -> str: # E[invalid_final_2+]
Tag 'invalid_final_2' [invalid-overload] `@final` decorator should be applied only to the overload implementation
145 ...
147 @final
148 def invalid_final_2(self, x: int | str) -> int | str:
149 raise NotImplementedError
151 # These methods are just here for the @override test below. We use an
152 # overload because mypy doesn't like overriding a non-overloaded method
153 # with an overloaded one, even if LSP isn't violated. That could be its own
154 # specification question, but it's not what we're trying to test here:
156 @overload
157 def good_override(self, x: int) -> int: ...
159 @overload
160 def good_override(self, x: str) -> str: ...
162 def good_override(self, x: int | str) -> int | str:
163 raise NotImplementedError
165 @overload
166 def to_override(self, x: int) -> int: ...
168 @overload
169 def to_override(self, x: str) -> str: ...
171 def to_override(self, x: int | str) -> int | str:
172 raise NotImplementedError
175class Child(Base): # E[override-final]
176 # The correctly-decorated @final method `Base.final_method` should cause an
177 # error if overridden in a child class (we use an overload here to avoid
178 # questions of override LSP compatibility and focus only on the override):
180 @overload # E[override-final]
181 def final_method(self, x: int) -> int: ... # E[override-final]
183 @overload
184 def final_method(self, x: str) -> str: ...
186 def final_method( # E[override-final] can't override final method
Tag 'override-final' [override-of-final-method] Cannot override final member `final_method` from superclass `Base`
187 self, x: int | str
188 ) -> int | str: # E[override-final] can't override final method
189 raise NotImplementedError
191 # This is the right way to mark an overload as @override (decorate
192 # implementation only), so the use of @override should cause an error
193 # (because there's no `Base.bad_override` method):
195 @overload # E[bad_override] marked as override but doesn't exist in base
196 def bad_override(self, x: int) -> int: # E[bad_override]
197 ...
199 @overload
200 def bad_override(self, x: str) -> str: ...
202 @override # E[bad_override]
203 def bad_override(self, x: int | str) -> int | str: # E[bad_override]
Tag 'bad_override' [invalid-explicit-override] Method `bad_override` is decorated with `@override` but does not override anything
204 raise NotImplementedError
206 # This is also a correctly-decorated overloaded @override, which is
207 # overriding a method that does exist in the base, so there should be no
208 # error. We need both this test and the previous one, because in the
209 # previous test, an incorrect error about the use of @override decorator
210 # could appear on the same line as the expected error about overriding a
211 # method that doesn't exist in base:
213 @overload
214 def good_override(self, x: int) -> int: ...
216 @overload
217 def good_override(self, x: str) -> str: ...
219 @override
220 def good_override(self, x: int | str) -> int | str:
221 raise NotImplementedError
223 # This is the wrong way to use @override with an overloaded method, and
224 # should emit an error:
226 @overload # E[override_impl+]: @override should appear only on implementation
227 @override # E[override_impl+]
228 def to_override(self, x: int) -> int: ... # E[override_impl+]
Tag 'override_impl' [invalid-overload] `@override` decorator should be applied only to the overload implementation
230 @overload
231 @override # E[override_impl+]
232 def to_override(self, x: str) -> str: ... # E[override_impl+]
Tag 'override_impl' [invalid-overload] `@override` decorator should be applied only to the overload implementation
234 @override
235 def to_override(self, x: int | str) -> int | str:
236 raise NotImplementedError