Skip to content

Commit 0a3a081

Browse files
committed
Merge remote-tracking branch 'upstream/master' into fix-generic-classvar
2 parents 6cbfd18 + a48ffed commit 0a3a081

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+756
-116
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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,10 +2338,10 @@ def apply_inferred_arguments(
23382338
# Report error if some of the variables could not be solved. In that
23392339
# case assume that all variables have type Any to avoid extra
23402340
# bogus error messages.
2341-
for i, inferred_type in enumerate(inferred_args):
2341+
for inferred_type, tv in zip(inferred_args, callee_type.variables):
23422342
if not inferred_type or has_erased_component(inferred_type):
23432343
# Could not infer a non-trivial type for a type variable.
2344-
self.msg.could_not_infer_type_arguments(callee_type, i + 1, context)
2344+
self.msg.could_not_infer_type_arguments(callee_type, tv, context)
23452345
inferred_args = [AnyType(TypeOfAny.from_error)] * len(inferred_args)
23462346
# Apply the inferred types to the function type. In this case the
23472347
# return type must be CallableType, since we give the right number of type

mypy/checkmember.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,13 @@ def expand_and_bind_callable(
967967
# TODO: a decorated property can result in Overloaded here.
968968
assert isinstance(expanded, CallableType)
969969
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
970-
# TODO: use check_call() to infer better type, same as for __set__().
970+
if expanded.variables:
971+
type_ctx = mx.rvalue or TempNode(AnyType(TypeOfAny.special_form), context=mx.context)
972+
_, inferred_expanded = mx.chk.expr_checker.check_call(
973+
expanded, [type_ctx], [ARG_POS], mx.context
974+
)
975+
expanded = get_proper_type(inferred_expanded)
976+
assert isinstance(expanded, CallableType)
971977
if not expanded.arg_types:
972978
# This can happen when accessing invalid property from its own body,
973979
# error will be reported elsewhere.

mypy/constraints.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,6 +1335,11 @@ def visit_tuple_type(self, template: TupleType) -> list[Constraint]:
13351335
res.extend(
13361336
infer_constraints(template_items[i], actual_items[i], self.direction)
13371337
)
1338+
res.extend(
1339+
infer_constraints(
1340+
template.partial_fallback, actual.partial_fallback, self.direction
1341+
)
1342+
)
13381343
return res
13391344
elif isinstance(actual, AnyType):
13401345
return self.infer_against_any(template.items, actual)

mypy/message_registry.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,10 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
352352
"TypeVar constraint type cannot be parametrized by type variables", codes.MISC
353353
)
354354

355+
TYPE_VAR_REDECLARED_IN_NESTED_CLASS: Final = ErrorMessage(
356+
'Type variable "{}" is bound by an outer class', codes.VALID_TYPE
357+
)
358+
355359
TYPE_ALIAS_WITH_YIELD_EXPRESSION: Final = ErrorMessage(
356360
"Yield expression cannot be used within a type alias", codes.SYNTAX
357361
)

mypy/messages.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,11 +1370,14 @@ def incompatible_type_application(
13701370
self.fail(f"Type application has too few types ({s})", context)
13711371

13721372
def could_not_infer_type_arguments(
1373-
self, callee_type: CallableType, n: int, context: Context
1373+
self, callee_type: CallableType, tv: TypeVarLikeType, context: Context
13741374
) -> None:
13751375
callee_name = callable_name(callee_type)
1376-
if callee_name is not None and n > 0:
1377-
self.fail(f"Cannot infer type argument {n} of {callee_name}", context)
1376+
if callee_name is not None:
1377+
self.fail(
1378+
f"Cannot infer value of type parameter {format_type(tv, self.options)} of {callee_name}",
1379+
context,
1380+
)
13781381
if callee_name == "<dict>":
13791382
# Invariance in key type causes more of these errors than we would want.
13801383
self.note(

mypy/semanal.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,14 @@ def tvar_defs_from_tvars(
23742374
tvar_expr.default = tvar_expr.default.accept(
23752375
TypeVarDefaultTranslator(self, tvar_expr.name, context)
23762376
)
2377+
# PEP-695 type variables that are redeclared in an inner scope are warned
2378+
# about elsewhere.
2379+
if not tvar_expr.is_new_style and not self.tvar_scope.allow_binding(
2380+
tvar_expr.fullname
2381+
):
2382+
self.fail(
2383+
message_registry.TYPE_VAR_REDECLARED_IN_NESTED_CLASS.format(name), context
2384+
)
23772385
tvar_def = self.tvar_scope.bind_new(name, tvar_expr)
23782386
if last_tvar_name_with_default is not None and not tvar_def.has_default():
23792387
self.msg.tvar_without_default_type(

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,

mypy/suggestions.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,18 @@ def is_implicit_any(typ: Type) -> bool:
229229
return isinstance(typ, AnyType) and not is_explicit_any(typ)
230230

231231

232+
def _arg_accepts_function(typ: ProperType) -> bool:
233+
return (
234+
# TypeVar / Callable
235+
isinstance(typ, (TypeVarType, CallableType))
236+
or
237+
# Protocol with __call__
238+
isinstance(typ, Instance)
239+
and typ.type.is_protocol
240+
and typ.type.get_method("__call__") is not None
241+
)
242+
243+
232244
class SuggestionEngine:
233245
"""Engine for finding call sites and suggesting signatures."""
234246

@@ -658,7 +670,7 @@ def extract_from_decorator(self, node: Decorator) -> FuncDef | None:
658670
for ct in typ.items:
659671
if not (
660672
len(ct.arg_types) == 1
661-
and isinstance(ct.arg_types[0], TypeVarType)
673+
and _arg_accepts_function(get_proper_type(ct.arg_types[0]))
662674
and ct.arg_types[0] == ct.ret_type
663675
):
664676
return None

0 commit comments

Comments
 (0)