Skip to content

Commit efe6ed7

Browse files
committed
Handle corner case: protocol vs classvar vs descriptor
1 parent 61cbe0c commit efe6ed7

File tree

2 files changed

+37
-1
lines changed

2 files changed

+37
-1
lines changed

mypy/subtypes.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1457,14 +1457,24 @@ def get_member_flags(name: str, itype: Instance, class_obj: bool = False) -> set
14571457
flags = {IS_VAR}
14581458
if not v.is_final:
14591459
flags.add(IS_SETTABLE)
1460-
if v.is_classvar:
1460+
# TODO: define cleaner rules for class vs instance variables.
1461+
if v.is_classvar and not is_descriptor(v.type):
14611462
flags.add(IS_CLASSVAR)
14621463
if class_obj and v.is_inferred:
14631464
flags.add(IS_CLASSVAR)
14641465
return flags
14651466
return set()
14661467

14671468

1469+
def is_descriptor(typ: Type | None) -> bool:
1470+
typ = get_proper_type(typ)
1471+
if isinstance(typ, Instance):
1472+
return typ.type.get("__get__") is not None
1473+
if isinstance(typ, UnionType):
1474+
return all(is_descriptor(item) for item in typ.relevant_items())
1475+
return False
1476+
1477+
14681478
def find_node_type(
14691479
node: Var | FuncBase,
14701480
itype: Instance,

test-data/unit/check-protocols.test

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4602,3 +4602,29 @@ def deco(fn: Callable[[], T]) -> Callable[[], list[T]]: ...
46024602
@deco
46034603
def defer() -> int: ...
46044604
[builtins fixtures/list.pyi]
4605+
4606+
[case testProtocolClassValDescriptor]
4607+
from typing import Any, Protocol, overload, ClassVar, Type
4608+
4609+
class Desc:
4610+
@overload
4611+
def __get__(self, instance: None, owner: Any) -> Desc: ...
4612+
@overload
4613+
def __get__(self, instance: object, owner: Any) -> int: ...
4614+
def __get__(self, instance, owner):
4615+
pass
4616+
4617+
class P(Protocol):
4618+
x: ClassVar[Desc]
4619+
4620+
class C:
4621+
x = Desc()
4622+
4623+
t: P = C()
4624+
reveal_type(t.x) # N: Revealed type is "builtins.int"
4625+
tt: Type[P] = C
4626+
reveal_type(tt.x) # N: Revealed type is "__main__.Desc"
4627+
4628+
bad: P = C # E: Incompatible types in assignment (expression has type "type[C]", variable has type "P") \
4629+
# N: Following member(s) of "C" have conflicts: \
4630+
# N: x: expected "int", got "Desc"

0 commit comments

Comments
 (0)