diff --git a/mypy/checker.py b/mypy/checker.py index 24076fca6710..ee0de3f3728a 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1374,49 +1374,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: @@ -1566,6 +1536,69 @@ 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 + + # 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. + 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? @@ -5303,6 +5336,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-classes.test b/test-data/unit/check-classes.test index ae91815d1e9e..f713fe69bcd2 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" @@ -7742,6 +7745,231 @@ 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 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 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] # flags: --disallow-any-expr 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: