python / mypy

Optional static typing for Python
https://www.mypy-lang.org/
Other
18.33k stars 2.81k forks source link

Dataclasses: `No overload variant of "asdict" matches argument type "Self" [call-overload]` when deciding decorator conditionally #17242

Open abravalheri opened 5 months ago

abravalheri commented 5 months ago

Bug Report

Mypy raises a false positive for dataclass.asdict (No overload variant of "asdict" matches argument type "Self" [call-overload]) on the very specific circumstances:

  1. The specific decorator for dataclass is decided based on a condition (e.g. Python version)
  2. The datalcasses.asdict method is called from a method that uses PEP 673's Self Type.

I also noted a similar problem occurs for dataclasses.replace: Value of type variable "_DataclassT" of "replace" cannot be "Self" [type-var].

To Reproduce

import sys
import dataclasses
import json
from typing import Self

if sys.version_info >= (3, 10):
    dataclass = dataclasses.dataclass(kw_only=True)
else:
    dataclass = dataclasses.dataclass

@dataclass
class HelloWorld:
    a: int = 1
    b: int = 2
    c: int = 3

    def double(self) -> Self:
        fields = dataclasses.asdict(self)
        return self.__class__(**{k: v*2 for k, v in fields.items()})

    def json_overwrite(self, text: str) -> Self:
        new = json.loads(text)
        return dataclasses.replace(self, **new)

x = HelloWorld().double()
y = HelloWorld().json_overwrite('{"c": 42}')
assert x.b == 4
assert y.c == 42
print(f"{x=}")
print(f"{y=}")

Gist URL: https://gist.github.com/mypy-play/70b801fbe15391f0750be8f6c403fdf4 Playground URL: https://mypy-play.net/?mypy=latest&python=3.12&flags=show-error-context%2Cstrict&gist=70b801fbe15391f0750be8f6c403fdf4

Expected Behavior

There should be no false negative. The program works fine in runtime:

$ python3.12 main.py
x=HelloWorld(a=2, b=4, c=6)
y=HelloWorld(a=1, b=2, c=42)

Actual Behavior

main.py: note: In member "double" of class "HelloWorld":
main.py:19: error: No overload variant of "asdict" matches argument type "Self"  [call-overload]
main.py:19: note: Possible overload variants:
main.py:19: note:     def asdict(obj: DataclassInstance) -> dict[str, Any]
main.py:19: note:     def [_T] asdict(obj: DataclassInstance, *, dict_factory: Callable[[list[tuple[str, Any]]], _T]) -> _T
main.py: note: In member "json_overwrite" of class "HelloWorld":
main.py:24: error: Value of type variable "_DataclassT" of "replace" cannot be "Self"  [type-var]
Found 2 errors in 1 file (checked 1 source file)

Your Environment

OWissett commented 1 week ago

I too have found this when working with pydantic dataclasses