From e59f264b980637ff817d2f76c9c03160b5af3782 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 11:31:56 -0800 Subject: [PATCH 01/17] Overloads in source files, not just stub files --- mypy/checker.py | 6 ++++++ mypy/fastparse.py | 16 ++++++++++++---- mypy/fixup.py | 2 ++ mypy/nodes.py | 10 ++++++++-- mypy/parse.py | 24 +++++++++++++++++------- mypy/semanal.py | 13 +++++++++++++ mypy/sharedparse.py | 7 +++++++ mypy/strconv.py | 2 ++ mypy/traverser.py | 2 ++ mypy/treetransform.py | 2 ++ test-data/unit/check-overloading.test | 20 +++++++++++++++++++- test-data/unit/fixtures/isinstance.pyi | 1 + 12 files changed, 91 insertions(+), 14 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5beb52a39e56..e8f4faa823b2 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -261,6 +261,9 @@ def accept_loop(self, body: Statement, else_body: Statement = None, *, def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: num_abstract = 0 + assert defn.items + if len(defn.items) == 1: + self.fail('Single overload definition, multiple required', defn) if defn.is_property: # HACK: Infer the type of the property. self.visit_decorator(defn.items[0]) @@ -274,6 +277,9 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: self.check_method_override(defn) self.check_inplace_operator_method(defn) self.check_overlapping_overloads(defn) + if defn.impl: + defn.impl.accept(self) + return None def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: for i, item in enumerate(defn.items): diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 26908b9ac399..206b5b689572 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -2,7 +2,9 @@ import sys from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List, Set -from mypy.sharedparse import special_function_elide_names, argument_elide_name +from mypy.sharedparse import ( + special_function_elide_names, argument_elide_name, is_overload_part, +) from mypy.nodes import ( MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef, ClassDef, Decorator, Block, Var, OperatorAssignmentStmt, @@ -215,15 +217,21 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: current_overload_name = None # mypy doesn't actually check that the decorator is literally @overload for stmt in stmts: - if isinstance(stmt, Decorator) and stmt.name() == current_overload_name: + if is_overload_part(stmt) and stmt.name() == current_overload_name: current_overload.append(stmt) + elif (isinstance(stmt, FuncDef) + and stmt.name() == current_overload_name + and stmt.name() is not None): + ret.append(OverloadedFuncDef(current_overload, stmt)) + current_overload = [] + current_overload_name = None else: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - ret.append(OverloadedFuncDef(current_overload)) + ret.append(OverloadedFuncDef(current_overload, None)) - if isinstance(stmt, Decorator): + if is_overload_part(stmt): current_overload = [stmt] current_overload_name = stmt.name() else: diff --git a/mypy/fixup.py b/mypy/fixup.py index 8375b9fa58ae..ea7cb4fdf43d 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -102,6 +102,8 @@ def visit_overloaded_func_def(self, o: OverloadedFuncDef) -> None: o.type.accept(self.type_fixer) for item in o.items: item.accept(self) + if o.impl: + o.impl.accept(self) def visit_decorator(self, d: Decorator) -> None: if self.current_info is not None: diff --git a/mypy/nodes.py b/mypy/nodes.py index 5d39921e315d..c2fd57a1eaf4 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -376,9 +376,11 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement): """ items = None # type: List[Decorator] + impl = None # type: Optional[FuncDef] - def __init__(self, items: List['Decorator']) -> None: + def __init__(self, items: List['Decorator'], impl: Optional['FuncDef'] = None) -> None: self.items = items + self.impl = impl self.set_line(items[0].line) def name(self) -> str: @@ -393,12 +395,16 @@ def serialize(self) -> JsonDict: 'type': None if self.type is None else self.type.serialize(), 'fullname': self._fullname, 'is_property': self.is_property, + 'impl': None if self.impl is None else self.impl.serialize() } @classmethod def deserialize(cls, data: JsonDict) -> 'OverloadedFuncDef': assert data['.class'] == 'OverloadedFuncDef' - res = OverloadedFuncDef([Decorator.deserialize(d) for d in data['items']]) + impl = None # type: Optional[FuncDef] + if data.get('impl') is not None: + impl = FuncDef.deserialize(data['impl']) + res = OverloadedFuncDef([Decorator.deserialize(d) for d in data['items']], impl) if data.get('type') is not None: res.type = mypy.types.Type.deserialize(data['type']) res._fullname = data['fullname'] diff --git a/mypy/parse.py b/mypy/parse.py index cbfec393ac6c..f06b6eeef00f 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -14,7 +14,9 @@ UnicodeLit, FloatLit, Op, Indent, Keyword, Punct, LexError, ComplexLit, EllipsisToken ) -from mypy.sharedparse import special_function_elide_names, argument_elide_name +from mypy.sharedparse import ( + special_function_elide_names, argument_elide_name, is_overload_part, +) from mypy.nodes import ( MypyFile, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef, ClassDef, Decorator, Block, Var, OperatorAssignmentStmt, Statement, @@ -900,14 +902,22 @@ def parse_block(self, allow_type: bool = False) -> Tuple[Block, Type]: return node, type def try_combine_overloads(self, s: Statement, stmt: List[Statement]) -> bool: - if isinstance(s, Decorator) and stmt: - fdef = s - n = fdef.func.name() - if isinstance(stmt[-1], Decorator) and stmt[-1].func.name() == n: - stmt[-1] = OverloadedFuncDef([stmt[-1], fdef]) + if is_overload_part(s) and stmt: + assert isinstance(s, Decorator) + n = s.func.name() + if is_overload_part(stmt[-1]) and stmt[-1].func.name() == n: + stmt[-1] = OverloadedFuncDef([stmt[-1], s], None) return True elif isinstance(stmt[-1], OverloadedFuncDef) and stmt[-1].name() == n: - stmt[-1].items.append(fdef) + stmt[-1].items.append(s) + return True + elif isinstance(s, FuncDef) and stmt: + n = s.name() + if is_overload_part(stmt[-1]) and stmt[-1].func.name() == n: + stmt[-1] = OverloadedFuncDef([stmt[-1]], s) + return True + elif isinstance(stmt[-1], OverloadedFuncDef) and stmt[-1].impl is None and stmt[-1].name() == n: + stmt[-1].impl = s return True return False diff --git a/mypy/semanal.py b/mypy/semanal.py index fe51d2e91476..977d171b72b6 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -449,6 +449,9 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: elif self.is_func_scope(): self.add_local(defn, defn) + if defn.impl: + defn.impl.accept(self) + def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -> None: """Analyze a property defined using multiple methods (e.g., using @x.setter). @@ -3133,6 +3136,16 @@ def visit_overloaded_func_def(self, func: OverloadedFuncDef) -> None: func._fullname = self.sem.qualified_name(func.name()) if kind == GDEF: self.sem.globals[func.name()] = SymbolTableNode(kind, func, self.sem.cur_mod_id) + if func.impl: + # Also analyze the function body (in case there are conditional imports). + sem = self.sem + sem.function_stack.append(func.impl) + sem.errors.push_function(func.name()) + sem.enter() + func.impl.body.accept(self) + sem.leave() + sem.errors.pop_function() + sem.function_stack.pop() def visit_class_def(self, cdef: ClassDef) -> None: kind = self.kind_by_scope() diff --git a/mypy/sharedparse.py b/mypy/sharedparse.py index 1643aaccf321..9ff6697c219c 100644 --- a/mypy/sharedparse.py +++ b/mypy/sharedparse.py @@ -1,5 +1,7 @@ from typing import Union, Tuple +from mypy.nodes import Decorator, NameExpr, Statement + """Shared logic between our three mypy parser files.""" @@ -98,3 +100,8 @@ def special_function_elide_names(name: str) -> bool: def argument_elide_name(name: Union[str, Tuple, None]) -> bool: return isinstance(name, str) and name.startswith("__") + +def is_overload_part(stmt: Statement) -> bool: + return isinstance(stmt, Decorator) and any( + isinstance(dec, NameExpr) and dec.name == 'overload' + for dec in stmt.decorators) diff --git a/mypy/strconv.py b/mypy/strconv.py index d7c1e48a813c..67cef6aadbf1 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -125,6 +125,8 @@ def visit_overloaded_func_def(self, o: 'mypy.nodes.OverloadedFuncDef') -> str: a = o.items[:] # type: Any if o.type: a.insert(0, o.type) + if o.impl: + a.append(o.impl) return self.dump(a, o) def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> str: diff --git a/mypy/traverser.py b/mypy/traverser.py index 35da22a27f2b..abc411835756 100644 --- a/mypy/traverser.py +++ b/mypy/traverser.py @@ -49,6 +49,8 @@ def visit_func_def(self, o: FuncDef) -> None: def visit_overloaded_func_def(self, o: OverloadedFuncDef) -> None: for item in o.items: item.accept(self) + if o.impl: + o.impl.accept(self) def visit_class_def(self, o: ClassDef) -> None: for d in o.decorators: diff --git a/mypy/treetransform.py b/mypy/treetransform.py index e6e4678097ca..ec1b25552d20 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -168,6 +168,8 @@ def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> OverloadedFuncDe new._fullname = node._fullname new.type = self.type(node.type) new.info = node.info + if node.impl: + node.impl = node.impl.accept(self) return new def visit_class_def(self, node: ClassDef) -> ClassDef: diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index e173fb27e49e..998db52058c5 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1,5 +1,24 @@ -- Test cases for function overloading +[case testTypeCheckOverloadWithImplementation] +from typing import overload, Any +@overload +def f(x: 'A') -> 'B': ... +@overload +def f(x: 'B') -> 'A': ... + +def f(x: Any) -> Any: + if isinstance(x, A): + return B() + else: + return A() + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadedFunctionBody] from typing import overload @@ -690,7 +709,6 @@ def f(a: int) -> None: pass def f(a: str) -> None: pass [out] main:2: error: Single overload definition, multiple required -main:4: error: Name 'f' already defined [case testSingleOverload2] from typing import overload diff --git a/test-data/unit/fixtures/isinstance.pyi b/test-data/unit/fixtures/isinstance.pyi index c155a9724ad4..9d87a161395f 100644 --- a/test-data/unit/fixtures/isinstance.pyi +++ b/test-data/unit/fixtures/isinstance.pyi @@ -20,3 +20,4 @@ class float: pass class bool(int): pass class str: def __add__(self, other: 'str') -> 'str': pass +class ellipsis: pass From bb5fdae5d7b374cc02a8d0429a37db5eecfd8fb2 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 16:37:58 -0800 Subject: [PATCH 02/17] Properties count as overloads too --- mypy/sharedparse.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mypy/sharedparse.py b/mypy/sharedparse.py index 9ff6697c219c..b1dc983939ff 100644 --- a/mypy/sharedparse.py +++ b/mypy/sharedparse.py @@ -1,6 +1,6 @@ from typing import Union, Tuple -from mypy.nodes import Decorator, NameExpr, Statement +from mypy.nodes import Decorator, NameExpr, Statement, MemberExpr """Shared logic between our three mypy parser files.""" @@ -101,7 +101,13 @@ def special_function_elide_names(name: str) -> bool: def argument_elide_name(name: Union[str, Tuple, None]) -> bool: return isinstance(name, str) and name.startswith("__") +def _is_overload_decorator(dec): + if isinstance(dec, NameExpr) and dec.name in {'overload', 'property', 'abstractproperty'}: + return True + elif isinstance(dec, MemberExpr) and dec.name in {'setter', 'deleter'}: + return True + return False + def is_overload_part(stmt: Statement) -> bool: - return isinstance(stmt, Decorator) and any( - isinstance(dec, NameExpr) and dec.name == 'overload' - for dec in stmt.decorators) + return isinstance(stmt, Decorator) and any(_is_overload_decorator(dec) + for dec in stmt.decorators) From ecb5f731e62b3d9f905b522452fe8a15ff334441 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 17:29:20 -0800 Subject: [PATCH 03/17] tests and fixes and types and such --- mypy/checker.py | 11 ++++- mypy/fastparse.py | 8 +-- mypy/messages.py | 8 +++ mypy/nodes.py | 4 +- mypy/parse.py | 15 ++++-- mypy/sharedparse.py | 7 +-- mypy/treetransform.py | 2 +- test-data/unit/check-overloading.test | 70 ++++++++++++++++++++++++++- test-data/unit/parse.test | 36 +++++++------- test-data/unit/semanal-errors.test | 10 ++-- 10 files changed, 131 insertions(+), 40 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e8f4faa823b2..f235f1d1b9af 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -36,7 +36,7 @@ from mypy import messages from mypy.subtypes import ( is_subtype, is_equivalent, is_proper_subtype, is_more_precise, - restrict_subtype_away, is_subtype_ignoring_tvars + restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_subtype ) from mypy.maptype import map_instance_to_supertype from mypy.typevars import fill_typevars, has_no_typevars @@ -283,13 +283,20 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: for i, item in enumerate(defn.items): + sig1 = self.function_type(item.func) for j, item2 in enumerate(defn.items[i + 1:]): # TODO overloads involving decorators - sig1 = self.function_type(item.func) sig2 = self.function_type(item2.func) if is_unsafe_overlapping_signatures(sig1, sig2): self.msg.overloaded_signatures_overlap(i + 1, i + j + 2, item.func) + if defn.impl: + assert isinstance(defn.impl.type, CallableType) and isinstance(sig1, CallableType) + + if not is_callable_subtype(defn.impl.type, sig1, ignore_return=True): + self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl) + if not is_subtype(sig1.ret_type, defn.impl.type.ret_type): + self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl) # Here's the scoop about generators and coroutines. # diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 206b5b689572..6e7145e8fced 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -213,11 +213,13 @@ def as_block(self, stmts: List[ast3.stmt], lineno: int) -> Block: def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: ret = [] # type: List[Statement] - current_overload = [] + current_overload = [] # type: List[Decorator] current_overload_name = None # mypy doesn't actually check that the decorator is literally @overload for stmt in stmts: - if is_overload_part(stmt) and stmt.name() == current_overload_name: + if (isinstance(stmt, Decorator) + and is_overload_part(stmt) + and stmt.name() == current_overload_name): current_overload.append(stmt) elif (isinstance(stmt, FuncDef) and stmt.name() == current_overload_name @@ -231,7 +233,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: elif len(current_overload) > 1: ret.append(OverloadedFuncDef(current_overload, None)) - if is_overload_part(stmt): + if isinstance(stmt, Decorator) and is_overload_part(stmt): current_overload = [stmt] current_overload_name = stmt.name() else: diff --git a/mypy/messages.py b/mypy/messages.py index c8b65047d3af..636756675cbc 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -829,6 +829,14 @@ def overloaded_signatures_overlap(self, index1: int, index2: int, self.fail('Overloaded function signatures {} and {} overlap with ' 'incompatible return types'.format(index1, index2), context) + def overloaded_signatures_arg_specific(self, index1: int, context: Context) -> None: + self.fail('Overloaded function implementation cannot accept all possible arguments ' + 'of signature {}'.format(index1), context) + + def overloaded_signatures_ret_specific(self, index1: int, context: Context) -> None: + self.fail('Overloaded function implementation cannot produce return type ' + 'of signature {}'.format(index1), context) + def operator_method_signatures_overlap( self, reverse_class: str, reverse_method: str, forward_class: str, forward_method: str, context: Context) -> None: diff --git a/mypy/nodes.py b/mypy/nodes.py index c2fd57a1eaf4..00a41d5bf2a7 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -376,7 +376,7 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement): """ items = None # type: List[Decorator] - impl = None # type: Optional[FuncDef] + impl = None # type: Optional[FuncDef] def __init__(self, items: List['Decorator'], impl: Optional['FuncDef'] = None) -> None: self.items = items @@ -401,7 +401,7 @@ def serialize(self) -> JsonDict: @classmethod def deserialize(cls, data: JsonDict) -> 'OverloadedFuncDef': assert data['.class'] == 'OverloadedFuncDef' - impl = None # type: Optional[FuncDef] + impl = None # type: Optional[FuncDef] if data.get('impl') is not None: impl = FuncDef.deserialize(data['impl']) res = OverloadedFuncDef([Decorator.deserialize(d) for d in data['items']], impl) diff --git a/mypy/parse.py b/mypy/parse.py index f06b6eeef00f..ed3a37233471 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -902,10 +902,11 @@ def parse_block(self, allow_type: bool = False) -> Tuple[Block, Type]: return node, type def try_combine_overloads(self, s: Statement, stmt: List[Statement]) -> bool: - if is_overload_part(s) and stmt: - assert isinstance(s, Decorator) + if isinstance(s, Decorator) and is_overload_part(s) and stmt: n = s.func.name() - if is_overload_part(stmt[-1]) and stmt[-1].func.name() == n: + if (isinstance(stmt[-1], Decorator) + and is_overload_part(stmt[-1]) + and stmt[-1].func.name() == n): stmt[-1] = OverloadedFuncDef([stmt[-1], s], None) return True elif isinstance(stmt[-1], OverloadedFuncDef) and stmt[-1].name() == n: @@ -913,10 +914,14 @@ def try_combine_overloads(self, s: Statement, stmt: List[Statement]) -> bool: return True elif isinstance(s, FuncDef) and stmt: n = s.name() - if is_overload_part(stmt[-1]) and stmt[-1].func.name() == n: + if (isinstance(stmt[-1], Decorator) + and is_overload_part(stmt[-1]) + and stmt[-1].func.name() == n): stmt[-1] = OverloadedFuncDef([stmt[-1]], s) return True - elif isinstance(stmt[-1], OverloadedFuncDef) and stmt[-1].impl is None and stmt[-1].name() == n: + elif (isinstance(stmt[-1], OverloadedFuncDef) + and stmt[-1].impl is None + and stmt[-1].name() == n): stmt[-1].impl = s return True return False diff --git a/mypy/sharedparse.py b/mypy/sharedparse.py index b1dc983939ff..8a975da51b56 100644 --- a/mypy/sharedparse.py +++ b/mypy/sharedparse.py @@ -101,6 +101,7 @@ def special_function_elide_names(name: str) -> bool: def argument_elide_name(name: Union[str, Tuple, None]) -> bool: return isinstance(name, str) and name.startswith("__") + def _is_overload_decorator(dec): if isinstance(dec, NameExpr) and dec.name in {'overload', 'property', 'abstractproperty'}: return True @@ -108,6 +109,6 @@ def _is_overload_decorator(dec): return True return False -def is_overload_part(stmt: Statement) -> bool: - return isinstance(stmt, Decorator) and any(_is_overload_decorator(dec) - for dec in stmt.decorators) + +def is_overload_part(stmt: Decorator) -> bool: + return any(_is_overload_decorator(dec) for dec in stmt.decorators) diff --git a/mypy/treetransform.py b/mypy/treetransform.py index ec1b25552d20..6c1d97e550c7 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -169,7 +169,7 @@ def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> OverloadedFuncDe new.type = self.type(node.type) new.info = node.info if node.impl: - node.impl = node.impl.accept(self) + node.impl = self.visit_func_def(node.impl) return new def visit_class_def(self, node: ClassDef) -> ClassDef: diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 998db52058c5..ca5bf3591828 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -20,6 +20,74 @@ class A: pass class B: pass [builtins fixtures/isinstance.pyi] +[case testTypeCheckOverloadWithImplementationError] +from typing import overload, Any + +@overload +def f(x: 'A') -> 'B': ... +@overload +def f(x: 'B') -> 'A': ... + +def f(x: Any) -> Any: + if isinstance(x, A): + y = x + y = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") + return y + else: + return A() + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + +[case testTypeCheckOverloadWithImplTooSpecificArg] +from typing import overload, Any + +class A: pass +class B: pass + +a = A() + +@overload +def f(x: 'A') -> 'B': ... +@overload +def f(x: 'B') -> 'A': ... + +def f(x: 'A') -> Any: # E: Overloaded function implementation cannot accept all possible arguments of signature 2 + if x is a: + return B() + else: + return A() + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +[builtins fixtures/isinstance.pyi] + +[case testTypeCheckOverloadWithImplTooSpecificRetType] +from typing import overload, Any + +class A: pass +class B: pass + +a = A() + +@overload +def f(x: 'A') -> 'B': ... +@overload +def f(x: 'B') -> 'A': ... + +def f(x: Any) -> 'B': # E: Overloaded function implementation cannot produce return type of signature 2 + return B() + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +[builtins fixtures/isinstance.pyi] + [case testTypeCheckOverloadedFunctionBody] from typing import overload @overload @@ -706,7 +774,7 @@ b.f((0, '')) # E: Argument 1 to "f" of "A" has incompatible type "Tuple[int, str from typing import overload @overload def f(a: int) -> None: pass -def f(a: str) -> None: pass +def f(a: int) -> None: pass [out] main:2: error: Single overload definition, multiple required diff --git a/test-data/unit/parse.test b/test-data/unit/parse.test index 4335fffaa938..14c1944a1563 100644 --- a/test-data/unit/parse.test +++ b/test-data/unit/parse.test @@ -2542,16 +2542,16 @@ MypyFile:1( NameExpr(interface))) [case testFunctionOverload] -@foo +@overload def f() -> x: pass -@foo +@overload def f() -> y: pass [out] MypyFile:1( OverloadedFuncDef:1( Decorator:1( Var(f) - NameExpr(foo) + NameExpr(overload) FuncDef:2( f def () -> x? @@ -2559,7 +2559,7 @@ MypyFile:1( PassStmt:2()))) Decorator:3( Var(f) - NameExpr(foo) + NameExpr(overload) FuncDef:4( f def () -> y? @@ -2568,9 +2568,9 @@ MypyFile:1( [case testFunctionOverloadAndOtherStatements] x -@foo +@overload def f() -> x: pass -@foo +@overload def f() -> y: pass x [out] @@ -2580,7 +2580,7 @@ MypyFile:1( OverloadedFuncDef:2( Decorator:2( Var(f) - NameExpr(foo) + NameExpr(overload) FuncDef:3( f def () -> x? @@ -2588,7 +2588,7 @@ MypyFile:1( PassStmt:3()))) Decorator:4( Var(f) - NameExpr(foo) + NameExpr(overload) FuncDef:5( f def () -> y? @@ -2598,18 +2598,18 @@ MypyFile:1( NameExpr(x))) [case testFunctionOverloadWithThreeVariants] -@foo +@overload def f() -> x: pass -@foo +@overload def f() -> y: pass -@foo +@overload def f(y): pass [out] MypyFile:1( OverloadedFuncDef:1( Decorator:1( Var(f) - NameExpr(foo) + NameExpr(overload) FuncDef:2( f def () -> x? @@ -2617,7 +2617,7 @@ MypyFile:1( PassStmt:2()))) Decorator:3( Var(f) - NameExpr(foo) + NameExpr(overload) FuncDef:4( f def () -> y? @@ -2625,7 +2625,7 @@ MypyFile:1( PassStmt:4()))) Decorator:5( Var(f) - NameExpr(foo) + NameExpr(overload) FuncDef:6( f Args( @@ -2659,9 +2659,9 @@ MypyFile:1( [case testFunctionOverloadWithinFunction] def f(): - @foo + @overload def g(): pass - @foo + @overload def g() -> x: pass [out] MypyFile:1( @@ -2671,14 +2671,14 @@ MypyFile:1( OverloadedFuncDef:2( Decorator:2( Var(g) - NameExpr(foo) + NameExpr(overload) FuncDef:3( g Block:3( PassStmt:3()))) Decorator:4( Var(g) - NameExpr(foo) + NameExpr(overload) FuncDef:5( g def () -> x? diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 8b5eef03d917..5f4d32659857 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -1062,25 +1062,25 @@ from typing import overload def dec(x): pass @overload def f(): pass -@dec # E: 'overload' decorator expected +@dec # E: Name 'f' already defined def f(): pass [out] [case testInconsistentOverload2] from typing import overload def dec(x): pass -@dec # E: 'overload' decorator expected +@dec def f(): pass -@overload +@overload # E: Name 'f' already defined def f(): pass [out] [case testMissingOverloadDecorator] from typing import overload def dec(x): pass -@dec # E: 'overload' decorator expected +@dec def f(): pass -@dec # E: 'overload' decorator expected +@dec # E: Name 'f' already defined def f(): pass [out] From 435751d68135685a1d302ba6921002e7242329b8 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 22 Dec 2016 21:30:38 -0800 Subject: [PATCH 04/17] Add impl & test for fastparse2 --- mypy/fastparse.py | 2 +- mypy/fastparse2.py | 22 ++++++++++++++++------ test-data/unit/check-overloading.test | 22 ++++++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 6e7145e8fced..a030d70e6bc2 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -244,7 +244,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - ret.append(OverloadedFuncDef(current_overload)) + ret.append(OverloadedFuncDef(current_overload, None)) return ret def in_class(self) -> bool: diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index a593fd404630..d4500d7fd339 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -18,7 +18,9 @@ import sys from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List, Set -from mypy.sharedparse import special_function_elide_names, argument_elide_name +from mypy.sharedparse import ( + special_function_elide_names, argument_elide_name, is_overload_part, +) from mypy.nodes import ( MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef, ClassDef, Decorator, Block, Var, OperatorAssignmentStmt, @@ -218,19 +220,27 @@ def as_block(self, stmts: List[ast27.stmt], lineno: int) -> Block: def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: ret = [] # type: List[Statement] - current_overload = [] + current_overload = [] # type: List[Decorator] current_overload_name = None # mypy doesn't actually check that the decorator is literally @overload for stmt in stmts: - if isinstance(stmt, Decorator) and stmt.name() == current_overload_name: + if (isinstance(stmt, Decorator) + and is_overload_part(stmt) + and stmt.name() == current_overload_name): current_overload.append(stmt) + elif (isinstance(stmt, FuncDef) + and stmt.name() == current_overload_name + and stmt.name() is not None): + ret.append(OverloadedFuncDef(current_overload, stmt)) + current_overload = [] + current_overload_name = None else: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - ret.append(OverloadedFuncDef(current_overload)) + ret.append(OverloadedFuncDef(current_overload, None)) - if isinstance(stmt, Decorator): + if isinstance(stmt, Decorator) and is_overload_part(stmt): current_overload = [stmt] current_overload_name = stmt.name() else: @@ -241,7 +251,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - ret.append(OverloadedFuncDef(current_overload)) + ret.append(OverloadedFuncDef(current_overload, None)) return ret def in_class(self) -> bool: diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index ca5bf3591828..266bd400c577 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -20,6 +20,28 @@ class A: pass class B: pass [builtins fixtures/isinstance.pyi] +[case testTypeCheckOverloadWithImplementationFastparse] +# flags: --fast-parser + +from typing import overload, Any +@overload +def f(x: 'A') -> 'B': ... +@overload +def f(x: 'B') -> 'A': ... + +def f(x: Any) -> Any: + if isinstance(x, A): + return B() + else: + return A() + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + [case testTypeCheckOverloadWithImplementationError] from typing import overload, Any From 7b3c08ddbf90b8834dd552f7bbb34fd1a8ee5d78 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 15 Feb 2017 16:58:42 -0800 Subject: [PATCH 05/17] Temporary checkpoint -- most tests pass but not overloads in classes --- mypy/checker.py | 22 ++++++-- mypy/fastparse.py | 22 ++++++-- mypy/fastparse2.py | 20 +++++-- mypy/nodes.py | 2 +- mypy/semanal.py | 52 ++++++++++++++++-- mypy/sharedparse.py | 4 +- test-data/unit/check-abstract.test | 4 ++ test-data/unit/check-classes.test | 27 ++++++++++ test-data/unit/check-expressions.test | 2 +- test-data/unit/check-functions.test | 3 ++ test-data/unit/check-generics.test | 2 + test-data/unit/check-inference-context.test | 2 + test-data/unit/check-optional.test | 1 + test-data/unit/check-overloading.test | 60 +++++++++++++++++++++ test-data/unit/check-statements.test | 1 + test-data/unit/check-typevar-values.test | 1 + test-data/unit/check-unions.test | 4 ++ test-data/unit/check-unsupported.test | 1 + test-data/unit/check-varargs.test | 1 + 19 files changed, 208 insertions(+), 23 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index f235f1d1b9af..e3fbd7c17286 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -264,6 +264,7 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: assert defn.items if len(defn.items) == 1: self.fail('Single overload definition, multiple required', defn) + if defn.is_property: # HACK: Infer the type of the property. self.visit_decorator(defn.items[0]) @@ -273,15 +274,16 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: num_abstract += 1 if num_abstract not in (0, len(defn.items)): self.fail(messages.INCONSISTENT_ABSTRACT_OVERLOAD, defn) + if defn.impl: + defn.impl.accept(self) if defn.info: self.check_method_override(defn) self.check_inplace_operator_method(defn) self.check_overlapping_overloads(defn) - if defn.impl: - defn.impl.accept(self) return None def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: + print("Checking overlap", defn.items) for i, item in enumerate(defn.items): sig1 = self.function_type(item.func) for j, item2 in enumerate(defn.items[i + 1:]): @@ -291,11 +293,21 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: self.msg.overloaded_signatures_overlap(i + 1, i + j + 2, item.func) if defn.impl: - assert isinstance(defn.impl.type, CallableType) and isinstance(sig1, CallableType) + if isinstance(defn.impl, FuncDef): + impl_type = defn.impl.type + elif isinstance(defn.impl, Decorator): + impl_type = defn.impl.var.type + else: + assert False, "Impl isn't the right type" + # This can happen if we've got an overload with a different + # decorator too. Just try not to crash. + if impl_type is None or sig1 is None: + return + assert isinstance(impl_type, CallableType) and isinstance(sig1, CallableType), "oops {}".format(impl_type) - if not is_callable_subtype(defn.impl.type, sig1, ignore_return=True): + if not is_callable_subtype(impl_type, sig1, ignore_return=True): self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl) - if not is_subtype(sig1.ret_type, defn.impl.type.ret_type): + if not is_subtype(sig1.ret_type, impl_type.ret_type): self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl) # Here's the scoop about generators and coroutines. diff --git a/mypy/fastparse.py b/mypy/fastparse.py index a030d70e6bc2..f345a47579cc 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -215,13 +215,14 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: ret = [] # type: List[Statement] current_overload = [] # type: List[Decorator] current_overload_name = None + print("Fixing overloads", stmts) # mypy doesn't actually check that the decorator is literally @overload for stmt in stmts: if (isinstance(stmt, Decorator) - and is_overload_part(stmt) and stmt.name() == current_overload_name): current_overload.append(stmt) elif (isinstance(stmt, FuncDef) + and not self.is_stub and stmt.name() == current_overload_name and stmt.name() is not None): ret.append(OverloadedFuncDef(current_overload, stmt)) @@ -231,9 +232,16 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - ret.append(OverloadedFuncDef(current_overload, None)) - - if isinstance(stmt, Decorator) and is_overload_part(stmt): + if self.is_stub: + ret.append(OverloadedFuncDef(current_overload, None)) + else: + # Outside of a stub file, the last definition of the + # overload is the implementation, even if it has a + # decorator. We will check it later to make sure it + # does *not* have the @overload decorator. + ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1])) + + if isinstance(stmt, Decorator): current_overload = [stmt] current_overload_name = stmt.name() else: @@ -244,7 +252,10 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - ret.append(OverloadedFuncDef(current_overload, None)) + if self.is_stub: + ret.append(OverloadedFuncDef(current_overload, None)) + else: + ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1])) return ret def in_class(self) -> bool: @@ -476,6 +487,7 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: if metaclass is None: metaclass = '' # To be reported later + print("ClassDef name", n.name, "body", n.body) cdef = ClassDef(n.name, self.as_block(n.body, n.lineno), None, diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index d4500d7fd339..ef72b93a7db8 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -229,6 +229,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: and stmt.name() == current_overload_name): current_overload.append(stmt) elif (isinstance(stmt, FuncDef) + and not self.is_stub and stmt.name() == current_overload_name and stmt.name() is not None): ret.append(OverloadedFuncDef(current_overload, stmt)) @@ -238,9 +239,16 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - ret.append(OverloadedFuncDef(current_overload, None)) - - if isinstance(stmt, Decorator) and is_overload_part(stmt): + if self.is_stub: + ret.append(OverloadedFuncDef(current_overload, None)) + else: + # Outside of a stub file, the last definition of the + # overload is the implementation, even if it has a + # decorator. We will check it later to make sure it + # does *not* have the @overload decorator. + ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1])) + + if isinstance(stmt, Decorator): current_overload = [stmt] current_overload_name = stmt.name() else: @@ -251,7 +259,11 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - ret.append(OverloadedFuncDef(current_overload, None)) + if self.is_stub: + ret.append(OverloadedFuncDef(current_overload, None)) + else: + ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1])) + return ret def in_class(self) -> bool: diff --git a/mypy/nodes.py b/mypy/nodes.py index 00a41d5bf2a7..4df47e87d051 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -378,7 +378,7 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement): items = None # type: List[Decorator] impl = None # type: Optional[FuncDef] - def __init__(self, items: List['Decorator'], impl: Optional['FuncDef'] = None) -> None: + def __init__(self, items: List['Decorator'], impl: Optional[Union['Decorator', 'FuncDef']] = None) -> None: self.items = items self.impl = impl self.set_line(items[0].line) diff --git a/mypy/semanal.py b/mypy/semanal.py index 977d171b72b6..3071cc5d7507 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -422,7 +422,16 @@ def is_defined_type_var(self, tvar: str, context: Context) -> bool: return self.lookup_qualified(tvar, context).kind == BOUND_TVAR def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: + # By "overloaded function" we mean in some sense "redefined function". + # There are two features that use function redefinition: overloads, and + # properties. The first of our items will tell us which one we are + # dealing with. + + print("Semanal overload", defn.name()) + + impl = defn.impl t = [] # type: List[CallableType] + last_non_overload = -1 for i, item in enumerate(defn.items): # TODO support decorated overloaded functions properly item.is_overload = True @@ -432,12 +441,41 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: assert isinstance(callable, CallableType) t.append(callable) if item.func.is_property and i == 0: - # This defines a property, probably with a setter and/or deleter. + # This defines a property, probably with a setter and/or + # deleter. Any "implementation" we might have should be part of + # that, not an overload. + if impl is not None: + defn.items.append(impl) + defn.impl = None self.analyze_property_with_multi_part_definition(defn) break if not [dec for dec in item.decorators if refers_to_fullname(dec, 'typing.overload')]: - self.fail("'overload' decorator expected", item) + if i != 0: + self.name_already_defined(item.name(), item) + last_non_overload = i + + if defn.impl: + impl = defn.impl + impl.accept(self) + if isinstance(impl, Decorator) and [ + dec for dec in impl.decorators + if refers_to_fullname(dec, 'typing.overload')]: + self.fail( + "Overload outside a stub must have implementation", + defn) + callable = function_type( + impl.func, + self.builtin_type('builtins.function')) + impl.is_overload = True + impl.func.is_overload = True + defn.items.append(impl) + defn.impl = None + t.append(callable) + elif last_non_overload == len(defn.items) - 1: + # If the thing before the impl wasn't an overload, the impl isn't an + # impl at all but a redefinition. + self.name_already_defined(impl.name(), impl) defn.type = Overloaded(t) defn.type.line = defn.line @@ -449,8 +487,7 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: elif self.is_func_scope(): self.add_local(defn, defn) - if defn.impl: - defn.impl.accept(self) + def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -> None: """Analyze a property defined using multiple methods (e.g., using @x.setter). @@ -2163,6 +2200,7 @@ def build_typeddict_typeinfo(self, name: str, items: List[str], return info def visit_decorator(self, dec: Decorator) -> None: + print("Semanal decorator", dec) for d in dec.decorators: d.accept(self) removed = [] # type: List[int] @@ -3142,7 +3180,11 @@ def visit_overloaded_func_def(self, func: OverloadedFuncDef) -> None: sem.function_stack.append(func.impl) sem.errors.push_function(func.name()) sem.enter() - func.impl.body.accept(self) + impl = func.impl + if isinstance(impl, FuncDef): + impl.body.accept(self) + elif isinstance(impl, Decorator): + impl.func.body.accept(self) sem.leave() sem.errors.pop_function() sem.function_stack.pop() diff --git a/mypy/sharedparse.py b/mypy/sharedparse.py index 8a975da51b56..3f6062ca80aa 100644 --- a/mypy/sharedparse.py +++ b/mypy/sharedparse.py @@ -1,6 +1,6 @@ from typing import Union, Tuple -from mypy.nodes import Decorator, NameExpr, Statement, MemberExpr +from mypy.nodes import Decorator, NameExpr, Statement, MemberExpr, Expression """Shared logic between our three mypy parser files.""" @@ -102,7 +102,7 @@ def argument_elide_name(name: Union[str, Tuple, None]) -> bool: return isinstance(name, str) and name.startswith("__") -def _is_overload_decorator(dec): +def _is_overload_decorator(dec: Expression) -> bool: if isinstance(dec, NameExpr) and dec.name in {'overload', 'property', 'abstractproperty'}: return True elif isinstance(dec, MemberExpr) and dec.name in {'setter', 'deleter'}: diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 1caeabf7858f..e6f76ec95d60 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -410,6 +410,7 @@ class B(object, A): pass \ # E: Cannot determine consistent method resolution order (MRO) for "B" [case testOverloadedAbstractMethod] +[file foo.pyi] from abc import abstractmethod, ABCMeta from typing import overload @@ -435,6 +436,7 @@ a.f('') a.f(B()) # E: No overload variant of "f" of "A" matches argument types [__main__.B] [case testOverloadedAbstractMethodWithAlternativeDecoratorOrder] +[file foo.pyi] from abc import abstractmethod, ABCMeta from typing import overload @@ -460,6 +462,7 @@ a.f('') a.f(B()) # E: No overload variant of "f" of "A" matches argument types [__main__.B] [case testOverloadedAbstractMethodVariantMissingDecorator1] +[file foo.pyi] from abc import abstractmethod, ABCMeta from typing import overload @@ -473,6 +476,7 @@ class A(metaclass=ABCMeta): [out] [case testOverloadedAbstractMethodVariantMissingDecorator1] +[file foo.pyi] from abc import abstractmethod, ABCMeta from typing import overload diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7e46c6c3f6c8..9a33fa7b1312 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -512,6 +512,7 @@ class A: pass A.x # E: "A" has no attribute "x" [case testAccessingUndefinedAttributeViaClassWithOverloadedInit] +[file foo.pyi] from typing import overload class A: @overload @@ -521,6 +522,7 @@ class A: A.x # E: "A" has no attribute "x" [case testAccessMethodOfClassWithOverloadedInit] +[file foo.pyi] from typing import overload, Any class A: @overload @@ -1296,6 +1298,7 @@ g(1.1) [case testOperatorMethodOverrideIntroducingOverloading] +[file foo.pyi] from typing import overload class A: def __add__(self, x: int) -> int: pass @@ -1329,6 +1332,7 @@ class B(A): def __add__(self, x): pass [case testOperatorMethodOverrideWithIdenticalOverloadedType] +[file foo.pyi] from typing import overload class A: @overload @@ -1342,6 +1346,7 @@ class B(A): def __add__(self, x: str) -> 'A': pass [case testOverloadedOperatorMethodOverrideWithDynamicallyTypedMethod] +[file foo.pyi] from typing import overload, Any class A: @overload @@ -1354,6 +1359,7 @@ class C(A): def __add__(self, x: Any) -> A: pass [case testOverloadedOperatorMethodOverrideWithNewItem] +[file foo.pyi] from typing import overload, Any class A: @overload @@ -1371,6 +1377,7 @@ class B(A): main:8: error: Signature of "__add__" incompatible with supertype "A" [case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder] +[file foo.pyi] from typing import overload, Any class A: @overload @@ -1433,6 +1440,7 @@ class C: main:5: error: Forward operator "__add__" is not callable [case testOverloadedReverseOperatorMethodArgumentType] +[file foo.pyi] from typing import overload, Any class A: @overload @@ -1442,6 +1450,7 @@ class A: [out] [case testReverseOperatorMethodArgumentTypeAndOverloadedMethod] +[file foo.pyi] from typing import overload class A: @overload @@ -1464,6 +1473,7 @@ class B: [out] [case testOperatorMethodsAndOverloadingSpecialCase] +[file foo.pyi] from typing import overload class A: @overload @@ -1475,6 +1485,7 @@ class B: [out] [case testUnsafeOverlappingWithOperatorMethodsAndOverloading2] +[file foo.pyi] from typing import overload class A: def __add__(self, x: 'A') -> int: pass @@ -1513,6 +1524,7 @@ class C(A): pass main:5: error: Signatures of "__iadd__" and "__add__" are incompatible [case testOverloadedNormalAndInplaceOperatorMethod] +[file foo.pyi] from typing import overload class A: @overload @@ -1689,6 +1701,7 @@ c.set(1) # E: Argument 1 to "set" of "C" has incompatible type "int"; expected " [builtins fixtures/__new__.pyi] [case testOverloaded__new__] +[file foo.pyi] from typing import overload class C: @overload @@ -1946,6 +1959,7 @@ def foo(arg: Type[int]): [out] [case testTypeUsingTypeCUnionOverload] +[file foo.pyi] from typing import Type, Union, overload class X: @overload @@ -2066,6 +2080,7 @@ def foo(arg: Type[Tuple[int]]): # E: Unsupported type Type["Tuple[int]"] [out] [case testTypeUsingTypeCOverloadedClass] +[file foo.pyi] from typing import Type, TypeVar, overload class User: @overload @@ -2127,6 +2142,7 @@ def foo(c: Type[C], d: Type[D]) -> None: main:7: error: Revealed type is 'builtins.list[Type[__main__.B]]' [case testTypeMatchesOverloadedFunctions] +[file foo.pyi] from typing import Type, overload, Union class User: pass @@ -2143,6 +2159,7 @@ reveal_type(f(UserType)) # E: Revealed type is 'builtins.int' [out] [case testTypeMatchesGeneralTypeInOverloadedFunctions] +[file foo.pyi] from typing import Type, overload class User: pass @@ -2162,6 +2179,7 @@ reveal_type(f(1)) # E: Revealed type is 'builtins.str' [out] [case testTypeMatchesSpecificTypeInOverloadedFunctions] +[file foo.pyi] from typing import Type, overload class User: pass @@ -2185,6 +2203,7 @@ reveal_type(f(1)) # E: Revealed type is 'builtins.str' [out] [case testMixingTypeTypeInOverloadedFunctions] +[file foo.pyi] from typing import Type, overload class User: pass @@ -2210,6 +2229,7 @@ reveal_type(f("hi")) # E: Revealed type is '__main__.User' [out] [case testGeneralTypeDoesNotMatchSpecificTypeInOverloadedFunctions] +[file foo.pyi] from typing import Type, overload class User: pass @@ -2227,6 +2247,7 @@ f(mock()) # E: No overload variant of "f" matches argument types [builtins.type [out] [case testNonTypeDoesNotMatchOverloadedFunctions] +[file foo.pyi] from typing import Type, overload class User: pass @@ -2241,6 +2262,7 @@ f(3) # E: No overload variant of "f" matches argument types [builtins.int] [out] [case testInstancesDoNotMatchTypeInOverloadedFunctions] +[file foo.pyi] from typing import Type, overload class User: pass @@ -2256,6 +2278,7 @@ f(User()) # E: No overload variant of "f" matches argument types [__main__.User [out] [case testTypeCovarianceWithOverloadedFunctions] +[file foo.pyi] from typing import Type, overload class A: pass @@ -2281,6 +2304,7 @@ f(CType) [case testOverloadedCovariantTypesFail] +[file foo.pyi] from typing import Type, overload class A: pass @@ -2294,6 +2318,7 @@ def f(a: Type[B]) -> str: pass [out] [case testDistinctOverloadedCovariantTypesSucceed] +[file foo.pyi] from typing import Type, overload class A: pass @@ -2323,6 +2348,7 @@ reveal_type(f(BChild())) # E: Revealed type is '__main__.B' [out] [case testTypeTypeOverlapsWithObjectAndType] +[file foo.pyi] from typing import Type, overload class User: pass @@ -2340,6 +2366,7 @@ def g(a: type) -> str: pass [out] [case testTypeOverlapsWithObject] +[file foo.pyi] from typing import Type, overload class User: pass diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 8d221748023e..db98ebf2eadd 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -806,7 +806,7 @@ main:4: error: Incompatible types in assignment (expression has type "A", target main:5: error: Unsupported target for indexed assignment [case testOverloadedIndexing] - +[file foo.pyi] from typing import overload a, b, c = None, None, None # type: (A, B, C) diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 5fb89326ce8a..441408114dc4 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -245,6 +245,7 @@ class A: def f() -> None: pass [case testFunctionTypesWithOverloads] +[file foo.pyi] from typing import Callable, overload f = None # type: Callable[[AA], A] g = None # type: Callable[[B], B] @@ -277,6 +278,7 @@ def j(x: A) -> AA: pass [case testOverloadWithThreeItems] +[file foo.pyi] from typing import Callable, overload g1 = None # type: Callable[[A], A] g2 = None # type: Callable[[B], B] @@ -446,6 +448,7 @@ a.f() a.f(a) # E: Too many arguments [case testOverloadedMethodAsDataAttribute] +[file foo.pyi] from typing import overload class B: pass class A: diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 1c0c730b2a2e..fec7b9683ca6 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1232,6 +1232,7 @@ class B: pass [case testGenericArgumentInOverload] +[file foo.pyi] from typing import overload, List class A: pass class B: pass @@ -1251,6 +1252,7 @@ b = f(b) [builtins fixtures/list.pyi] [case testGenericFunctionAsOverloadItem] +[file foo.pyi] from typing import overload, TypeVar, List T = TypeVar('T') class A: pass diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index f93429e82130..97d5443e9f2e 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -484,6 +484,7 @@ class D: pass [out] [case testIntersectionWithInferredGenericArgument] +[file foo.pyi] from typing import overload, TypeVar, Generic T = TypeVar('T') f(A()) @@ -644,6 +645,7 @@ f( # E: Argument 1 to "f" has incompatible type Callable[[A], A]; expected Calla [case testMapWithOverloadedFunc] +[file foo.pyi] from typing import TypeVar, Callable, List, overload, Any t = TypeVar('t') s = TypeVar('s') diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 7b492e7f53ac..8ddf52f7642f 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -211,6 +211,7 @@ class C: [out] [case testOverloadWithNone] +[file foo.pyi] from typing import overload @overload def f(x: None) -> str: pass diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 266bd400c577..b1f1abc349fb 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1,6 +1,7 @@ -- Test cases for function overloading [case testTypeCheckOverloadWithImplementation] +[file foo.pyi] from typing import overload, Any @overload def f(x: 'A') -> 'B': ... @@ -21,6 +22,7 @@ class B: pass [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadWithImplementationFastparse] +[file foo.pyi] # flags: --fast-parser from typing import overload, Any @@ -43,6 +45,7 @@ class B: pass [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadWithImplementationError] +[file foo.pyi] from typing import overload, Any @overload @@ -66,6 +69,7 @@ class B: pass [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadWithImplTooSpecificArg] +[file foo.pyi] from typing import overload, Any class A: pass @@ -90,6 +94,7 @@ reveal_type(f(B())) # E: Revealed type is '__main__.A' [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadWithImplTooSpecificRetType] +[file foo.pyi] from typing import overload, Any class A: pass @@ -111,6 +116,7 @@ reveal_type(f(B())) # E: Revealed type is '__main__.A' [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadedFunctionBody] +[file foo.pyi] from typing import overload @overload def f(x: 'A'): @@ -125,6 +131,7 @@ class B: pass [out] [case testTypeCheckOverloadedMethodBody] +[file foo.pyi] from typing import overload class A: @overload @@ -139,6 +146,7 @@ class B: pass [out] [case testCallToOverloadedFunction] +[file foo.pyi] from typing import overload f(C()) # E: No overload variant of "f" matches argument types [__main__.C] f(A()) @@ -154,6 +162,7 @@ class B: pass class C: pass [case testOverloadedFunctionReturnValue] +[file foo.pyi] from typing import overload a, b = None, None # type: (A, B) b = f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -169,6 +178,7 @@ class A: pass class B: pass [case testCallToOverloadedMethod] +[file foo.pyi] from typing import overload A().f(C()) # E: No overload variant of "f" of "A" matches argument types [__main__.C] A().f(A()) @@ -184,6 +194,7 @@ class B: pass class C: pass [case testOverloadedMethodReturnValue] +[file foo.pyi] from typing import overload a, b = None, None # type: (A, B) b = a.f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") @@ -199,6 +210,7 @@ class A: class B: pass [case testOverloadsWithDifferentArgumentCounts] +[file foo.pyi] from typing import overload a, b = None, None # type: (A, B) a = f(a) @@ -217,6 +229,7 @@ class A: pass class B: pass [case testGenericOverloadVariant] +[file foo.pyi] from typing import overload, TypeVar, Generic t = TypeVar('t') ab, ac, b, c = None, None, None, None # type: (A[B], A[C], B, C) @@ -234,6 +247,7 @@ class B: pass class C: pass [case testOverloadedInit] +[file foo.pyi] from typing import overload a, b = None, None # type: (A, B) a = A(a) @@ -248,6 +262,7 @@ class A: class B: pass [case testIntersectionTypeCompatibility] +[file foo.pyi] from typing import overload, Callable o = None # type: object a = None # type: A @@ -262,6 +277,7 @@ def f(a: Callable[[], None]) -> None: pass class A: pass [case testCompatibilityOfIntersectionTypeObjectWithStdType] +[file foo.pyi] from typing import overload t, a = None, None # type: (type, A) @@ -276,6 +292,7 @@ class A: class B: pass [case testOverloadedGetitem] +[file foo.pyi] from typing import overload a, b = None, None # type: int, str a = A()[a] @@ -290,6 +307,7 @@ class A: def __getitem__(self, b: str) -> str: pass [case testOverloadedGetitemWithGenerics] +[file foo.pyi] from typing import TypeVar, Generic, overload t = TypeVar('t') a, b, c = None, None, None # type: (A, B, C[A]) @@ -307,6 +325,7 @@ class A: pass class B: pass [case testImplementingOverloadedMethod] +[file foo.pyi] from typing import overload from abc import abstractmethod, ABCMeta @@ -324,6 +343,7 @@ class A(I): def f(self, a: 'A') -> None: pass [case testOverloadWithFunctionType] +[file foo.pyi] from typing import overload, Callable class A: pass @overload @@ -335,6 +355,7 @@ f(A()) [builtins fixtures/function.pyi] [case testVarArgsOverload] +[file foo.pyi] from typing import overload, Any @overload def f(x: 'A', *more: Any) -> 'A': pass @@ -351,6 +372,7 @@ class B: pass [builtins fixtures/list.pyi] [case testVarArgsOverload2] +[file foo.pyi] from typing import overload @overload def f(x: 'A', *more: 'B') -> 'A': pass @@ -365,6 +387,7 @@ class B: pass [builtins fixtures/list.pyi] [case testOverloadWithTypeObject] +[file foo.pyi] from typing import overload @overload def f(a: 'A', t: type) -> None: pass @@ -377,6 +400,7 @@ class B: pass [builtins fixtures/function.pyi] [case testOverloadedInitAndTypeObjectInOverload] +[file foo.pyi] from typing import overload @overload def f(t: type) -> 'A': pass @@ -396,6 +420,7 @@ class B: pass [case testOverlappingErasedSignatures] +[file foo.pyi] from typing import overload, List @overload def f(a: List[int]) -> int: pass @@ -414,6 +439,7 @@ f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expect [builtins fixtures/list.pyi] [case testOverlappingOverloadSignatures] +[file foo.pyi] from typing import overload class A: pass class B(A): pass @@ -423,6 +449,7 @@ def f(x: B) -> int: pass # E: Overloaded function signatures 1 and 2 overlap wit def f(x: A) -> str: pass [case testContravariantOverlappingOverloadSignatures] +[file foo.pyi] from typing import overload class A: pass class B(A): pass @@ -433,6 +460,7 @@ def f(x: B) -> B: pass # This is more specific than the first item, and thus # will never be called. [case testPartiallyCovariantOverlappingOverloadSignatures] +[file foo.pyi] from typing import overload class A: pass class B(A): pass @@ -442,6 +470,7 @@ def f(x: B) -> A: pass # E: Overloaded function signatures 1 and 2 overlap with def f(x: A) -> B: pass [case testPartiallyContravariantOverloadSignatures] +[file foo.pyi] from typing import overload class A: pass class B(A): pass @@ -451,6 +480,7 @@ def g(x: A) -> int: pass # Fine, since A us supertype of B. def g(x: B) -> str: pass [case testCovariantOverlappingOverloadSignatures] +[file foo.pyi] from typing import overload class A: pass class B(A): pass @@ -460,6 +490,7 @@ def g(x: B) -> B: pass def g(x: A) -> A: pass [case testCovariantOverlappingOverloadSignaturesWithSomeSameArgTypes] +[file foo.pyi] from typing import overload class A: pass class B(A): pass @@ -469,6 +500,7 @@ def g(x: int, y: B) -> B: pass def g(x: int, y: A) -> A: pass [case testCovariantOverlappingOverloadSignaturesWithAnyType] +[file foo.pyi] from typing import Any, overload @overload def g(x: int) -> int: pass @@ -476,6 +508,7 @@ def g(x: int) -> int: pass def g(x: Any) -> Any: pass [case testContravariantOverlappingOverloadSignaturesWithAnyType] +[file foo.pyi] from typing import Any, overload @overload def g(x: Any) -> Any: pass # E: Overloaded function signatures 1 and 2 overlap with incompatible return types @@ -483,6 +516,7 @@ def g(x: Any) -> Any: pass # E: Overloaded function signatures 1 and 2 overlap w def g(x: int) -> int: pass [case testOverloadedLtAndGtMethods] +[file foo.pyi] from typing import overload class A: def __lt__(self, x: A) -> int: pass @@ -504,6 +538,7 @@ A() < object() # E: Unsupported operand types for < ("A" and "object") B() < object() # E: No overload variant of "__lt__" of "B" matches argument types [builtins.object] [case testOverloadedForwardMethodAndCallingReverseMethod] +[file foo.pyi] from typing import overload class A: @overload @@ -518,6 +553,7 @@ A() + B() A() + '' # E: No overload variant of "__add__" of "A" matches argument types [builtins.str] [case testOverrideOverloadedMethodWithMoreGeneralArgumentTypes] +[file foo.pyi] from typing import overload class IntSub(int): pass @@ -536,6 +572,7 @@ class B(A): [out] [case testOverrideOverloadedMethodWithMoreSpecificArgumentTypes] +[file foo.pyi] from typing import overload class IntSub(int): pass @@ -566,6 +603,7 @@ main:12: error: Signature of "f" incompatible with supertype "A" main:17: error: Signature of "f" incompatible with supertype "A" [case testOverloadingAndDucktypeCompatibility] +[file foo.pyi] from typing import overload, _promote class A: pass @@ -584,6 +622,7 @@ f(B()) + 'x' # E: Unsupported left operand type for + ("B") f(A()) + 'x' # E: Unsupported left operand type for + ("A") [case testOverloadingAndIntFloatSubtyping] +[file foo.pyi] from typing import overload @overload def f(x: float) -> None: pass @@ -597,6 +636,7 @@ f(()) # E: No overload variant of "f" matches argument types [Tuple[]] [out] [case testOverloadingVariableInputs] +[file foo.pyi] from typing import overload @overload def f(x: int, y: int) -> None: pass @@ -610,6 +650,7 @@ f(*z) [out] [case testTypeInferenceSpecialCaseWithOverloading] +[file foo.pyi] from typing import overload class A: @@ -625,6 +666,7 @@ def f(x: B) -> B: pass f(A() + B())() # E: "B" not callable [case testKeywordArgOverload] +[file foo.pyi] from typing import overload @overload def f(x: int, y: str) -> int: pass @@ -634,6 +676,7 @@ f(x=1, y='')() # E: "int" not callable f(y=1, x='')() # E: "str" not callable [case testIgnoreOverloadVariantBasedOnKeywordArg] +[file foo.pyi] from typing import overload @overload def f(x: int) -> int: pass @@ -643,6 +686,7 @@ f(x=1)() # E: "int" not callable f(y=1)() # E: "str" not callable [case testOverloadWithTupleVarArg] +[file foo.pyi] from typing import overload @overload def f(x: int, y: str) -> int: pass @@ -654,6 +698,7 @@ f(*(1, ''))() # E: "int" not callable f(*(1, '', 1))() # E: No overload variant of "f" matches argument types [Tuple[builtins.int, builtins.str, builtins.int]] [case testPreferExactSignatureMatchInOverload] +[file foo.pyi] from typing import overload, List @overload def f(x: int, y: List[int] = None) -> int: pass @@ -666,6 +711,7 @@ a() # E: "int" not callable [builtins fixtures/list.pyi] [case testOverloadWithDerivedFromAny] +[file foo.pyi] from typing import Any, overload Base = None # type: Any @@ -682,6 +728,7 @@ C(Derived()) # fails without the hack C(Base()) # Always ok [case testOverloadWithBoundedTypeVar] +[file foo.pyi] from typing import overload, TypeVar T = TypeVar('T', bound=str) @overload @@ -697,6 +744,7 @@ f(mystr())() # E: "mystr" not callable [builtins fixtures/primitives.pyi] [case testOverloadedCallWithVariableTypes] +[file foo.pyi] from typing import overload, TypeVar, List T = TypeVar('T', bound=str) @overload @@ -717,6 +765,7 @@ def g(x: U, y: V) -> None: [out] [case testOverlapWithTypeVars] +[file foo.pyi] from typing import overload, TypeVar, Sequence T = TypeVar('T', bound=str) @overload @@ -728,6 +777,7 @@ def f(x: Sequence[int]) -> int: pass main:4: error: Overloaded function signatures 1 and 2 overlap with incompatible return types [case testOverlapWithTypeVarsWithValues] +[file foo.pyi] from typing import overload, TypeVar AnyStr = TypeVar('AnyStr', bytes, str) @@ -755,6 +805,7 @@ g(1, 'foo', b'bar') # E: Type argument 1 of "g" has incompatible value "object" [builtins fixtures/primitives.pyi] [case testBadOverlapWithTypeVarsWithValues] +[file foo.pyi] from typing import overload, TypeVar AnyStr = TypeVar('AnyStr', bytes, str) @@ -765,6 +816,7 @@ def f(x: str) -> bool: pass [builtins fixtures/primitives.pyi] [case testOverlappingOverloadCounting] +[file foo.pyi] from typing import overload class A: pass class B(A): pass @@ -776,6 +828,7 @@ def f(x: B) -> str: pass # E: Overloaded function signatures 2 and 3 overlap wit def f(x: A) -> int: pass [case testOverloadWithTupleMatchingTypeVar] +[file foo.pyi] from typing import TypeVar, Generic, Tuple, overload T = TypeVar('T') @@ -793,6 +846,7 @@ b.f((0, 0)) b.f((0, '')) # E: Argument 1 to "f" of "A" has incompatible type "Tuple[int, str]"; expected "Tuple[int, int]" [case testSingleOverload] +[file foo.pyi] from typing import overload @overload def f(a: int) -> None: pass @@ -801,6 +855,7 @@ def f(a: int) -> None: pass main:2: error: Single overload definition, multiple required [case testSingleOverload2] +[file foo.pyi] from typing import overload def f(a: int) -> None: pass @overload @@ -810,6 +865,7 @@ main:3: error: Name 'f' already defined main:3: error: Single overload definition, multiple required [case testNonconsecutiveOverloads] +[file foo.pyi] from typing import overload @overload def f(a: int) -> None: pass @@ -822,6 +878,7 @@ main:5: error: Name 'f' already defined main:5: error: Single overload definition, multiple required [case testNonconsecutiveOverloadsMissingFirstOverload] +[file foo.pyi] from typing import overload def f(a: int) -> None: pass 1 @@ -832,6 +889,7 @@ main:4: error: Name 'f' already defined main:4: error: Single overload definition, multiple required [case testNonconsecutiveOverloadsMissingLaterOverload] +[file foo.pyi] from typing import overload @overload def f(a: int) -> None: pass @@ -842,6 +900,7 @@ main:2: error: Single overload definition, multiple required main:5: error: Name 'f' already defined [case testOverloadTuple] +[file foo.pyi] from typing import overload, Tuple @overload def f(x: int, y: Tuple[str, ...]) -> None: pass @@ -859,6 +918,7 @@ f(1, y) # E: Argument 2 to "f" has incompatible type Tuple[int, ...]; expected T [builtins fixtures/tuple.pyi] [case testCallableSpecificOverload] +[file foo.pyi] from typing import overload, Callable @overload def f(a: Callable[[], int]) -> None: pass diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 8fd7d09e538c..62afa43ab1d5 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -881,6 +881,7 @@ except exs1 as e: pass # E: Exception type must be derived from BaseException [builtins fixtures/exception.pyi] [case testOverloadedExceptionType] +[file foo.pyi] from typing import overload class E(BaseException): @overload diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 0219c805d93a..61d5d14b8b35 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -431,6 +431,7 @@ class C(Generic[T]): def f(self, x: int = None) -> None: pass [case testTypevarValuesWithOverloadedFunctionSpecialCase] +[file foo.pyi] from typing import TypeVar, overload, Callable T = TypeVar('T', int, str) diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index c51cb06a0002..4242a52c2e9f 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -94,11 +94,15 @@ x[2] + 1 # E: Unsupported operand types for + (likely involving Union) [builtins fixtures/isinstancelist.pyi] [case testUnionAsOverloadArg] +[file foo.pyi] from typing import Union, overload @overload def f(x: Union[int, str]) -> int: pass @overload def f(x: type) -> str: pass + +[file main.py] +from foo import f x = 0 x = f(1) x = f('') diff --git a/test-data/unit/check-unsupported.test b/test-data/unit/check-unsupported.test index 7f36e6955e38..4b897db6eb11 100644 --- a/test-data/unit/check-unsupported.test +++ b/test-data/unit/check-unsupported.test @@ -2,6 +2,7 @@ [case testDecorateOverloadedFunction] +[file foo.pyi] # The error messages are not the most informative ever. def d(x): pass @d diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index cad1dad0231c..557114a8bafd 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -420,6 +420,7 @@ f(y='x', *(1,)) [case testIntersectionTypesAndVarArgs] +[file foo.pyi] from typing import overload a, b = None, None # type: (A, B) From 6296587a15dd166a03b6931b77ba56c50c10f931 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 15 Feb 2017 18:50:48 -0800 Subject: [PATCH 06/17] Partly-programatically modify all non-impl overload tests to be in stub files --- mypy/fastparse.py | 2 - mypy/semanal.py | 3 - mypy/test/data.py | 2 + test-data/unit/check-abstract.test | 8 +- test-data/unit/check-classes.test | 64 +++++++--- test-data/unit/check-expressions.test | 1 + test-data/unit/check-functions.test | 5 +- test-data/unit/check-generics.test | 2 + test-data/unit/check-inference-context.test | 2 + test-data/unit/check-optional.test | 1 + test-data/unit/check-overloading.test | 126 ++++++++++++-------- test-data/unit/check-statements.test | 1 + test-data/unit/check-typevar-values.test | 5 +- test-data/unit/check-unions.test | 17 ++- test-data/unit/check-unsupported.test | 5 +- test-data/unit/check-varargs.test | 1 + 16 files changed, 158 insertions(+), 87 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index f345a47579cc..202c37499010 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -215,7 +215,6 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: ret = [] # type: List[Statement] current_overload = [] # type: List[Decorator] current_overload_name = None - print("Fixing overloads", stmts) # mypy doesn't actually check that the decorator is literally @overload for stmt in stmts: if (isinstance(stmt, Decorator) @@ -487,7 +486,6 @@ def visit_ClassDef(self, n: ast3.ClassDef) -> ClassDef: if metaclass is None: metaclass = '' # To be reported later - print("ClassDef name", n.name, "body", n.body) cdef = ClassDef(n.name, self.as_block(n.body, n.lineno), None, diff --git a/mypy/semanal.py b/mypy/semanal.py index 3071cc5d7507..7e3214d8d92c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -427,8 +427,6 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: # properties. The first of our items will tell us which one we are # dealing with. - print("Semanal overload", defn.name()) - impl = defn.impl t = [] # type: List[CallableType] last_non_overload = -1 @@ -2200,7 +2198,6 @@ def build_typeddict_typeinfo(self, name: str, items: List[str], return info def visit_decorator(self, dec: Decorator) -> None: - print("Semanal decorator", dec) for d in dec.decorators: d.accept(self) removed = [] # type: List[int] diff --git a/mypy/test/data.py b/mypy/test/data.py index 50c41aa7a253..28985259a8e3 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -118,6 +118,8 @@ def parse_test_cases( if ok: input = expand_includes(p[i0].data, include_path) expand_errors(input, tcout, 'main') + for file_path, contents in files: + expand_errors(contents.split('\n'), tcout, file_path) lastline = p[i].line if i < len(p) else p[i - 1].line + 9999 tc = DataDrivenTestCase(p[i0].arg, input, tcout, tcout2, path, p[i0].line, lastline, perform, diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index e6f76ec95d60..b02086714d94 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -410,6 +410,7 @@ class B(object, A): pass \ # E: Cannot determine consistent method resolution order (MRO) for "B" [case testOverloadedAbstractMethod] +from foo import * [file foo.pyi] from abc import abstractmethod, ABCMeta from typing import overload @@ -433,9 +434,10 @@ B().f(1) a = B() # type: A a.f(1) a.f('') -a.f(B()) # E: No overload variant of "f" of "A" matches argument types [__main__.B] +a.f(B()) # E: No overload variant of "f" of "A" matches argument types [foo.B] [case testOverloadedAbstractMethodWithAlternativeDecoratorOrder] +from foo import * [file foo.pyi] from abc import abstractmethod, ABCMeta from typing import overload @@ -459,9 +461,10 @@ B().f(1) a = B() # type: A a.f(1) a.f('') -a.f(B()) # E: No overload variant of "f" of "A" matches argument types [__main__.B] +a.f(B()) # E: No overload variant of "f" of "A" matches argument types [foo.B] [case testOverloadedAbstractMethodVariantMissingDecorator1] +from foo import * [file foo.pyi] from abc import abstractmethod, ABCMeta from typing import overload @@ -476,6 +479,7 @@ class A(metaclass=ABCMeta): [out] [case testOverloadedAbstractMethodVariantMissingDecorator1] +from foo import * [file foo.pyi] from abc import abstractmethod, ABCMeta from typing import overload diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 9a33fa7b1312..84d009a599b4 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -512,6 +512,7 @@ class A: pass A.x # E: "A" has no attribute "x" [case testAccessingUndefinedAttributeViaClassWithOverloadedInit] +from foo import * [file foo.pyi] from typing import overload class A: @@ -522,6 +523,7 @@ class A: A.x # E: "A" has no attribute "x" [case testAccessMethodOfClassWithOverloadedInit] +from foo import * [file foo.pyi] from typing import overload, Any class A: @@ -1298,6 +1300,7 @@ g(1.1) [case testOperatorMethodOverrideIntroducingOverloading] +from foo import * [file foo.pyi] from typing import overload class A: @@ -1332,6 +1335,7 @@ class B(A): def __add__(self, x): pass [case testOperatorMethodOverrideWithIdenticalOverloadedType] +from foo import * [file foo.pyi] from typing import overload class A: @@ -1346,6 +1350,7 @@ class B(A): def __add__(self, x: str) -> 'A': pass [case testOverloadedOperatorMethodOverrideWithDynamicallyTypedMethod] +from foo import * [file foo.pyi] from typing import overload, Any class A: @@ -1359,6 +1364,7 @@ class C(A): def __add__(self, x: Any) -> A: pass [case testOverloadedOperatorMethodOverrideWithNewItem] +from foo import * [file foo.pyi] from typing import overload, Any class A: @@ -1374,9 +1380,10 @@ class B(A): @overload def __add__(self, x: type) -> A: pass [out] -main:8: error: Signature of "__add__" incompatible with supertype "A" +tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A" [case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder] +from foo import * [file foo.pyi] from typing import overload, Any class A: @@ -1390,7 +1397,7 @@ class B(A): @overload def __add__(self, x: 'B') -> 'B': pass [out] -main:8: error: Signature of "__add__" incompatible with supertype "A" +tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A" [case testReverseOperatorMethodArgumentType] from typing import Any @@ -1440,6 +1447,7 @@ class C: main:5: error: Forward operator "__add__" is not callable [case testOverloadedReverseOperatorMethodArgumentType] +from foo import * [file foo.pyi] from typing import overload, Any class A: @@ -1450,6 +1458,7 @@ class A: [out] [case testReverseOperatorMethodArgumentTypeAndOverloadedMethod] +from foo import * [file foo.pyi] from typing import overload class A: @@ -1473,6 +1482,7 @@ class B: [out] [case testOperatorMethodsAndOverloadingSpecialCase] +from foo import * [file foo.pyi] from typing import overload class A: @@ -1485,6 +1495,8 @@ class B: [out] [case testUnsafeOverlappingWithOperatorMethodsAndOverloading2] +from foo import A, B +from foo import * [file foo.pyi] from typing import overload class A: @@ -1497,7 +1509,7 @@ class B: class X: def __add__(self, x): pass [out] -main:6: error: Signatures of "__radd__" of "B" and "__add__" of "X" are unsafely overlapping +tmp/foo.pyi:6: error: Signatures of "__radd__" of "B" and "__add__" of "X" are unsafely overlapping [case testUnsafeOverlappingWithLineNo] from typing import TypeVar @@ -1524,6 +1536,7 @@ class C(A): pass main:5: error: Signatures of "__iadd__" and "__add__" are incompatible [case testOverloadedNormalAndInplaceOperatorMethod] +from foo import * [file foo.pyi] from typing import overload class A: @@ -1545,7 +1558,7 @@ class B: @overload def __iadd__(self, x: str) -> str: pass [out] -main:7: error: Signatures of "__iadd__" and "__add__" are incompatible +tmp/foo.pyi:7: error: Signatures of "__iadd__" and "__add__" are incompatible [case testIntroducingInplaceOperatorInSubclass] import typing @@ -1701,6 +1714,7 @@ c.set(1) # E: Argument 1 to "set" of "C" has incompatible type "int"; expected " [builtins fixtures/__new__.pyi] [case testOverloaded__new__] +from foo import * [file foo.pyi] from typing import overload class C: @@ -1959,6 +1973,7 @@ def foo(arg: Type[int]): [out] [case testTypeUsingTypeCUnionOverload] +from foo import * [file foo.pyi] from typing import Type, Union, overload class X: @@ -2080,6 +2095,7 @@ def foo(arg: Type[Tuple[int]]): # E: Unsupported type Type["Tuple[int]"] [out] [case testTypeUsingTypeCOverloadedClass] +from foo import * [file foo.pyi] from typing import Type, TypeVar, overload class User: @@ -2102,8 +2118,8 @@ def new(uc: Type[U]) -> U: u = new(User) [builtins fixtures/classmethod.pyi] [out] -main:16: error: No overload variant of "User" matches argument types [builtins.str] -main:17: error: Too many arguments for "foo" of "User" +tmp/foo.pyi:16: error: No overload variant of "User" matches argument types [builtins.str] +tmp/foo.pyi:17: error: Too many arguments for "foo" of "User" [case testTypeUsingTypeCInUpperBound] from typing import TypeVar, Type @@ -2142,6 +2158,7 @@ def foo(c: Type[C], d: Type[D]) -> None: main:7: error: Revealed type is 'builtins.list[Type[__main__.B]]' [case testTypeMatchesOverloadedFunctions] +from foo import * [file foo.pyi] from typing import Type, overload, Union @@ -2159,6 +2176,7 @@ reveal_type(f(UserType)) # E: Revealed type is 'builtins.int' [out] [case testTypeMatchesGeneralTypeInOverloadedFunctions] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2179,6 +2197,7 @@ reveal_type(f(1)) # E: Revealed type is 'builtins.str' [out] [case testTypeMatchesSpecificTypeInOverloadedFunctions] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2197,12 +2216,13 @@ def f(a: int) -> str: reveal_type(f(User)) # E: Revealed type is 'builtins.int' reveal_type(f(UserType)) # E: Revealed type is 'builtins.int' -reveal_type(f(User())) # E: Revealed type is '__main__.User' +reveal_type(f(User())) # E: Revealed type is 'foo.User' reveal_type(f(1)) # E: Revealed type is 'builtins.str' [builtins fixtures/classmethod.pyi] [out] [case testMixingTypeTypeInOverloadedFunctions] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2221,14 +2241,15 @@ def f(a: int) -> Type[User]: def f(a: str) -> User: return User() -reveal_type(f(User())) # E: Revealed type is 'Type[__main__.User]' -reveal_type(f(User)) # E: Revealed type is '__main__.User' -reveal_type(f(3)) # E: Revealed type is 'Type[__main__.User]' -reveal_type(f("hi")) # E: Revealed type is '__main__.User' +reveal_type(f(User())) # E: Revealed type is 'Type[foo.User]' +reveal_type(f(User)) # E: Revealed type is 'foo.User' +reveal_type(f(3)) # E: Revealed type is 'Type[foo.User]' +reveal_type(f("hi")) # E: Revealed type is 'foo.User' [builtins fixtures/classmethod.pyi] [out] [case testGeneralTypeDoesNotMatchSpecificTypeInOverloadedFunctions] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2247,6 +2268,7 @@ f(mock()) # E: No overload variant of "f" matches argument types [builtins.type [out] [case testNonTypeDoesNotMatchOverloadedFunctions] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2262,6 +2284,7 @@ f(3) # E: No overload variant of "f" matches argument types [builtins.int] [out] [case testInstancesDoNotMatchTypeInOverloadedFunctions] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2273,11 +2296,12 @@ def f(a: Type[User]) -> None: pass def f(a: int) -> None: pass f(User) -f(User()) # E: No overload variant of "f" matches argument types [__main__.User] +f(User()) # E: No overload variant of "f" matches argument types [foo.User] [builtins fixtures/classmethod.pyi] [out] [case testTypeCovarianceWithOverloadedFunctions] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2293,10 +2317,10 @@ def f(a: Type[B]) -> None: pass @overload def f(a: int) -> None: pass -f(A) # E: No overload variant of "f" matches argument types [def () -> __main__.A] +f(A) # E: No overload variant of "f" matches argument types [def () -> foo.A] f(B) f(C) -f(AType) # E: No overload variant of "f" matches argument types [Type[__main__.A]] +f(AType) # E: No overload variant of "f" matches argument types [Type[foo.A]] f(BType) f(CType) [builtins fixtures/classmethod.pyi] @@ -2304,6 +2328,7 @@ f(CType) [case testOverloadedCovariantTypesFail] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2318,6 +2343,7 @@ def f(a: Type[B]) -> str: pass [out] [case testDistinctOverloadedCovariantTypesSucceed] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2340,14 +2366,15 @@ reveal_type(f(AChild)) # E: Revealed type is 'builtins.int' reveal_type(f(B)) # E: Revealed type is 'builtins.str' reveal_type(f(BChild)) # E: Revealed type is 'builtins.str' -reveal_type(f(A())) # E: Revealed type is '__main__.A' -reveal_type(f(AChild())) # E: Revealed type is '__main__.A' -reveal_type(f(B())) # E: Revealed type is '__main__.B' -reveal_type(f(BChild())) # E: Revealed type is '__main__.B' +reveal_type(f(A())) # E: Revealed type is 'foo.A' +reveal_type(f(AChild())) # E: Revealed type is 'foo.A' +reveal_type(f(B())) # E: Revealed type is 'foo.B' +reveal_type(f(BChild())) # E: Revealed type is 'foo.B' [builtins fixtures/classmethod.pyi] [out] [case testTypeTypeOverlapsWithObjectAndType] +from foo import * [file foo.pyi] from typing import Type, overload @@ -2366,6 +2393,7 @@ def g(a: type) -> str: pass [out] [case testTypeOverlapsWithObject] +from foo import * [file foo.pyi] from typing import Type, overload diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index db98ebf2eadd..55187e9650f1 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -806,6 +806,7 @@ main:4: error: Incompatible types in assignment (expression has type "A", target main:5: error: Unsupported target for indexed assignment [case testOverloadedIndexing] +from foo import * [file foo.pyi] from typing import overload diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 441408114dc4..d4870fa23112 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -245,6 +245,7 @@ class A: def f() -> None: pass [case testFunctionTypesWithOverloads] +from foo import * [file foo.pyi] from typing import Callable, overload f = None # type: Callable[[AA], A] @@ -278,6 +279,7 @@ def j(x: A) -> AA: pass [case testOverloadWithThreeItems] +from foo import * [file foo.pyi] from typing import Callable, overload g1 = None # type: Callable[[A], A] @@ -448,6 +450,7 @@ a.f() a.f(a) # E: Too many arguments [case testOverloadedMethodAsDataAttribute] +from foo import * [file foo.pyi] from typing import overload class B: pass @@ -460,7 +463,7 @@ class A: a = None # type: A a.g() a.g(B()) -a.g(a) # E: No overload variant matches argument types [__main__.A] +a.g(a) # E: No overload variant matches argument types [foo.A] [case testMethodAsDataAttributeInferredFromDynamicallyTypedMethod] diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index fec7b9683ca6..ffe324a690f5 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -1232,6 +1232,7 @@ class B: pass [case testGenericArgumentInOverload] +from foo import * [file foo.pyi] from typing import overload, List class A: pass @@ -1252,6 +1253,7 @@ b = f(b) [builtins fixtures/list.pyi] [case testGenericFunctionAsOverloadItem] +from foo import * [file foo.pyi] from typing import overload, TypeVar, List T = TypeVar('T') diff --git a/test-data/unit/check-inference-context.test b/test-data/unit/check-inference-context.test index 97d5443e9f2e..9482c70a86f5 100644 --- a/test-data/unit/check-inference-context.test +++ b/test-data/unit/check-inference-context.test @@ -484,6 +484,7 @@ class D: pass [out] [case testIntersectionWithInferredGenericArgument] +from foo import * [file foo.pyi] from typing import overload, TypeVar, Generic T = TypeVar('T') @@ -645,6 +646,7 @@ f( # E: Argument 1 to "f" has incompatible type Callable[[A], A]; expected Calla [case testMapWithOverloadedFunc] +from foo import * [file foo.pyi] from typing import TypeVar, Callable, List, overload, Any t = TypeVar('t') diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 8ddf52f7642f..399b4b08ac1e 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -211,6 +211,7 @@ class C: [out] [case testOverloadWithNone] +from foo import * [file foo.pyi] from typing import overload @overload diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index b1f1abc349fb..7552e4d27257 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -1,30 +1,6 @@ -- Test cases for function overloading [case testTypeCheckOverloadWithImplementation] -[file foo.pyi] -from typing import overload, Any -@overload -def f(x: 'A') -> 'B': ... -@overload -def f(x: 'B') -> 'A': ... - -def f(x: Any) -> Any: - if isinstance(x, A): - return B() - else: - return A() - -reveal_type(f(A())) # E: Revealed type is '__main__.B' -reveal_type(f(B())) # E: Revealed type is '__main__.A' - -class A: pass -class B: pass -[builtins fixtures/isinstance.pyi] - -[case testTypeCheckOverloadWithImplementationFastparse] -[file foo.pyi] -# flags: --fast-parser - from typing import overload, Any @overload def f(x: 'A') -> 'B': ... @@ -45,7 +21,6 @@ class B: pass [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadWithImplementationError] -[file foo.pyi] from typing import overload, Any @overload @@ -69,7 +44,6 @@ class B: pass [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadWithImplTooSpecificArg] -[file foo.pyi] from typing import overload, Any class A: pass @@ -94,7 +68,6 @@ reveal_type(f(B())) # E: Revealed type is '__main__.A' [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadWithImplTooSpecificRetType] -[file foo.pyi] from typing import overload, Any class A: pass @@ -116,6 +89,7 @@ reveal_type(f(B())) # E: Revealed type is '__main__.A' [builtins fixtures/isinstance.pyi] [case testTypeCheckOverloadedFunctionBody] +from foo import * [file foo.pyi] from typing import overload @overload @@ -131,6 +105,7 @@ class B: pass [out] [case testTypeCheckOverloadedMethodBody] +from foo import * [file foo.pyi] from typing import overload class A: @@ -146,9 +121,10 @@ class B: pass [out] [case testCallToOverloadedFunction] +from foo import * [file foo.pyi] from typing import overload -f(C()) # E: No overload variant of "f" matches argument types [__main__.C] +f(C()) # E: No overload variant of "f" matches argument types [foo.C] f(A()) f(B()) @@ -162,6 +138,7 @@ class B: pass class C: pass [case testOverloadedFunctionReturnValue] +from foo import * [file foo.pyi] from typing import overload a, b = None, None # type: (A, B) @@ -178,9 +155,10 @@ class A: pass class B: pass [case testCallToOverloadedMethod] +from foo import * [file foo.pyi] from typing import overload -A().f(C()) # E: No overload variant of "f" of "A" matches argument types [__main__.C] +A().f(C()) # E: No overload variant of "f" of "A" matches argument types [foo.C] A().f(A()) A().f(B()) @@ -194,6 +172,7 @@ class B: pass class C: pass [case testOverloadedMethodReturnValue] +from foo import * [file foo.pyi] from typing import overload a, b = None, None # type: (A, B) @@ -210,16 +189,17 @@ class A: class B: pass [case testOverloadsWithDifferentArgumentCounts] +from foo import * [file foo.pyi] from typing import overload a, b = None, None # type: (A, B) a = f(a) b = f(a) # E: Incompatible types in assignment (expression has type "A", variable has type "B") -f(b) # E: No overload variant of "f" matches argument types [__main__.B] +f(b) # E: No overload variant of "f" matches argument types [foo.B] b = f(b, a) a = f(b, a) # E: Incompatible types in assignment (expression has type "B", variable has type "A") -f(a, a) # E: No overload variant of "f" matches argument types [__main__.A, __main__.A] -f(b, b) # E: No overload variant of "f" matches argument types [__main__.B, __main__.B] +f(a, a) # E: No overload variant of "f" matches argument types [foo.A, foo.A] +f(b, b) # E: No overload variant of "f" matches argument types [foo.B, foo.B] @overload def f(x: 'A') -> 'A': pass @@ -229,6 +209,7 @@ class A: pass class B: pass [case testGenericOverloadVariant] +from foo import * [file foo.pyi] from typing import overload, TypeVar, Generic t = TypeVar('t') @@ -247,6 +228,7 @@ class B: pass class C: pass [case testOverloadedInit] +from foo import * [file foo.pyi] from typing import overload a, b = None, None # type: (A, B) @@ -262,6 +244,7 @@ class A: class B: pass [case testIntersectionTypeCompatibility] +from foo import * [file foo.pyi] from typing import overload, Callable o = None # type: object @@ -277,6 +260,7 @@ def f(a: Callable[[], None]) -> None: pass class A: pass [case testCompatibilityOfIntersectionTypeObjectWithStdType] +from foo import * [file foo.pyi] from typing import overload t, a = None, None # type: (type, A) @@ -292,6 +276,7 @@ class A: class B: pass [case testOverloadedGetitem] +from foo import * [file foo.pyi] from typing import overload a, b = None, None # type: int, str @@ -307,6 +292,7 @@ class A: def __getitem__(self, b: str) -> str: pass [case testOverloadedGetitemWithGenerics] +from foo import * [file foo.pyi] from typing import TypeVar, Generic, overload t = TypeVar('t') @@ -325,6 +311,7 @@ class A: pass class B: pass [case testImplementingOverloadedMethod] +from foo import * [file foo.pyi] from typing import overload from abc import abstractmethod, ABCMeta @@ -343,6 +330,7 @@ class A(I): def f(self, a: 'A') -> None: pass [case testOverloadWithFunctionType] +from foo import * [file foo.pyi] from typing import overload, Callable class A: pass @@ -355,6 +343,7 @@ f(A()) [builtins fixtures/function.pyi] [case testVarArgsOverload] +from foo import * [file foo.pyi] from typing import overload, Any @overload @@ -372,6 +361,7 @@ class B: pass [builtins fixtures/list.pyi] [case testVarArgsOverload2] +from foo import * [file foo.pyi] from typing import overload @overload @@ -380,13 +370,14 @@ def f(x: 'A', *more: 'B') -> 'A': pass def f(x: 'B', *more: 'A') -> 'A': pass f(A(), B()) f(A(), B(), B()) -f(A(), A(), B()) # E: No overload variant of "f" matches argument types [__main__.A, __main__.A, __main__.B] -f(A(), B(), A()) # E: No overload variant of "f" matches argument types [__main__.A, __main__.B, __main__.A] +f(A(), A(), B()) # E: No overload variant of "f" matches argument types [foo.A, foo.A, foo.B] +f(A(), B(), A()) # E: No overload variant of "f" matches argument types [foo.A, foo.B, foo.A] class A: pass class B: pass [builtins fixtures/list.pyi] [case testOverloadWithTypeObject] +from foo import * [file foo.pyi] from typing import overload @overload @@ -400,6 +391,7 @@ class B: pass [builtins fixtures/function.pyi] [case testOverloadedInitAndTypeObjectInOverload] +from foo import * [file foo.pyi] from typing import overload @overload @@ -420,6 +412,7 @@ class B: pass [case testOverlappingErasedSignatures] +from foo import * [file foo.pyi] from typing import overload, List @overload @@ -439,6 +432,7 @@ f(list_object) # E: Argument 1 to "f" has incompatible type List[object]; expect [builtins fixtures/list.pyi] [case testOverlappingOverloadSignatures] +from foo import * [file foo.pyi] from typing import overload class A: pass @@ -449,6 +443,7 @@ def f(x: B) -> int: pass # E: Overloaded function signatures 1 and 2 overlap wit def f(x: A) -> str: pass [case testContravariantOverlappingOverloadSignatures] +from foo import * [file foo.pyi] from typing import overload class A: pass @@ -460,6 +455,7 @@ def f(x: B) -> B: pass # This is more specific than the first item, and thus # will never be called. [case testPartiallyCovariantOverlappingOverloadSignatures] +from foo import * [file foo.pyi] from typing import overload class A: pass @@ -470,6 +466,7 @@ def f(x: B) -> A: pass # E: Overloaded function signatures 1 and 2 overlap with def f(x: A) -> B: pass [case testPartiallyContravariantOverloadSignatures] +from foo import * [file foo.pyi] from typing import overload class A: pass @@ -480,6 +477,7 @@ def g(x: A) -> int: pass # Fine, since A us supertype of B. def g(x: B) -> str: pass [case testCovariantOverlappingOverloadSignatures] +from foo import * [file foo.pyi] from typing import overload class A: pass @@ -490,6 +488,7 @@ def g(x: B) -> B: pass def g(x: A) -> A: pass [case testCovariantOverlappingOverloadSignaturesWithSomeSameArgTypes] +from foo import * [file foo.pyi] from typing import overload class A: pass @@ -500,6 +499,7 @@ def g(x: int, y: B) -> B: pass def g(x: int, y: A) -> A: pass [case testCovariantOverlappingOverloadSignaturesWithAnyType] +from foo import * [file foo.pyi] from typing import Any, overload @overload @@ -508,6 +508,7 @@ def g(x: int) -> int: pass def g(x: Any) -> Any: pass [case testContravariantOverlappingOverloadSignaturesWithAnyType] +from foo import * [file foo.pyi] from typing import Any, overload @overload @@ -516,6 +517,7 @@ def g(x: Any) -> Any: pass # E: Overloaded function signatures 1 and 2 overlap w def g(x: int) -> int: pass [case testOverloadedLtAndGtMethods] +from foo import * [file foo.pyi] from typing import overload class A: @@ -538,6 +540,7 @@ A() < object() # E: Unsupported operand types for < ("A" and "object") B() < object() # E: No overload variant of "__lt__" of "B" matches argument types [builtins.object] [case testOverloadedForwardMethodAndCallingReverseMethod] +from foo import * [file foo.pyi] from typing import overload class A: @@ -553,6 +556,7 @@ A() + B() A() + '' # E: No overload variant of "__add__" of "A" matches argument types [builtins.str] [case testOverrideOverloadedMethodWithMoreGeneralArgumentTypes] +from foo import * [file foo.pyi] from typing import overload @@ -572,6 +576,7 @@ class B(A): [out] [case testOverrideOverloadedMethodWithMoreSpecificArgumentTypes] +from foo import * [file foo.pyi] from typing import overload @@ -599,10 +604,11 @@ class D(A): @overload def f(self, x: str) -> str: return '' [out] -main:12: error: Signature of "f" incompatible with supertype "A" -main:17: error: Signature of "f" incompatible with supertype "A" +tmp/foo.pyi:12: error: Signature of "f" incompatible with supertype "A" +tmp/foo.pyi:17: error: Signature of "f" incompatible with supertype "A" [case testOverloadingAndDucktypeCompatibility] +from foo import * [file foo.pyi] from typing import overload, _promote @@ -622,6 +628,7 @@ f(B()) + 'x' # E: Unsupported left operand type for + ("B") f(A()) + 'x' # E: Unsupported left operand type for + ("A") [case testOverloadingAndIntFloatSubtyping] +from foo import * [file foo.pyi] from typing import overload @overload @@ -636,6 +643,7 @@ f(()) # E: No overload variant of "f" matches argument types [Tuple[]] [out] [case testOverloadingVariableInputs] +from foo import * [file foo.pyi] from typing import overload @overload @@ -650,6 +658,7 @@ f(*z) [out] [case testTypeInferenceSpecialCaseWithOverloading] +from foo import * [file foo.pyi] from typing import overload @@ -666,6 +675,7 @@ def f(x: B) -> B: pass f(A() + B())() # E: "B" not callable [case testKeywordArgOverload] +from foo import * [file foo.pyi] from typing import overload @overload @@ -676,6 +686,7 @@ f(x=1, y='')() # E: "int" not callable f(y=1, x='')() # E: "str" not callable [case testIgnoreOverloadVariantBasedOnKeywordArg] +from foo import * [file foo.pyi] from typing import overload @overload @@ -686,6 +697,7 @@ f(x=1)() # E: "int" not callable f(y=1)() # E: "str" not callable [case testOverloadWithTupleVarArg] +from foo import * [file foo.pyi] from typing import overload @overload @@ -698,6 +710,7 @@ f(*(1, ''))() # E: "int" not callable f(*(1, '', 1))() # E: No overload variant of "f" matches argument types [Tuple[builtins.int, builtins.str, builtins.int]] [case testPreferExactSignatureMatchInOverload] +from foo import * [file foo.pyi] from typing import overload, List @overload @@ -711,6 +724,7 @@ a() # E: "int" not callable [builtins fixtures/list.pyi] [case testOverloadWithDerivedFromAny] +from foo import * [file foo.pyi] from typing import Any, overload Base = None # type: Any @@ -728,6 +742,7 @@ C(Derived()) # fails without the hack C(Base()) # Always ok [case testOverloadWithBoundedTypeVar] +from foo import * [file foo.pyi] from typing import overload, TypeVar T = TypeVar('T', bound=str) @@ -744,6 +759,7 @@ f(mystr())() # E: "mystr" not callable [builtins fixtures/primitives.pyi] [case testOverloadedCallWithVariableTypes] +from foo import * [file foo.pyi] from typing import overload, TypeVar, List T = TypeVar('T', bound=str) @@ -765,6 +781,7 @@ def g(x: U, y: V) -> None: [out] [case testOverlapWithTypeVars] +from foo import * [file foo.pyi] from typing import overload, TypeVar, Sequence T = TypeVar('T', bound=str) @@ -774,9 +791,10 @@ def f(x: Sequence[T]) -> None: pass def f(x: Sequence[int]) -> int: pass # These are considered overlapping despite the bound on T due to runtime type erasure. [out] -main:4: error: Overloaded function signatures 1 and 2 overlap with incompatible return types +tmp/foo.pyi:4: error: Overloaded function signatures 1 and 2 overlap with incompatible return types [case testOverlapWithTypeVarsWithValues] +from foo import * [file foo.pyi] from typing import overload, TypeVar AnyStr = TypeVar('AnyStr', bytes, str) @@ -805,6 +823,7 @@ g(1, 'foo', b'bar') # E: Type argument 1 of "g" has incompatible value "object" [builtins fixtures/primitives.pyi] [case testBadOverlapWithTypeVarsWithValues] +from foo import * [file foo.pyi] from typing import overload, TypeVar AnyStr = TypeVar('AnyStr', bytes, str) @@ -816,6 +835,7 @@ def f(x: str) -> bool: pass [builtins fixtures/primitives.pyi] [case testOverlappingOverloadCounting] +from foo import * [file foo.pyi] from typing import overload class A: pass @@ -828,6 +848,7 @@ def f(x: B) -> str: pass # E: Overloaded function signatures 2 and 3 overlap wit def f(x: A) -> int: pass [case testOverloadWithTupleMatchingTypeVar] +from foo import * [file foo.pyi] from typing import TypeVar, Generic, Tuple, overload @@ -845,26 +866,31 @@ b = A() # type: A[Tuple[int, int]] b.f((0, 0)) b.f((0, '')) # E: Argument 1 to "f" of "A" has incompatible type "Tuple[int, str]"; expected "Tuple[int, int]" -[case testSingleOverload] +[case testSingleOverloadStub] +from foo import * [file foo.pyi] from typing import overload @overload def f(a: int) -> None: pass def f(a: int) -> None: pass [out] -main:2: error: Single overload definition, multiple required +tmp/foo.pyi:4: error: Name 'f' already defined +tmp/foo.pyi:2: error: Single overload definition, multiple required + [case testSingleOverload2] +from foo import * [file foo.pyi] from typing import overload def f(a: int) -> None: pass @overload def f(a: str) -> None: pass [out] -main:3: error: Name 'f' already defined -main:3: error: Single overload definition, multiple required +tmp/foo.pyi:3: error: Name 'f' already defined +tmp/foo.pyi:3: error: Single overload definition, multiple required [case testNonconsecutiveOverloads] +from foo import * [file foo.pyi] from typing import overload @overload @@ -873,11 +899,12 @@ def f(a: int) -> None: pass @overload def f(a: str) -> None: pass [out] -main:2: error: Single overload definition, multiple required -main:5: error: Name 'f' already defined -main:5: error: Single overload definition, multiple required +tmp/foo.pyi:5: error: Name 'f' already defined +tmp/foo.pyi:2: error: Single overload definition, multiple required +tmp/foo.pyi:5: error: Single overload definition, multiple required [case testNonconsecutiveOverloadsMissingFirstOverload] +from foo import * [file foo.pyi] from typing import overload def f(a: int) -> None: pass @@ -885,10 +912,11 @@ def f(a: int) -> None: pass @overload def f(a: str) -> None: pass [out] -main:4: error: Name 'f' already defined -main:4: error: Single overload definition, multiple required +tmp/foo.pyi:4: error: Name 'f' already defined +tmp/foo.pyi:4: error: Single overload definition, multiple required [case testNonconsecutiveOverloadsMissingLaterOverload] +from foo import * [file foo.pyi] from typing import overload @overload @@ -896,10 +924,11 @@ def f(a: int) -> None: pass 1 def f(a: str) -> None: pass [out] -main:2: error: Single overload definition, multiple required -main:5: error: Name 'f' already defined +tmp/foo.pyi:5: error: Name 'f' already defined +tmp/foo.pyi:2: error: Single overload definition, multiple required [case testOverloadTuple] +from foo import * [file foo.pyi] from typing import overload, Tuple @overload @@ -918,6 +947,7 @@ f(1, y) # E: Argument 2 to "f" has incompatible type Tuple[int, ...]; expected T [builtins fixtures/tuple.pyi] [case testCallableSpecificOverload] +from foo import * [file foo.pyi] from typing import overload, Callable @overload diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 62afa43ab1d5..173380774d89 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -881,6 +881,7 @@ except exs1 as e: pass # E: Exception type must be derived from BaseException [builtins fixtures/exception.pyi] [case testOverloadedExceptionType] +from foo import * [file foo.pyi] from typing import overload class E(BaseException): diff --git a/test-data/unit/check-typevar-values.test b/test-data/unit/check-typevar-values.test index 61d5d14b8b35..2252c0d778c8 100644 --- a/test-data/unit/check-typevar-values.test +++ b/test-data/unit/check-typevar-values.test @@ -431,6 +431,7 @@ class C(Generic[T]): def f(self, x: int = None) -> None: pass [case testTypevarValuesWithOverloadedFunctionSpecialCase] +from foo import * [file foo.pyi] from typing import TypeVar, overload, Callable @@ -449,8 +450,8 @@ def g(x: int) -> int: return x @overload def g(x: str) -> str: return x [out] -main:7: error: Incompatible types in assignment (expression has type "object", variable has type "int") -main:7: error: Incompatible types in assignment (expression has type "object", variable has type "str") +tmp/foo.pyi:7: error: Incompatible types in assignment (expression has type "object", variable has type "int") +tmp/foo.pyi:7: error: Incompatible types in assignment (expression has type "object", variable has type "str") [case testGenericFunctionSubtypingWithTypevarValues] from typing import TypeVar diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 4242a52c2e9f..f6a149e354b8 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -94,15 +94,7 @@ x[2] + 1 # E: Unsupported operand types for + (likely involving Union) [builtins fixtures/isinstancelist.pyi] [case testUnionAsOverloadArg] -[file foo.pyi] -from typing import Union, overload -@overload -def f(x: Union[int, str]) -> int: pass -@overload -def f(x: type) -> str: pass - -[file main.py] -from foo import f +from foo import * x = 0 x = f(1) x = f('') @@ -111,6 +103,13 @@ s = f(int) s = f(1) # E: Incompatible types in assignment (expression has type "int", variable has type "str") x = f(int) # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[file foo.pyi] +from typing import Union, overload +@overload +def f(x: Union[int, str]) -> int: pass +@overload +def f(x: type) -> str: pass + [case testUnionWithNoneItem] from typing import Union def f() -> Union[int, None]: pass diff --git a/test-data/unit/check-unsupported.test b/test-data/unit/check-unsupported.test index 4b897db6eb11..3406d57d845b 100644 --- a/test-data/unit/check-unsupported.test +++ b/test-data/unit/check-unsupported.test @@ -2,6 +2,7 @@ [case testDecorateOverloadedFunction] +from foo import * [file foo.pyi] # The error messages are not the most informative ever. def d(x): pass @@ -12,5 +13,5 @@ def g(): pass @d # E def g(x): pass [out] -main:5: error: Name 'f' already defined -main:7: error: Name 'g' already defined +tmp/foo.pyi:5: error: Name 'f' already defined +tmp/foo.pyi:7: error: Name 'g' already defined diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 557114a8bafd..a101deb677e5 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -420,6 +420,7 @@ f(y='x', *(1,)) [case testIntersectionTypesAndVarArgs] +from foo import * [file foo.pyi] from typing import overload a, b = None, None # type: (A, B) From 91b1726a81f491892dc4a520525f5c51e384eba2 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 16 Feb 2017 22:20:31 -0800 Subject: [PATCH 07/17] Refactor so that overload decides on its impl at semanal time --- mypy/checker.py | 21 ++-- mypy/checkmember.py | 3 +- mypy/fastparse.py | 23 ++--- mypy/fastparse2.py | 24 +---- mypy/nodes.py | 37 +++++-- mypy/parse.py | 25 ++--- mypy/semanal.py | 135 +++++++++++++++----------- mypy/strconv.py | 2 +- mypy/treetransform.py | 7 +- test-data/unit/check-overloading.test | 2 +- test-data/unit/check-unsupported.test | 2 +- test-data/unit/semanal-classes.test | 10 ++ test-data/unit/semanal-errors.test | 12 ++- test-data/unit/semanal-types.test | 44 +++++++-- test-data/unit/typexport-basic.test | 30 ++++-- 15 files changed, 219 insertions(+), 158 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index e3fbd7c17286..75e233e89589 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -261,14 +261,18 @@ def accept_loop(self, body: Statement, else_body: Statement = None, *, def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: num_abstract = 0 - assert defn.items + if not defn.items: + # In this case we have already complained about none of these being + # valid overloads. + return None if len(defn.items) == 1: self.fail('Single overload definition, multiple required', defn) if defn.is_property: # HACK: Infer the type of the property. - self.visit_decorator(defn.items[0]) + self.visit_decorator(cast(Decorator, defn.items[0])) for fdef in defn.items: + assert isinstance(fdef, Decorator) self.check_func_item(fdef.func, name=fdef.func.name()) if fdef.func.is_abstract: num_abstract += 1 @@ -283,11 +287,14 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: return None def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: - print("Checking overlap", defn.items) + # At this point we should have set the impl already, and all remaining + # items are decorators for i, item in enumerate(defn.items): + assert isinstance(item, Decorator) sig1 = self.function_type(item.func) for j, item2 in enumerate(defn.items[i + 1:]): # TODO overloads involving decorators + assert isinstance(item2, Decorator) sig2 = self.function_type(item2.func) if is_unsafe_overlapping_signatures(sig1, sig2): self.msg.overloaded_signatures_overlap(i + 1, i + j + 2, @@ -300,11 +307,12 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: else: assert False, "Impl isn't the right type" # This can happen if we've got an overload with a different - # decorator too. Just try not to crash. + # decorator too -- we gave up on the types. if impl_type is None or sig1 is None: return - assert isinstance(impl_type, CallableType) and isinstance(sig1, CallableType), "oops {}".format(impl_type) + assert isinstance(impl_type, CallableType) + assert isinstance(sig1, CallableType) if not is_callable_subtype(impl_type, sig1, ignore_return=True): self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl) if not is_subtype(sig1.ret_type, impl_type.ret_type): @@ -2097,7 +2105,8 @@ def check_incompatible_property_override(self, e: Decorator) -> None: continue if (isinstance(base_attr.node, OverloadedFuncDef) and base_attr.node.is_property and - base_attr.node.items[0].var.is_settable_property): + cast(Decorator, + base_attr.node.items[0]).var.is_settable_property): self.fail(messages.READ_ONLY_PROPERTY_OVERRIDES_READ_WRITE, e) def visit_with_stmt(self, s: WithStmt) -> None: diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 7390bb7baf93..80a1a8df9f8e 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -75,7 +75,8 @@ def analyze_member_access(name: str, if method: if method.is_property: assert isinstance(method, OverloadedFuncDef) - return analyze_var(name, method.items[0].var, typ, info, node, is_lvalue, msg, + first_item = cast(Decorator, method.items[0]) + return analyze_var(name, first_item.var, typ, info, node, is_lvalue, msg, original_type, not_ready_callback) if is_lvalue: msg.cant_assign_to_method(node) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 202c37499010..c3126b2ad94d 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -6,7 +6,8 @@ special_function_elide_names, argument_elide_name, is_overload_part, ) from mypy.nodes import ( - MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef, + MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, + OverloadedFuncDef, OverloadPart, ClassDef, Decorator, Block, Var, OperatorAssignmentStmt, ExpressionStmt, AssignmentStmt, ReturnStmt, RaiseStmt, AssertStmt, DelStmt, BreakStmt, ContinueStmt, PassStmt, GlobalDecl, @@ -213,32 +214,23 @@ def as_block(self, stmts: List[ast3.stmt], lineno: int) -> Block: def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: ret = [] # type: List[Statement] - current_overload = [] # type: List[Decorator] + current_overload = [] # type: List[OverloadPart] current_overload_name = None - # mypy doesn't actually check that the decorator is literally @overload for stmt in stmts: if (isinstance(stmt, Decorator) and stmt.name() == current_overload_name): current_overload.append(stmt) elif (isinstance(stmt, FuncDef) - and not self.is_stub and stmt.name() == current_overload_name and stmt.name() is not None): - ret.append(OverloadedFuncDef(current_overload, stmt)) + ret.append(OverloadedFuncDef(current_overload + [stmt])) current_overload = [] current_overload_name = None else: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - if self.is_stub: - ret.append(OverloadedFuncDef(current_overload, None)) - else: - # Outside of a stub file, the last definition of the - # overload is the implementation, even if it has a - # decorator. We will check it later to make sure it - # does *not* have the @overload decorator. - ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1])) + ret.append(OverloadedFuncDef(current_overload)) if isinstance(stmt, Decorator): current_overload = [stmt] @@ -251,10 +243,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - if self.is_stub: - ret.append(OverloadedFuncDef(current_overload, None)) - else: - ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1])) + ret.append(OverloadedFuncDef(current_overload)) return ret def in_class(self) -> bool: diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index ef72b93a7db8..e3eac3ef60ca 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -33,7 +33,7 @@ UnaryExpr, FuncExpr, ComparisonExpr, DictionaryComprehension, SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, Argument, Expression, Statement, BackquoteExpr, PrintStmt, ExecStmt, - ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2 + ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2, OverloadPart, ) from mypy.types import ( Type, CallableType, AnyType, UnboundType, EllipsisType @@ -220,33 +220,23 @@ def as_block(self, stmts: List[ast27.stmt], lineno: int) -> Block: def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: ret = [] # type: List[Statement] - current_overload = [] # type: List[Decorator] + current_overload = [] # type: List[OverloadPart] current_overload_name = None - # mypy doesn't actually check that the decorator is literally @overload for stmt in stmts: if (isinstance(stmt, Decorator) - and is_overload_part(stmt) and stmt.name() == current_overload_name): current_overload.append(stmt) elif (isinstance(stmt, FuncDef) - and not self.is_stub and stmt.name() == current_overload_name and stmt.name() is not None): - ret.append(OverloadedFuncDef(current_overload, stmt)) + ret.append(OverloadedFuncDef(current_overload + [stmt])) current_overload = [] current_overload_name = None else: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - if self.is_stub: - ret.append(OverloadedFuncDef(current_overload, None)) - else: - # Outside of a stub file, the last definition of the - # overload is the implementation, even if it has a - # decorator. We will check it later to make sure it - # does *not* have the @overload decorator. - ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1])) + ret.append(OverloadedFuncDef(current_overload)) if isinstance(stmt, Decorator): current_overload = [stmt] @@ -259,11 +249,7 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: if len(current_overload) == 1: ret.append(current_overload[0]) elif len(current_overload) > 1: - if self.is_stub: - ret.append(OverloadedFuncDef(current_overload, None)) - else: - ret.append(OverloadedFuncDef(current_overload[:-1], current_overload[-1])) - + ret.append(OverloadedFuncDef(current_overload)) return ret def in_class(self) -> bool: diff --git a/mypy/nodes.py b/mypy/nodes.py index 4df47e87d051..5778186ae809 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -375,16 +375,16 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement): Overloaded variants must be consecutive in the source file. """ - items = None # type: List[Decorator] - impl = None # type: Optional[FuncDef] + items = None # type: List[OverloadPart] + impl = None # type: Optional[OverloadPart] - def __init__(self, items: List['Decorator'], impl: Optional[Union['Decorator', 'FuncDef']] = None) -> None: + def __init__(self, items: List['OverloadPart']) -> None: self.items = items - self.impl = impl + self.impl = None self.set_line(items[0].line) def name(self) -> str: - return self.items[0].func.name() + return self.items[0].name() def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_overloaded_func_def(self) @@ -401,10 +401,11 @@ def serialize(self) -> JsonDict: @classmethod def deserialize(cls, data: JsonDict) -> 'OverloadedFuncDef': assert data['.class'] == 'OverloadedFuncDef' - impl = None # type: Optional[FuncDef] + res = OverloadedFuncDef([ + cast(OverloadPart, OverloadPart.deserialize(d)) + for d in data['items']]) if data.get('impl') is not None: - impl = FuncDef.deserialize(data['impl']) - res = OverloadedFuncDef([Decorator.deserialize(d) for d in data['items']], impl) + res.impl = cast(OverloadPart, OverloadPart.deserialize(data['impl'])) if data.get('type') is not None: res.type = mypy.types.Type.deserialize(data['type']) res._fullname = data['fullname'] @@ -511,7 +512,16 @@ def is_dynamic(self) -> bool: return self.type is None -class FuncDef(FuncItem, SymbolNode, Statement): +class OverloadPart(SymbolNode, Statement): + """Interface class for statments that can be part of an OverloadedFuncDef""" + + is_overload = False + + @abstractmethod + def get_body(self) -> Optional['Block']: pass + + +class FuncDef(FuncItem, OverloadPart, Statement): """Function definition. This is a non-lambda function defined using 'def'. @@ -541,6 +551,9 @@ def name(self) -> str: def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_func_def(self) + def get_body(self) -> Optional['Block']: + return self.body + def serialize(self) -> JsonDict: # We're deliberating omitting arguments and storing only arg_names and # arg_kinds for space-saving reasons (arguments is not used in later @@ -579,7 +592,7 @@ def deserialize(cls, data: JsonDict) -> 'FuncDef': return ret -class Decorator(SymbolNode, Statement): +class Decorator(OverloadPart, Statement): """A decorated function. A single Decorator object can include any number of function decorators. @@ -588,6 +601,7 @@ class Decorator(SymbolNode, Statement): func = None # type: FuncDef # Decorated function decorators = None # type: List[Expression] # Decorators, at least one # XXX Not true var = None # type: Var # Represents the decorated function obj + type = None # type: mypy.types.Type is_overload = False def __init__(self, func: FuncDef, decorators: List[Expression], @@ -600,6 +614,9 @@ def __init__(self, func: FuncDef, decorators: List[Expression], def name(self) -> str: return self.func.name() + def get_body(self) -> Optional['Block']: + return self.func.body + def fullname(self) -> str: return self.func.fullname() diff --git a/mypy/parse.py b/mypy/parse.py index ed3a37233471..516ec85717f0 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -29,7 +29,7 @@ UnaryExpr, FuncExpr, PrintStmt, ImportBase, ComparisonExpr, StarExpr, YieldFromExpr, NonlocalDecl, DictionaryComprehension, SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, ExecStmt, Argument, - BackquoteExpr + BackquoteExpr, OverloadPart ) from mypy import defaults from mypy import nodes @@ -902,28 +902,15 @@ def parse_block(self, allow_type: bool = False) -> Tuple[Block, Type]: return node, type def try_combine_overloads(self, s: Statement, stmt: List[Statement]) -> bool: - if isinstance(s, Decorator) and is_overload_part(s) and stmt: - n = s.func.name() - if (isinstance(stmt[-1], Decorator) - and is_overload_part(stmt[-1]) - and stmt[-1].func.name() == n): - stmt[-1] = OverloadedFuncDef([stmt[-1], s], None) + if isinstance(s, OverloadPart) and stmt: + n = s.name() + if (isinstance(stmt[-1], OverloadPart) + and stmt[-1].name() == n): + stmt[-1] = OverloadedFuncDef([stmt[-1], s]) return True elif isinstance(stmt[-1], OverloadedFuncDef) and stmt[-1].name() == n: stmt[-1].items.append(s) return True - elif isinstance(s, FuncDef) and stmt: - n = s.name() - if (isinstance(stmt[-1], Decorator) - and is_overload_part(stmt[-1]) - and stmt[-1].func.name() == n): - stmt[-1] = OverloadedFuncDef([stmt[-1]], s) - return True - elif (isinstance(stmt[-1], OverloadedFuncDef) - and stmt[-1].impl is None - and stmt[-1].name() == n): - stmt[-1].impl = s - return True return False def parse_statement(self) -> Tuple[Statement, bool]: diff --git a/mypy/semanal.py b/mypy/semanal.py index 7e3214d8d92c..9fce9a010bee 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -422,61 +422,77 @@ def is_defined_type_var(self, tvar: str, context: Context) -> bool: return self.lookup_qualified(tvar, context).kind == BOUND_TVAR def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: - # By "overloaded function" we mean in some sense "redefined function". - # There are two features that use function redefinition: overloads, and - # properties. The first of our items will tell us which one we are - # dealing with. - - impl = defn.impl + # Decide whether to analyze this as a property or an overload. + # If an overload, and we're outside a stub, find the impl and set it. + # Remove it from the item list, it's special. t = [] # type: List[CallableType] - last_non_overload = -1 + non_overload_indexes = [] for i, item in enumerate(defn.items): # TODO support decorated overloaded functions properly item.is_overload = True - item.func.is_overload = True item.accept(self) - callable = function_type(item.func, self.builtin_type('builtins.function')) - assert isinstance(callable, CallableType) - t.append(callable) - if item.func.is_property and i == 0: - # This defines a property, probably with a setter and/or - # deleter. Any "implementation" we might have should be part of - # that, not an overload. - if impl is not None: - defn.items.append(impl) - defn.impl = None - self.analyze_property_with_multi_part_definition(defn) - break - if not [dec for dec in item.decorators - if refers_to_fullname(dec, 'typing.overload')]: - if i != 0: - self.name_already_defined(item.name(), item) - last_non_overload = i - - if defn.impl: - impl = defn.impl - impl.accept(self) - if isinstance(impl, Decorator) and [ - dec for dec in impl.decorators - if refers_to_fullname(dec, 'typing.overload')]: + + if isinstance(item, Decorator): + item.func.is_overload = True + callable = function_type(item.func, self.builtin_type('builtins.function')) + assert isinstance(callable, CallableType) + if item.func.is_property and i == 0: + # This defines a property, probably with a setter and/or + # deleter. + t.append(callable) + self.analyze_property_with_multi_part_definition(defn) + break + elif not any(refers_to_fullname(dec, 'typing.overload') + for dec in item.decorators): + if i == len(defn.items) - 1 and not self.is_stub_file: + # Last item outside a stub is impl + defn.impl = item + else: + # Oops it wasn't an overload after all. A clear error + # will vary based on where in the list it is, record + # that. + non_overload_indexes.append(i) + else: + t.append(callable) + elif isinstance(item, FuncDef): + if i == len(defn.items) - 1 and not self.is_stub_file: + defn.impl = item + else: + non_overload_indexes.append(i) + else: + # No break, so it was an overload. + if non_overload_indexes: + if t: + # Some of them were overloads, but not all. + for idx in non_overload_indexes: + if self.is_stub_file: + self.fail("Implementations of overloaded functions " + "not allowed in stub files", defn.items[idx]) + else: + self.fail("Implementations of overloaded functions " + "must come last", defn.items[idx]) + else: + for idx in non_overload_indexes[1:]: + self.name_already_defined(defn.name(), defn.items[idx]) + if defn.impl: + self.name_already_defined(defn.name(), defn.impl) + # Remove the non-overloads + for idx in reversed(non_overload_indexes): + del defn.items[idx] + # If we found an implementation, remove it from the overloads to + # consider. + if defn.impl is not None: + assert defn.impl is defn.items[-1] + defn.items = defn.items[:-1] + + elif not self.is_stub_file and not non_overload_indexes: self.fail( "Overload outside a stub must have implementation", defn) - callable = function_type( - impl.func, - self.builtin_type('builtins.function')) - impl.is_overload = True - impl.func.is_overload = True - defn.items.append(impl) - defn.impl = None - t.append(callable) - elif last_non_overload == len(defn.items) - 1: - # If the thing before the impl wasn't an overload, the impl isn't an - # impl at all but a redefinition. - self.name_already_defined(impl.name(), impl) - - defn.type = Overloaded(t) - defn.type.line = defn.line + + if t: + defn.type = Overloaded(t) + defn.type.line = defn.line if self.is_class_scope(): self.type.names[defn.name()] = SymbolTableNode(MDEF, defn, @@ -485,8 +501,6 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: elif self.is_func_scope(): self.add_local(defn, defn) - - def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) -> None: """Analyze a property defined using multiple methods (e.g., using @x.setter). @@ -494,18 +508,19 @@ def analyze_property_with_multi_part_definition(self, defn: OverloadedFuncDef) - """ defn.is_property = True items = defn.items + first_item = cast(Decorator, defn.items[0]) for item in items[1:]: - if len(item.decorators) == 1: + if isinstance(item, Decorator) and len(item.decorators) == 1: node = item.decorators[0] if isinstance(node, MemberExpr): if node.name == 'setter': # The first item represents the entire property. - defn.items[0].var.is_settable_property = True + first_item.var.is_settable_property = True # Get abstractness from the original definition. - item.func.is_abstract = items[0].func.is_abstract + item.func.is_abstract = first_item.func.is_abstract + item.func.accept(self) else: self.fail("Decorated property not supported", item) - item.func.accept(self) def next_function_tvar_id(self) -> int: return self.next_function_tvar_id_stack[-1] @@ -2234,6 +2249,7 @@ def visit_decorator(self, dec: Decorator) -> None: self.fail('Too many arguments', dec.func) elif refers_to_fullname(d, 'typing.no_type_check'): dec.var.type = AnyType() + dec.type = dec.var.type no_type_check = True for i in reversed(removed): del dec.decorators[i] @@ -3165,6 +3181,7 @@ def visit_func_def(self, func: FuncDef) -> None: sem.function_stack.pop() def visit_overloaded_func_def(self, func: OverloadedFuncDef) -> None: + # REFACTOR: Visit all bodies kind = self.kind_by_scope() if kind == GDEF: self.sem.check_no_global(func.name(), func, True) @@ -3172,15 +3189,19 @@ def visit_overloaded_func_def(self, func: OverloadedFuncDef) -> None: if kind == GDEF: self.sem.globals[func.name()] = SymbolTableNode(kind, func, self.sem.cur_mod_id) if func.impl: + impl = func.impl # Also analyze the function body (in case there are conditional imports). sem = self.sem - sem.function_stack.append(func.impl) - sem.errors.push_function(func.name()) - sem.enter() - impl = func.impl + if isinstance(impl, FuncDef): + sem.function_stack.append(impl) + sem.errors.push_function(func.name()) + sem.enter() impl.body.accept(self) elif isinstance(impl, Decorator): + sem.function_stack.append(impl.func) + sem.errors.push_function(func.name()) + sem.enter() impl.func.body.accept(self) sem.leave() sem.errors.pop_function() diff --git a/mypy/strconv.py b/mypy/strconv.py index 67cef6aadbf1..fffa11e2a384 100644 --- a/mypy/strconv.py +++ b/mypy/strconv.py @@ -126,7 +126,7 @@ def visit_overloaded_func_def(self, o: 'mypy.nodes.OverloadedFuncDef') -> str: if o.type: a.insert(0, o.type) if o.impl: - a.append(o.impl) + a.insert(0, o.impl) return self.dump(a, o) def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> str: diff --git a/mypy/treetransform.py b/mypy/treetransform.py index 6c1d97e550c7..92bdfca92d7f 100644 --- a/mypy/treetransform.py +++ b/mypy/treetransform.py @@ -19,7 +19,7 @@ ComparisonExpr, TempNode, StarExpr, Statement, Expression, YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SetComprehension, DictionaryComprehension, ComplexExpr, TypeAliasExpr, EllipsisExpr, - YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr, + YieldExpr, ExecStmt, Argument, BackquoteExpr, AwaitExpr, OverloadPart ) from mypy.types import Type, FunctionLike from mypy.traverser import TraverserVisitor @@ -160,8 +160,7 @@ def copy_function_attributes(self, new: FuncItem, new.line = original.line def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> OverloadedFuncDef: - items = [self.visit_decorator(decorator) - for decorator in node.items] + items = [cast(OverloadPart, item.accept(self)) for item in node.items] for newitem, olditem in zip(items, node.items): newitem.line = olditem.line new = OverloadedFuncDef(items) @@ -169,7 +168,7 @@ def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> OverloadedFuncDe new.type = self.type(node.type) new.info = node.info if node.impl: - node.impl = self.visit_func_def(node.impl) + new.impl = cast(OverloadPart, node.impl.accept(self)) return new def visit_class_def(self, node: ClassDef) -> ClassDef: diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 7552e4d27257..82dc69aa05cf 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -874,8 +874,8 @@ from typing import overload def f(a: int) -> None: pass def f(a: int) -> None: pass [out] -tmp/foo.pyi:4: error: Name 'f' already defined tmp/foo.pyi:2: error: Single overload definition, multiple required +tmp/foo.pyi:4: error: Implementations of overloaded functions not allowed in stub files [case testSingleOverload2] diff --git a/test-data/unit/check-unsupported.test b/test-data/unit/check-unsupported.test index 3406d57d845b..ea3b50f7e776 100644 --- a/test-data/unit/check-unsupported.test +++ b/test-data/unit/check-unsupported.test @@ -13,5 +13,5 @@ def g(): pass @d # E def g(x): pass [out] -tmp/foo.pyi:5: error: Name 'f' already defined tmp/foo.pyi:7: error: Name 'g' already defined +tmp/foo.pyi:5: error: Name 'f' already defined diff --git a/test-data/unit/semanal-classes.test b/test-data/unit/semanal-classes.test index a99f8511ad0c..aff6ce062693 100644 --- a/test-data/unit/semanal-classes.test +++ b/test-data/unit/semanal-classes.test @@ -186,12 +186,22 @@ class A: def f(self) -> None: self @overload def f(self, x: 'A') -> None: self + def f(self, *args): self [out] MypyFile:1( ImportFrom:1(typing, [overload]) ClassDef:2( A OverloadedFuncDef:3( + FuncDef:7( + f + Args( + Var(self)) + VarArg( + Var(args)) + Block:7( + ExpressionStmt:7( + NameExpr(self [l])))) Overload(def (self: __main__.A), \ def (self: __main__.A, x: __main__.A)) Decorator:3( diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 5f4d32659857..5dd8e9e2b828 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -89,6 +89,7 @@ class A: pass def f(): pass @overload # E: "A" expects no type arguments, but 1 given def f(x: A[int]) -> None: pass +def f(*args): pass [out] [case testInvalidNumberOfGenericArgsInBaseType] @@ -651,6 +652,7 @@ def f() -> None: x = 1 def p(): pass # fail [out] +main:3: error: Overload outside a stub must have implementation main:8: error: Name 'p' already defined [case testNestedFunctionInMethod] @@ -1060,18 +1062,18 @@ def f(x: 'int['): pass # E: syntax error in type comment [case testInconsistentOverload] from typing import overload def dec(x): pass -@overload +@dec # E: Implementations of overloaded functions must come last def f(): pass -@dec # E: Name 'f' already defined +@overload def f(): pass [out] [case testInconsistentOverload2] from typing import overload def dec(x): pass -@dec +@dec # E: Implementations of overloaded functions must come last def f(): pass -@overload # E: Name 'f' already defined +@overload def f(): pass [out] @@ -1152,7 +1154,7 @@ class A: [case testOverloadedProperty2] from typing import overload class A: - @overload + @overload # E: Overload outside a stub must have implementation def f(self) -> int: pass @property # E: Decorated property not supported @overload diff --git a/test-data/unit/semanal-types.test b/test-data/unit/semanal-types.test index beb3cbfa660e..d1f1790a8725 100644 --- a/test-data/unit/semanal-types.test +++ b/test-data/unit/semanal-types.test @@ -722,14 +722,25 @@ MypyFile:1( [case testOverloadedFunction] from typing import overload @overload -def f(o: object) -> int: o +def f(a: object) -> int: a @overload def f(a: str) -> object: a + +def f(a: Any) -> Any: return a + [out] MypyFile:1( ImportFrom:1(typing, [overload]) OverloadedFuncDef:2( - Overload(def (o: builtins.object) -> builtins.int, \ + FuncDef:7( + f + Args( + Var(a)) + def (a: builtins.Any) -> builtins.Any + Block:7( + ReturnStmt:7( + NameExpr(a [l])))) + Overload(def (a: builtins.object) -> builtins.int, \ def (a: builtins.str) -> builtins.object) Decorator:2( Var(f) @@ -737,11 +748,11 @@ MypyFile:1( FuncDef:3( f Args( - Var(o)) - def (o: builtins.object) -> builtins.int + Var(a)) + def (a: builtins.object) -> builtins.int Block:3( ExpressionStmt:3( - NameExpr(o [l]))))) + NameExpr(a [l]))))) Decorator:4( Var(f) NameExpr(overload [typing.overload]) @@ -760,11 +771,21 @@ from typing import overload def f() -> None: pass @overload def f(x: int) -> None: pass + +def f(*args) -> None: pass + x = f [out] MypyFile:1( ImportFrom:1(typing, [overload]) OverloadedFuncDef:2( + FuncDef:7( + f + def (*args: Any) + VarArg( + Var(args)) + Block:7( + PassStmt:7())) Overload(def (), def (x: builtins.int)) Decorator:2( Var(f) @@ -784,7 +805,7 @@ MypyFile:1( def (x: builtins.int) Block:5( PassStmt:5())))) - AssignmentStmt:6( + AssignmentStmt:9( NameExpr(x* [__main__.x]) NameExpr(f [__main__.f]))) @@ -795,6 +816,9 @@ def f(): def g(): pass @overload def g(x): pass + + def g(*args): pass + y = g [out] MypyFile:1( @@ -803,6 +827,12 @@ MypyFile:1( f Block:2( OverloadedFuncDef:3( + FuncDef:8( + g + VarArg( + Var(args)) + Block:8( + PassStmt:8())) Overload(def () -> Any, def (x: Any) -> Any) Decorator:3( Var(g) @@ -820,7 +850,7 @@ MypyFile:1( Var(x)) Block:6( PassStmt:6())))) - AssignmentStmt:7( + AssignmentStmt:10( NameExpr(y* [l]) NameExpr(g [l]))))) diff --git a/test-data/unit/typexport-basic.test b/test-data/unit/typexport-basic.test index 2440fe75ca2f..4244d5aac31e 100644 --- a/test-data/unit/typexport-basic.test +++ b/test-data/unit/typexport-basic.test @@ -897,10 +897,12 @@ class A: @overload def f(self) -> None: pass @overload - def f(self, x: object) -> None: pass + def f(self, __x: object) -> None: pass + + def f(self, *args) -> None: pass A.f [out] -MemberExpr(8) : Overload(def (self: A), def (self: A, x: builtins.object)) +MemberExpr(10) : Overload(def (self: A), def (self: A, builtins.object)) [case testOverloadedUnboundMethodWithImplicitSig] ## MemberExpr @@ -909,10 +911,12 @@ class A: @overload def f(self): pass @overload - def f(self, x): pass + def f(self, __x): pass + + def f(self, *args): pass A.f [out] -MemberExpr(8) : Overload(def (self: Any) -> Any, def (self: Any, x: Any) -> Any) +MemberExpr(10) : Overload(def (self: Any) -> Any, def (self: Any, Any) -> Any) [case testUnboundMethodWithInheritance] ## MemberExpr @@ -961,10 +965,12 @@ class A(Generic[t]): def f(self, x: t) -> t: pass @overload def f(self) -> object: pass + def f(self, *args): pass + ab, b = None, None # type: (A[B], B) A.f(ab, b) [out] -CallExpr(11) : B +CallExpr(13) : B [case testUnboundMethodOfGenericClassWithImplicitSig] ## MemberExpr @@ -1059,28 +1065,32 @@ from typing import overload def f(x: int) -> str: pass @overload def f(x: str) -> int: pass +def f(x): pass f(1) f('') [out] -CallExpr(7) : builtins.str -CallExpr(8) : builtins.int +CallExpr(8) : builtins.str +CallExpr(9) : builtins.int [case testOverlappingOverloadedFunctionType] ## CallExpr -from typing import overload +from typing import overload, Any class A: pass class B(A): pass @overload def f(x: B) -> B: pass @overload def f(x: A) -> A: pass + +def f(x) -> Any: pass + a = None # type: A b = None # type: B f(a) f(b) [out] -CallExpr(11) : A -CallExpr(12) : B +CallExpr(14) : A +CallExpr(15) : B From dbc770c30528837eded3c354656283f622926658 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 16 Feb 2017 22:55:19 -0800 Subject: [PATCH 08/17] Guido comments and polish --- mypy/fastparse.py | 2 +- mypy/fastparse2.py | 2 +- mypy/parse.py | 2 +- mypy/semanal.py | 11 ++++-- mypy/sharedparse.py | 14 ------- test-data/unit/check-overloading.test | 55 +++++++++++++++++++-------- test-data/unit/semanal-errors.test | 2 +- 7 files changed, 51 insertions(+), 37 deletions(-) diff --git a/mypy/fastparse.py b/mypy/fastparse.py index c3126b2ad94d..8600cfa23ad4 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -3,7 +3,7 @@ from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List, Set from mypy.sharedparse import ( - special_function_elide_names, argument_elide_name, is_overload_part, + special_function_elide_names, argument_elide_name, ) from mypy.nodes import ( MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index e3eac3ef60ca..6c9c6e8a4654 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -19,7 +19,7 @@ from typing import Tuple, Union, TypeVar, Callable, Sequence, Optional, Any, cast, List, Set from mypy.sharedparse import ( - special_function_elide_names, argument_elide_name, is_overload_part, + special_function_elide_names, argument_elide_name, ) from mypy.nodes import ( MypyFile, Node, ImportBase, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef, diff --git a/mypy/parse.py b/mypy/parse.py index 516ec85717f0..be7beb4bc3c0 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -15,7 +15,7 @@ EllipsisToken ) from mypy.sharedparse import ( - special_function_elide_names, argument_elide_name, is_overload_part, + special_function_elide_names, argument_elide_name, ) from mypy.nodes import ( MypyFile, Import, ImportAll, ImportFrom, FuncDef, OverloadedFuncDef, diff --git a/mypy/semanal.py b/mypy/semanal.py index 9fce9a010bee..94cf4402c297 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -422,9 +422,14 @@ def is_defined_type_var(self, tvar: str, context: Context) -> bool: return self.lookup_qualified(tvar, context).kind == BOUND_TVAR def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: - # Decide whether to analyze this as a property or an overload. - # If an overload, and we're outside a stub, find the impl and set it. - # Remove it from the item list, it's special. + # OverloadedFuncDef refers to any legitimate situation where you have + # more than one declaration for the same function in a row. This occurs + # with a @property with a setter or a deleter, and for a classic + # @overload. + + # Decide whether to analyze this as a property or an overload. If an + # overload, and we're outside a stub, find the impl and set it. Remove + # the impl from the item list, it's special. t = [] # type: List[CallableType] non_overload_indexes = [] for i, item in enumerate(defn.items): diff --git a/mypy/sharedparse.py b/mypy/sharedparse.py index 3f6062ca80aa..1643aaccf321 100644 --- a/mypy/sharedparse.py +++ b/mypy/sharedparse.py @@ -1,7 +1,5 @@ from typing import Union, Tuple -from mypy.nodes import Decorator, NameExpr, Statement, MemberExpr, Expression - """Shared logic between our three mypy parser files.""" @@ -100,15 +98,3 @@ def special_function_elide_names(name: str) -> bool: def argument_elide_name(name: Union[str, Tuple, None]) -> bool: return isinstance(name, str) and name.startswith("__") - - -def _is_overload_decorator(dec: Expression) -> bool: - if isinstance(dec, NameExpr) and dec.name in {'overload', 'property', 'abstractproperty'}: - return True - elif isinstance(dec, MemberExpr) and dec.name in {'setter', 'deleter'}: - return True - return False - - -def is_overload_part(stmt: Decorator) -> bool: - return any(_is_overload_decorator(dec) for dec in stmt.decorators) diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 82dc69aa05cf..e36884297fa4 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -8,10 +8,7 @@ def f(x: 'A') -> 'B': ... def f(x: 'B') -> 'A': ... def f(x: Any) -> Any: - if isinstance(x, A): - return B() - else: - return A() + pass reveal_type(f(A())) # E: Revealed type is '__main__.B' reveal_type(f(B())) # E: Revealed type is '__main__.A' @@ -20,6 +17,30 @@ class A: pass class B: pass [builtins fixtures/isinstance.pyi] +[case testTypeCheckOverloadWithImplementationPy2] +# flags: --python-version 2.7 + +from typing import overload +@overload +def f(x): + # type: (A) -> B + pass + +@overload +def f(x): + # type: (B) -> A + pass + +def f(x): + pass + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + [case testTypeCheckOverloadWithImplementationError] from typing import overload, Any @@ -29,15 +50,20 @@ def f(x: 'A') -> 'B': ... def f(x: 'B') -> 'A': ... def f(x: Any) -> Any: - if isinstance(x, A): - y = x - y = B() # E: Incompatible types in assignment (expression has type "B", variable has type "A") - return y - else: - return A() + foo = 1 + foo = "bar" # E: Incompatible types in assignment (expression has type "str", variable has type "int") -reveal_type(f(A())) # E: Revealed type is '__main__.B' -reveal_type(f(B())) # E: Revealed type is '__main__.A' +@overload +def g(x: 'A') -> 'B': ... +@overload +def g(x: 'B') -> 'A': ... + +def g(x): + foo = 1 + foo = "bar" + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' class A: pass class B: pass @@ -57,10 +83,7 @@ def f(x: 'A') -> 'B': ... def f(x: 'B') -> 'A': ... def f(x: 'A') -> Any: # E: Overloaded function implementation cannot accept all possible arguments of signature 2 - if x is a: - return B() - else: - return A() + pass reveal_type(f(A())) # E: Revealed type is '__main__.B' reveal_type(f(B())) # E: Revealed type is '__main__.A' diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 5dd8e9e2b828..f64283b374ec 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -1082,7 +1082,7 @@ from typing import overload def dec(x): pass @dec def f(): pass -@dec # E: Name 'f' already defined +@dec # E: Name 'f' already defined def f(): pass [out] From 80fc979c1c82f9f377554a808517b96a5cc81b49 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 16 Feb 2017 23:03:12 -0800 Subject: [PATCH 09/17] s/cannot/does not/ --- mypy/messages.py | 2 +- test-data/unit/check-overloading.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 636756675cbc..d945a336f1d0 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -830,7 +830,7 @@ def overloaded_signatures_overlap(self, index1: int, index2: int, 'incompatible return types'.format(index1, index2), context) def overloaded_signatures_arg_specific(self, index1: int, context: Context) -> None: - self.fail('Overloaded function implementation cannot accept all possible arguments ' + self.fail('Overloaded function implementation does not accept all possible arguments ' 'of signature {}'.format(index1), context) def overloaded_signatures_ret_specific(self, index1: int, context: Context) -> None: diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index e36884297fa4..464cb0b661f1 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -82,7 +82,7 @@ def f(x: 'A') -> 'B': ... @overload def f(x: 'B') -> 'A': ... -def f(x: 'A') -> Any: # E: Overloaded function implementation cannot accept all possible arguments of signature 2 +def f(x: 'A') -> Any: # E: Overloaded function implementation does not accept all possible arguments of signature 2 pass reveal_type(f(A())) # E: Revealed type is '__main__.B' From 045be3e3c0d8bcc1bc668c4d249e502287b5b4d6 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 17 Feb 2017 09:23:50 -0800 Subject: [PATCH 10/17] Fix windows paths when expanding errors in non-main files --- mypy/test/data.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mypy/test/data.py b/mypy/test/data.py index 28985259a8e3..9388397a0b26 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -119,6 +119,8 @@ def parse_test_cases( input = expand_includes(p[i0].data, include_path) expand_errors(input, tcout, 'main') for file_path, contents in files: + if native_sep and os.path.sep == '\\': + file_path = fix_win_path(file_path) expand_errors(contents.split('\n'), tcout, file_path) lastline = p[i].line if i < len(p) else p[i - 1].line + 9999 tc = DataDrivenTestCase(p[i0].arg, input, tcout, tcout2, path, From 95555e66f74d6e155a755840a47475a3bb24416c Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 17 Feb 2017 11:08:17 -0800 Subject: [PATCH 11/17] Another theory about how to make windows paths work --- mypy/semanal.py | 1 - mypy/test/data.py | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 94cf4402c297..e5962d3ae0e9 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3186,7 +3186,6 @@ def visit_func_def(self, func: FuncDef) -> None: sem.function_stack.pop() def visit_overloaded_func_def(self, func: OverloadedFuncDef) -> None: - # REFACTOR: Visit all bodies kind = self.kind_by_scope() if kind == GDEF: self.sem.check_no_global(func.name(), func, True) diff --git a/mypy/test/data.py b/mypy/test/data.py index 9388397a0b26..9ef63b688f5c 100644 --- a/mypy/test/data.py +++ b/mypy/test/data.py @@ -2,6 +2,7 @@ import os.path import os +import posixpath import re from os import remove, rmdir import shutil @@ -27,7 +28,10 @@ def parse_test_cases( myunit and pytest codepaths -- if something looks redundant, that's likely the reason. """ - + if native_sep: + join = os.path.join + else: + join = posixpath.join # type: ignore if not include_path: include_path = os.path.dirname(path) with open(path, encoding='utf-8') as f: @@ -57,7 +61,7 @@ def parse_test_cases( # Record an extra file needed for the test case. arg = p[i].arg assert arg is not None - file_entry = (os.path.join(base_path, arg), '\n'.join(p[i].data)) + file_entry = (join(base_path, arg), '\n'.join(p[i].data)) if p[i].id == 'file': files.append(file_entry) elif p[i].id == 'outfile': @@ -66,14 +70,14 @@ def parse_test_cases( # Use a custom source file for the std module. arg = p[i].arg assert arg is not None - mpath = os.path.join(os.path.dirname(path), arg) + mpath = join(os.path.dirname(path), arg) if p[i].id == 'builtins': fnam = 'builtins.pyi' else: # Python 2 fnam = '__builtin__.pyi' with open(mpath) as f: - files.append((os.path.join(base_path, fnam), f.read())) + files.append((join(base_path, fnam), f.read())) elif p[i].id == 'stale': arg = p[i].arg if arg is None: @@ -119,8 +123,6 @@ def parse_test_cases( input = expand_includes(p[i0].data, include_path) expand_errors(input, tcout, 'main') for file_path, contents in files: - if native_sep and os.path.sep == '\\': - file_path = fix_win_path(file_path) expand_errors(contents.split('\n'), tcout, file_path) lastline = p[i].line if i < len(p) else p[i - 1].line + 9999 tc = DataDrivenTestCase(p[i0].arg, input, tcout, tcout2, path, From c12a9fd6d44bf1148c0e864dcfb96a8cd52d37f0 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 17 Feb 2017 11:37:38 -0800 Subject: [PATCH 12/17] It is simpler to have OverloadPart be a union --- mypy/nodes.py | 20 +++++++------------- mypy/parse.py | 15 ++++++++------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/mypy/nodes.py b/mypy/nodes.py index 5778186ae809..1eaa2d528270 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -368,6 +368,9 @@ def fullname(self) -> str: return self._fullname +OverloadPart = Union['FuncDef', 'Decorator'] + + class OverloadedFuncDef(FuncBase, SymbolNode, Statement): """A logical node representing all the variants of an overloaded function. @@ -402,10 +405,10 @@ def serialize(self) -> JsonDict: def deserialize(cls, data: JsonDict) -> 'OverloadedFuncDef': assert data['.class'] == 'OverloadedFuncDef' res = OverloadedFuncDef([ - cast(OverloadPart, OverloadPart.deserialize(d)) + cast(OverloadPart, SymbolNode.deserialize(d)) for d in data['items']]) if data.get('impl') is not None: - res.impl = cast(OverloadPart, OverloadPart.deserialize(data['impl'])) + res.impl = cast(OverloadPart, SymbolNode.deserialize(data['impl'])) if data.get('type') is not None: res.type = mypy.types.Type.deserialize(data['type']) res._fullname = data['fullname'] @@ -512,16 +515,7 @@ def is_dynamic(self) -> bool: return self.type is None -class OverloadPart(SymbolNode, Statement): - """Interface class for statments that can be part of an OverloadedFuncDef""" - - is_overload = False - - @abstractmethod - def get_body(self) -> Optional['Block']: pass - - -class FuncDef(FuncItem, OverloadPart, Statement): +class FuncDef(FuncItem, SymbolNode, Statement): """Function definition. This is a non-lambda function defined using 'def'. @@ -592,7 +586,7 @@ def deserialize(cls, data: JsonDict) -> 'FuncDef': return ret -class Decorator(OverloadPart, Statement): +class Decorator(SymbolNode, Statement): """A decorated function. A single Decorator object can include any number of function decorators. diff --git a/mypy/parse.py b/mypy/parse.py index be7beb4bc3c0..cb5f9065511c 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -29,7 +29,7 @@ UnaryExpr, FuncExpr, PrintStmt, ImportBase, ComparisonExpr, StarExpr, YieldFromExpr, NonlocalDecl, DictionaryComprehension, SetComprehension, ComplexExpr, EllipsisExpr, YieldExpr, ExecStmt, Argument, - BackquoteExpr, OverloadPart + BackquoteExpr ) from mypy import defaults from mypy import nodes @@ -902,14 +902,15 @@ def parse_block(self, allow_type: bool = False) -> Tuple[Block, Type]: return node, type def try_combine_overloads(self, s: Statement, stmt: List[Statement]) -> bool: - if isinstance(s, OverloadPart) and stmt: + if isinstance(s, (FuncDef, Decorator)) and stmt: n = s.name() - if (isinstance(stmt[-1], OverloadPart) - and stmt[-1].name() == n): - stmt[-1] = OverloadedFuncDef([stmt[-1], s]) + last = stmt[-1] + if (isinstance(last, (FuncDef, Decorator)) + and last.name() == n): + stmt[-1] = OverloadedFuncDef([last, s]) return True - elif isinstance(stmt[-1], OverloadedFuncDef) and stmt[-1].name() == n: - stmt[-1].items.append(s) + elif isinstance(last, OverloadedFuncDef) and last.name() == n: + last.items.append(s) return True return False From 04ce906a0b5b4db96111e83f225c9a6a925a28df Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 15 Mar 2017 11:51:51 -0700 Subject: [PATCH 13/17] Many of Guidos comments --- mypy/build.py | 3 +- mypy/checker.py | 6 +- mypy/fastparse.py | 9 +- mypy/fastparse2.py | 9 +- mypy/nodes.py | 12 ++- mypy/semanal.py | 82 +++++++++-------- test-data/unit/check-classes.test | 2 + test-data/unit/check-overloading.test | 121 +++++++++++++++++++++++++- test-data/unit/check-unsupported.test | 2 +- test-data/unit/semanal-errors.test | 8 +- typeshed | 2 +- 11 files changed, 184 insertions(+), 72 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index fb6bc34f40b0..8e7f78a9e8ef 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -1397,7 +1397,8 @@ def parse_file(self) -> None: # this before processing imports, since this may mark some # import statements as unreachable. first = FirstPass(manager.semantic_analyzer) - first.visit_file(self.tree, self.xpath, self.id, self.options) + with self.wrap_context(): + first.visit_file(self.tree, self.xpath, self.id, self.options) # Initialize module symbol table, which was populated by the # semantic analyzer. diff --git a/mypy/checker.py b/mypy/checker.py index ab4080d0ffbb..d8d87116a812 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -308,7 +308,7 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: assert False, "Impl isn't the right type" # This can happen if we've got an overload with a different # decorator too -- we gave up on the types. - if impl_type is None or sig1 is None: + if impl_type is None or isinstance(impl_type, AnyType) or sig1 is None: return assert isinstance(impl_type, CallableType) @@ -2140,9 +2140,7 @@ def visit_decorator(self, e: Decorator) -> None: e.func.accept(self) sig = self.function_type(e.func) # type: Type # Process decorators from the inside out. - for i in range(len(e.decorators)): - n = len(e.decorators) - 1 - i - d = e.decorators[n] + for d in reversed(e.decorators): if isinstance(d, NameExpr) and d.fullname == 'typing.overload': self.fail('Single overload definition, multiple required', e) continue diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 10c4343ab1cc..a40ff68c7bdc 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -228,15 +228,10 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: current_overload = [] # type: List[OverloadPart] current_overload_name = None for stmt in stmts: - if (isinstance(stmt, Decorator) + if (current_overload_name is not None + and isinstance(stmt, (Decorator, FuncDef)) and stmt.name() == current_overload_name): current_overload.append(stmt) - elif (isinstance(stmt, FuncDef) - and stmt.name() == current_overload_name - and stmt.name() is not None): - ret.append(OverloadedFuncDef(current_overload + [stmt])) - current_overload = [] - current_overload_name = None else: if len(current_overload) == 1: ret.append(current_overload[0]) diff --git a/mypy/fastparse2.py b/mypy/fastparse2.py index 19f9652b0f6d..602f5d57e4d7 100644 --- a/mypy/fastparse2.py +++ b/mypy/fastparse2.py @@ -230,15 +230,10 @@ def fix_function_overloads(self, stmts: List[Statement]) -> List[Statement]: current_overload = [] # type: List[OverloadPart] current_overload_name = None for stmt in stmts: - if (isinstance(stmt, Decorator) + if (current_overload_name is not None + and isinstance(stmt, (Decorator, FuncDef)) and stmt.name() == current_overload_name): current_overload.append(stmt) - elif (isinstance(stmt, FuncDef) - and stmt.name() == current_overload_name - and stmt.name() is not None): - ret.append(OverloadedFuncDef(current_overload + [stmt])) - current_overload = [] - current_overload_name = None else: if len(current_overload) == 1: ret.append(current_overload[0]) diff --git a/mypy/nodes.py b/mypy/nodes.py index 26d63d65660f..0b15cdcddef9 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -376,10 +376,14 @@ def fullname(self) -> str: class OverloadedFuncDef(FuncBase, SymbolNode, Statement): - """A logical node representing all the variants of an overloaded function. + """A logical node representing all the variants of a multi-declaration function. + + A multi-declaration function is often an @overload, but can also be a + @property with a setter and a/or a deleter. This node has no explicit representation in the source program. Overloaded variants must be consecutive in the source file. + """ items = None # type: List[OverloadPart] @@ -549,9 +553,6 @@ def name(self) -> str: def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_func_def(self) - def get_body(self) -> Optional['Block']: - return self.body - def serialize(self) -> JsonDict: # We're deliberating omitting arguments and storing only arg_names and # arg_kinds for space-saving reasons (arguments is not used in later @@ -613,9 +614,6 @@ def __init__(self, func: FuncDef, decorators: List[Expression], def name(self) -> str: return self.func.name() - def get_body(self) -> Optional['Block']: - return self.func.body - def fullname(self) -> str: return self.func.fullname() diff --git a/mypy/semanal.py b/mypy/semanal.py index 1e541f1882de..8eec5e0cb639 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -432,51 +432,57 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: # Decide whether to analyze this as a property or an overload. If an # overload, and we're outside a stub, find the impl and set it. Remove # the impl from the item list, it's special. - t = [] # type: List[CallableType] + types = [] # type: List[CallableType] non_overload_indexes = [] - for i, item in enumerate(defn.items): - # TODO support decorated overloaded functions properly - item.is_overload = True - item.accept(self) - if isinstance(item, Decorator): - item.func.is_overload = True - callable = function_type(item.func, self.builtin_type('builtins.function')) - assert isinstance(callable, CallableType) - if item.func.is_property and i == 0: - # This defines a property, probably with a setter and/or - # deleter. - t.append(callable) - self.analyze_property_with_multi_part_definition(defn) - break - elif not any(refers_to_fullname(dec, 'typing.overload') - for dec in item.decorators): + # See if the first item is a property (and not an overload) + first_item = defn.items[0] + first_item.is_overload = True + first_item.accept(self) + + if isinstance(first_item, Decorator) and first_item.func.is_property: + first_item.func.is_overload = True + self.analyze_property_with_multi_part_definition(defn) + typ = function_type(first_item.func, self.builtin_type('builtins.function')) + assert isinstance(typ, CallableType) + types = [typ] + else: + for i, item in enumerate(defn.items): + if i != 0: + # The first item was already visited + item.is_overload = True + item.accept(self) + # TODO support decorated overloaded functions properly + if isinstance(item, Decorator): + item.func.is_overload = True + callable = function_type(item.func, self.builtin_type('builtins.function')) + assert isinstance(callable, CallableType) + if not any(refers_to_fullname(dec, 'typing.overload') + for dec in item.decorators): + if i == len(defn.items) - 1 and not self.is_stub_file: + # Last item outside a stub is impl + defn.impl = item + else: + # Oops it wasn't an overload after all. A clear error + # will vary based on where in the list it is, record + # that. + non_overload_indexes.append(i) + else: + types.append(callable) + elif isinstance(item, FuncDef): if i == len(defn.items) - 1 and not self.is_stub_file: - # Last item outside a stub is impl defn.impl = item else: - # Oops it wasn't an overload after all. A clear error - # will vary based on where in the list it is, record - # that. non_overload_indexes.append(i) - else: - t.append(callable) - elif isinstance(item, FuncDef): - if i == len(defn.items) - 1 and not self.is_stub_file: - defn.impl = item - else: - non_overload_indexes.append(i) - else: - # No break, so it was an overload. if non_overload_indexes: - if t: + if types: # Some of them were overloads, but not all. for idx in non_overload_indexes: if self.is_stub_file: - self.fail("Implementations of overloaded functions " - "not allowed in stub files", defn.items[idx]) + self.fail("An implementation for an overloaded function " + "is not allowed in a stub file", defn.items[idx]) else: - self.fail("Implementations of overloaded functions " + self.fail("The implementation for an overloaded function " "must come last", defn.items[idx]) else: for idx in non_overload_indexes[1:]: @@ -494,11 +500,11 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: elif not self.is_stub_file and not non_overload_indexes: self.fail( - "Overload outside a stub must have implementation", + "An overloaded function outside a stub file must have an implementation", defn) - if t: - defn.type = Overloaded(t) + if types: + defn.type = Overloaded(types) defn.type.line = defn.line if self.is_class_scope(): @@ -3279,6 +3285,8 @@ def visit_overloaded_func_def(self, func: OverloadedFuncDef) -> None: sem.errors.push_function(func.name()) sem.enter() impl.func.body.accept(self) + else: + assert False, "Implementation of an overload needs to be FuncDef or Decorator" sem.leave() sem.errors.pop_function() sem.function_stack.pop() diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 010967e8a4d0..a9d4ce693872 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -925,6 +925,8 @@ a = A() a.f = a.f a.f.x # E: "int" has no attribute "x" a.f = '' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +a.f = 1 +reveal_type(a.f) # E: Revealed type is 'builtins.int' [builtins fixtures/property.pyi] [case testPropertyWithDeleterButNoSetter] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 464cb0b661f1..64791a3728cd 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -17,6 +17,121 @@ class A: pass class B: pass [builtins fixtures/isinstance.pyi] +[case testOverloadNeedsImplementation] +from typing import overload, Any +@overload # E: An overloaded function outside a stub file must have an implementation +def f(x: 'A') -> 'B': ... +@overload +def f(x: 'B') -> 'A': ... + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + +[case testSingleOverloadNoImplementation] +from typing import overload, Any +@overload # E: Single overload definition, multiple required +def f(x: 'A') -> 'B': ... + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + + +[case testOverloadByAnyOtherName] +from typing import overload as rose +from typing import Any +@rose +def f(x: 'A') -> 'B': ... +@rose +def f(x: 'B') -> 'A': ... + +def f(x: Any) -> Any: + pass + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + +[case testTypeCheckOverloadWithDecoratedImplementation] +from typing import overload, Any + +def deco(fun): ... + +@overload +def f(x: 'A') -> 'B': ... +@overload +def f(x: 'B') -> 'A': ... + +@deco +def f(x: Any) -> Any: + pass + +reveal_type(f(A())) # E: Revealed type is '__main__.B' +reveal_type(f(B())) # E: Revealed type is '__main__.A' + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + +[case testOverloadDecoratedImplementationNotLast] +from typing import overload, Any + +def deco(fun): ... + +@overload +def f(x: 'A') -> 'B': ... + +@deco # E: The implementation for an overloaded function must come last +def f(x: Any) -> Any: + pass + +@overload +def f(x: 'B') -> 'A': ... + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + +[case testOverloadImplementationNotLast] +from typing import overload, Any + +@overload +def f(x: 'A') -> 'B': ... + +def f(x: Any) -> Any: # E: The implementation for an overloaded function must come last + pass + +@overload +def f(x: 'B') -> 'A': ... + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + +[case testDecoratedRedefinitionIsNotOverload] +from typing import overload, Any + +def deco(fun): ... + +@deco +def f(x: 'A') -> 'B': ... +@deco # E: Name 'f' already defined +def f(x: 'B') -> 'A': ... +@deco # E: Name 'f' already defined +def f(x: Any) -> Any: ... + +class A: pass +class B: pass +[builtins fixtures/isinstance.pyi] + + [case testTypeCheckOverloadWithImplementationPy2] # flags: --python-version 2.7 @@ -898,7 +1013,7 @@ def f(a: int) -> None: pass def f(a: int) -> None: pass [out] tmp/foo.pyi:2: error: Single overload definition, multiple required -tmp/foo.pyi:4: error: Implementations of overloaded functions not allowed in stub files +tmp/foo.pyi:4: error: An implementation for an overloaded function is not allowed in a stub file [case testSingleOverload2] @@ -922,8 +1037,8 @@ def f(a: int) -> None: pass @overload def f(a: str) -> None: pass [out] -tmp/foo.pyi:5: error: Name 'f' already defined tmp/foo.pyi:2: error: Single overload definition, multiple required +tmp/foo.pyi:5: error: Name 'f' already defined tmp/foo.pyi:5: error: Single overload definition, multiple required [case testNonconsecutiveOverloadsMissingFirstOverload] @@ -947,8 +1062,8 @@ def f(a: int) -> None: pass 1 def f(a: str) -> None: pass [out] -tmp/foo.pyi:5: error: Name 'f' already defined tmp/foo.pyi:2: error: Single overload definition, multiple required +tmp/foo.pyi:5: error: Name 'f' already defined [case testOverloadTuple] from foo import * diff --git a/test-data/unit/check-unsupported.test b/test-data/unit/check-unsupported.test index ea3b50f7e776..3406d57d845b 100644 --- a/test-data/unit/check-unsupported.test +++ b/test-data/unit/check-unsupported.test @@ -13,5 +13,5 @@ def g(): pass @d # E def g(x): pass [out] -tmp/foo.pyi:7: error: Name 'g' already defined tmp/foo.pyi:5: error: Name 'f' already defined +tmp/foo.pyi:7: error: Name 'g' already defined diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index a1fca13cc73b..d21c0532ff74 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -652,7 +652,7 @@ def f() -> None: x = 1 def p(): pass # fail [out] -main:3: error: Overload outside a stub must have implementation +main:3: error: An overloaded function outside a stub file must have an implementation main:8: error: Name 'p' already defined [case testNestedFunctionInMethod] @@ -1062,7 +1062,7 @@ def f(x: 'int['): pass # E: syntax error in type comment [case testInconsistentOverload] from typing import overload def dec(x): pass -@dec # E: Implementations of overloaded functions must come last +@dec # E: The implementation for an overloaded function must come last def f(): pass @overload def f(): pass @@ -1071,7 +1071,7 @@ def f(): pass [case testInconsistentOverload2] from typing import overload def dec(x): pass -@dec # E: Implementations of overloaded functions must come last +@dec # E: The implementation for an overloaded function must come last def f(): pass @overload def f(): pass @@ -1154,7 +1154,7 @@ class A: [case testOverloadedProperty2] from typing import overload class A: - @overload # E: Overload outside a stub must have implementation + @overload # E: An overloaded function outside a stub file must have an implementation def f(self) -> int: pass @property # E: Decorated property not supported @overload diff --git a/typeshed b/typeshed index 43d7747f59a2..bd5b33f3b18f 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 43d7747f59a2763db4333b8f2197730b3e417be2 +Subproject commit bd5b33f3b18fc8811ad2403d35f21ad6aae94b62 From a191620d8c3408f13e373c739b52f3329d8df820 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 23 Mar 2017 16:14:02 -0700 Subject: [PATCH 14/17] Test implementation with type variables, and fix the bugs --- mypy/checker.py | 12 +++++-- mypy/nodes.py | 1 - test-data/unit/check-overloading.test | 47 ++++++++++++++++++++++++++- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index d8d87116a812..8c793fadfa94 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -36,7 +36,8 @@ from mypy import messages from mypy.subtypes import ( is_subtype, is_equivalent, is_proper_subtype, is_more_precise, - restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_subtype + restrict_subtype_away, is_subtype_ignoring_tvars, is_callable_subtype, + unify_generic_callable, ) from mypy.maptype import map_instance_to_supertype from mypy.typevars import fill_typevars, has_no_typevars @@ -315,7 +316,14 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: assert isinstance(sig1, CallableType) if not is_callable_subtype(impl_type, sig1, ignore_return=True): self.msg.overloaded_signatures_arg_specific(i + 1, defn.impl) - if not is_subtype(sig1.ret_type, impl_type.ret_type): + impl_type_subst = impl_type + if impl_type.variables: + impl_type_subst = unify_generic_callable(impl_type, sig1, ignore_return=False) + if impl_type_subst is None: + self.fail("Type variable mismatch between " + + "overload signature {} and implementation".format(i+1), defn.impl) + return + if not is_subtype(sig1.ret_type, impl_type_subst.ret_type): self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl) # Here's the scoop about generators and coroutines. diff --git a/mypy/nodes.py b/mypy/nodes.py index 0b15cdcddef9..d90dadba7485 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -383,7 +383,6 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement): This node has no explicit representation in the source program. Overloaded variants must be consecutive in the source file. - """ items = None # type: List[OverloadPart] diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 64791a3728cd..9c48a3169932 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -40,7 +40,6 @@ class A: pass class B: pass [builtins fixtures/isinstance.pyi] - [case testOverloadByAnyOtherName] from typing import overload as rose from typing import Any @@ -226,6 +225,52 @@ reveal_type(f(B())) # E: Revealed type is '__main__.A' [builtins fixtures/isinstance.pyi] +[case testTypeCheckOverloadWithImplTypeVar] +from typing import overload, Any, TypeVar + +T = TypeVar('T') + +class A: pass +class B: pass + +a = A() + +@overload +def f(x: 'A') -> 'A': ... +@overload +def f(x: 'B') -> 'B': ... + +def f(x: T) -> T: + ... + +reveal_type(f(A())) # E: Revealed type is '__main__.A' +reveal_type(f(B())) # E: Revealed type is '__main__.B' + +[builtins fixtures/isinstance.pyi] + +[case testTypeCheckOverloadWithImplTypeVarProblems] +from typing import overload, Any, TypeVar + +T = TypeVar('T', bound='A') + +class A: pass +class B: pass + +a = A() + +@overload +def f(x: 'A') -> 'A': ... +@overload +def f(x: 'B') -> 'B': ... + +def f(x: Any) -> T: # E: Type variable mismatch between overload signature 2 and implementation + ... + +reveal_type(f(A())) # E: Revealed type is '__main__.A' +reveal_type(f(B())) # E: Revealed type is '__main__.B' + +[builtins fixtures/isinstance.pyi] + [case testTypeCheckOverloadedFunctionBody] from foo import * [file foo.pyi] From b24d2ec0e66cc7f4987b3bd4652ed715d2312aca Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Thu, 23 Mar 2017 16:19:44 -0700 Subject: [PATCH 15/17] lint --- mypy/checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 8c793fadfa94..7033eda5a23b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -321,7 +321,8 @@ def check_overlapping_overloads(self, defn: OverloadedFuncDef) -> None: impl_type_subst = unify_generic_callable(impl_type, sig1, ignore_return=False) if impl_type_subst is None: self.fail("Type variable mismatch between " + - "overload signature {} and implementation".format(i+1), defn.impl) + "overload signature {} and implementation".format(i + 1), + defn.impl) return if not is_subtype(sig1.ret_type, impl_type_subst.ret_type): self.msg.overloaded_signatures_ret_specific(i + 1, defn.impl) From 9fa66f427288bb4e3c03b67f37fefbb9713cb06f Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 24 Mar 2017 18:37:59 -0700 Subject: [PATCH 16/17] remove extra blank linkes --- test-data/unit/check-overloading.test | 2 -- 1 file changed, 2 deletions(-) diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 9c48a3169932..ed5275d7d3b9 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -130,7 +130,6 @@ class A: pass class B: pass [builtins fixtures/isinstance.pyi] - [case testTypeCheckOverloadWithImplementationPy2] # flags: --python-version 2.7 @@ -1060,7 +1059,6 @@ def f(a: int) -> None: pass tmp/foo.pyi:2: error: Single overload definition, multiple required tmp/foo.pyi:4: error: An implementation for an overloaded function is not allowed in a stub file - [case testSingleOverload2] from foo import * [file foo.pyi] From c6c6d0312c80db6df1a3295f66662d81087304d4 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 24 Mar 2017 18:47:23 -0700 Subject: [PATCH 17/17] Put is_overload in correct else clause --- mypy/semanal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 8eec5e0cb639..6fb19185c6d1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -454,7 +454,6 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: item.accept(self) # TODO support decorated overloaded functions properly if isinstance(item, Decorator): - item.func.is_overload = True callable = function_type(item.func, self.builtin_type('builtins.function')) assert isinstance(callable, CallableType) if not any(refers_to_fullname(dec, 'typing.overload') @@ -468,6 +467,7 @@ def visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: # that. non_overload_indexes.append(i) else: + item.func.is_overload = True types.append(callable) elif isinstance(item, FuncDef): if i == len(defn.items) - 1 and not self.is_stub_file: