From bea57927aca8cbd036ac05c365078eb6261c1602 Mon Sep 17 00:00:00 2001 From: Reid Barton Date: Fri, 10 Jun 2016 18:57:41 -0700 Subject: [PATCH] Warn about redundant casts A cast is considered redundant if the target type of the cast is the same as the inferred type of the expression. A cast to a supertype like `cast(object, 1)` is not considered redundant because such a cast could be needed to work around deficiencies in type inference. Fixes #958. --- mypy/build.py | 5 ++++- mypy/checker.py | 5 ++++- mypy/checkexpr.py | 2 ++ mypy/main.py | 4 ++++ mypy/messages.py | 3 +++ mypy/test/testcheck.py | 1 + test-data/unit/check-warnings.test | 36 ++++++++++++++++++++++++++++++ 7 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 test-data/unit/check-warnings.test diff --git a/mypy/build.py b/mypy/build.py index 04f7a49a4cc5..b61178be4ed9 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -68,6 +68,8 @@ CHECK_UNTYPED_DEFS = 'check-untyped-defs' # Also check typeshed for missing annotations WARN_INCOMPLETE_STUB = 'warn-incomplete-stub' +# Warn about casting an expression to its inferred type +WARN_REDUNDANT_CASTS = 'warn-redundant-casts' PYTHON_EXTENSIONS = ['.pyi', '.py'] @@ -386,7 +388,8 @@ def __init__(self, data_dir: str, DISALLOW_UNTYPED_CALLS in self.flags, DISALLOW_UNTYPED_DEFS in self.flags, check_untyped_defs, - WARN_INCOMPLETE_STUB in self.flags) + WARN_INCOMPLETE_STUB in self.flags, + WARN_REDUNDANT_CASTS in self.flags) self.missing_modules = set() # type: Set[str] def all_imported_modules_in_file(self, diff --git a/mypy/checker.py b/mypy/checker.py index d0a51dac70d4..03e73a624cae 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -380,12 +380,14 @@ class TypeChecker(NodeVisitor[Type]): # Should we check untyped function defs? check_untyped_defs = False warn_incomplete_stub = False + warn_redundant_casts = False is_typeshed_stub = False def __init__(self, errors: Errors, modules: Dict[str, MypyFile], pyversion: Tuple[int, int] = defaults.PYTHON3_VERSION, disallow_untyped_calls=False, disallow_untyped_defs=False, - check_untyped_defs=False, warn_incomplete_stub=False) -> None: + check_untyped_defs=False, warn_incomplete_stub=False, + warn_redundant_casts=False) -> None: """Construct a type checker. Use errors to report type check errors. @@ -411,6 +413,7 @@ def __init__(self, errors: Errors, modules: Dict[str, MypyFile], self.disallow_untyped_defs = disallow_untyped_defs self.check_untyped_defs = check_untyped_defs self.warn_incomplete_stub = warn_incomplete_stub + self.warn_redundant_casts = warn_redundant_casts def visit_file(self, file_node: MypyFile, path: str) -> None: """Type check a mypy file with the given path.""" diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index d759c96475e1..f70979c59b1a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1229,6 +1229,8 @@ def visit_cast_expr(self, expr: CastExpr) -> Type: """Type check a cast expression.""" source_type = self.accept(expr.expr, context=AnyType()) target_type = expr.type + if self.chk.warn_redundant_casts and is_same_type(source_type, target_type): + self.msg.redundant_cast(target_type, expr) if not self.is_valid_cast(source_type, target_type): self.msg.invalid_cast(target_type, source_type, expr) return target_type diff --git a/mypy/main.py b/mypy/main.py index 256abb429e2d..a45b4f163618 100644 --- a/mypy/main.py +++ b/mypy/main.py @@ -153,6 +153,8 @@ def parse_version(v): parser.add_argument('--warn-incomplete-stub', action='store_true', help="warn if missing type annotation in typeshed, only relevant with" " --check-untyped-defs enabled") + parser.add_argument('--warn-redundant-casts', action='store_true', + help="warn about casting an expression to its inferred type") parser.add_argument('--fast-parser', action='store_true', help="enable experimental fast parser") parser.add_argument('-i', '--incremental', action='store_true', @@ -251,6 +253,8 @@ def parse_version(v): if args.warn_incomplete_stub: options.build_flags.append(build.WARN_INCOMPLETE_STUB) + if args.warn_redundant_casts: + options.build_flags.append(build.WARN_REDUNDANT_CASTS) # experimental if args.fast_parser: diff --git a/mypy/messages.py b/mypy/messages.py index c73f641d3afd..209010764eca 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -841,6 +841,9 @@ def reveal_type(self, typ: Type, context: Context) -> None: def unsupported_type_type(self, item: Type, context: Context) -> None: self.fail('Unsupported type Type[{}]'.format(self.format(item)), context) + def redundant_cast(self, typ: Type, context: Context) -> None: + self.note('Redundant cast to {}'.format(self.format(typ)), context) + def capitalize(s: str) -> str: """Capitalize the first character of a string.""" diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 62341282482e..11cb20553fe6 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -60,6 +60,7 @@ 'check-bound.test', 'check-optional.test', 'check-fastparse.test', + 'check-warnings.test', ] diff --git a/test-data/unit/check-warnings.test b/test-data/unit/check-warnings.test new file mode 100644 index 000000000000..17ed833711f9 --- /dev/null +++ b/test-data/unit/check-warnings.test @@ -0,0 +1,36 @@ +-- Test cases for warning generation. + +-- Redundant casts +-- --------------- + +[case testRedundantCast] +# flags: warn-redundant-casts +from typing import cast +a = 1 +b = cast(str, a) +c = cast(int, a) +[out] +main:5: note: Redundant cast to "int" + +[case testRedundantCastWithIsinstance] +# flags: warn-redundant-casts +from typing import cast, Union +x = 1 # type: Union[int, str] +if isinstance(x, str): + cast(str, x) +[builtins fixtures/isinstance.py] +[out] +main:5: note: Redundant cast to "str" + +[case testCastToSuperclassNotRedundant] +# flags: warn-redundant-casts +from typing import cast, TypeVar, List +T = TypeVar('T') +def add(xs: List[T], ys: List[T]) -> List[T]: pass +class A: pass +class B(A): pass +a = A() +b = B() +# Without the cast, the following line would fail to type check. +c = add([cast(A, b)], [a]) +[builtins fixtures/list.py]