Skip to content

issubclass() assertions not narrowing types #10680

Open

Description

@jwodder

Consider the following (contrived) code:

from typing import Type

def untyped_function():
    return int

def foo() -> Type[int]:
    x = untyped_function()
    assert issubclass(x, int)
    return x

Running mypy on this with the --warn-return-any option produces the error "Returning Any from function declared to return "Type[int]"", even though the assert issubclass should convince mypy that x is of type Type[int] and not an Any.

Your Environment

  • Mypy version used: 0.902
  • Mypy command-line flags: --warn-return-any
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: Python 3.9.5
  • Operating system and version: macOS 11.3.1

Activity

  1. kveretennicov commented on Jun 9, 2022

    @kveretennicov

    We hit this issue in production code.

    mypy Playground gist with repro and a workaround: https://mypy-play.net/?mypy=latest&python=3.10&gist=35deff0d7118af72d07a113a1f05c82b (mypy 0.961, Python 3.10)

  2. GertyP commented on Jul 3, 2023

    @GertyP

    Just in case it's a useful test-case, I have a slight variation on this error, where it's clear from looking that we can deduce the narrowed return type in all conditional paths but where mypy complains that it thinks we could potentially be dealing with an 'Any' type -

    def f(something: bool) -> dict:
        my_dict = None
        if something:
            exec('d = {}', globals(), locals())
            my_dict = locals()['d']
            assert isinstance(my_dict, dict)
            # ... No good if we use this assert.
        else:
            my_dict = {}
        # assert isinstance(my_dict, dict)
        # ... but all good if we use this assert
        return my_dict
  3. dave-v commented on May 20, 2025

    @dave-v

    I think I'm seeing this error as well. Abbreviated example:

    _registry: dict[str, type[Config] | Action]
    ...
    for key, config in _registry.items():
        if config is NOOP:  # NOOP is an Action object, and it's the only one that exists
            continue
        assert issubclass(config, Config)
        config.configure()  # <- error: Item "Action" of "type[Config] | Action" has no attribute "configure"  [union-attr]

    (PyCharm's type checker is happy with this.)

    If I add an additional assertion assert not isinstance(config, Action), then the error goes away, but I feel like that shouldn't be necessary: if config is indeed a subclass of Config, how can it also be an instance of Action? (Action inherits only from object.)

    Edit: I found that assert not isinstance(config, Action) as the sole assertion works as well, but my question still stands - why isn't the issubclass check also enough to convince mypy?

  4. added a commit that references this issue on Apr 4, 2026
    ec08f1f
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