Skip to content

Commit 17f15cb

Browse files
committed
Fix subtyping of Callable[..., t] types
1 parent dc63432 commit 17f15cb

File tree

6 files changed

+38
-9
lines changed

6 files changed

+38
-9
lines changed

mypy/constraints.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def visit_callable_type(self, template: CallableType) -> List[Constraint]:
214214

215215
# We can't infer constraints from arguments if the template is Callable[..., T] (with
216216
# literal '...').
217-
if not template.is_ellipsis_args():
217+
if not template.is_ellipsis_args:
218218
for i in range(len(template.arg_types)):
219219
# Negate constraints due function argument type contravariance.
220220
res.extend(negate_constraints(infer_constraints(

mypy/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def format(self, typ: Type, verbose: bool = False) -> str:
142142
return result
143143
elif isinstance(func, CallableType):
144144
return_type = strip_quotes(self.format(func.ret_type))
145-
if func.is_ellipsis_args():
145+
if func.is_ellipsis_args:
146146
return 'Callable[..., {}]'.format(return_type)
147147
arg_types = [strip_quotes(self.format(t)) for t in func.arg_types]
148148
return 'Callable[[{}], {}]'.format(", ".join(arg_types), return_type)

mypy/subtypes.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ def visit_union_type(self, left: UnionType) -> bool:
163163
return all(is_subtype(item, self.right) for item in left.items)
164164

165165

166-
def is_callable_subtype(left: CallableType, right: CallableType, ignore_return: bool = False) -> bool:
166+
def is_callable_subtype(left: CallableType, right: CallableType,
167+
ignore_return: bool = False) -> bool:
167168
"""Is left a subtype of right?"""
168169
# TODO: Support named arguments, **args, etc.
169170
# Non-type cannot be a subtype of type.
@@ -182,6 +183,9 @@ def is_callable_subtype(left: CallableType, right: CallableType, ignore_return:
182183
if not ignore_return and not is_subtype(left.ret_type, right.ret_type):
183184
return False
184185

186+
if right.is_ellipsis_args:
187+
return True
188+
185189
# Check argument types.
186190
if left.min_args > right.min_args:
187191
return False

mypy/test/data/check-functions.test

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,3 +811,26 @@ def g(*x: int) -> str: pass
811811
x = f(g)
812812
x + 1 # E: Unsupported left operand type for + ("str")
813813
[builtins fixtures/list.py]
814+
815+
[case testCallableWithArbitraryArgsSubtyping]
816+
from typing import Callable
817+
def f(x: Callable[..., int]) -> None: pass
818+
def g1(): pass
819+
def g2(x, y) -> int: pass
820+
def g3(*, y: str) -> int: pass
821+
def g4(*, y: int) -> str: pass
822+
f(g1)
823+
f(g2)
824+
f(g3)
825+
f(g4) # E: Argument 1 to "f" has incompatible type Callable[..., str]; expected Callable[..., int]
826+
827+
[case testCallableWithArbitraryArgsSubtypingWithGenericFunc]
828+
from typing import Callable, TypeVar
829+
T = TypeVar('T')
830+
def f(x: Callable[..., int]) -> None: pass
831+
def g1(x: T) -> int: pass
832+
def g2(*x: T) -> int: pass
833+
def g3(*x: T) -> T: pass
834+
f(g1)
835+
f(g2)
836+
f(g3)

mypy/typeanal.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ def analyze_callable_type(self, t: UnboundType) -> Type:
198198
[nodes.ARG_STAR, nodes.ARG_STAR2],
199199
[None, None],
200200
ret_type=ret_type,
201-
fallback=fallback)
201+
fallback=fallback,
202+
is_ellipsis_args=True)
202203
else:
203204
self.fail('Invalid function type', t)
204205
return AnyType()

mypy/types.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -246,15 +246,18 @@ class CallableType(FunctionLike):
246246
# Stored as tuples (id, type).
247247
bound_vars = None # type: List[Tuple[int, Type]]
248248

249-
_is_type_obj = False # Does this represent a type object?
249+
# Is this Callable[..., t] (with literal '...')?
250+
is_ellipsis_args = False
250251

251252
def __init__(self, arg_types: List[Type],
252253
arg_kinds: List[int],
253254
arg_names: List[str],
254255
ret_type: Type,
255256
fallback: Instance,
256-
name: str = None, variables: List[TypeVarDef] = None,
257+
name: str = None,
258+
variables: List[TypeVarDef] = None,
257259
bound_vars: List[Tuple[int, Type]] = None,
260+
is_ellipsis_args: bool = False,
258261
line: int = -1) -> None:
259262
if variables is None:
260263
variables = []
@@ -271,6 +274,7 @@ def __init__(self, arg_types: List[Type],
271274
self.name = name
272275
self.variables = variables
273276
self.bound_vars = bound_vars
277+
self.is_ellipsis_args = is_ellipsis_args
274278
super().__init__(line)
275279

276280
def is_type_obj(self) -> bool:
@@ -313,9 +317,6 @@ def items(self) -> List['CallableType']:
313317
def is_generic(self) -> bool:
314318
return bool(self.variables)
315319

316-
def is_ellipsis_args(self) -> bool:
317-
return self.arg_kinds == [mypy.nodes.ARG_STAR, mypy.nodes.ARG_STAR2]
318-
319320
def type_var_ids(self) -> List[int]:
320321
a = [] # type: List[int]
321322
for tv in self.variables:

0 commit comments

Comments
 (0)