@@ -230,9 +230,9 @@ def filtered_errors(self) -> list[ErrorInfo]:
230230
231231class IterationDependentErrors :
232232 """An `IterationDependentErrors` instance serves to collect the `unreachable`,
233- `redundant-expr`, and `redundant-casts` errors, as well as the revealed types,
234- handled by the individual `IterationErrorWatcher` instances sequentially applied to
235- the same code section."""
233+ `redundant-expr`, and `redundant-casts` errors, as well as the revealed types and
234+ non-overlapping types, handled by the individual `IterationErrorWatcher` instances
235+ sequentially applied to the same code section."""
236236
237237 # One set of `unreachable`, `redundant-expr`, and `redundant-casts` errors per
238238 # iteration step. Meaning of the tuple items: ErrorCode, message, line, column,
@@ -248,9 +248,18 @@ class IterationDependentErrors:
248248 # end_line, end_column:
249249 revealed_types : dict [tuple [int , int , int | None , int | None ], list [Type ]]
250250
251+ # One dictionary of non-overlapping types per iteration step. Meaning of the key
252+ # tuple items: line, column, end_line, end_column, kind:
253+ nonoverlapping_types : list [
254+ dict [
255+ tuple [int , int , int | None , int | None , str ], tuple [Type , Type ]
256+ ],
257+ ]
258+
251259 def __init__ (self ) -> None :
252260 self .uselessness_errors = []
253261 self .unreachable_lines = []
262+ self .nonoverlapping_types = []
254263 self .revealed_types = defaultdict (list )
255264
256265 def yield_uselessness_error_infos (self ) -> Iterator [tuple [str , Context , ErrorCode ]]:
@@ -270,6 +279,39 @@ def yield_uselessness_error_infos(self) -> Iterator[tuple[str, Context, ErrorCod
270279 context .end_column = error_info [5 ]
271280 yield error_info [1 ], context , error_info [0 ]
272281
282+
283+ def yield_nonoverlapping_types (self ) -> Iterator [
284+ tuple [tuple [list [Type ], list [Type ]], str , Context ]
285+ ]:
286+ """Report expressions were non-overlapping types were detected for all iterations
287+ were the expression was reachable."""
288+
289+ selected = set ()
290+ for candidate in set (chain (* self .nonoverlapping_types )):
291+ if all (
292+ (candidate in nonoverlap ) or (candidate [0 ] in lines )
293+ for nonoverlap , lines in zip (
294+ self .nonoverlapping_types , self .unreachable_lines
295+ )
296+ ):
297+ selected .add (candidate )
298+
299+ persistent_nonoverlaps : dict [
300+ tuple [int , int , int | None , int | None , str ], tuple [list [Type ], list [Type ]]
301+ ] = defaultdict (lambda : ([], []))
302+ for nonoverlaps in self .nonoverlapping_types :
303+ for candidate , (left , right ) in nonoverlaps .items ():
304+ if candidate in selected :
305+ types = persistent_nonoverlaps [candidate ]
306+ types [0 ].append (left )
307+ types [1 ].append (right )
308+
309+ for error_info , types in persistent_nonoverlaps .items ():
310+ context = Context (line = error_info [0 ], column = error_info [1 ])
311+ context .end_line = error_info [2 ]
312+ context .end_column = error_info [3 ]
313+ yield (types [0 ], types [1 ]), error_info [4 ], context
314+
273315 def yield_revealed_type_infos (self ) -> Iterator [tuple [list [Type ], Context ]]:
274316 """Yield all types revealed in at least one iteration step."""
275317
@@ -282,8 +324,9 @@ def yield_revealed_type_infos(self) -> Iterator[tuple[list[Type], Context]]:
282324
283325class IterationErrorWatcher (ErrorWatcher ):
284326 """Error watcher that filters and separately collects `unreachable` errors,
285- `redundant-expr` and `redundant-casts` errors, and revealed types when analysing
286- code sections iteratively to help avoid making too-hasty reports."""
327+ `redundant-expr` and `redundant-casts` errors, and revealed types and
328+ non-overlapping types when analysing code sections iteratively to help avoid
329+ making too-hasty reports."""
287330
288331 iteration_dependent_errors : IterationDependentErrors
289332
@@ -304,6 +347,7 @@ def __init__(
304347 )
305348 self .iteration_dependent_errors = iteration_dependent_errors
306349 iteration_dependent_errors .uselessness_errors .append (set ())
350+ iteration_dependent_errors .nonoverlapping_types .append ({})
307351 iteration_dependent_errors .unreachable_lines .append (set ())
308352
309353 def on_error (self , file : str , info : ErrorInfo ) -> bool :
0 commit comments