From edfff2242e30661b048b496a1ff9054e59e89b1c Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Fri, 4 Nov 2016 16:03:32 -0700 Subject: [PATCH 1/6] Parse the Arg bits of callables --- extensions/mypy_extensions.py | 8 ++ mypy/erasetype.py | 4 +- mypy/expandtype.py | 4 +- mypy/exprtotype.py | 41 +++++++++- mypy/fastparse.py | 4 +- mypy/fixup.py | 4 +- mypy/indirection.py | 2 +- mypy/join.py | 4 +- mypy/meet.py | 4 +- mypy/nodes.py | 2 + mypy/parse.py | 2 + mypy/parsetype.py | 87 +++++++++++++++++++-- mypy/sametypes.py | 4 +- mypy/semanal.py | 6 +- mypy/subtypes.py | 4 +- mypy/typeanal.py | 10 +-- mypy/types.py | 48 ++++++++---- test-data/unit/check-functions.test | 12 +++ test-data/unit/check-optional.test | 2 +- test-data/unit/fixtures/dict.pyi | 3 + test-data/unit/lib-stub/mypy_extensions.pyi | 7 +- test-data/unit/parse.test | 8 +- test-data/unit/semanal-errors.test | 2 +- 23 files changed, 210 insertions(+), 62 deletions(-) mode change 100644 => 120000 test-data/unit/lib-stub/mypy_extensions.pyi diff --git a/extensions/mypy_extensions.py b/extensions/mypy_extensions.py index db66f586f051..ba645b58b82a 100644 --- a/extensions/mypy_extensions.py +++ b/extensions/mypy_extensions.py @@ -5,6 +5,8 @@ from mypy_extensions import TypedDict """ +from typing import Any + # NOTE: This module must support Python 2.7 in addition to Python 3.x @@ -20,3 +22,9 @@ def new_dict(*args, **kwargs): new_dict.__name__ = typename new_dict.__supertype__ = dict return new_dict + +class Arg(object): + def __init__(name=None, typ=Any, keyword_only=False): + self.name = name + self.typ = typ + self.named_only = named_only diff --git a/mypy/erasetype.py b/mypy/erasetype.py index 3f53f75219b2..8c57e5f0cce5 100644 --- a/mypy/erasetype.py +++ b/mypy/erasetype.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, TypeVisitor, UnboundType, ErrorType, AnyType, Void, NoneTyp, TypeVarId, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, ErasedType, - PartialType, DeletedType, TypeTranslator, TypeList, UninhabitedType, TypeType + PartialType, DeletedType, TypeTranslator, ArgumentList, UninhabitedType, TypeType ) from mypy import experiments @@ -32,7 +32,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: def visit_error_type(self, t: ErrorType) -> Type: return t - def visit_type_list(self, t: TypeList) -> Type: + def visit_type_list(self, t: ArgumentList) -> Type: assert False, 'Not supported' def visit_any(self, t: AnyType) -> Type: diff --git a/mypy/expandtype.py b/mypy/expandtype.py index c1ff8088b3c5..7e682ee174ee 100644 --- a/mypy/expandtype.py +++ b/mypy/expandtype.py @@ -2,7 +2,7 @@ from mypy.types import ( Type, Instance, CallableType, TypeVisitor, UnboundType, ErrorType, AnyType, - Void, NoneTyp, TypeVarType, Overloaded, TupleType, UnionType, ErasedType, TypeList, + Void, NoneTyp, TypeVarType, Overloaded, TupleType, UnionType, ErasedType, ArgumentList, PartialType, DeletedType, UninhabitedType, TypeType, TypeVarId ) @@ -42,7 +42,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: def visit_error_type(self, t: ErrorType) -> Type: return t - def visit_type_list(self, t: TypeList) -> Type: + def visit_type_list(self, t: ArgumentList) -> Type: assert False, 'Not supported' def visit_any(self, t: AnyType) -> Type: diff --git a/mypy/exprtotype.py b/mypy/exprtotype.py index 764c716b1f96..2a40984b6833 100644 --- a/mypy/exprtotype.py +++ b/mypy/exprtotype.py @@ -2,10 +2,11 @@ from mypy.nodes import ( Expression, NameExpr, MemberExpr, IndexExpr, TupleExpr, - ListExpr, StrExpr, BytesExpr, EllipsisExpr + ListExpr, StrExpr, BytesExpr, EllipsisExpr, CallExpr, + ARG_POS, ARG_NAMED, ) from mypy.parsetype import parse_str_as_type, TypeParseError -from mypy.types import Type, UnboundType, TypeList, EllipsisType +from mypy.types import Type, UnboundType, ArgumentList, EllipsisType, AnyType class TypeTranslationError(Exception): @@ -15,7 +16,7 @@ class TypeTranslationError(Exception): def expr_to_unanalyzed_type(expr: Expression) -> Type: """Translate an expression to the corresponding type. - The result is not semantically analyzed. It can be UnboundType or TypeList. + The result is not semantically analyzed. It can be UnboundType or ArgumentList. Raise TypeTranslationError if the expression cannot represent a type. """ if isinstance(expr, NameExpr): @@ -41,7 +42,39 @@ def expr_to_unanalyzed_type(expr: Expression) -> Type: else: raise TypeTranslationError() elif isinstance(expr, ListExpr): - return TypeList([expr_to_unanalyzed_type(t) for t in expr.items], + types = [] # type: List[Type] + names = [] # type: List[Optional[str]] + kinds = [] # type: List[int] + for it in expr.items: + if isinstance(expr_to_unanalyzed_type(it), CallExpr): + if not isinstance(it.callee, NameExpr): + raise TypeTranslationError() + arg_const = it.callee.name + if arg_const == 'Arg': + if len(it.args) > 0: + name = it.args[0] + if not isinstance(name, StrLit): + raise TypeTranslationError() + names.append(name.parsed()) + else: + names.append(None) + + if len(it.args) > 1: + typ = it.args[1] + types.append(expr_to_unanalyzed_type(typ)) + else: + types.append(AnyType) + + if len(it.args) > 2: + kinds.append(ARG_NAMED) + else: + kinds.append(ARG_POS) + + else: + types.append(expr_to_unanalyzed_type(it)) + names.append(None) + kinds.append(ARG_POS) + return ArgumentList(types, names, kinds, line=expr.line) elif isinstance(expr, (StrExpr, BytesExpr)): # Parse string literal type. diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 8b6eb3d28b67..942f352f05a9 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -18,7 +18,7 @@ ARG_POS, ARG_OPT, ARG_STAR, ARG_NAMED, ARG_STAR2 ) from mypy.types import ( - Type, CallableType, AnyType, UnboundType, TupleType, TypeList, EllipsisType, + Type, CallableType, AnyType, UnboundType, TupleType, ArgumentList, EllipsisType, ) from mypy import defaults from mypy import experiments @@ -904,7 +904,7 @@ def visit_Ellipsis(self, n: ast35.Ellipsis) -> Type: # List(expr* elts, expr_context ctx) def visit_List(self, n: ast35.List) -> Type: - return TypeList(self.translate_expr_list(n.elts), line=self.line) + return ArgumentList(self.translate_expr_list(n.elts), line=self.line) class TypeCommentParseError(Exception): diff --git a/mypy/fixup.py b/mypy/fixup.py index 147238db8e43..e3b18a50277d 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -7,7 +7,7 @@ TypeVarExpr, ClassDef, LDEF, MDEF, GDEF) from mypy.types import (CallableType, EllipsisType, Instance, Overloaded, TupleType, - TypeList, TypeVarType, UnboundType, UnionType, TypeVisitor, + ArgumentList, TypeVarType, UnboundType, UnionType, TypeVisitor, TypeType) from mypy.visitor import NodeVisitor @@ -192,7 +192,7 @@ def visit_tuple_type(self, tt: TupleType) -> None: if tt.fallback is not None: tt.fallback.accept(self) - def visit_type_list(self, tl: TypeList) -> None: + def visit_type_list(self, tl: ArgumentList) -> None: for t in tl.items: t.accept(self) diff --git a/mypy/indirection.py b/mypy/indirection.py index 77c5a59e88e3..8baf253b452c 100644 --- a/mypy/indirection.py +++ b/mypy/indirection.py @@ -42,7 +42,7 @@ def _visit(self, *typs: types.Type) -> Set[str]: def visit_unbound_type(self, t: types.UnboundType) -> Set[str]: return self._visit(*t.args) - def visit_type_list(self, t: types.TypeList) -> Set[str]: + def visit_type_list(self, t: types.ArgumentList) -> Set[str]: return self._visit(*t.items) def visit_error_type(self, t: types.ErrorType) -> Set[str]: diff --git a/mypy/join.py b/mypy/join.py index 6d86106be281..801be4146872 100644 --- a/mypy/join.py +++ b/mypy/join.py @@ -4,7 +4,7 @@ from mypy.types import ( Type, AnyType, NoneTyp, Void, TypeVisitor, Instance, UnboundType, - ErrorType, TypeVarType, CallableType, TupleType, ErasedType, TypeList, + ErrorType, TypeVarType, CallableType, TupleType, ErasedType, ArgumentList, UnionType, FunctionLike, Overloaded, PartialType, DeletedType, UninhabitedType, TypeType, true_or_false ) @@ -114,7 +114,7 @@ def visit_union_type(self, t: UnionType) -> Type: def visit_error_type(self, t: ErrorType) -> Type: return t - def visit_type_list(self, t: TypeList) -> Type: + def visit_type_list(self, t: ArgumentList) -> Type: assert False, 'Not supported' def visit_any(self, t: AnyType) -> Type: diff --git a/mypy/meet.py b/mypy/meet.py index 18796ae2491c..2000ed4344c0 100644 --- a/mypy/meet.py +++ b/mypy/meet.py @@ -3,7 +3,7 @@ from mypy.join import is_similar_callables, combine_similar_callables from mypy.types import ( Type, AnyType, TypeVisitor, UnboundType, Void, ErrorType, NoneTyp, TypeVarType, - Instance, CallableType, TupleType, ErasedType, TypeList, UnionType, PartialType, + Instance, CallableType, TupleType, ErasedType, ArgumentList, UnionType, PartialType, DeletedType, UninhabitedType, TypeType ) from mypy.subtypes import is_subtype @@ -133,7 +133,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: def visit_error_type(self, t: ErrorType) -> Type: return t - def visit_type_list(self, t: TypeList) -> Type: + def visit_type_list(self, t: ArgumentList) -> Type: assert False, 'Not supported' def visit_any(self, t: AnyType) -> Type: diff --git a/mypy/nodes.py b/mypy/nodes.py index e8a6a573087a..e052e7dd553c 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1213,6 +1213,8 @@ def accept(self, visitor: NodeVisitor[T]) -> T: ARG_NAMED = 3 # type: int # **arg argument ARG_STAR2 = 4 # type: int +# In an argument list, keyword-only and also optional +ARG_NAMED_OPT = 5 class CallExpr(Expression): diff --git a/mypy/parse.py b/mypy/parse.py index 1c889f4430b3..bd2f79c570f4 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -3,6 +3,7 @@ Constructs a parse tree (abstract syntax tree) based on a string representing a source file. Performs only minimal semantic checks. """ +import traceback import re @@ -1896,6 +1897,7 @@ def parse_type(self) -> Type: try: typ, self.ind = parse_type(self.tok, self.ind) except TypeParseError as e: + traceback.print_stack() self.parse_error_at(e.token, reason=e.message) raise ParseError() return typ diff --git a/mypy/parsetype.py b/mypy/parsetype.py index 9c1923c3cff4..a8e8042e77e9 100644 --- a/mypy/parsetype.py +++ b/mypy/parsetype.py @@ -3,8 +3,8 @@ from typing import List, Tuple, Union, Optional from mypy.types import ( - Type, UnboundType, TupleType, TypeList, CallableType, StarType, - EllipsisType + Type, UnboundType, TupleType, ArgumentList, CallableType, StarType, + EllipsisType, AnyType ) from mypy.lex import Token, Name, StrLit, lex from mypy import nodes @@ -57,7 +57,7 @@ def parse_type(self) -> Type: if isinstance(t, Name): return self.parse_named_type() elif t.string == '[': - return self.parse_type_list() + return self.parse_argument_list() elif t.string == '*': return self.parse_star_type() elif t.string == '...': @@ -102,19 +102,89 @@ def parse_types(self) -> Type: type = TupleType(items, None, type.line, implicit=True) return type - def parse_type_list(self) -> TypeList: + def parse_argument_spec(self) -> Tuple[Type, Optional[str], int]: + current = self.current_token() + nxt = self.next_token() + # This is a small recreation of a subset of parsing a CallExpr; just + # enough to parse what happens in an arugment list. + # TODO: Doesn't handle an explicit name of None yet. + if isinstance(current, Name) and nxt is not None and nxt.string == '(': + arg_const = self.expect_type(Name).string + name = None # type: Optional[str] + typ = AnyType # type: Type + kind = nodes.ARG_POS + self.expect('(') + if arg_const == 'Arg': + kind = nodes.ARG_POS + if self.current_token_str() != ')': + name = self.expect_type(StrLit).parsed() + if self.current_token_str() != ')': + self.expect(',') + typ = self.parse_type() + if self.current_token_str() != ')': + self.expect(',') + self.expect('keyword_only') + self.expect('=') + if self.current_token_str() == 'True': + kind = nodes.ARG_NAMED + self.skip() + elif self.current_token_str() == 'False': + self.skip() + else: + self.parse_error() + elif arg_const == 'DefaultArg': + kind = nodes.ARG_OPT + if self.current_token_str() != ')': + name = self.expect_type(StrLit).parsed() + if self.current_token_str() != ')': + self.expect(',') + typ = self.parse_type() + if self.current_token_str() != ')': + self.expect(',') + self.expect('keyword_only') + self.expect('=') + if self.current_token_str() == 'True': + kind = nodes.ARG_NAMED_OPT + self.skip() + elif self.current_token_str() == 'False': + self.skip() + else: + self.parse_error() + elif arg_const == 'StarArg': + # Takes one type + kind = nodes.ARG_STAR + if self.current_token_str() != ')': + typ = self.parse_type() + elif arg_const == 'KwArg': + # Takes one type + kind = nodes.ARG_STAR2 + if self.current_token_str() != ')': + typ = self.parse_type() + else: + self.parse_error() + self.expect(')') + return typ, name, kind + else: + return self.parse_type(), None, nodes.ARG_POS + + def parse_argument_list(self) -> ArgumentList: """Parse type list [t, ...].""" lbracket = self.expect('[') commas = [] # type: List[Token] items = [] # type: List[Type] + names = [] # type: List[Optional[str]] + kinds = [] # type: List[int] while self.current_token_str() != ']': - t = self.parse_type() + t, name, kind = self.parse_argument_spec() items.append(t) + names.append(name) + kinds.append(kind) + if self.current_token_str() != ',': break commas.append(self.skip()) self.expect(']') - return TypeList(items, line=lbracket.line) + return ArgumentList(items, names, kinds, line=lbracket.line) def parse_named_type(self) -> Type: line = self.current_token().line @@ -178,6 +248,11 @@ def expect_type(self, typ: type) -> Token: def current_token(self) -> Token: return self.tok[self.ind] + def next_token(self) -> Optional[Token]: + if self.ind + 1>= len(self.tok): + return None + return self.tok[self.ind + 1] + def current_token_str(self) -> str: return self.current_token().string diff --git a/mypy/sametypes.py b/mypy/sametypes.py index 9e428ae60e15..fe52737b13ea 100644 --- a/mypy/sametypes.py +++ b/mypy/sametypes.py @@ -2,7 +2,7 @@ from mypy.types import ( Type, UnboundType, ErrorType, AnyType, NoneTyp, Void, TupleType, UnionType, CallableType, - TypeVarType, Instance, TypeVisitor, ErasedType, TypeList, Overloaded, PartialType, + TypeVarType, Instance, TypeVisitor, ErasedType, ArgumentList, Overloaded, PartialType, DeletedType, UninhabitedType, TypeType ) @@ -58,7 +58,7 @@ def visit_unbound_type(self, left: UnboundType) -> bool: def visit_error_type(self, left: ErrorType) -> bool: return False - def visit_type_list(self, t: TypeList) -> bool: + def visit_type_list(self, t: ArgumentList) -> bool: assert False, 'Not supported' def visit_any(self, left: AnyType) -> bool: diff --git a/mypy/semanal.py b/mypy/semanal.py index 999c847cceb9..d08ddf255561 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -71,7 +71,7 @@ from mypy.errors import Errors, report_internal_error from mypy.types import ( NoneTyp, CallableType, Overloaded, Instance, Type, TypeVarType, AnyType, - FunctionLike, UnboundType, TypeList, TypeVarDef, + FunctionLike, UnboundType, ArgumentList, TypeVarDef, TupleType, UnionType, StarType, EllipsisType, function_type) from mypy.nodes import implicit_module_attrs from mypy.typeanal import TypeAnalyser, TypeAnalyserPass3, analyze_type_alias @@ -396,8 +396,8 @@ def find_type_variables_in_type(self, type: Type) -> List[Tuple[str, TypeVarExpr result.append((name, node.node)) for arg in type.args: result.extend(self.find_type_variables_in_type(arg)) - elif isinstance(type, TypeList): - for item in type.items: + elif isinstance(type, ArgumentList): + for item in type.types: result.extend(self.find_type_variables_in_type(item)) elif isinstance(type, UnionType): for item in type.items: diff --git a/mypy/subtypes.py b/mypy/subtypes.py index e40a76514df4..c395ac9fc835 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -3,7 +3,7 @@ from mypy.types import ( Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, Instance, TypeVarType, CallableType, TupleType, UnionType, Overloaded, - ErasedType, TypeList, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance + ErasedType, ArgumentList, PartialType, DeletedType, UninhabitedType, TypeType, is_named_instance ) import mypy.applytype import mypy.constraints @@ -91,7 +91,7 @@ def visit_unbound_type(self, left: UnboundType) -> bool: def visit_error_type(self, left: ErrorType) -> bool: return False - def visit_type_list(self, t: TypeList) -> bool: + def visit_type_list(self, t: ArgumentList) -> bool: assert False, 'Not supported' def visit_any(self, left: AnyType) -> bool: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index d2f6fbca0cad..16af54766e74 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -4,7 +4,7 @@ from mypy.types import ( Type, UnboundType, TypeVarType, TupleType, UnionType, Instance, - AnyType, CallableType, Void, NoneTyp, DeletedType, TypeList, TypeVarDef, TypeVisitor, + AnyType, CallableType, Void, NoneTyp, DeletedType, ArgumentList, TypeVarDef, TypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType ) from mypy.nodes import ( @@ -196,7 +196,7 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> Type: def visit_deleted_type(self, t: DeletedType) -> Type: return t - def visit_type_list(self, t: TypeList) -> Type: + def visit_type_list(self, t: ArgumentList) -> Type: self.fail('Invalid type', t) return AnyType() @@ -251,9 +251,9 @@ def analyze_callable_type(self, t: UnboundType) -> Type: is_ellipsis_args=True) elif len(t.args) == 2: ret_type = t.args[1].accept(self) - if isinstance(t.args[0], TypeList): + if isinstance(t.args[0], ArgumentList): # Callable[[ARG, ...], RET] (ordinary callable type) - args = t.args[0].items + args = t.args[0].types return CallableType(self.anal_array(args), [nodes.ARG_POS] * len(args), [None] * len(args), @@ -411,7 +411,7 @@ def visit_uninhabited_type(self, t: UninhabitedType) -> None: def visit_deleted_type(self, t: DeletedType) -> None: pass - def visit_type_list(self, t: TypeList) -> None: + def visit_type_list(self, t: ArgumentList) -> None: self.fail('Invalid type', t) def visit_type_var(self, t: TypeVarType) -> None: diff --git a/mypy/types.py b/mypy/types.py index 9c80b590cd38..920bc48cf4b1 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -225,8 +225,8 @@ def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_error_type(self) -class TypeList(Type): - """A list of types [...]. +class ArgumentList(Type): + """Information about argument types and names [...]. This is only used for the arguments of a Callable type, i.e. for [arg, ...] in Callable[[arg, ...], ret]. This is not a real type @@ -234,23 +234,41 @@ class TypeList(Type): """ items = None # type: List[Type] + names = None # type: List[Optional[str]] + kinds = None # type: List[int] - def __init__(self, items: List[Type], line: int = -1, column: int = -1) -> None: + def __init__(self, + types: List[Type], + names: List[Optional[str]], + kinds: List[int], + line: int = -1, + column: int = -1) -> None: super().__init__(line, column) - self.items = items + self.types = types + self.names = names + self.kinds = kinds def accept(self, visitor: 'TypeVisitor[T]') -> T: return visitor.visit_type_list(self) def serialize(self) -> JsonDict: - return {'.class': 'TypeList', - 'items': [t.serialize() for t in self.items], - } + return {'.class': 'ArgumentList', + 'items': [t.serialize() for t in self.types], + 'names': self.names, + 'kinds': self.kinds, + } @classmethod - def deserialize(cls, data: JsonDict) -> 'TypeList': - assert data['.class'] == 'TypeList' - return TypeList([Type.deserialize(t) for t in data['items']]) + def deserialize(cls, data: JsonDict) -> 'ArgumentList': + assert data['.class'] == 'ArgumentList' or data['.class'] == 'TypeList' + types = [Type.deserialize(t) for t in data['items']] + names = cast(List[Optional[str]], data.get('names', [None]*len(types))) + kinds = cast(List[int], data.get('kinds', [nodes.ARG_POS]*len(types))) + return ArgumentList( + types=[Type.deserialize(t) for t in data['items']], + names=names, + kinds=kinds, + ) class AnyType(Type): @@ -1030,7 +1048,7 @@ def _notimplemented_helper(self, name: str) -> NotImplementedError: def visit_unbound_type(self, t: UnboundType) -> T: pass - def visit_type_list(self, t: TypeList) -> T: + def visit_type_list(self, t: ArgumentList) -> T: raise self._notimplemented_helper('type_list') def visit_error_type(self, t: ErrorType) -> T: @@ -1107,7 +1125,7 @@ class TypeTranslator(TypeVisitor[Type]): def visit_unbound_type(self, t: UnboundType) -> Type: return t - def visit_type_list(self, t: TypeList) -> Type: + def visit_type_list(self, t: ArgumentList) -> Type: return t def visit_error_type(self, t: ErrorType) -> Type: @@ -1198,8 +1216,8 @@ def visit_unbound_type(self, t: UnboundType)-> str: s += '[{}]'.format(self.list_str(t.args)) return s - def visit_type_list(self, t: TypeList) -> str: - return ''.format(self.list_str(t.items)) + def visit_type_list(self, t: ArgumentList) -> str: + return ''.format(self.list_str(t.items)) def visit_error_type(self, t: ErrorType) -> str: return '' @@ -1349,7 +1367,7 @@ def __init__(self, default: bool, strategy: int) -> None: def visit_unbound_type(self, t: UnboundType) -> bool: return self.default - def visit_type_list(self, t: TypeList) -> bool: + def visit_type_list(self, t: ArgumentList) -> bool: return self.default def visit_error_type(self, t: ErrorType) -> bool: diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index c95a49d92b85..c11202ee34d6 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1275,6 +1275,18 @@ f('x') # E: Argument 1 to "f" has incompatible type "str"; expected "int" g('x') g(1) # E: Argument 1 to "g" has incompatible type "int"; expected "str" +-- Callable with specific arg list +-- ------------------------------- + +[case testCallableWithNamedArg] +from typing import Callable +from mypy_extensions import Arg + +def a(f: Callable[[Arg('x', int)], int]): + f(x=4) + f(5) + +[builtins fixtures/dict.pyi] -- Callable[..., T] -- ---------------- diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index d33c4c8442ee..f9a63307ae92 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -358,7 +358,7 @@ x = f # type: Callable[[], None] from typing import Callable, Optional T = Optional[Callable[..., None]] -[case testAnyTypeInPartialTypeList] +[case testAnyTypeInPartialArgumentList] # flags: --check-untyped-defs def f(): ... diff --git a/test-data/unit/fixtures/dict.pyi b/test-data/unit/fixtures/dict.pyi index 5a7886439692..c79a21f7e6bc 100644 --- a/test-data/unit/fixtures/dict.pyi +++ b/test-data/unit/fixtures/dict.pyi @@ -32,3 +32,6 @@ class list(Iterable[T], Generic[T]): # needed by some test cases class tuple: pass class function: pass class float: pass +class bool: pass + +class ellipsis: pass \ No newline at end of file diff --git a/test-data/unit/lib-stub/mypy_extensions.pyi b/test-data/unit/lib-stub/mypy_extensions.pyi deleted file mode 100644 index 6c57954d8a81..000000000000 --- a/test-data/unit/lib-stub/mypy_extensions.pyi +++ /dev/null @@ -1,6 +0,0 @@ -from typing import Dict, Type, TypeVar - -T = TypeVar('T') - - -def TypedDict(typename: str, fields: Dict[str, Type[T]]) -> Type[dict]: ... diff --git a/test-data/unit/lib-stub/mypy_extensions.pyi b/test-data/unit/lib-stub/mypy_extensions.pyi new file mode 120000 index 000000000000..8eeafff7f90d --- /dev/null +++ b/test-data/unit/lib-stub/mypy_extensions.pyi @@ -0,0 +1 @@ +typeshed/third_party/2and3/mypy_extensions.pyi \ No newline at end of file diff --git a/test-data/unit/parse.test b/test-data/unit/parse.test index 40213a3408b0..b454be36ea8e 100644 --- a/test-data/unit/parse.test +++ b/test-data/unit/parse.test @@ -2408,7 +2408,7 @@ MypyFile:1( AssignmentStmt:1( NameExpr(f) NameExpr(None) - Callable?[, None?])) + Callable?[, None?])) [case testFunctionTypeWithArgument] f = None # type: Callable[[str], int] @@ -2417,7 +2417,7 @@ MypyFile:1( AssignmentStmt:1( NameExpr(f) NameExpr(None) - Callable?[, int?])) + Callable?[, int?])) [case testFunctionTypeWithTwoArguments] f = None # type: Callable[[a[b], x.y], List[int]] @@ -2426,7 +2426,7 @@ MypyFile:1( AssignmentStmt:1( NameExpr(f) NameExpr(None) - Callable?[, List?[int?]])) + Callable?[, List?[int?]])) [case testFunctionTypeWithExtraComma] def f(x: Callable[[str,], int]): pass @@ -2436,7 +2436,7 @@ MypyFile:1( f Args( Var(x)) - def (x: Callable?[, int?]) -> Any + def (x: Callable?[, int?]) -> Any Block:1( PassStmt:1()))) diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 2f3e05ea0a4f..04579a3c6d37 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -834,7 +834,7 @@ Any(str, None) # E: 'Any' expects 1 argument Any(arg=str) # E: 'Any' must be called with 1 positional argument [out] -[case testTypeListAsType] +[case testArgumentListAsType] def f(x:[int, str]) -> None: # E: Invalid type pass [out] From 1b47e0547ca6e8be0eab3afdc0d50335fd2a4eb6 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sun, 6 Nov 2016 11:55:18 -0800 Subject: [PATCH 2/6] Woo it works! --- mypy/typeanal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 16af54766e74..49adf8279010 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -255,8 +255,8 @@ def analyze_callable_type(self, t: UnboundType) -> Type: # Callable[[ARG, ...], RET] (ordinary callable type) args = t.args[0].types return CallableType(self.anal_array(args), - [nodes.ARG_POS] * len(args), - [None] * len(args), + t.args[0].kinds, + t.args[0].names, ret_type=ret_type, fallback=fallback) elif isinstance(t.args[0], EllipsisType): From fc772b7792802da366469e2572563a86d790851d Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Sun, 6 Nov 2016 21:53:01 -0800 Subject: [PATCH 3/6] All tests pass, including some ones of new functionality. I worry about fastparse though --- extensions/mypy_extensions.py | 6 ++ mypy/checker.py | 2 +- mypy/fastparse.py | 6 +- mypy/messages.py | 38 +++++++++- mypy/parse.py | 48 +++++++++---- mypy/parsetype.py | 103 +++++++++++++++++----------- mypy/subtypes.py | 10 ++- test-data/unit/check-classes.test | 4 +- test-data/unit/check-functions.test | 48 ++++++++++++- test-data/unit/check-modules.test | 2 +- test-data/unit/check-super.test | 2 +- test-data/unit/fixtures/tuple.pyi | 2 +- test-data/unit/lib-stub/typing.pyi | 3 +- 13 files changed, 210 insertions(+), 64 deletions(-) diff --git a/extensions/mypy_extensions.py b/extensions/mypy_extensions.py index ba645b58b82a..1b926d85b071 100644 --- a/extensions/mypy_extensions.py +++ b/extensions/mypy_extensions.py @@ -28,3 +28,9 @@ def __init__(name=None, typ=Any, keyword_only=False): self.name = name self.typ = typ self.named_only = named_only + +class DefaultArg(object): + def __init__(name=None, typ=Any, keyword_only=False): + self.name = name + self.typ = typ + self.named_only = named_only diff --git a/mypy/checker.py b/mypy/checker.py index 4e15278bb614..c5a47068a5b3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -816,7 +816,7 @@ def check_inplace_operator_method(self, defn: FuncBase) -> None: def check_getattr_method(self, typ: CallableType, context: Context) -> None: method_type = CallableType([AnyType(), self.named_type('builtins.str')], [nodes.ARG_POS, nodes.ARG_POS], - [None], + [None, None], AnyType(), self.named_type('builtins.function')) if not is_subtype(typ, method_type): diff --git a/mypy/fastparse.py b/mypy/fastparse.py index 942f352f05a9..c80eb79e5d4e 100644 --- a/mypy/fastparse.py +++ b/mypy/fastparse.py @@ -904,7 +904,11 @@ def visit_Ellipsis(self, n: ast35.Ellipsis) -> Type: # List(expr* elts, expr_context ctx) def visit_List(self, n: ast35.List) -> Type: - return ArgumentList(self.translate_expr_list(n.elts), line=self.line) + return ArgumentList( + self.translate_expr_list(n.elts), + [None]*len(n.elts), + [0]*len(n.elts), + line=self.line) class TypeCommentParseError(Exception): diff --git a/mypy/messages.py b/mypy/messages.py index 803ca62f6509..cd91c587112f 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -15,7 +15,7 @@ ) from mypy.nodes import ( TypeInfo, Context, MypyFile, op_methods, FuncDef, reverse_type_aliases, - ARG_STAR, ARG_STAR2 + ARG_POS, ARG_OPT, ARG_NAMED, ARG_NAMED_OPT, ARG_STAR, ARG_STAR2 ) @@ -167,8 +167,40 @@ def format(self, typ: Type, verbosity: int = 0) -> str: return_type = strip_quotes(self.format(func.ret_type)) if func.is_ellipsis_args: return 'Callable[..., {}]'.format(return_type) - arg_types = [strip_quotes(self.format(t)) for t in func.arg_types] - return 'Callable[[{}], {}]'.format(", ".join(arg_types), return_type) + arg_strings = [] + for arg_name, arg_type, arg_kind in zip( + func.arg_names, func.arg_types, func.arg_kinds): + if arg_kind == ARG_POS and arg_name is None or verbosity == 0: + arg_strings.append( + strip_quotes( + self.format( + arg_type, + verbosity = max(verbosity-1, 0)))) + else: + constructor = { + ARG_POS: "Arg", + ARG_OPT: "DefaultArg", + ARG_NAMED: "Arg", + ARG_NAMED_OPT: "DefaultArg", + ARG_STAR: "StarArg", + ARG_STAR2: "KwArg", + }[arg_kind] + if arg_kind in ( + ARG_POS, + ARG_OPT, + ARG_NAMED, + ARG_NAMED_OPT + ): + arg_strings.append("{}('{}', {}, {})".format( + constructor, + arg_name, + strip_quotes(self.format(arg_type)), + arg_kind in (ARG_NAMED, ARG_NAMED_OPT))) + else: + arg_strings.append("{}({})".format( + constructor, + arg_name)) + return 'Callable[[{}], {}]'.format(", ".join(arg_strings), return_type) else: # Use a simple representation for function types; proper # function types may result in long and difficult-to-read diff --git a/mypy/parse.py b/mypy/parse.py index bd2f79c570f4..7f5625922b83 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -70,7 +70,6 @@ none = Token('') # Empty token - def parse(source: Union[str, bytes], fnam: str, errors: Errors, @@ -415,6 +414,11 @@ def parse_function(self, no_type_checks: bool = False) -> FuncDef: arg_kinds = [arg.kind for arg in args] arg_names = [arg.variable.name() for arg in args] + # for overloads of special methods, let people name their arguments + # whatever they want, and don't let them call those functions with + # arguments by name. + if _special_function_elide_names(name): + arg_names = [None]*len(arg_names) body, comment_type = self.parse_block(allow_type=True) # Potentially insert extra assignment statements to the beginning of the @@ -531,10 +535,11 @@ def parse_function_header( try: name_tok = self.expect_type(Name) name = name_tok.string + include_names = not _special_function_elide_names(name) self.errors.push_function(name) - args, typ, extra_stmts = self.parse_args(no_type_checks) + args, typ, extra_stmts = self.parse_args(no_type_checks, include_names) except ParseError: if not isinstance(self.current(), Break): self.ind -= 1 # Kludge: go back to the Break token @@ -545,9 +550,11 @@ def parse_function_header( return (name, args, typ, False, extra_stmts) - def parse_args(self, no_type_checks: bool=False) -> Tuple[List[Argument], - CallableType, - List[AssignmentStmt]]: + def parse_args(self, + no_type_checks: bool=False, + include_names: bool=True) -> Tuple[List[Argument], + CallableType, + List[AssignmentStmt]]: """Parse a function signature (...) [-> t]. See parse_arg_list for an explanation of the final tuple item. @@ -573,18 +580,24 @@ def parse_args(self, no_type_checks: bool=False) -> Tuple[List[Argument], self.verify_argument_kinds(arg_kinds, lparen.line, lparen.column) annotation = self.build_func_annotation( - ret_type, args, lparen.line, lparen.column) + ret_type, args, lparen.line, lparen.column, include_names=include_names) return args, annotation, extra_stmts - def build_func_annotation(self, ret_type: Type, args: List[Argument], - line: int, column: int, is_default_ret: bool = False) -> CallableType: + def build_func_annotation(self, + ret_type: Type, + args: List[Argument], + line: int, + column: int, + is_default_ret: bool = False, + *, + include_names: bool = True) -> CallableType: arg_types = [arg.type_annotation for arg in args] # Are there any type annotations? if ((ret_type and not is_default_ret) or arg_types != [None] * len(arg_types)): # Yes. Construct a type for the function signature. - return self.construct_function_type(args, ret_type, line, column) + return self.construct_function_type(args, ret_type, line, column, include_names) else: return None @@ -812,8 +825,12 @@ def verify_argument_kinds(self, kinds: List[int], line: int, column: int) -> Non self.fail('Invalid argument list', line, column) found.add(kind) - def construct_function_type(self, args: List[Argument], ret_type: Type, - line: int, column: int) -> CallableType: + def construct_function_type(self, + args: List[Argument], + ret_type: Type, + line: int, + column: int, + include_names: bool=True) -> CallableType: # Complete the type annotation by replacing omitted types with 'Any'. arg_types = [arg.type_annotation for arg in args] for i in range(len(arg_types)): @@ -822,7 +839,10 @@ def construct_function_type(self, args: List[Argument], ret_type: Type, if ret_type is None: ret_type = AnyType(implicit=True) arg_kinds = [arg.kind for arg in args] - arg_names = [arg.variable.name() for arg in args] + if include_names: + arg_names = [arg.variable.name() for arg in args] + else: + arg_names = [None]*len(args) return CallableType(arg_types, arg_kinds, arg_names, ret_type, None, name=None, variables=None, line=line, column=column) @@ -1937,6 +1957,10 @@ def parse_type_comment(self, token: Token, signature: bool) -> Type: else: return None +def _special_function_elide_names(name: str) -> bool: + if name == "__init__" or name == "__new__": + return False + return name.startswith("__") and name.endswith("__") class ParseError(Exception): pass diff --git a/mypy/parsetype.py b/mypy/parsetype.py index a8e8042e77e9..1a41ae72c330 100644 --- a/mypy/parsetype.py +++ b/mypy/parsetype.py @@ -112,61 +112,86 @@ def parse_argument_spec(self) -> Tuple[Type, Optional[str], int]: arg_const = self.expect_type(Name).string name = None # type: Optional[str] typ = AnyType # type: Type - kind = nodes.ARG_POS - self.expect('(') if arg_const == 'Arg': kind = nodes.ARG_POS - if self.current_token_str() != ')': - name = self.expect_type(StrLit).parsed() - if self.current_token_str() != ')': - self.expect(',') - typ = self.parse_type() - if self.current_token_str() != ')': - self.expect(',') - self.expect('keyword_only') - self.expect('=') - if self.current_token_str() == 'True': - kind = nodes.ARG_NAMED - self.skip() - elif self.current_token_str() == 'False': - self.skip() - else: - self.parse_error() + name, typ, keyword_only = self.parse_arguments( + read_name = True, + read_typ = True, + read_keyword_only = True) + if keyword_only: + kind = nodes.ARG_NAMED elif arg_const == 'DefaultArg': kind = nodes.ARG_OPT - if self.current_token_str() != ')': - name = self.expect_type(StrLit).parsed() - if self.current_token_str() != ')': - self.expect(',') - typ = self.parse_type() - if self.current_token_str() != ')': - self.expect(',') - self.expect('keyword_only') - self.expect('=') - if self.current_token_str() == 'True': - kind = nodes.ARG_NAMED_OPT - self.skip() - elif self.current_token_str() == 'False': - self.skip() - else: - self.parse_error() + name, typ, keyword_only = self.parse_arguments( + read_name = True, + read_typ = True, + read_keyword_only = True) + if keyword_only: + kind = nodes.ARG_NAMED_OPT elif arg_const == 'StarArg': # Takes one type kind = nodes.ARG_STAR - if self.current_token_str() != ')': - typ = self.parse_type() + _, typ, _ = self.parse_arguments( + read_name = False, + read_typ = True, + read_keyword_only = False) elif arg_const == 'KwArg': # Takes one type kind = nodes.ARG_STAR2 - if self.current_token_str() != ')': - typ = self.parse_type() + _, typ, _ = self.parse_arguments( + read_name = False, + read_typ = True, + read_keyword_only = False) else: self.parse_error() - self.expect(')') return typ, name, kind else: return self.parse_type(), None, nodes.ARG_POS + def parse_arguments(self, + *, + read_name: bool, + read_typ: bool, + read_keyword_only: bool) -> Tuple[ + Optional[str], + Optional[Type], + bool]: + self.expect('(') + name = None + typ = AnyType + keyword_only = False + try: + if self.current_token_str() == ')': + return name, typ, keyword_only + if read_name: + if self.current_token_str() != 'None': + name = self.expect_type(StrLit).parsed() + else: + self.skip() + if self.current_token_str() == ')': + return name, typ, keyword_only + else: + self.expect(',') + if read_typ: + typ = self.parse_type() + if self.current_token_str() == ')': + return name, typ, keyword_only + else: + self.expect(',') + if read_keyword_only: + if self.current_token_str() == 'True': + keyword_only = True + self.skip() + else: + self.expect('False') + if self.current_token_str() == ')': + return name, typ, keyword_only + else: + self.expect(',') + finally: + self.expect(')') + + def parse_argument_list(self) -> ArgumentList: """Parse type list [t, ...].""" lbracket = self.expect('[') diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c395ac9fc835..ca06cc391619 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -10,7 +10,7 @@ # Circular import; done in the function instead. # import mypy.solve from mypy import messages, sametypes -from mypy.nodes import CONTRAVARIANT, COVARIANT +from mypy.nodes import CONTRAVARIANT, COVARIANT, ARG_POS, ARG_OPT from mypy.maptype import map_instance_to_supertype from mypy import experiments @@ -278,9 +278,17 @@ def is_callable_subtype(left: CallableType, right: CallableType, return False if len(left.arg_types) < len(right.arg_types): return False + for i in range(len(right.arg_types)): if not is_subtype(right.arg_types[i], left.arg_types[i]): return False + # A function with a named argument can be a subtype of a function + # without that argument named. Otherwise, the argument names at the + # positions need to match, for positional arguments, for L to be a + # subtype of R. + if right.arg_kinds[i] in (ARG_POS, ARG_OPT) and right.arg_names[i] is not None: + if left.arg_names[i] != right.arg_names[i]: + return False return True diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 445bc9900f4c..0d30ae4fdfb1 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -1230,9 +1230,9 @@ class D: def __getattr__(self, x: str) -> None: pass [out] main: note: In member "__getattr__" of class "B": -main:4: error: Invalid signature "def (self: __main__.B, x: __main__.A) -> __main__.B" +main:4: error: Invalid signature "def (__main__.B, __main__.A) -> __main__.B" main: note: In member "__getattr__" of class "C": -main:6: error: Invalid signature "def (self: __main__.C, x: builtins.str, y: builtins.str) -> __main__.C" +main:6: error: Invalid signature "def (__main__.C, builtins.str, builtins.str) -> __main__.C" -- CallableType objects diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index c11202ee34d6..435197b50931 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1285,6 +1285,50 @@ from mypy_extensions import Arg def a(f: Callable[[Arg('x', int)], int]): f(x=4) f(5) + f(y=3) # E: Unexpected keyword argument "y" + +[builtins fixtures/dict.pyi] + +[out] +main: note: In function "a": + +[case testCallableWithOptionalArg] +from typing import Callable +from mypy_extensions import DefaultArg + +def a(f: Callable[[DefaultArg('x', int)], int]): + f(x=4) + f(2) + f() + f(y=3) # E: Unexpected keyword argument "y" + f("foo") # E: Argument 1 has incompatible type "str"; expected "int" + +[builtins fixtures/dict.pyi] + +[out] +main: note: In function "a": + +[case testCallableArgKindSubtyping] +from typing import Callable +from mypy_extensions import Arg, DefaultArg + +int_str_fun = None # type: Callable[[int, str], str] +int_opt_str_fun = None # type: Callable[[int, DefaultArg(None, str)], str] +int_named_str_fun = None # type: Callable[[int, Arg('s', str)], str] + +def isf(ii: int, ss: str) -> str: + return ss + +def iosf(i: int, s: str = "bar") -> str: + return s + +int_str_fun = isf +int_opt_str_fun = iosf +int_str_fun = iosf +int_opt_str_fun = isf # E: Incompatible types in assignment (expression has type Callable[[Arg('ii', int, False), Arg('ss', str, False)], str], variable has type Callable[[int, DefaultArg('None', str, False)], str]) + +int_named_str_fun = isf # E: Incompatible types in assignment (expression has type Callable[[Arg('ii', int, False), Arg('ss', str, False)], str], variable has type Callable[[int, Arg('s', str, False)], str]) +int_named_str_fun = iosf [builtins fixtures/dict.pyi] @@ -1470,8 +1514,10 @@ class A(Generic[t]): [case testRedefineFunction] def f(x): pass def g(x, y): pass -def h(y): pass +def h(x): pass +def j(y): pass f = h +f = j # E: Incompatible types in assignment (expression has type Callable[[Arg('y', Any, False)], Any], variable has type Callable[[Arg('x', Any, False)], Any]) f = g # E: Incompatible types in assignment (expression has type Callable[[Any, Any], Any], variable has type Callable[[Any], Any]) [case testRedefineFunction2] diff --git a/test-data/unit/check-modules.test b/test-data/unit/check-modules.test index 8aac9840097e..bd103329b55e 100644 --- a/test-data/unit/check-modules.test +++ b/test-data/unit/check-modules.test @@ -590,7 +590,7 @@ try: except: f = g [file m.py] -def f(y): pass +def f(x): pass [case testImportFunctionAndAssignIncompatible] try: diff --git a/test-data/unit/check-super.test b/test-data/unit/check-super.test index 39a1c63a87ee..c5722c0a81d6 100644 --- a/test-data/unit/check-super.test +++ b/test-data/unit/check-super.test @@ -23,7 +23,7 @@ from typing import Any class B: def f(self, y: 'A') -> None: pass class A(B): - def f(self, x: Any) -> None: + def f(self, y: Any) -> None: a, b = None, None # type: (A, B) super().f(b) # E: Argument 1 to "f" of "B" has incompatible type "B"; expected "A" super().f(a) diff --git a/test-data/unit/fixtures/tuple.pyi b/test-data/unit/fixtures/tuple.pyi index f6d36cc0d2df..f6576c03af94 100644 --- a/test-data/unit/fixtures/tuple.pyi +++ b/test-data/unit/fixtures/tuple.pyi @@ -12,7 +12,7 @@ class type: def __call__(self, *a) -> object: pass class tuple(Sequence[Tco], Generic[Tco]): def __iter__(self) -> Iterator[Tco]: pass - def __getitem__(self, x: int) -> Tco: pass + def __getitem__(self, n: int) -> Tco: pass class function: pass # We need int for indexing tuples. diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 56a07e8fb47c..a4aa5992c7df 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -75,8 +75,9 @@ class AsyncIterator(AsyncIterable[T], Generic[T]): def __anext__(self) -> Awaitable[T]: pass class Sequence(Iterable[T], Generic[T]): + # Use int because slice isn't defined in the default test builtins @abstractmethod - def __getitem__(self, n: Any) -> T: pass + def __getitem__(self, n: int) -> T: pass class Mapping(Generic[T, U]): pass From 0ca7ed2510dd06a2304126be40c303fa29c559ca Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 9 Nov 2016 14:15:12 -0800 Subject: [PATCH 4/6] New function subtyping rule! --- mypy/subtypes.py | 162 ++++++++++++++++++++++------- mypy/types.py | 61 +++++++++-- test-data/unit/lib-stub/typing.pyi | 2 +- 3 files changed, 176 insertions(+), 49 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index ca06cc391619..1a85b7091e2f 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -1,4 +1,4 @@ -from typing import List, Dict, Callable +from typing import List, Dict, Callable, Optional from mypy.types import ( Type, AnyType, UnboundType, TypeVisitor, ErrorType, Void, NoneTyp, @@ -10,7 +10,7 @@ # Circular import; done in the function instead. # import mypy.solve from mypy import messages, sametypes -from mypy.nodes import CONTRAVARIANT, COVARIANT, ARG_POS, ARG_OPT +from mypy.nodes import CONTRAVARIANT, COVARIANT, ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT from mypy.maptype import map_instance_to_supertype from mypy import experiments @@ -269,54 +269,138 @@ def is_callable_subtype(left: CallableType, right: CallableType, if right.is_ellipsis_args: return True - # Check argument types. - if left.min_args > right.min_args: - return False - if left.is_var_arg: - return is_var_arg_callable_subtype_helper(left, right) - if right.is_var_arg: - return False - if len(left.arg_types) < len(right.arg_types): - return False + right_star_type = None # type: Optional[Type] + right_star2_type = None # type: Optional[Type] + # Check argument types. + done_with_positional = False for i in range(len(right.arg_types)): - if not is_subtype(right.arg_types[i], left.arg_types[i]): - return False - # A function with a named argument can be a subtype of a function - # without that argument named. Otherwise, the argument names at the - # positions need to match, for positional arguments, for L to be a - # subtype of R. - if right.arg_kinds[i] in (ARG_POS, ARG_OPT) and right.arg_names[i] is not None: - if left.arg_names[i] != right.arg_names[i]: + right_name = right.arg_names[i] + right_kind = right.arg_kinds[i] + right_type = right.arg_types[i] + if right_kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): + done_with_positional = True + + if not done_with_positional: + right_pos = i + else: + right_pos = None + + if right_kind == ARG_STAR: + right_star_type = right_type + # Right has an infinite series of optional positional arguments + # here. Get all further positional arguments of left, and make sure + # they're more general than their corresponding member in this + # series. Also make sure left has its own inifite series of + # optional positional arguments. + if not left.is_var_arg: return False - return True + j = i + while j < len(left.arg_kinds) and left.arg_kinds[j] in (ARG_POS, ARG_OPT): + left_name, left_pos, left_type, left_required = left.argument_by_position(j) + # This fetches the synthetic argument that's from the *args + right_name, right_kind, right_type, right_required = right.argument_by_position(j) + if not is_left_more_general(left_name, left_pos, left_type, left_required, + right_name, right_pos, right_type, right_required): + return False + j += 1 + + continue + if right_kind == ARG_STAR2: + right_star2_type = right_type + # Right has an infinite set of optional named arguments here. Get + # all further named arguments of left and make sure they're more + # general than their corresponding member in this set. Also make + # sure left has its own infinite set of optional named arguments. + if not left.is_kw_arg: + return False + left_names = set([name for name in left.arg_names if name is not None]) + right_names = set([name for name in right.arg_names if name is not None]) + left_only_names = left_names - right_names + for name in left_only_names: + left_name, left_pos, left_type, left_required = left.argument_by_name(name) + # This fetches the synthetic argument that's from the **kwargs + right_name, right_kind, right_type, right_required = right.argument_by_name(name) + if not is_left_more_general(left_name, left_pos, left_type, left_required, + right_name, right_pos, right_type, right_required): + return False + continue + right_required = right_kind in (ARG_POS, ARG_NAMED) -def is_var_arg_callable_subtype_helper(left: CallableType, right: CallableType) -> bool: - """Is left a subtype of right, assuming left has *args? + # Get possible corresponding arguments by name and by position. + if not done_with_positional: + left_by_position = left.argument_by_position(i) + else: + left_by_position = None + if right_name is not None: + left_by_name = left.argument_by_name(right_name) + else: + left_by_name = None - See also is_callable_subtype for additional assumptions we can make. - """ - left_fixed = left.max_fixed_args() - right_fixed = right.max_fixed_args() - num_fixed_matching = min(left_fixed, right_fixed) - for i in range(num_fixed_matching): - if not is_subtype(right.arg_types[i], left.arg_types[i]): + # Left must have some kind of corresponding argument. + if left_by_name is None and left_by_position is None: + return False + # If there's ambiguity as to how to match up this argument, it's not a subtype + if (left_by_name is not None + and left_by_position is not None + and left_by_name != left_by_position): return False - if not right.is_var_arg: - for i in range(num_fixed_matching, len(right.arg_types)): - if not is_subtype(right.arg_types[i], left.arg_types[-1]): + + left_name, left_pos, left_type, left_required = left_by_name or left_by_position + + if not is_left_more_general(left_name, left_pos, left_type, left_required, + right_name, right_pos, right_type, right_required): + return False + + for i in range(len(left.arg_types)): + left_name = left.arg_names[i] + left_kind = left.arg_kinds[i] + left_type = left.arg_types[i] + # All *required* left-hand arguments must have a corresponding + # right-hand argument. The relationship between the two is handled in + # the loop above. + + # Also check that *arg types match in this loop + if left_kind == ARG_POS and right.argument_by_position(i) is None: + if left_name is None: return False - return True - else: - for i in range(left_fixed, right_fixed): - if not is_subtype(right.arg_types[i], left.arg_types[-1]): + if right.argument_by_name(left_name) == None: + return False + elif left_kind == ARG_NAMED: + if right.argument_by_name(left_name) == None: return False - for i in range(right_fixed, left_fixed): - if not is_subtype(right.arg_types[-1], left.arg_types[i]): + elif left_kind == ARG_STAR: + if right_star_type is not None and not is_subtype(right_star_type, left_type): return False - return is_subtype(right.arg_types[-1], left.arg_types[-1]) + elif left_kind == ARG_STAR2: + if right_star2_type is not None and not is_subtype(right_star2_type, left_type): + return False + return True +def is_left_more_general( + left_name: Optional[str], + left_pos: Optional[int], + left_type: Type, + left_required: bool, + right_name: Optional[str], + right_pos: Optional[int], + right_type: Type, + right_required: bool) -> bool: + # If right has a specific name it wants this argument to be, left must + # have the same. + if right_name is not None and left_name != right_name: + return False + # If right is at a specific position, left must have the same: + if right_pos is not None and left_pos != right_pos: + return False + # Left must have a more general type + if not is_subtype(right_type, left_type): + return False + # If right's argument is optional, left's must also be. + if not right_required and left_required: + return False + return True def unify_generic_callable(type: CallableType, target: CallableType, ignore_return: bool) -> CallableType: diff --git a/mypy/types.py b/mypy/types.py index 920bc48cf4b1..13c921429b42 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -7,7 +7,7 @@ ) import mypy.nodes -from mypy.nodes import INVARIANT, SymbolNode +from mypy.nodes import INVARIANT, SymbolNode, ARG_POS, ARG_OPT, ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT from mypy import experiments @@ -263,7 +263,7 @@ def deserialize(cls, data: JsonDict) -> 'ArgumentList': assert data['.class'] == 'ArgumentList' or data['.class'] == 'TypeList' types = [Type.deserialize(t) for t in data['items']] names = cast(List[Optional[str]], data.get('names', [None]*len(types))) - kinds = cast(List[int], data.get('kinds', [nodes.ARG_POS]*len(types))) + kinds = cast(List[int], data.get('kinds', [ARG_POS]*len(types))) return ArgumentList( types=[Type.deserialize(t) for t in data['items']], names=names, @@ -556,7 +556,7 @@ class CallableType(FunctionLike): """Type of a non-overloaded callable object (function).""" arg_types = None # type: List[Type] # Types of function arguments - arg_kinds = None # type: List[int] # mypy.nodes.ARG_ constants + arg_kinds = None # type: List[int] # ARG_ constants arg_names = None # type: List[str] # None if not a keyword argument min_args = 0 # Minimum number of arguments; derived from arg_kinds is_var_arg = False # Is it a varargs function? derived from arg_kinds @@ -597,8 +597,9 @@ def __init__(self, self.arg_types = arg_types self.arg_kinds = arg_kinds self.arg_names = arg_names - self.min_args = arg_kinds.count(mypy.nodes.ARG_POS) - self.is_var_arg = mypy.nodes.ARG_STAR in arg_kinds + self.min_args = arg_kinds.count(ARG_POS) + self.is_var_arg = ARG_STAR in arg_kinds + self.is_kw_arg = ARG_STAR2 in arg_kinds self.ret_type = ret_type self.fallback = fallback assert not name or ' int: n -= 1 return n + def argument_by_name(self, name: str) -> Optional[Tuple[ + Optional[str], + Optional[int], + Type, + bool]]: + seen_star = False + for i, (arg_name, kind, typ) in enumerate( + zip(self.arg_names, self.arg_kinds, self.arg_types)): + # No more positional arguments after these. + if kind in (ARG_STAR, ARG_STAR2, ARG_NAMED, ARG_NAMED_OPT): + seen_star = True + if kind in (ARG_STAR, ARG_STAR2): + continue + if arg_name == name: + position = None if seen_star else i + return name, position, typ, kind in (ARG_POS, ARG_NAMED) + return None + + def argument_by_position(self, position: int) -> Optional[Tuple[ + Optional[str], + Optional[int], + Type, + bool]]: + if self.is_var_arg: + for kind, typ in zip(self.arg_kinds, self.arg_types): + if kind == ARG_STAR: + star_type = typ + break + if position >= len(self.arg_names): + if self.is_var_arg: + return None, position, star_type, False + else: + return None + name, kind, typ = self.arg_names[position], self.arg_kinds[position], self.arg_types[position] + if kind in (ARG_POS, ARG_OPT): + return name, position, typ, kind == ARG_POS + else: + if self.is_var_arg: + return None, position, star_type, False + else: + return None + def items(self) -> List['CallableType']: return [self] @@ -1266,17 +1309,17 @@ def visit_callable_type(self, t: CallableType) -> str: for i in range(len(t.arg_types)): if s != '': s += ', ' - if t.arg_kinds[i] == mypy.nodes.ARG_NAMED and not bare_asterisk: + if t.arg_kinds[i] == ARG_NAMED and not bare_asterisk: s += '*, ' bare_asterisk = True - if t.arg_kinds[i] == mypy.nodes.ARG_STAR: + if t.arg_kinds[i] == ARG_STAR: s += '*' - if t.arg_kinds[i] == mypy.nodes.ARG_STAR2: + if t.arg_kinds[i] == ARG_STAR2: s += '**' if t.arg_names[i]: s += t.arg_names[i] + ': ' s += str(t.arg_types[i]) - if t.arg_kinds[i] == mypy.nodes.ARG_OPT: + if t.arg_kinds[i] == ARG_OPT: s += ' =' s = '({})'.format(s) diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index a4aa5992c7df..abd3c09d3d83 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -82,7 +82,7 @@ class Sequence(Iterable[T], Generic[T]): class Mapping(Generic[T, U]): pass def NewType(name: str, tp: Type[T]) -> Callable[[T], T]: - def new_type(x): + def new_type(x: T) -> T: return x return new_type From 56fb35291bcbbcc4ee8bdb56f011e0ac536ab482 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 9 Nov 2016 14:15:51 -0800 Subject: [PATCH 5/6] Remove TODO now that I did it --- mypy/subtypes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 1a85b7091e2f..2cc1e9601278 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -240,7 +240,6 @@ def visit_type_type(self, left: TypeType) -> bool: def is_callable_subtype(left: CallableType, right: CallableType, ignore_return: bool = False) -> bool: """Is left a subtype of right?""" - # TODO: Support named arguments, **args, etc. # Non-type cannot be a subtype of type. if right.is_type_obj() and not left.is_type_obj(): return False From f97613a07b53fcf010e12eb51ea1796c8a2f37b6 Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Wed, 9 Nov 2016 15:00:08 -0800 Subject: [PATCH 6/6] Allow unnamed args in defs --- mypy/parse.py | 4 +- mypy/visitor.py | 140 ++++++++++++++-------------- test-data/unit/check-functions.test | 5 + 3 files changed, 78 insertions(+), 71 deletions(-) diff --git a/mypy/parse.py b/mypy/parse.py index 7f5625922b83..f2b9689a3b52 100644 --- a/mypy/parse.py +++ b/mypy/parse.py @@ -840,7 +840,9 @@ def construct_function_type(self, ret_type = AnyType(implicit=True) arg_kinds = [arg.kind for arg in args] if include_names: - arg_names = [arg.variable.name() for arg in args] + arg_names = [arg.variable.name() + if not arg.variable.name().startswith('__') else None + for arg in args] else: arg_names = [None]*len(args) return CallableType(arg_types, arg_kinds, arg_names, ret_type, None, name=None, diff --git a/mypy/visitor.py b/mypy/visitor.py index 33f287b9863b..c8f31c6cdedf 100644 --- a/mypy/visitor.py +++ b/mypy/visitor.py @@ -22,220 +22,220 @@ class NodeVisitor(Generic[T]): # Module structure - def visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> T: + def visit_mypy_file(self, __o: 'mypy.nodes.MypyFile') -> T: pass - def visit_import(self, o: 'mypy.nodes.Import') -> T: + def visit_import(self, __o: 'mypy.nodes.Import') -> T: pass - def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> T: + def visit_import_from(self, __o: 'mypy.nodes.ImportFrom') -> T: pass - def visit_import_all(self, o: 'mypy.nodes.ImportAll') -> T: + def visit_import_all(self, __o: 'mypy.nodes.ImportAll') -> T: pass # Definitions - def visit_func_def(self, o: 'mypy.nodes.FuncDef') -> T: + def visit_func_def(self, __o: 'mypy.nodes.FuncDef') -> T: pass def visit_overloaded_func_def(self, - o: 'mypy.nodes.OverloadedFuncDef') -> T: + __o: 'mypy.nodes.OverloadedFuncDef') -> T: pass - def visit_class_def(self, o: 'mypy.nodes.ClassDef') -> T: + def visit_class_def(self, __o: 'mypy.nodes.ClassDef') -> T: pass - def visit_global_decl(self, o: 'mypy.nodes.GlobalDecl') -> T: + def visit_global_decl(self, __o: 'mypy.nodes.GlobalDecl') -> T: pass - def visit_nonlocal_decl(self, o: 'mypy.nodes.NonlocalDecl') -> T: + def visit_nonlocal_decl(self, __o: 'mypy.nodes.NonlocalDecl') -> T: pass - def visit_decorator(self, o: 'mypy.nodes.Decorator') -> T: + def visit_decorator(self, __o: 'mypy.nodes.Decorator') -> T: pass - def visit_var(self, o: 'mypy.nodes.Var') -> T: + def visit_var(self, __o: 'mypy.nodes.Var') -> T: pass # Statements - def visit_block(self, o: 'mypy.nodes.Block') -> T: + def visit_block(self, __o: 'mypy.nodes.Block') -> T: pass - def visit_expression_stmt(self, o: 'mypy.nodes.ExpressionStmt') -> T: + def visit_expression_stmt(self, __o: 'mypy.nodes.ExpressionStmt') -> T: pass - def visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt') -> T: + def visit_assignment_stmt(self, __o: 'mypy.nodes.AssignmentStmt') -> T: pass def visit_operator_assignment_stmt(self, - o: 'mypy.nodes.OperatorAssignmentStmt') -> T: + __o: 'mypy.nodes.OperatorAssignmentStmt') -> T: pass - def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> T: + def visit_while_stmt(self, __o: 'mypy.nodes.WhileStmt') -> T: pass - def visit_for_stmt(self, o: 'mypy.nodes.ForStmt') -> T: + def visit_for_stmt(self, __o: 'mypy.nodes.ForStmt') -> T: pass - def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> T: + def visit_return_stmt(self, __o: 'mypy.nodes.ReturnStmt') -> T: pass - def visit_assert_stmt(self, o: 'mypy.nodes.AssertStmt') -> T: + def visit_assert_stmt(self, __o: 'mypy.nodes.AssertStmt') -> T: pass - def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> T: + def visit_del_stmt(self, __o: 'mypy.nodes.DelStmt') -> T: pass - def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> T: + def visit_if_stmt(self, __o: 'mypy.nodes.IfStmt') -> T: pass - def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> T: + def visit_break_stmt(self, __o: 'mypy.nodes.BreakStmt') -> T: pass - def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> T: + def visit_continue_stmt(self, __o: 'mypy.nodes.ContinueStmt') -> T: pass - def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> T: + def visit_pass_stmt(self, __o: 'mypy.nodes.PassStmt') -> T: pass - def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> T: + def visit_raise_stmt(self, __o: 'mypy.nodes.RaiseStmt') -> T: pass - def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> T: + def visit_try_stmt(self, __o: 'mypy.nodes.TryStmt') -> T: pass - def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> T: + def visit_with_stmt(self, __o: 'mypy.nodes.WithStmt') -> T: pass - def visit_print_stmt(self, o: 'mypy.nodes.PrintStmt') -> T: + def visit_print_stmt(self, __o: 'mypy.nodes.PrintStmt') -> T: pass - def visit_exec_stmt(self, o: 'mypy.nodes.ExecStmt') -> T: + def visit_exec_stmt(self, __o: 'mypy.nodes.ExecStmt') -> T: pass # Expressions - def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> T: + def visit_int_expr(self, __o: 'mypy.nodes.IntExpr') -> T: pass - def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> T: + def visit_str_expr(self, __o: 'mypy.nodes.StrExpr') -> T: pass - def visit_bytes_expr(self, o: 'mypy.nodes.BytesExpr') -> T: + def visit_bytes_expr(self, __o: 'mypy.nodes.BytesExpr') -> T: pass - def visit_unicode_expr(self, o: 'mypy.nodes.UnicodeExpr') -> T: + def visit_unicode_expr(self, __o: 'mypy.nodes.UnicodeExpr') -> T: pass - def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> T: + def visit_float_expr(self, __o: 'mypy.nodes.FloatExpr') -> T: pass - def visit_complex_expr(self, o: 'mypy.nodes.ComplexExpr') -> T: + def visit_complex_expr(self, __o: 'mypy.nodes.ComplexExpr') -> T: pass - def visit_ellipsis(self, o: 'mypy.nodes.EllipsisExpr') -> T: + def visit_ellipsis(self, __o: 'mypy.nodes.EllipsisExpr') -> T: pass - def visit_star_expr(self, o: 'mypy.nodes.StarExpr') -> T: + def visit_star_expr(self, __o: 'mypy.nodes.StarExpr') -> T: pass - def visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> T: + def visit_name_expr(self, __o: 'mypy.nodes.NameExpr') -> T: pass - def visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> T: + def visit_member_expr(self, __o: 'mypy.nodes.MemberExpr') -> T: pass - def visit_yield_from_expr(self, o: 'mypy.nodes.YieldFromExpr') -> T: + def visit_yield_from_expr(self, __o: 'mypy.nodes.YieldFromExpr') -> T: pass - def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> T: + def visit_yield_expr(self, __o: 'mypy.nodes.YieldExpr') -> T: pass - def visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> T: + def visit_call_expr(self, __o: 'mypy.nodes.CallExpr') -> T: pass - def visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> T: + def visit_op_expr(self, __o: 'mypy.nodes.OpExpr') -> T: pass - def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> T: + def visit_comparison_expr(self, __o: 'mypy.nodes.ComparisonExpr') -> T: pass - def visit_cast_expr(self, o: 'mypy.nodes.CastExpr') -> T: + def visit_cast_expr(self, __o: 'mypy.nodes.CastExpr') -> T: pass - def visit_reveal_type_expr(self, o: 'mypy.nodes.RevealTypeExpr') -> T: + def visit_reveal_type_expr(self, __o: 'mypy.nodes.RevealTypeExpr') -> T: pass - def visit_super_expr(self, o: 'mypy.nodes.SuperExpr') -> T: + def visit_super_expr(self, __o: 'mypy.nodes.SuperExpr') -> T: pass - def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> T: + def visit_unary_expr(self, __o: 'mypy.nodes.UnaryExpr') -> T: pass - def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> T: + def visit_list_expr(self, __o: 'mypy.nodes.ListExpr') -> T: pass - def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> T: + def visit_dict_expr(self, __o: 'mypy.nodes.DictExpr') -> T: pass - def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> T: + def visit_tuple_expr(self, __o: 'mypy.nodes.TupleExpr') -> T: pass - def visit_set_expr(self, o: 'mypy.nodes.SetExpr') -> T: + def visit_set_expr(self, __o: 'mypy.nodes.SetExpr') -> T: pass - def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> T: + def visit_index_expr(self, __o: 'mypy.nodes.IndexExpr') -> T: pass - def visit_type_application(self, o: 'mypy.nodes.TypeApplication') -> T: + def visit_type_application(self, __o: 'mypy.nodes.TypeApplication') -> T: pass - def visit_func_expr(self, o: 'mypy.nodes.FuncExpr') -> T: + def visit_func_expr(self, __o: 'mypy.nodes.FuncExpr') -> T: pass - def visit_list_comprehension(self, o: 'mypy.nodes.ListComprehension') -> T: + def visit_list_comprehension(self, __o: 'mypy.nodes.ListComprehension') -> T: pass - def visit_set_comprehension(self, o: 'mypy.nodes.SetComprehension') -> T: + def visit_set_comprehension(self, __o: 'mypy.nodes.SetComprehension') -> T: pass - def visit_dictionary_comprehension(self, o: 'mypy.nodes.DictionaryComprehension') -> T: + def visit_dictionary_comprehension(self, __o: 'mypy.nodes.DictionaryComprehension') -> T: pass - def visit_generator_expr(self, o: 'mypy.nodes.GeneratorExpr') -> T: + def visit_generator_expr(self, __o: 'mypy.nodes.GeneratorExpr') -> T: pass - def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> T: + def visit_slice_expr(self, __o: 'mypy.nodes.SliceExpr') -> T: pass - def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> T: + def visit_conditional_expr(self, __o: 'mypy.nodes.ConditionalExpr') -> T: pass - def visit_backquote_expr(self, o: 'mypy.nodes.BackquoteExpr') -> T: + def visit_backquote_expr(self, __o: 'mypy.nodes.BackquoteExpr') -> T: pass - def visit_type_var_expr(self, o: 'mypy.nodes.TypeVarExpr') -> T: + def visit_type_var_expr(self, __o: 'mypy.nodes.TypeVarExpr') -> T: pass - def visit_type_alias_expr(self, o: 'mypy.nodes.TypeAliasExpr') -> T: + def visit_type_alias_expr(self, __o: 'mypy.nodes.TypeAliasExpr') -> T: pass - def visit_namedtuple_expr(self, o: 'mypy.nodes.NamedTupleExpr') -> T: + def visit_namedtuple_expr(self, __o: 'mypy.nodes.NamedTupleExpr') -> T: pass - def visit_typeddict_expr(self, o: 'mypy.nodes.TypedDictExpr') -> T: + def visit_typeddict_expr(self, __o: 'mypy.nodes.TypedDictExpr') -> T: pass - def visit_newtype_expr(self, o: 'mypy.nodes.NewTypeExpr') -> T: + def visit_newtype_expr(self, __o: 'mypy.nodes.NewTypeExpr') -> T: pass - def visit__promote_expr(self, o: 'mypy.nodes.PromoteExpr') -> T: + def visit__promote_expr(self, __o: 'mypy.nodes.PromoteExpr') -> T: pass - def visit_await_expr(self, o: 'mypy.nodes.AwaitExpr') -> T: + def visit_await_expr(self, __o: 'mypy.nodes.AwaitExpr') -> T: pass - def visit_temp_node(self, o: 'mypy.nodes.TempNode') -> T: + def visit_temp_node(self, __o: 'mypy.nodes.TempNode') -> T: pass diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 435197b50931..ce81e91c928f 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -1322,7 +1322,12 @@ def isf(ii: int, ss: str) -> str: def iosf(i: int, s: str = "bar") -> str: return s +def isf_unnamed(__i: int, __s: str) -> str: + return __s + int_str_fun = isf +int_str_fun = isf_unnamed +int_named_str_fun = isf_unnamed # E: Incompatible types in assignment (expression has type Callable[[int, str], str], variable has type Callable[[int, Arg('s', str, False)], str]) int_opt_str_fun = iosf int_str_fun = iosf int_opt_str_fun = isf # E: Incompatible types in assignment (expression has type Callable[[Arg('ii', int, False), Arg('ss', str, False)], str], variable has type Callable[[int, DefaultArg('None', str, False)], str])