8
8
DeletedType , NoneTyp , TypeType
9
9
)
10
10
from mypy .nodes import TypeInfo , FuncBase , Var , FuncDef , SymbolNode , Context
11
- from mypy .nodes import ARG_POS , ARG_STAR , ARG_STAR2 , function_type , Decorator , OverloadedFuncDef
11
+ from mypy .nodes import ARG_POS , ARG_STAR , ARG_STAR2 , OpExpr , ComparisonExpr
12
+ from mypy .nodes import function_type , Decorator , OverloadedFuncDef
12
13
from mypy .messages import MessageBuilder
13
14
from mypy .maptype import map_instance_to_supertype
14
15
from mypy .expandtype import expand_type_by_instance
@@ -23,6 +24,7 @@ def analyze_member_access(name: str,
23
24
node : Context ,
24
25
is_lvalue : bool ,
25
26
is_super : bool ,
27
+ is_operator : bool ,
26
28
builtin_type : Callable [[str ], Instance ],
27
29
not_ready_callback : Callable [[str , Context ], None ],
28
30
msg : MessageBuilder ,
@@ -79,45 +81,59 @@ def analyze_member_access(name: str,
79
81
elif isinstance (typ , NoneTyp ):
80
82
# The only attribute NoneType has are those it inherits from object
81
83
return analyze_member_access (name , builtin_type ('builtins.object' ), node , is_lvalue ,
82
- is_super , builtin_type , not_ready_callback , msg ,
84
+ is_super , is_operator , builtin_type , not_ready_callback , msg ,
83
85
report_type = report_type )
84
86
elif isinstance (typ , UnionType ):
85
87
# The base object has dynamic type.
86
88
msg .disable_type_names += 1
87
- results = [analyze_member_access (name , subtype , node , is_lvalue ,
88
- is_super , builtin_type , not_ready_callback , msg )
89
+ results = [analyze_member_access (name , subtype , node , is_lvalue , is_super ,
90
+ is_operator , builtin_type , not_ready_callback , msg )
89
91
for subtype in typ .items ]
90
92
msg .disable_type_names -= 1
91
93
return UnionType .make_simplified_union (results )
92
94
elif isinstance (typ , TupleType ):
93
95
# Actually look up from the fallback instance type.
94
- return analyze_member_access (name , typ .fallback , node , is_lvalue ,
95
- is_super , builtin_type , not_ready_callback , msg )
96
+ return analyze_member_access (name , typ .fallback , node , is_lvalue , is_super ,
97
+ is_operator , builtin_type , not_ready_callback , msg )
96
98
elif isinstance (typ , FunctionLike ) and typ .is_type_obj ():
97
99
# Class attribute.
98
100
# TODO super?
99
101
ret_type = typ .items ()[0 ].ret_type
100
102
if isinstance (ret_type , TupleType ):
101
103
ret_type = ret_type .fallback
102
104
if isinstance (ret_type , Instance ):
103
- result = analyze_class_attribute_access (ret_type , name , node , is_lvalue ,
104
- builtin_type , not_ready_callback , msg )
105
- if result :
106
- return result
105
+ if not is_operator :
106
+ # When Python sees an operator (eg `3 == 4`), it automatically translates that
107
+ # into something like `int.__eq__(3, 4)` instead of `(3).__eq__(4)` as an
108
+ # optimation.
109
+ #
110
+ # While it normally it doesn't matter which of the two versions are used, it
111
+ # does cause inconsistencies when working with classes. For example, translating
112
+ # `int == int` to `int.__eq__(int)` would not work since `int.__eq__` is meant to
113
+ # compare two int _instances_. What we really want is `type(int).__eq__`, which
114
+ # is meant to compare two types or classes.
115
+ #
116
+ # This check makes sure that when we encounter an operator, we skip looking up
117
+ # the corresponding method in the current instance to avoid this edge case.
118
+ # See https://github.com/python/mypy/pull/1787 for more info.
119
+ result = analyze_class_attribute_access (ret_type , name , node , is_lvalue ,
120
+ builtin_type , not_ready_callback , msg )
121
+ if result :
122
+ return result
107
123
# Look up from the 'type' type.
108
124
return analyze_member_access (name , typ .fallback , node , is_lvalue , is_super ,
109
- builtin_type , not_ready_callback , msg ,
125
+ is_operator , builtin_type , not_ready_callback , msg ,
110
126
report_type = report_type )
111
127
else :
112
128
assert False , 'Unexpected type {}' .format (repr (ret_type ))
113
129
elif isinstance (typ , FunctionLike ):
114
130
# Look up from the 'function' type.
115
131
return analyze_member_access (name , typ .fallback , node , is_lvalue , is_super ,
116
- builtin_type , not_ready_callback , msg ,
132
+ is_operator , builtin_type , not_ready_callback , msg ,
117
133
report_type = report_type )
118
134
elif isinstance (typ , TypeVarType ):
119
135
return analyze_member_access (name , typ .upper_bound , node , is_lvalue , is_super ,
120
- builtin_type , not_ready_callback , msg ,
136
+ is_operator , builtin_type , not_ready_callback , msg ,
121
137
report_type = report_type )
122
138
elif isinstance (typ , DeletedType ):
123
139
msg .deleted_as_rvalue (typ , node )
@@ -130,14 +146,15 @@ def analyze_member_access(name: str,
130
146
elif isinstance (typ .item , TypeVarType ):
131
147
if isinstance (typ .item .upper_bound , Instance ):
132
148
item = typ .item .upper_bound
133
- if item :
149
+ if item and not is_operator :
150
+ # See comment above for why operators are skipped
134
151
result = analyze_class_attribute_access (item , name , node , is_lvalue ,
135
152
builtin_type , not_ready_callback , msg )
136
153
if result :
137
154
return result
138
155
fallback = builtin_type ('builtins.type' )
139
156
return analyze_member_access (name , fallback , node , is_lvalue , is_super ,
140
- builtin_type , not_ready_callback , msg ,
157
+ is_operator , builtin_type , not_ready_callback , msg ,
141
158
report_type = report_type )
142
159
return msg .has_no_attr (report_type , name , node )
143
160
0 commit comments