Repository: python/mypy

Issue:
stubgen cli flag to emit `Any` type for untyped function arguments.
This is a feature request for stubgen.

In my use-case, I am executing `mypy --strict --ignore-missing-imports` on my package's source files and I use stubgen to generate stubs for my package so that other scripts that import my package can type check against it.  I wrote something like:

```py
class _MyPrivateClass(third_party_util.TheirClass):
    ...
```

In this case mypy complains because I'm inheriting from something from a third party library that has no stubs.  My first inclination was to simply generate stubs for the third party library, but they aren't annotated and the stubs generated by `stubgen` seem to actively suppress writing `Any` annotations in function arguments.

e.g.:

```py
# stub_test.py
import typing

class Foo:
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c
```

```py
# stub_test.pyi
# Stubs for stub_test (Python 3.6)
#
# NOTE: This dynamically typed stub was automatically generated by stubgen.

from typing import Any

class Foo:
    a: Any = ...
    b: Any = ...
    c: Any = ...
    def __init__(self, a, b, c) -> None: ...
```

For the purposes of checking against this, it'd be really helpful if stubgen actually made the signature of `Foo.__init__` to be `def __init__(self, a: Any, b: Any, c: Any) -> None ...`.  As implemented currently, I think that this will cause warnings to be generated by `mypy --strict` for anything that relies on these stubs (I think `--disallow-untyped-calls` is the culprit) whereas the proposed solution would allow that mypy flag to pass and then I can assert that (at the very least), the code under my control has type annotations for all the functions. 

I'm not sure if this behavior should be accepted outright, or hidden behind a commandline argument, but it feels like there _should_ be a way to generate stubs where everything is typed (even if the type is `Any`).




Discussion:
Have you tested your assumption that this makes any difference (e.g. by manually adding some `Any` annotations to a generated stub)? An argument without a type is assumed to have type `Any`, and a missing return type is likewise assumed to be `Any`.

If you have a concrete (small) example where adding the `Any` annotations makes a difference please show it.
Sure, stubs aside, the following code behaves differently depending on which `__init__` line you have commented out:

```py
"""Quick test.

This is quick_test.py

`mypy --strict quick_test.py` will behave differently depending on which `__init__` you comment out.
"""
from typing import Any

class Foo:
    def __init__(self, a, b, c) -> None:
    # def __init__(self, a: Any, b: Any, c: Any) -> None:
        self.a = a
        self.b = b
        self.c = c

def do_something() -> None:
    Foo(10, 12, 13)
```

I'm not actually sure what flag in mypy is tickling this warning though:

```sh
$ mypy --strict --allow-untyped-calls --allow-untyped-defs --allow-incomplete-defs quick_test.py 
$ mypy --strict --allow-incomplete-defs quick_test.py 
quick_test.py:10: error: Function is missing a type annotation for one or more arguments
$ mypy --strict --allow-untyped-calls quick_test.py 
quick_test.py:10: error: Function is missing a type annotation for one or more arguments
$ mypy --strict --allow-untyped-defs quick_test.py 
quick_test.py:10: error: Function is missing a type annotation for one or more arguments
$ mypy --version
mypy 0.570
```

It's --disallow-incomplete-defs, which is implied by --strict (or
--disallow-untyped-defs, if none of the arguments nor the return type are
annotated.

So I'll concede this is something that stubgen might add. Perhaps you can
come up with a PR? It shouldn't be too complicated.

I might be able to come up with a PR.  Do we need to decide on whether to toggle this behind a flag, or do we just add `Any` every time it was inferred?
I think it's fine to always do this.

On Tue, Mar 20, 2018 at 11:06 AM, Matt Gilson <notifications@github.com>
wrote:

> I might be able to come up with a PR. Do we need to decide on whether to
> toggle this behind a flag, or do we just add Any all the time?
>
> —
> You are receiving this because you commented.
> Reply to this email directly, view it on GitHub
> <https://github.com/python/mypy/issues/4766#issuecomment-374699905>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/ACwrMqWnRCwSmvF3H5WcXiIyUNfmT0Iyks5tgUUzgaJpZM4SyWAi>
> .
>



-- 
--Guido van Rossum (python.org/~guido)


Resolving patch:
diff --git a/mypy/stubgen.py b/mypy/stubgen.py
--- a/mypy/stubgen.py
+++ b/mypy/stubgen.py
@@ -472,8 +472,15 @@ def visit_func_def(self, o: FuncDef) -> None:
             kind = arg_.kind
             name = var.name()
             annotated_type = o.type.arg_types[i] if isinstance(o.type, CallableType) else None
-            if annotated_type and not (
-                    i == 0 and name == 'self' and isinstance(annotated_type, AnyType)):
+            is_self_arg = i == 0 and name == 'self'
+            is_cls_arg = i == 0 and name == 'cls'
+            if (annotated_type is None
+                    and not arg_.initializer
+                    and not is_self_arg
+                    and not is_cls_arg):
+                self.add_typing_import("Any")
+                annotation = ": Any"
+            elif annotated_type and not is_self_arg:
                 annotation = ": {}".format(self.print_annotation(annotated_type))
             else:
                 annotation = ""


Test patch:
diff --git a/test-data/unit/stubgen.test b/test-data/unit/stubgen.test
--- a/test-data/unit/stubgen.test
+++ b/test-data/unit/stubgen.test
@@ -13,14 +13,18 @@ def f(a, b):
 def g(arg):
     pass
 [out]
-def f(a, b) -> None: ...
-def g(arg) -> None: ...
+from typing import Any
+
+def f(a: Any, b: Any) -> None: ...
+def g(arg: Any) -> None: ...
 
 [case testDefaultArgInt]
 def f(a, b=2): ...
 def g(b=-1, c=0): ...
 [out]
-def f(a, b: int = ...) -> None: ...
+from typing import Any
+
+def f(a: Any, b: int = ...) -> None: ...
 def g(b: int = ..., c: int = ...) -> None: ...
 
 [case testDefaultArgNone]
@@ -75,12 +79,16 @@ x: Foo
 [case testVarArgs]
 def f(x, *y): ...
 [out]
-def f(x, *y) -> None: ...
+from typing import Any
+
+def f(x: Any, *y: Any) -> None: ...
 
 [case testKwVarArgs]
 def f(x, **y): ...
 [out]
-def f(x, **y) -> None: ...
+from typing import Any
+
+def f(x: Any, **y: Any) -> None: ...
 
 [case testVarArgsWithKwVarArgs]
 def f(a, *b, **c): ...
@@ -89,11 +97,13 @@ def h(a, *b, c=1, **d): ...
 def i(a, *, b=1): ...
 def j(a, *, b=1, **c): ...
 [out]
-def f(a, *b, **c) -> None: ...
-def g(a, *b, c: int = ...) -> None: ...
-def h(a, *b, c: int = ..., **d) -> None: ...
-def i(a, *, b: int = ...) -> None: ...
-def j(a, *, b: int = ..., **c) -> None: ...
+from typing import Any
+
+def f(a: Any, *b: Any, **c: Any) -> None: ...
+def g(a: Any, *b: Any, c: int = ...) -> None: ...
+def h(a: Any, *b: Any, c: int = ..., **d: Any) -> None: ...
+def i(a: Any, *, b: int = ...) -> None: ...
+def j(a: Any, *, b: int = ..., **c: Any) -> None: ...
 
 [case testClass]
 class A:
@@ -101,8 +111,10 @@ class A:
         x = 1
 def g(): ...
 [out]
+from typing import Any
+
 class A:
-    def f(self, x) -> None: ...
+    def f(self, x: Any) -> None: ...
 
 def g() -> None: ...
 
@@ -241,7 +253,9 @@ class B(A): ...
 @decorator
 def foo(x): ...
 [out]
-def foo(x) -> None: ...
+from typing import Any
+
+def foo(x: Any) -> None: ...
 
 [case testMultipleAssignment]
 x, y = 1, 2
@@ -269,8 +283,10 @@ y: Any
 def f(x, *, y=1): ...
 def g(x, *, y=1, z=2): ...
 [out]
-def f(x, *, y: int = ...) -> None: ...
-def g(x, *, y: int = ..., z: int = ...) -> None: ...
+from typing import Any
+
+def f(x: Any, *, y: int = ...) -> None: ...
+def g(x: Any, *, y: int = ..., z: int = ...) -> None: ...
 
 [case testProperty]
 class A:
@@ -280,20 +296,24 @@ class A:
     @f.setter
     def f(self, x): ...
 [out]
+from typing import Any
+
 class A:
     @property
     def f(self): ...
     @f.setter
-    def f(self, x) -> None: ...
+    def f(self, x: Any) -> None: ...
 
 [case testStaticMethod]
 class A:
     @staticmethod
     def f(x): ...
 [out]
+from typing import Any
+
 class A:
     @staticmethod
-    def f(x) -> None: ...
+    def f(x: Any) -> None: ...
 
 [case testClassMethod]
 class A:
@@ -695,13 +715,17 @@ class A(X): ...
 def syslog(a): pass
 def syslog(a): pass
 [out]
-def syslog(a) -> None: ...
+from typing import Any
+
+def syslog(a: Any) -> None: ...
 
 [case testAsyncAwait_fast_parser]
 async def f(a):
    x = await y
 [out]
-def f(a) -> None: ...
+from typing import Any
+
+def f(a: Any) -> None: ...
 
 [case testInferOptionalOnlyFunc]
 class A: