Skip to content

Conversation

@tyralla
Copy link
Collaborator

@tyralla tyralla commented Aug 10, 2025

Fixes #19328

The logic is very similar to what we did to report different revealed types that were discovered in multiple iteration steps in one line. I think this fix is the last one needed before I can implement #19256.

@github-actions

This comment has been minimized.

@tyralla
Copy link
Collaborator Author

tyralla commented Aug 10, 2025

The primer results are a little mysterious. I could simplify to:

for y in [1.0]:
    if y is not None or y != "None":  # E: Non-overlapping equality check (left operand type: "float", right operand type: "Literal['None']")
        ...

This obvious error is only reported with my change, but I do not know why current master misses it and therefore have no idea what in this PR contributes to detecting it.

For whatever reason this happens, I will add a corresponding test case.

@github-actions

This comment has been minimized.

@tyralla
Copy link
Collaborator Author

tyralla commented Aug 10, 2025

Ah, I got it. It is because optuna enables --strict-equality but not unreachable. y is not None is always true, so that the y != "None" mismatch is not reported.

I could invest some time so that

for y in [1.0]:
    if y is not None or y != "None":
        ...

and

y = 1.0
if y is not None or y != "None":
    ...

would be handled identically again (no --strict-equality warnings). However, this is a good example that failing to raise warnings in unreachable code can be confusing, and there is already a PR that tries to improve the situation (#18707). Hence, maybe no further action is required here.

@tyralla tyralla requested review from A5rocks and JukkaL August 10, 2025 21:18
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@tyralla
Copy link
Collaborator Author

tyralla commented Aug 11, 2025

I revisited the problem and added two lines of code that reset the old behaviour for the discussed problem and make some sense to me. The problem was that "nearer" error watchers could not filter non-overlap reports anymore. Now, the collection mechanism of IterationErrorWatcher only applies if no nearer error watcher has _filter activated. I think this is the place where the now again respected error watcher is created:

mypy/mypy/checkexpr.py

Lines 5982 to 5999 in 5a78607

def analyze_cond_branch(
self,
map: dict[Expression, Type] | None,
node: Expression,
context: Type | None,
allow_none_return: bool = False,
suppress_unreachable_errors: bool = True,
) -> Type:
with self.chk.binder.frame_context(can_skip=True, fall_through=0):
if map is None:
# We still need to type check node, in case we want to
# process it for isinstance checks later. Since the branch was
# determined to be unreachable, any errors should be suppressed.
with self.msg.filter_errors(filter_errors=suppress_unreachable_errors):
self.accept(node, type_context=context, allow_none_return=allow_none_return)
return UninhabitedType()
self.chk.push_type_map(map)
return self.accept(node, type_context=context, allow_none_return=allow_none_return)

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@tyralla
Copy link
Collaborator Author

tyralla commented Oct 12, 2025

@ilevkivskyi: I don't want to overstretch your willingness to help, but could you also take a look at this one? This PR aligns closely with what we began in #19324 and would enable me to implement reachability checking for functions with constrained type variables.

@tyralla tyralla requested a review from ilevkivskyi October 12, 2025 12:15
# respective types of the current iteration here so that we can report the error
# later if it is persistent over all iteration steps:
for watcher in self.errors.get_watchers():
if watcher._filter:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this logic into the watcher itself? This access to private followed by special-casing on watcher type is a bit painful to read, IMO this would be clearer as

for watcher in self.errors.get_watchers():
    if watcher.store_nonoverlapping_types(ctx, kind, left, right):
        return

Where store_nonoverlapping_types (not the best name) is a no-op in ErrorWatcher, overridden with this block in IterationErrorWatcher

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ough, you really need both break and return, sorry.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to excuse. I am obviously using things a little differently from how they were originally intended. Hence, I highly appreciate any thoughts on improving readability.

# One dictionary of non-overlapping types per iteration step. Meaning of the key
# tuple items: line, column, end_line, end_column, kind:
nonoverlapping_types: list[
dict[tuple[int, int, int | None, int | None, str], tuple[Type, Type]],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe NamedTuple or at least TypeAlias? I'm personally lost in brackets here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, TypeAlias doesn't help much. But using NamedTuple (here and in the similar cases above) would definitely increase readability. I will adjust it if there are no performance concerns.

Co-authored-by: Stanislav Terliakov <[email protected]>
@github-actions
Copy link
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

--strict-equality too strict in iteratively visited code

2 participants