From 20cea74666ce1f8e4186115661856e3a6859e62f Mon Sep 17 00:00:00 2001 From: STerliakov Date: Wed, 23 Jul 2025 02:47:18 +0200 Subject: [PATCH 1/6] Move self argument checks to a later phase (after decorator application, if any) --- mypy/checker.py | 95 ++++++++++++++++++----------- mypy/semanal.py | 7 +-- test-data/unit/check-functions.test | 59 ++++++++++++++++++ 3 files changed, 120 insertions(+), 41 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 7579c36a97d0..ef94fb32ec90 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1369,49 +1369,19 @@ def check_func_def( ) # Store argument types. + found_self = False + if isinstance(defn, FuncDef) and not defn.is_decorated: + found_self = self.require_correct_self_argument(typ, defn) for i in range(len(typ.arg_types)): arg_type = typ.arg_types[i] - if ( - isinstance(defn, FuncDef) - and ref_type is not None - and i == 0 - and defn.has_self_or_cls_argument - and typ.arg_kinds[0] not in [nodes.ARG_STAR, nodes.ARG_STAR2] - ): - if defn.is_class or defn.name == "__new__": - ref_type = mypy.types.TypeType.make_normalized(ref_type) - if not is_same_type(arg_type, ref_type): - # This level of erasure matches the one in checkmember.check_self_arg(), - # better keep these two checks consistent. - erased = get_proper_type(erase_typevars(erase_to_bound(arg_type))) - if not is_subtype(ref_type, erased, ignore_type_params=True): - if ( - isinstance(erased, Instance) - and erased.type.is_protocol - or isinstance(erased, TypeType) - and isinstance(erased.item, Instance) - and erased.item.type.is_protocol - ): - # We allow the explicit self-type to be not a supertype of - # the current class if it is a protocol. For such cases - # the consistency check will be performed at call sites. - msg = None - elif typ.arg_names[i] in {"self", "cls"}: - msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format( - erased.str_with_options(self.options), - ref_type.str_with_options(self.options), - ) - else: - msg = message_registry.MISSING_OR_INVALID_SELF_TYPE - if msg: - self.fail(msg, defn) - elif isinstance(arg_type, TypeVarType): + if isinstance(arg_type, TypeVarType): # Refuse covariant parameter type variables # TODO: check recursively for inner type variables if ( arg_type.variance == COVARIANT and defn.name not in ("__init__", "__new__", "__post_init__") and not is_private(defn.name) # private methods are not inherited + and (i != 0 or not found_self) ): ctx: Context = arg_type if ctx.line < 0: @@ -1561,6 +1531,60 @@ def check_func_def( self.binder = old_binder + def require_correct_self_argument(self, func: Type, defn: FuncDef) -> bool: + func = get_proper_type(func) + if not isinstance(func, CallableType): + return False + + with self.scope.push_function(defn): + # We temporary push the definition to get the self type as + # visible from *inside* of this function/method. + ref_type: Type | None = self.scope.active_self_type() + if ref_type is None: + return False + + if not defn.has_self_or_cls_argument or ( + func.arg_kinds and func.arg_kinds[0] in [nodes.ARG_STAR, nodes.ARG_STAR2] + ): + return False + + if not func.arg_types: + self.fail( + 'Method must have at least one argument. Did you forget the "self" argument?', defn + ) + return False + + arg_type = func.arg_types[0] + if defn.is_class or defn.name == "__new__": + ref_type = mypy.types.TypeType.make_normalized(ref_type) + if is_same_type(arg_type, ref_type): + return True + + # This level of erasure matches the one in checkmember.check_self_arg(), + # better keep these two checks consistent. + erased = get_proper_type(erase_typevars(erase_to_bound(arg_type))) + if not is_subtype(ref_type, erased, ignore_type_params=True): + if ( + isinstance(erased, Instance) + and erased.type.is_protocol + or isinstance(erased, TypeType) + and isinstance(erased.item, Instance) + and erased.item.type.is_protocol + ): + # We allow the explicit self-type to be not a supertype of + # the current class if it is a protocol. For such cases + # the consistency check will be performed at call sites. + msg = None + elif func.arg_names[0] in {"self", "cls"}: + msg = message_registry.ERASED_SELF_TYPE_NOT_SUPERTYPE.format( + erased.str_with_options(self.options), ref_type.str_with_options(self.options) + ) + else: + msg = message_registry.MISSING_OR_INVALID_SELF_TYPE + if msg: + self.fail(msg, defn) + return True + def is_var_redefined_in_outer_context(self, v: Var, after_line: int) -> bool: """Can the variable be assigned to at module top level or outer function? @@ -5298,6 +5322,7 @@ def visit_decorator_inner( ) if non_trivial_decorator: self.check_untyped_after_decorator(sig, e.func) + self.require_correct_self_argument(sig, e.func) sig = set_callable_name(sig, e.func) e.var.type = sig e.var.is_ready = True diff --git a/mypy/semanal.py b/mypy/semanal.py index 01b7f4989d80..1840e606af37 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1072,12 +1072,7 @@ def prepare_method_signature(self, func: FuncDef, info: TypeInfo, has_self_type: if func.has_self_or_cls_argument: if func.name in ["__init_subclass__", "__class_getitem__"]: func.is_class = True - if not func.arguments: - self.fail( - 'Method must have at least one argument. Did you forget the "self" argument?', - func, - ) - elif isinstance(functype, CallableType): + if func.arguments and isinstance(functype, CallableType): self_type = get_proper_type(functype.arg_types[0]) if isinstance(self_type, AnyType): if has_self_type: diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 7fa34a398ea0..0f715f100317 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3708,3 +3708,62 @@ foo(*args) # E: Argument 1 to "foo" has incompatible type "*list[object]"; expe kwargs: dict[str, object] foo(**kwargs) # E: Argument 1 to "foo" has incompatible type "**dict[str, object]"; expected "P" [builtins fixtures/dict.pyi] + +[case testMethodDecoratorSelfChecks] +from typing import Callable, ParamSpec, TypeVar + +T = TypeVar("T") +P = ParamSpec("P") + +def to_number_1(fn: Callable[[], int]) -> int: + return 0 + +def to_number_2(fn: Callable[[int], int]) -> int: + return 0 + +def to_same_callable(fn: Callable[P, T]) -> Callable[P, T]: + return fn + +class A: + @to_number_1 + def fn1() -> int: + return 0 + + @to_number_1 # E: Argument 1 to "to_number_1" has incompatible type "Callable[[int], int]"; expected "Callable[[], int]" + def fn2(_x: int) -> int: + return 0 + + @to_number_2 # E: Argument 1 to "to_number_2" has incompatible type "Callable[[], int]"; expected "Callable[[int], int]" + def fn3() -> int: + return 0 + + @to_number_2 + def fn4(_x: int) -> int: + return 0 + + @to_number_2 # E: Argument 1 to "to_number_2" has incompatible type "Callable[[str], int]"; expected "Callable[[int], int]" + def fn5(_x: str) -> int: + return 0 + + @to_same_callable + def g1() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? + + @to_same_callable + def g2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) + + @to_same_callable + def g3(self: int) -> None: ... # E: The erased type of self "builtins.int" is not a supertype of its class "__main__.A" + +reveal_type(A().fn1) # N: Revealed type is "builtins.int" +reveal_type(A().fn2) # N: Revealed type is "builtins.int" +reveal_type(A().fn3) # N: Revealed type is "builtins.int" +reveal_type(A().fn4) # N: Revealed type is "builtins.int" +reveal_type(A().fn5) # N: Revealed type is "builtins.int" + +reveal_type(A().g1) # E: Attribute function "g1" with type "Callable[[], None]" does not accept self argument \ + # N: Revealed type is "def ()" +reveal_type(A().g2) # E: Invalid self argument "A" to attribute function "g2" with type "Callable[[int], None]" \ + # N: Revealed type is "def ()" +reveal_type(A().g3) # E: Invalid self argument "A" to attribute function "g3" with type "Callable[[int], None]" \ + # N: Revealed type is "def ()" +[builtins fixtures/tuple.pyi] From 7be5bd39a21d50bf499d4391cb9ebd7e1d12c72d Mon Sep 17 00:00:00 2001 From: STerliakov Date: Wed, 23 Jul 2025 03:07:33 +0200 Subject: [PATCH 2/6] Sync remaining tests --- test-data/unit/check-classes.test | 5 ++++- test-data/unit/fine-grained.test | 2 +- test-data/unit/semanal-errors.test | 16 ---------------- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ae91815d1e9e..0509af0930ad 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3261,7 +3261,10 @@ b.bad = 'a' # E: Incompatible types in assignment (expression has type "str", v 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__" + def __setattr__() -> None: ... \ + # E: Invalid signature "Callable[[], None]" for "__setattr__" \ + # E: Method must have at least one argument. Did you forget the "self" argument? + t = Test() t.crash = 'test' # E: Attribute function "__setattr__" with type "Callable[[], None]" does not accept self argument \ # E: "Test" has no attribute "crash" diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index 503135d901f8..c25ed79e7356 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -1720,7 +1720,7 @@ from typing import Iterator, Callable, List, Optional from a import f import a -def dec(f: Callable[['A'], Optional[Iterator[int]]]) -> Callable[[int], int]: pass +def dec(f: Callable[['A'], Optional[Iterator[int]]]) -> Callable[['A', int], int]: pass class A: @dec diff --git a/test-data/unit/semanal-errors.test b/test-data/unit/semanal-errors.test index 2d381644629b..8053b33b94fd 100644 --- a/test-data/unit/semanal-errors.test +++ b/test-data/unit/semanal-errors.test @@ -537,13 +537,6 @@ def f(y: t): x = t main:4: error: "t" is a type variable and only valid in type context main:5: error: "t" is a type variable and only valid in type context -[case testMissingSelf] -import typing -class A: - def f(): pass -[out] -main:3: error: Method must have at least one argument. Did you forget the "self" argument? - [case testInvalidBaseClass] import typing class A(B): pass @@ -558,15 +551,6 @@ def f() -> None: super().y main:2: error: "super" used outside class main:3: error: "super" used outside class -[case testMissingSelfInMethod] -import typing -class A: - def f() -> None: pass - def g(): pass -[out] -main:3: error: Method must have at least one argument. Did you forget the "self" argument? -main:4: error: Method must have at least one argument. Did you forget the "self" argument? - [case testMultipleMethodDefinition] import typing class A: From 4a199ad111bee512f86f7dc98a60a8c4743a8223 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Sat, 26 Jul 2025 00:06:40 +0200 Subject: [PATCH 3/6] Restore tests removed from semanal, move to better file --- test-data/unit/check-classes.test | 68 +++++++++++++++++++++++++++++ test-data/unit/check-functions.test | 59 ------------------------- 2 files changed, 68 insertions(+), 59 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 0509af0930ad..ecca2975a789 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7745,6 +7745,74 @@ class Foo: def bad(): # E: Method must have at least one argument. Did you forget the "self" argument? self.x = 0 # E: Name "self" is not defined +[case testMethodSelfArgumentChecks] +from typing import Callable, ParamSpec, TypeVar + +T = TypeVar("T") +P = ParamSpec("P") + +def to_number_1(fn: Callable[[], int]) -> int: + return 0 + +def to_number_2(fn: Callable[[int], int]) -> int: + return 0 + +def to_same_callable(fn: Callable[P, T]) -> Callable[P, T]: + return fn + +class A: + def undecorated() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? + + def undecorated_not_self(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) + + 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" + + @to_number_1 + def fn1() -> int: + return 0 + + @to_number_1 # E: Argument 1 to "to_number_1" has incompatible type "Callable[[int], int]"; expected "Callable[[], int]" + def fn2(_x: int) -> int: + return 0 + + @to_number_2 # E: Argument 1 to "to_number_2" has incompatible type "Callable[[], int]"; expected "Callable[[int], int]" + def fn3() -> int: + return 0 + + @to_number_2 + def fn4(_x: int) -> int: + return 0 + + @to_number_2 # E: Argument 1 to "to_number_2" has incompatible type "Callable[[str], int]"; expected "Callable[[int], int]" + def fn5(_x: str) -> int: + return 0 + + @to_same_callable + def g1() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? + + @to_same_callable + def g2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) + + @to_same_callable + def g3(self: int) -> None: ... # E: The erased type of self "builtins.int" is not a supertype of its class "__main__.A" + +reveal_type(A().fn1) # N: Revealed type is "builtins.int" +reveal_type(A().fn2) # N: Revealed type is "builtins.int" +reveal_type(A().fn3) # N: Revealed type is "builtins.int" +reveal_type(A().fn4) # N: Revealed type is "builtins.int" +reveal_type(A().fn5) # N: Revealed type is "builtins.int" + +reveal_type(A().g1) # E: Attribute function "g1" with type "Callable[[], None]" does not accept self argument \ + # N: Revealed type is "def ()" +reveal_type(A().g2) # E: Invalid self argument "A" to attribute function "g2" with type "Callable[[int], None]" \ + # N: Revealed type is "def ()" +reveal_type(A().g3) # E: Invalid self argument "A" to attribute function "g3" with type "Callable[[int], None]" \ + # N: Revealed type is "def ()" + + + +[builtins fixtures/tuple.pyi] + [case testTypeAfterAttributeAccessWithDisallowAnyExpr] # flags: --disallow-any-expr diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 0f715f100317..7fa34a398ea0 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3708,62 +3708,3 @@ foo(*args) # E: Argument 1 to "foo" has incompatible type "*list[object]"; expe kwargs: dict[str, object] foo(**kwargs) # E: Argument 1 to "foo" has incompatible type "**dict[str, object]"; expected "P" [builtins fixtures/dict.pyi] - -[case testMethodDecoratorSelfChecks] -from typing import Callable, ParamSpec, TypeVar - -T = TypeVar("T") -P = ParamSpec("P") - -def to_number_1(fn: Callable[[], int]) -> int: - return 0 - -def to_number_2(fn: Callable[[int], int]) -> int: - return 0 - -def to_same_callable(fn: Callable[P, T]) -> Callable[P, T]: - return fn - -class A: - @to_number_1 - def fn1() -> int: - return 0 - - @to_number_1 # E: Argument 1 to "to_number_1" has incompatible type "Callable[[int], int]"; expected "Callable[[], int]" - def fn2(_x: int) -> int: - return 0 - - @to_number_2 # E: Argument 1 to "to_number_2" has incompatible type "Callable[[], int]"; expected "Callable[[int], int]" - def fn3() -> int: - return 0 - - @to_number_2 - def fn4(_x: int) -> int: - return 0 - - @to_number_2 # E: Argument 1 to "to_number_2" has incompatible type "Callable[[str], int]"; expected "Callable[[int], int]" - def fn5(_x: str) -> int: - return 0 - - @to_same_callable - def g1() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? - - @to_same_callable - def g2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) - - @to_same_callable - def g3(self: int) -> None: ... # E: The erased type of self "builtins.int" is not a supertype of its class "__main__.A" - -reveal_type(A().fn1) # N: Revealed type is "builtins.int" -reveal_type(A().fn2) # N: Revealed type is "builtins.int" -reveal_type(A().fn3) # N: Revealed type is "builtins.int" -reveal_type(A().fn4) # N: Revealed type is "builtins.int" -reveal_type(A().fn5) # N: Revealed type is "builtins.int" - -reveal_type(A().g1) # E: Attribute function "g1" with type "Callable[[], None]" does not accept self argument \ - # N: Revealed type is "def ()" -reveal_type(A().g2) # E: Invalid self argument "A" to attribute function "g2" with type "Callable[[int], None]" \ - # N: Revealed type is "def ()" -reveal_type(A().g3) # E: Invalid self argument "A" to attribute function "g3" with type "Callable[[int], None]" \ - # N: Revealed type is "def ()" -[builtins fixtures/tuple.pyi] From aad9eda9cd03478682d43d1d027fe18f8047cadf Mon Sep 17 00:00:00 2001 From: STerliakov Date: Mon, 28 Jul 2025 17:04:19 +0200 Subject: [PATCH 4/6] Add testcase with Concatenate --- test-data/unit/check-classes.test | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index ecca2975a789..a5993beb5cef 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7808,9 +7808,107 @@ reveal_type(A().g2) # E: Invalid self argument "A" to attribute function "g2" w # N: Revealed type is "def ()" reveal_type(A().g3) # E: Invalid self argument "A" to attribute function "g3" with type "Callable[[int], None]" \ # N: Revealed type is "def ()" +[builtins fixtures/tuple.pyi] + +[case testMethodSelfArgumentChecksConcatenate] +from typing import Callable, ParamSpec, TypeVar +from typing_extensions import Concatenate + +T = TypeVar("T") +P = ParamSpec("P") +R = TypeVar("R") + +def to_same_callable(fn: Callable[Concatenate[T, P], R]) -> Callable[Concatenate[T, P], R]: + return fn + +def remove_first(fn: Callable[Concatenate[T, P], R]) -> Callable[P, R]: + ... + +def add_correct_first(fn: Callable[P, R]) -> Callable[Concatenate["C", P], R]: + ... +def add_wrong_first(fn: Callable[P, R]) -> Callable[Concatenate[int, P], R]: + ... +class A: + @to_same_callable # E: Argument 1 to "to_same_callable" has incompatible type "Callable[[], int]"; expected "Callable[[T], int]" + def fn1() -> int: + return 0 + + @to_same_callable + def fn2(_x: int) -> int: # E: Self argument missing for a non-static method (or an invalid type for self) + return 0 + + @to_same_callable + def fn3(self, _x: int) -> int: + return 0 + +reveal_type(A().fn1) # N: Revealed type is "def () -> builtins.int" +reveal_type(A().fn2) # E: Invalid self argument "A" to attribute function "fn2" with type "Callable[[int], int]" \ + # N: Revealed type is "def () -> builtins.int" +reveal_type(A().fn3) # N: Revealed type is "def (_x: builtins.int) -> builtins.int" + +class B: + @remove_first # E: Argument 1 to "remove_first" has incompatible type "Callable[[], int]"; expected "Callable[[T], int]" + def fn1() -> int: # E: Method must have at least one argument. Did you forget the "self" argument? + return 0 + + @remove_first + def fn2(_x: int) -> int: # E: Method must have at least one argument. Did you forget the "self" argument? + return 0 + + @remove_first + def fn3(self, _x: int) -> int: # E: Self argument missing for a non-static method (or an invalid type for self) + return 0 + + @remove_first + def fn4(self, new_self: 'B') -> int: + return 0 + +reveal_type(B().fn1) # E: Attribute function "fn1" with type "Callable[[], int]" does not accept self argument \ + # N: Revealed type is "def () -> builtins.int" +reveal_type(B().fn2) # E: Attribute function "fn2" with type "Callable[[], int]" does not accept self argument \ + # N: Revealed type is "def () -> builtins.int" +reveal_type(B().fn3) # E: Invalid self argument "B" to attribute function "fn3" with type "Callable[[int], int]" \ + # N: Revealed type is "def () -> builtins.int" +reveal_type(B().fn4) # N: Revealed type is "def () -> builtins.int" + +class C: + @add_correct_first + def fn1() -> int: + return 0 + + @add_correct_first + def fn2(_x: int) -> int: + return 0 + + @add_correct_first + def fn3(self, _x: int) -> int: + return 0 + +reveal_type(C().fn1) # N: Revealed type is "def () -> builtins.int" +reveal_type(C().fn2) # N: Revealed type is "def (_x: builtins.int) -> builtins.int" +reveal_type(C().fn3) # N: Revealed type is "def (self: __main__.C, _x: builtins.int) -> builtins.int" + +class D: + @add_wrong_first + def fn1() -> int: # E: Self argument missing for a non-static method (or an invalid type for self) + return 0 + + @add_wrong_first + def fn2(_x: int) -> int: # E: Self argument missing for a non-static method (or an invalid type for self) + return 0 + + @add_wrong_first + def fn3(self, _x: int) -> int: # E: Self argument missing for a non-static method (or an invalid type for self) + return 0 +reveal_type(D().fn1) # E: Invalid self argument "D" to attribute function "fn1" with type "Callable[[int], int]" \ + # N: Revealed type is "def () -> builtins.int" +reveal_type(D().fn2) # E: Invalid self argument "D" to attribute function "fn2" with type "Callable[[int, int], int]" \ + # N: Revealed type is "def (_x: builtins.int) -> builtins.int" +reveal_type(D().fn3) # E: Invalid self argument "D" to attribute function "fn3" with type "Callable[[int, D, int], int]" \ + # N: Revealed type is "def (self: __main__.D, _x: builtins.int) -> builtins.int" [builtins fixtures/tuple.pyi] [case testTypeAfterAttributeAccessWithDisallowAnyExpr] From 1c9209675b3aa13e1b31c4590692b18c37a84691 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Mon, 28 Jul 2025 20:00:23 +0200 Subject: [PATCH 5/6] Do not report errors in untyped defs --- mypy/checker.py | 9 +++++++++ test-data/unit/check-classes.test | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/mypy/checker.py b/mypy/checker.py index 7b0108560401..ee0de3f3728a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1541,6 +1541,15 @@ def require_correct_self_argument(self, func: Type, defn: FuncDef) -> bool: if not isinstance(func, CallableType): return False + # Do not report errors for untyped methods in classes nested in untyped funcs. + if not ( + self.options.check_untyped_defs + or len(self.dynamic_funcs) < 2 + or not self.dynamic_funcs[-2] + or not defn.is_dynamic() + ): + return bool(func.arg_types) + with self.scope.push_function(defn): # We temporary push the definition to get the self type as # visible from *inside* of this function/method. diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a5993beb5cef..055427d5c713 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7911,6 +7911,30 @@ reveal_type(D().fn3) # E: Invalid self argument "D" to attribute function "fn3" # N: Revealed type is "def (self: __main__.D, _x: builtins.int) -> builtins.int" [builtins fixtures/tuple.pyi] +[case testMethodSelfArgumentChecksInUntyped] +def unchecked(): + class Bad: + def fn() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? + def fn2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) + + class Ok: + def fn(): ... + def fn2(x): ... + +def checked() -> None: + class Bad: + def fn() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? + def fn2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) + + class AlsoBad: + def fn(): ... # E: Method must have at least one argument. Did you forget the "self" argument? + def fn2(x): ... + +class Ok: + def fn(): ... # E: Method must have at least one argument. Did you forget the "self" argument? + def fn2(x): ... +[builtins fixtures/tuple.pyi] + [case testTypeAfterAttributeAccessWithDisallowAnyExpr] # flags: --disallow-any-expr From b71402ae4d08d3c3a8ac42c8e90ba18a18c68f93 Mon Sep 17 00:00:00 2001 From: STerliakov Date: Mon, 28 Jul 2025 20:12:37 +0200 Subject: [PATCH 6/6] Add decorated scenarios too --- test-data/unit/check-classes.test | 35 +++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 055427d5c713..f713fe69bcd2 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -7912,27 +7912,62 @@ reveal_type(D().fn3) # E: Invalid self argument "D" to attribute function "fn3" [builtins fixtures/tuple.pyi] [case testMethodSelfArgumentChecksInUntyped] +from typing import Callable, ParamSpec, TypeVar + +T = TypeVar("T") +P = ParamSpec("P") + +def to_same_callable(fn: Callable[P, T]) -> Callable[P, T]: + return fn + def unchecked(): class Bad: def fn() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? def fn2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) + # TODO: would be nice to make this error, but now we see the func + # being decorated as Any, not as a callable + @to_same_callable + def gaaa() -> None: ... + @to_same_callable + def gaaa2(x: int) -> None: ... + class Ok: def fn(): ... def fn2(x): ... + @to_same_callable + def g(): ... + @to_same_callable + def g2(x): ... + def checked() -> None: class Bad: def fn() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? def fn2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) + @to_same_callable + def g() -> None: ... # E: Method must have at least one argument. Did you forget the "self" argument? + @to_same_callable + def g2(x: int) -> None: ... # E: Self argument missing for a non-static method (or an invalid type for self) + class AlsoBad: def fn(): ... # E: Method must have at least one argument. Did you forget the "self" argument? def fn2(x): ... + @to_same_callable + def g(): ... # E: Method must have at least one argument. Did you forget the "self" argument? + @to_same_callable + def g2(x): ... + class Ok: def fn(): ... # E: Method must have at least one argument. Did you forget the "self" argument? def fn2(x): ... + + @to_same_callable + def g(): ... # E: Method must have at least one argument. Did you forget the "self" argument? + @to_same_callable + def g2(x): ... [builtins fixtures/tuple.pyi] [case testTypeAfterAttributeAccessWithDisallowAnyExpr]