Skip to content

Commit 801f274

Browse files
committed
Let’s try to just warn in boolean context (no tests yet)
1 parent 76bd3e2 commit 801f274

File tree

2 files changed

+48
-32
lines changed

2 files changed

+48
-32
lines changed

mypy/checker.py

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3971,6 +3971,25 @@ def conditional_callable_type_map(self, expr: Expression,
39713971

39723972
return None, {}
39733973

3974+
@staticmethod
3975+
def _is_truthy_instance(t: Type) -> bool:
3976+
return (
3977+
isinstance(t, Instance) and
3978+
t.type and
3979+
not t.type.has_readable_member('__bool__') and
3980+
not t.type.has_readable_member('__len__')
3981+
)
3982+
3983+
def _check_for_truthy_type(self, t: Type, node: Node) -> None:
3984+
if self._is_truthy_instance(t):
3985+
self.msg.note(
3986+
"{} has type '{}' which does not implement __bool__ or __len__ "
3987+
"so it will always be truthy in boolean context".format(node, t), node)
3988+
elif isinstance(t, UnionType) and all(self._is_truthy_instance(t) for t in t.items):
3989+
self.msg.note(
3990+
"{} has type '{}' where none of the members implement __bool__ or __len__ "
3991+
"so it will always be truthy in boolean context".format(node, t), node)
3992+
39743993
def find_type_equals_check(self, node: ComparisonExpr, expr_indices: List[int]
39753994
) -> Tuple[TypeMap, TypeMap]:
39763995
"""Narrow types based on any checks of the type ``type(x) == T``
@@ -4073,37 +4092,41 @@ def find_isinstance_check_helper(self, node: Expression) -> Tuple[TypeMap, TypeM
40734092
elif is_false_literal(node):
40744093
return None, {}
40754094
elif isinstance(node, CallExpr):
4076-
if len(node.args) == 0:
4077-
return {}, {}
4078-
expr = collapse_walrus(node.args[0])
4079-
if refers_to_fullname(node.callee, 'builtins.isinstance'):
4080-
if len(node.args) != 2: # the error will be reported elsewhere
4081-
return {}, {}
4082-
if literal(expr) == LITERAL_TYPE:
4083-
return self.conditional_type_map_with_intersection(
4084-
expr,
4085-
type_map[expr],
4086-
get_isinstance_type(node.args[1], type_map),
4087-
)
4088-
elif refers_to_fullname(node.callee, 'builtins.issubclass'):
4089-
if len(node.args) != 2: # the error will be reported elsewhere
4090-
return {}, {}
4091-
if literal(expr) == LITERAL_TYPE:
4092-
return self.infer_issubclass_maps(node, expr, type_map)
4093-
elif refers_to_fullname(node.callee, 'builtins.callable'):
4094-
if len(node.args) != 1: # the error will be reported elsewhere
4095-
return {}, {}
4096-
if literal(expr) == LITERAL_TYPE:
4097-
vartype = type_map[expr]
4098-
return self.conditional_callable_type_map(expr, vartype)
4099-
elif isinstance(node.callee, RefExpr):
4095+
if len(node.args) > 0:
4096+
expr = collapse_walrus(node.args[0])
4097+
if refers_to_fullname(node.callee, 'builtins.isinstance'):
4098+
if len(node.args) != 2: # the error will be reported elsewhere
4099+
return {}, {}
4100+
if literal(expr) == LITERAL_TYPE:
4101+
return self.conditional_type_map_with_intersection(
4102+
expr,
4103+
type_map[expr],
4104+
get_isinstance_type(node.args[1], type_map),
4105+
)
4106+
elif refers_to_fullname(node.callee, 'builtins.issubclass'):
4107+
if len(node.args) != 2: # the error will be reported elsewhere
4108+
return {}, {}
4109+
if literal(expr) == LITERAL_TYPE:
4110+
return self.infer_issubclass_maps(node, expr, type_map)
4111+
elif refers_to_fullname(node.callee, 'builtins.callable'):
4112+
if len(node.args) != 1: # the error will be reported elsewhere
4113+
return {}, {}
4114+
if literal(expr) == LITERAL_TYPE:
4115+
vartype = type_map[expr]
4116+
return self.conditional_callable_type_map(expr, vartype)
4117+
4118+
if isinstance(node.callee, RefExpr):
41004119
if node.callee.type_guard is not None:
41014120
# TODO: Follow keyword args or *args, **kwargs
41024121
if node.arg_kinds[0] != nodes.ARG_POS:
41034122
self.fail("Type guard requires positional argument", node)
41044123
return {}, {}
41054124
if literal(expr) == LITERAL_TYPE:
41064125
return {expr: TypeGuardType(node.callee.type_guard)}, {}
4126+
4127+
self._check_for_truthy_type(type_map[node], node)
4128+
4129+
return {}, {}
41074130
elif isinstance(node, ComparisonExpr):
41084131
# Step 1: Obtain the types of each operand and whether or not we can
41094132
# narrow their types. (For example, we shouldn't try narrowing the
@@ -4255,6 +4278,7 @@ def has_no_custom_eq_checks(t: Type) -> bool:
42554278
# Restrict the type of the variable to True-ish/False-ish in the if and else branches
42564279
# respectively
42574280
vartype = type_map[node]
4281+
self._check_for_truthy_type(vartype, node)
42584282
if_type = true_only(vartype) # type: Type
42594283
else_type = false_only(vartype) # type: Type
42604284
ref = node # type: Expression

mypy/types.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -842,14 +842,6 @@ def __init__(self, typ: mypy.nodes.TypeInfo, args: Sequence[Type],
842842
# Literal context.
843843
self.last_known_value = last_known_value
844844

845-
if (
846-
self.type and
847-
not self.type.has_readable_member('__bool__') and
848-
not self.type.has_readable_member('__len__')
849-
):
850-
self.can_be_true = True
851-
self.can_be_false = False
852-
853845
def accept(self, visitor: 'TypeVisitor[T]') -> T:
854846
return visitor.visit_instance(self)
855847

0 commit comments

Comments
 (0)