@@ -778,7 +778,7 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
778
778
self .msg , context = fdef )
779
779
780
780
if name : # Special method names
781
- if defn .info and name in nodes . reverse_op_method_set :
781
+ if defn .info and self . is_reverse_op_method ( name ) :
782
782
self .check_reverse_op_method (item , typ , name , defn )
783
783
elif name in ('__getattr__' , '__getattribute__' ):
784
784
self .check_getattr_method (typ , defn , name )
@@ -923,6 +923,18 @@ def check_func_def(self, defn: FuncItem, typ: CallableType, name: Optional[str])
923
923
924
924
self .binder = old_binder
925
925
926
+ def is_forward_op_method (self , method_name : str ) -> bool :
927
+ if self .options .python_version [0 ] == 2 and method_name == '__div__' :
928
+ return True
929
+ else :
930
+ return method_name in nodes .reverse_op_methods
931
+
932
+ def is_reverse_op_method (self , method_name : str ) -> bool :
933
+ if self .options .python_version [0 ] == 2 and method_name == '__rdiv__' :
934
+ return True
935
+ else :
936
+ return method_name in nodes .reverse_op_method_set
937
+
926
938
def check_for_missing_annotations (self , fdef : FuncItem ) -> None :
927
939
# Check for functions with unspecified/not fully specified types.
928
940
def is_unannotated_any (t : Type ) -> bool :
@@ -1010,7 +1022,10 @@ def check_reverse_op_method(self, defn: FuncItem,
1010
1022
arg_names = [reverse_type .arg_names [0 ], "_" ])
1011
1023
assert len (reverse_type .arg_types ) >= 2
1012
1024
1013
- forward_name = nodes .normal_from_reverse_op [reverse_name ]
1025
+ if self .options .python_version [0 ] == 2 and reverse_name == '__rdiv__' :
1026
+ forward_name = '__div__'
1027
+ else :
1028
+ forward_name = nodes .normal_from_reverse_op [reverse_name ]
1014
1029
forward_inst = reverse_type .arg_types [1 ]
1015
1030
if isinstance (forward_inst , TypeVarType ):
1016
1031
forward_inst = forward_inst .upper_bound
@@ -1042,73 +1057,105 @@ def check_overlapping_op_methods(self,
1042
1057
context : Context ) -> None :
1043
1058
"""Check for overlapping method and reverse method signatures.
1044
1059
1045
- Assume reverse method has valid argument count and kinds.
1060
+ This function assumes that:
1061
+
1062
+ - The reverse method has valid argument count and kinds.
1063
+ - If the reverse operator method accepts some argument of type
1064
+ X, the forward operator method also belong to class X.
1065
+
1066
+ For example, if we have the reverse operator `A.__radd__(B)`, then the
1067
+ corresponding forward operator must have the type `B.__add__(...)`.
1046
1068
"""
1047
1069
1048
- # Reverse operator method that overlaps unsafely with the
1049
- # forward operator method can result in type unsafety. This is
1050
- # similar to overlapping overload variants.
1070
+ # Note: Suppose we have two operator methods "A.__rOP__(B) -> R1" and
1071
+ # "B.__OP__(C) -> R2". We check if these two methods are unsafely overlapping
1072
+ # by using the following algorithm:
1073
+ #
1074
+ # 1. Rewrite "B.__OP__(C) -> R1" to "temp1(B, C) -> R1"
1075
+ #
1076
+ # 2. Rewrite "A.__rOP__(B) -> R2" to "temp2(B, A) -> R2"
1077
+ #
1078
+ # 3. Treat temp1 and temp2 as if they were both variants in the same
1079
+ # overloaded function. (This mirrors how the Python runtime calls
1080
+ # operator methods: we first try __OP__, then __rOP__.)
1081
+ #
1082
+ # If the first signature is unsafely overlapping with the second,
1083
+ # report an error.
1051
1084
#
1052
- # This example illustrates the issue:
1085
+ # 4. However, if temp1 shadows temp2 (e.g. the __rOP__ method can never
1086
+ # be called), do NOT report an error.
1053
1087
#
1054
- # class X: pass
1055
- # class A:
1056
- # def __add__(self, x: X) -> int:
1057
- # if isinstance(x, X):
1058
- # return 1
1059
- # return NotImplemented
1060
- # class B:
1061
- # def __radd__(self, x: A) -> str: return 'x'
1062
- # class C(X, B): pass
1063
- # def f(b: B) -> None:
1064
- # A() + b # Result is 1, even though static type seems to be str!
1065
- # f(C())
1088
+ # This behavior deviates from how we handle overloads -- many of the
1089
+ # modules in typeshed seem to define __OP__ methods that shadow the
1090
+ # corresponding __rOP__ method.
1066
1091
#
1067
- # The reason for the problem is that B and X are overlapping
1068
- # types, and the return types are different. Also, if the type
1069
- # of x in __radd__ would not be A, the methods could be
1070
- # non-overlapping.
1092
+ # Note: we do not attempt to handle unsafe overlaps related to multiple
1093
+ # inheritance. (This is consistent with how we handle overloads: we also
1094
+ # do not try checking unsafe overlaps due to multiple inheritance there.)
1071
1095
1072
1096
for forward_item in union_items (forward_type ):
1073
1097
if isinstance (forward_item , CallableType ):
1074
- # TODO check argument kinds
1075
- if len (forward_item .arg_types ) < 1 :
1076
- # Not a valid operator method -- can't succeed anyway.
1077
- return
1078
-
1079
- # Construct normalized function signatures corresponding to the
1080
- # operator methods. The first argument is the left operand and the
1081
- # second operand is the right argument -- we switch the order of
1082
- # the arguments of the reverse method.
1083
- forward_tweaked = CallableType (
1084
- [forward_base , forward_item .arg_types [0 ]],
1085
- [nodes .ARG_POS ] * 2 ,
1086
- [None ] * 2 ,
1087
- forward_item .ret_type ,
1088
- forward_item .fallback ,
1089
- name = forward_item .name )
1090
- reverse_args = reverse_type .arg_types
1091
- reverse_tweaked = CallableType (
1092
- [reverse_args [1 ], reverse_args [0 ]],
1093
- [nodes .ARG_POS ] * 2 ,
1094
- [None ] * 2 ,
1095
- reverse_type .ret_type ,
1096
- fallback = self .named_type ('builtins.function' ),
1097
- name = reverse_type .name )
1098
-
1099
- if is_unsafe_overlapping_operator_signatures (
1100
- forward_tweaked , reverse_tweaked ):
1098
+ if self .is_unsafe_overlapping_op (forward_item , forward_base , reverse_type ):
1101
1099
self .msg .operator_method_signatures_overlap (
1102
1100
reverse_class , reverse_name ,
1103
1101
forward_base , forward_name , context )
1104
1102
elif isinstance (forward_item , Overloaded ):
1105
1103
for item in forward_item .items ():
1106
- self .check_overlapping_op_methods (
1107
- reverse_type , reverse_name , reverse_class ,
1108
- item , forward_name , forward_base , context )
1104
+ if self .is_unsafe_overlapping_op (item , forward_base , reverse_type ):
1105
+ self .msg .operator_method_signatures_overlap (
1106
+ reverse_class , reverse_name ,
1107
+ forward_base , forward_name ,
1108
+ context )
1109
1109
elif not isinstance (forward_item , AnyType ):
1110
1110
self .msg .forward_operator_not_callable (forward_name , context )
1111
1111
1112
+ def is_unsafe_overlapping_op (self ,
1113
+ forward_item : CallableType ,
1114
+ forward_base : Type ,
1115
+ reverse_type : CallableType ) -> bool :
1116
+ # TODO: check argument kinds?
1117
+ if len (forward_item .arg_types ) < 1 :
1118
+ # Not a valid operator method -- can't succeed anyway.
1119
+ return False
1120
+
1121
+ # Erase the type if necessary to make sure we don't have a single
1122
+ # TypeVar in forward_tweaked. (Having a function signature containing
1123
+ # just a single TypeVar can lead to unpredictable behavior.)
1124
+ forward_base_erased = forward_base
1125
+ if isinstance (forward_base , TypeVarType ):
1126
+ forward_base_erased = erase_to_bound (forward_base )
1127
+
1128
+ # Construct normalized function signatures corresponding to the
1129
+ # operator methods. The first argument is the left operand and the
1130
+ # second operand is the right argument -- we switch the order of
1131
+ # the arguments of the reverse method.
1132
+
1133
+ forward_tweaked = forward_item .copy_modified (
1134
+ arg_types = [forward_base_erased , forward_item .arg_types [0 ]],
1135
+ arg_kinds = [nodes .ARG_POS ] * 2 ,
1136
+ arg_names = [None ] * 2 ,
1137
+ )
1138
+ reverse_tweaked = reverse_type .copy_modified (
1139
+ arg_types = [reverse_type .arg_types [1 ], reverse_type .arg_types [0 ]],
1140
+ arg_kinds = [nodes .ARG_POS ] * 2 ,
1141
+ arg_names = [None ] * 2 ,
1142
+ )
1143
+
1144
+ reverse_base_erased = reverse_type .arg_types [0 ]
1145
+ if isinstance (reverse_base_erased , TypeVarType ):
1146
+ reverse_base_erased = erase_to_bound (reverse_base_erased )
1147
+
1148
+ if is_same_type (reverse_base_erased , forward_base_erased ):
1149
+ return False
1150
+ elif is_subtype (reverse_base_erased , forward_base_erased ):
1151
+ first = reverse_tweaked
1152
+ second = forward_tweaked
1153
+ else :
1154
+ first = forward_tweaked
1155
+ second = reverse_tweaked
1156
+
1157
+ return is_unsafe_overlapping_overload_signatures (first , second )
1158
+
1112
1159
def check_inplace_operator_method (self , defn : FuncBase ) -> None :
1113
1160
"""Check an inplace operator method such as __iadd__.
1114
1161
@@ -1312,7 +1359,7 @@ def check_override(self, override: FunctionLike, original: FunctionLike,
1312
1359
fail = True
1313
1360
elif (not isinstance (original , Overloaded ) and
1314
1361
isinstance (override , Overloaded ) and
1315
- name in nodes . reverse_op_methods . keys ( )):
1362
+ self . is_forward_op_method ( name )):
1316
1363
# Operator method overrides cannot introduce overloading, as
1317
1364
# this could be unsafe with reverse operator methods.
1318
1365
fail = True
0 commit comments