Skip to content

Improve the first overload of is_dataclass #9758

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

Merged
merged 3 commits into from
Feb 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion stdlib/dataclasses.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ else:

def fields(class_or_instance: DataclassInstance | type[DataclassInstance]) -> tuple[Field[Any], ...]: ...
@overload
def is_dataclass(obj: DataclassInstance | type[DataclassInstance]) -> Literal[True]: ...
def is_dataclass(obj: DataclassInstance) -> Literal[True]: ...
@overload
def is_dataclass(obj: type) -> TypeGuard[type[DataclassInstance]]: ...
@overload
Expand Down
33 changes: 21 additions & 12 deletions test_cases/stdlib/check_dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from __future__ import annotations

import dataclasses as dc
from typing import Any, Dict, Tuple, Type
from typing import TYPE_CHECKING, Any, Dict, Tuple, Type, Union
from typing_extensions import assert_type

if TYPE_CHECKING:
from _typeshed import DataclassInstance


@dc.dataclass
class Foo:
Expand All @@ -21,10 +24,9 @@ class Foo:
# dc.astuple(Foo)
# dc.replace(Foo)

if dc.is_dataclass(Foo):
# The inferred type doesn't change
# if it's already known to be a subtype of type[_DataclassInstance]
assert_type(Foo, Type[Foo])
# See #9723 for why we can't make this assertion
# if dc.is_dataclass(Foo):
# assert_type(Foo, Type[Foo])

f = Foo(attr="attr")

Expand All @@ -39,7 +41,7 @@ class Foo:
assert_type(f, Foo)


def test_other_isdataclass_overloads(x: type, y: object) -> None:
def check_other_isdataclass_overloads(x: type, y: object) -> None:
# TODO: pyright correctly emits an error on this, but mypy does not -- why?
# dc.fields(x)

Expand All @@ -55,17 +57,23 @@ def test_other_isdataclass_overloads(x: type, y: object) -> None:
dc.replace(y) # type: ignore

if dc.is_dataclass(x):
assert_type(x, Type["DataclassInstance"])
assert_type(dc.fields(x), Tuple[dc.Field[Any], ...])
# These should cause type checkers to emit errors
# due to the fact it's a dataclass class, not an instance
dc.asdict(x) # type: ignore
dc.astuple(x) # type: ignore
dc.replace(x) # type: ignore

# Mypy correctly emits an error on these due to the fact
# that it's a dataclass class, not a dataclass instance.
# Pyright, however, handles ClassVar members in protocols differently.
# See https://github.com/microsoft/pyright/issues/4339
#
# dc.asdict(x)
# dc.astuple(x)
# dc.replace(x)
Comment on lines +63 to +70
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could possibly put these tests inside an if MYPY block, and add "defineConstant": { "MYPY": false } } to our pyrightconfig file for our test cases, to ensure that pyright doesn't check any code inside if MYPY blocks. I'm sort-of wary of doing that, however, as I'd really like to keep typechecker-specific test cases to a minimum in typeshed.


if dc.is_dataclass(y):
assert_type(y, Union["DataclassInstance", Type["DataclassInstance"]])
assert_type(dc.fields(y), Tuple[dc.Field[Any], ...])

# Mypy corrextly emits an error on these due to the fact we don't know
# Mypy correctly emits an error on these due to the fact we don't know
# whether it's a dataclass class or a dataclass instance.
# Pyright, however, handles ClassVar members in protocols differently.
# See https://github.com/microsoft/pyright/issues/4339
Expand All @@ -75,6 +83,7 @@ def test_other_isdataclass_overloads(x: type, y: object) -> None:
# dc.replace(y)

if dc.is_dataclass(y) and not isinstance(y, type):
assert_type(y, "DataclassInstance")
assert_type(dc.fields(y), Tuple[dc.Field[Any], ...])
assert_type(dc.asdict(y), Dict[str, Any])
assert_type(dc.astuple(y), Tuple[Any, ...])
Expand Down