Skip to content

Commit ad76e16

Browse files
authored
Move self argument checks to a later phase - after decorator application, if any (#19490)
Fixes #19392. Fixes #18989. Fixes #18720. Fixes #13434 (correct support for `staticmethod` wrappers, but not for equivalent `classmethod`s reported in #18968). Deferring this check in presence of decorators allows decorators that perform non-trivial transformations (such as making methods from non-methods and vice versa).
1 parent bd94bcb commit ad76e16

File tree

5 files changed

+300
-59
lines changed

5 files changed

+300
-59
lines changed

mypy/checker.py

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,49 +1373,19 @@ def check_func_def(
13731373
)
13741374

13751375
# Store argument types.
1376+
found_self = False
1377+
if isinstance(defn, FuncDef) and not defn.is_decorated:
1378+
found_self = self.require_correct_self_argument(typ, defn)
13761379
for i in range(len(typ.arg_types)):
13771380
arg_type = typ.arg_types[i]
1378-
if (
1379-
isinstance(defn, FuncDef)
1380-
and ref_type is not None
1381-
and i == 0
1382-
and defn.has_self_or_cls_argument
1383-
and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2]
1384-
):
1385-
if defn.is_class or defn.name == "__new__":
1386-
ref_type = mypy.types.TypeType.make_normalized(ref_type)
1387-
if not is_same_type(arg_type, ref_type):
1388-
# This level of erasure matches the one in checkmember.check_self_arg(),
1389-
# better keep these two checks consistent.
1390-
erased = get_proper_type(erase_typevars(erase_to_bound(arg_type)))
1391-
if not is_subtype(ref_type, erased, ignore_type_params=True):
1392-
if (
1393-
isinstance(erased, Instance)
1394-
and erased.type.is_protocol
1395-
or isinstance(erased, TypeType)
1396-
and isinstance(erased.item, Instance)
1397-
and erased.item.type.is_protocol
1398-
):
1399-
# We allow the explicit self-type to be not a supertype of
1400-
# the current class if it is a protocol. For such cases
1401-
# the consistency check will be performed at call sites.
1402-
msg = None
1403-
elif typ.arg_names[i] in {"self", "cls"}:
1404-
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
1405-
erased.str_with_options(self.options),
1406-
ref_type.str_with_options(self.options),
1407-
)
1408-
else:
1409-
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
1410-
if msg:
1411-
self.fail(msg, defn)
1412-
elif isinstance(arg_type, TypeVarType):
1381+
if isinstance(arg_type, TypeVarType):
14131382
# Refuse covariant parameter type variables
14141383
# TODO: check recursively for inner type variables
14151384
if (
14161385
arg_type.variance == COVARIANT
14171386
and defn.name not in ("__init__", "__new__", "__post_init__")
14181387
and not is_private(defn.name) # private methods are not inherited
1388+
and (i != 0 or not found_self)
14191389
):
14201390
ctx: Context = arg_type
14211391
if ctx.line < 0:
@@ -1565,6 +1535,69 @@ def check_func_def(
15651535

15661536
self.binder = old_binder
15671537

1538+
def require_correct_self_argument(self, func: Type, defn: FuncDef) -> bool:
1539+
func = get_proper_type(func)
1540+
if not isinstance(func, CallableType):
1541+
return False
1542+
1543+
# Do not report errors for untyped methods in classes nested in untyped funcs.
1544+
if not (
1545+
self.options.check_untyped_defs
1546+
or len(self.dynamic_funcs) < 2
1547+
or not self.dynamic_funcs[-2]
1548+
or not defn.is_dynamic()
1549+
):
1550+
return bool(func.arg_types)
1551+
1552+
with self.scope.push_function(defn):
1553+
# We temporary push the definition to get the self type as
1554+
# visible from *inside* of this function/method.
1555+
ref_type: Type | None = self.scope.active_self_type()
1556+
if ref_type is None:
1557+
return False
1558+
1559+
if not defn.has_self_or_cls_argument or (
1560+
func.arg_kinds and func.arg_kinds[0] in [nodes.ARG_STAR, nodes.ARG_STAR2]
1561+
):
1562+
return False
1563+
1564+
if not func.arg_types:
1565+
self.fail(
1566+
'Method must have at least one argument. Did you forget the "self" argument?', defn
1567+
)
1568+
return False
1569+
1570+
arg_type = func.arg_types[0]
1571+
if defn.is_class or defn.name == "__new__":
1572+
ref_type = mypy.types.TypeType.make_normalized(ref_type)
1573+
if is_same_type(arg_type, ref_type):
1574+
return True
1575+
1576+
# This level of erasure matches the one in checkmember.check_self_arg(),
1577+
# better keep these two checks consistent.
1578+
erased = get_proper_type(erase_typevars(erase_to_bound(arg_type)))
1579+
if not is_subtype(ref_type, erased, ignore_type_params=True):
1580+
if (
1581+
isinstance(erased, Instance)
1582+
and erased.type.is_protocol
1583+
or isinstance(erased, TypeType)
1584+
and isinstance(erased.item, Instance)
1585+
and erased.item.type.is_protocol
1586+
):
1587+
# We allow the explicit self-type to be not a supertype of
1588+
# the current class if it is a protocol. For such cases
1589+
# the consistency check will be performed at call sites.
1590+
msg = None
1591+
elif func.arg_names[0] in {"self", "cls"}:
1592+
msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format(
1593+
erased.str_with_options(self.options), ref_type.str_with_options(self.options)
1594+
)
1595+
else:
1596+
msg = message_registry.MISSING_OR_INVALID_SELF_TYPE
1597+
if msg:
1598+
self.fail(msg, defn)
1599+
return True
1600+
15681601
def is_var_redefined_in_outer_context(self, v: Var, after_line: int) -> bool:
15691602
"""Can the variable be assigned to at module top level or outer function?
15701603
@@ -5306,6 +5339,7 @@ def visit_decorator_inner(
53065339
)
53075340
if non_trivial_decorator:
53085341
self.check_untyped_after_decorator(sig, e.func)
5342+
self.require_correct_self_argument(sig, e.func)
53095343
sig = set_callable_name(sig, e.func)
53105344
e.var.type = sig
53115345
e.var.is_ready = True

mypy/semanal.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,12 +1072,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type:
10721072
if func.has_self_or_cls_argument:
10731073
if func.name in ["__init_subclass__", "__class_getitem__"]:
10741074
func.is_class = True
1075-
if not func.arguments:
1076-
self.fail(
1077-
'Method must have at least one argument. Did you forget the "self" argument?',
1078-
func,
1079-
)
1080-
elif isinstance(functype, CallableType):
1075+
if func.arguments and isinstance(functype, CallableType):
10811076
self_type = get_proper_type(functype.arg_types[0])
10821077
if isinstance(self_type, AnyType):
10831078
if has_self_type:

test-data/unit/check-classes.test

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3261,7 +3261,10 @@ b.bad = 'a' # E: Incompatible types in assignment (expression has type "str", v
32613261
from typing import Any
32623262

32633263
class Test:
3264-
def __setattr__() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? # E: Invalid signature "Callable[[], None]" for "__setattr__"
3264+
def __setattr__() -> None: ... \
3265+
# E: Invalid signature "Callable[[], None]" for "__setattr__" \
3266+
# E: Method must have at least one argument. Did you forget the "self" argument?
3267+
32653268
t = Test()
32663269
t.crash = 'test' # E: Attribute function "__setattr__" with type "Callable[[], None]" does not accept self argument \
32673270
# E: "Test" has no attribute "crash"
@@ -7742,6 +7745,231 @@ class Foo:
77427745
def bad(): # E: Method must have at least one argument. Did you forget the "self" argument?
77437746
self.x = 0 # E: Name "self" is not defined
77447747

7748+
[case testMethodSelfArgumentChecks]
7749+
from typing import Callable, ParamSpec, TypeVar
7750+
7751+
T = TypeVar("T")
7752+
P = ParamSpec("P")
7753+
7754+
def to_number_1(fn: Callable[[], int]) -> int:
7755+
return 0
7756+
7757+
def to_number_2(fn: Callable[[int], int]) -> int:
7758+
return 0
7759+
7760+
def to_same_callable(fn: Callable[P, T]) -> Callable[P, T]:
7761+
return fn
7762+
7763+
class A:
7764+
def undecorated() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument?
7765+
7766+
def undecorated_not_self(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self)
7767+
7768+
def undecorated_not_self_2(self: int) -> None: ... # E: The erased type of self "builtins.int" is not a supertype of its class "__main__.A"
7769+
7770+
@to_number_1
7771+
def fn1() -> int:
7772+
return 0
7773+
7774+
@to_number_1 # E: Argument 1 to "to_number_1" has incompatible type "Callable[[int], int]"; expected "Callable[[], int]"
7775+
def fn2(_x: int) -> int:
7776+
return 0
7777+
7778+
@to_number_2 # E: Argument 1 to "to_number_2" has incompatible type "Callable[[], int]"; expected "Callable[[int], int]"
7779+
def fn3() -> int:
7780+
return 0
7781+
7782+
@to_number_2
7783+
def fn4(_x: int) -> int:
7784+
return 0
7785+
7786+
@to_number_2 # E: Argument 1 to "to_number_2" has incompatible type "Callable[[str], int]"; expected "Callable[[int], int]"
7787+
def fn5(_x: str) -> int:
7788+
return 0
7789+
7790+
@to_same_callable
7791+
def g1() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument?
7792+
7793+
@to_same_callable
7794+
def g2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self)
7795+
7796+
@to_same_callable
7797+
def g3(self: int) -> None: ... # E: The erased type of self "builtins.int" is not a supertype of its class "__main__.A"
7798+
7799+
reveal_type(A().fn1) # N: Revealed type is "builtins.int"
7800+
reveal_type(A().fn2) # N: Revealed type is "builtins.int"
7801+
reveal_type(A().fn3) # N: Revealed type is "builtins.int"
7802+
reveal_type(A().fn4) # N: Revealed type is "builtins.int"
7803+
reveal_type(A().fn5) # N: Revealed type is "builtins.int"
7804+
7805+
reveal_type(A().g1) # E: Attribute function "g1" with type "Callable[[], None]" does not accept self argument \
7806+
# N: Revealed type is "def ()"
7807+
reveal_type(A().g2) # E: Invalid self argument "A" to attribute function "g2" with type "Callable[[int], None]" \
7808+
# N: Revealed type is "def ()"
7809+
reveal_type(A().g3) # E: Invalid self argument "A" to attribute function "g3" with type "Callable[[int], None]" \
7810+
# N: Revealed type is "def ()"
7811+
[builtins fixtures/tuple.pyi]
7812+
7813+
[case testMethodSelfArgumentChecksConcatenate]
7814+
from typing import Callable, ParamSpec, TypeVar
7815+
from typing_extensions import Concatenate
7816+
7817+
T = TypeVar("T")
7818+
P = ParamSpec("P")
7819+
R = TypeVar("R")
7820+
7821+
def to_same_callable(fn: Callable[Concatenate[T, P], R]) -> Callable[Concatenate[T, P], R]:
7822+
return fn
7823+
7824+
def remove_first(fn: Callable[Concatenate[T, P], R]) -> Callable[P, R]:
7825+
...
7826+
7827+
def add_correct_first(fn: Callable[P, R]) -> Callable[Concatenate["C", P], R]:
7828+
...
7829+
7830+
def add_wrong_first(fn: Callable[P, R]) -> Callable[Concatenate[int, P], R]:
7831+
...
7832+
7833+
class A:
7834+
@to_same_callable # E: Argument 1 to "to_same_callable" has incompatible type "Callable[[], int]"; expected "Callable[[T], int]"
7835+
def fn1() -> int:
7836+
return 0
7837+
7838+
@to_same_callable
7839+
def fn2(_x: int) -> int: # E: Self argument missing for a non-static method (or an invalid type for self)
7840+
return 0
7841+
7842+
@to_same_callable
7843+
def fn3(self, _x: int) -> int:
7844+
return 0
7845+
7846+
reveal_type(A().fn1) # N: Revealed type is "def () -> builtins.int"
7847+
reveal_type(A().fn2) # E: Invalid self argument "A" to attribute function "fn2" with type "Callable[[int], int]" \
7848+
# N: Revealed type is "def () -> builtins.int"
7849+
reveal_type(A().fn3) # N: Revealed type is "def (_x: builtins.int) -> builtins.int"
7850+
7851+
class B:
7852+
@remove_first # E: Argument 1 to "remove_first" has incompatible type "Callable[[], int]"; expected "Callable[[T], int]"
7853+
def fn1() -> int: # E: Method must have at least one argument. Did you forget the "self" argument?
7854+
return 0
7855+
7856+
@remove_first
7857+
def fn2(_x: int) -> int: # E: Method must have at least one argument. Did you forget the "self" argument?
7858+
return 0
7859+
7860+
@remove_first
7861+
def fn3(self, _x: int) -> int: # E: Self argument missing for a non-static method (or an invalid type for self)
7862+
return 0
7863+
7864+
@remove_first
7865+
def fn4(self, new_self: 'B') -> int:
7866+
return 0
7867+
7868+
reveal_type(B().fn1) # E: Attribute function "fn1" with type "Callable[[], int]" does not accept self argument \
7869+
# N: Revealed type is "def () -> builtins.int"
7870+
reveal_type(B().fn2) # E: Attribute function "fn2" with type "Callable[[], int]" does not accept self argument \
7871+
# N: Revealed type is "def () -> builtins.int"
7872+
reveal_type(B().fn3) # E: Invalid self argument "B" to attribute function "fn3" with type "Callable[[int], int]" \
7873+
# N: Revealed type is "def () -> builtins.int"
7874+
reveal_type(B().fn4) # N: Revealed type is "def () -> builtins.int"
7875+
7876+
class C:
7877+
@add_correct_first
7878+
def fn1() -> int:
7879+
return 0
7880+
7881+
@add_correct_first
7882+
def fn2(_x: int) -> int:
7883+
return 0
7884+
7885+
@add_correct_first
7886+
def fn3(self, _x: int) -> int:
7887+
return 0
7888+
7889+
reveal_type(C().fn1) # N: Revealed type is "def () -> builtins.int"
7890+
reveal_type(C().fn2) # N: Revealed type is "def (_x: builtins.int) -> builtins.int"
7891+
reveal_type(C().fn3) # N: Revealed type is "def (self: __main__.C, _x: builtins.int) -> builtins.int"
7892+
7893+
class D:
7894+
@add_wrong_first
7895+
def fn1() -> int: # E: Self argument missing for a non-static method (or an invalid type for self)
7896+
return 0
7897+
7898+
@add_wrong_first
7899+
def fn2(_x: int) -> int: # E: Self argument missing for a non-static method (or an invalid type for self)
7900+
return 0
7901+
7902+
@add_wrong_first
7903+
def fn3(self, _x: int) -> int: # E: Self argument missing for a non-static method (or an invalid type for self)
7904+
return 0
7905+
7906+
reveal_type(D().fn1) # E: Invalid self argument "D" to attribute function "fn1" with type "Callable[[int], int]" \
7907+
# N: Revealed type is "def () -> builtins.int"
7908+
reveal_type(D().fn2) # E: Invalid self argument "D" to attribute function "fn2" with type "Callable[[int, int], int]" \
7909+
# N: Revealed type is "def (_x: builtins.int) -> builtins.int"
7910+
reveal_type(D().fn3) # E: Invalid self argument "D" to attribute function "fn3" with type "Callable[[int, D, int], int]" \
7911+
# N: Revealed type is "def (self: __main__.D, _x: builtins.int) -> builtins.int"
7912+
[builtins fixtures/tuple.pyi]
7913+
7914+
[case testMethodSelfArgumentChecksInUntyped]
7915+
from typing import Callable, ParamSpec, TypeVar
7916+
7917+
T = TypeVar("T")
7918+
P = ParamSpec("P")
7919+
7920+
def to_same_callable(fn: Callable[P, T]) -> Callable[P, T]:
7921+
return fn
7922+
7923+
def unchecked():
7924+
class Bad:
7925+
def fn() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument?
7926+
def fn2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self)
7927+
7928+
# TODO: would be nice to make this error, but now we see the func
7929+
# being decorated as Any, not as a callable
7930+
@to_same_callable
7931+
def gaaa() -> None: ...
7932+
@to_same_callable
7933+
def gaaa2(x: int) -> None: ...
7934+
7935+
class Ok:
7936+
def fn(): ...
7937+
def fn2(x): ...
7938+
7939+
@to_same_callable
7940+
def g(): ...
7941+
@to_same_callable
7942+
def g2(x): ...
7943+
7944+
def checked() -> None:
7945+
class Bad:
7946+
def fn() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument?
7947+
def fn2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self)
7948+
7949+
@to_same_callable
7950+
def g() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument?
7951+
@to_same_callable
7952+
def g2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self)
7953+
7954+
class AlsoBad:
7955+
def fn(): ... # E: Method must have at least one argument. Did you forget the "self" argument?
7956+
def fn2(x): ...
7957+
7958+
@to_same_callable
7959+
def g(): ... # E: Method must have at least one argument. Did you forget the "self" argument?
7960+
@to_same_callable
7961+
def g2(x): ...
7962+
7963+
class Ok:
7964+
def fn(): ... # E: Method must have at least one argument. Did you forget the "self" argument?
7965+
def fn2(x): ...
7966+
7967+
@to_same_callable
7968+
def g(): ... # E: Method must have at least one argument. Did you forget the "self" argument?
7969+
@to_same_callable
7970+
def g2(x): ...
7971+
[builtins fixtures/tuple.pyi]
7972+
77457973
[case testTypeAfterAttributeAccessWithDisallowAnyExpr]
77467974
# flags: --disallow-any-expr
77477975

test-data/unit/fine-grained.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1720,7 +1720,7 @@ from typing import Iterator, Callable, List, Optional
17201720
from a import f
17211721
import a
17221722

1723-
def dec(f: Callable[['A'], Optional[Iterator[int]]]) -> Callable[[int], int]: pass
1723+
def dec(f: Callable[['A'], Optional[Iterator[int]]]) -> Callable[['A', int], int]: pass
17241724

17251725
class A:
17261726
@dec

0 commit comments

Comments
 (0)