Skip to content

Commit

Permalink
Warn about redundant casts (#1705)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rwbarton authored and ddfisher committed Jun 15, 2016
1 parent 741543f commit 9b71668
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 2 deletions.
5 changes: 4 additions & 1 deletion mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']

Expand Down Expand Up @@ -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,
Expand Down
5 changes: 4 additions & 1 deletion mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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."""
Expand Down
2 changes: 2 additions & 0 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,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
Expand Down
4 changes: 4 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
1 change: 1 addition & 0 deletions mypy/test/testcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
'check-bound.test',
'check-optional.test',
'check-fastparse.test',
'check-warnings.test',
]


Expand Down
36 changes: 36 additions & 0 deletions test-data/unit/check-warnings.test
Original file line number Diff line number Diff line change
@@ -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]

0 comments on commit 9b71668

Please sign in to comment.