From 6292edcea6fe936e88f60c3bef20eb777fc64aa4 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Tue, 4 Apr 2017 02:51:14 -0700 Subject: [PATCH 1/2] Add --warn-implicit-any --- mypy/main.py | 4 +++ mypy/options.py | 4 +++ mypy/semanal.py | 14 +++++++++-- mypy/typeanal.py | 17 +++++++++++++ test-data/unit/check-warnings.test | 39 ++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/mypy/main.py b/mypy/main.py index b90d82a309794..364b16d8619de 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -231,6 +231,10 @@ def add_invertible_flag(flag: str, add_invertible_flag('--warn-return-any', default=False, strict_flag=True, help="warn about returning values of type Any" " from non-Any typed functions") + add_invertible_flag('--warn-implicit-any', default=False, + help='warn about implicit creation of type "Any" ' + "(experimental -- only warns in some limited circusmstances.)" + ) add_invertible_flag('--warn-unused-ignores', default=False, strict_flag=True, help="warn about unneeded '# type: ignore' comments") add_invertible_flag('--show-error-context', default=False, diff --git a/mypy/options.py b/mypy/options.py index 8c8764200800a..09328973f8799 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -27,6 +27,7 @@ class Options: "show_none_errors", "warn_no_return", "warn_return_any", + "warn_implicit_any", "ignore_errors", "strict_boolean", } @@ -70,6 +71,9 @@ def __init__(self) -> None: # declared with a precise type self.warn_return_any = False + # Warn about implicit creation of Any type (work in progress) + self.warn_implicit_any = False + # Warn about unused '# type: ignore' comments self.warn_unused_ignores = False diff --git a/mypy/semanal.py b/mypy/semanal.py index d390455f8fbc1..365e915f5cd5b 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3251,12 +3251,15 @@ def name_already_defined(self, name: str, ctx: Context) -> None: self.fail("Name '{}' already defined".format(name), ctx) def fail(self, msg: str, ctx: Context, serious: bool = False, *, - blocker: bool = False) -> None: + blocker: bool = False, implicit_any: bool = False) -> None: if (not serious and not self.options.check_untyped_defs and self.function_stack and self.function_stack[-1].is_dynamic()): return + if implicit_any: + if not self.options.warn_implicit_any or self.cur_mod_node.is_stub: + return # In case it's a bug and we don't really have context assert ctx is not None, msg self.errors.report(ctx.get_line(), ctx.get_column(), msg, blocker=blocker) @@ -3680,7 +3683,14 @@ def analyze(self, type: Type) -> None: analyzer = TypeAnalyserPass3(self.fail) type.accept(analyzer) - def fail(self, msg: str, ctx: Context, *, blocker: bool = False) -> None: + def fail(self, msg: str, ctx: Context, *, blocker: bool = False, + implicit_any: bool = False) -> None: + if implicit_any: + if not self.options.warn_implicit_any or self.errors.file.endswith('.pyi'): + return + # TempNode, so must have already reported in the first pass + if ctx.get_line() == -1: + return self.errors.report(ctx.get_line(), ctx.get_column(), msg) def fail_blocker(self, msg: str, ctx: Context) -> None: diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 8777145810809..0a31a94d41a16 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -152,6 +152,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: elif fullname == 'typing.Tuple': if len(t.args) == 0 and not t.empty_tuple_index: # Bare 'Tuple' is same as 'tuple' + self.implicit_any('Tuple without type args', t) return self.builtin_type('builtins.tuple') if len(t.args) == 2 and isinstance(t.args[1], EllipsisType): # Tuple[T, ...] (uniform, variable-length tuple) @@ -174,6 +175,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: return self.analyze_callable_type(t) elif fullname == 'typing.Type': if len(t.args) == 0: + self.implicit_any('Type without type args', t) return TypeType(AnyType(), line=t.line) if len(t.args) != 1: self.fail('Type[...] must have exactly one type argument', t) @@ -183,6 +185,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: if self.nesting_level > 0: self.fail('Invalid type: ClassVar nested inside other type', t) if len(t.args) == 0: + self.implicit_any('ClassVar without type args', t) return AnyType(line=t.line) if len(t.args) != 1: self.fail('ClassVar[...] must have at most one type argument', t) @@ -202,6 +205,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: act_len = len(an_args) if exp_len > 0 and act_len == 0: # Interpret bare Alias same as normal generic, i.e., Alias[Any, Any, ...] + self.implicit_any('Generic type without type args', t) return self.replace_alias_tvars(override, all_vars, [AnyType()] * exp_len, t.line, t.column) if exp_len == 0 and act_len == 0: @@ -220,6 +224,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: # context. This is slightly problematic as it allows using the type 'Any' # as a base class -- however, this will fail soon at runtime so the problem # is pretty minor. + self.implicit_any('Assigning value of type Any', t) return AnyType() # Allow unbound type variables when defining an alias if not (self.aliasing and sym.kind == TVAR and @@ -259,6 +264,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: fallback=instance) return instance else: + self.implicit_any('Fallback', t) return AnyType() def get_type_var_names(self, tp: Type) -> List[str]: @@ -374,6 +380,7 @@ def analyze_callable_type(self, t: UnboundType) -> Type: fallback = self.builtin_type('builtins.function') if len(t.args) == 0: # Callable (bare). Treat as Callable[..., Any]. + self.implicit_any('Callable without type args', t) ret = CallableType([AnyType(), AnyType()], [nodes.ARG_STAR, nodes.ARG_STAR2], [None, None], @@ -492,6 +499,10 @@ def builtin_type(self, fully_qualified_name: str, args: List[Type] = None) -> In def tuple_type(self, items: List[Type]) -> TupleType: return TupleType(items, fallback=self.builtin_type('builtins.tuple', [AnyType()])) + def implicit_any(self, details: str, t: Type) -> None: + msg = 'Type Any created implicitly: ' + details + self.fail(msg, t, implicit_any=True) # type: ignore + class TypeAnalyserPass3(TypeVisitor[None]): """Analyze type argument counts and values of generic types. @@ -522,6 +533,8 @@ def visit_instance(self, t: Instance) -> None: if len(t.args) != len(info.type_vars): if len(t.args) == 0: # Insert implicit 'Any' type arguments. + if t.type.fullname() not in ('typing.Generator'): + self.implicit_any('{} without type args'.format(t), t) t.args = [AnyType()] * len(info.type_vars) return # Invalid number of type parameters. @@ -624,6 +637,10 @@ def visit_partial_type(self, t: PartialType) -> None: def visit_type_type(self, t: TypeType) -> None: pass + def implicit_any(self, details: str, t: Type) -> None: + msg = 'Type Any created implicitly: ' + details + self.fail(msg, t, implicit_any=True) # type: ignore + TypeVarList = List[Tuple[str, TypeVarExpr]] diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index 2f2d592b13fd1..3b4f3a21c4141 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -165,3 +165,42 @@ from typing import Any def g() -> Any: pass def f() -> Any: return g() [out] + +[case testWarnImplicitAny] +# flags: --warn-implicit-any +from typing import TypeVar, List, Tuple, Generic, Callable, Union + +T = TypeVar('T') +U = TypeVar('U') +A = TypeVar('A', str, int) + +class X(Generic[T]): + pass + +class Y(Generic[T, U]): + pass + +a1: Tuple # E: Type Any created implicitly: Tuple without type args +a2: Callable # E: Type Any created implicitly: Callable without type args +a4: list # E: Type Any created implicitly: builtins.list without type args +a5: List # E: Type Any created implicitly: builtins.list without type args +a6: X # E: Type Any created implicitly: __main__.X without type args +a7: Y # E: Type Any created implicitly: __main__.Y without type args + +def f(x: X) -> None: # E: Type Any created implicitly: __main__.X without type args + pass + +def g() -> X: # E: Type Any created implicitly: __main__.X without type args + pass + +b1: str +b2: X[int] +b3: Y[int, str] +b4 = (1, 2) +b5 = [1, 2] +b6 = 'abc' + +Z = Union[A, X[A]] +def q(z: Z) -> None: ... # E: Type Any created implicitly: Generic type without type args + +[builtins fixtures/list.pyi] From aceeca30005eeb4d4dbaf051bc17790ab3d302f1 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Sat, 24 Jun 2017 10:12:57 -0700 Subject: [PATCH 2/2] CR: add tests --- mypy/typeanal.py | 2 +- test-data/unit/check-warnings.test | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 888b8dd1cd94c..946aa3dddbf2e 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -283,7 +283,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: fallback=instance) return instance else: - self.implicit_any('Fallback', t) + self.implicit_any('Unknown type converted to Any', t) return AnyType() def get_type_var_names(self, tp: Type) -> List[str]: diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test index 072ce8df779a6..cbfb5dd3cbc84 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -168,7 +168,7 @@ def f() -> Any: return g() [case testWarnImplicitAny] # flags: --warn-implicit-any -from typing import TypeVar, List, Tuple, Generic, Callable, Union +from typing import TypeVar, List, Tuple, Generic, Callable, Union, Type, ClassVar, Any T = TypeVar('T') U = TypeVar('U') @@ -186,6 +186,14 @@ a4: list # E: Type Any created implicitly: builtins.list without type args a5: List # E: Type Any created implicitly: builtins.list without type args a6: X # E: Type Any created implicitly: __main__.X without type args a7: Y # E: Type Any created implicitly: __main__.Y without type args +a8: Type # E: Type Any created implicitly: Type without type args + +class C: + c: ClassVar # E: Type Any created implicitly: ClassVar without type args + +C1: Any = 5 + +class C2(C1): ... # E: Type Any created implicitly: Assigning value of type Any def f(x: X) -> None: # E: Type Any created implicitly: __main__.X without type args pass