Skip to content

Commit eb70fa7

Browse files
committed
Merge branch 'master'
2 parents 4937bf0 + ed88d82 commit eb70fa7

File tree

11 files changed

+245
-5
lines changed

11 files changed

+245
-5
lines changed

docs/source/protocols.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,53 @@ the parameters are positional-only. Example (using the legacy syntax for generic
352352
copy_a = copy_b # OK
353353
copy_b = copy_a # Also OK
354354
355+
Binding of types in protocol attributes
356+
***************************************
357+
358+
All protocol attributes annotations are treated as externally visible types
359+
of those attributes. This means that for example callables are not bound,
360+
and descriptors are not invoked:
361+
362+
.. code-block:: python
363+
364+
from typing import Callable, Protocol, overload
365+
366+
class Integer:
367+
@overload
368+
def __get__(self, instance: None, owner: object) -> Integer: ...
369+
@overload
370+
def __get__(self, instance: object, owner: object) -> int: ...
371+
# <some implementation>
372+
373+
class Example(Protocol):
374+
foo: Callable[[object], int]
375+
bar: Integer
376+
377+
ex: Example
378+
reveal_type(ex.foo) # Revealed type is Callable[[object], int]
379+
reveal_type(ex.bar) # Revealed type is Integer
380+
381+
In other words, protocol attribute types are handled as they would appear in a
382+
``self`` attribute annotation in a regular class. If you want some protocol
383+
attributes to be handled as though they were defined at class level, you should
384+
declare them explicitly using ``ClassVar[...]``. Continuing previous example:
385+
386+
.. code-block:: python
387+
388+
from typing import ClassVar
389+
390+
class OtherExample(Protocol):
391+
# This style is *not recommended*, but may be needed to reuse
392+
# some complex callable types. Otherwise use regular methods.
393+
foo: ClassVar[Callable[[object], int]]
394+
# This may be needed to mimic descriptor access on Type[...] types,
395+
# otherwise use a plain "bar: int" style.
396+
bar: ClassVar[Integer]
397+
398+
ex2: OtherExample
399+
reveal_type(ex2.foo) # Revealed type is Callable[[], int]
400+
reveal_type(ex2.bar) # Revealed type is int
401+
355402
.. _predefined_protocols_reference:
356403

357404
Predefined protocol reference

misc/upload-pypi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def tmp_twine() -> Iterator[Path]:
108108
def upload_dist(dist: Path, dry_run: bool = True) -> None:
109109
with tmp_twine() as twine:
110110
files = [item for item in dist.iterdir() if item_ok_for_pypi(item.name)]
111-
cmd: list[Any] = [twine, "upload"]
111+
cmd: list[Any] = [twine, "upload", "--skip-existing"]
112112
cmd += files
113113
if dry_run:
114114
print("[dry run] " + " ".join(map(str, cmd)))

mypy/checkexpr.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4288,7 +4288,9 @@ def check_boolean_op(self, e: OpExpr, context: Context) -> Type:
42884288
):
42894289
self.msg.unreachable_right_operand(e.op, e.right)
42904290

4291-
right_type = self.analyze_cond_branch(right_map, e.right, expanded_left_type)
4291+
right_type = self.analyze_cond_branch(
4292+
right_map, e.right, self._combined_context(expanded_left_type)
4293+
)
42924294

42934295
if left_map is None and right_map is None:
42944296
return UninhabitedType()
@@ -5919,6 +5921,16 @@ def analyze_cond_branch(
59195921
self.chk.push_type_map(map)
59205922
return self.accept(node, type_context=context, allow_none_return=allow_none_return)
59215923

5924+
def _combined_context(self, ty: Type | None) -> Type | None:
5925+
ctx_items = []
5926+
if ty is not None:
5927+
ctx_items.append(ty)
5928+
if self.type_context and self.type_context[-1] is not None:
5929+
ctx_items.append(self.type_context[-1])
5930+
if ctx_items:
5931+
return make_simplified_union(ctx_items)
5932+
return None
5933+
59225934
#
59235935
# Helpers
59245936
#

mypy/checkmember.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -968,7 +968,13 @@ def expand_and_bind_callable(
968968
# TODO: a decorated property can result in Overloaded here.
969969
assert isinstance(expanded, CallableType)
970970
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
971-
# TODO: use check_call() to infer better type, same as for __set__().
971+
if expanded.variables:
972+
type_ctx = mx.rvalue or TempNode(AnyType(TypeOfAny.special_form), context=mx.context)
973+
_, inferred_expanded = mx.chk.expr_checker.check_call(
974+
expanded, [type_ctx], [ARG_POS], mx.context
975+
)
976+
expanded = get_proper_type(inferred_expanded)
977+
assert isinstance(expanded, CallableType)
972978
if not expanded.arg_types:
973979
# This can happen when accessing invalid property from its own body,
974980
# error will be reported elsewhere.
@@ -1221,6 +1227,9 @@ def analyze_class_attribute_access(
12211227
is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
12221228
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class
12231229
)
1230+
is_staticmethod = (is_decorated and cast(Decorator, node.node).func.is_static) or (
1231+
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_static
1232+
)
12241233
t = get_proper_type(t)
12251234
is_trivial_self = False
12261235
if isinstance(node.node, Decorator):
@@ -1230,14 +1239,20 @@ def analyze_class_attribute_access(
12301239
is_trivial_self = node.node.is_trivial_self
12311240
if isinstance(t, FunctionLike) and is_classmethod and not is_trivial_self:
12321241
t = check_self_arg(t, mx.self_type, False, mx.context, name, mx.msg)
1233-
result = add_class_tvars(
1242+
t = add_class_tvars(
12341243
t,
12351244
isuper,
12361245
is_classmethod,
12371246
mx,
12381247
original_vars=original_vars,
12391248
is_trivial_self=is_trivial_self,
12401249
)
1250+
if is_decorated and not is_staticmethod:
1251+
t = expand_self_type_if_needed(
1252+
t, mx, cast(Decorator, node.node).var, itype, is_class=is_classmethod
1253+
)
1254+
1255+
result = t
12411256
# __set__ is not called on class objects.
12421257
if not mx.is_lvalue:
12431258
result = analyze_descriptor_access(result, mx)

mypy/join.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,8 @@ def visit_literal_type(self, t: LiteralType) -> ProperType:
625625
if self.s.fallback.type.is_enum and t.fallback.type.is_enum:
626626
return mypy.typeops.make_simplified_union([self.s, t])
627627
return join_types(self.s.fallback, t.fallback)
628+
elif isinstance(self.s, Instance) and self.s.last_known_value == t:
629+
return t
628630
else:
629631
return join_types(self.s, t.fallback)
630632

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-generics.test

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,3 +3628,32 @@ def draw_none(
36283628
takes_int_str_none(c2)
36293629
takes_int_str_none(c3)
36303630
[builtins fixtures/tuple.pyi]
3631+
3632+
[case testPropertyWithGenericSetter]
3633+
from typing import TypeVar
3634+
3635+
class B: ...
3636+
class C(B): ...
3637+
T = TypeVar("T", bound=B)
3638+
3639+
class Test:
3640+
@property
3641+
def foo(self) -> list[C]: ...
3642+
@foo.setter
3643+
def foo(self, val: list[T]) -> None: ...
3644+
3645+
t1: Test
3646+
t2: Test
3647+
3648+
lb: list[B]
3649+
lc: list[C]
3650+
li: list[int]
3651+
3652+
t1.foo = lb
3653+
t1.foo = lc
3654+
t1.foo = li # E: Value of type variable "T" of "foo" of "Test" cannot be "int"
3655+
3656+
t2.foo = [B()]
3657+
t2.foo = [C()]
3658+
t2.foo = [1] # E: Value of type variable "T" of "foo" of "Test" cannot be "int"
3659+
[builtins fixtures/property.pyi]

test-data/unit/check-inference-context.test

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,3 +1510,24 @@ def mymin(
15101510
def check(paths: Iterable[str], key: Callable[[str], int]) -> Union[str, None]:
15111511
return mymin(paths, key=key, default=None)
15121512
[builtins fixtures/tuple.pyi]
1513+
1514+
[case testBinaryOpInferenceContext]
1515+
from typing import Literal, TypeVar
1516+
1517+
T = TypeVar("T")
1518+
1519+
def identity(x: T) -> T:
1520+
return x
1521+
1522+
def check1(use: bool, val: str) -> "str | Literal[True]":
1523+
return use or identity(val)
1524+
1525+
def check2(use: bool, val: str) -> "str | bool":
1526+
return use or identity(val)
1527+
1528+
def check3(use: bool, val: str) -> "str | Literal[False]":
1529+
return use and identity(val)
1530+
1531+
def check4(use: bool, val: str) -> "str | bool":
1532+
return use and identity(val)
1533+
[builtins fixtures/tuple.pyi]

test-data/unit/check-literal.test

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2976,3 +2976,22 @@ x: Type[Literal[1]] # E: Type[...] can't contain "Literal[...]"
29762976
y: Type[Union[Literal[1], Literal[2]]] # E: Type[...] can't contain "Union[Literal[...], Literal[...]]"
29772977
z: Type[Literal[1, 2]] # E: Type[...] can't contain "Union[Literal[...], Literal[...]]"
29782978
[builtins fixtures/tuple.pyi]
2979+
2980+
[case testJoinLiteralAndInstance]
2981+
from typing import Generic, TypeVar, Literal
2982+
2983+
T = TypeVar("T")
2984+
2985+
class A(Generic[T]): ...
2986+
2987+
def f(a: A[T], t: T) -> T: ...
2988+
def g(a: T, t: A[T]) -> T: ...
2989+
2990+
def check(obj: A[Literal[1]]) -> None:
2991+
reveal_type(f(obj, 1)) # N: Revealed type is "Literal[1]"
2992+
reveal_type(f(obj, '')) # E: Cannot infer type argument 1 of "f" \
2993+
# N: Revealed type is "Any"
2994+
reveal_type(g(1, obj)) # N: Revealed type is "Literal[1]"
2995+
reveal_type(g('', obj)) # E: Cannot infer type argument 1 of "g" \
2996+
# N: Revealed type is "Any"
2997+
[builtins fixtures/tuple.pyi]

test-data/unit/check-protocols.test

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4602,3 +4602,47 @@ 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: object) -> Desc: ...
4612+
@overload
4613+
def __get__(self, instance: object, owner: object) -> 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"
4631+
4632+
[case testProtocolClassValCallable]
4633+
from typing import Any, Protocol, overload, ClassVar, Type, Callable
4634+
4635+
class P(Protocol):
4636+
foo: Callable[[object], int]
4637+
bar: ClassVar[Callable[[object], int]]
4638+
4639+
class C:
4640+
foo: Callable[[object], int]
4641+
bar: ClassVar[Callable[[object], int]]
4642+
4643+
t: P = C()
4644+
reveal_type(t.foo) # N: Revealed type is "def (builtins.object) -> builtins.int"
4645+
reveal_type(t.bar) # N: Revealed type is "def () -> builtins.int"
4646+
tt: Type[P] = C
4647+
reveal_type(tt.foo) # N: Revealed type is "def (builtins.object) -> builtins.int"
4648+
reveal_type(tt.bar) # N: Revealed type is "def (builtins.object) -> builtins.int"

0 commit comments

Comments
 (0)