Skip to content

[1.16 regression] narrowing on is_dataclass #19139

Closed
@henryiii

Description

@henryiii
Contributor

Bug Report

If you use isinstance(item, type), this used to be used in conjunction with dataclasses.is_dataclass to identify types and instances. It's the recommendation in the CPython documentation, in fact: https://docs.python.org/3/library/dataclasses.html#dataclasses.is_dataclass. However, the latests MyPy no longer narrows this correctly (from what I can tell, unless I'm doing something wrong), and still thinks I might have an instance, which then breaks later when creating an instance, etc.

Note that I can flip the order, in which case I just get builtins.type for both versions. This isn't enough to fix it for some of the more complex usages in scikit-build-core, though.

To Reproduce

import dataclasses

raw_target: object

if isinstance(raw_target, type) and dataclasses.is_dataclass(raw_target):
    reveal_type(raw_target)

Expected Behavior

This is from 1.15:

tmp.py:6: note: Revealed type is "Type[_typeshed.DataclassInstance]"

Actual Behavior

tmp.py:6: note: Revealed type is "Union[_typeshed.DataclassInstance, Type[_typeshed.DataclassInstance]]"

Your Environment

  • Mypy version used: uvx --from git+https://github.com/python/mypy mypy tmp.py was used, so latest commit, also latest release 1.15 version for comparison.
  • Python version used: 3.13

Noticed in hauntsaninja/mypy_primer#174.

Activity

sterliakov

sterliakov commented on May 27, 2025

@sterliakov
Collaborator
This is a regression, and a rather bad one. Note also that this is somehow dependent on checks order. MRE without stdlib dependency:
from typing import TypeIs

class A: ...

def is_a(o: object) -> TypeIs[type[A] | A]:
    return isinstance(o, int)

raw_target: object

if is_a(raw_target) and isinstance(raw_target, type):
    reveal_type(raw_target)  # N: Revealed type is "builtins.type"
if isinstance(raw_target, type) and is_a(raw_target):
    reveal_type(raw_target)  # N: Revealed type is "Union[type[__main__.A], __main__.A]"

https://mypy-play.net/?mypy=master&python=3.13&flags=strict&gist=6e2a57a00f5aa275ebbb2abbefa972e6

My MRE exhibits the same behaviour on 1.15.0, so it isn't the same. And this is correct - A and type can have a common LSP-compatible subclass.

This issue bisects to #17678; is_dataclass is an overloaded function:

# HACK: `obj: Never` typing matches if object argument is using `Any` type.
@overload
def is_dataclass(obj: Never) -> TypeIs[DataclassInstance | type[DataclassInstance]]: ...  # type: ignore[narrowed-type-not-subtype]  # pyright: ignore[reportGeneralTypeIssues]
@overload
def is_dataclass(obj: type) -> TypeIs[type[DataclassInstance]]: ...
@overload
def is_dataclass(obj: object) -> TypeIs[DataclassInstance | type[DataclassInstance]]: ...

And this looks like a bug - after first isinstance we should have raw_target already narrowed to type, so the second overload should match.

sterliakov

sterliakov commented on May 27, 2025

@sterliakov
Collaborator

@hauntsaninja could you please create a label for PEP 742 / TypeIs?

sterliakov

sterliakov commented on May 28, 2025

@sterliakov
Collaborator

I get it now. When find_isinstance_check_helper is applied, we check all arguments at once, which means wrong @overload is selected - mypy only stores narrowed types when all typemaps are known. cc @sobolevn as original author of #17678 - this sounds like a painful limitation, any overload resolution here won't take preceding narrowing statements into account.

Splitting the ladder works:

import dataclasses

raw_target: object

if isinstance(raw_target, type): 
    if dataclasses.is_dataclass(raw_target):
        reveal_type(raw_target)  # N: Revealed type is "type[_typeshed.DataclassInstance]"    
changed the title [-]Regression in 1.16 on DataclassInstance?[/-] [+][1.16 regression] narrowing on is_dataclass[/+] on May 28, 2025

3 remaining items

Loading
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-typeguard-typeisTypeGuard / TypeIs / PEP 647 / PEP 742

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      Participants

      @henryiii@hauntsaninja@sterliakov

      Issue actions

        [1.16 regression] narrowing on is_dataclass · Issue #19139 · python/mypy