Skip to content

[1.16 regression] narrowing on is_dataclass #19139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
henryiii opened this issue May 23, 2025 · 3 comments
Open

[1.16 regression] narrowing on is_dataclass #19139

henryiii opened this issue May 23, 2025 · 3 comments
Labels
bug mypy got something wrong topic-typeguard-typeis TypeGuard / TypeIs / PEP 647 / PEP 742

Comments

@henryiii
Copy link
Contributor

henryiii commented May 23, 2025

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.

@henryiii henryiii added the bug mypy got something wrong label May 23, 2025
henryiii added a commit to scikit-build/scikit-build-core that referenced this issue May 23, 2025
This types better on 1.16 (will need a few more adjustments when it
comes out, but this is closer). See
hauntsaninja/mypy_primer#174 and
python/mypy#19139.

Signed-off-by: Henry Schreiner <[email protected]>
@sterliakov
Copy link
Collaborator

sterliakov commented May 27, 2025

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
Copy link
Collaborator

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

@hauntsaninja hauntsaninja added the topic-typeguard-typeis TypeGuard / TypeIs / PEP 647 / PEP 742 label May 28, 2025
@sterliakov
Copy link
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]"    

@hauntsaninja hauntsaninja changed the title Regression in 1.16 on DataclassInstance? [1.16 regression] narrowing on is_dataclass May 28, 2025
JukkaL added a commit that referenced this issue May 28, 2025
)"

This reverts commit 43ea203.

The commit caused a regression (#19139). If we can't fix the
regression soon enough, reverting the original change temporarily will
at least unblock the mypy public release. The reverted PR can be
merged again once the regression is fixed.
JukkaL added a commit that referenced this issue May 29, 2025
)

This reverts commit 43ea203 (#17678).

The commit caused a regression (#19139). If we can't fix the regression
soon enough, reverting the original change temporarily will at least
unblock the mypy public release. The reverted PR can be merged again
once the regression is fixed.
JukkaL added a commit that referenced this issue May 29, 2025
)

This reverts commit 43ea203 (#17678).

The commit caused a regression (#19139). If we can't fix the regression
soon enough, reverting the original change temporarily will at least
unblock the mypy public release. The reverted PR can be merged again
once the regression is fixed.
cdce8p pushed a commit to cdce8p/mypy that referenced this issue May 31, 2025
…hon#19161)

This reverts commit 43ea203 (python#17678).

The commit caused a regression (python#19139). If we can't fix the regression
soon enough, reverting the original change temporarily will at least
unblock the mypy public release. The reverted PR can be merged again
once the regression is fixed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-typeguard-typeis TypeGuard / TypeIs / PEP 647 / PEP 742
Projects
None yet
Development

No branches or pull requests

3 participants