Skip to content

Fix narrowing in comprehensions when the intersection is impossible#21669

Open
apoorvdarshan wants to merge 1 commit into
python:masterfrom
apoorvdarshan:fix-21635-comprehension-impossible-narrowing
Open

Fix narrowing in comprehensions when the intersection is impossible#21669
apoorvdarshan wants to merge 1 commit into
python:masterfrom
apoorvdarshan:fix-21635-comprehension-impossible-narrowing

Conversation

@apoorvdarshan

Copy link
Copy Markdown

Fixes #21635

Problem

When a comprehension condition narrows the index variable to an impossible type — e.g. issubclass(cls, M) where mypy determines a subclass of both classes cannot exist (the dataclass case from the issue, classes with incompatible method signatures, or @final classes) — the narrowing was silently discarded:

alist: list[type[A]] = [B, C]
mlist: list[type[M]] = [cls for cls in alist if issubclass(cls, M)]
# error: List comprehension has incompatible type List[type[A]]; expected List[type[M]]

Root cause

check_for_comp() pushes the condition's true_map via push_type_map(). For an impossible narrowing the map contains an UninhabitedType, so push_type_map() takes the is_unreachable_map() branch and only calls binder.unreachable()no narrowing is recorded. That's fine for statements, where the checker skips unreachable blocks, but the comprehension's left expression is still type checked, so it was checked with the unnarrowed type (type[A]), producing the false positive.

(The equivalent for/if statement version "works" only because the if body is skipped as unreachable; with --warn-unreachable it reports the impossible subclass instead.)

Fix

In check_for_comp(), when the condition's true_map is an unreachable map, apply the impossible narrowing to the binder instead of marking the frame unreachable. The left expression then checks against Never — matching the narrowing an equivalent if statement would produce — and the example above typechecks (the item type is Never, which is compatible with any expected item type).

This also covers generator expressions and dict comprehensions (all go through check_for_comp()), and mirrors what analyze_cond_branch() already does for conditional expressions (it types the unreachable branch as UninhabitedType).

Verification

  • New test cases in check-isinstance.test (testComprehensionIsInstanceImpossibleIntersection, testComprehensionIsSubclassImpossibleIntersectionFinal) — both fail on master and pass with this change.
  • Full testcheck suite: 8189 passed, 33 skipped, 7 xfailed.
  • Self check and ruff check/ruff format clean on the changed file.

LLM disclosure

Per the CONTRIBUTING note on LLM-assisted contributions: this fix was developed with the assistance of an AI tool (Claude Code). I've reviewed the change and the investigation behind it, take full responsibility for it, and will respond to review feedback personally.

When a comprehension condition narrows the index variable to an
impossible type (e.g. isinstance/issubclass where mypy determines a
subclass of both classes cannot exist, or the class is @Final),
check_for_comp() pushed an unreachable type map, which just marked the
binder frame unreachable without recording any narrowing. Unlike
statements, the comprehension's left expression is still type checked,
so it was checked with the *unnarrowed* type, producing false positives
like:

    error: List comprehension has incompatible type List[type[A]];
    expected List[type[M]]

Apply the impossible narrowing to the binder instead, so the left
expression checks against Never, matching the narrowing an equivalent
if statement would produce.

Fixes python#21635
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Diff from mypy_primer, showing the effect of this PR on open source code:

strawberry (https://github.com/strawberry-graphql/strawberry)
+ strawberry/relay/fields.py:189: error: Need type annotation for "nodes"  [var-annotated]
+ strawberry/relay/fields.py:190: error: Unused "type: ignore" comment  [unused-ignore]

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.

issubclass() fails to narrow dataclasses in comprehensions

1 participant