1313from collections import OrderedDict
1414import re
1515import difflib
16+ from textwrap import dedent
1617
1718from typing import cast , List , Dict , Any , Sequence , Iterable , Tuple , Set , Optional , Union
1819
@@ -187,7 +188,7 @@ def report(self, msg: str, context: Optional[Context], severity: str,
187188 if self .disable_count <= 0 :
188189 self .errors .report (context .get_line () if context else - 1 ,
189190 context .get_column () if context else - 1 ,
190- msg . strip () , severity = severity , file = file , offset = offset ,
191+ msg , severity = severity , file = file , offset = offset ,
191192 origin_line = origin .get_line () if origin else None )
192193
193194 def fail (self , msg : str , context : Optional [Context ], file : Optional [str ] = None ,
@@ -198,7 +199,15 @@ def fail(self, msg: str, context: Optional[Context], file: Optional[str] = None,
198199 def note (self , msg : str , context : Context , file : Optional [str ] = None ,
199200 origin : Optional [Context ] = None , offset : int = 0 ) -> None :
200201 """Report a note (unless disabled)."""
201- self .report (msg , context , 'note' , file = file , origin = origin , offset = offset )
202+ self .report (msg , context , 'note' , file = file , origin = origin ,
203+ offset = offset )
204+
205+ def note_multiline (self , messages : str , context : Context , file : Optional [str ] = None ,
206+ origin : Optional [Context ] = None , offset : int = 0 ) -> None :
207+ """Report as many notes as lines in the message (unless disabled)."""
208+ for msg in messages .splitlines ():
209+ self .report (msg , context , 'note' , file = file , origin = origin ,
210+ offset = offset )
202211
203212 def warn (self , msg : str , context : Context , file : Optional [str ] = None ,
204213 origin : Optional [Context ] = None ) -> None :
@@ -854,12 +863,25 @@ def signature_incompatible_with_supertype(
854863 name , target ), context )
855864
856865 def argument_incompatible_with_supertype (
857- self , arg_num : int , name : str , name_in_supertype : str ,
858- supertype : str , context : Context ) -> None :
866+ self , arg_num : int , name : str , type_name : Optional [ str ] ,
867+ name_in_supertype : str , supertype : str , context : Context ) -> None :
859868 target = self .override_target (name , name_in_supertype , supertype )
860869 self .fail ('Argument {} of "{}" incompatible with {}'
861870 .format (arg_num , name , target ), context )
862871
872+ if name == "__eq__" and type_name :
873+ multiline_msg = self .comparison_method_example_msg (class_name = type_name )
874+ self .note_multiline (multiline_msg , context )
875+
876+ def comparison_method_example_msg (self , class_name : str ) -> str :
877+ return dedent ('''\
878+ It is recommended for "__eq__" to work with arbitrary objects, for example:
879+ def __eq__(self, other: object) -> bool:
880+ if not isinstance(other, {class_name}):
881+ return NotImplemented
882+ return <logic to compare two {class_name} instances>
883+ ''' .format (class_name = class_name ))
884+
863885 def return_type_incompatible_with_supertype (
864886 self , name : str , name_in_supertype : str , supertype : str ,
865887 context : Context ) -> None :
@@ -1110,8 +1132,6 @@ def reveal_locals(self, type_map: Dict[str, Optional[Type]], context: Context) -
11101132 # use an ordered dictionary sorted by variable name
11111133 sorted_locals = OrderedDict (sorted (type_map .items (), key = lambda t : t [0 ]))
11121134 self .fail ("Revealed local types are:" , context )
1113- # Note that self.fail does a strip() on the message, so we cannot prepend with spaces
1114- # for indentation
11151135 for line in ['{}: {}' .format (k , v ) for k , v in sorted_locals .items ()]:
11161136 self .fail (line , context )
11171137
0 commit comments