Skip to content

Commit fce14a0

Browse files
authored
[PEP 695] Allow covariance with attribute that has "_" name prefix (#17782)
Fix this conformance test: ``` class ShouldBeCovariant5[T]: def __init__(self, x: T) -> None: self._x = x @Property def x(self) -> T: return self._x vo5_1: ShouldBeCovariant5[float] = ShouldBeCovariant5[int](1) # OK vo5_2: ShouldBeCovariant5[int] = ShouldBeCovariant5[float](1) # E ``` My fix is to treat such attributes as not settable when inferring variance. Link: https://github.com/python/typing/blob/main/conformance/tests/generics_variance_inference.py#L79
1 parent a47f301 commit fce14a0

File tree

2 files changed

+79
-6
lines changed

2 files changed

+79
-6
lines changed

mypy/subtypes.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2006,16 +2006,22 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
20062006
for member in all_non_object_members(info):
20072007
if member in ("__init__", "__new__"):
20082008
continue
2009-
node = info[member].node
2010-
if isinstance(node, Var) and node.type is None:
2011-
tv.variance = VARIANCE_NOT_READY
2012-
return False
2009+
20132010
if isinstance(self_type, TupleType):
20142011
self_type = mypy.typeops.tuple_fallback(self_type)
2015-
20162012
flags = get_member_flags(member, self_type)
2017-
typ = find_member(member, self_type, self_type)
20182013
settable = IS_SETTABLE in flags
2014+
2015+
node = info[member].node
2016+
if isinstance(node, Var):
2017+
if node.type is None:
2018+
tv.variance = VARIANCE_NOT_READY
2019+
return False
2020+
if has_underscore_prefix(member):
2021+
# Special case to avoid false positives (and to pass conformance tests)
2022+
settable = False
2023+
2024+
typ = find_member(member, self_type, self_type)
20192025
if typ:
20202026
typ2 = expand_type(typ, {tvar.id: object_type})
20212027
if not is_subtype(typ, typ2):
@@ -2036,6 +2042,10 @@ def infer_variance(info: TypeInfo, i: int) -> bool:
20362042
return True
20372043

20382044

2045+
def has_underscore_prefix(name: str) -> bool:
2046+
return name.startswith("_") and not (name.startswith("__") and name.endswith("__"))
2047+
2048+
20392049
def infer_class_variances(info: TypeInfo) -> bool:
20402050
if not info.defn.type_args:
20412051
return True

test-data/unit/check-python312.test

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,69 @@ class Invariant[T]:
342342

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

345+
[case testPEP695InferVarianceUnderscorePrefix]
346+
# flags: --enable-incomplete-feature=NewGenericSyntax
347+
348+
class Covariant1[T]:
349+
def __init__(self, x: T) -> None:
350+
self._x = x
351+
352+
@property
353+
def x(self) -> T:
354+
return self._x
355+
356+
co1_1: Covariant1[float] = Covariant1[int](1)
357+
co1_2: Covariant1[int] = Covariant1[float](1) # E: Incompatible types in assignment (expression has type "Covariant1[float]", variable has type "Covariant1[int]")
358+
359+
class Covariant2[T]:
360+
def __init__(self, x: T) -> None:
361+
self.__foo_bar = x
362+
363+
@property
364+
def x(self) -> T:
365+
return self.__foo_bar
366+
367+
co2_1: Covariant2[float] = Covariant2[int](1)
368+
co2_2: Covariant2[int] = Covariant2[float](1) # E: Incompatible types in assignment (expression has type "Covariant2[float]", variable has type "Covariant2[int]")
369+
370+
class Invariant1[T]:
371+
def __init__(self, x: T) -> None:
372+
self._x = x
373+
374+
# Methods behave differently from attributes
375+
def _f(self, x: T) -> None: ...
376+
377+
@property
378+
def x(self) -> T:
379+
return self._x
380+
381+
inv1_1: Invariant1[float] = Invariant1[int](1) # E: Incompatible types in assignment (expression has type "Invariant1[int]", variable has type "Invariant1[float]")
382+
inv1_2: Invariant1[int] = Invariant1[float](1) # E: Incompatible types in assignment (expression has type "Invariant1[float]", variable has type "Invariant1[int]")
383+
384+
class Invariant2[T]:
385+
def __init__(self, x: T) -> None:
386+
# Dunders are special
387+
self.__x__ = x
388+
389+
@property
390+
def x(self) -> T:
391+
return self.__x__
392+
393+
inv2_1: Invariant2[float] = Invariant2[int](1) # E: Incompatible types in assignment (expression has type "Invariant2[int]", variable has type "Invariant2[float]")
394+
inv2_2: Invariant2[int] = Invariant2[float](1) # E: Incompatible types in assignment (expression has type "Invariant2[float]", variable has type "Invariant2[int]")
395+
396+
class Invariant3[T]:
397+
def __init__(self, x: T) -> None:
398+
self._x = Invariant1(x)
399+
400+
@property
401+
def x(self) -> T:
402+
return self._x._x
403+
404+
inv3_1: Invariant3[float] = Invariant3[int](1) # E: Incompatible types in assignment (expression has type "Invariant3[int]", variable has type "Invariant3[float]")
405+
inv3_2: Invariant3[int] = Invariant3[float](1) # E: Incompatible types in assignment (expression has type "Invariant3[float]", variable has type "Invariant3[int]")
406+
[builtins fixtures/property.pyi]
407+
345408
[case testPEP695InheritInvariant]
346409
# flags: --enable-incomplete-feature=NewGenericSyntax
347410

0 commit comments

Comments
 (0)