Repository: python/mypy

Issue:
Flag to disallow partially annotated functions
It would be useful to have a flag to disallow partially annotated functions.

It would work something like this:
```py
def f(i: int):  # error: Missing type annotation
  pass

def g(i):  # no error
  pass

def h(i: int) -> None:  # no error
  pass
```


Discussion:

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -619,19 +619,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None:
                         self.fail(messages.MUST_HAVE_NONE_RETURN_TYPE.format(fdef.name()),
                                   item)
 
-                    show_untyped = not self.is_typeshed_stub or self.options.warn_incomplete_stub
-                    if self.options.disallow_untyped_defs and show_untyped:
-                        # Check for functions with unspecified/not fully specified types.
-                        def is_implicit_any(t: Type) -> bool:
-                            return isinstance(t, AnyType) and t.implicit
-
-                        if fdef.type is None:
-                            self.fail(messages.FUNCTION_TYPE_EXPECTED, fdef)
-                        elif isinstance(fdef.type, CallableType):
-                            if is_implicit_any(fdef.type.ret_type):
-                                self.fail(messages.RETURN_TYPE_EXPECTED, fdef)
-                            if any(is_implicit_any(t) for t in fdef.type.arg_types):
-                                self.fail(messages.ARGUMENT_TYPE_EXPECTED, fdef)
+                    self.check_for_missing_annotations(fdef)
                     if 'unimported' in self.options.disallow_any:
                         if fdef.type and isinstance(fdef.type, CallableType):
                             ret_type = fdef.type.ret_type
@@ -773,6 +761,26 @@ def is_implicit_any(t: Type) -> bool:
 
             self.binder = old_binder
 
+    def check_for_missing_annotations(self, fdef: FuncItem) -> None:
+        # Check for functions with unspecified/not fully specified types.
+        def is_implicit_any(t: Type) -> bool:
+            return isinstance(t, AnyType) and t.implicit
+
+        has_explicit_annotation = (isinstance(fdef.type, CallableType)
+                                   and any(not is_implicit_any(t)
+                                           for t in fdef.type.arg_types + [fdef.type.ret_type]))
+
+        show_untyped = not self.is_typeshed_stub or self.options.warn_incomplete_stub
+        check_incomplete_defs = self.options.disallow_incomplete_defs and has_explicit_annotation
+        if show_untyped and (self.options.disallow_untyped_defs or check_incomplete_defs):
+            if fdef.type is None and self.options.disallow_untyped_defs:
+                self.fail(messages.FUNCTION_TYPE_EXPECTED, fdef)
+            elif isinstance(fdef.type, CallableType):
+                if is_implicit_any(fdef.type.ret_type):
+                    self.fail(messages.RETURN_TYPE_EXPECTED, fdef)
+                if any(is_implicit_any(t) for t in fdef.type.arg_types):
+                    self.fail(messages.ARGUMENT_TYPE_EXPECTED, fdef)
+
     def is_trivial_body(self, block: Block) -> bool:
         body = block.body
 
diff --git a/mypy/main.py b/mypy/main.py
--- a/mypy/main.py
+++ b/mypy/main.py
@@ -258,6 +258,8 @@ def add_invertible_flag(flag: str,
     add_invertible_flag('--disallow-untyped-defs', default=False, strict_flag=True,
                         help="disallow defining functions without type annotations"
                         " or with incomplete type annotations")
+    add_invertible_flag('--disallow-incomplete-defs', default=False, strict_flag=True,
+                        help="disallow defining functions with incomplete type annotations")
     add_invertible_flag('--check-untyped-defs', default=False, strict_flag=True,
                         help="type check the interior of functions without type annotations")
     add_invertible_flag('--disallow-subclassing-any', default=False, strict_flag=True,
diff --git a/mypy/options.py b/mypy/options.py
--- a/mypy/options.py
+++ b/mypy/options.py
@@ -57,6 +57,9 @@ def __init__(self) -> None:
         # Disallow defining untyped (or incompletely typed) functions
         self.disallow_untyped_defs = False
 
+        # Disallow defining incompletely typed functions
+        self.disallow_incomplete_defs = False
+
         # Type check unannotated functions
         self.check_untyped_defs = False
 


Test patch:
diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test
--- a/test-data/unit/check-flags.test
+++ b/test-data/unit/check-flags.test
@@ -898,3 +898,37 @@ Movie = TypedDict('Movie', {'name': str, 'year': int})
 def g(m: Movie) -> Movie:
     return m
 [builtins fixtures/dict.pyi]
+
+[case testDisallowIncompleteDefs]
+# flags: --disallow-incomplete-defs
+
+def f(i: int):  # E: Function is missing a return type annotation
+    pass
+def g(i) -> None:  # E: Function is missing a type annotation for one or more arguments
+    pass
+def h(i: int) -> int:  # no error
+    return i
+def i() -> None:  # no error
+    pass
+
+[case testDisallowIncompleteDefsNoReturn]
+# flags: --disallow-incomplete-defs --disallow-untyped-defs
+
+def f(i: int):  # E: Function is missing a return type annotation
+    pass
+
+[case testDisallowIncompleteDefsSelf]
+# flags: --disallow-incomplete-defs
+class C:
+    def foo(self) -> None:  # no error
+        pass
+
+[case testDisallowIncompleteDefsPartiallyAnnotatedParams]
+# flags: --disallow-incomplete-defs
+
+def f(i: int, s):
+    pass
+
+[out]
+main:3: error: Function is missing a return type annotation
+main:3: error: Function is missing a type annotation for one or more arguments