Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PEP 695] Allow covariance with attribute that has "_" name prefix #17782

Merged
merged 1 commit into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions mypy/subtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2006,16 +2006,22 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
for member in all_non_object_members(info):
if member in ("__init__", "__new__"):
continue
node = info[member].node
if isinstance(node, Var) and node.type is None:
tv.variance = VARIANCE_NOT_READY
return False

if isinstance(self_type, TupleType):
self_type = mypy.typeops.tuple_fallback(self_type)

flags = get_member_flags(member, self_type)
typ = find_member(member, self_type, self_type)
settable = IS_SETTABLE in flags

node = info[member].node
if isinstance(node, Var):
if node.type is None:
tv.variance = VARIANCE_NOT_READY
return False
if has_underscore_prefix(member):
# Special case to avoid false positives (and to pass conformance tests)
settable = False

typ = find_member(member, self_type, self_type)
if typ:
typ2 = expand_type(typ, {tvar.id: object_type})
if not is_subtype(typ, typ2):
Expand All @@ -2036,6 +2042,10 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
return True


def has_underscore_prefix(name: str) -> bool:
return name.startswith("_") and not (name.startswith("__") and name.endswith("__"))


def infer_class_variances(info: TypeInfo) -> bool:
if not info.defn.type_args:
return True
Expand Down
63 changes: 63 additions & 0 deletions test-data/unit/check-python312.test
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,69 @@ class Invariant[T]:

reveal_type(c(a1, a2)) # N: Revealed type is "Never"

[case testPEP695InferVarianceUnderscorePrefix]
# flags: --enable-incomplete-feature=NewGenericSyntax

class Covariant1[T]:
def __init__(self, x: T) -> None:
self._x = x

@property
def x(self) -> T:
return self._x

co1_1: Covariant1[float] = Covariant1[int](1)
co1_2: Covariant1[int] = Covariant1[float](1) # E: Incompatible types in assignment (expression has type "Covariant1[float]", variable has type "Covariant1[int]")

class Covariant2[T]:
def __init__(self, x: T) -> None:
self.__foo_bar = x

@property
def x(self) -> T:
return self.__foo_bar

co2_1: Covariant2[float] = Covariant2[int](1)
co2_2: Covariant2[int] = Covariant2[float](1) # E: Incompatible types in assignment (expression has type "Covariant2[float]", variable has type "Covariant2[int]")

class Invariant1[T]:
def __init__(self, x: T) -> None:
self._x = x

# Methods behave differently from attributes
def _f(self, x: T) -> None: ...

@property
def x(self) -> T:
return self._x

inv1_1: Invariant1[float] = Invariant1[int](1) # E: Incompatible types in assignment (expression has type "Invariant1[int]", variable has type "Invariant1[float]")
inv1_2: Invariant1[int] = Invariant1[float](1) # E: Incompatible types in assignment (expression has type "Invariant1[float]", variable has type "Invariant1[int]")

class Invariant2[T]:
def __init__(self, x: T) -> None:
# Dunders are special
self.__x__ = x

@property
def x(self) -> T:
return self.__x__

inv2_1: Invariant2[float] = Invariant2[int](1) # E: Incompatible types in assignment (expression has type "Invariant2[int]", variable has type "Invariant2[float]")
inv2_2: Invariant2[int] = Invariant2[float](1) # E: Incompatible types in assignment (expression has type "Invariant2[float]", variable has type "Invariant2[int]")

class Invariant3[T]:
def __init__(self, x: T) -> None:
self._x = Invariant1(x)

@property
def x(self) -> T:
return self._x._x

inv3_1: Invariant3[float] = Invariant3[int](1) # E: Incompatible types in assignment (expression has type "Invariant3[int]", variable has type "Invariant3[float]")
inv3_2: Invariant3[int] = Invariant3[float](1) # E: Incompatible types in assignment (expression has type "Invariant3[float]", variable has type "Invariant3[int]")
[builtins fixtures/property.pyi]

[case testPEP695InheritInvariant]
# flags: --enable-incomplete-feature=NewGenericSyntax

Expand Down
Loading