basedpyright icon indicating copy to clipboard operation
basedpyright copied to clipboard

private attributes/methods shouldn't affect variance

Open DetachHead opened this issue 1 year ago • 6 comments

Code sample in basedpyright playground

class Foo[T]: # (type parameter) T: T@Foo (contravariant)
    def __foo(self, value: T): ...

since __foo can't be called outside of the class, i'm pretty sure it's safe for the generic to be covariant

DetachHead avatar Sep 26 '24 06:09 DetachHead

additionally, protected members, while they can be called from subclasses, that isn't relevant to variance safety

additionally, these same checks should also apply to old style TypeVars:

from typing import Generic, TypeVar


T = TypeVar("T", covariant=True)

class Foo(Generic[T]):
    def _foo(self, value: T):  # Covariant type variable cannot be used in parameter type (reportLiteralNonsense)
        ...

playground

KotlinIsland avatar Oct 07 '24 06:10 KotlinIsland

i've made a groundbreaking discovery:

Code sample in basedpyright playground

from typing import reveal_type


class Foo[T]:
    def __init__(self, value: T) -> None:
        self.__value: T = value

    def __set_value(self, value: T):
        self.__value = value

    def get_value(self) -> T:
        return self.__value

    @staticmethod
    def asdf():
        foo: Foo[int] = Foo(1)
        foo.__set_value(1)
        # correct error, which would be removed by this proposed change:
        bar: Foo[object] = foo # reportAssignmentType
        bar.__set_value("")
        reveal_type(foo.get_value())  # runtime: str, basedpyright: int


Foo.asdf()

private/protected attributes/methods can be accessed "publicly" from other instances of the same class, so it's not safe to prevent them from impacting variance

this also seems to be consistent with other languages:

this means the current behavior, while extremely annoying, is technically correct.

possible solutions:

  • add a new rule to prevent accessing private/protected variables from other instances like this
  • invent a stricter visibility modifier for this use case

DetachHead avatar Sep 29 '25 06:09 DetachHead

ah yes, great minds think alike, see my prior research on this issue:

  • https://github.com/astral-sh/ty/issues/1224
  • https://github.com/facebook/pyrefly/issues/1126
  • https://github.com/DetachHead/basedpyright/issues/1101

KotlinIsland avatar Sep 29 '25 06:09 KotlinIsland

doesn't affect kotlin, but it seems very (overly?) strict

KotlinIsland avatar Sep 29 '25 07:09 KotlinIsland

the conclusion i've reached is "if something is private, then it's values are never materialized, only T, so you can't assign a value that's not T, you can't send an argument that is not T, and you similarly can't retrieve any value that is not T"

KotlinIsland avatar Sep 29 '25 07:09 KotlinIsland

See also:

  • https://discuss.python.org/t/whether-private-members-should-affect-pep-695s-type-var-variance/38959
  • https://discuss.python.org/t/should-variance-inference-ignore-underscore-prefixed-attributes/102978

jorenham avatar Sep 30 '25 18:09 jorenham