Skip to content

Self not accepted when used with a dataclass #18920

@jrdnh

Description

@jrdnh

Using Self with a dataclass raises a type-var error even though Self is an acceptable type.

Suspect it may be the same as #18812.

To Reproduce

from __future__ import annotations
from dataclasses import dataclass
from typing import Self

type HasParent[N] = N | Node[N | HasParent[N]]


class Node[P = None]:
    def parent(self) -> P: ...  # type: ignore

@dataclass
class C[P: Node | None = Node | None](Node[P]):
    item: D[Self]

class D[P: HasParent[C] = C](Node[P]):
    pass

Changing to Self to the explicit class type (item: D[C]) is accepted. Similarly using Self in a regular class (e.g. below with __init__) does not raise any errors.

class C[P: Node | None = Node | None](Node[P]):
    def __init__(self, item: D[Self]):
        self.item = item

Expected Behavior

No errors.

Actual Behavior

error: Type argument "C[P]" of "D" must be a subtype of "HasParent[C[Node[None] | None]]"  [type-var]
error: Type argument "Self" of "D" must be a subtype of "HasParent[C[Node[None] | None]]"  [type-var]

Environment

  • Mypy version used: 1.15.0
  • Python version used: 13.2

Activity

Jdwashin9

Jdwashin9 commented on Apr 14, 2025

@Jdwashin9

Hello! I am working on an Open Source project for the first time, but I have taken multiple academic classes in Python. Can I work on this issue?

sobolevn

sobolevn commented on Apr 14, 2025

@sobolevn
Member

@Jdwashin9 sure, feel free :)

brianschubert

brianschubert commented on Apr 18, 2025

@brianschubert
Member

I think this boils down to:

from typing import Self

class Node: ...

class C[P: Node]:
    item: D[Self]   # E: Type argument "Self" of "D" must be a subtype of "C[Node]"

class D[P: C[Node]]: ...

Whether this is correct depends on what the inferred variance of P@C is supposed to be. Currently, mypy infers it to be invariant, since it treats Self like C[P] for the purposes of variance inference.

I don't think (?) the typing spec comments on whether Self should contribute to the inferred variance of other type variables. But I think this behavior is at least defensible, since it guards against unsound modification of item via a supertype reference that would otherwise be possible if P@C were covariant.

If anything, I wonder if we should always warn about Self being used invariantly like this. E.g., the following code is unsound due to B implicitly overriding a mutable attribute, but is accepted by mypy and pryight:

from typing import Self

class A:
    x: Self  # should this be an error?

class B(A): ...  # or this?

def mutate(bad: A) -> None:
    bad.x = A()

b = B()
mutate(b)
assert isinstance(b.x, B)  # boom
sterliakov

sterliakov commented on May 1, 2025

@sterliakov
Collaborator

I'm big +1 on rejecting such Self usage in instance attributes right at the definition site. Self is only well-defined in covariant positions, so any use in writeable attributes and parameters is immediately unsafe and confusing - it violates all the rules we know (like "upcasting to a supertype should never make an ill-typed program well-typed") and can hide a lot of soundness issues.

However, it's too late to start doing that - we have final PEP 673, we have typing spec, and they both define Self usage in attribute position. I think it's a linter job - like ruff's banned-api - to prevent from typing import Self once and forever in projects that want that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sobolevn@jrdnh@brianschubert@sterliakov@Jdwashin9

        Issue actions

          `Self` not accepted when used with a `dataclass` · Issue #18920 · python/mypy