From 93d4c192223adaa29fbd223503e12966328c2158 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Thu, 8 Jun 2017 16:21:39 -0700 Subject: [PATCH 01/16] Implement --disallow-any=expr The 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 --- mypy/checker.py | 2 +- mypy/checkexpr.py | 30 ++++++++++++++++++--- mypy/main.py | 3 +-- mypy/messages.py | 6 ++++- test-data/unit/check-flags.test | 47 +++++++++++++++++++++++++++++++++ 5 files changed, 80 insertions(+), 8 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9cde2c54cbf66..accb87c6ac428 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1736,7 +1736,7 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Expression, # '...' is always a valid initializer in a stub. return AnyType() else: - rvalue_type = self.expr_checker.accept(rvalue, lvalue_type) + rvalue_type = self.expr_checker.accept(rvalue, lvalue_type, disallow_any=False) if isinstance(rvalue_type, DeletedType): self.msg.deleted_as_rvalue(rvalue_type, context) if isinstance(lvalue_type, DeletedType): diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 6e0bb9ff81683..d0b138681c232 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -11,7 +11,7 @@ PartialType, DeletedType, UnboundType, UninhabitedType, TypeType, true_only, false_only, is_named_instance, function_type, callable_type, FunctionLike, get_typ_args, set_typ_args, - StarType) + StarType, TypeQuery) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, MemberExpr, IntExpr, StrExpr, BytesExpr, UnicodeExpr, FloatExpr, @@ -204,7 +204,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: or isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS): self.msg.type_arguments_not_allowed(e) self.try_infer_partial_type(e) - callee_type = self.accept(e.callee) + callee_type = self.accept(e.callee, disallow_any=False) if (self.chk.options.disallow_untyped_calls and self.chk.in_checked_function() and isinstance(callee_type, CallableType) @@ -1670,7 +1670,8 @@ def visit_enum_index_expr(self, enum_type: TypeInfo, index: Expression, def visit_cast_expr(self, expr: CastExpr) -> Type: """Type check a cast expression.""" - source_type = self.accept(expr.expr, type_context=AnyType(), allow_none_return=True) + source_type = self.accept(expr.expr, type_context=AnyType(), allow_none_return=True, + disallow_any=False) target_type = expr.type options = self.chk.options if options.warn_redundant_casts and is_same_type(source_type, target_type): @@ -2191,7 +2192,8 @@ def visit_backquote_expr(self, e: BackquoteExpr) -> Type: def accept(self, node: Expression, type_context: Type = None, - allow_none_return: bool = False + allow_none_return: bool = False, + disallow_any: bool = True, ) -> Type: """Type check a node in the given type context. If allow_none_return is True and this expression is a call, allow it to return None. This @@ -2211,6 +2213,13 @@ def accept(self, self.type_context.pop() assert typ is not None self.chk.store_type(node, typ) + + if (disallow_any and + not self.chk.is_stub and + has_any_type(typ) and + 'expr' in self.chk.options.disallow_any): + self.msg.disallowed_any_type(typ, node) + if not self.chk.in_checked_function(): return AnyType() else: @@ -2429,6 +2438,19 @@ def narrow_type_from_binder(self, expr: Expression, known_type: Type) -> Type: return known_type +def has_any_type(t: Type) -> bool: + """Whether t contains an Any type""" + return t.accept(HasAnyType()) + + +class HasAnyType(TypeQuery[bool]): + def __init__(self): + super().__init__(any) + + def visit_any(self, t: AnyType) -> bool: + return True + + def has_coroutine_decorator(t: Type) -> bool: """Whether t came from a function decorated with `@coroutine`.""" return isinstance(t, Instance) and t.type.fullname() == 'typing.AwaitableGenerator' diff --git a/mypy/main.py b/mypy/main.py index 422ca3ccec033..4e2a1674b3a14 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -97,7 +97,7 @@ def type_check_only(sources: List[BuildSource], bin_dir: str, options: Options) options=options) -disallow_any_options = ['unimported'] +disallow_any_options = ['unimported', 'expr'] def disallow_any_argument_type(raw_options: str) -> List[str]: @@ -201,7 +201,6 @@ def process_options(args: List[str], strict_flag_names = [] # type: List[str] strict_flag_assignments = [] # type: List[Tuple[str, bool]] - disallow_any_options = ['unimported'] def add_invertible_flag(flag: str, *, diff --git a/mypy/messages.py b/mypy/messages.py index 305007592602a..784d30b0df8c8 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -6,7 +6,7 @@ import re import difflib -from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple +from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Optional from mypy.erasetype import erase_type from mypy.errors import Errors @@ -897,6 +897,10 @@ def typeddict_item_name_not_found(self, def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) + def disallowed_any_type(self, typ: Type, context: Context): + self.fail('Expressions of type "Any" are disallowed ' + '(has type {})'.format(self.format(typ)), context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 6f8dcdd4a94b3..2db93b970ec7b 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -509,3 +509,50 @@ x, y = 1, 2 # type: Unchecked, Unchecked [out] 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 testDisallowAnyBasic] +# flags: --disallow-any=expr +from typing import List, Any, cast + +def foo(z: int): + return z + +def print(s: Any) -> None: + s = s + 1 + pass + +class Foo: + g: Any = 2 + +x: List[str] = ['hello'] + +f: int = Foo.doo() +k = Foo.doo() + +z = cast(int, Foo().g) +m = cast(Any, Foo().g) +y = Foo().g +print(x[0]) +print(x[foo(0)]) +[builtins fixtures/list.pyi] +[out] +main:8: error: Expressions of type "Any" are disallowed (has type "Any") +main:16: error: Type[Foo] has no attribute "doo" +main:17: error: Type[Foo] has no attribute "doo" +main:17: error: Expressions of type "Any" are disallowed (has type "Any") +main:20: error: Expressions of type "Any" are disallowed (has type "Any") +main:21: error: Expressions of type "Any" are disallowed (has type "Any") +main:23: error: Expressions of type "Any" are disallowed (has type "Any") + +[case testDisallowAnyExprGeneric] +# flags: --disallow-any=expr +from typing import List + +blargle: List = [] +blargle.append(1) +k = blargle[0] +[builtins fixtures/list.pyi] +[out] +main:5: error: Expressions of type "Any" are disallowed (has type List[Any]) +main:6: error: Expressions of type "Any" are disallowed (has type List[Any]) +main:6: error: Expressions of type "Any" are disallowed (has type "Any") From 6517ae4055199642a7d3f8b52f81e7b37458995e Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Fri, 9 Jun 2017 14:48:54 -0700 Subject: [PATCH 02/16] Add return types to functions (Was causing failures because of --disallow-untyped-defs) --- mypy/checkexpr.py | 2 +- mypy/messages.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d0b138681c232..3a72b3dadffb7 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2444,7 +2444,7 @@ def has_any_type(t: Type) -> bool: class HasAnyType(TypeQuery[bool]): - def __init__(self): + def __init__(self) -> None: super().__init__(any) def visit_any(self, t: AnyType) -> bool: diff --git a/mypy/messages.py b/mypy/messages.py index 784d30b0df8c8..d5c209b260393 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -897,7 +897,7 @@ def typeddict_item_name_not_found(self, def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) - def disallowed_any_type(self, typ: Type, context: Context): + def disallowed_any_type(self, typ: Type, context: Context) -> None: self.fail('Expressions of type "Any" are disallowed ' '(has type {})'.format(self.format(typ)), context) From ee76320d9152bb5e4b6c89f6f1a13bf288e48ff4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 12 Jun 2017 17:02:16 -0700 Subject: [PATCH 03/16] Address code review feedback --- test-data/unit/check-flags.test | 70 ++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 2db93b970ec7b..b782fd7036afd 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -510,47 +510,69 @@ 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 testDisallowAnyBasic] +[case testDisallowAnyExprUnannotatedFunction] # flags: --disallow-any=expr -from typing import List, Any, cast +def g(s): + return s # E: Expressions of type "Any" are disallowed (has type "Any") -def foo(z: int): - return z +g(0) +g('hello') -def print(s: Any) -> None: - s = s + 1 +v = g(1) # E: Expressions of type "Any" are disallowed (has type "Any") +w: int = g(1) + +[case testDisallowAnyExprExplicitAnyParam] +from typing import Any +def f(s: Any) -> None: pass +f(0) +f('hello') +[builtins fixtures/list.pyi] + +[case testDisallowAnyExprAllowsAnyInCast] +# flags: --disallow-any=expr +from typing import Any, cast class Foo: g: Any = 2 -x: List[str] = ['hello'] - -f: int = Foo.doo() -k = Foo.doo() - z = cast(int, Foo().g) m = cast(Any, Foo().g) -y = Foo().g -print(x[0]) -print(x[foo(0)]) +k = Foo.g # E: Expressions of type "Any" are disallowed (has type "Any") +[builtins fixtures/list.pyi] + +[case testDisallowAnyExprAllowsAnyInVariableAssignmentWithExplicitTypeAnnotation] +# flags: --disallow-any=expr +from typing import Any +class Foo: + g: Any = 2 + +z: int = Foo().g +m: Any = Foo().g +[builtins fixtures/list.pyi] + +[case testDisallowAnyExprAllowsNonExistentMembers] +# flags: --disallow-any=expr +from typing import cast +class Foo: pass + +i: int = Foo.doo() +j = cast(int, Foo.doo()) +k = Foo.doo() [builtins fixtures/list.pyi] [out] -main:8: error: Expressions of type "Any" are disallowed (has type "Any") -main:16: error: Type[Foo] has no attribute "doo" -main:17: error: Type[Foo] has no attribute "doo" -main:17: error: Expressions of type "Any" are disallowed (has type "Any") -main:20: error: Expressions of type "Any" are disallowed (has type "Any") -main:21: error: Expressions of type "Any" are disallowed (has type "Any") -main:23: error: Expressions of type "Any" are disallowed (has type "Any") +main:5: error: Type[Foo] has no attribute "doo" +main:6: error: Type[Foo] has no attribute "doo" +main:7: error: Type[Foo] has no attribute "doo" +main:7: error: Expressions of type "Any" are disallowed (has type "Any") [case testDisallowAnyExprGeneric] # flags: --disallow-any=expr from typing import List -blargle: List = [] -blargle.append(1) -k = blargle[0] +l: List = [] +l.append(1) +k = l[0] [builtins fixtures/list.pyi] [out] main:5: error: Expressions of type "Any" are disallowed (has type List[Any]) From f059dfdac576a48b9954c61a809825a4062318f3 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 12 Jun 2017 17:04:28 -0700 Subject: [PATCH 04/16] The rest of the feedback (accidentally committed just one file) --- mypy/checker.py | 10 ++++++---- mypy/checkexpr.py | 18 +++++++++--------- mypy/messages.py | 2 +- mypy/nodes.py | 3 +++ 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index accb87c6ac428..65f0ffee8bf1b 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1292,8 +1292,9 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.check_indexed_assignment(index_lvalue, rvalue, lvalue) if inferred: - self.infer_variable_type(inferred, lvalue, self.expr_checker.accept(rvalue), - rvalue) + is_cast = isinstance(rvalue, CallExpr) and rvalue.is_cast() + init_type = self.expr_checker.accept(rvalue, always_allow_any=is_cast) + self.infer_variable_type(inferred, lvalue, init_type, rvalue) def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Optional[Type], rvalue: Expression) -> bool: @@ -1736,7 +1737,8 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Expression, # '...' is always a valid initializer in a stub. return AnyType() else: - rvalue_type = self.expr_checker.accept(rvalue, lvalue_type, disallow_any=False) + rvalue_type = self.expr_checker.accept(rvalue, lvalue_type, + always_allow_any=lvalue_type is not None) if isinstance(rvalue_type, DeletedType): self.msg.deleted_as_rvalue(rvalue_type, context) if isinstance(lvalue_type, DeletedType): @@ -1855,7 +1857,7 @@ def try_infer_partial_type_from_indexed_assignment( del partial_types[var] def visit_expression_stmt(self, s: ExpressionStmt) -> None: - self.expr_checker.accept(s.expr, allow_none_return=True) + self.expr_checker.accept(s.expr, allow_none_return=True, always_allow_any=True) def visit_return_stmt(self, s: ReturnStmt) -> None: """Type check a return statement.""" diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 3a72b3dadffb7..e85a375afbd8b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -11,7 +11,7 @@ PartialType, DeletedType, UnboundType, UninhabitedType, TypeType, true_only, false_only, is_named_instance, function_type, callable_type, FunctionLike, get_typ_args, set_typ_args, - StarType, TypeQuery) + StarType) from mypy.nodes import ( NameExpr, RefExpr, Var, FuncDef, OverloadedFuncDef, TypeInfo, CallExpr, MemberExpr, IntExpr, StrExpr, BytesExpr, UnicodeExpr, FloatExpr, @@ -185,7 +185,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: """Type check a call expression.""" if e.analyzed: # It's really a special form that only looks like a call. - return self.accept(e.analyzed, self.type_context[-1]) + return self.accept(e.analyzed, self.type_context[-1], always_allow_any=True) if isinstance(e.callee, NameExpr) and isinstance(e.callee.node, TypeInfo) and \ e.callee.node.typeddict_type is not None: return self.check_typeddict_call(e.callee.node.typeddict_type, @@ -204,7 +204,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: or isinstance(typ, NameExpr) and node and node.kind == nodes.TYPE_ALIAS): self.msg.type_arguments_not_allowed(e) self.try_infer_partial_type(e) - callee_type = self.accept(e.callee, disallow_any=False) + callee_type = self.accept(e.callee, always_allow_any=True) if (self.chk.options.disallow_untyped_calls and self.chk.in_checked_function() and isinstance(callee_type, CallableType) @@ -1671,7 +1671,7 @@ def visit_enum_index_expr(self, enum_type: TypeInfo, index: Expression, def visit_cast_expr(self, expr: CastExpr) -> Type: """Type check a cast expression.""" source_type = self.accept(expr.expr, type_context=AnyType(), allow_none_return=True, - disallow_any=False) + always_allow_any=True) target_type = expr.type options = self.chk.options if options.warn_redundant_casts and is_same_type(source_type, target_type): @@ -2193,7 +2193,7 @@ def accept(self, node: Expression, type_context: Type = None, allow_none_return: bool = False, - disallow_any: bool = True, + always_allow_any: bool = False, ) -> Type: """Type check a node in the given type context. If allow_none_return is True and this expression is a call, allow it to return None. This @@ -2214,10 +2214,10 @@ def accept(self, assert typ is not None self.chk.store_type(node, typ) - if (disallow_any and + if ('expr' in self.chk.options.disallow_any and + not always_allow_any and not self.chk.is_stub and - has_any_type(typ) and - 'expr' in self.chk.options.disallow_any): + has_any_type(typ)): self.msg.disallowed_any_type(typ, node) if not self.chk.in_checked_function(): @@ -2443,7 +2443,7 @@ def has_any_type(t: Type) -> bool: return t.accept(HasAnyType()) -class HasAnyType(TypeQuery[bool]): +class HasAnyType(types.TypeQuery[bool]): def __init__(self) -> None: super().__init__(any) diff --git a/mypy/messages.py b/mypy/messages.py index d5c209b260393..ef53da9e16473 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -6,7 +6,7 @@ import re import difflib -from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Optional +from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple from mypy.erasetype import erase_type from mypy.errors import Errors diff --git a/mypy/nodes.py b/mypy/nodes.py index 48715662d9d6b..95990a406d2b9 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1300,6 +1300,9 @@ def __init__(self, callee: Expression, args: List[Expression], arg_kinds: List[i self.arg_names = arg_names self.analyzed = analyzed + def is_cast(self) -> bool: + return self.analyzed is not None and isinstance(self.analyzed, CastExpr) + def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_call_expr(self) From ca06a5a13725e77966c841f77008ecc3340414a8 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 12:28:50 -0700 Subject: [PATCH 05/16] Update error message to be more precise when type contains `Any` --- mypy/messages.py | 8 ++++++-- test-data/unit/check-flags.test | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index ef53da9e16473..9a9c88129a8f0 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -898,8 +898,12 @@ def type_arguments_not_allowed(self, context: Context) -> None: self.fail('Parameterized generics cannot be used with class or instance checks', context) def disallowed_any_type(self, typ: Type, context: Context) -> None: - self.fail('Expressions of type "Any" are disallowed ' - '(has type {})'.format(self.format(typ)), context) + if isinstance(typ, AnyType): + infix = 'type' + else: + infix = 'type that contains type' + self.fail('Expressions of {} "Any" are disallowed ' + '(has type {})'.format(infix, self.format(typ)), context) def capitalize(s: str) -> str: diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index b782fd7036afd..e6a01f93d1fb4 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -575,6 +575,6 @@ l.append(1) k = l[0] [builtins fixtures/list.pyi] [out] -main:5: error: Expressions of type "Any" are disallowed (has type List[Any]) -main:6: error: Expressions of type "Any" are disallowed (has type List[Any]) +main:5: error: Expressions of type that contains type "Any" are disallowed (has type List[Any]) +main:6: error: Expressions of type that contains type "Any" are disallowed (has type List[Any]) main:6: error: Expressions of type "Any" are disallowed (has type "Any") From edef0b112bee2fc1e4cc25d0a6d561139d6d6981 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 14:15:05 -0700 Subject: [PATCH 06/16] Remove redundant tests --- test-data/unit/check-flags.test | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index e6a01f93d1fb4..c0049d2d42063 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -516,7 +516,6 @@ def g(s): return s # E: Expressions of type "Any" are disallowed (has type "Any") g(0) -g('hello') v = g(1) # E: Expressions of type "Any" are disallowed (has type "Any") w: int = g(1) @@ -527,7 +526,6 @@ def f(s: Any) -> None: pass f(0) -f('hello') [builtins fixtures/list.pyi] [case testDisallowAnyExprAllowsAnyInCast] @@ -551,21 +549,6 @@ z: int = Foo().g m: Any = Foo().g [builtins fixtures/list.pyi] -[case testDisallowAnyExprAllowsNonExistentMembers] -# flags: --disallow-any=expr -from typing import cast -class Foo: pass - -i: int = Foo.doo() -j = cast(int, Foo.doo()) -k = Foo.doo() -[builtins fixtures/list.pyi] -[out] -main:5: error: Type[Foo] has no attribute "doo" -main:6: error: Type[Foo] has no attribute "doo" -main:7: error: Type[Foo] has no attribute "doo" -main:7: error: Expressions of type "Any" are disallowed (has type "Any") - [case testDisallowAnyExprGeneric] # flags: --disallow-any=expr from typing import List From b5c33d7d7f8d656de27fb84b7aed3e69384b9714 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 14:29:18 -0700 Subject: [PATCH 07/16] Disallow casts to `Any` (explicit or implicit) --- mypy/checker.py | 8 ++++---- mypy/nodes.py | 3 --- test-data/unit/check-flags.test | 4 ++-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 65f0ffee8bf1b..302c9536c28f3 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1292,9 +1292,8 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type self.check_indexed_assignment(index_lvalue, rvalue, lvalue) if inferred: - is_cast = isinstance(rvalue, CallExpr) and rvalue.is_cast() - init_type = self.expr_checker.accept(rvalue, always_allow_any=is_cast) - self.infer_variable_type(inferred, lvalue, init_type, rvalue) + self.infer_variable_type(inferred, lvalue, self.expr_checker.accept(rvalue), + rvalue) def check_compatibility_all_supers(self, lvalue: NameExpr, lvalue_type: Optional[Type], rvalue: Expression) -> bool: @@ -1737,8 +1736,9 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Expression, # '...' is always a valid initializer in a stub. return AnyType() else: + always_allow_any = lvalue_type and not isinstance(lvalue_type, AnyType) rvalue_type = self.expr_checker.accept(rvalue, lvalue_type, - always_allow_any=lvalue_type is not None) + always_allow_any=always_allow_any) if isinstance(rvalue_type, DeletedType): self.msg.deleted_as_rvalue(rvalue_type, context) if isinstance(lvalue_type, DeletedType): diff --git a/mypy/nodes.py b/mypy/nodes.py index 95990a406d2b9..48715662d9d6b 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -1300,9 +1300,6 @@ def __init__(self, callee: Expression, args: List[Expression], arg_kinds: List[i self.arg_names = arg_names self.analyzed = analyzed - def is_cast(self) -> bool: - return self.analyzed is not None and isinstance(self.analyzed, CastExpr) - def accept(self, visitor: ExpressionVisitor[T]) -> T: return visitor.visit_call_expr(self) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index c0049d2d42063..c0b7041e1b0eb 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -535,7 +535,7 @@ class Foo: g: Any = 2 z = cast(int, Foo().g) -m = cast(Any, Foo().g) +m = cast(Any, Foo().g) # E: Expressions of type "Any" are disallowed (has type "Any") k = Foo.g # E: Expressions of type "Any" are disallowed (has type "Any") [builtins fixtures/list.pyi] @@ -546,7 +546,7 @@ class Foo: g: Any = 2 z: int = Foo().g -m: Any = Foo().g +m: Any = Foo().g # E: Expressions of type "Any" are disallowed (has type "Any") [builtins fixtures/list.pyi] [case testDisallowAnyExprGeneric] From a07ad12fc551e200d906eaeb483d2911380a66bc Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 15:55:11 -0700 Subject: [PATCH 08/16] Disallow calling functions that take parameters of type `Any` --- mypy/checker.py | 2 +- mypy/checkexpr.py | 4 ++++ mypy/messages.py | 12 ++++++++++++ test-data/unit/check-flags.test | 22 +++++++++++++++++----- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 302c9536c28f3..ec927a8a64906 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1736,7 +1736,7 @@ def check_simple_assignment(self, lvalue_type: Type, rvalue: Expression, # '...' is always a valid initializer in a stub. return AnyType() else: - always_allow_any = lvalue_type and not isinstance(lvalue_type, AnyType) + always_allow_any = lvalue_type is not None and not isinstance(lvalue_type, AnyType) rvalue_type = self.expr_checker.accept(rvalue, lvalue_type, always_allow_any=always_allow_any) if isinstance(rvalue_type, DeletedType): diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index e85a375afbd8b..8d65ea33ffa9a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -993,6 +993,10 @@ def check_arg(self, caller_type: Type, original_caller_type: Type, return messages.incompatible_argument(n, m, callee, original_caller_type, caller_kind, context) + elif ('expr' in self.chk.options.disallow_any and + has_any_type(callee_type) and + not self.chk.is_stub): + messages.call_any_parameter(n, callee.name, callee_type, context) def overload_call_target(self, arg_types: List[Type], arg_kinds: List[int], arg_names: List[str], diff --git a/mypy/messages.py b/mypy/messages.py index 9a9c88129a8f0..8d0b59a95c9fb 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -905,6 +905,18 @@ def disallowed_any_type(self, typ: Type, context: Context) -> None: self.fail('Expressions of {} "Any" are disallowed ' '(has type {})'.format(infix, self.format(typ)), context) + def call_any_parameter(self, + param_index: int, + func_name: str, + typ: Type, + context: Context) -> None: + if isinstance(typ, AnyType): + msg = 'Parameter {} to {} has disallowed type "Any"'.format(param_index, func_name) + else: + msg = 'Parameter {} to {} contains disallowed type "Any" ({})'.format( + param_index, func_name, self.format(typ)) + self.fail(msg, context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index c0b7041e1b0eb..6dcf685b4cc5a 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -515,17 +515,28 @@ main:6: error: A type on this line becomes "Any" due to an unfollowed import def g(s): return s # E: Expressions of type "Any" are disallowed (has type "Any") -g(0) - -v = g(1) # E: Expressions of type "Any" are disallowed (has type "Any") -w: int = g(1) +g(0) # E: Parameter 1 to "g" has disallowed type "Any" +w: int = g(1) # E: Parameter 1 to "g" has disallowed type "Any" [case testDisallowAnyExprExplicitAnyParam] -from typing import Any +# flags: --disallow-any=expr +from typing import Any, List def f(s: Any) -> None: pass +def g(s: List[Any]) -> None: + pass + f(0) + +# because expected type is List[Any] the inferred type of [''] is List[Any], which causes +# more than one error on the line below +g(['']) +[out] +main:9: error: Parameter 1 to "f" has disallowed type "Any" +main:13: error: Parameter 1 to "g" contains disallowed type "Any" (List[Any]) +main:13: error: Parameter 1 to has disallowed type "Any" +main:13: error: Expressions of type that contains type "Any" are disallowed (has type List[Any]) [builtins fixtures/list.pyi] [case testDisallowAnyExprAllowsAnyInCast] @@ -559,5 +570,6 @@ k = l[0] [builtins fixtures/list.pyi] [out] main:5: error: Expressions of type that contains type "Any" are disallowed (has type List[Any]) +main:5: error: Parameter 1 to "append" of "list" has disallowed type "Any" main:6: error: Expressions of type that contains type "Any" are disallowed (has type List[Any]) main:6: error: Expressions of type "Any" are disallowed (has type "Any") From ebc51a8db4d23fa8ab4e0753778851c0043df8e2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 16:02:28 -0700 Subject: [PATCH 09/16] Add test for implicit casts with type comments --- test-data/unit/check-flags.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 6dcf685b4cc5a..d0660b0bc04e8 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -557,7 +557,9 @@ class Foo: g: Any = 2 z: int = Foo().g +x = Foo().g # type: int m: Any = Foo().g # E: Expressions of type "Any" are disallowed (has type "Any") +n = Foo().g # type: Any # E: Expressions of type "Any" are disallowed (has type "Any") [builtins fixtures/list.pyi] [case testDisallowAnyExprGeneric] From d6d9ecb4504c4442b0a06b87c588e609cb3cf40c Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Tue, 13 Jun 2017 16:40:31 -0700 Subject: [PATCH 10/16] Add documentation --- docs/source/command_line.rst | 9 ++++++++- docs/source/config_file.rst | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index ad7555bb2759d..bd11800abc3b6 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -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``. + ``unimported``, ``unannotated``, ``expr``. ``unimported`` disallows usage of types that come from unfollowed imports (such types become aliases for ``Any``). Unfollowed imports occur either @@ -290,6 +290,13 @@ Here are some more useful flags: of the parameters or the return type). ``unannotated`` option is interchangeable with ``--disallow-untyped-defs``. + ``expr`` disallows all expressions in the module that have type ``Any``. + If an expression of type ``Any`` appears anywhere in the module + mypy will output an error unless the expression is immediately + used as an argument to ``cast`` or assigned to a variable with an + explicit type annotation. Note that declaring a variable of type ``Any`` + or casting to type ``Any`` is not allowed. + - ``--disallow-untyped-defs`` reports an error whenever it encounters a function definition without type annotations. diff --git a/docs/source/config_file.rst b/docs/source/config_file.rst index 07db822df8a1f..6fbcfbb2db9a2 100644 --- a/docs/source/config_file.rst +++ b/docs/source/config_file.rst @@ -150,7 +150,7 @@ overridden by the pattern sections matching the module name. - ``disallow_any`` (Comma-separated list, default empty) is an option to disallow various types of ``Any`` in a module. The flag takes a comma-separated list of the following arguments: ``unimported``, - ``unannotated``. For explanations see the discussion for the + ``unannotated``, ``expr``. For explanations see the discussion for the :ref:`--disallow-any ` option. - ``disallow_untyped_calls`` (Boolean, default False) disallows From 775c5ec2fa6846014c4879109e9e4b62ecd88c42 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Thu, 15 Jun 2017 17:35:43 -0700 Subject: [PATCH 11/16] Update error messages to be more clear --- mypy/messages.py | 7 +++---- test-data/unit/check-flags.test | 18 +++++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 00da44c4f5d05..dc3b48443a4ca 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -906,11 +906,10 @@ def type_arguments_not_allowed(self, context: Context) -> None: def disallowed_any_type(self, typ: Type, context: Context) -> None: if isinstance(typ, AnyType): - infix = 'type' + message = 'Expression has type "Any"' else: - infix = 'type that contains type' - self.fail('Expressions of {} "Any" are disallowed ' - '(has type {})'.format(infix, self.format(typ)), context) + message = 'Expression type contains "Any" (has type {})'.format(self.format(typ)) + self.fail(message, context) def call_any_parameter(self, param_index: int, diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 0daaa1de61a79..757b9510bb47e 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -519,7 +519,7 @@ main:6: error: A type on this line becomes "Any" due to an unfollowed import [case testDisallowAnyExprUnannotatedFunction] # flags: --disallow-any=expr def g(s): - return s # E: Expressions of type "Any" are disallowed (has type "Any") + return s # E: Expression has type "Any" g(0) # E: Parameter 1 to "g" has disallowed type "Any" w: int = g(1) # E: Parameter 1 to "g" has disallowed type "Any" @@ -542,7 +542,7 @@ g(['']) main:9: error: Parameter 1 to "f" has disallowed type "Any" main:13: error: Parameter 1 to "g" contains disallowed type "Any" (List[Any]) main:13: error: Parameter 1 to has disallowed type "Any" -main:13: error: Expressions of type that contains type "Any" are disallowed (has type List[Any]) +main:13: error: Expression type contains "Any" (has type List[Any]) [builtins fixtures/list.pyi] [case testDisallowAnyExprAllowsAnyInCast] @@ -552,8 +552,8 @@ class Foo: g: Any = 2 z = cast(int, Foo().g) -m = cast(Any, Foo().g) # E: Expressions of type "Any" are disallowed (has type "Any") -k = Foo.g # E: Expressions of type "Any" are disallowed (has type "Any") +m = cast(Any, Foo().g) # E: Expression has type "Any" +k = Foo.g # E: Expression has type "Any" [builtins fixtures/list.pyi] [case testDisallowAnyExprAllowsAnyInVariableAssignmentWithExplicitTypeAnnotation] @@ -564,8 +564,8 @@ class Foo: z: int = Foo().g x = Foo().g # type: int -m: Any = Foo().g # E: Expressions of type "Any" are disallowed (has type "Any") -n = Foo().g # type: Any # E: Expressions of type "Any" are disallowed (has type "Any") +m: Any = Foo().g # E: Expression has type "Any" +n = Foo().g # type: Any # E: Expression has type "Any" [builtins fixtures/list.pyi] [case testDisallowAnyExprGeneric] @@ -577,7 +577,7 @@ l.append(1) k = l[0] [builtins fixtures/list.pyi] [out] -main:5: error: Expressions of type that contains type "Any" are disallowed (has type List[Any]) +main:5: error: Expression type contains "Any" (has type List[Any]) main:5: error: Parameter 1 to "append" of "list" has disallowed type "Any" -main:6: error: Expressions of type that contains type "Any" are disallowed (has type List[Any]) -main:6: error: Expressions of type "Any" are disallowed (has type "Any") +main:6: error: Expression type contains "Any" (has type List[Any]) +main:6: error: Expression has type "Any" From a8731e9f7dac78f39dcf8b01d039b1fd833cb80d Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Thu, 15 Jun 2017 17:48:51 -0700 Subject: [PATCH 12/16] Do not report errors for calls to functions that accept Any --- mypy/checkexpr.py | 4 ---- mypy/messages.py | 12 ------------ test-data/unit/check-flags.test | 24 +++++++----------------- 3 files changed, 7 insertions(+), 33 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4665c79ddf179..fb0b8328ced4b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -993,10 +993,6 @@ def check_arg(self, caller_type: Type, original_caller_type: Type, return messages.incompatible_argument(n, m, callee, original_caller_type, caller_kind, context) - elif ('expr' in self.chk.options.disallow_any and - has_any_type(callee_type) and - not self.chk.is_stub): - messages.call_any_parameter(n, callee.name, callee_type, context) def overload_call_target(self, arg_types: List[Type], arg_kinds: List[int], arg_names: List[str], diff --git a/mypy/messages.py b/mypy/messages.py index dc3b48443a4ca..067cbd37f7447 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -911,18 +911,6 @@ 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 call_any_parameter(self, - param_index: int, - func_name: str, - typ: Type, - context: Context) -> None: - if isinstance(typ, AnyType): - msg = 'Parameter {} to {} has disallowed type "Any"'.format(param_index, func_name) - else: - msg = 'Parameter {} to {} contains disallowed type "Any" ({})'.format( - param_index, func_name, self.format(typ)) - self.fail(msg, context) - def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 757b9510bb47e..5eca9c01e432f 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -521,8 +521,8 @@ main:6: error: A type on this line becomes "Any" due to an unfollowed import def g(s): return s # E: Expression has type "Any" -g(0) # E: Parameter 1 to "g" has disallowed type "Any" -w: int = g(1) # E: Parameter 1 to "g" has disallowed type "Any" +g(0) +w: int = g(1) [case testDisallowAnyExprExplicitAnyParam] # flags: --disallow-any=expr @@ -535,14 +535,9 @@ def g(s: List[Any]) -> None: f(0) -# because expected type is List[Any] the inferred type of [''] is List[Any], which causes -# more than one error on the line below -g(['']) -[out] -main:9: error: Parameter 1 to "f" has disallowed type "Any" -main:13: error: Parameter 1 to "g" contains disallowed type "Any" (List[Any]) -main:13: error: Parameter 1 to has disallowed type "Any" -main:13: error: Expression type contains "Any" (has type List[Any]) +# type of list below is inferred with expected type of List[Any], so that becomes it's type +# instead of List[str] +g(['']) # E: Expression type contains "Any" (has type List[Any]) [builtins fixtures/list.pyi] [case testDisallowAnyExprAllowsAnyInCast] @@ -573,11 +568,6 @@ n = Foo().g # type: Any # E: Expression has type "Any" from typing import List l: List = [] -l.append(1) -k = l[0] +l.append(1) # E: Expression type contains "Any" (has type List[Any]) +k = l[0] # E: Expression type contains "Any" (has type List[Any]) # E: Expression has type "Any" [builtins fixtures/list.pyi] -[out] -main:5: error: Expression type contains "Any" (has type List[Any]) -main:5: error: Parameter 1 to "append" of "list" has disallowed type "Any" -main:6: error: Expression type contains "Any" (has type List[Any]) -main:6: error: Expression has type "Any" From b0193fb2b2678eac831522f50fa568bfbd9423ed Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Thu, 15 Jun 2017 18:00:23 -0700 Subject: [PATCH 13/16] Remove unneeded always_allow_any = True --- mypy/checkexpr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index fb0b8328ced4b..5af3e389e9e49 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -185,7 +185,7 @@ def visit_call_expr(self, e: CallExpr, allow_none_return: bool = False) -> Type: """Type check a call expression.""" if e.analyzed: # It's really a special form that only looks like a call. - return self.accept(e.analyzed, self.type_context[-1], always_allow_any=True) + return self.accept(e.analyzed, self.type_context[-1]) if isinstance(e.callee, NameExpr) and isinstance(e.callee.node, TypeInfo) and \ e.callee.node.typeddict_type is not None: return self.check_typeddict_call(e.callee.node.typeddict_type, From fd166b1fb30c7a7159d368f274c525e67f75ed07 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 19 Jun 2017 13:27:48 -0700 Subject: [PATCH 14/16] Update documentation to explain that calling untyped function is allowed --- docs/source/command_line.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/command_line.rst b/docs/source/command_line.rst index bd11800abc3b6..5b41a490102b4 100644 --- a/docs/source/command_line.rst +++ b/docs/source/command_line.rst @@ -294,8 +294,10 @@ Here are some more useful flags: If an expression of type ``Any`` appears anywhere in the module mypy will output an error unless the expression is immediately used as an argument to ``cast`` or assigned to a variable with an - explicit type annotation. Note that declaring a variable of type ``Any`` - or casting to type ``Any`` is not allowed. + explicit type annotation. In addition, declaring a variable of type ``Any`` + or casting to type ``Any`` is not allowed. Note that calling functions + that take parameters of type ``Any`` is still allowed. + - ``--disallow-untyped-defs`` reports an error whenever it encounters a function definition without type annotations. From a2dbe3506ff534a31370e7887003e369c2b1e633 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 19 Jun 2017 13:31:06 -0700 Subject: [PATCH 15/16] Do not report errors in unchecked functions --- mypy/checkexpr.py | 1 + test-data/unit/check-flags.test | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 5af3e389e9e49..2f4246cffd92e 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -2217,6 +2217,7 @@ def accept(self, if ('expr' in self.chk.options.disallow_any and not always_allow_any and not self.chk.is_stub and + self.chk.in_checked_function() and has_any_type(typ)): self.msg.disallowed_any_type(typ, node) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 5eca9c01e432f..7be7d4934bbca 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -519,7 +519,7 @@ main:6: error: A type on this line becomes "Any" due to an unfollowed import [case testDisallowAnyExprUnannotatedFunction] # flags: --disallow-any=expr def g(s): - return s # E: Expression has type "Any" + return s g(0) w: int = g(1) From 52b9206294769d65c0b3b524c0d5429c23911ba1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Ilinskiy Date: Mon, 19 Jun 2017 13:55:12 -0700 Subject: [PATCH 16/16] Add test for basic functionality --- test-data/unit/check-flags.test | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 7be7d4934bbca..08c5a40aaec37 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -516,6 +516,25 @@ 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 testDisallowAnyExprSimple] +# flags: --disallow-any=expr +from typing import Any +def f(s): + yield s + +x = f(0) # E: Expression has type "Any" +for x in f(0): # E: Expression has type "Any" + g(x) # E: Expression has type "Any" + +def g(x) -> Any: + yield x # E: Expression has type "Any" + +l = [1, 2, 3] +l[f(0)] # E: Expression has type "Any" +f(l) +f(f(0)) # E: Expression has type "Any" +[builtins fixtures/list.pyi] + [case testDisallowAnyExprUnannotatedFunction] # flags: --disallow-any=expr def g(s):