@@ -6032,72 +6032,14 @@ def find_isinstance_check_helper(
60326032 partial_type_maps = []
60336033 for operator , expr_indices in simplified_operator_list :
60346034 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+ )
61016043 elif operator in {"in" , "not in" }:
61026044 assert len (expr_indices ) == 2
61036045 left_index , right_index = expr_indices
@@ -6242,6 +6184,81 @@ def has_no_custom_eq_checks(t: Type) -> bool:
62426184 else_map = {node : else_type } if not isinstance (else_type , UninhabitedType ) else None
62436185 return if_map , else_map
62446186
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+
62456262 def propagate_up_typemap_info (self , new_types : TypeMap ) -> TypeMap :
62466263 """Attempts refining parent expressions of any MemberExpr or IndexExprs in new_types.
62476264
0 commit comments