Skip to content

Commit 4762fa8

Browse files
committed
Merge remote-tracking branch 'upstream/master' into method-subtype-checkmember
2 parents fcf4c07 + d6cb14f commit 4762fa8

File tree

11 files changed

+236
-62
lines changed

11 files changed

+236
-62
lines changed

mypy/checkexpr.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,7 +2357,8 @@ def check_argument_count(
23572357

23582358
# Check for too many or few values for formals.
23592359
for i, kind in enumerate(callee.arg_kinds):
2360-
if kind.is_required() and not formal_to_actual[i] and not is_unexpected_arg_error:
2360+
mapped_args = formal_to_actual[i]
2361+
if kind.is_required() and not mapped_args and not is_unexpected_arg_error:
23612362
# No actual for a mandatory formal
23622363
if kind.is_positional():
23632364
self.msg.too_few_arguments(callee, context, actual_names)
@@ -2368,28 +2369,36 @@ def check_argument_count(
23682369
self.msg.missing_named_argument(callee, context, argname)
23692370
ok = False
23702371
elif not kind.is_star() and is_duplicate_mapping(
2371-
formal_to_actual[i], actual_types, actual_kinds
2372+
mapped_args, actual_types, actual_kinds
23722373
):
23732374
if self.chk.in_checked_function() or isinstance(
2374-
get_proper_type(actual_types[formal_to_actual[i][0]]), TupleType
2375+
get_proper_type(actual_types[mapped_args[0]]), TupleType
23752376
):
23762377
self.msg.duplicate_argument_value(callee, i, context)
23772378
ok = False
23782379
elif (
23792380
kind.is_named()
2380-
and formal_to_actual[i]
2381-
and actual_kinds[formal_to_actual[i][0]] not in [nodes.ARG_NAMED, nodes.ARG_STAR2]
2381+
and mapped_args
2382+
and actual_kinds[mapped_args[0]] not in [nodes.ARG_NAMED, nodes.ARG_STAR2]
23822383
):
23832384
# Positional argument when expecting a keyword argument.
23842385
self.msg.too_many_positional_arguments(callee, context)
23852386
ok = False
2386-
elif (
2387-
callee.param_spec() is not None
2388-
and not formal_to_actual[i]
2389-
and callee.special_sig != "partial"
2390-
):
2391-
self.msg.too_few_arguments(callee, context, actual_names)
2392-
ok = False
2387+
elif callee.param_spec() is not None:
2388+
if not mapped_args and callee.special_sig != "partial":
2389+
self.msg.too_few_arguments(callee, context, actual_names)
2390+
ok = False
2391+
elif len(mapped_args) > 1:
2392+
paramspec_entries = sum(
2393+
isinstance(get_proper_type(actual_types[k]), ParamSpecType)
2394+
for k in mapped_args
2395+
)
2396+
if actual_kinds[mapped_args[0]] == nodes.ARG_STAR and paramspec_entries > 1:
2397+
self.msg.fail("ParamSpec.args should only be passed once", context)
2398+
ok = False
2399+
if actual_kinds[mapped_args[0]] == nodes.ARG_STAR2 and paramspec_entries > 1:
2400+
self.msg.fail("ParamSpec.kwargs should only be passed once", context)
2401+
ok = False
23932402
return ok
23942403

23952404
def check_for_extra_actual_arguments(

mypy/checkmember.py

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
erase_to_bound,
4343
freeze_all_type_vars,
4444
function_type,
45+
get_all_type_vars,
4546
get_type_vars,
4647
make_simplified_union,
4748
supported_self_type,
@@ -606,7 +607,10 @@ def analyze_member_var_access(
606607
setattr_meth = info.get_method("__setattr__")
607608
if setattr_meth and setattr_meth.info.fullname != "builtins.object":
608609
bound_type = analyze_decorator_or_funcbase_access(
609-
defn=setattr_meth, itype=itype, name=name, mx=mx.copy_modified(is_lvalue=False)
610+
defn=setattr_meth,
611+
itype=itype,
612+
name="__setattr__",
613+
mx=mx.copy_modified(is_lvalue=False),
610614
)
611615
typ = map_instance_to_supertype(itype, setattr_meth.info)
612616
setattr_type = get_proper_type(expand_type_by_instance(bound_type, typ))
@@ -872,15 +876,13 @@ def analyze_var(
872876
mx.msg.read_only_property(name, itype.type, mx.context)
873877
if var.is_classvar:
874878
mx.msg.cant_assign_to_classvar(name, mx.context)
875-
t = freshen_all_functions_type_vars(typ)
876-
t = expand_self_type_if_needed(t, mx, var, original_itype)
877-
t = expand_type_by_instance(t, itype)
878-
freeze_all_type_vars(t)
879-
result = t
880-
typ = get_proper_type(typ)
879+
# This is the most common case for variables, so start with this.
880+
result = expand_without_binding(typ, var, itype, original_itype, mx)
881881

882+
# A non-None value indicates that we should actually bind self for this variable.
882883
call_type: ProperType | None = None
883884
if var.is_initialized_in_class and (not is_instance_var(var) or mx.is_operator):
885+
typ = get_proper_type(typ)
884886
if isinstance(typ, FunctionLike) and not typ.is_type_obj():
885887
call_type = typ
886888
elif var.is_property:
@@ -890,37 +892,23 @@ def analyze_var(
890892
else:
891893
call_type = typ
892894

895+
# Bound variables with callable types are treated like methods
896+
# (these are usually method aliases like __rmul__ = __mul__).
893897
if isinstance(call_type, FunctionLike) and not call_type.is_type_obj():
894-
if mx.is_lvalue and not mx.suppress_errors:
895-
if var.is_property and not var.is_settable_property:
896-
mx.msg.read_only_property(name, itype.type, mx.context)
897-
elif not var.is_property:
898-
mx.msg.cant_assign_to_method(mx.context)
899-
900-
if not var.is_staticmethod:
901-
# Class-level function objects and classmethods become bound methods:
902-
# the former to the instance, the latter to the class.
903-
functype: FunctionLike = call_type
904-
signature = freshen_all_functions_type_vars(functype)
905-
bound = get_proper_type(expand_self_type(var, signature, mx.original_type))
906-
assert isinstance(bound, FunctionLike)
907-
signature = bound
908-
signature = check_self_arg(
909-
signature, mx.self_type, var.is_classmethod, mx.context, name, mx.msg
910-
)
911-
signature = bind_self(signature, mx.self_type, var.is_classmethod)
912-
expanded_signature = expand_type_by_instance(signature, itype)
913-
freeze_all_type_vars(expanded_signature)
914-
if var.is_property:
915-
# A property cannot have an overloaded type => the cast is fine.
916-
assert isinstance(expanded_signature, CallableType)
917-
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
918-
# TODO: use check_call() to infer better type, same as for __set__().
919-
result = expanded_signature.arg_types[0]
920-
else:
921-
result = expanded_signature.ret_type
898+
if mx.is_lvalue and not var.is_property and not mx.suppress_errors:
899+
mx.msg.cant_assign_to_method(mx.context)
900+
901+
# Bind the self type for each callable component (when needed).
902+
if call_type and not var.is_staticmethod:
903+
bound_items = []
904+
for ct in call_type.items if isinstance(call_type, UnionType) else [call_type]:
905+
p_ct = get_proper_type(ct)
906+
if isinstance(p_ct, FunctionLike) and not p_ct.is_type_obj():
907+
item = expand_and_bind_callable(p_ct, var, itype, name, mx)
922908
else:
923-
result = expanded_signature
909+
item = expand_without_binding(ct, var, itype, original_itype, mx)
910+
bound_items.append(item)
911+
result = UnionType.make_union(bound_items)
924912
else:
925913
if not var.is_ready and not mx.no_deferral:
926914
mx.not_ready_callback(var.name, mx.context)
@@ -939,6 +927,37 @@ def analyze_var(
939927
return result
940928

941929

930+
def expand_without_binding(
931+
typ: Type, var: Var, itype: Instance, original_itype: Instance, mx: MemberContext
932+
) -> Type:
933+
typ = freshen_all_functions_type_vars(typ)
934+
typ = expand_self_type_if_needed(typ, mx, var, original_itype)
935+
expanded = expand_type_by_instance(typ, itype)
936+
freeze_all_type_vars(expanded)
937+
return expanded
938+
939+
940+
def expand_and_bind_callable(
941+
functype: FunctionLike, var: Var, itype: Instance, name: str, mx: MemberContext
942+
) -> Type:
943+
functype = freshen_all_functions_type_vars(functype)
944+
typ = get_proper_type(expand_self_type(var, functype, mx.original_type))
945+
assert isinstance(typ, FunctionLike)
946+
typ = check_self_arg(typ, mx.self_type, var.is_classmethod, mx.context, name, mx.msg)
947+
typ = bind_self(typ, mx.self_type, var.is_classmethod)
948+
expanded = expand_type_by_instance(typ, itype)
949+
freeze_all_type_vars(expanded)
950+
if not var.is_property:
951+
return expanded
952+
# TODO: a decorated property can result in Overloaded here.
953+
assert isinstance(expanded, CallableType)
954+
if var.is_settable_property and mx.is_lvalue and var.setter_type is not None:
955+
# TODO: use check_call() to infer better type, same as for __set__().
956+
return expanded.arg_types[0]
957+
else:
958+
return expanded.ret_type
959+
960+
942961
def expand_self_type_if_needed(
943962
t: Type, mx: MemberContext, var: Var, itype: Instance, is_class: bool = False
944963
) -> Type:
@@ -1018,7 +1037,16 @@ def f(self: S) -> T: ...
10181037
selfarg = get_proper_type(item.arg_types[0])
10191038
# This level of erasure matches the one in checker.check_func_def(),
10201039
# better keep these two checks consistent.
1021-
if subtypes.is_subtype(dispatched_arg_type, erase_typevars(erase_to_bound(selfarg))):
1040+
if subtypes.is_subtype(
1041+
dispatched_arg_type,
1042+
erase_typevars(erase_to_bound(selfarg)),
1043+
# This is to work around the fact that erased ParamSpec and TypeVarTuple
1044+
# callables are not always compatible with non-erased ones both ways.
1045+
always_covariant=any(
1046+
not isinstance(tv, TypeVarType) for tv in get_all_type_vars(selfarg)
1047+
),
1048+
ignore_pos_arg_names=True,
1049+
):
10221050
new_items.append(item)
10231051
elif isinstance(selfarg, ParamSpecType):
10241052
# TODO: This is not always right. What's the most reasonable thing to do here?
@@ -1151,6 +1179,7 @@ def analyze_class_attribute_access(
11511179
def_vars = set(node.node.info.defn.type_vars)
11521180
if not node.node.is_classvar and node.node.info.self_type:
11531181
def_vars.add(node.node.info.self_type)
1182+
# TODO: should we include ParamSpec etc. here (i.e. use get_all_type_vars)?
11541183
typ_vars = set(get_type_vars(t))
11551184
if def_vars & typ_vars:
11561185
# Exception: access on Type[...], including first argument of class methods is OK.
@@ -1392,6 +1421,6 @@ def analyze_decorator_or_funcbase_access(
13921421
"""
13931422
if isinstance(defn, Decorator):
13941423
return analyze_var(name, defn.var, itype, mx)
1395-
return bind_self(
1396-
function_type(defn, mx.chk.named_type("builtins.function")), original_type=mx.self_type
1397-
)
1424+
typ = function_type(defn, mx.chk.named_type("builtins.function"))
1425+
typ = check_self_arg(typ, mx.self_type, defn.is_class, mx.context, name, mx.msg)
1426+
return bind_self(typ, original_type=mx.self_type, is_classmethod=defn.is_class)

mypy/semanal.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3464,8 +3464,9 @@ def record_special_form_lvalue(self, s: AssignmentStmt) -> None:
34643464
def analyze_enum_assign(self, s: AssignmentStmt) -> bool:
34653465
"""Check if s defines an Enum."""
34663466
if isinstance(s.rvalue, CallExpr) and isinstance(s.rvalue.analyzed, EnumCallExpr):
3467-
# Already analyzed enum -- nothing to do here.
3468-
return True
3467+
# This is an analyzed enum definition.
3468+
# It is valid iff it can be stored correctly, failures were already reported.
3469+
return self._is_single_name_assignment(s)
34693470
return self.enum_call_analyzer.process_enum_call(s, self.is_func_scope())
34703471

34713472
def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
@@ -3474,7 +3475,9 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
34743475
if s.rvalue.analyzed.info.tuple_type and not has_placeholder(
34753476
s.rvalue.analyzed.info.tuple_type
34763477
):
3477-
return True # This is a valid and analyzed named tuple definition, nothing to do here.
3478+
# This is an analyzed named tuple definition.
3479+
# It is valid iff it can be stored correctly, failures were already reported.
3480+
return self._is_single_name_assignment(s)
34783481
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
34793482
return False
34803483
lvalue = s.lvalues[0]
@@ -3515,8 +3518,9 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
35153518
if s.rvalue.analyzed.info.typeddict_type and not has_placeholder(
35163519
s.rvalue.analyzed.info.typeddict_type
35173520
):
3518-
# This is a valid and analyzed typed dict definition, nothing to do here.
3519-
return True
3521+
# This is an analyzed typed dict definition.
3522+
# It is valid iff it can be stored correctly, failures were already reported.
3523+
return self._is_single_name_assignment(s)
35203524
if len(s.lvalues) != 1 or not isinstance(s.lvalues[0], (NameExpr, MemberExpr)):
35213525
return False
35223526
lvalue = s.lvalues[0]
@@ -3540,6 +3544,9 @@ def analyze_typeddict_assign(self, s: AssignmentStmt) -> bool:
35403544
self.setup_alias_type_vars(defn)
35413545
return True
35423546

3547+
def _is_single_name_assignment(self, s: AssignmentStmt) -> bool:
3548+
return len(s.lvalues) == 1 and isinstance(s.lvalues[0], NameExpr)
3549+
35433550
def analyze_lvalues(self, s: AssignmentStmt) -> None:
35443551
# We cannot use s.type, because analyze_simple_literal_type() will set it.
35453552
explicit = s.unanalyzed_type is not None

runtests.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@
6565
"-p",
6666
"mypyc",
6767
],
68+
# Type check setup.py as well
69+
"self-packaging": [
70+
executable,
71+
"-m",
72+
"mypy",
73+
"--config-file",
74+
"mypy_self_check.ini",
75+
"setup.py",
76+
],
6877
# Lint
6978
"lint": ["pre-commit", "run", "--all-files"],
7079
# Fast test cases only (this is the bulk of the test suite)

setup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def is_list_of_setuptools_extension(items: list[Any]) -> TypeGuard[list[Extensio
3131
return all(isinstance(item, Extension) for item in items)
3232

3333

34-
def find_package_data(base, globs, root="mypy"):
34+
def find_package_data(base: str, globs: list[str], root: str = "mypy") -> list[str]:
3535
"""Find all interesting data files, for setup(package_data=)
3636
3737
Arguments:
@@ -52,13 +52,13 @@ def find_package_data(base, globs, root="mypy"):
5252

5353

5454
class CustomPythonBuild(build_py):
55-
def pin_version(self):
55+
def pin_version(self) -> None:
5656
path = os.path.join(self.build_lib, "mypy")
5757
self.mkpath(path)
5858
with open(os.path.join(path, "version.py"), "w") as stream:
5959
stream.write(f'__version__ = "{version}"\n')
6060

61-
def run(self):
61+
def run(self) -> None:
6262
self.execute(self.pin_version, ())
6363
build_py.run(self)
6464

@@ -153,10 +153,10 @@ def run(self):
153153
# our Appveyor builds run out of memory sometimes.
154154
multi_file=sys.platform == "win32" or force_multifile,
155155
)
156-
assert is_list_of_setuptools_extension(ext_modules), "Expected mypycify to use setuptools"
157156

158157
else:
159158
ext_modules = []
160159

160+
assert is_list_of_setuptools_extension(ext_modules), "Expected mypycify to use setuptools"
161161

162162
setup(version=version, ext_modules=ext_modules, cmdclass=cmdclass)

test-data/unit/check-classes.test

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3135,7 +3135,8 @@ from typing import Any
31353135
class Test:
31363136
def __setattr__() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? # E: Invalid signature "Callable[[], None]" for "__setattr__"
31373137
t = Test()
3138-
t.crash = 'test' # E: "Test" has no attribute "crash"
3138+
t.crash = 'test' # E: Attribute function "__setattr__" with type "Callable[[], None]" does not accept self argument \
3139+
# E: "Test" has no attribute "crash"
31393140

31403141
class A:
31413142
def __setattr__(self): ... # E: Invalid signature "Callable[[A], Any]" for "__setattr__"
@@ -8650,3 +8651,34 @@ class C(B):
86508651
def meth(self) -> None:
86518652
def cb() -> None:
86528653
self.x: int = 1 # E: Incompatible types in assignment (expression has type "int", base class "B" defined the type as "str")
8654+
8655+
[case testOverloadedDescriptorSelected]
8656+
from typing import Generic, TypeVar, Any, overload
8657+
8658+
T_co = TypeVar("T_co", covariant=True)
8659+
class Field(Generic[T_co]):
8660+
@overload
8661+
def __get__(self: Field[bool], instance: None, owner: Any) -> BoolField: ...
8662+
@overload
8663+
def __get__(self: Field[int], instance: None, owner: Any) -> NumField: ...
8664+
@overload
8665+
def __get__(self: Field[Any], instance: None, owner: Any) -> AnyField[T_co]: ...
8666+
@overload
8667+
def __get__(self, instance: Any, owner: Any) -> T_co: ...
8668+
8669+
def __get__(self, instance: Any, owner: Any) -> Any:
8670+
pass
8671+
8672+
class BoolField(Field[bool]): ...
8673+
class NumField(Field[int]): ...
8674+
class AnyField(Field[T_co]): ...
8675+
class Custom: ...
8676+
8677+
class Fields:
8678+
bool_f: Field[bool]
8679+
int_f: Field[int]
8680+
custom_f: Field[Custom]
8681+
8682+
reveal_type(Fields.bool_f) # N: Revealed type is "__main__.BoolField"
8683+
reveal_type(Fields.int_f) # N: Revealed type is "__main__.NumField"
8684+
reveal_type(Fields.custom_f) # N: Revealed type is "__main__.AnyField[__main__.Custom]"

test-data/unit/check-classvar.test

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,12 @@ class C:
334334
c:C
335335
c.foo() # E: Too few arguments \
336336
# N: "foo" is considered instance variable, to make it class variable use ClassVar[...]
337+
338+
[case testClassVarUnionBoundOnInstance]
339+
from typing import Union, Callable, ClassVar
340+
341+
class C:
342+
def f(self) -> int: ...
343+
g: ClassVar[Union[Callable[[C], int], int]] = f
344+
345+
reveal_type(C().g) # N: Revealed type is "Union[def () -> builtins.int, builtins.int]"

test-data/unit/check-enum.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,3 +2512,15 @@ def list_vals(e: Type[T]) -> list[T]:
25122512

25132513
reveal_type(list_vals(Choices)) # N: Revealed type is "builtins.list[__main__.Choices]"
25142514
[builtins fixtures/enum.pyi]
2515+
2516+
[case testEnumAsClassMemberNoCrash]
2517+
# https://github.com/python/mypy/issues/18736
2518+
from enum import Enum
2519+
2520+
class Base:
2521+
def __init__(self, namespace: tuple[str, ...]) -> None:
2522+
# Not a bug: trigger defer
2523+
names = [name for name in namespace if fail] # E: Name "fail" is not defined
2524+
self.o = Enum("o", names) # E: Enum type as attribute is not supported \
2525+
# E: Second argument of Enum() must be string, tuple, list or dict literal for mypy to determine Enum members
2526+
[builtins fixtures/tuple.pyi]

0 commit comments

Comments
 (0)