1212from __future__ import annotations
1313
1414import difflib
15+ import itertools
1516import re
1617from contextlib import contextmanager
1718from textwrap import dedent
@@ -210,34 +211,40 @@ def report(
210211 origin : Context | None = None ,
211212 offset : int = 0 ,
212213 allow_dups : bool = False ,
214+ secondary_context : Context | None = None ,
213215 ) -> None :
214216 """Report an error or note (unless disabled).
215217
216218 Note that context controls where error is reported, while origin controls
217219 where # type: ignore comments have effect.
218220 """
219221
220- def span_from_context (ctx : Context ) -> tuple [ int , int ]:
222+ def span_from_context (ctx : Context ) -> Iterable [ int ]:
221223 """This determines where a type: ignore for a given context has effect.
222224
223225 Current logic is a bit tricky, to keep as much backwards compatibility as
224226 possible. We may reconsider this to always be a single line (or otherwise
225227 simplify it) when we drop Python 3.7.
226228 """
227229 if isinstance (ctx , (ClassDef , FuncDef )):
228- return ctx .deco_line or ctx .line , ctx .line
230+ return range ( ctx .deco_line or ctx .line , ctx .line + 1 )
229231 elif not isinstance (ctx , Expression ):
230- return ctx .line , ctx . line
232+ return [ ctx .line ]
231233 else :
232- return ctx .line , ctx .end_line or ctx .line
234+ return range ( ctx .line , ( ctx .end_line or ctx .line ) + 1 )
233235
234- origin_span : tuple [ int , int ] | None
236+ origin_span : Iterable [ int ] | None
235237 if origin is not None :
236238 origin_span = span_from_context (origin )
237239 elif context is not None :
238240 origin_span = span_from_context (context )
239241 else :
240242 origin_span = None
243+
244+ if secondary_context is not None :
245+ assert origin_span is not None
246+ origin_span = itertools .chain (origin_span , span_from_context (secondary_context ))
247+
241248 self .errors .report (
242249 context .line if context else - 1 ,
243250 context .column if context else - 1 ,
@@ -260,9 +267,18 @@ def fail(
260267 code : ErrorCode | None = None ,
261268 file : str | None = None ,
262269 allow_dups : bool = False ,
270+ secondary_context : Context | None = None ,
263271 ) -> None :
264272 """Report an error message (unless disabled)."""
265- self .report (msg , context , "error" , code = code , file = file , allow_dups = allow_dups )
273+ self .report (
274+ msg ,
275+ context ,
276+ "error" ,
277+ code = code ,
278+ file = file ,
279+ allow_dups = allow_dups ,
280+ secondary_context = secondary_context ,
281+ )
266282
267283 def note (
268284 self ,
@@ -274,6 +290,7 @@ def note(
274290 allow_dups : bool = False ,
275291 * ,
276292 code : ErrorCode | None = None ,
293+ secondary_context : Context | None = None ,
277294 ) -> None :
278295 """Report a note (unless disabled)."""
279296 self .report (
@@ -285,6 +302,7 @@ def note(
285302 offset = offset ,
286303 allow_dups = allow_dups ,
287304 code = code ,
305+ secondary_context = secondary_context ,
288306 )
289307
290308 def note_multiline (
@@ -295,11 +313,20 @@ def note_multiline(
295313 offset : int = 0 ,
296314 allow_dups : bool = False ,
297315 code : ErrorCode | None = None ,
316+ * ,
317+ secondary_context : Context | None = None ,
298318 ) -> None :
299319 """Report as many notes as lines in the message (unless disabled)."""
300320 for msg in messages .splitlines ():
301321 self .report (
302- msg , context , "note" , file = file , offset = offset , allow_dups = allow_dups , code = code
322+ msg ,
323+ context ,
324+ "note" ,
325+ file = file ,
326+ offset = offset ,
327+ allow_dups = allow_dups ,
328+ code = code ,
329+ secondary_context = secondary_context ,
303330 )
304331
305332 #
@@ -1153,6 +1180,7 @@ def argument_incompatible_with_supertype(
11531180 arg_type_in_supertype : Type ,
11541181 supertype : str ,
11551182 context : Context ,
1183+ secondary_context : Context ,
11561184 ) -> None :
11571185 target = self .override_target (name , name_in_supertype , supertype )
11581186 arg_type_in_supertype_f = format_type_bare (arg_type_in_supertype )
@@ -1163,17 +1191,26 @@ def argument_incompatible_with_supertype(
11631191 ),
11641192 context ,
11651193 code = codes .OVERRIDE ,
1194+ secondary_context = secondary_context ,
1195+ )
1196+ self .note (
1197+ "This violates the Liskov substitution principle" ,
1198+ context ,
1199+ code = codes .OVERRIDE ,
1200+ secondary_context = secondary_context ,
11661201 )
1167- self .note ("This violates the Liskov substitution principle" , context , code = codes .OVERRIDE )
11681202 self .note (
11691203 "See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides" ,
11701204 context ,
11711205 code = codes .OVERRIDE ,
1206+ secondary_context = secondary_context ,
11721207 )
11731208
11741209 if name == "__eq__" and type_name :
11751210 multiline_msg = self .comparison_method_example_msg (class_name = type_name )
1176- self .note_multiline (multiline_msg , context , code = codes .OVERRIDE )
1211+ self .note_multiline (
1212+ multiline_msg , context , code = codes .OVERRIDE , secondary_context = secondary_context
1213+ )
11771214
11781215 def comparison_method_example_msg (self , class_name : str ) -> str :
11791216 return dedent (
0 commit comments