@@ -3971,6 +3971,25 @@ def conditional_callable_type_map(self, expr: Expression,
3971
3971
3972
3972
return None , {}
3973
3973
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
+
3974
3993
def find_type_equals_check (self , node : ComparisonExpr , expr_indices : List [int ]
3975
3994
) -> Tuple [TypeMap , TypeMap ]:
3976
3995
"""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
4073
4092
elif is_false_literal (node ):
4074
4093
return None , {}
4075
4094
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 ):
4100
4119
if node .callee .type_guard is not None :
4101
4120
# TODO: Follow keyword args or *args, **kwargs
4102
4121
if node .arg_kinds [0 ] != nodes .ARG_POS :
4103
4122
self .fail ("Type guard requires positional argument" , node )
4104
4123
return {}, {}
4105
4124
if literal (expr ) == LITERAL_TYPE :
4106
4125
return {expr : TypeGuardType (node .callee .type_guard )}, {}
4126
+
4127
+ self ._check_for_truthy_type (type_map [node ], node )
4128
+
4129
+ return {}, {}
4107
4130
elif isinstance (node , ComparisonExpr ):
4108
4131
# Step 1: Obtain the types of each operand and whether or not we can
4109
4132
# narrow their types. (For example, we shouldn't try narrowing the
@@ -4255,6 +4278,7 @@ def has_no_custom_eq_checks(t: Type) -> bool:
4255
4278
# Restrict the type of the variable to True-ish/False-ish in the if and else branches
4256
4279
# respectively
4257
4280
vartype = type_map [node ]
4281
+ self ._check_for_truthy_type (vartype , node )
4258
4282
if_type = true_only (vartype ) # type: Type
4259
4283
else_type = false_only (vartype ) # type: Type
4260
4284
ref = node # type: Expression
0 commit comments