1212from __future__ import annotations
1313
1414import difflib
15+ import itertools
1516import re
1617from contextlib import contextmanager
1718from textwrap import dedent
@@ -208,34 +209,40 @@ def report(
208209 origin : Context | None = None ,
209210 offset : int = 0 ,
210211 allow_dups : bool = False ,
212+ secondary_context : Context | None = None ,
211213 ) -> None :
212214 """Report an error or note (unless disabled).
213215
214216 Note that context controls where error is reported, while origin controls
215217 where # type: ignore comments have effect.
216218 """
217219
218- def span_from_context (ctx : Context ) -> tuple [ int , int ]:
220+ def span_from_context (ctx : Context ) -> Iterable [ int ]:
219221 """This determines where a type: ignore for a given context has effect.
220222
221223 Current logic is a bit tricky, to keep as much backwards compatibility as
222224 possible. We may reconsider this to always be a single line (or otherwise
223225 simplify it) when we drop Python 3.7.
224226 """
225227 if isinstance (ctx , (ClassDef , FuncDef )):
226- return ctx .deco_line or ctx .line , ctx .line
228+ return range ( ctx .deco_line or ctx .line , ctx .line + 1 )
227229 elif not isinstance (ctx , Expression ):
228- return ctx .line , ctx . line
230+ return [ ctx .line ]
229231 else :
230- return ctx .line , ctx .end_line or ctx .line
232+ return range ( ctx .line , ( ctx .end_line or ctx .line ) + 1 )
231233
232- origin_span : tuple [ int , int ] | None
234+ origin_span : Iterable [ int ] | None
233235 if origin is not None :
234236 origin_span = span_from_context (origin )
235237 elif context is not None :
236238 origin_span = span_from_context (context )
237239 else :
238240 origin_span = None
241+
242+ if secondary_context is not None :
243+ assert origin_span is not None
244+ origin_span = itertools .chain (origin_span , span_from_context (secondary_context ))
245+
239246 self .errors .report (
240247 context .line if context else - 1 ,
241248 context .column if context else - 1 ,
@@ -258,9 +265,18 @@ def fail(
258265 code : ErrorCode | None = None ,
259266 file : str | None = None ,
260267 allow_dups : bool = False ,
268+ secondary_context : Context | None = None ,
261269 ) -> None :
262270 """Report an error message (unless disabled)."""
263- self .report (msg , context , "error" , code = code , file = file , allow_dups = allow_dups )
271+ self .report (
272+ msg ,
273+ context ,
274+ "error" ,
275+ code = code ,
276+ file = file ,
277+ allow_dups = allow_dups ,
278+ secondary_context = secondary_context ,
279+ )
264280
265281 def note (
266282 self ,
@@ -272,6 +288,7 @@ def note(
272288 allow_dups : bool = False ,
273289 * ,
274290 code : ErrorCode | None = None ,
291+ secondary_context : Context | None = None ,
275292 ) -> None :
276293 """Report a note (unless disabled)."""
277294 self .report (
@@ -283,6 +300,7 @@ def note(
283300 offset = offset ,
284301 allow_dups = allow_dups ,
285302 code = code ,
303+ secondary_context = secondary_context ,
286304 )
287305
288306 def note_multiline (
@@ -293,11 +311,20 @@ def note_multiline(
293311 offset : int = 0 ,
294312 allow_dups : bool = False ,
295313 code : ErrorCode | None = None ,
314+ * ,
315+ secondary_context : Context | None = None ,
296316 ) -> None :
297317 """Report as many notes as lines in the message (unless disabled)."""
298318 for msg in messages .splitlines ():
299319 self .report (
300- msg , context , "note" , file = file , offset = offset , allow_dups = allow_dups , code = code
320+ msg ,
321+ context ,
322+ "note" ,
323+ file = file ,
324+ offset = offset ,
325+ allow_dups = allow_dups ,
326+ code = code ,
327+ secondary_context = secondary_context ,
301328 )
302329
303330 #
@@ -1151,6 +1178,7 @@ def argument_incompatible_with_supertype(
11511178 arg_type_in_supertype : Type ,
11521179 supertype : str ,
11531180 context : Context ,
1181+ secondary_context : Context ,
11541182 ) -> None :
11551183 target = self .override_target (name , name_in_supertype , supertype )
11561184 arg_type_in_supertype_f = format_type_bare (arg_type_in_supertype )
@@ -1161,17 +1189,26 @@ def argument_incompatible_with_supertype(
11611189 ),
11621190 context ,
11631191 code = codes .OVERRIDE ,
1192+ secondary_context = secondary_context ,
1193+ )
1194+ self .note (
1195+ "This violates the Liskov substitution principle" ,
1196+ context ,
1197+ code = codes .OVERRIDE ,
1198+ secondary_context = secondary_context ,
11641199 )
1165- self .note ("This violates the Liskov substitution principle" , context , code = codes .OVERRIDE )
11661200 self .note (
11671201 "See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides" ,
11681202 context ,
11691203 code = codes .OVERRIDE ,
1204+ secondary_context = secondary_context ,
11701205 )
11711206
11721207 if name == "__eq__" and type_name :
11731208 multiline_msg = self .comparison_method_example_msg (class_name = type_name )
1174- self .note_multiline (multiline_msg , context , code = codes .OVERRIDE )
1209+ self .note_multiline (
1210+ multiline_msg , context , code = codes .OVERRIDE , secondary_context = secondary_context
1211+ )
11751212
11761213 def comparison_method_example_msg (self , class_name : str ) -> str :
11771214 return dedent (
0 commit comments