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

Commitd06d3d9

Browse files
tyrallapre-commit-ci[bot]sterliakovhauntsaninja
authored
Fix--strict-equality for iteratively visited code (#19635)
Fixes#19328Fixes#20294The logic is very similar to what we did to report different revealedtypes that were discovered in multiple iteration steps in one line. Ithink this fix is the last one needed before I can implement#19256.---------Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>Co-authored-by: Stanislav Terliakov <50529348+sterliakov@users.noreply.github.com>Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
1 parenteb144ce commitd06d3d9

File tree

3 files changed

+135
-5
lines changed

3 files changed

+135
-5
lines changed

‎mypy/errors.py‎

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,42 @@ def filtered_errors(self) -> list[ErrorInfo]:
229229
returnself._filtered
230230

231231

232+
classNonOverlapErrorInfo:
233+
line:int
234+
column:int
235+
end_line:int|None
236+
end_column:int|None
237+
kind:str
238+
239+
def__init__(
240+
self,*,line:int,column:int,end_line:int|None,end_column:int|None,kind:str
241+
)->None:
242+
self.line=line
243+
self.column=column
244+
self.end_line=end_line
245+
self.end_column=end_column
246+
self.kind=kind
247+
248+
def__eq__(self,other:object)->bool:
249+
ifisinstance(other,NonOverlapErrorInfo):
250+
return (
251+
self.line==other.line
252+
andself.column==other.column
253+
andself.end_line==other.end_line
254+
andself.end_column==other.end_column
255+
andself.kind==other.kind
256+
)
257+
returnFalse
258+
259+
def__hash__(self)->int:
260+
returnhash((self.line,self.column,self.end_line,self.end_column,self.kind))
261+
262+
232263
classIterationDependentErrors:
233264
"""An `IterationDependentErrors` instance serves to collect the `unreachable`,
234-
`redundant-expr`, and `redundant-casts` errors, as well as the revealed types,
235-
handled by the individual `IterationErrorWatcher` instances sequentially applied to
236-
the same code section."""
265+
`redundant-expr`, and `redundant-casts` errors, as well as the revealed types and
266+
non-overlapping types,handled by the individual `IterationErrorWatcher` instances
267+
sequentially applied tothe same code section."""
237268

238269
# One set of `unreachable`, `redundant-expr`, and `redundant-casts` errors per
239270
# iteration step. Meaning of the tuple items: ErrorCode, message, line, column,
@@ -249,9 +280,13 @@ class IterationDependentErrors:
249280
# end_line, end_column:
250281
revealed_types:dict[tuple[int,int,int|None,int|None],list[Type]]
251282

283+
# One dictionary of non-overlapping types per iteration step:
284+
nonoverlapping_types:list[dict[NonOverlapErrorInfo,tuple[Type,Type]]]
285+
252286
def__init__(self)->None:
253287
self.uselessness_errors= []
254288
self.unreachable_lines= []
289+
self.nonoverlapping_types= []
255290
self.revealed_types=defaultdict(list)
256291

257292
defyield_uselessness_error_infos(self)->Iterator[tuple[str,Context,ErrorCode]]:
@@ -271,6 +306,36 @@ def yield_uselessness_error_infos(self) -> Iterator[tuple[str, Context, ErrorCod
271306
context.end_column=error_info[5]
272307
yielderror_info[1],context,error_info[0]
273308

309+
defyield_nonoverlapping_types(
310+
self,
311+
)->Iterator[tuple[tuple[list[Type],list[Type]],str,Context]]:
312+
"""Report expressions where non-overlapping types were detected for all iterations
313+
were the expression was reachable."""
314+
315+
selected=set()
316+
forcandidateinset(chain.from_iterable(self.nonoverlapping_types)):
317+
ifall(
318+
(candidateinnonoverlap)or (candidate.lineinlines)
319+
fornonoverlap,linesinzip(self.nonoverlapping_types,self.unreachable_lines)
320+
):
321+
selected.add(candidate)
322+
323+
persistent_nonoverlaps:dict[NonOverlapErrorInfo,tuple[list[Type],list[Type]]]= (
324+
defaultdict(lambda: ([], []))
325+
)
326+
fornonoverlapsinself.nonoverlapping_types:
327+
forcandidate, (left,right)innonoverlaps.items():
328+
ifcandidateinselected:
329+
types=persistent_nonoverlaps[candidate]
330+
types[0].append(left)
331+
types[1].append(right)
332+
333+
forerror_info,typesinpersistent_nonoverlaps.items():
334+
context=Context(line=error_info.line,column=error_info.column)
335+
context.end_line=error_info.end_line
336+
context.end_column=error_info.end_column
337+
yield (types[0],types[1]),error_info.kind,context
338+
274339
defyield_revealed_type_infos(self)->Iterator[tuple[list[Type],Context]]:
275340
"""Yield all types revealed in at least one iteration step."""
276341

@@ -283,8 +348,9 @@ def yield_revealed_type_infos(self) -> Iterator[tuple[list[Type], Context]]:
283348

284349
classIterationErrorWatcher(ErrorWatcher):
285350
"""Error watcher that filters and separately collects `unreachable` errors,
286-
`redundant-expr` and `redundant-casts` errors, and revealed types when analysing
287-
code sections iteratively to help avoid making too-hasty reports."""
351+
`redundant-expr` and `redundant-casts` errors, and revealed types and
352+
non-overlapping types when analysing code sections iteratively to help avoid
353+
making too-hasty reports."""
288354

289355
iteration_dependent_errors:IterationDependentErrors
290356

@@ -305,6 +371,7 @@ def __init__(
305371
)
306372
self.iteration_dependent_errors=iteration_dependent_errors
307373
iteration_dependent_errors.uselessness_errors.append(set())
374+
iteration_dependent_errors.nonoverlapping_types.append({})
308375
iteration_dependent_errors.unreachable_lines.append(set())
309376

310377
defon_error(self,file:str,info:ErrorInfo)->bool:

‎mypy/messages.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
ErrorWatcher,
3030
IterationDependentErrors,
3131
IterationErrorWatcher,
32+
NonOverlapErrorInfo,
3233
)
3334
frommypy.nodesimport (
3435
ARG_NAMED,
@@ -1624,6 +1625,26 @@ def incompatible_typevar_value(
16241625
)
16251626

16261627
defdangerous_comparison(self,left:Type,right:Type,kind:str,ctx:Context)->None:
1628+
# In loops (and similar cases), the same expression might be analysed multiple
1629+
# times and thereby confronted with different types. We only want to raise a
1630+
# `comparison-overlap` error if it occurs in all cases and therefore collect the
1631+
# respective types of the current iteration here so that we can report the error
1632+
# later if it is persistent over all iteration steps:
1633+
forwatcherinself.errors.get_watchers():
1634+
ifwatcher._filter:
1635+
break
1636+
ifisinstance(watcher,IterationErrorWatcher):
1637+
watcher.iteration_dependent_errors.nonoverlapping_types[-1][
1638+
NonOverlapErrorInfo(
1639+
line=ctx.line,
1640+
column=ctx.column,
1641+
end_line=ctx.end_line,
1642+
end_column=ctx.end_column,
1643+
kind=kind,
1644+
)
1645+
]= (left,right)
1646+
return
1647+
16271648
left_str="element"ifkind=="container"else"left operand"
16281649
right_str="container item"ifkind=="container"else"right operand"
16291650
message="Non-overlapping {} check ({} type: {}, {} type: {})"
@@ -2510,6 +2531,13 @@ def match_statement_inexhaustive_match(self, typ: Type, context: Context) -> Non
25102531
defiteration_dependent_errors(self,iter_errors:IterationDependentErrors)->None:
25112532
forerror_infoiniter_errors.yield_uselessness_error_infos():
25122533
self.fail(*error_info[:2],code=error_info[2])
2534+
fornonoverlaps,kind,contextiniter_errors.yield_nonoverlapping_types():
2535+
self.dangerous_comparison(
2536+
mypy.typeops.make_simplified_union(nonoverlaps[0]),
2537+
mypy.typeops.make_simplified_union(nonoverlaps[1]),
2538+
kind,
2539+
context,
2540+
)
25132541
fortypes,contextiniter_errors.yield_revealed_type_infos():
25142542
self.reveal_type(mypy.typeops.make_simplified_union(types),context)
25152543

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

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

2449+
[case testAvoidFalseNonOverlappingEqualityCheckInLoop1]
2450+
# flags: --allow-redefinition-new --local-partial-types --strict-equality
2451+
2452+
x = 1
2453+
while True:
2454+
if x == str():
2455+
break
2456+
x = str()
2457+
if x == int(): # E: Non-overlapping equality check (left operand type: "str", right operand type: "int")
2458+
break
2459+
[builtins fixtures/primitives.pyi]
2460+
2461+
[case testAvoidFalseNonOverlappingEqualityCheckInLoop2]
2462+
# flags: --allow-redefinition-new --local-partial-types --strict-equality
2463+
2464+
class A: ...
2465+
class B: ...
2466+
class C: ...
2467+
2468+
x = A()
2469+
while True:
2470+
if x == C(): # E: Non-overlapping equality check (left operand type: "A | B", right operand type: "C")
2471+
break
2472+
x = B()
2473+
[builtins fixtures/primitives.pyi]
2474+
2475+
[case testAvoidFalseNonOverlappingEqualityCheckInLoop3]
2476+
# flags: --strict-equality
2477+
2478+
for y in [1.0]:
2479+
if y is not None or y != "None":
2480+
...
2481+
2482+
[builtins fixtures/primitives.pyi]
2483+
24492484
[case testNarrowPromotionsInsideUnions1]
24502485

24512486
from typing import Union

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp