Skip to content

Dataclasses fail to narrow with issubclass() in Python >= 3.13 #21635

Description

@ygale

Bug Report

With Python >= 3.13, given cls: type[A] and if issubclass(cls, M) where A and M are dataclasses, mypy is unable to narrow the type of cls to type[M]. Instead, the guarded block is not typechecked at all.

When iterating over a list[type[A]] in a comprehension with if issubclass(cls, M) where A and M are dataclasses, mypy is unable to narrow the type of cls to type[M]. Instead, the comprehension result expression is typechecked with cls at its original unnarrowed type type[A].

To Reproduce

from dataclasses import dataclass
from typing import reveal_type

@dataclass
class A:
  pass

@dataclass
class M:
  pass

@dataclass
class B(A):
  pass

@dataclass
class C(M, A):
  pass

cls: type[A] = C
if issubclass(cls, M):
  reveal_type(cls)
  n: int = 'foo'

Expected Behavior

The type of cls is revealed as type[M], followed by a type error for the assignment of a str to a variable of type int.

Actual Behavior

No type is revealed, silent success.

For comprehensions:

from dataclasses import dataclass
from typing import reveal_type

@dataclass
class A:
  pass

@dataclass
class M:
  pass

@dataclass
class B(A):
  pass

@dataclass
class C(M, A):
  pass

alist: list[type[A]] = [B, C]
mlist = [cls for cls in alist if issubclass(cls, M)]
reveal_type(mlist)

Expected Behavior

note: Revealed type is "list[type[<subclass of "A" and "M">]]"

And if we annotate mlist: list[type[M]] it still typechecks.

Actual Behavior

note: Revealed type is "list[type[A]]"

And if we annotate mlist: list[type[M]] we get:

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

Notes

This error DOES NOT occur if the classes are not dataclasses.

This error DOES NOT occur in a regular for loop:

mlist: list[type[M]] = []
cls: type[A]
for cls in alist:
  if issubclass(cls, M):
    mlist.append(cls)

This error DOES still occur if issubclass() is wrapped in a TypeIs guard function:

def is_m(t: type) -> TypeIs[type[M]]:
  return issubclass(t, M)

alist: list[type[A]] = [B, C]
mlist: list[type[M]] = [cls for cls in alist
  if is_m(cls)]

Your Environment

  • Mypy version used: mypy 2.1.0 (compiled: no)
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): nothing special
  • Python version used: 3.13.13

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions