Skip to content

Commit

Permalink
stubtest: construct callable types from runtime values (#9680)
Browse files Browse the repository at this point in the history
Fixes false negatives where a stub defines an object as a not callable,
but it is in fact a function

Co-authored-by: hauntsaninja <>
  • Loading branch information
hauntsaninja authored Nov 1, 2020
1 parent fd16f84 commit 24fdf34
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 6 deletions.
56 changes: 50 additions & 6 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -897,9 +897,56 @@ def get_mypy_type_of_runtime_value(runtime: Any) -> Optional[mypy.types.Type]:
if isinstance(runtime, property):
# Give up on properties to avoid issues with things that are typed as attributes.
return None
if isinstance(runtime, (types.FunctionType, types.BuiltinFunctionType)):
# TODO: Construct a mypy.types.CallableType
return None

def anytype() -> mypy.types.AnyType:
return mypy.types.AnyType(mypy.types.TypeOfAny.unannotated)

if isinstance(
runtime,
(types.FunctionType, types.BuiltinFunctionType,
types.MethodType, types.BuiltinMethodType)
):
builtins = get_stub("builtins")
assert builtins is not None
type_info = builtins.names["function"].node
assert isinstance(type_info, nodes.TypeInfo)
fallback = mypy.types.Instance(type_info, [anytype()])
try:
signature = inspect.signature(runtime)
arg_types = []
arg_kinds = []
arg_names = []
for arg in signature.parameters.values():
arg_types.append(anytype())
arg_names.append(
None if arg.kind == inspect.Parameter.POSITIONAL_ONLY else arg.name
)
has_default = arg.default == inspect.Parameter.empty
if arg.kind == inspect.Parameter.POSITIONAL_ONLY:
arg_kinds.append(nodes.ARG_POS if has_default else nodes.ARG_OPT)
elif arg.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
arg_kinds.append(nodes.ARG_POS if has_default else nodes.ARG_OPT)
elif arg.kind == inspect.Parameter.KEYWORD_ONLY:
arg_kinds.append(nodes.ARG_NAMED if has_default else nodes.ARG_NAMED_OPT)
elif arg.kind == inspect.Parameter.VAR_POSITIONAL:
arg_kinds.append(nodes.ARG_STAR)
elif arg.kind == inspect.Parameter.VAR_KEYWORD:
arg_kinds.append(nodes.ARG_STAR2)
else:
raise AssertionError
except ValueError:
arg_types = [anytype(), anytype()]
arg_kinds = [nodes.ARG_STAR, nodes.ARG_STAR2]
arg_names = [None, None]

return mypy.types.CallableType(
arg_types,
arg_kinds,
arg_names,
ret_type=anytype(),
fallback=fallback,
is_ellipsis_args=True,
)

# Try and look up a stub for the runtime object
stub = get_stub(type(runtime).__module__)
Expand All @@ -914,9 +961,6 @@ def get_mypy_type_of_runtime_value(runtime: Any) -> Optional[mypy.types.Type]:
if not isinstance(type_info, nodes.TypeInfo):
return None

def anytype() -> mypy.types.AnyType:
return mypy.types.AnyType(mypy.types.TypeOfAny.unannotated)

if isinstance(runtime, tuple):
# Special case tuples so we construct a valid mypy.types.TupleType
optional_items = [get_mypy_type_of_runtime_value(v) for v in runtime]
Expand Down
6 changes: 6 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,12 @@ def test_var(self) -> Iterator[Case]:
runtime="x4 = (1, 3, 5)",
error="x4",
)
yield Case(stub="x5: int", runtime="def x5(a, b): pass", error="x5")
yield Case(
stub="def foo(a: int, b: int) -> None: ...\nx6 = foo",
runtime="def foo(a, b): pass\ndef x6(c, d): pass",
error="x6",
)
yield Case(
stub="""
class X:
Expand Down

0 comments on commit 24fdf34

Please sign in to comment.