From 9b079f6592740a51c0e629728eeb0324ad85126f Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 11 Jun 2025 13:25:18 +0100 Subject: [PATCH 1/9] Bump version to 1.16.1+dev --- mypy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/version.py b/mypy/version.py index 34fb2f642c5e..d2d42a386dec 100644 --- a/mypy/version.py +++ b/mypy/version.py @@ -8,7 +8,7 @@ # - Release versions have the form "1.2.3". # - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440). # - Before 1.0 we had the form "0.NNN". -__version__ = "1.16.0" +__version__ = "1.16.1+dev" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) From 0a4f28431faa18e59d35bc269cb0ea6c00810653 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 3 Jun 2025 08:18:05 +0100 Subject: [PATCH 2/9] Fix crash on invalid property inside its own body (#19208) Fixes https://github.com/python/mypy/issues/19205 --- mypy/checkmember.py | 4 ++++ test-data/unit/check-classes.test | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index cc104fed0752..4821c622cb0c 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -974,6 +974,10 @@ def expand_and_bind_callable( assert isinstance(expanded, CallableType) if var.is_settable_property and mx.is_lvalue and var.setter_type is not None: # TODO: use check_call() to infer better type, same as for __set__(). + if not expanded.arg_types: + # This can happen when accessing invalid property from its own body, + # error will be reported elsewhere. + return AnyType(TypeOfAny.from_error) return expanded.arg_types[0] else: return expanded.ret_type diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index e0ea00aee361..59204cc31c43 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -8726,3 +8726,16 @@ class Fields: reveal_type(Fields.bool_f) # N: Revealed type is "__main__.BoolField" reveal_type(Fields.int_f) # N: Revealed type is "__main__.NumField" reveal_type(Fields.custom_f) # N: Revealed type is "__main__.AnyField[__main__.Custom]" + +[case testRecursivePropertyWithInvalidSetterNoCrash] +class NoopPowerResource: + _hardware_type: int + + @property + def hardware_type(self) -> int: + return self._hardware_type + + @hardware_type.setter + def hardware_type(self) -> None: # E: Invalid property setter signature + self.hardware_type = None # Note: intentionally recursive +[builtins fixtures/property.pyi] From c39f5e73c47182e51c5d8d488f7cc7301257c974 Mon Sep 17 00:00:00 2001 From: Advait Dixit <48302999+advait-dixit@users.noreply.github.com> Date: Mon, 2 Jun 2025 07:20:25 -0700 Subject: [PATCH 3/9] [mypyc] Fixing condition for handling user-defined __del__ (#19188) Fixes #19175. Conditions for generating and invoking `del` method were not consistent. As things currently stand, this pull request fixes the crash. However, for classes that derive from Python built-ins, user-defined `__del__` will not be invoked. --- mypyc/codegen/emitclass.py | 6 +++--- mypyc/test-data/run-classes.test | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/mypyc/codegen/emitclass.py b/mypyc/codegen/emitclass.py index c5191e5fb939..9cb9074b9fc4 100644 --- a/mypyc/codegen/emitclass.py +++ b/mypyc/codegen/emitclass.py @@ -304,9 +304,6 @@ def emit_line() -> None: emit_line() generate_dealloc_for_class(cl, dealloc_name, clear_name, bool(del_method), emitter) emit_line() - if del_method: - generate_finalize_for_class(del_method, finalize_name, emitter) - emit_line() if cl.allow_interpreted_subclasses: shadow_vtable_name: str | None = generate_vtables( @@ -317,6 +314,9 @@ def emit_line() -> None: shadow_vtable_name = None vtable_name = generate_vtables(cl, vtable_setup_name, vtable_name, emitter, shadow=False) emit_line() + if del_method: + generate_finalize_for_class(del_method, finalize_name, emitter) + emit_line() if needs_getseters: generate_getseter_declarations(cl, emitter) emit_line() diff --git a/mypyc/test-data/run-classes.test b/mypyc/test-data/run-classes.test index 97bc063dd8ea..288f281c0a94 100644 --- a/mypyc/test-data/run-classes.test +++ b/mypyc/test-data/run-classes.test @@ -2754,6 +2754,21 @@ def test_function(): assert(isinstance(d.fitem, ForwardDefinedClass)) assert(isinstance(d.fitems, ForwardDefinedClass)) +[case testDelForDictSubclass-xfail] +# The crash in issue mypy#19175 is fixed. +# But, for classes that derive from built-in Python classes, user-defined __del__ method is not +# being invoked. +class DictSubclass(dict): + def __del__(self): + print("deleting DictSubclass...") + +[file driver.py] +import native +native.DictSubclass() + +[out] +deleting DictSubclass... + [case testDel] class A: def __del__(self): @@ -2774,6 +2789,12 @@ class C(B): class D(A): pass +# Just make sure that this class compiles (see issue mypy#19175). testDelForDictSubclass tests for +# correct output. +class NormDict(dict): + def __del__(self) -> None: + pass + [file driver.py] import native native.C() From cb3c6ec6a7aaa96a0e26768a946ac63ea14115f2 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Tue, 3 Jun 2025 18:25:02 +0100 Subject: [PATCH 4/9] Fix crash on partial type used as context (#19216) Fixes https://github.com/python/mypy/issues/19213 --- mypy/checker.py | 4 +++- test-data/unit/check-inference.test | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/mypy/checker.py b/mypy/checker.py index 9c389cccd95f..cb465b390aa6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -3426,7 +3426,9 @@ def check_compatibility_all_supers(self, lvalue: RefExpr, rvalue: Expression) -> # store the rvalue type on the variable. actual_lvalue_type = None if lvalue_node.is_inferred and not lvalue_node.explicit_self_type: - rvalue_type = self.expr_checker.accept(rvalue, lvalue_node.type) + # Don't use partial types as context, similar to regular code path. + ctx = lvalue_node.type if not isinstance(lvalue_node.type, PartialType) else None + rvalue_type = self.expr_checker.accept(rvalue, ctx) actual_lvalue_type = lvalue_node.type lvalue_node.type = rvalue_type lvalue_type, _ = self.node_type_from_base(lvalue_node.name, lvalue_node.info, lvalue) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 25565946158e..093924ab2e26 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3979,3 +3979,24 @@ def check(mapping: Mapping[str, _T]) -> None: reveal_type(ok1) # N: Revealed type is "Union[_T`-1, builtins.str]" ok2: Union[_T, str] = mapping.get("", "") [builtins fixtures/tuple.pyi] + +[case testNoCrashOnPartialTypeAsContext] +from typing import overload, TypeVar, Optional, Protocol + +T = TypeVar("T") +class DbManager(Protocol): + @overload + def get(self, key: str) -> Optional[T]: + pass + + @overload + def get(self, key: str, default: T) -> T: + pass + +class Foo: + def __init__(self, db: DbManager, bar: bool) -> None: + if bar: + self.qux = db.get("qux") + else: + self.qux = {} # E: Need type annotation for "qux" (hint: "qux: Dict[, ] = ...") +[builtins fixtures/dict.pyi] From c86480ce51e4bb6db21f4b3f0b3ec8833aafc8ce Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 1 Jun 2025 02:30:37 +0100 Subject: [PATCH 5/9] Tighten metaclass __call__ handling in protocols (#19191) Fixes https://github.com/python/mypy/issues/19184 This fixes an (edge-case) regression introduced in 1.16. Fix is straightforward, only ignore `__call__` if it comes from an _actual_ metaclass. --- mypy/constraints.py | 4 ++-- mypy/nodes.py | 4 ++-- mypy/typeops.py | 2 +- test-data/unit/check-protocols.test | 22 ++++++++++++++++++++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/mypy/constraints.py b/mypy/constraints.py index 8e7a30e05ffb..73b7ec7954a4 100644 --- a/mypy/constraints.py +++ b/mypy/constraints.py @@ -1066,8 +1066,8 @@ def infer_constraints_from_protocol_members( inst, erase_typevars(temp), ignore_pos_arg_names=True ): continue - # This exception matches the one in subtypes.py, see PR #14121 for context. - if member == "__call__" and instance.type.is_metaclass(): + # This exception matches the one in typeops.py, see PR #14121 for context. + if member == "__call__" and instance.type.is_metaclass(precise=True): continue res.extend(infer_constraints(temp, inst, self.direction)) if mypy.subtypes.IS_SETTABLE in mypy.subtypes.get_member_flags(member, protocol): diff --git a/mypy/nodes.py b/mypy/nodes.py index 2fb459142066..9a9403722a16 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -3371,11 +3371,11 @@ def calculate_metaclass_type(self) -> mypy.types.Instance | None: return c return None - def is_metaclass(self) -> bool: + def is_metaclass(self, *, precise: bool = False) -> bool: return ( self.has_base("builtins.type") or self.fullname == "abc.ABCMeta" - or self.fallback_to_any + or (self.fallback_to_any and not precise) ) def has_base(self, fullname: str) -> bool: diff --git a/mypy/typeops.py b/mypy/typeops.py index bcf946900563..3715081ae173 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -1257,7 +1257,7 @@ def named_type(fullname: str) -> Instance: return type_object_type(left.type, named_type) - if member == "__call__" and left.type.is_metaclass(): + if member == "__call__" and left.type.is_metaclass(precise=True): # Special case: we want to avoid falling back to metaclass __call__ # if constructor signature didn't match, this can cause many false negatives. return None diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 34e3f3e88080..56c3d72d23e2 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4460,3 +4460,25 @@ f2(a4) # E: Argument 1 to "f2" has incompatible type "A4"; expected "P2" \ # N: foo: expected "B1", got "str" \ # N: foo: expected setter type "C1", got "str" [builtins fixtures/property.pyi] + +[case testInferCallableProtoWithAnySubclass] +from typing import Any, Generic, Protocol, TypeVar + +T = TypeVar("T", covariant=True) + +Unknown: Any +class Mock(Unknown): + def __init__(self, **kwargs: Any) -> None: ... + def __call__(self, *args: Any, **kwargs: Any) -> Any: ... + +class Factory(Protocol[T]): + def __call__(self, **kwargs: Any) -> T: ... + + +class Test(Generic[T]): + def __init__(self, f: Factory[T]) -> None: + ... + +t = Test(Mock()) +reveal_type(t) # N: Revealed type is "__main__.Test[Any]" +[builtins fixtures/dict.pyi] From c20fd7838338cd65d6c7c6e252eda85996cfc98e Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 5 Jun 2025 15:09:36 +0100 Subject: [PATCH 6/9] Handle assignment of bound methods in class bodies (#19233) Fixes https://github.com/python/mypy/issues/18438 Fixes https://github.com/python/mypy/issues/19146 Surprisingly, a very small change is sufficient to replicate Python runtime behavior for all the important cases (see `checkmember.py`). I also replace the `bound_args` argument of `CallableType`, that was mostly unused, with a flag (as suggested by @JukkaL) and make sure it is properly set/preserved everywhere. --- mypy/checker.py | 2 +- mypy/checkexpr.py | 2 +- mypy/checkmember.py | 4 +-- mypy/fixup.py | 3 -- mypy/messages.py | 4 +-- mypy/server/astdiff.py | 1 + mypy/typeops.py | 3 +- mypy/types.py | 20 ++++++------- test-data/unit/check-classes.test | 2 +- test-data/unit/check-functions.test | 42 +++++++++++++++++++++++++++ test-data/unit/check-incremental.test | 24 +++++++++++++++ test-data/unit/fine-grained.test | 24 +++++++++++++++ 12 files changed, 110 insertions(+), 21 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index cb465b390aa6..5a5d19cc0006 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -2448,7 +2448,7 @@ def erase_override(t: Type) -> Type: if not is_subtype(original_arg_type, erase_override(override_arg_type)): context: Context = node if isinstance(node, FuncDef) and not node.is_property: - arg_node = node.arguments[i + len(override.bound_args)] + arg_node = node.arguments[i + override.bound()] if arg_node.line != -1: context = arg_node self.msg.argument_incompatible_with_supertype( diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index f375c14c5fc4..df0e9bdaedde 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -4970,7 +4970,7 @@ def apply_type_arguments_to_callable( tp.fallback, name="tuple", definition=tp.definition, - bound_args=tp.bound_args, + is_bound=tp.is_bound, ) self.msg.incompatible_type_application( min_arg_count, len(type_vars), len(args), ctx diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 4821c622cb0c..ea76a6cc304f 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -916,7 +916,7 @@ def analyze_var( bound_items = [] for ct in call_type.items if isinstance(call_type, UnionType) else [call_type]: p_ct = get_proper_type(ct) - if isinstance(p_ct, FunctionLike) and not p_ct.is_type_obj(): + if isinstance(p_ct, FunctionLike) and (not p_ct.bound() or var.is_property): item = expand_and_bind_callable(p_ct, var, itype, name, mx, is_trivial_self) else: item = expand_without_binding(ct, var, itype, original_itype, mx) @@ -1503,6 +1503,6 @@ def bind_self_fast(method: F, original_type: Type | None = None) -> F: arg_types=method.arg_types[1:], arg_kinds=method.arg_kinds[1:], arg_names=method.arg_names[1:], - bound_args=[original_type], + is_bound=True, ) return cast(F, res) diff --git a/mypy/fixup.py b/mypy/fixup.py index 8e7cd40544bf..0e9c186fd42a 100644 --- a/mypy/fixup.py +++ b/mypy/fixup.py @@ -271,9 +271,6 @@ def visit_callable_type(self, ct: CallableType) -> None: ct.ret_type.accept(self) for v in ct.variables: v.accept(self) - for arg in ct.bound_args: - if arg: - arg.accept(self) if ct.type_guard is not None: ct.type_guard.accept(self) if ct.type_is is not None: diff --git a/mypy/messages.py b/mypy/messages.py index 2e07d7f63498..47e7d20dde78 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -644,8 +644,8 @@ def incompatible_argument( callee_name = callable_name(callee) if callee_name is not None: name = callee_name - if callee.bound_args and callee.bound_args[0] is not None: - base = format_type(callee.bound_args[0], self.options) + if object_type is not None: + base = format_type(object_type, self.options) else: base = extract_type(name) diff --git a/mypy/server/astdiff.py b/mypy/server/astdiff.py index 1b0cc218ed16..16a0d882a8aa 100644 --- a/mypy/server/astdiff.py +++ b/mypy/server/astdiff.py @@ -460,6 +460,7 @@ def visit_callable_type(self, typ: CallableType) -> SnapshotItem: typ.is_type_obj(), typ.is_ellipsis_args, snapshot_types(typ.variables), + typ.is_bound, ) def normalize_callable_variables(self, typ: CallableType) -> CallableType: diff --git a/mypy/typeops.py b/mypy/typeops.py index 3715081ae173..1a29054513a2 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -185,6 +185,7 @@ def type_object_type(info: TypeInfo, named_type: Callable[[str], Instance]) -> P arg_kinds=[ARG_STAR, ARG_STAR2], arg_names=["_args", "_kwds"], ret_type=any_type, + is_bound=True, fallback=named_type("builtins.function"), ) return class_callable(sig, info, fallback, None, is_new=False) @@ -479,7 +480,7 @@ class B(A): pass arg_kinds=func.arg_kinds[1:], arg_names=func.arg_names[1:], variables=variables, - bound_args=[original_type], + is_bound=True, ) return cast(F, res) diff --git a/mypy/types.py b/mypy/types.py index 41a958ae93cc..7f67c303a929 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1597,6 +1597,9 @@ def with_name(self, name: str) -> FunctionLike: def get_name(self) -> str | None: pass + def bound(self) -> bool: + return bool(self.items) and self.items[0].is_bound + class FormalArgument(NamedTuple): name: str | None @@ -1826,8 +1829,7 @@ class CallableType(FunctionLike): # 'dict' and 'partial' for a `functools.partial` evaluation) "from_type_type", # Was this callable generated by analyzing Type[...] # instantiation? - "bound_args", # Bound type args, mostly unused but may be useful for - # tools that consume mypy ASTs + "is_bound", # Is this a bound method? "def_extras", # Information about original definition we want to serialize. # This is used for more detailed error messages. "type_guard", # T, if -> TypeGuard[T] (ret_type is bool in this case). @@ -1855,7 +1857,7 @@ def __init__( implicit: bool = False, special_sig: str | None = None, from_type_type: bool = False, - bound_args: Sequence[Type | None] = (), + is_bound: bool = False, def_extras: dict[str, Any] | None = None, type_guard: Type | None = None, type_is: Type | None = None, @@ -1888,9 +1890,7 @@ def __init__( self.from_type_type = from_type_type self.from_concatenate = from_concatenate self.imprecise_arg_kinds = imprecise_arg_kinds - if not bound_args: - bound_args = () - self.bound_args = bound_args + self.is_bound = is_bound if def_extras: self.def_extras = def_extras elif isinstance(definition, FuncDef): @@ -1927,7 +1927,7 @@ def copy_modified( implicit: Bogus[bool] = _dummy, special_sig: Bogus[str | None] = _dummy, from_type_type: Bogus[bool] = _dummy, - bound_args: Bogus[list[Type | None]] = _dummy, + is_bound: Bogus[bool] = _dummy, def_extras: Bogus[dict[str, Any]] = _dummy, type_guard: Bogus[Type | None] = _dummy, type_is: Bogus[Type | None] = _dummy, @@ -1952,7 +1952,7 @@ def copy_modified( implicit=implicit if implicit is not _dummy else self.implicit, special_sig=special_sig if special_sig is not _dummy else self.special_sig, from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type, - bound_args=bound_args if bound_args is not _dummy else self.bound_args, + is_bound=is_bound if is_bound is not _dummy else self.is_bound, def_extras=def_extras if def_extras is not _dummy else dict(self.def_extras), type_guard=type_guard if type_guard is not _dummy else self.type_guard, type_is=type_is if type_is is not _dummy else self.type_is, @@ -2277,7 +2277,7 @@ def serialize(self) -> JsonDict: "variables": [v.serialize() for v in self.variables], "is_ellipsis_args": self.is_ellipsis_args, "implicit": self.implicit, - "bound_args": [(None if t is None else t.serialize()) for t in self.bound_args], + "is_bound": self.is_bound, "def_extras": dict(self.def_extras), "type_guard": self.type_guard.serialize() if self.type_guard is not None else None, "type_is": (self.type_is.serialize() if self.type_is is not None else None), @@ -2300,7 +2300,7 @@ def deserialize(cls, data: JsonDict) -> CallableType: variables=[cast(TypeVarLikeType, deserialize_type(v)) for v in data["variables"]], is_ellipsis_args=data["is_ellipsis_args"], implicit=data["implicit"], - bound_args=[(None if t is None else deserialize_type(t)) for t in data["bound_args"]], + is_bound=data["is_bound"], def_extras=data["def_extras"], type_guard=( deserialize_type(data["type_guard"]) if data["type_guard"] is not None else None diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 59204cc31c43..036c8e21c310 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -4292,7 +4292,7 @@ int.__eq__(3, 4) [builtins fixtures/args.pyi] [out] main:33: error: Too few arguments for "__eq__" of "int" -main:33: error: Unsupported operand types for == ("int" and "Type[int]") +main:33: error: Unsupported operand types for == ("Type[int]" and "Type[int]") [case testDupBaseClasses] class A: diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index ac93c6c20354..35d76a65ccc3 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3599,3 +3599,45 @@ class Bar(Foo): def foo(self, value: Union[int, str]) -> Union[int, str]: return super().foo(value) # E: Call to abstract method "foo" of "Foo" with trivial body via super() is unsafe + +[case testBoundMethodsAssignedInClassBody] +from typing import Callable + +class A: + def f(self, x: int) -> str: + pass + @classmethod + def g(cls, x: int) -> str: + pass + @staticmethod + def h(x: int) -> str: + pass + attr: Callable[[int], str] + +class C: + x1 = A.f + x2 = A.g + x3 = A().f + x4 = A().g + x5 = A.h + x6 = A().h + x7 = A().attr + +reveal_type(C.x1) # N: Revealed type is "def (self: __main__.A, x: builtins.int) -> builtins.str" +reveal_type(C.x2) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x3) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x4) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x5) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x6) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C.x7) # N: Revealed type is "def (builtins.int) -> builtins.str" + +reveal_type(C().x1) # E: Invalid self argument "C" to attribute function "x1" with type "Callable[[A, int], str]" \ + # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x2) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x3) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x4) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x5) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x6) # N: Revealed type is "def (x: builtins.int) -> builtins.str" +reveal_type(C().x7) # E: Invalid self argument "C" to attribute function "x7" with type "Callable[[int], str]" \ + # N: Revealed type is "def () -> builtins.str" +[builtins fixtures/classmethod.pyi] diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 9d5902246ae5..fabd7e294d9a 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -6862,3 +6862,27 @@ if int(): [out] [out2] main:6: error: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testMethodMakeBoundIncremental] +from a import A +a = A() +a.f() +[file a.py] +class B: + def f(self, s: A) -> int: ... + +def f(s: A) -> int: ... + +class A: + f = f +[file a.py.2] +class B: + def f(self, s: A) -> int: ... + +def f(s: A) -> int: ... + +class A: + f = B().f +[out] +[out2] +main:3: error: Too few arguments diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index df244b3135e9..eb07f37a8671 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -11217,3 +11217,27 @@ class A: [out] == main:3: error: Property "f" defined in "A" is read-only + +[case testMethodMakeBoundFineGrained] +from a import A +a = A() +a.f() +[file a.py] +class B: + def f(self, s: A) -> int: ... + +def f(s: A) -> int: ... + +class A: + f = f +[file a.py.2] +class B: + def f(self, s: A) -> int: ... + +def f(s: A) -> int: ... + +class A: + f = B().f +[out] +== +main:3: error: Too few arguments From 9fb5ff66c51bd971d7a6b1260cc0ec9f1b82cc06 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sat, 7 Jun 2025 23:59:23 +0100 Subject: [PATCH 7/9] Fix properties with setters after deleters (#19248) Fixes https://github.com/python/mypy/issues/19224 Note we must add an additional attribute on `OverloadedFuncDef` since decorator expressions are not serialized. --- mypy/checker.py | 10 ++++------ mypy/checkmember.py | 4 ++-- mypy/nodes.py | 24 +++++++++++++++++++++++- mypy/semanal.py | 1 + test-data/unit/check-classes.test | 20 ++++++++++++++++++++ 5 files changed, 50 insertions(+), 9 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 5a5d19cc0006..dc3d670dd73e 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -675,11 +675,9 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: assert isinstance(defn.items[0], Decorator) self.visit_decorator(defn.items[0]) if defn.items[0].var.is_settable_property: - # TODO: here and elsewhere we assume setter immediately follows getter. - assert isinstance(defn.items[1], Decorator) # Perform a reduced visit just to infer the actual setter type. - self.visit_decorator_inner(defn.items[1], skip_first_item=True) - setter_type = defn.items[1].var.type + self.visit_decorator_inner(defn.setter, skip_first_item=True) + setter_type = defn.setter.var.type # Check if the setter can accept two positional arguments. any_type = AnyType(TypeOfAny.special_form) fallback_setter_type = CallableType( @@ -690,7 +688,7 @@ def _visit_overloaded_func_def(self, defn: OverloadedFuncDef) -> None: fallback=self.named_type("builtins.function"), ) if setter_type and not is_subtype(setter_type, fallback_setter_type): - self.fail("Invalid property setter signature", defn.items[1].func) + self.fail("Invalid property setter signature", defn.setter.func) setter_type = self.extract_callable_type(setter_type, defn) if not isinstance(setter_type, CallableType) or len(setter_type.arg_types) != 2: # TODO: keep precise type for callables with tricky but valid signatures. @@ -2149,7 +2147,7 @@ def check_setter_type_override(self, defn: OverloadedFuncDef, base: TypeInfo) -> assert typ is not None and original_type is not None if not is_subtype(original_type, typ): - self.msg.incompatible_setter_override(defn.items[1], typ, original_type, base) + self.msg.incompatible_setter_override(defn.setter, typ, original_type, base) def check_method_override_for_base_with_name( self, defn: FuncDef | OverloadedFuncDef | Decorator, name: str, base: TypeInfo diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ea76a6cc304f..596eccab09a7 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -341,8 +341,8 @@ def analyze_instance_member_access( assert isinstance(method, OverloadedFuncDef) getter = method.items[0] assert isinstance(getter, Decorator) - if mx.is_lvalue and (len(items := method.items) > 1): - mx.chk.warn_deprecated(items[1], mx.context) + if mx.is_lvalue and getter.var.is_settable_property: + mx.chk.warn_deprecated(method.setter, mx.context) return analyze_var(name, getter.var, typ, mx) if mx.is_lvalue and not mx.suppress_errors: diff --git a/mypy/nodes.py b/mypy/nodes.py index 9a9403722a16..0d4c96ac5793 100644 --- a/mypy/nodes.py +++ b/mypy/nodes.py @@ -550,12 +550,20 @@ class OverloadedFuncDef(FuncBase, SymbolNode, Statement): Overloaded variants must be consecutive in the source file. """ - __slots__ = ("items", "unanalyzed_items", "impl", "deprecated", "_is_trivial_self") + __slots__ = ( + "items", + "unanalyzed_items", + "impl", + "deprecated", + "setter_index", + "_is_trivial_self", + ) items: list[OverloadPart] unanalyzed_items: list[OverloadPart] impl: OverloadPart | None deprecated: str | None + setter_index: int | None def __init__(self, items: list[OverloadPart]) -> None: super().__init__() @@ -563,6 +571,7 @@ def __init__(self, items: list[OverloadPart]) -> None: self.unanalyzed_items = items.copy() self.impl = None self.deprecated = None + self.setter_index = None self._is_trivial_self: bool | None = None if items: # TODO: figure out how to reliably set end position (we don't know the impl here). @@ -598,6 +607,17 @@ def is_trivial_self(self) -> bool: self._is_trivial_self = True return True + @property + def setter(self) -> Decorator: + # Do some consistency checks first. + first_item = self.items[0] + assert isinstance(first_item, Decorator) + assert first_item.var.is_settable_property + assert self.setter_index is not None + item = self.items[self.setter_index] + assert isinstance(item, Decorator) + return item + def accept(self, visitor: StatementVisitor[T]) -> T: return visitor.visit_overloaded_func_def(self) @@ -610,6 +630,7 @@ def serialize(self) -> JsonDict: "impl": None if self.impl is None else self.impl.serialize(), "flags": get_flags(self, FUNCBASE_FLAGS), "deprecated": self.deprecated, + "setter_index": self.setter_index, } @classmethod @@ -630,6 +651,7 @@ def deserialize(cls, data: JsonDict) -> OverloadedFuncDef: res._fullname = data["fullname"] set_flags(res, data["flags"]) res.deprecated = data["deprecated"] + res.setter_index = data["setter_index"] # NOTE: res.info will be set in the fixup phase. return res diff --git a/mypy/semanal.py b/mypy/semanal.py index 89bb5ab97c2a..bc64e7e7ca53 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1558,6 +1558,7 @@ def analyze_property_with_multi_part_definition( ) assert isinstance(setter_func_type, CallableType) bare_setter_type = setter_func_type + defn.setter_index = i + 1 if first_node.name == "deleter": item.func.abstract_status = first_item.func.abstract_status for other_node in item.decorators[1:]: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 036c8e21c310..10e15139218f 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -8739,3 +8739,23 @@ class NoopPowerResource: def hardware_type(self) -> None: # E: Invalid property setter signature self.hardware_type = None # Note: intentionally recursive [builtins fixtures/property.pyi] + +[case testPropertyAllowsDeleterBeforeSetter] +class C: + @property + def foo(self) -> str: ... + @foo.deleter + def foo(self) -> None: ... + @foo.setter + def foo(self, val: int) -> None: ... + + @property + def bar(self) -> int: ... + @bar.deleter + def bar(self) -> None: ... + @bar.setter + def bar(self, value: int, val: int) -> None: ... # E: Invalid property setter signature + +C().foo = "no" # E: Incompatible types in assignment (expression has type "str", variable has type "int") +C().bar = "fine" +[builtins fixtures/property.pyi] From e253eded9c887630f3f5404c4b9f73f13570476a Mon Sep 17 00:00:00 2001 From: A5rocks Date: Wed, 11 Jun 2025 13:57:31 +0900 Subject: [PATCH 8/9] Single underscore is not a sunder (#19273) Fixes https://github.com/python/mypy/issues/19271. --- mypy/util.py | 2 +- test-data/unit/check-enum.test | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/mypy/util.py b/mypy/util.py index d3f49f74bbae..d7ff2a367fa2 100644 --- a/mypy/util.py +++ b/mypy/util.py @@ -66,7 +66,7 @@ def is_dunder(name: str, exclude_special: bool = False) -> bool: def is_sunder(name: str) -> bool: - return not is_dunder(name) and name.startswith("_") and name.endswith("_") + return not is_dunder(name) and name.startswith("_") and name.endswith("_") and name != "_" def split_module_names(mod_name: str) -> list[str]: diff --git a/test-data/unit/check-enum.test b/test-data/unit/check-enum.test index cc9048db18dc..3001bc9a69cb 100644 --- a/test-data/unit/check-enum.test +++ b/test-data/unit/check-enum.test @@ -2524,3 +2524,18 @@ class Base: self.o = Enum("o", names) # E: Enum type as attribute is not supported \ # E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members [builtins fixtures/tuple.pyi] + +[case testSingleUnderscoreNameEnumMember] +# flags: --warn-unreachable + +# https://github.com/python/mypy/issues/19271 +from enum import Enum + +class Things(Enum): + _ = "under score" + +def check(thing: Things) -> None: + if thing is Things._: + return None + return None # E: Statement is unreachable +[builtins fixtures/enum.pyi] From 68b8fa097d080c92d30a429bc74de8acd56caf85 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 11 Jun 2025 16:25:56 +0100 Subject: [PATCH 9/9] Bump version to 1.16.1 --- mypy/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/version.py b/mypy/version.py index d2d42a386dec..a003b2b70558 100644 --- a/mypy/version.py +++ b/mypy/version.py @@ -8,7 +8,7 @@ # - Release versions have the form "1.2.3". # - Dev versions have the form "1.2.3+dev" (PLUS sign to conform to PEP 440). # - Before 1.0 we had the form "0.NNN". -__version__ = "1.16.1+dev" +__version__ = "1.16.1" base_version = __version__ mypy_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy