Skip to content

Fix descriptor overload selection #18868

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
erase_to_bound,
freeze_all_type_vars,
function_type,
get_all_type_vars,
get_type_vars,
make_simplified_union,
supported_self_type,
Expand Down Expand Up @@ -604,7 +605,10 @@ def analyze_member_var_access(
setattr_meth = info.get_method("__setattr__")
if setattr_meth and setattr_meth.info.fullname != "builtins.object":
bound_type = analyze_decorator_or_funcbase_access(
defn=setattr_meth, itype=itype, name=name, mx=mx.copy_modified(is_lvalue=False)
defn=setattr_meth,
itype=itype,
name="__setattr__",
mx=mx.copy_modified(is_lvalue=False),
)
typ = map_instance_to_supertype(itype, setattr_meth.info)
setattr_type = get_proper_type(expand_type_by_instance(bound_type, typ))
Expand Down Expand Up @@ -1016,7 +1020,16 @@ def f(self: S) -> T: ...
selfarg = get_proper_type(item.arg_types[0])
# This level of erasure matches the one in checker.check_func_def(),
# better keep these two checks consistent.
if subtypes.is_subtype(dispatched_arg_type, erase_typevars(erase_to_bound(selfarg))):
if subtypes.is_subtype(
dispatched_arg_type,
erase_typevars(erase_to_bound(selfarg)),
# This is to work around the fact that erased ParamSpec and TypeVarTuple
# callables are not always compatible with non-erased ones both ways.
always_covariant=any(
not isinstance(tv, TypeVarType) for tv in get_all_type_vars(selfarg)
),
ignore_pos_arg_names=True,
):
new_items.append(item)
elif isinstance(selfarg, ParamSpecType):
# TODO: This is not always right. What's the most reasonable thing to do here?
Expand Down Expand Up @@ -1149,6 +1162,7 @@ def analyze_class_attribute_access(
def_vars = set(node.node.info.defn.type_vars)
if not node.node.is_classvar and node.node.info.self_type:
def_vars.add(node.node.info.self_type)
# TODO: should we include ParamSpec etc. here (i.e. use get_all_type_vars)?
typ_vars = set(get_type_vars(t))
if def_vars & typ_vars:
# Exception: access on Type[...], including first argument of class methods is OK.
Expand Down Expand Up @@ -1390,6 +1404,6 @@ def analyze_decorator_or_funcbase_access(
"""
if isinstance(defn, Decorator):
return analyze_var(name, defn.var, itype, mx)
return bind_self(
function_type(defn, mx.chk.named_type("builtins.function")), original_type=mx.self_type
)
typ = function_type(defn, mx.chk.named_type("builtins.function"))
typ = check_self_arg(typ, mx.self_type, defn.is_class, mx.context, name, mx.msg)
return bind_self(typ, original_type=mx.self_type, is_classmethod=defn.is_class)
34 changes: 33 additions & 1 deletion test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -3135,7 +3135,8 @@ from typing import Any
class Test:
def __setattr__() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? # E: Invalid signature "Callable[[], None]" for "__setattr__"
t = Test()
t.crash = 'test' # E: "Test" has no attribute "crash"
t.crash = 'test' # E: Attribute function "__setattr__" with type "Callable[[], None]" does not accept self argument \
# E: "Test" has no attribute "crash"

class A:
def __setattr__(self): ... # E: Invalid signature "Callable[[A], Any]" for "__setattr__"
Expand Down Expand Up @@ -8648,3 +8649,34 @@ class C(B):
def meth(self) -> None:
def cb() -> None:
self.x: int = 1 # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "str")

[case testOverloadedDescriptorSelected]
from typing import Generic, TypeVar, Any, overload

T_co = TypeVar("T_co", covariant=True)
class Field(Generic[T_co]):
@overload
def __get__(self: Field[bool], instance: None, owner: Any) -> BoolField: ...
@overload
def __get__(self: Field[int], instance: None, owner: Any) -> NumField: ...
@overload
def __get__(self: Field[Any], instance: None, owner: Any) -> AnyField[T_co]: ...
@overload
def __get__(self, instance: Any, owner: Any) -> T_co: ...

def __get__(self, instance: Any, owner: Any) -> Any:
pass

class BoolField(Field[bool]): ...
class NumField(Field[int]): ...
class AnyField(Field[T_co]): ...
class Custom: ...

class Fields:
bool_f: Field[bool]
int_f: Field[int]
custom_f: Field[Custom]

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]"