diff --git a/mypy/main.py b/mypy/main.py index 978db244b089..85e26301067c 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -272,6 +272,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 fac8fe6d4459..5923768f2984 100644 --- a/mypy/options.py +++ b/mypy/options.py @@ -28,6 +28,7 @@ class Options: "show_none_errors", "warn_no_return", "warn_return_any", + "warn_implicit_any", "ignore_errors", "strict_boolean", "no_implicit_optional", @@ -73,6 +74,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 c6749d25a92c..f801d6fd1b74 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3542,12 +3542,15 @@ def name_already_defined(self, name: str, ctx: Context, self.fail("Name '{}' already defined{}".format(name, extra_msg), 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) @@ -3984,7 +3987,14 @@ def analyze(self, type: Optional[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 00b5c9fc52c9..946aa3dddbf2 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -170,6 +170,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.named_type('builtins.tuple') if len(t.args) == 2 and isinstance(t.args[1], EllipsisType): # Tuple[T, ...] (uniform, variable-length tuple) @@ -192,6 +193,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) @@ -201,6 +203,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) @@ -221,6 +224,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: @@ -239,6 +243,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(from_unimported_type=True) # Allow unbound type variables when defining an alias if not (self.aliasing and sym.kind == TVAR and @@ -278,6 +283,7 @@ def visit_unbound_type(self, t: UnboundType) -> Type: fallback=instance) return instance else: + self.implicit_any('Unknown type converted to Any', t) return AnyType() def get_type_var_names(self, tp: Type) -> List[str]: @@ -397,6 +403,7 @@ def analyze_callable_type(self, t: UnboundType) -> Type: fallback = self.named_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], @@ -472,8 +479,8 @@ def analyze_callable_args(self, arglist: TypeList) -> Optional[Tuple[List[Type], def analyze_type(self, t: Type) -> Type: return t.accept(self) - def fail(self, msg: str, ctx: Context) -> None: - self.fail_func(msg, ctx) + def fail(self, msg: str, ctx: Context, implicit_any: bool = False) -> None: + self.fail_func(msg, ctx, implicit_any=implicit_any) # type: ignore @contextmanager def tvar_scope_frame(self) -> Iterator[None]: @@ -562,6 +569,10 @@ def named_type(self, fully_qualified_name: str, args: List[Type] = None) -> Inst def tuple_type(self, items: List[Type]) -> TupleType: return TupleType(items, fallback=self.named_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. @@ -592,6 +603,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. @@ -697,6 +710,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 c95baec1cc93..cbfb5dd3cbc8 100644 --- a/test-data/unit/check-warnings.test +++ b/test-data/unit/check-warnings.test @@ -166,6 +166,53 @@ 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, Type, ClassVar, Any + +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 +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 + +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] + [case testOKReturnAnyIfProperSubtype] # flags: --warn-return-any --strict-optional from typing import Any, Optional