Skip to content

Commit 7ce8090

Browse files
committed
Handle tuples, do some other cleanups
1 parent c77c875 commit 7ce8090

File tree

2 files changed

+70
-26
lines changed

2 files changed

+70
-26
lines changed

mypy/checker.py

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2907,31 +2907,36 @@ def gen_unique_name(base: str, table: SymbolTable) -> str:
29072907
return base + str(i)
29082908

29092909

2910-
def intersect_instance_callable(type: Instance, callable_type: CallableType,
2911-
typechecker: TypeChecker) -> Type:
2910+
def intersect_instance_callable(typ: Instance, callable_type: CallableType,
2911+
typechecker: TypeChecker) -> Instance:
29122912
"""Creates a fake type that represents the intersection of an
29132913
Instance and a CallableType.
29142914
29152915
It operates by creating a bare-minimum dummy TypeInfo that
2916-
subclasses type and adds a __call__ method matching callable_type."""
2916+
subclasses type and adds a __call__ method matching callable_type.
2917+
"""
29172918

29182919
# In order for this to work in incremental mode, the type we generate needs to
29192920
# have a valid fullname and a corresponding entry in a symbol table. We generate
29202921
# a unique name inside the symbol table of the current module.
29212922
cur_module = cast(MypyFile, typechecker.scope.stack[0])
2922-
gen_name = gen_unique_name("<callable subtype of {}>".format(type.type.name()),
2923+
gen_name = gen_unique_name("<callable subtype of {}>".format(typ.type.name()),
29232924
cur_module.names)
29242925

29252926
# Build the fake ClassDef and TypeInfo together.
29262927
# The ClassDef is full of lies and doesn't actually contain a body.
29272928
# Use format_bare to generate a nice name for error messages.
2928-
short_name = typechecker.msg.format_bare(type)
2929+
# We skip filling fully filling out a handful of TypeInfo fields because they
2930+
# should be irrelevant for a generated type like this:
2931+
# is_protocol, protocol_members, is_abstract
2932+
short_name = typechecker.msg.format_bare(typ)
29292933
cdef = ClassDef(short_name, Block([]))
29302934
cdef.fullname = cur_module.fullname() + '.' + gen_name
29312935
info = TypeInfo(SymbolTable(), cdef, cur_module.fullname())
29322936
cdef.info = info
2933-
info.bases = [type]
2937+
info.bases = [typ]
29342938
info.calculate_mro()
2939+
info.calculate_metaclass_type()
29352940

29362941
# Build up a fake FuncDef so we can populate the symbol table.
29372942
func_def = FuncDef('__call__', [], Block([]), callable_type)
@@ -2943,7 +2948,7 @@ def intersect_instance_callable(type: Instance, callable_type: CallableType,
29432948
return Instance(info, [])
29442949

29452950

2946-
def make_fake_callable(type: Instance, typechecker: TypeChecker) -> Type:
2951+
def make_fake_callable(typ: Instance, typechecker: TypeChecker) -> Instance:
29472952
"""Produce a new type that makes type Callable with a generic callable type."""
29482953

29492954
fallback = typechecker.named_type('builtins.function')
@@ -2955,10 +2960,10 @@ def make_fake_callable(type: Instance, typechecker: TypeChecker) -> Type:
29552960
fallback=fallback,
29562961
is_ellipsis_args=True)
29572962

2958-
return intersect_instance_callable(type, callable_type, typechecker)
2963+
return intersect_instance_callable(typ, callable_type, typechecker)
29592964

29602965

2961-
def partition_by_callable(type: Type, typechecker: TypeChecker,
2966+
def partition_by_callable(typ: Type, typechecker: TypeChecker,
29622967
unsound_partition: bool) -> Tuple[List[Type], List[Type]]:
29632968
"""Takes in a type and partitions that type into callable subtypes and
29642969
uncallable subtypes.
@@ -2976,16 +2981,16 @@ def partition_by_callable(type: Type, typechecker: TypeChecker,
29762981
Guaranteed to not return [], []
29772982
29782983
"""
2979-
if isinstance(type, FunctionLike) or isinstance(type, TypeType):
2980-
return [type], []
2984+
if isinstance(typ, FunctionLike) or isinstance(typ, TypeType):
2985+
return [typ], []
29812986

2982-
if isinstance(type, AnyType):
2983-
return [type], [type]
2987+
if isinstance(typ, AnyType):
2988+
return [typ], [typ]
29842989

2985-
if isinstance(type, UnionType):
2990+
if isinstance(typ, UnionType):
29862991
callables = []
29872992
uncallables = []
2988-
for subtype in type.relevant_items():
2993+
for subtype in typ.relevant_items():
29892994
# Use unsound_partition when handling unions in order to
29902995
# allow the expected type discrimination.
29912996
subcallables, subuncallables = partition_by_callable(subtype, typechecker,
@@ -2994,7 +2999,7 @@ def partition_by_callable(type: Type, typechecker: TypeChecker,
29942999
uncallables.extend(subuncallables)
29953000
return callables, uncallables
29963001

2997-
if isinstance(type, TypeVarType):
3002+
if isinstance(typ, TypeVarType):
29983003
# We could do better probably?
29993004
# Refine the the type variable's bound as our type in the case that
30003005
# callable() is true. This unfortuantely loses the information that
@@ -3003,30 +3008,39 @@ def partition_by_callable(type: Type, typechecker: TypeChecker,
30033008
# do better.
30043009
# If it is possible for the false branch to execute, return the original
30053010
# type to avoid losing type information.
3006-
callables, uncallables = partition_by_callable(type.erase_to_union_or_bound(), typechecker,
3011+
callables, uncallables = partition_by_callable(typ.erase_to_union_or_bound(), typechecker,
30073012
unsound_partition)
3008-
uncallables = [type] if len(uncallables) else []
3013+
uncallables = [typ] if len(uncallables) else []
30093014
return callables, uncallables
30103015

3011-
if isinstance(type, Instance):
3012-
method = type.type.get_method('__call__')
3016+
# A TupleType is callable if its fallback is, but needs special handling
3017+
# when we dummy up a new type.
3018+
ityp = typ
3019+
if isinstance(typ, TupleType):
3020+
ityp = typ.fallback
3021+
3022+
if isinstance(ityp, Instance):
3023+
method = ityp.type.get_method('__call__')
30133024
if method and method.type:
30143025
callables, uncallables = partition_by_callable(method.type, typechecker,
30153026
unsound_partition=False)
30163027
if len(callables) and not len(uncallables):
30173028
# Only consider the type callable if its __call__ method is
30183029
# definitely callable.
3019-
return [type], []
3030+
return [typ], []
30203031

30213032
if not unsound_partition:
3022-
ret = make_fake_callable(type, typechecker)
3023-
return [ret], [type]
3033+
fake = make_fake_callable(ityp, typechecker)
3034+
if isinstance(typ, TupleType):
3035+
fake.type.tuple_type = TupleType(typ.items, fake)
3036+
return [fake.type.tuple_type], [typ]
3037+
return [fake], [typ]
30243038

30253039
if unsound_partition:
3026-
return [], [type]
3040+
return [], [typ]
30273041
else:
30283042
# We don't know how properly make the type callable.
3029-
return [type], [type]
3043+
return [typ], [typ]
30303044

30313045

30323046
def conditional_callable_type_map(expr: Expression,
@@ -3038,7 +3052,8 @@ def conditional_callable_type_map(expr: Expression,
30383052
Returns a 2-tuple: The first element is a map from the expression to
30393053
the restricted type if it were callable. The second element is a
30403054
map from the expression to the type it would hold if it weren't
3041-
callable."""
3055+
callable.
3056+
"""
30423057
if not current_type:
30433058
return {}, {}
30443059

test-data/unit/check-callable.test

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ def g(o: Foo) -> None:
373373
o.bar()
374374

375375
[builtins fixtures/callable.pyi]
376+
376377
[case testCallableObjectAny]
377378

378379
from typing import Any
@@ -411,3 +412,31 @@ def g(o: Test[T], x: T) -> T:
411412
return x
412413

413414
[builtins fixtures/callable.pyi]
415+
416+
[case testCallablePromote]
417+
418+
def take_float(f: float) -> None:
419+
pass
420+
421+
def g(o: int) -> None:
422+
if callable(o):
423+
take_float(o)
424+
o(1,2,3)
425+
426+
[builtins fixtures/callable.pyi]
427+
428+
[case testCallableTuple]
429+
430+
from typing import NamedTuple
431+
432+
Thing = NamedTuple('Thing', [('s', str), ('n', int)])
433+
434+
def g(o: Thing) -> None:
435+
if callable(o):
436+
o.s + o.n # E: Unsupported operand types for + ("str" and "int")
437+
i, s = o
438+
i + s # E: Unsupported operand types for + ("str" and "int")
439+
# Tuples can't be called yet but this should work at least
440+
o.__call__(1,2,3)
441+
442+
[builtins fixtures/callable.pyi]

0 commit comments

Comments
 (0)