private attributes/methods shouldn't affect variance
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
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)
...
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
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
doesn't affect kotlin, but it seems very (overly?) strict
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"
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