microsoft / pyright

Static Type Checker for Python
Other
13.12k stars 1.4k forks source link

NamedTuple/dataclass special attribute __name__ incorrectly treated as immutable for matching protocols #8829

Closed maxfischer2781 closed 3 days ago

maxfischer2781 commented 2 weeks ago

Describe the bug When matching a Protocol with mutable special (class) attribute __name__, a NamedTuple/dataclass class is rejected as immutable even though the (class) attribute is actually writeable. This seems to only affect __name__; the __module__ and __qualname__ attributes are considered r/w.

This seems to only trigger when matching against a Protocol. When directly working with the class attribute (e.g. my_tup.__name__ = "something"), PyRight does not complain.

Code or Screenshots Consider a decorator that patches the name of classes. With recent PyRight versions, this only works for regular classes, not NamedTuple/dataclass classes.

from typing import Protocol, NamedTuple
from dataclasses import dataclass

class Named(Protocol):
    __name__: str

def transform[N: Named](n: N) -> N:
    n.__name__ = f"transfomed_{n.__name__}"
    return n

@transform
class Working: ...

@transform
class BrokenT(NamedTuple): ...

@transform
@dataclass(frozen=True)
class BrokenD: ...
  /Users/maxfischer/vscode/snippets/playground.py:19:2 - error: Argument of type "type[BrokenT]" cannot be assigned to parameter "n" of type "N@transform" in function "transform"
    Type "type[BrokenT]" is incompatible with type "Named"
      "__name__" is not read-only in protocol (reportArgumentType)
  /Users/maxfischer/vscode/snippets/playground.py:23:2 - error: Argument of type "type[BrokenD]" cannot be assigned to parameter "n" of type "N@transform" in function "transform"
    Type "type[BrokenD]" is incompatible with type "Named"
      "__name__" is not read-only in protocol (reportArgumentType)

Notably, there are no runtime errors. The __name__ attribute is writeable on both BrokenT and BrokenD at runtime.

VS Code extension or command-line Tested with the python -m pyright CLI on version pyright 1.1.377. Testing this in the PyRight Playground, the code was accepted up to PyRight 1.1.375.

erictraut commented 3 days ago

This is addressed in pyright 1.1.380.