Skip to content
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 docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ Here are some more useful flags:

- ``--disallow-any`` disallows various types of ``Any`` in a module.
The option takes a comma-separated list of the following values:
``unimported``, ``unannotated``, ``expr``.
``unimported``, ``unannotated``, ``expr``, ``decorated``.

``unimported`` disallows usage of types that come from unfollowed imports
(such types become aliases for ``Any``). Unfollowed imports occur either
Expand All @@ -298,6 +298,9 @@ Here are some more useful flags:
or casting to type ``Any`` is not allowed. Note that calling functions
that take parameters of type ``Any`` is still allowed.

``decorated`` disallows functions that have ``Any`` in their signature
after decorator transformation.


- ``--disallow-untyped-defs`` reports an error whenever it encounters
a function definition without type annotations.
Expand Down
8 changes: 8 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2267,6 +2267,7 @@ def visit_decorator(self, e: Decorator) -> None:
sig, t2 = self.expr_checker.check_call(dec, [temp],
[nodes.ARG_POS], e,
callable_name=fullname)
self.check_untyped_after_decorator(sig, e.func)
sig = cast(FunctionLike, sig)
sig = set_callable_name(sig, e.func)
e.var.type = sig
Expand Down Expand Up @@ -2295,6 +2296,13 @@ def visit_with_stmt(self, s: WithStmt) -> None:
self.check_with_item(expr, target, s.target_type is None)
self.accept(s.body)

def check_untyped_after_decorator(self, typ: Type, func: FuncDef) -> None:
if 'decorated' not in self.options.disallow_any or self.is_stub:
return

if mypy.checkexpr.has_any_type(typ):
self.msg.untyped_decorated_function(typ, func)

def check_async_with_item(self, expr: Expression, target: Expression,
infer_lvalue_type: bool) -> None:
echk = self.expr_checker
Expand Down
2 changes: 1 addition & 1 deletion mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def type_check_only(sources: List[BuildSource], bin_dir: str, options: Options)
options=options)


disallow_any_options = ['unimported', 'expr', 'unannotated']
disallow_any_options = ['unimported', 'expr', 'unannotated', 'decorated']


def disallow_any_argument_type(raw_options: str) -> List[str]:
Expand Down
7 changes: 7 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,13 @@ def disallowed_any_type(self, typ: Type, context: Context) -> None:
message = 'Expression type contains "Any" (has type {})'.format(self.format(typ))
self.fail(message, context)

def untyped_decorated_function(self, typ: Type, context: Context) -> None:
if isinstance(typ, AnyType):
self.fail("Function is untyped after decorator transformation", context)
else:
self.fail('Type of decorated function contains type "Any" ({})'.format(
self.format(typ)), context)


def capitalize(s: str) -> str:
"""Capitalize the first character of a string."""
Expand Down
120 changes: 120 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,126 @@ x, y = 1, 2 # type: Unchecked, Unchecked
main:4: error: Type of variable becomes "Any" due to an unfollowed import
main:6: error: A type on this line becomes "Any" due to an unfollowed import

[case testDisallowAnyDecoratedUnannotatedDecorator]
# flags: --disallow-any=decorated
from typing import Any

def d(f):
return f

@d
def f(x: Any) -> Any: # E: Function is untyped after decorator transformation
pass
@d
def h(x): # E: Function is untyped after decorator transformation
pass
[builtins fixtures/list.pyi]
[case testDisallowAnyDecoratedErrorIsReportedOnlyOnce]
# flags: --disallow-any=decorated

def d(f):
return f

def d2(f):
return f

@d
@d2
@d
def f(x: int) -> None: pass # E: Function is untyped after decorator transformation
[case testDisallowAnyDecoratedReturnAny]
# flags: --disallow-any=decorated
from typing import Any

def d(f) -> Any:
return f

@d
def f() -> None: pass # E: Function is untyped after decorator transformation
[builtins fixtures/list.pyi]
[case testDisallowAnyDecoratedReturnCallable]
# flags: --disallow-any=decorated
from typing import Any, Callable

def d(f) -> Callable[..., None]:
return f

@d
def g(i: int, s: str) -> None: pass # E: Type of decorated function contains type "Any" (Callable[..., None])

[builtins fixtures/list.pyi]
[case testDisallowAnyDecoratedNonexistentDecorator]
# flags: --disallow-any=decorated --ignore-missing-imports
from nonexistent import d

@d
def f() -> None: pass # E: Function is untyped after decorator transformation
[builtins fixtures/list.pyi]

[case testDisallowAnyDecoratedPartlyTypedCallable]
# flags: --disallow-any=decorated --ignore-missing-imports
from typing import Callable, Any, List

def d(f) -> Callable[[int, Any], Any]: pass
def d2(f) -> Callable[[int], List[Any]]: pass

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be good to also test a callable which doesn't have Any as part of its return value.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I was unclear. The test you added is good, but it'd also be worth testing a Callable that contains Any but not as part of its return value. (It looks to me like this case is currently not covered by any of the tests.)

@ilinum ilinum Jun 21, 2017 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, this should be good now.
Test with Callable that takes parameters of type Any but returns something that's not Any.

def d3(f) -> Callable[[Any], List[str]]: pass

@d
def f(i: int, s: str) -> None: # E: Type of decorated function contains type "Any" (Callable[[int, Any], Any])
pass
@d2
def g(i: int) -> None: # E: Type of decorated function contains type "Any" (Callable[[int], List[Any]])
pass
@d3
def h(i: int) -> None: # E: Type of decorated function contains type "Any" (Callable[[Any], List[str]])
pass
[builtins fixtures/list.pyi]

[case testDisallowAnyDecoratedReturnsCallableNoParams]
# flags: --disallow-any=decorated
from typing import Callable

def d(p) -> Callable[[], int]:
return p

@d
def f(i):
return i
[builtins fixtures/list.pyi]

[case testDisallowAnyDecoratedDecoratorReturnsNonCallable]
# flags: --disallow-any=decorated
def d(p) -> int:
return p(0)

@d
def f(i):
return i

[case testDisallowAnyDecoratedUntypedUndecoratedFunction]
# flags: --disallow-any=decorated
from typing import Callable

def f(i): # no error
return i

[case testDisallowAnyDecoratedTwoDecorators]
# flags: --disallow-any=decorated
from typing import Callable

def typed_dec(f) -> Callable[[], int]: pass
def untyped_dec(f): pass

@typed_dec
@untyped_dec
def f(): # no error
return i

@untyped_dec
@typed_dec
def g(): # E: Function is untyped after decorator transformation
return i

[case testDisallowAnyExprSimple]
# flags: --disallow-any=expr
from typing import Any
Expand Down