Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitab4fd57

Browse files
tyrallaesarp
authored andcommitted
Improve the handling of "iteration dependent" errors and notes in finally clauses. (#19270)
Fixes#19269This PR refactors the logic implemented in#19118 (which only targetedrepeatedly checked loops) and applies it to repeatedly checked finallyclauses.I moved nearly all relevant code to the class `LoopErrorWatcher`, whichnow has the more general name `IterationErrorWatcher`, to avoid codeduplication. However, one duplication is left, which concerns errorreporting. It would be nice and easy to move this functionality to`IterationErrorWatcher`, too, but this would result in import cycles,and I am unsure if working with `TYPE_CHECKING` and postponed importingis acceptable in such cases (both for Mypy and Mypyc).After the refactoring, it should not be much effort to apply the logicto other cases where code sections are analysed iteratively. However,the only thing that comes to my mind is the repeated checking offunctions with arguments that contain constrained type variables. I willcheck it. If anyone finds a similar case and the solution is as simpleas expected, we could add the fix to this PR, of course.
1 parent09ba1f6 commitab4fd57

File tree

5 files changed

+148
-58
lines changed

5 files changed

+148
-58
lines changed

‎mypy/checker.py‎

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,14 @@
2525
frommypy.constraintsimportSUPERTYPE_OF
2626
frommypy.erasetypeimporterase_type,erase_typevars,remove_instance_last_known_values
2727
frommypy.errorcodesimportTYPE_VAR,UNUSED_AWAITABLE,UNUSED_COROUTINE,ErrorCode
28-
frommypy.errorsimportErrorInfo,Errors,ErrorWatcher,LoopErrorWatcher,report_internal_error
28+
frommypy.errorsimport (
29+
ErrorInfo,
30+
Errors,
31+
ErrorWatcher,
32+
IterationDependentErrors,
33+
IterationErrorWatcher,
34+
report_internal_error,
35+
)
2936
frommypy.expandtypeimportexpand_type
3037
frommypy.literalsimportKey,extract_var_from_literal_hash,literal,literal_hash
3138
frommypy.maptypeimportmap_instance_to_supertype
@@ -598,26 +605,15 @@ def accept_loop(
598605
# on without bound otherwise)
599606
widened_old=len(self.widened_vars)
600607

601-
# one set of `unreachable`, `redundant-expr`, and `redundant-casts` errors
602-
# per iteration step:
603-
uselessness_errors= []
604-
# one set of unreachable line numbers per iteration step:
605-
unreachable_lines= []
606-
# one set of revealed types per line where `reveal_type` is used (each
607-
# created set can grow during the iteration):
608-
revealed_types=defaultdict(set)
608+
iter_errors=IterationDependentErrors()
609609
iter=1
610610
whileTrue:
611611
withself.binder.frame_context(can_skip=True,break_frame=2,continue_frame=1):
612612
ifon_enter_bodyisnotNone:
613613
on_enter_body()
614614

615-
withLoopErrorWatcher(self.msg.errors)aswatcher:
615+
withIterationErrorWatcher(self.msg.errors,iter_errors)aswatcher:
616616
self.accept(body)
617-
uselessness_errors.append(watcher.uselessness_errors)
618-
unreachable_lines.append(watcher.unreachable_lines)
619-
forkey,valuesinwatcher.revealed_types.items():
620-
revealed_types[key].update(values)
621617

622618
partials_new=sum(len(pts.map)forptsinself.partial_types)
623619
widened_new=len(self.widened_vars)
@@ -639,29 +635,10 @@ def accept_loop(
639635
ifiter==20:
640636
raiseRuntimeError("Too many iterations when checking a loop")
641637

642-
# Report only those `unreachable`, `redundant-expr`, and `redundant-casts`
643-
# errors that could not be ruled out in any iteration step:
644-
persistent_uselessness_errors=set()
645-
forcandidateinset(itertools.chain(*uselessness_errors)):
646-
ifall(
647-
(candidateinerrors)or (candidate[2]inlines)
648-
forerrors,linesinzip(uselessness_errors,unreachable_lines)
649-
):
650-
persistent_uselessness_errors.add(candidate)
651-
forerror_infoinpersistent_uselessness_errors:
652-
context=Context(line=error_info[2],column=error_info[3])
653-
context.end_line=error_info[4]
654-
context.end_column=error_info[5]
655-
self.msg.fail(error_info[1],context,code=error_info[0])
656-
657-
# Report all types revealed in at least one iteration step:
658-
fornote_info,typesinrevealed_types.items():
659-
sorted_=sorted(types,key=lambdatyp:typ.lower())
660-
revealed=sorted_[0]iflen(types)==1elsef"Union[{', '.join(sorted_)}]"
661-
context=Context(line=note_info[1],column=note_info[2])
662-
context.end_line=note_info[3]
663-
context.end_column=note_info[4]
664-
self.note(f'Revealed type is "{revealed}"',context)
638+
forerror_infoinwatcher.yield_error_infos():
639+
self.msg.fail(*error_info[:2],code=error_info[2])
640+
fornote_infoinwatcher.yield_note_infos(self.options):
641+
self.note(*note_info)
665642

666643
# If exit_condition is set, assume it must be False on exit from the loop:
667644
ifexit_condition:
@@ -4948,6 +4925,9 @@ def type_check_raise(self, e: Expression, s: RaiseStmt, optional: bool = False)
49484925

49494926
defvisit_try_stmt(self,s:TryStmt)->None:
49504927
"""Type check a try statement."""
4928+
4929+
iter_errors=None
4930+
49514931
# Our enclosing frame will get the result if the try/except falls through.
49524932
# This one gets all possible states after the try block exited abnormally
49534933
# (by exception, return, break, etc.)
@@ -4962,7 +4942,9 @@ def visit_try_stmt(self, s: TryStmt) -> None:
49624942
self.visit_try_without_finally(s,try_frame=bool(s.finally_body))
49634943
ifs.finally_body:
49644944
# First we check finally_body is type safe on all abnormal exit paths
4965-
self.accept(s.finally_body)
4945+
iter_errors=IterationDependentErrors()
4946+
withIterationErrorWatcher(self.msg.errors,iter_errors)aswatcher:
4947+
self.accept(s.finally_body)
49664948

49674949
ifs.finally_body:
49684950
# Then we try again for the more restricted set of options
@@ -4976,8 +4958,15 @@ def visit_try_stmt(self, s: TryStmt) -> None:
49764958
# type checks in both contexts, but only the resulting types
49774959
# from the latter context affect the type state in the code
49784960
# that follows the try statement.)
4961+
assertiter_errorsisnotNone
49794962
ifnotself.binder.is_unreachable():
4980-
self.accept(s.finally_body)
4963+
withIterationErrorWatcher(self.msg.errors,iter_errors)aswatcher:
4964+
self.accept(s.finally_body)
4965+
4966+
forerror_infoinwatcher.yield_error_infos():
4967+
self.msg.fail(*error_info[:2],code=error_info[2])
4968+
fornote_infoinwatcher.yield_note_infos(self.options):
4969+
self.msg.note(*note_info)
49814970

49824971
defvisit_try_without_finally(self,s:TryStmt,try_frame:bool)->None:
49834972
"""Type check a try statement, ignoring the finally block.

‎mypy/errors.py‎

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
importsys
55
importtraceback
66
fromcollectionsimportdefaultdict
7-
fromcollections.abcimportIterable
7+
fromcollections.abcimportIterable,Iterator
8+
fromitertoolsimportchain
89
fromtypingimportCallable,Final,NoReturn,Optional,TextIO,TypeVar
910
fromtyping_extensionsimportLiteral,Self,TypeAliasas_TypeAlias
1011

1112
frommypyimporterrorcodesascodes
1213
frommypy.error_formatterimportErrorFormatter
1314
frommypy.errorcodesimportIMPORT,IMPORT_NOT_FOUND,IMPORT_UNTYPED,ErrorCode,mypy_error_codes
15+
frommypy.nodesimportContext
1416
frommypy.optionsimportOptions
1517
frommypy.scopeimportScope
1618
frommypy.utilimportDEFAULT_SOURCE_OFFSET,is_typeshed_file
@@ -219,23 +221,43 @@ def filtered_errors(self) -> list[ErrorInfo]:
219221
returnself._filtered
220222

221223

222-
classLoopErrorWatcher(ErrorWatcher):
223-
"""Error watcher that filters and separately collects `unreachable` errors,
224-
`redundant-expr` and `redundant-casts` errors, and revealed types when analysing
225-
loops iteratively to help avoid making too-hasty reports."""
224+
classIterationDependentErrors:
225+
"""An `IterationDependentErrors` instance serves to collect the `unreachable`,
226+
`redundant-expr`, and `redundant-casts` errors, as well as the revealed types,
227+
handled by the individual `IterationErrorWatcher` instances sequentially applied to
228+
the same code section."""
226229

227-
# Meaning of the tuple items: ErrorCode, message, line, column, end_line, end_column:
228-
uselessness_errors:set[tuple[ErrorCode,str,int,int,int,int]]
230+
# One set of `unreachable`, `redundant-expr`, and `redundant-casts` errors per
231+
# iteration step. Meaning of the tuple items: ErrorCode, message, line, column,
232+
# end_line, end_column.
233+
uselessness_errors:list[set[tuple[ErrorCode,str,int,int,int,int]]]
229234

230-
# Meaning of the tuple items: function_or_member, line, column, end_line, end_column:
235+
# One set of unreachable line numbers per iteration step. Not only the lines where
236+
# the error report occurs but really all unreachable lines.
237+
unreachable_lines:list[set[int]]
238+
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:
231242
revealed_types:dict[tuple[str|None,int,int,int,int],set[str]]
232243

233-
# Not only the lines where the error report occurs but really all unreachable lines:
234-
unreachable_lines:set[int]
244+
def__init__(self)->None:
245+
self.uselessness_errors= []
246+
self.unreachable_lines= []
247+
self.revealed_types=defaultdict(set)
248+
249+
250+
classIterationErrorWatcher(ErrorWatcher):
251+
"""Error watcher that filters and separately collects `unreachable` errors,
252+
`redundant-expr` and `redundant-casts` errors, and revealed types when analysing
253+
code sections iteratively to help avoid making too-hasty reports."""
254+
255+
iteration_dependent_errors:IterationDependentErrors
235256

236257
def__init__(
237258
self,
238259
errors:Errors,
260+
iteration_dependent_errors:IterationDependentErrors,
239261
*,
240262
filter_errors:bool|Callable[[str,ErrorInfo],bool]=False,
241263
save_filtered_errors:bool=False,
@@ -247,31 +269,71 @@ def __init__(
247269
save_filtered_errors=save_filtered_errors,
248270
filter_deprecated=filter_deprecated,
249271
)
250-
self.uselessness_errors=set()
251-
self.unreachable_lines=set()
252-
self.revealed_types=defaultdict(set)
272+
self.iteration_dependent_errors=iteration_dependent_errors
273+
iteration_dependent_errors.uselessness_errors.append(set())
274+
iteration_dependent_errors.unreachable_lines.append(set())
253275

254276
defon_error(self,file:str,info:ErrorInfo)->bool:
277+
"""Filter out the "iteration-dependent" errors and notes and store their
278+
information to handle them after iteration is completed."""
279+
280+
iter_errors=self.iteration_dependent_errors
255281

256282
ifinfo.codein (codes.UNREACHABLE,codes.REDUNDANT_EXPR,codes.REDUNDANT_CAST):
257-
self.uselessness_errors.add(
283+
iter_errors.uselessness_errors[-1].add(
258284
(info.code,info.message,info.line,info.column,info.end_line,info.end_column)
259285
)
260286
ifinfo.code==codes.UNREACHABLE:
261-
self.unreachable_lines.update(range(info.line,info.end_line+1))
287+
iter_errors.unreachable_lines[-1].update(range(info.line,info.end_line+1))
262288
returnTrue
263289

264290
ifinfo.code==codes.MISCandinfo.message.startswith("Revealed type is "):
265291
key=info.function_or_member,info.line,info.column,info.end_line,info.end_column
266292
types=info.message.split('"')[1]
267293
iftypes.startswith("Union["):
268-
self.revealed_types[key].update(types[6:-1].split(", "))
294+
iter_errors.revealed_types[key].update(types[6:-1].split(", "))
269295
else:
270-
self.revealed_types[key].add(types)
296+
iter_errors.revealed_types[key].add(types)
271297
returnTrue
272298

273299
returnsuper().on_error(file,info)
274300

301+
defyield_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+
forcandidateinset(chain(*iter_errors.uselessness_errors)):
308+
ifall(
309+
(candidateinerrors)or (candidate[2]inlines)
310+
forerrors,linesinzip(
311+
iter_errors.uselessness_errors,iter_errors.unreachable_lines
312+
)
313+
):
314+
persistent_uselessness_errors.add(candidate)
315+
forerror_infoinpersistent_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+
yielderror_info[1],context,error_info[0]
320+
321+
defyield_note_infos(self,options:Options)->Iterator[tuple[str,Context]]:
322+
"""Yield all types revealed in at least one iteration step."""
323+
324+
fornote_info,typesinself.iteration_dependent_errors.revealed_types.items():
325+
sorted_=sorted(types,key=lambdatyp:typ.lower())
326+
iflen(types)==1:
327+
revealed=sorted_[0]
328+
elifoptions.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+
yieldf'Revealed type is "{revealed}"',context
336+
275337

276338
classErrors:
277339
"""Container for compile errors.

‎test-data/unit/check-narrowing.test‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,25 @@ while x is not None and b():
24462446
x = f()
24472447
[builtins fixtures/primitives.pyi]
24482448

2449+
[case testAvoidFalseUnreachableInFinally]
2450+
# flags: --allow-redefinition-new --local-partial-types --warn-unreachable
2451+
def f() -> None:
2452+
try:
2453+
x = 1
2454+
if int():
2455+
x = ""
2456+
return
2457+
if int():
2458+
x = None
2459+
return
2460+
finally:
2461+
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str, None]"
2462+
if isinstance(x, str):
2463+
reveal_type(x) # N: Revealed type is "builtins.str"
2464+
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str, None]"
2465+
2466+
[builtins fixtures/isinstancelist.pyi]
2467+
24492468
[case testNarrowingTypeVarMultiple]
24502469
from typing import TypeVar
24512470

‎test-data/unit/check-redefine2.test‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -791,8 +791,7 @@ def f3() -> None:
791791
x = ""
792792
return
793793
finally:
794-
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]" \
795-
# N: Revealed type is "builtins.int"
794+
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
796795
reveal_type(x) # N: Revealed type is "builtins.int"
797796

798797
def f4() -> None:

‎test-data/unit/check-union-error-syntax.test‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,24 @@ from typing import Literal, Union
5555
x : Union[Literal[1], None]
5656
x = 3 # E: Incompatible types in assignment (expression has type "Literal[3]", variable has type "Optional[Literal[1]]")
5757
[builtins fixtures/tuple.pyi]
58+
59+
[case testUnionSyntaxRecombined]
60+
# flags: --python-version 3.10 --force-union-syntax --allow-redefinition-new --local-partial-types
61+
# The following revealed type is recombined because the finally body is visited twice.
62+
try:
63+
x = 1
64+
x = ""
65+
finally:
66+
reveal_type(x) # N: Revealed type is "Union[builtins.int, builtins.str]"
67+
[builtins fixtures/isinstancelist.pyi]
68+
69+
[case testOrSyntaxRecombined]
70+
# flags: --python-version 3.10 --no-force-union-syntax --allow-redefinition-new --local-partial-types
71+
# The following revealed type is recombined because the finally body is visited twice.
72+
# ToDo: Improve this recombination logic, especially (but not only) for the "or syntax".
73+
try:
74+
x = 1
75+
x = ""
76+
finally:
77+
reveal_type(x) # N: Revealed type is "builtins.int | builtins.str | builtins.str"
78+
[builtins fixtures/isinstancelist.pyi]

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp