Skip to content

Commit 37ba7d4

Browse files
committed
Refactor so that overload decides on its impl at semanal time
1 parent 8decbe0 commit 37ba7d4

15 files changed

+219
-158
lines changed

mypy/checker.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -261,14 +261,18 @@ def accept_loop(self, body: Statement, else_body: Statement = None, *,
261261

262262
def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
263263
num_abstract = 0
264-
assert defn.items
264+
if not defn.items:
265+
# In this case we have already complained about none of these being
266+
# valid overloads.
267+
return None
265268
if len(defn.items) == 1:
266269
self.fail('Single overload definition, multiple required', defn)
267270

268271
if defn.is_property:
269272
# HACK: Infer the type of the property.
270-
self.visit_decorator(defn.items[0])
273+
self.visit_decorator(cast(Decorator, defn.items[0]))
271274
for fdef in defn.items:
275+
assert isinstance(fdef, Decorator)
272276
self.check_func_item(fdef.func, name=fdef.func.name())
273277
if fdef.func.is_abstract:
274278
num_abstract += 1
@@ -283,11 +287,14 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None:
283287
return None
284288

285289
def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
286-
print("Checking overlap", defn.items)
290+
# At this point we should have set the impl already, and all remaining
291+
# items are decorators
287292
for i, item in enumerate(defn.items):
293+
assert isinstance(item, Decorator)
288294
sig1 = self.function_type(item.func)
289295
for j, item2 in enumerate(defn.items[i + 1:]):
290296
# TODO overloads involving decorators
297+
assert isinstance(item2, Decorator)
291298
sig2 = self.function_type(item2.func)
292299
if is_unsafe_overlapping_signatures(sig1, sig2):
293300
self.msg.overloaded_signatures_overlap(i + 1, i + j + 2,
@@ -300,11 +307,12 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None:
300307
else:
301308
assert False, "Impl isn't the right type"
302309
# This can happen if we've got an overload with a different
303-
# decorator too. Just try not to crash.
310+
# decorator too -- we gave up on the types.
304311
if impl_type is None or sig1 is None:
305312
return
306-
assert isinstance(impl_type, CallableType) and isinstance(sig1, CallableType), "oops {}".format(impl_type)
307313

314+
assert isinstance(impl_type, CallableType)
315+
assert isinstance(sig1, CallableType)
308316
if not is_callable_subtype(impl_type, sig1, ignore_return=True):
309317
self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl)
310318
if not is_subtype(sig1.ret_type, impl_type.ret_type):
@@ -2093,7 +2101,8 @@ def check_incompatible_property_override(self, e: Decorator) -> None:
20932101
continue
20942102
if (isinstance(base_attr.node, OverloadedFuncDef) and
20952103
base_attr.node.is_property and
2096-
base_attr.node.items[0].var.is_settable_property):
2104+
cast(Decorator,
2105+
base_attr.node.items[0]).var.is_settable_property):
20972106
self.fail(messages.READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE, e)
20982107

20992108
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: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
special_function_elide_names, argument_elide_name, is_overload_part,
77
)
88
from mypy.nodes import (
9-
MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef,
9+
MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef,
10+
OverloadedFuncDef, OverloadPart,
1011
ClassDef, Decorator, Block, Var, OperatorAssignmentStmt,
1112
ExpressionStmt, AssignmentStmt, ReturnStmt, RaiseStmt, AssertStmt,
1213
DelStmt, BreakStmt, ContinueStmt, PassStmt, GlobalDecl,
@@ -213,32 +214,23 @@ def as_block(self, stmts: List[ast3.stmt], lineno: int) -> Block:
213214

214215
def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
215216
ret = [] # type: List[Statement]
216-
current_overload = [] # type: List[Decorator]
217+
current_overload = [] # type: List[OverloadPart]
217218
current_overload_name = None
218-
# mypy doesn't actually check that the decorator is literally @overload
219219
for stmt in stmts:
220220
if (isinstance(stmt, Decorator)
221221
and stmt.name() == current_overload_name):
222222
current_overload.append(stmt)
223223
elif (isinstance(stmt, FuncDef)
224-
and not self.is_stub
225224
and stmt.name() == current_overload_name
226225
and stmt.name() is not None):
227-
ret.append(OverloadedFuncDef(current_overload, stmt))
226+
ret.append(OverloadedFuncDef(current_overload + [stmt]))
228227
current_overload = []
229228
current_overload_name = None
230229
else:
231230
if len(current_overload) == 1:
232231
ret.append(current_overload[0])
233232
elif len(current_overload) > 1:
234-
if self.is_stub:
235-
ret.append(OverloadedFuncDef(current_overload, None))
236-
else:
237-
# Outside of a stub file, the last definition of the
238-
# overload is the implementation, even if it has a
239-
# decorator. We will check it later to make sure it
240-
# does *not* have the @overload decorator.
241-
ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1]))
233+
ret.append(OverloadedFuncDef(current_overload))
242234

243235
if isinstance(stmt, Decorator):
244236
current_overload = [stmt]
@@ -251,10 +243,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
251243
if len(current_overload) == 1:
252244
ret.append(current_overload[0])
253245
elif len(current_overload) > 1:
254-
if self.is_stub:
255-
ret.append(OverloadedFuncDef(current_overload, None))
256-
else:
257-
ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1]))
246+
ret.append(OverloadedFuncDef(current_overload))
258247
return ret
259248

260249
def in_class(self) -> bool:

mypy/fastparse2.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
UnaryExpr, FuncExpr, ComparisonExpr, DictionaryComprehension,
3434
SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, Argument,
3535
Expression, Statement, BackquoteExpr, PrintStmt, ExecStmt,
36-
ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2
36+
ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2, OverloadPart,
3737
)
3838
from mypy.types import (
3939
Type, CallableType, AnyType, UnboundType, EllipsisType
@@ -220,33 +220,23 @@ def as_block(self, stmts: List[ast27.stmt], lineno: int) -> Block:
220220

221221
def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
222222
ret = [] # type: List[Statement]
223-
current_overload = [] # type: List[Decorator]
223+
current_overload = [] # type: List[OverloadPart]
224224
current_overload_name = None
225-
# mypy doesn't actually check that the decorator is literally @overload
226225
for stmt in stmts:
227226
if (isinstance(stmt, Decorator)
228-
and is_overload_part(stmt)
229227
and stmt.name() == current_overload_name):
230228
current_overload.append(stmt)
231229
elif (isinstance(stmt, FuncDef)
232-
and not self.is_stub
233230
and stmt.name() == current_overload_name
234231
and stmt.name() is not None):
235-
ret.append(OverloadedFuncDef(current_overload, stmt))
232+
ret.append(OverloadedFuncDef(current_overload + [stmt]))
236233
current_overload = []
237234
current_overload_name = None
238235
else:
239236
if len(current_overload) == 1:
240237
ret.append(current_overload[0])
241238
elif len(current_overload) > 1:
242-
if self.is_stub:
243-
ret.append(OverloadedFuncDef(current_overload, None))
244-
else:
245-
# Outside of a stub file, the last definition of the
246-
# overload is the implementation, even if it has a
247-
# decorator. We will check it later to make sure it
248-
# does *not* have the @overload decorator.
249-
ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1]))
239+
ret.append(OverloadedFuncDef(current_overload))
250240

251241
if isinstance(stmt, Decorator):
252242
current_overload = [stmt]
@@ -259,11 +249,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]:
259249
if len(current_overload) == 1:
260250
ret.append(current_overload[0])
261251
elif len(current_overload) > 1:
262-
if self.is_stub:
263-
ret.append(OverloadedFuncDef(current_overload, None))
264-
else:
265-
ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1]))
266-
252+
ret.append(OverloadedFuncDef(current_overload))
267253
return ret
268254

269255
def in_class(self) -> bool:

mypy/nodes.py

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -375,16 +375,16 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement):
375375
Overloaded variants must be consecutive in the source file.
376376
"""
377377

378-
items = None # type: List[Decorator]
379-
impl = None # type: Optional[FuncDef]
378+
items = None # type: List[OverloadPart]
379+
impl = None # type: Optional[OverloadPart]
380380

381-
def __init__(self, items: List['Decorator'], impl: Optional[Union['Decorator', 'FuncDef']] = None) -> None:
381+
def __init__(self, items: List['OverloadPart']) -> None:
382382
self.items = items
383-
self.impl = impl
383+
self.impl = None
384384
self.set_line(items[0].line)
385385

386386
def name(self) -> str:
387-
return self.items[0].func.name()
387+
return self.items[0].name()
388388

389389
def accept(self, visitor: StatementVisitor[T]) -> T:
390390
return visitor.visit_overloaded_func_def(self)
@@ -401,10 +401,11 @@ def serialize(self) -> JsonDict:
401401
@classmethod
402402
def deserialize(cls, data: JsonDict) -> 'OverloadedFuncDef':
403403
assert data['.class'] == 'OverloadedFuncDef'
404-
impl = None # type: Optional[FuncDef]
404+
res = OverloadedFuncDef([
405+
cast(OverloadPart, OverloadPart.deserialize(d))
406+
for d in data['items']])
405407
if data.get('impl') is not None:
406-
impl = FuncDef.deserialize(data['impl'])
407-
res = OverloadedFuncDef([Decorator.deserialize(d) for d in data['items']], impl)
408+
res.impl = cast(OverloadPart, OverloadPart.deserialize(data['impl']))
408409
if data.get('type') is not None:
409410
res.type = mypy.types.Type.deserialize(data['type'])
410411
res._fullname = data['fullname']
@@ -511,7 +512,16 @@ def is_dynamic(self) -> bool:
511512
return self.type is None
512513

513514

514-
class FuncDef(FuncItem, SymbolNode, Statement):
515+
class OverloadPart(SymbolNode, Statement):
516+
"""Interface class for statments that can be part of an OverloadedFuncDef"""
517+
518+
is_overload = False
519+
520+
@abstractmethod
521+
def get_body(self) -> Optional['Block']: pass
522+
523+
524+
class FuncDef(FuncItem, OverloadPart, Statement):
515525
"""Function definition.
516526
517527
This is a non-lambda function defined using 'def'.
@@ -541,6 +551,9 @@ def name(self) -> str:
541551
def accept(self, visitor: StatementVisitor[T]) -> T:
542552
return visitor.visit_func_def(self)
543553

554+
def get_body(self) -> Optional['Block']:
555+
return self.body
556+
544557
def serialize(self) -> JsonDict:
545558
# We're deliberating omitting arguments and storing only arg_names and
546559
# arg_kinds for space-saving reasons (arguments is not used in later
@@ -579,7 +592,7 @@ def deserialize(cls, data: JsonDict) -> 'FuncDef':
579592
return ret
580593

581594

582-
class Decorator(SymbolNode, Statement):
595+
class Decorator(OverloadPart, Statement):
583596
"""A decorated function.
584597
585598
A single Decorator object can include any number of function decorators.
@@ -588,6 +601,7 @@ class Decorator(SymbolNode, Statement):
588601
func = None # type: FuncDef # Decorated function
589602
decorators = None # type: List[Expression] # Decorators, at least one # XXX Not true
590603
var = None # type: Var # Represents the decorated function obj
604+
type = None # type: mypy.types.Type
591605
is_overload = False
592606

593607
def __init__(self, func: FuncDef, decorators: List[Expression],
@@ -600,6 +614,9 @@ def __init__(self, func: FuncDef, decorators: List[Expression],
600614
def name(self) -> str:
601615
return self.func.name()
602616

617+
def get_body(self) -> Optional['Block']:
618+
return self.func.body
619+
603620
def fullname(self) -> str:
604621
return self.func.fullname()
605622

mypy/parse.py

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
UnaryExpr, FuncExpr, PrintStmt, ImportBase, ComparisonExpr,
3030
StarExpr, YieldFromExpr, NonlocalDecl, DictionaryComprehension,
3131
SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, ExecStmt, Argument,
32-
BackquoteExpr
32+
BackquoteExpr, OverloadPart
3333
)
3434
from mypy import defaults
3535
from mypy import nodes
@@ -902,28 +902,15 @@ def parse_block(self, allow_type: bool = False) -> Tuple[Block, Type]:
902902
return node, type
903903

904904
def try_combine_overloads(self, s: Statement, stmt: List[Statement]) -> bool:
905-
if isinstance(s, Decorator) and is_overload_part(s) and stmt:
906-
n = s.func.name()
907-
if (isinstance(stmt[-1], Decorator)
908-
and is_overload_part(stmt[-1])
909-
and stmt[-1].func.name() == n):
910-
stmt[-1] = OverloadedFuncDef([stmt[-1], s], None)
905+
if isinstance(s, OverloadPart) and stmt:
906+
n = s.name()
907+
if (isinstance(stmt[-1], OverloadPart)
908+
and stmt[-1].name() == n):
909+
stmt[-1] = OverloadedFuncDef([stmt[-1], s])
911910
return True
912911
elif isinstance(stmt[-1], OverloadedFuncDef) and stmt[-1].name() == n:
913912
stmt[-1].items.append(s)
914913
return True
915-
elif isinstance(s, FuncDef) and stmt:
916-
n = s.name()
917-
if (isinstance(stmt[-1], Decorator)
918-
and is_overload_part(stmt[-1])
919-
and stmt[-1].func.name() == n):
920-
stmt[-1] = OverloadedFuncDef([stmt[-1]], s)
921-
return True
922-
elif (isinstance(stmt[-1], OverloadedFuncDef)
923-
and stmt[-1].impl is None
924-
and stmt[-1].name() == n):
925-
stmt[-1].impl = s
926-
return True
927914
return False
928915

929916
def parse_statement(self) -> Tuple[Statement, bool]:

0 commit comments

Comments
 (0)