Skip to content

Commit edd2b64

Browse files
committed
Merge pull request #645 from spkersten/no_type_checks
Support for no_type_check decorator
2 parents fd203b7 + d84ccfe commit edd2b64

File tree

4 files changed

+72
-14
lines changed

4 files changed

+72
-14
lines changed

mypy/checker.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
Decorator, SetExpr, PassStmt, TypeVarExpr, UndefinedExpr, PrintStmt,
1919
LITERAL_TYPE, BreakStmt, ContinueStmt, ComparisonExpr, StarExpr,
2020
YieldFromExpr, YieldFromStmt, NamedTupleExpr, SetComprehension,
21-
DictionaryComprehension, ComplexExpr, EllipsisNode, TypeAliasExpr
21+
DictionaryComprehension, ComplexExpr, EllipsisNode, TypeAliasExpr,
22+
RefExpr
2223
)
2324
from mypy.nodes import function_type, method_type, method_type_with_fallback
2425
from mypy import nodes
@@ -1659,6 +1660,11 @@ def visit_del_stmt(self, s: DelStmt) -> Type:
16591660
return None
16601661

16611662
def visit_decorator(self, e: Decorator) -> Type:
1663+
for d in e.decorators:
1664+
if isinstance(d, RefExpr):
1665+
if d.fullname == 'typing.no_type_check':
1666+
return NoneTyp()
1667+
16621668
e.func.accept(self)
16631669
sig = self.function_type(e.func) # type: Type
16641670
# Process decorators from the inside out.

mypy/parse.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,16 @@ def parse_metaclass(self) -> str:
310310

311311
def parse_decorated_function_or_class(self) -> Node:
312312
decorators = [] # type: List[Node]
313+
no_type_checks = False
313314
while self.current_str() == '@':
314315
self.expect('@')
315-
decorators.append(self.parse_expression())
316+
d_exp = self.parse_expression()
317+
if self.is_no_type_check_decorator(d_exp):
318+
no_type_checks = True
319+
decorators.append(d_exp)
316320
self.expect_break()
317321
if self.current_str() != 'class':
318-
func = self.parse_function()
322+
func = self.parse_function(no_type_checks)
319323
func.is_decorated = True
320324
var = Var(func.name())
321325
# Types of decorated functions must always be inferred.
@@ -328,13 +332,22 @@ def parse_decorated_function_or_class(self) -> Node:
328332
cls.decorators = decorators
329333
return cls
330334

331-
def parse_function(self) -> FuncDef:
335+
def is_no_type_check_decorator(self, expr: Node) -> bool:
336+
if isinstance(expr, NameExpr):
337+
return expr.name == 'no_type_check'
338+
elif isinstance(expr, MemberExpr):
339+
if isinstance(expr.expr, NameExpr):
340+
return expr.expr.name == 'typing' and expr.name == 'no_type_check'
341+
else:
342+
return False
343+
344+
def parse_function(self, no_type_checks: bool=False) -> FuncDef:
332345
def_tok = self.expect('def')
333346
is_method = self.is_class_body
334347
self.is_class_body = False
335348
try:
336349
(name, args, init, kinds,
337-
typ, is_error) = self.parse_function_header()
350+
typ, is_error) = self.parse_function_header(no_type_checks)
338351

339352
body, comment_type = self.parse_block(allow_type=True)
340353
if comment_type:
@@ -394,8 +407,8 @@ def check_argument_kinds(self, funckinds: List[int], sigkinds: List[int],
394407
"Inconsistent use of '{}' in function "
395408
"signature".format(token), line)
396409

397-
def parse_function_header(self) -> Tuple[str, List[Var], List[Node],
398-
List[int], CallableType, bool]:
410+
def parse_function_header(self, no_type_checks: bool=False) -> Tuple[str, List[Var], List[Node],
411+
List[int], CallableType, bool]:
399412
"""Parse function header (a name followed by arguments)
400413
401414
Returns a 7-tuple with the following items:
@@ -415,7 +428,7 @@ def parse_function_header(self) -> Tuple[str, List[Var], List[Node],
415428

416429
self.errors.push_function(name)
417430

418-
(args, init, kinds, typ) = self.parse_args()
431+
(args, init, kinds, typ) = self.parse_args(no_type_checks)
419432
except ParseError:
420433
if not isinstance(self.current(), Break):
421434
self.ind -= 1 # Kludge: go back to the Break token
@@ -426,21 +439,25 @@ def parse_function_header(self) -> Tuple[str, List[Var], List[Node],
426439

427440
return (name, args, init, kinds, typ, False)
428441

429-
def parse_args(self) -> Tuple[List[Var], List[Node], List[int], CallableType]:
442+
def parse_args(self, no_type_checks: bool=False) -> Tuple[List[Var], List[Node], List[int], CallableType]:
430443
"""Parse a function signature (...) [-> t]."""
431444
lparen = self.expect('(')
432445

433446
# Parse the argument list (everything within '(' and ')').
434447
(args, init, kinds,
435448
has_inits, arg_names,
436449
commas, asterisk,
437-
assigns, arg_types) = self.parse_arg_list()
450+
assigns, arg_types) = self.parse_arg_list(no_type_checks=no_type_checks)
438451

439452
self.expect(')')
440453

441454
if self.current_str() == '->':
442455
self.skip()
443-
ret_type = self.parse_type()
456+
if no_type_checks:
457+
self.parse_expression()
458+
ret_type = None # type: Type
459+
else:
460+
ret_type = self.parse_type()
444461
else:
445462
ret_type = None
446463

@@ -466,7 +483,7 @@ def build_func_annotation(self, ret_type: Type, arg_types: List[Type],
466483
return None
467484

468485
def parse_arg_list(
469-
self, allow_signature: bool = True) -> Tuple[List[Var], List[Node],
486+
self, allow_signature: bool = True, no_type_checks: bool=False) -> Tuple[List[Var], List[Node],
470487
List[int], bool,
471488
List[Token], List[Token],
472489
List[Token], List[Token],
@@ -517,13 +534,23 @@ def parse_arg_list(
517534
kinds.append(nodes.ARG_STAR2)
518535
else:
519536
kinds.append(nodes.ARG_STAR)
520-
arg_types.append(self.parse_arg_type(allow_signature))
537+
538+
if no_type_checks:
539+
self.parse_parameter_annotation()
540+
arg_types.append(None)
541+
else:
542+
arg_types.append(self.parse_arg_type(allow_signature))
521543
require_named = True
522544
else:
523545
name = self.expect_type(Name)
524546
arg_names.append(name)
525547
args.append(Var(name.string))
526-
arg_types.append(self.parse_arg_type(allow_signature))
548+
549+
if no_type_checks:
550+
self.parse_parameter_annotation()
551+
arg_types.append(None)
552+
else:
553+
arg_types.append(self.parse_arg_type(allow_signature))
527554

528555
if self.current_str() == '=':
529556
assigns.append(self.expect('='))
@@ -549,6 +576,11 @@ def parse_arg_list(
549576
return (args, init, kinds, has_inits, arg_names, commas, asterisk,
550577
assigns, arg_types)
551578

579+
def parse_parameter_annotation(self) -> Node:
580+
if self.current_str() == ':':
581+
self.skip()
582+
return self.parse_expression(precedence[','])
583+
552584
def parse_arg_type(self, allow_signature: bool) -> Type:
553585
if self.current_str() == ':' and allow_signature:
554586
self.skip()

mypy/test/data/check-functions.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,25 @@ def dec2(f: Callable[[Any, Any], None]) -> Callable[[Any], None]: pass
560560
def f(x, y): pass
561561

562562

563+
[case testNoTypeCheckDecoratorOnMethod1]
564+
from typing import no_type_check
565+
566+
@no_type_check
567+
def foo(x: 'bar', y: {'x': 4}) -> 42:
568+
1 + 'x'
569+
570+
[case testNoTypeCheckDecoratorOnMethod2]
571+
import typing
572+
573+
@typing.no_type_check
574+
def foo(x: 's', y: {'x': 4}) -> 42:
575+
1 + 'x'
576+
577+
@typing.no_type_check
578+
def bar() -> None:
579+
1 + 'x'
580+
581+
563582
-- Conditional function definition
564583
-- -------------------------------
565584

mypy/test/data/lib-stub/typing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
builtinclass = object()
1717
_promote = object()
1818
NamedTuple = object()
19+
no_type_check = object()
1920

2021
# Type aliases.
2122
List = object()

0 commit comments

Comments
 (0)