6
6
7
7
from typing import (
8
8
Dict , Set , List , cast , Tuple , TypeVar , Union , Optional , NamedTuple , Iterator , Sequence ,
9
- Mapping ,
9
+ Mapping , Callable
10
10
)
11
11
from typing_extensions import Final
12
12
50
50
erase_def_to_union_or_bound , erase_to_union_or_bound , coerce_to_literal ,
51
51
try_getting_str_literals_from_type , try_getting_int_literals_from_type ,
52
52
tuple_fallback , is_singleton_type , try_expanding_enum_to_union ,
53
- true_only , false_only , function_type , TypeVarExtractor ,
53
+ true_only , false_only , function_type , TypeVarExtractor , custom_special_method ,
54
+ is_literal_type_like ,
54
55
)
55
56
from mypy import message_registry
56
57
from mypy .subtypes import (
@@ -3844,20 +3845,59 @@ def find_isinstance_check_helper(self, node: Expression) -> Tuple[TypeMap, TypeM
3844
3845
3845
3846
partial_type_maps = []
3846
3847
for operator , expr_indices in simplified_operator_list :
3847
- if operator in {'is' , 'is not' }:
3848
- if_map , else_map = self .refine_identity_comparison_expression (
3849
- operands ,
3850
- operand_types ,
3851
- expr_indices ,
3852
- narrowable_operand_indices ,
3853
- )
3854
- elif operator in {'==' , '!=' }:
3855
- if_map , else_map = self .refine_equality_comparison_expression (
3856
- operands ,
3857
- operand_types ,
3858
- expr_indices ,
3859
- narrowable_operand_indices ,
3860
- )
3848
+ if operator in {'is' , 'is not' , '==' , '!=' }:
3849
+ # is_valid_target:
3850
+ # Controls which types we're allowed to narrow exprs to. Note that
3851
+ # we cannot use 'is_literal_type_like' in both cases since doing
3852
+ # 'x = 10000 + 1; x is 10001' is not always True in all Python impls.
3853
+ #
3854
+ # coerce_only_in_literal_context:
3855
+ # If true, coerce types into literal types only if one or more of
3856
+ # the provided exprs contains an explicit Literal type. This could
3857
+ # technically be set to any arbitrary value, but it seems being liberal
3858
+ # with narrowing when using 'is' and conservative when using '==' seems
3859
+ # to break the least amount of real-world code.
3860
+ #
3861
+ # should_narrow_by_identity:
3862
+ # Set to 'false' only if the user defines custom __eq__ or __ne__ methods
3863
+ # that could cause identity-based narrowing to produce invalid results.
3864
+ if operator in {'is' , 'is not' }:
3865
+ is_valid_target = is_singleton_type # type: Callable[[Type], bool]
3866
+ coerce_only_in_literal_context = False
3867
+ should_narrow_by_identity = True
3868
+ else :
3869
+ is_valid_target = is_exactly_literal_type
3870
+ coerce_only_in_literal_context = True
3871
+
3872
+ def has_no_custom_eq_checks (t : Type ) -> bool :
3873
+ return not custom_special_method (t , '__eq__' , check_all = False ) \
3874
+ and not custom_special_method (t , '__ne__' , check_all = False )
3875
+ expr_types = [operand_types [i ] for i in expr_indices ]
3876
+ should_narrow_by_identity = all (map (has_no_custom_eq_checks , expr_types ))
3877
+
3878
+ if_map = {} # type: TypeMap
3879
+ else_map = {} # type: TypeMap
3880
+ if should_narrow_by_identity :
3881
+ if_map , else_map = self .refine_identity_comparison_expression (
3882
+ operands ,
3883
+ operand_types ,
3884
+ expr_indices ,
3885
+ narrowable_operand_indices ,
3886
+ is_valid_target ,
3887
+ coerce_only_in_literal_context ,
3888
+ )
3889
+
3890
+ # Strictly speaking, we should also skip this check if the objects in the expr
3891
+ # chain have custom __eq__ or __ne__ methods. But we (maybe optimistically)
3892
+ # assume nobody would actually create a custom objects that considers itself
3893
+ # equal to None.
3894
+ if if_map == {} and else_map == {}:
3895
+ if_map , else_map = self .refine_away_none_in_comparison (
3896
+ operands ,
3897
+ operand_types ,
3898
+ expr_indices ,
3899
+ narrowable_operand_indices ,
3900
+ )
3861
3901
elif operator in {'in' , 'not in' }:
3862
3902
assert len (expr_indices ) == 2
3863
3903
left_index , right_index = expr_indices
@@ -4100,8 +4140,10 @@ def refine_identity_comparison_expression(self,
4100
4140
operand_types : List [Type ],
4101
4141
chain_indices : List [int ],
4102
4142
narrowable_operand_indices : Set [int ],
4143
+ is_valid_target : Callable [[ProperType ], bool ],
4144
+ coerce_only_in_literal_context : bool ,
4103
4145
) -> Tuple [TypeMap , TypeMap ]:
4104
- """Produces conditional type maps refining expressions used in an identity comparison.
4146
+ """Produces conditional type maps refining exprs used in an identity/equality comparison.
4105
4147
4106
4148
The 'operands' and 'operand_types' lists should be the full list of operands used
4107
4149
in the overall comparison expression. The 'chain_indices' list is the list of indices
@@ -4117,49 +4159,65 @@ def refine_identity_comparison_expression(self,
4117
4159
The 'narrowable_operand_indices' parameter is the set of all indices we are allowed
4118
4160
to refine the types of: that is, all operands that will potentially be a part of
4119
4161
the output TypeMaps.
4162
+
4163
+ Although this function could theoretically try setting the types of the operands
4164
+ in the chains to the meet, doing that causes too many issues in real-world code.
4165
+ Instead, we use 'is_valid_target' to identify which of the given chain types
4166
+ we could plausibly use as the refined type for the expressions in the chain.
4167
+
4168
+ Similarly, 'coerce_only_in_literal_context' controls whether we should try coercing
4169
+ expressions in the chain to a Literal type. Performing this coercion is sometimes
4170
+ too aggressive of a narrowing, depending on context.
4120
4171
"""
4121
- singleton = None # type: Optional[ProperType]
4122
- possible_singleton_indices = []
4172
+ should_coerce = True
4173
+ if coerce_only_in_literal_context :
4174
+ should_coerce = any (is_literal_type_like (operand_types [i ]) for i in chain_indices )
4175
+
4176
+ target = None # type: Optional[Type]
4177
+ possible_target_indices = []
4123
4178
for i in chain_indices :
4124
- coerced_type = coerce_to_literal (operand_types [i ])
4125
- if not is_singleton_type (coerced_type ):
4179
+ expr_type = operand_types [i ]
4180
+ if should_coerce :
4181
+ expr_type = coerce_to_literal (expr_type )
4182
+ if not is_valid_target (get_proper_type (expr_type )):
4126
4183
continue
4127
- if singleton and not is_same_type (singleton , coerced_type ):
4128
- # We have multiple disjoint singleton types. So the 'if' branch
4184
+ if target and not is_same_type (target , expr_type ):
4185
+ # We have multiple disjoint target types. So the 'if' branch
4129
4186
# must be unreachable.
4130
4187
return None , {}
4131
- singleton = coerced_type
4132
- possible_singleton_indices .append (i )
4188
+ target = expr_type
4189
+ possible_target_indices .append (i )
4133
4190
4134
- # There's nothing we can currently infer if none of the operands are singleton types ,
4191
+ # There's nothing we can currently infer if none of the operands are valid targets ,
4135
4192
# so we end early and infer nothing.
4136
- if singleton is None :
4193
+ if target is None :
4137
4194
return {}, {}
4138
4195
4139
- # If possible, use an unassignable expression as the singleton .
4140
- # We skip refining the type of the singleton below, so ideally we'd
4196
+ # If possible, use an unassignable expression as the target .
4197
+ # We skip refining the type of the target below, so ideally we'd
4141
4198
# want to pick an expression we were going to skip anyways.
4142
4199
singleton_index = - 1
4143
- for i in possible_singleton_indices :
4200
+ for i in possible_target_indices :
4144
4201
if i not in narrowable_operand_indices :
4145
4202
singleton_index = i
4146
4203
4147
4204
# Oh well, give up and just arbitrarily pick the last item.
4148
4205
if singleton_index == - 1 :
4149
- singleton_index = possible_singleton_indices [- 1 ]
4206
+ singleton_index = possible_target_indices [- 1 ]
4150
4207
4151
4208
enum_name = None
4152
- if isinstance (singleton , LiteralType ) and singleton .is_enum_literal ():
4153
- enum_name = singleton .fallback .type .fullname
4209
+ target = get_proper_type (target )
4210
+ if isinstance (target , LiteralType ) and target .is_enum_literal ():
4211
+ enum_name = target .fallback .type .fullname
4154
4212
4155
- target_type = [TypeRange (singleton , is_upper_bound = False )]
4213
+ target_type = [TypeRange (target , is_upper_bound = False )]
4156
4214
4157
4215
partial_type_maps = []
4158
4216
for i in chain_indices :
4159
- # If we try refining a singleton against itself, conditional_type_map
4217
+ # If we try refining a type against itself, conditional_type_map
4160
4218
# will end up assuming that the 'else' branch is unreachable. This is
4161
4219
# typically not what we want: generally the user will intend for the
4162
- # singleton type to be some fixed 'sentinel' value and will want to refine
4220
+ # target type to be some fixed 'sentinel' value and will want to refine
4163
4221
# the other exprs against this one instead.
4164
4222
if i == singleton_index :
4165
4223
continue
@@ -4177,17 +4235,16 @@ def refine_identity_comparison_expression(self,
4177
4235
4178
4236
return reduce_partial_type_maps (partial_type_maps )
4179
4237
4180
- def refine_equality_comparison_expression (self ,
4181
- operands : List [Expression ],
4182
- operand_types : List [Type ],
4183
- chain_indices : List [int ],
4184
- narrowable_operand_indices : Set [int ],
4185
- ) -> Tuple [TypeMap , TypeMap ]:
4186
- """Produces conditional type maps refining expressions used in an equality comparison .
4238
+ def refine_away_none_in_comparison (self ,
4239
+ operands : List [Expression ],
4240
+ operand_types : List [Type ],
4241
+ chain_indices : List [int ],
4242
+ narrowable_operand_indices : Set [int ],
4243
+ ) -> Tuple [TypeMap , TypeMap ]:
4244
+ """Produces conditional type maps refining away None in an identity/ equality chain .
4187
4245
4188
- For more details, see the docstring of 'refine_equality_comparison' up above.
4189
- The only difference is that this function is for refining equality operations
4190
- (e.g. 'a == b == c') instead of identity ('a is b is c').
4246
+ For more details about what the different arguments mean, see the
4247
+ docstring of 'refine_identity_comparison_expression' up above.
4191
4248
"""
4192
4249
non_optional_types = []
4193
4250
for i in chain_indices :
@@ -4662,7 +4719,7 @@ def is_literal_enum(type_map: Mapping[Expression, Type], n: Expression) -> bool:
4662
4719
return False
4663
4720
4664
4721
parent_type = get_proper_type (parent_type )
4665
- member_type = coerce_to_literal (member_type )
4722
+ member_type = get_proper_type ( coerce_to_literal (member_type ) )
4666
4723
if not isinstance (parent_type , FunctionLike ) or not isinstance (member_type , LiteralType ):
4667
4724
return False
4668
4725
@@ -5252,3 +5309,9 @@ def has_bool_item(typ: ProperType) -> bool:
5252
5309
return any (is_named_instance (item , 'builtins.bool' )
5253
5310
for item in typ .items )
5254
5311
return False
5312
+
5313
+
5314
+ # TODO: why can't we define this as an inline function?
5315
+ # Does mypyc not support them?
5316
+ def is_exactly_literal_type (t : Type ) -> bool :
5317
+ return isinstance (get_proper_type (t ), LiteralType )
0 commit comments