|
15 | 15 | from mypy.nodes import Context |
16 | 16 | from mypy.options import Options |
17 | 17 | from mypy.scope import Scope |
| 18 | +from mypy.types import Type |
18 | 19 | from mypy.util import DEFAULT_SOURCE_OFFSET, is_typeshed_file |
19 | 20 | from mypy.version import __version__ as mypy_version |
20 | 21 |
|
@@ -166,18 +167,24 @@ class ErrorWatcher: |
166 | 167 | out by one of the ErrorWatcher instances. |
167 | 168 | """ |
168 | 169 |
|
| 170 | + # public attribute for the special treatment of `reveal_type` by |
| 171 | + # `MessageBuilder.reveal_type`: |
| 172 | + filter_revealed_type: bool |
| 173 | + |
169 | 174 | def __init__( |
170 | 175 | self, |
171 | 176 | errors: Errors, |
172 | 177 | *, |
173 | 178 | filter_errors: bool | Callable[[str, ErrorInfo], bool] = False, |
174 | 179 | save_filtered_errors: bool = False, |
175 | 180 | filter_deprecated: bool = False, |
| 181 | + filter_revealed_type: bool = False, |
176 | 182 | ) -> None: |
177 | 183 | self.errors = errors |
178 | 184 | self._has_new_errors = False |
179 | 185 | self._filter = filter_errors |
180 | 186 | self._filter_deprecated = filter_deprecated |
| 187 | + self.filter_revealed_type = filter_revealed_type |
181 | 188 | self._filtered: list[ErrorInfo] | None = [] if save_filtered_errors else None |
182 | 189 |
|
183 | 190 | def __enter__(self) -> Self: |
@@ -236,15 +243,41 @@ class IterationDependentErrors: |
236 | 243 | # the error report occurs but really all unreachable lines. |
237 | 244 | unreachable_lines: list[set[int]] |
238 | 245 |
|
239 | | - # One set of revealed types for each `reveal_type` statement. Each created set can |
240 | | - # grow during the iteration. Meaning of the tuple items: function_or_member, line, |
241 | | - # column, end_line, end_column: |
242 | | - revealed_types: dict[tuple[str | None, int, int, int, int], set[str]] |
| 246 | + # One list of revealed types for each `reveal_type` statement. Each created list |
| 247 | + # can grow during the iteration. Meaning of the tuple items: line, column, |
| 248 | + # end_line, end_column: |
| 249 | + revealed_types: dict[tuple[int, int, int | None, int | None], list[Type]] |
243 | 250 |
|
244 | 251 | def __init__(self) -> None: |
245 | 252 | self.uselessness_errors = [] |
246 | 253 | self.unreachable_lines = [] |
247 | | - self.revealed_types = defaultdict(set) |
| 254 | + self.revealed_types = defaultdict(list) |
| 255 | + |
| 256 | + def yield_uselessness_error_infos(self) -> Iterator[tuple[str, Context, ErrorCode]]: |
| 257 | + """Report only those `unreachable`, `redundant-expr`, and `redundant-casts` |
| 258 | + errors that could not be ruled out in any iteration step.""" |
| 259 | + |
| 260 | + persistent_uselessness_errors = set() |
| 261 | + for candidate in set(chain(*self.uselessness_errors)): |
| 262 | + if all( |
| 263 | + (candidate in errors) or (candidate[2] in lines) |
| 264 | + for errors, lines in zip(self.uselessness_errors, self.unreachable_lines) |
| 265 | + ): |
| 266 | + persistent_uselessness_errors.add(candidate) |
| 267 | + for error_info in persistent_uselessness_errors: |
| 268 | + context = Context(line=error_info[2], column=error_info[3]) |
| 269 | + context.end_line = error_info[4] |
| 270 | + context.end_column = error_info[5] |
| 271 | + yield error_info[1], context, error_info[0] |
| 272 | + |
| 273 | + def yield_revealed_type_infos(self) -> Iterator[tuple[list[Type], Context]]: |
| 274 | + """Yield all types revealed in at least one iteration step.""" |
| 275 | + |
| 276 | + for note_info, types in self.revealed_types.items(): |
| 277 | + context = Context(line=note_info[0], column=note_info[1]) |
| 278 | + context.end_line = note_info[2] |
| 279 | + context.end_column = note_info[3] |
| 280 | + yield types, context |
248 | 281 |
|
249 | 282 |
|
250 | 283 | class IterationErrorWatcher(ErrorWatcher): |
@@ -287,53 +320,8 @@ def on_error(self, file: str, info: ErrorInfo) -> bool: |
287 | 320 | iter_errors.unreachable_lines[-1].update(range(info.line, info.end_line + 1)) |
288 | 321 | return True |
289 | 322 |
|
290 | | - if info.code == codes.MISC and info.message.startswith("Revealed type is "): |
291 | | - key = info.function_or_member, info.line, info.column, info.end_line, info.end_column |
292 | | - types = info.message.split('"')[1] |
293 | | - if types.startswith("Union["): |
294 | | - iter_errors.revealed_types[key].update(types[6:-1].split(", ")) |
295 | | - else: |
296 | | - iter_errors.revealed_types[key].add(types) |
297 | | - return True |
298 | | - |
299 | 323 | return super().on_error(file, info) |
300 | 324 |
|
301 | | - def yield_error_infos(self) -> Iterator[tuple[str, Context, ErrorCode]]: |
302 | | - """Report only those `unreachable`, `redundant-expr`, and `redundant-casts` |
303 | | - errors that could not be ruled out in any iteration step.""" |
304 | | - |
305 | | - persistent_uselessness_errors = set() |
306 | | - iter_errors = self.iteration_dependent_errors |
307 | | - for candidate in set(chain(*iter_errors.uselessness_errors)): |
308 | | - if all( |
309 | | - (candidate in errors) or (candidate[2] in lines) |
310 | | - for errors, lines in zip( |
311 | | - iter_errors.uselessness_errors, iter_errors.unreachable_lines |
312 | | - ) |
313 | | - ): |
314 | | - persistent_uselessness_errors.add(candidate) |
315 | | - for error_info in persistent_uselessness_errors: |
316 | | - context = Context(line=error_info[2], column=error_info[3]) |
317 | | - context.end_line = error_info[4] |
318 | | - context.end_column = error_info[5] |
319 | | - yield error_info[1], context, error_info[0] |
320 | | - |
321 | | - def yield_note_infos(self, options: Options) -> Iterator[tuple[str, Context]]: |
322 | | - """Yield all types revealed in at least one iteration step.""" |
323 | | - |
324 | | - for note_info, types in self.iteration_dependent_errors.revealed_types.items(): |
325 | | - sorted_ = sorted(types, key=lambda typ: typ.lower()) |
326 | | - if len(types) == 1: |
327 | | - revealed = sorted_[0] |
328 | | - elif options.use_or_syntax(): |
329 | | - revealed = " | ".join(sorted_) |
330 | | - else: |
331 | | - revealed = f"Union[{', '.join(sorted_)}]" |
332 | | - context = Context(line=note_info[1], column=note_info[2]) |
333 | | - context.end_line = note_info[3] |
334 | | - context.end_column = note_info[4] |
335 | | - yield f'Revealed type is "{revealed}"', context |
336 | | - |
337 | 325 |
|
338 | 326 | class Errors: |
339 | 327 | """Container for compile errors. |
@@ -596,18 +584,19 @@ def _add_error_info(self, file: str, info: ErrorInfo) -> None: |
596 | 584 | if info.code in (IMPORT, IMPORT_UNTYPED, IMPORT_NOT_FOUND): |
597 | 585 | self.seen_import_error = True |
598 | 586 |
|
| 587 | + def get_watchers(self) -> Iterator[ErrorWatcher]: |
| 588 | + """Yield the `ErrorWatcher` stack from top to bottom.""" |
| 589 | + i = len(self._watchers) |
| 590 | + while i > 0: |
| 591 | + i -= 1 |
| 592 | + yield self._watchers[i] |
| 593 | + |
599 | 594 | def _filter_error(self, file: str, info: ErrorInfo) -> bool: |
600 | 595 | """ |
601 | 596 | process ErrorWatcher stack from top to bottom, |
602 | 597 | stopping early if error needs to be filtered out |
603 | 598 | """ |
604 | | - i = len(self._watchers) |
605 | | - while i > 0: |
606 | | - i -= 1 |
607 | | - w = self._watchers[i] |
608 | | - if w.on_error(file, info): |
609 | | - return True |
610 | | - return False |
| 599 | + return any(w.on_error(file, info) for w in self.get_watchers()) |
611 | 600 |
|
612 | 601 | def add_error_info(self, info: ErrorInfo) -> None: |
613 | 602 | file, lines = info.origin |
|
0 commit comments