Skip to content

Commit e674e25

Browse files
sixoletgvanrossum
authored andcommitted
Allow overloads in source files, not just stubs (#2603)
Fixes #1136. - The implementation must directly follow all the overloads - The implementation is typechecked exactly according to its own declared types - Indicates an error if the implementation's argument list is not more general than every override variant - Also indicates an error if the implementation's return type is also not more general than the return type of every override variant It also limits overloads to the decorators that are specifically geared towards providing overload-type functionality -- @overload, but also @Property and its cousins @funcname.setter and @funcname.deleter. All other decorators are treated purely as decorators, and now provide redefinition errors if you repeat a function definition with them, instead of errors about an overload you probably did not mean.
1 parent eb1eb0d commit e674e25

32 files changed

+844
-149
lines changed

mypy/build.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1401,7 +1401,8 @@ def parse_file(self) -> None:
14011401
# this before processing imports, since this may mark some
14021402
# import statements as unreachable.
14031403
first = FirstPass(manager.semantic_analyzer)
1404-
first.visit_file(self.tree, self.xpath, self.id, self.options)
1404+
with self.wrap_context():
1405+
first.visit_file(self.tree, self.xpath, self.id, self.options)
14051406

14061407
# Initialize module symbol table, which was populated by the
14071408
# semantic analyzer.

mypy/checker.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
from mypy import messages
3838
from mypy.subtypes import (
3939
is_subtype, is_equivalent, is_proper_subtype, is_more_precise,
40-
restrict_subtype_away, is_subtype_ignoring_tvars
40+
restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_subtype,
41+
unify_generic_callable,
4142
)
4243
from mypy.maptype import map_instance_to_supertype
4344
from mypy.typevars import fill_typevars, has_no_typevars
@@ -262,29 +263,70 @@ def accept_loop(self, body: Statement, else_body: Statement = None, *,
262263

263264
def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
264265
num_abstract = 0
266+
if not defn.items:
267+
# In this case we have already complained about none of these being
268+
# valid overloads.
269+
return None
270+
if len(defn.items) == 1:
271+
self.fail('Single overload definition, multiple required', defn)
272+
265273
if defn.is_property:
266274
# HACK: Infer the type of the property.
267-
self.visit_decorator(defn.items[0])
275+
self.visit_decorator(cast(Decorator, defn.items[0]))
268276
for fdef in defn.items:
277+
assert isinstance(fdef, Decorator)
269278
self.check_func_item(fdef.func, name=fdef.func.name())
270279
if fdef.func.is_abstract:
271280
num_abstract += 1
272281
if num_abstract not in (0, len(defn.items)):
273282
self.fail(messages.INCONSISTENT_ABSTRACT_OVERLOAD, defn)
283+
if defn.impl:
284+
defn.impl.accept(self)
274285
if defn.info:
275286
self.check_method_override(defn)
276287
self.check_inplace_operator_method(defn)
277288
self.check_overlapping_overloads(defn)
289+
return None
278290

279291
def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
292+
# At this point we should have set the impl already, and all remaining
293+
# items are decorators
280294
for i, item in enumerate(defn.items):
295+
assert isinstance(item, Decorator)
296+
sig1 = self.function_type(item.func)
281297
for j, item2 in enumerate(defn.items[i + 1:]):
282298
# TODO overloads involving decorators
283-
sig1 = self.function_type(item.func)
299+
assert isinstance(item2, Decorator)
284300
sig2 = self.function_type(item2.func)
285301
if is_unsafe_overlapping_signatures(sig1, sig2):
286302
self.msg.overloaded_signatures_overlap(i + 1, i + j + 2,
287303
item.func)
304+
if defn.impl:
305+
if isinstance(defn.impl, FuncDef):
306+
impl_type = defn.impl.type
307+
elif isinstance(defn.impl, Decorator):
308+
impl_type = defn.impl.var.type
309+
else:
310+
assert False, "Impl isn't the right type"
311+
# This can happen if we've got an overload with a different
312+
# decorator too -- we gave up on the types.
313+
if impl_type is None or isinstance(impl_type, AnyType) or sig1 is None:
314+
return
315+
316+
assert isinstance(impl_type, CallableType)
317+
assert isinstance(sig1, CallableType)
318+
if not is_callable_subtype(impl_type, sig1, ignore_return=True):
319+
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
320+
impl_type_subst = impl_type
321+
if impl_type.variables:
322+
impl_type_subst = unify_generic_callable(impl_type, sig1, ignore_return=False)
323+
if impl_type_subst is None:
324+
self.fail("Type variable mismatch between " +
325+
"overload signature {} and implementation".format(i + 1),
326+
defn.impl)
327+
return
328+
if not is_subtype(sig1.ret_type, impl_type_subst.ret_type):
329+
self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl)
288330

289331
# Here's the scoop about generators and coroutines.
290332
#
@@ -2133,9 +2175,7 @@ def visit_decorator(self, e: Decorator) -> None:
21332175
e.func.accept(self)
21342176
sig = self.function_type(e.func) # type: Type
21352177
# Process decorators from the inside out.
2136-
for i in range(len(e.decorators)):
2137-
n = len(e.decorators) - 1 - i
2138-
d = e.decorators[n]
2178+
for d in reversed(e.decorators):
21392179
if isinstance(d, NameExpr) and d.fullname == 'typing.overload':
21402180
self.fail('Single overload definition, multiple required', e)
21412181
continue
@@ -2159,7 +2199,8 @@ def check_incompatible_property_override(self, e: Decorator) -> None:
21592199
continue
21602200
if (isinstance(base_attr.node, OverloadedFuncDef) and
21612201
base_attr.node.is_property and
2162-
base_attr.node.items[0].var.is_settable_property):
2202+
cast(Decorator,
2203+
base_attr.node.items[0]).var.is_settable_property):
21632204
self.fail(messages.READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE, e)
21642205

21652206
def visit_with_stmt(self, s: WithStmt) -> None:

mypy/checkmember.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ def analyze_member_access(name: str,
7575
if method:
7676
if method.is_property:
7777
assert isinstance(method, OverloadedFuncDef)
78-
return analyze_var(name, method.items[0].var, typ, info, node, is_lvalue, msg,
78+
first_item = cast(Decorator, method.items[0])
79+
return analyze_var(name, first_item.var, typ, info, node, is_lvalue, msg,
7980
original_type, not_ready_callback)
8081
if is_lvalue:
8182
msg.cant_assign_to_method(node)

mypy/fastparse.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
import sys
33

44
from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List, Set
5-
from mypy.sharedparse import special_function_elide_names, argument_elide_name
5+
from mypy.sharedparse import (
6+
special_function_elide_names, argument_elide_name,
7+
)
68
from mypy.nodes import (
7-
MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef,
9+
MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef,
10+
OverloadedFuncDef, OverloadPart,
811
ClassDef, Decorator, Block, Var, OperatorAssignmentStmt,
912
ExpressionStmt, AssignmentStmt, ReturnStmt, RaiseStmt, AssertStmt,
1013
DelStmt, BreakStmt, ContinueStmt, PassStmt, GlobalDecl,
@@ -222,11 +225,12 @@ def as_block(self, stmts: List[ast3.stmt], lineno: int) -> Block:
222225

223226
def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
224227
ret = [] # type: List[Statement]
225-
current_overload = []
228+
current_overload = [] # type: List[OverloadPart]
226229
current_overload_name = None
227-
# mypy doesn't actually check that the decorator is literally @overload
228230
for stmt in stmts:
229-
if isinstance(stmt, Decorator) and stmt.name() == current_overload_name:
231+
if (current_overload_name is not None
232+
and isinstance(stmt, (Decorator, FuncDef))
233+
and stmt.name() == current_overload_name):
230234
current_overload.append(stmt)
231235
else:
232236
if len(current_overload) == 1:

mypy/fastparse2.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import sys
1919

2020
from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List, Set
21-
from mypy.sharedparse import special_function_elide_names, argument_elide_name
21+
from mypy.sharedparse import (
22+
special_function_elide_names, argument_elide_name,
23+
)
2224
from mypy.nodes import (
2325
MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef,
2426
ClassDef, Decorator, Block, Var, OperatorAssignmentStmt,
@@ -31,7 +33,7 @@
3133
UnaryExpr, LambdaExpr, ComparisonExpr, DictionaryComprehension,
3234
SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, Argument,
3335
Expression, Statement, BackquoteExpr, PrintStmt, ExecStmt,
34-
ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2
36+
ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2, OverloadPart,
3537
)
3638
from mypy.types import (
3739
Type, CallableType, AnyType, UnboundType, EllipsisType
@@ -225,11 +227,12 @@ def as_block(self, stmts: List[ast27.stmt], lineno: int) -> Block:
225227

226228
def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
227229
ret = [] # type: List[Statement]
228-
current_overload = []
230+
current_overload = [] # type: List[OverloadPart]
229231
current_overload_name = None
230-
# mypy doesn't actually check that the decorator is literally @overload
231232
for stmt in stmts:
232-
if isinstance(stmt, Decorator) and stmt.name() == current_overload_name:
233+
if (current_overload_name is not None
234+
and isinstance(stmt, (Decorator, FuncDef))
235+
and stmt.name() == current_overload_name):
233236
current_overload.append(stmt)
234237
else:
235238
if len(current_overload) == 1:

mypy/fixup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ def visit_overloaded_func_def(self, o: OverloadedFuncDef) -> None:
106106
o.type.accept(self.type_fixer)
107107
for item in o.items:
108108
item.accept(self)
109+
if o.impl:
110+
o.impl.accept(self)
109111

110112
def visit_decorator(self, d: Decorator) -> None:
111113
if self.current_info is not None:

mypy/messages.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,14 @@ def overloaded_signatures_overlap(self, index1: int, index2: int,
806806
self.fail('Overloaded function signatures {} and {} overlap with '
807807
'incompatible return types'.format(index1, index2), context)
808808

809+
def overloaded_signatures_arg_specific(self, index1: int, context: Context) -> None:
810+
self.fail('Overloaded function implementation does not accept all possible arguments '
811+
'of signature {}'.format(index1), context)
812+
813+
def overloaded_signatures_ret_specific(self, index1: int, context: Context) -> None:
814+
self.fail('Overloaded function implementation cannot produce return type '
815+
'of signature {}'.format(index1), context)
816+
809817
def operator_method_signatures_overlap(
810818
self, reverse_class: str, reverse_method: str, forward_class: str,
811819
forward_method: str, context: Context) -> None:

mypy/nodes.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -382,21 +382,29 @@ def fullname(self) -> str:
382382
return self._fullname
383383

384384

385+
OverloadPart = Union['FuncDef', 'Decorator']
386+
387+
385388
class OverloadedFuncDef(FuncBase, SymbolNode, Statement):
386-
"""A logical node representing all the variants of an overloaded function.
389+
"""A logical node representing all the variants of a multi-declaration function.
390+
391+
A multi-declaration function is often an @overload, but can also be a
392+
@property with a setter and a/or a deleter.
387393
388394
This node has no explicit representation in the source program.
389395
Overloaded variants must be consecutive in the source file.
390396
"""
391397

392-
items = None # type: List[Decorator]
398+
items = None # type: List[OverloadPart]
399+
impl = None # type: Optional[OverloadPart]
393400

394-
def __init__(self, items: List['Decorator']) -> None:
401+
def __init__(self, items: List['OverloadPart']) -> None:
395402
self.items = items
403+
self.impl = None
396404
self.set_line(items[0].line)
397405

398406
def name(self) -> str:
399-
return self.items[0].func.name()
407+
return self.items[0].name()
400408

401409
def accept(self, visitor: StatementVisitor[T]) -> T:
402410
return visitor.visit_overloaded_func_def(self)
@@ -407,12 +415,17 @@ def serialize(self) -> JsonDict:
407415
'type': None if self.type is None else self.type.serialize(),
408416
'fullname': self._fullname,
409417
'is_property': self.is_property,
418+
'impl': None if self.impl is None else self.impl.serialize()
410419
}
411420

412421
@classmethod
413422
def deserialize(cls, data: JsonDict) -> 'OverloadedFuncDef':
414423
assert data['.class'] == 'OverloadedFuncDef'
415-
res = OverloadedFuncDef([Decorator.deserialize(d) for d in data['items']])
424+
res = OverloadedFuncDef([
425+
cast(OverloadPart, SymbolNode.deserialize(d))
426+
for d in data['items']])
427+
if data.get('impl') is not None:
428+
res.impl = cast(OverloadPart, SymbolNode.deserialize(data['impl']))
416429
if data.get('type') is not None:
417430
res.type = mypy.types.deserialize_type(data['type'])
418431
res._fullname = data['fullname']
@@ -598,6 +611,7 @@ class Decorator(SymbolNode, Statement):
598611
func = None # type: FuncDef # Decorated function
599612
decorators = None # type: List[Expression] # Decorators, at least one # XXX Not true
600613
var = None # type: Var # Represents the decorated function obj
614+
type = None # type: mypy.types.Type
601615
is_overload = False
602616

603617
def __init__(self, func: FuncDef, decorators: List[Expression],

0 commit comments

Comments
 (0)