Skip to content

Add --disallow-untyped-functions #1285

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
FAST_PARSER = 'fast-parser' # Use experimental fast parser
# Disallow calling untyped functions from typed ones
DISALLOW_UNTYPED_CALLS = 'disallow-untyped-calls'
# Disallow defining untyped (or incompletely typed) functions
DISALLOW_UNTYPED_DEFS = 'disallow-untyped-defs'

# State ids. These describe the states a source file / module can be in a
# build.
Expand Down Expand Up @@ -383,7 +385,8 @@ def __init__(self, data_dir: str,
self.type_checker = TypeChecker(self.errors,
modules,
self.pyversion,
DISALLOW_UNTYPED_CALLS in self.flags)
DISALLOW_UNTYPED_CALLS in self.flags,
DISALLOW_UNTYPED_DEFS in self.flags)
self.states = [] # type: List[State]
self.module_files = {} # type: Dict[str, str]
self.module_deps = {} # type: Dict[Tuple[str, str], bool]
Expand Down
18 changes: 17 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,10 +366,12 @@ class TypeChecker(NodeVisitor[Type]):
current_node_deferred = False
# This makes it an error to call an untyped function from a typed one
disallow_untyped_calls = False
# This makes it an error to define an untyped or partially-typed function
disallow_untyped_defs = False

def __init__(self, errors: Errors, modules: Dict[str, MypyFile],
pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION,
disallow_untyped_calls=False) -> None:
disallow_untyped_calls=False, disallow_untyped_defs=False) -> None:
"""Construct a type checker.

Use errors to report type check errors. Assume symtable has been
Expand All @@ -393,6 +395,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile],
self.pass_num = 0
self.current_node_deferred = False
self.disallow_untyped_calls = disallow_untyped_calls
self.disallow_untyped_defs = disallow_untyped_defs

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

if self.disallow_untyped_defs:
# 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)

if name in nodes.reverse_op_method_set:
self.check_reverse_op_method(item, typ, name)
elif name == '__getattr__':
Expand Down
5 changes: 5 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ def process_options(args: List[str]) -> Tuple[List[BuildSource], Options]:
elif args[0] == '--disallow-untyped-calls':
options.build_flags.append(build.DISALLOW_UNTYPED_CALLS)
args = args[1:]
elif args[0] == '--disallow-untyped-defs':
options.build_flags.append(build.DISALLOW_UNTYPED_DEFS)
args = args[1:]
elif args[0] in ('--version', '-V'):
ver = True
args = args[1:]
Expand Down Expand Up @@ -316,6 +319,8 @@ def usage(msg: str = None) -> None:
-s, --silent-imports don't follow imports to .py files
--disallow-untyped-calls disallow calling functions without type annotations
from functions with type annotations
--disallow-untyped-defs disallow defining functions without type annotations
or with incomplete type annotations
--implicit-any behave as though all functions were annotated with Any
-f, --dirty-stubs don't warn if typeshed is out of sync
--pdb invoke pdb on fatal error
Expand Down
3 changes: 3 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@
RETURN_TYPE_CANNOT_BE_CONTRAVARIANT = "Cannot use a contravariant type variable as return type"
FUNCTION_PARAMETER_CANNOT_BE_COVARIANT = "Cannot use a covariant type variable as a parameter"
INCOMPATIBLE_IMPORT_OF = "Incompatible import of"
FUNCTION_TYPE_EXPECTED = "Function is missing a type annotation"
RETURN_TYPE_EXPECTED = "Function is missing a return type annotation"
ARGUMENT_TYPE_EXPECTED = "Function is missing a type annotation for one or more arguments"


class MessageBuilder:
Expand Down
8 changes: 4 additions & 4 deletions mypy/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,8 @@ def parse_function(self, no_type_checks: bool=False) -> FuncDef:
if is_method and name == '__init__':
ret_type = UnboundType('None', [])
else:
ret_type = AnyType()
typ = CallableType([AnyType() for _ in args],
ret_type = AnyType(implicit=True)
typ = CallableType([AnyType(implicit=True) for _ in args],
arg_kinds,
[a.variable.name() for a in args],
ret_type,
Expand Down Expand Up @@ -812,9 +812,9 @@ def construct_function_type(self, args: List[Argument], ret_type: Type,
arg_types = [arg.type_annotation for arg in args]
for i in range(len(arg_types)):
if arg_types[i] is None:
arg_types[i] = AnyType()
arg_types[i] = AnyType(implicit=True)
if ret_type is None:
ret_type = AnyType()
ret_type = AnyType(implicit=True)
arg_kinds = [arg.kind for arg in args]
arg_names = [arg.variable.name() for arg in args]
return CallableType(arg_types, arg_kinds, arg_names, ret_type, None, name=None,
Expand Down
32 changes: 32 additions & 0 deletions mypy/test/data/check-flags.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# test cases for --disallow-untyped-defs

[case testUnannotatedFunction]
# flags: disallow-untyped-defs
def f(x): pass
[out]
main: note: In function "f":
main:2: error: Function is missing a type annotation

[case testUnannotatedArgument]
# flags: disallow-untyped-defs
def f(x) -> int: pass
[out]
main: note: In function "f":
main:2: error: Function is missing a type annotation for one or more arguments

[case testNoArgumentFunction]
# flags: disallow-untyped-defs
def f() -> int: pass
[out]

[case testUnannotatedReturn]
# flags: disallow-untyped-defs
def f(x: int): pass
[out]
main: note: In function "f":
main:2: error: Function is missing a return type annotation

[case testLambda]
# flags: disallow-untyped-defs
lambda x: x
[out]
4 changes: 3 additions & 1 deletion mypy/test/data/lib-stub/builtins.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
class Any: pass

class object:
def __init__(self) -> None: pass

class type:
def __init__(self, x) -> None: pass
def __init__(self, x: Any) -> None: pass

# These are provided here for convenience.
class int: pass
Expand Down
13 changes: 11 additions & 2 deletions mypy/test/testcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import re
import sys

from typing import Tuple
from typing import Tuple, List

from mypy import build
import mypy.myunit # for mutable globals (ick!)
Expand Down Expand Up @@ -53,6 +53,7 @@
'check-ignore.test',
'check-type-promotion.test',
'check-semanal-error.test',
'check-flags.test',
]


Expand All @@ -69,12 +70,13 @@ def run_test(self, testcase):
pyversion = testcase_pyversion(testcase.file, testcase.name)
program_text = '\n'.join(testcase.input)
module_name, program_name, program_text = self.parse_options(program_text)
flags = self.parse_flags(program_text)
source = BuildSource(program_name, module_name, program_text)
try:
build.build(target=build.TYPE_CHECK,
sources=[source],
pyversion=pyversion,
flags=[build.TEST_BUILTINS],
flags=flags + [build.TEST_BUILTINS],
alt_lib_path=test_temp_dir)
except CompileError as e:
a = normalize_error_messages(e.messages)
Expand Down Expand Up @@ -109,3 +111,10 @@ def parse_options(self, program_text: str) -> Tuple[str, str, str]:
return m.group(1), path, program_text
else:
return '__main__', 'main', program_text

def parse_flags(self, program_text: str) -> List[str]:
m = re.search('# flags: (.*)$', program_text, flags=re.MULTILINE)
if m:
return m.group(1).split()
else:
return []
4 changes: 4 additions & 0 deletions mypy/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ def accept(self, visitor: 'TypeVisitor[T]') -> T:
class AnyType(Type):
"""The type 'Any'."""

def __init__(self, implicit=False, line: int = -1) -> None:
super().__init__(line)
self.implicit = implicit

def accept(self, visitor: 'TypeVisitor[T]') -> T:
return visitor.visit_any(self)

Expand Down