Skip to content

[1.20 regression] when same object is narrowed multiple times #21181

Closed

Description

@ktbarrett

Bug Report

There was a regression in narrowing with isinstance checks from 1.19 to 1.20. This seems to only happen when the same object is narrowed multiple times. For example, this works correct if removing the outer is not None guard and the | None from the argument.

To Reproduce

mypy playground link

from __future__ import annotations


def _score_test(
    exc: BaseException | None, expected_excs: set[type[BaseException]]
) -> None:
    if exc is not None:
        reveal_type(exc)
        reveal_type(expected_excs)  # Revealed type is "BaseException"
        reveal_type(tuple(expected_excs))  # Revealed type is "tuple[type[BaseException], ...]"
        if isinstance(exc, tuple(expected_excs)):
            reveal_type(exc)  # Revealed type is "object"
            return logexc(exc)  # Argument 1 to "logexc" has incompatible type "object"; expected "BaseException | None"

def logexc(
    exc: BaseException | None
) -> None:
    ...

Activity

  1. changed the title [-]Regression with isinstance narrowing 1.19.1->1.20.0[/-] [+][1.20 regression] when same object is narrowed multiple times[/+] on Apr 7, 2026
  2. hauntsaninja commented on Apr 13, 2026

    @hauntsaninja
    Collaborator

    This bisects to #20675

    While you can patch by making changes in the spot that that code touched, I wonder if the more correct fix is in conditional_types, or maybe even the binder... we shouldn't be widening the type at all

  3. hauntsaninja commented on Apr 13, 2026

    @hauntsaninja
    Collaborator

    Okay yeah....

    So if you look at the following code

    from __future__ import annotations
    from typing import Iterable
    from typing_extensions import TypeAlias
    import types
    
    # Regression test for https://github.com/python/mypy/issues/21181
    # We don't have the same type context as with the real stubs, so sort of fake it
    _ClassInfoLike: TypeAlias = "type | tuple[_ClassInfoLike, ...]"
    
    class A: ...
    
    def fake_type_context(ts: list[type[A]]) -> _ClassInfoLike:
        return tuple(ts)  # E: Too many arguments for "tuple"
    
    def f1(x: A | None, ts: list[type[A]]) -> None:
        if x is not None:
            reveal_type(x)  # N: Revealed type is "__main__.A"
            if isinstance(x, fake_type_context(ts)):
                reveal_type(x)  # N: Revealed type is "__main__.A"
    
    def f2(x: A | None) -> None:
        if x is not None:
            reveal_type(x)  # N: Revealed type is "__main__.A"
            if isinstance(x, object):
                reveal_type(x)  # N: Revealed type is "__main__.A"
    
    def f3(x: A | None, t: type) -> None:
        if x is not None:
            reveal_type(x)  # N: Revealed type is "__main__.A"
            if isinstance(x, t):
                reveal_type(x)  # N: Revealed type is "__main__.A"
    

    f1 is like your case, and it regresses when we go from 1.19 to 1.20
    But f3 I claim is the underlying issue and we incorrectly widen the type in both 1.19 and 1.20

  4. added a commit that references this issue on Apr 16, 2026
    de7574c
  5. added a commit that references this issue on Apr 21, 2026
    93bc02b
  6. added a commit that references this issue on Apr 21, 2026
    92b74f2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongtopic-type-narrowingConditional type narrowing / binder

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions