Skip to content

Commit f19e6e4

Browse files
ilinumddfisher
authored andcommitted
Option to disallow all expressions of type Any(--disallow-any=expr) (#3519)
This option disallows all expressions of type Any except: * if a value of type Any used as a second parameter to `cast` * if a value of type Any is assigned to a variable with an explicit type annotation
1 parent 9ff0f0b commit f19e6e4

File tree

7 files changed

+124
-8
lines changed

7 files changed

+124
-8
lines changed

docs/source/command_line.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ Here are some more useful flags:
278278

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

283283
``unimported`` disallows usage of types that come from unfollowed imports
284284
(such types become aliases for ``Any``). Unfollowed imports occur either
@@ -290,6 +290,15 @@ Here are some more useful flags:
290290
of the parameters or the return type). ``unannotated`` option is
291291
interchangeable with ``--disallow-untyped-defs``.
292292

293+
``expr`` disallows all expressions in the module that have type ``Any``.
294+
If an expression of type ``Any`` appears anywhere in the module
295+
mypy will output an error unless the expression is immediately
296+
used as an argument to ``cast`` or assigned to a variable with an
297+
explicit type annotation. In addition, declaring a variable of type ``Any``
298+
or casting to type ``Any`` is not allowed. Note that calling functions
299+
that take parameters of type ``Any`` is still allowed.
300+
301+
293302
- ``--disallow-untyped-defs`` reports an error whenever it encounters
294303
a function definition without type annotations.
295304

docs/source/config_file.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ overridden by the pattern sections matching the module name.
150150
- ``disallow_any`` (Comma-separated list, default empty) is an option to
151151
disallow various types of ``Any`` in a module. The flag takes a
152152
comma-separated list of the following arguments: ``unimported``,
153-
``unannotated``. For explanations see the discussion for the
153+
``unannotated``, ``expr``. For explanations see the discussion for the
154154
:ref:`--disallow-any <disallow-any>` option.
155155

156156
- ``disallow_untyped_calls`` (Boolean, default False) disallows

mypy/checker.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,7 +1736,9 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Expression,
17361736
# '...' is always a valid initializer in a stub.
17371737
return AnyType()
17381738
else:
1739-
rvalue_type = self.expr_checker.accept(rvalue, lvalue_type)
1739+
always_allow_any = lvalue_type is not None and not isinstance(lvalue_type, AnyType)
1740+
rvalue_type = self.expr_checker.accept(rvalue, lvalue_type,
1741+
always_allow_any=always_allow_any)
17401742
if isinstance(rvalue_type, DeletedType):
17411743
self.msg.deleted_as_rvalue(rvalue_type, context)
17421744
if isinstance(lvalue_type, DeletedType):
@@ -1855,7 +1857,7 @@ def try_infer_partial_type_from_indexed_assignment(
18551857
del partial_types[var]
18561858

18571859
def visit_expression_stmt(self, s: ExpressionStmt) -> None:
1858-
self.expr_checker.accept(s.expr, allow_none_return=True)
1860+
self.expr_checker.accept(s.expr, allow_none_return=True, always_allow_any=True)
18591861

18601862
def visit_return_stmt(self, s: ReturnStmt) -> None:
18611863
"""Type check a return statement."""

mypy/checkexpr.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type:
204204
or isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS):
205205
self.msg.type_arguments_not_allowed(e)
206206
self.try_infer_partial_type(e)
207-
callee_type = self.accept(e.callee)
207+
callee_type = self.accept(e.callee, always_allow_any=True)
208208
if (self.chk.options.disallow_untyped_calls and
209209
self.chk.in_checked_function() and
210210
isinstance(callee_type, CallableType)
@@ -1670,7 +1670,8 @@ def visit_enum_index_expr(self, enum_type: TypeInfo, index: Expression,
16701670

16711671
def visit_cast_expr(self, expr: CastExpr) -> Type:
16721672
"""Type check a cast expression."""
1673-
source_type = self.accept(expr.expr, type_context=AnyType(), allow_none_return=True)
1673+
source_type = self.accept(expr.expr, type_context=AnyType(), allow_none_return=True,
1674+
always_allow_any=True)
16741675
target_type = expr.type
16751676
options = self.chk.options
16761677
if options.warn_redundant_casts and is_same_type(source_type, target_type):
@@ -2196,7 +2197,8 @@ def visit_backquote_expr(self, e: BackquoteExpr) -> Type:
21962197
def accept(self,
21972198
node: Expression,
21982199
type_context: Type = None,
2199-
allow_none_return: bool = False
2200+
allow_none_return: bool = False,
2201+
always_allow_any: bool = False,
22002202
) -> Type:
22012203
"""Type check a node in the given type context. If allow_none_return
22022204
is True and this expression is a call, allow it to return None. This
@@ -2216,6 +2218,14 @@ def accept(self,
22162218
self.type_context.pop()
22172219
assert typ is not None
22182220
self.chk.store_type(node, typ)
2221+
2222+
if ('expr' in self.chk.options.disallow_any and
2223+
not always_allow_any and
2224+
not self.chk.is_stub and
2225+
self.chk.in_checked_function() and
2226+
has_any_type(typ)):
2227+
self.msg.disallowed_any_type(typ, node)
2228+
22192229
if not self.chk.in_checked_function():
22202230
return AnyType()
22212231
else:
@@ -2434,6 +2444,19 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type) -> Type:
24342444
return known_type
24352445

24362446

2447+
def has_any_type(t: Type) -> bool:
2448+
"""Whether t contains an Any type"""
2449+
return t.accept(HasAnyType())
2450+
2451+
2452+
class HasAnyType(types.TypeQuery[bool]):
2453+
def __init__(self) -> None:
2454+
super().__init__(any)
2455+
2456+
def visit_any(self, t: AnyType) -> bool:
2457+
return True
2458+
2459+
24372460
def has_coroutine_decorator(t: Type) -> bool:
24382461
"""Whether t came from a function decorated with `@coroutine`."""
24392462
return isinstance(t, Instance) and t.type.fullname() == 'typing.AwaitableGenerator'

mypy/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def type_check_only(sources: List[BuildSource], bin_dir: str, options: Options)
9797
options=options)
9898

9999

100-
disallow_any_options = ['unimported', 'unannotated']
100+
disallow_any_options = ['unimported', 'expr', 'unannotated']
101101

102102

103103
def disallow_any_argument_type(raw_options: str) -> List[str]:

mypy/messages.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,13 @@ def typeddict_item_name_not_found(self,
904904
def type_arguments_not_allowed(self, context: Context) -> None:
905905
self.fail('Parameterized generics cannot be used with class or instance checks', context)
906906

907+
def disallowed_any_type(self, typ: Type, context: Context) -> None:
908+
if isinstance(typ, AnyType):
909+
message = 'Expression has type "Any"'
910+
else:
911+
message = 'Expression type contains "Any" (has type {})'.format(self.format(typ))
912+
self.fail(message, context)
913+
907914

908915
def capitalize(s: str) -> str:
909916
"""Capitalize the first character of a string."""

test-data/unit/check-flags.test

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,3 +515,78 @@ x, y = 1, 2 # type: Unchecked, Unchecked
515515
[out]
516516
main:4: error: Type of variable becomes "Any" due to an unfollowed import
517517
main:6: error: A type on this line becomes "Any" due to an unfollowed import
518+
519+
[case testDisallowAnyExprSimple]
520+
# flags: --disallow-any=expr
521+
from typing import Any
522+
def f(s):
523+
yield s
524+
525+
x = f(0) # E: Expression has type "Any"
526+
for x in f(0): # E: Expression has type "Any"
527+
g(x) # E: Expression has type "Any"
528+
529+
def g(x) -> Any:
530+
yield x # E: Expression has type "Any"
531+
532+
l = [1, 2, 3]
533+
l[f(0)] # E: Expression has type "Any"
534+
f(l)
535+
f(f(0)) # E: Expression has type "Any"
536+
[builtins fixtures/list.pyi]
537+
538+
[case testDisallowAnyExprUnannotatedFunction]
539+
# flags: --disallow-any=expr
540+
def g(s):
541+
return s
542+
543+
g(0)
544+
w: int = g(1)
545+
546+
[case testDisallowAnyExprExplicitAnyParam]
547+
# flags: --disallow-any=expr
548+
from typing import Any, List
549+
def f(s: Any) -> None:
550+
pass
551+
552+
def g(s: List[Any]) -> None:
553+
pass
554+
555+
f(0)
556+
557+
# type of list below is inferred with expected type of List[Any], so that becomes it's type
558+
# instead of List[str]
559+
g(['']) # E: Expression type contains "Any" (has type List[Any])
560+
[builtins fixtures/list.pyi]
561+
562+
[case testDisallowAnyExprAllowsAnyInCast]
563+
# flags: --disallow-any=expr
564+
from typing import Any, cast
565+
class Foo:
566+
g: Any = 2
567+
568+
z = cast(int, Foo().g)
569+
m = cast(Any, Foo().g) # E: Expression has type "Any"
570+
k = Foo.g # E: Expression has type "Any"
571+
[builtins fixtures/list.pyi]
572+
573+
[case testDisallowAnyExprAllowsAnyInVariableAssignmentWithExplicitTypeAnnotation]
574+
# flags: --disallow-any=expr
575+
from typing import Any
576+
class Foo:
577+
g: Any = 2
578+
579+
z: int = Foo().g
580+
x = Foo().g # type: int
581+
m: Any = Foo().g # E: Expression has type "Any"
582+
n = Foo().g # type: Any # E: Expression has type "Any"
583+
[builtins fixtures/list.pyi]
584+
585+
[case testDisallowAnyExprGeneric]
586+
# flags: --disallow-any=expr
587+
from typing import List
588+
589+
l: List = []
590+
l.append(1) # E: Expression type contains "Any" (has type List[Any])
591+
k = l[0] # E: Expression type contains "Any" (has type List[Any]) # E: Expression has type "Any"
592+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)