Skip to content

Commit a67157e

Browse files
committed
Add --disallow-untyped-functions
1 parent 67d9319 commit a67157e

File tree

6 files changed

+38
-6
lines changed

6 files changed

+38
-6
lines changed

mypy/build.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@
5454
FAST_PARSER = 'fast-parser' # Use experimental fast parser
5555
# Disallow calling untyped functions from typed ones
5656
DISALLOW_UNTYPED_CALLS = 'disallow-untyped-calls'
57+
# Disallow defining untyped (or incompletely typed) functions
58+
DISALLOW_UNTYPED_FUNCS = 'disallow-untyped-funcs'
5759

5860
# State ids. These describe the states a source file / module can be in a
5961
# build.
@@ -383,7 +385,8 @@ def __init__(self, data_dir: str,
383385
self.type_checker = TypeChecker(self.errors,
384386
modules,
385387
self.pyversion,
386-
DISALLOW_UNTYPED_CALLS in self.flags)
388+
DISALLOW_UNTYPED_CALLS in self.flags,
389+
DISALLOW_UNTYPED_FUNCS in self.flags)
387390
self.states = [] # type: List[State]
388391
self.module_files = {} # type: Dict[str, str]
389392
self.module_deps = {} # type: Dict[Tuple[str, str], bool]

mypy/checker.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,12 @@ class TypeChecker(NodeVisitor[Type]):
366366
current_node_deferred = False
367367
# This makes it an error to call an untyped function from a typed one
368368
disallow_untyped_calls = False
369+
# This makes it an error to define an untyped or partially-typed function
370+
disallow_untyped_funcs = False
369371

370372
def __init__(self, errors: Errors, modules: Dict[str, MypyFile],
371373
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
372-
disallow_untyped_calls=False) -> None:
374+
disallow_untyped_calls=False, disallow_untyped_funcs=False) -> None:
373375
"""Construct a type checker.
374376
375377
Use errors to report type check errors. Assume symtable has been
@@ -393,6 +395,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile],
393395
self.pass_num = 0
394396
self.current_node_deferred = False
395397
self.disallow_untyped_calls = disallow_untyped_calls
398+
self.disallow_untyped_funcs = disallow_untyped_funcs
396399

397400
def visit_file(self, file_node: MypyFile, path: str) -> None:
398401
"""Type check a mypy file with the given path."""
@@ -658,6 +661,20 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: str) -> None:
658661
self.fail(messages.INIT_MUST_HAVE_NONE_RETURN_TYPE,
659662
item.type)
660663

664+
if self.disallow_untyped_funcs:
665+
# Check for functions with unspecified/not fully specified types.
666+
def is_implicit_any(t: Type) -> bool:
667+
return isinstance(t, AnyType) and t.implicit
668+
669+
if fdef.type is None:
670+
self.fail(messages.FUNCTION_TYPE_EXPECTED, fdef)
671+
elif isinstance(fdef.type, CallableType):
672+
if is_implicit_any(fdef.type.ret_type):
673+
self.fail(messages.RETURN_TYPE_EXPECTED, fdef)
674+
if any(is_implicit_any(t) for t in fdef.type.arg_types):
675+
self.fail(messages.ARGUMENT_TYPE_EXPECTED, fdef)
676+
677+
661678
if name in nodes.reverse_op_method_set:
662679
self.check_reverse_op_method(item, typ, name)
663680
elif name == '__getattr__':

mypy/main.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ def process_options(args: List[str]) -> Tuple[List[BuildSource], Options]:
180180
elif args[0] == '--disallow-untyped-calls':
181181
options.build_flags.append(build.DISALLOW_UNTYPED_CALLS)
182182
args = args[1:]
183+
elif args[0] == '--disallow-untyped-functions':
184+
options.build_flags.append(build.DISALLOW_UNTYPED_FUNCS)
185+
args = args[1:]
183186
elif args[0] in ('--version', '-V'):
184187
ver = True
185188
args = args[1:]
@@ -315,6 +318,8 @@ def usage(msg: str = None) -> None:
315318
-s, --silent-imports don't follow imports to .py files
316319
--disallow-untyped-calls disallow calling functions without type annotations
317320
from functions with type annotations
321+
--disallow-untyped-funcs disallow defining functions without type annotations
322+
or with incomplete type annotations
318323
--implicit-any behave as though all functions were annotated with Any
319324
-f, --dirty-stubs don't warn if typeshed is out of sync
320325
--pdb invoke pdb on fatal error

mypy/messages.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@
7373
RETURN_TYPE_CANNOT_BE_CONTRAVARIANT = "Cannot use a contravariant type variable as return type"
7474
FUNCTION_PARAMETER_CANNOT_BE_COVARIANT = "Cannot use a covariant type variable as a parameter"
7575
INCOMPATIBLE_IMPORT_OF = "Incompatible import of"
76+
FUNCTION_TYPE_EXPECTED = "Function is missing a type annotation"
77+
RETURN_TYPE_EXPECTED = "Function is missing a return type annotation"
78+
ARGUMENT_TYPE_EXPECTED = "Function is missing a type annotation for one or more arguments"
7679

7780

7881
class MessageBuilder:

mypy/parse.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,8 @@ def parse_function(self, no_type_checks: bool=False) -> FuncDef:
477477
if is_method and name == '__init__':
478478
ret_type = UnboundType('None', [])
479479
else:
480-
ret_type = AnyType()
481-
typ = CallableType([AnyType() for _ in args],
480+
ret_type = AnyType(implicit=True)
481+
typ = CallableType([AnyType(implicit=True) for _ in args],
482482
arg_kinds,
483483
[a.variable.name() for a in args],
484484
ret_type,
@@ -812,9 +812,9 @@ def construct_function_type(self, args: List[Argument], ret_type: Type,
812812
arg_types = [arg.type_annotation for arg in args]
813813
for i in range(len(arg_types)):
814814
if arg_types[i] is None:
815-
arg_types[i] = AnyType()
815+
arg_types[i] = AnyType(implicit=True)
816816
if ret_type is None:
817-
ret_type = AnyType()
817+
ret_type = AnyType(implicit=True)
818818
arg_kinds = [arg.kind for arg in args]
819819
arg_names = [arg.variable.name() for arg in args]
820820
return CallableType(arg_types, arg_kinds, arg_names, ret_type, None, name=None,

mypy/types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ def accept(self, visitor: 'TypeVisitor[T]') -> T:
102102
class AnyType(Type):
103103
"""The type 'Any'."""
104104

105+
def __init__(self, implicit=False, line: int = -1) -> None:
106+
super().__init__(line)
107+
self.implicit = implicit
108+
105109
def accept(self, visitor: 'TypeVisitor[T]') -> T:
106110
return visitor.visit_any(self)
107111

0 commit comments

Comments
 (0)