@@ -6032,72 +6032,14 @@ def find_isinstance_check_helper(
6032
6032
partial_type_maps = []
6033
6033
for operator , expr_indices in simplified_operator_list :
6034
6034
if operator in {"is" , "is not" , "==" , "!=" }:
6035
- # is_valid_target:
6036
- # Controls which types we're allowed to narrow exprs to. Note that
6037
- # we cannot use 'is_literal_type_like' in both cases since doing
6038
- # 'x = 10000 + 1; x is 10001' is not always True in all Python
6039
- # implementations.
6040
- #
6041
- # coerce_only_in_literal_context:
6042
- # If true, coerce types into literal types only if one or more of
6043
- # the provided exprs contains an explicit Literal type. This could
6044
- # technically be set to any arbitrary value, but it seems being liberal
6045
- # with narrowing when using 'is' and conservative when using '==' seems
6046
- # to break the least amount of real-world code.
6047
- #
6048
- # should_narrow_by_identity:
6049
- # Set to 'false' only if the user defines custom __eq__ or __ne__ methods
6050
- # that could cause identity-based narrowing to produce invalid results.
6051
- if operator in {"is" , "is not" }:
6052
- is_valid_target : Callable [[Type ], bool ] = is_singleton_type
6053
- coerce_only_in_literal_context = False
6054
- should_narrow_by_identity = True
6055
- else :
6056
-
6057
- def is_exactly_literal_type (t : Type ) -> bool :
6058
- return isinstance (get_proper_type (t ), LiteralType )
6059
-
6060
- def has_no_custom_eq_checks (t : Type ) -> bool :
6061
- return not custom_special_method (
6062
- t , "__eq__" , check_all = False
6063
- ) and not custom_special_method (t , "__ne__" , check_all = False )
6064
-
6065
- is_valid_target = is_exactly_literal_type
6066
- coerce_only_in_literal_context = True
6067
-
6068
- expr_types = [operand_types [i ] for i in expr_indices ]
6069
- should_narrow_by_identity = all (
6070
- map (has_no_custom_eq_checks , expr_types )
6071
- ) and not is_ambiguous_mix_of_enums (expr_types )
6072
-
6073
- if_map : TypeMap = {}
6074
- else_map : TypeMap = {}
6075
- if should_narrow_by_identity :
6076
- if_map , else_map = self .refine_identity_comparison_expression (
6077
- operands ,
6078
- operand_types ,
6079
- expr_indices ,
6080
- narrowable_operand_index_to_hash .keys (),
6081
- is_valid_target ,
6082
- coerce_only_in_literal_context ,
6083
- )
6084
-
6085
- # Strictly speaking, we should also skip this check if the objects in the expr
6086
- # chain have custom __eq__ or __ne__ methods. But we (maybe optimistically)
6087
- # assume nobody would actually create a custom objects that considers itself
6088
- # equal to None.
6089
- if if_map == {} and else_map == {}:
6090
- if_map , else_map = self .refine_away_none_in_comparison (
6091
- operands ,
6092
- operand_types ,
6093
- expr_indices ,
6094
- narrowable_operand_index_to_hash .keys (),
6095
- )
6096
-
6097
- # If we haven't been able to narrow types yet, we might be dealing with a
6098
- # explicit type(x) == some_type check
6099
- if if_map == {} and else_map == {}:
6100
- if_map , else_map = self .find_type_equals_check (node , expr_indices )
6035
+ if_map , else_map = self .equality_type_narrowing_helper (
6036
+ node ,
6037
+ operator ,
6038
+ operands ,
6039
+ operand_types ,
6040
+ expr_indices ,
6041
+ narrowable_operand_index_to_hash ,
6042
+ )
6101
6043
elif operator in {"in" , "not in" }:
6102
6044
assert len (expr_indices ) == 2
6103
6045
left_index , right_index = expr_indices
@@ -6242,6 +6184,81 @@ def has_no_custom_eq_checks(t: Type) -> bool:
6242
6184
else_map = {node : else_type } if not isinstance (else_type , UninhabitedType ) else None
6243
6185
return if_map , else_map
6244
6186
6187
+ def equality_type_narrowing_helper (
6188
+ self ,
6189
+ node : ComparisonExpr ,
6190
+ operator : str ,
6191
+ operands : list [Expression ],
6192
+ operand_types : list [Type ],
6193
+ expr_indices : list [int ],
6194
+ narrowable_operand_index_to_hash : dict [int , tuple [Key , ...]],
6195
+ ) -> tuple [TypeMap , TypeMap ]:
6196
+ """Calculate type maps for '==', '!=', 'is' or 'is not' expression."""
6197
+ # is_valid_target:
6198
+ # Controls which types we're allowed to narrow exprs to. Note that
6199
+ # we cannot use 'is_literal_type_like' in both cases since doing
6200
+ # 'x = 10000 + 1; x is 10001' is not always True in all Python
6201
+ # implementations.
6202
+ #
6203
+ # coerce_only_in_literal_context:
6204
+ # If true, coerce types into literal types only if one or more of
6205
+ # the provided exprs contains an explicit Literal type. This could
6206
+ # technically be set to any arbitrary value, but it seems being liberal
6207
+ # with narrowing when using 'is' and conservative when using '==' seems
6208
+ # to break the least amount of real-world code.
6209
+ #
6210
+ # should_narrow_by_identity:
6211
+ # Set to 'false' only if the user defines custom __eq__ or __ne__ methods
6212
+ # that could cause identity-based narrowing to produce invalid results.
6213
+ if operator in {"is" , "is not" }:
6214
+ is_valid_target : Callable [[Type ], bool ] = is_singleton_type
6215
+ coerce_only_in_literal_context = False
6216
+ should_narrow_by_identity = True
6217
+ else :
6218
+
6219
+ def is_exactly_literal_type (t : Type ) -> bool :
6220
+ return isinstance (get_proper_type (t ), LiteralType )
6221
+
6222
+ def has_no_custom_eq_checks (t : Type ) -> bool :
6223
+ return not custom_special_method (
6224
+ t , "__eq__" , check_all = False
6225
+ ) and not custom_special_method (t , "__ne__" , check_all = False )
6226
+
6227
+ is_valid_target = is_exactly_literal_type
6228
+ coerce_only_in_literal_context = True
6229
+
6230
+ expr_types = [operand_types [i ] for i in expr_indices ]
6231
+ should_narrow_by_identity = all (
6232
+ map (has_no_custom_eq_checks , expr_types )
6233
+ ) and not is_ambiguous_mix_of_enums (expr_types )
6234
+
6235
+ if_map : TypeMap = {}
6236
+ else_map : TypeMap = {}
6237
+ if should_narrow_by_identity :
6238
+ if_map , else_map = self .refine_identity_comparison_expression (
6239
+ operands ,
6240
+ operand_types ,
6241
+ expr_indices ,
6242
+ narrowable_operand_index_to_hash .keys (),
6243
+ is_valid_target ,
6244
+ coerce_only_in_literal_context ,
6245
+ )
6246
+
6247
+ # Strictly speaking, we should also skip this check if the objects in the expr
6248
+ # chain have custom __eq__ or __ne__ methods. But we (maybe optimistically)
6249
+ # assume nobody would actually create a custom objects that considers itself
6250
+ # equal to None.
6251
+ if if_map == {} and else_map == {}:
6252
+ if_map , else_map = self .refine_away_none_in_comparison (
6253
+ operands , operand_types , expr_indices , narrowable_operand_index_to_hash .keys ()
6254
+ )
6255
+
6256
+ # If we haven't been able to narrow types yet, we might be dealing with a
6257
+ # explicit type(x) == some_type check
6258
+ if if_map == {} and else_map == {}:
6259
+ if_map , else_map = self .find_type_equals_check (node , expr_indices )
6260
+ return if_map , else_map
6261
+
6245
6262
def propagate_up_typemap_info (self , new_types : TypeMap ) -> TypeMap :
6246
6263
"""Attempts refining parent expressions of any MemberExpr or IndexExprs in new_types.
6247
6264
0 commit comments