Skip to content

Flexible callable #2426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions extensions/mypy_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -20,3 +22,15 @@ 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

class DefaultArg(object):
def __init__(name=None, typ=Any, keyword_only=False):
self.name = name
self.typ = typ
self.named_only = named_only
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add the rest of the kinds of arguments.

2 changes: 1 addition & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions mypy/erasetype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions mypy/expandtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down Expand Up @@ -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:
Expand Down
41 changes: 37 additions & 4 deletions mypy/exprtotype.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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.
Expand Down
8 changes: 6 additions & 2 deletions mypy/fastparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 TypeList(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):
Expand Down
4 changes: 2 additions & 2 deletions mypy/fixup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion mypy/indirection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
4 changes: 2 additions & 2 deletions mypy/join.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions mypy/meet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
38 changes: 35 additions & 3 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)


Expand Down Expand Up @@ -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 = {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Move this constant dict thing out to a constant

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 (
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move tuple constant out to a constant.

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
Expand Down
2 changes: 2 additions & 0 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading