Repository: python/mypy (Python)

Issue:
Class with function fields incorrectly thinks first argument is self
This is a bug report.

# Minimal repro

```python
from dataclasses import dataclass
from typing import Callable

@dataclass
class Repro:
    fn: Callable[[int], str]

r = Repro(fn=repr)
assert "1" == r.fn(1)
```

Works correctly when dataclass is not used, i.e. when `Repro` is

```python
class Repro:
    def __init__(self, fn: Callable[[int], str]) -> None:
        self.fn = fn
```

## Actual behavior

```python
repro.py:9: error: Invalid self argument "Repro" to attribute function "fn" with type "Callable[[int], str]"
repro.py:9: error: Too many arguments
```

When running with Python, works correctly. Doesn't throw any exceptions.

## Expected behavior

Successful type check

# Environment

## Versions

Tested with two versions, 0.620 and current version from GitHub.

* Python 3.7.0
* mypy 0.620
* mypy 0.630+dev-a2ffde90635baf05e264f8d899f5705c52b27c2a

## Mypy config

```ini
[mypy]
warn_redundant_casts = True
warn_unused_ignores = True
incremental = True

follow_imports = normal
ignore_missing_imports = True
disallow_untyped_calls = True
disallow_untyped_defs = True
check_untyped_defs = True
warn_return_any = True
warn_no_return = True
no_implicit_optional = True
strict_optional = True
```



Hints:
This is not something specific to dataclasses. There is a simpler repro that gives same errors:
```python
class Repro:
    fn: Callable[[int], str]

r = Repro()
r.fn(1)
```
The problem is the same as in https://github.com/python/mypy/issues/708. I would however _not_ close this as a duplicate, because this issue highlights another aspect of the problem.
I just came across the same issue. I guess a lot of people are going to face this issue very soon, as it's very easy to reproduce it using dataclasses.
I guess this should be not hard to fix, mypy cares whether a callable was assigned in class (to match what Python does), so we should just tweak the `Var`s in plugin to pretend they were assigned on `self`. But we still need a volunteer who will fix this.
If someone needs a workaround, this works:

```python
from dataclasses import dataclass
from typing import *

T = TypeVar("T")


@dataclass
class Box(Generic[T]):
    inner: T

    @property
    def unboxed(self) -> T:
        return self.inner


@dataclass
class Repro:
    fn: Box[Callable[[int], str]]


Repro(fn=Box(repr)).fn.unboxed(1)
```
I've found that another workaround to this is to use a [callback protocol](https://mypy.readthedocs.io/en/latest/protocols.html#callback-protocols) instead of `Callable`
When using NamedTuple instead of dataclass this problem does not arise.
I volunteer to fix this and any guidances will be greatly helpful @ilevkivskyi 
@TH3CHARLie Fixing this in general may be tricky, but fixing just the dataclass case, should be easy. In any case, please go ahead!
> Fixing this in general may be tricky,

Taking into account the fact that parent issue is from 2015 I'd say that is an understatement of the year :P
Spent hours on this until someone linked me the issue.
I'm still running into this issue with dataclasses. @TH3CHARLie @ilevkivskyi  Any resolution on this? 😃 

### versions
mypy 0.782
Python 3.8.7
@jordan-bucholtz i don't know if it makes any difference but fyi there is 0.800 (and 0.790 in between).

https://pypi.org/project/mypy/0.800/
@altendky I'm using 0.800, and this is still broken for me.
Wondering if the solution is to somehow flag the Callable instance variable as a static method.  Because that's essentially the intended use.  Now, I'm not sure if the fix should be in mypy or in the typing library.
> I guess this should be not hard to fix, mypy cares whether a callable was assigned in class (to match what Python does), so we should just tweak the `Var`s in plugin to pretend they were assigned on `self`. But we still need a volunteer who will fix this.

That's what I've done in #10536 (and I could have saved investigation time if I'd read your comment).
I've also started to implement a fix for the general case (not only dataclasses) in #10548.
However, I've found at least 10 tests (listed below) where callable fields are bound as methods as the expected test result.
For example:
```python
# [case testCallableDataAttribute]
from typing import Callable
class A:
    g = None # type: [Callable[[A], None]]
    def __init__(self, f: Callable[[], None]) -> None:
        self.f = f
a = A(None)
a.f()
a.g()
a.f(a) # E: Too many arguments
a.g(a) # E: Too many arguments
```
According to #708 and this very issue, `a.g` should not be bound.

In my fix, I differentiate instance variables from class variables (according to PEP 526 definitions), handling only the last ones as bounded methods when callable.
So I had to fix the previous test like this:
```python
# [case testCallableDataAttribute]
from typing import Callable, ClassVar
class A:
    g = None # type: ClassVar[Callable[[A], None]]
    def __init__(self, f: Callable[[], None]) -> None:
        self.f = f
a = A(None)
a.f()
a.g()
a.f(a) # E: Too many arguments
a.g(a) # E: Too many arguments
```

@ilevkivskyi May I ask you what do you think about this solution (and about the current behavior in the tests)?

The 10 faulty tests:
- mypy/test/testcheck.py::TypeCheckSuite::testSelfTypeProperSupertypeAttribute
- mypy/test/testcheck.py::TypeCheckSuite::testSelfTypeProperSupertypeAttributeTuple 
- mypy/test/testcheck.py::TypeCheckSuite::testSelfTypeProperSupertypeAttributeGeneric
- mypy/test/testcheck.py::TypeCheckSuite::testMethodWithDynamicallyTypedMethodAsDataAttribute
- mypy/test/testcheck.py::TypeCheckSuite::testInvalidMethodAsDataAttributeInGenericClass
- mypy/test/testcheck.py::TypeCheckSuite::testPartiallyTypedSelfInMethodDataAttribute
- mypy/test/testcheck.py::TypeCheckSuite::testCallableDataAttribute
- mypy/test/testcheck.py::TypeCheckSuite::testMethodAsDataAttribute
- mypy/test/testcheck.py::TypeCheckSuite::testMethodWithInvalidMethodAsDataAttribute
- mypy/test/testcheck.py::TypeCheckSuite::testTotalOrderingLambda


@wyfo TBH it has been a while since I last thought about this. I don't have any strong opinions on this now. Probably it is important that correct code like this should be accepted:
```python
def m(self: A) -> None: ...

class A:
    n = m

a = A()
a.n()  # OK
```

@JukkaL is probably a better person to ask about this.
> Probably it is important that correct code like this should be accepted:

This use case is fine with the PR, and I've added a test to enforce it.
So let's wait for @JukkaL opinion.

Patch:
diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py
--- a/mypy/checkexpr.py
+++ b/mypy/checkexpr.py
@@ -1318,7 +1318,14 @@ def check_callable_call(
         arg_types = self.infer_arg_types_in_context(callee, args, arg_kinds, formal_to_actual)
 
         self.check_argument_count(
-            callee, arg_types, arg_kinds, arg_names, formal_to_actual, context
+            callee,
+            arg_types,
+            arg_kinds,
+            arg_names,
+            formal_to_actual,
+            context,
+            object_type,
+            callable_name,
         )
 
         self.check_argument_types(
@@ -1723,6 +1730,8 @@ def check_argument_count(
         actual_names: Optional[Sequence[Optional[str]]],
         formal_to_actual: List[List[int]],
         context: Optional[Context],
+        object_type: Optional[Type] = None,
+        callable_name: Optional[str] = None,
     ) -> bool:
         """Check that there is a value for all required arguments to a function.
 
@@ -1753,6 +1762,8 @@ def check_argument_count(
                 # No actual for a mandatory formal
                 if kind.is_positional():
                     self.msg.too_few_arguments(callee, context, actual_names)
+                    if object_type and callable_name and "." in callable_name:
+                        self.missing_classvar_callable_note(object_type, callable_name, context)
                 else:
                     argname = callee.arg_names[i] or "?"
                     self.msg.missing_named_argument(callee, context, argname)
@@ -1836,6 +1847,20 @@ def check_for_extra_actual_arguments(
 
         return ok, is_unexpected_arg_error
 
+    def missing_classvar_callable_note(
+        self, object_type: Type, callable_name: str, context: Context
+    ) -> None:
+        if isinstance(object_type, ProperType) and isinstance(object_type, Instance):
+            _, var_name = callable_name.rsplit(".", maxsplit=1)
+            node = object_type.type.get(var_name)
+            if node is not None and isinstance(node.node, Var):
+                if not node.node.is_inferred and not node.node.is_classvar:
+                    self.msg.note(
+                        f'"{var_name}" is considered instance variable,'
+                        " to make it class variable use ClassVar[...]",
+                        context,
+                    )
+
     def check_argument_types(
         self,
         arg_types: List[Type],
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -659,6 +659,18 @@ def instance_alias_type(alias: TypeAlias, named_type: Callable[[str], Instance])
     return expand_type_by_instance(tp, target)
 
 
+def is_instance_var(var: Var, info: TypeInfo) -> bool:
+    """Return if var is an instance variable according to PEP 526."""
+    return (
+        # check the type_info node is the var (not a decorated function, etc.)
+        var.name in info.names
+        and info.names[var.name].node is var
+        and not var.is_classvar
+        # variables without annotations are treated as classvar
+        and not var.is_inferred
+    )
+
+
 def analyze_var(
     name: str,
     var: Var,
@@ -690,7 +702,12 @@ def analyze_var(
         t = get_proper_type(expand_type_by_instance(typ, itype))
         result: Type = t
         typ = get_proper_type(typ)
-        if var.is_initialized_in_class and isinstance(typ, FunctionLike) and not typ.is_type_obj():
+        if (
+            var.is_initialized_in_class
+            and (not is_instance_var(var, info) or mx.is_operator)
+            and isinstance(typ, FunctionLike)
+            and not typ.is_type_obj()
+        ):
             if mx.is_lvalue:
                 if var.is_property:
                     if not var.is_settable_property:
diff --git a/mypy/nodes.py b/mypy/nodes.py
--- a/mypy/nodes.py
+++ b/mypy/nodes.py
@@ -939,6 +939,7 @@ def deserialize(cls, data: JsonDict) -> "Decorator":
     "final_set_in_init",
     "explicit_self_type",
     "is_ready",
+    "is_inferred",
     "from_module_getattr",
     "has_explicit_value",
     "allow_incompatible_override",
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -3851,10 +3851,14 @@ def check_classvar(self, s: AssignmentStmt) -> None:
             if isinstance(node, Var):
                 node.is_classvar = True
             analyzed = self.anal_type(s.type)
-            if analyzed is not None and get_type_vars(analyzed):
+            assert self.type is not None
+            if analyzed is not None and set(get_type_vars(analyzed)) & set(
+                self.type.defn.type_vars
+            ):
                 # This means that we have a type var defined inside of a ClassVar.
                 # This is not allowed by PEP526.
                 # See https://github.com/python/mypy/issues/11538
+
                 self.fail(message_registry.CLASS_VAR_WITH_TYPEVARS, s)
         elif not isinstance(lvalue, MemberExpr) or self.is_self_member_ref(lvalue):
             # In case of member access, report error only when assigning to self


Test patch:
diff --git a/test-data/unit/check-classvar.test b/test-data/unit/check-classvar.test
--- a/test-data/unit/check-classvar.test
+++ b/test-data/unit/check-classvar.test
@@ -325,3 +325,12 @@ class Good(A[int, str]):
     x = 42
 reveal_type(Good.x)  # N: Revealed type is "builtins.int"
 [builtins fixtures/classmethod.pyi]
+
+[case testSuggestClassVarOnTooFewArgumentsMethod]
+from typing import Callable
+
+class C:
+    foo: Callable[[C], int]
+c:C
+c.foo()  # E: Too few arguments \
+         # N: "foo" is considered instance variable, to make it class variable use ClassVar[...]
diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test
--- a/test-data/unit/check-dataclasses.test
+++ b/test-data/unit/check-dataclasses.test
@@ -1304,80 +1304,41 @@ reveal_type(A.__dataclass_fields__)  # N: Revealed type is "builtins.dict[builti
 
 [builtins fixtures/dict.pyi]
 
-[case testDataclassCallableProperty]
+[case testDataclassCallableFieldAccess]
 # flags: --python-version 3.7
 from dataclasses import dataclass
 from typing import Callable
 
 @dataclass
 class A:
-    foo: Callable[[int], int]
+    x: Callable[[int], int]
+    y: Callable[[int], int] = lambda i: i
 
-def my_foo(x: int) -> int:
-    return x
-
-a = A(foo=my_foo)
-a.foo(1)
-reveal_type(a.foo)  # N: Revealed type is "def (builtins.int) -> builtins.int"
-reveal_type(A.foo)  # N: Revealed type is "def (builtins.int) -> builtins.int"
-[typing fixtures/typing-medium.pyi]
-[builtins fixtures/dataclasses.pyi]
-
-[case testDataclassCallableAssignment]
-# flags: --python-version 3.7
-from dataclasses import dataclass
-from typing import Callable
-
-@dataclass
-class A:
-    foo: Callable[[int], int]
-
-def my_foo(x: int) -> int:
-    return x
-
-a = A(foo=my_foo)
-
-def another_foo(x: int) -> int:
-    return x
-
-a.foo = another_foo
+a = A(lambda i:i)
+x: int = a.x(0)
+y: str = a.y(0) # E: Incompatible types in assignment (expression has type "int", variable has type "str")
+reveal_type(a.x)  # N: Revealed type is "def (builtins.int) -> builtins.int"
+reveal_type(a.y)  # N: Revealed type is "def (builtins.int) -> builtins.int"
+reveal_type(A.y)  # N: Revealed type is "def (builtins.int) -> builtins.int"
 [builtins fixtures/dataclasses.pyi]
 
-[case testDataclassCallablePropertyWrongType]
+[case testDataclassCallableFieldAssignment]
 # flags: --python-version 3.7
 from dataclasses import dataclass
 from typing import Callable
 
 @dataclass
 class A:
-    foo: Callable[[int], int]
+    x: Callable[[int], int]
 
-def my_foo(x: int) -> str:
-    return "foo"
+def x(i: int) -> int:
+    return i
+def x2(s: str) -> str:
+    return s
 
-a = A(foo=my_foo)  # E: Argument "foo" to "A" has incompatible type "Callable[[int], str]"; expected "Callable[[int], int]"
-[typing fixtures/typing-medium.pyi]
-[builtins fixtures/dataclasses.pyi]
-
-[case testDataclassCallablePropertyWrongTypeAssignment]
-# flags: --python-version 3.7
-from dataclasses import dataclass
-from typing import Callable
-
-@dataclass
-class A:
-    foo: Callable[[int], int]
-
-def my_foo(x: int) -> int:
-    return x
-
-a = A(foo=my_foo)
-
-def another_foo(x: int) -> str:
-    return "foo"
-
-a.foo = another_foo  # E: Incompatible types in assignment (expression has type "Callable[[int], str]", variable has type "Callable[[int], int]")
-[typing fixtures/typing-medium.pyi]
+a = A(lambda i:i)
+a.x = x
+a.x = x2 # E: Incompatible types in assignment (expression has type "Callable[[str], str]", variable has type "Callable[[int], int]")
 [builtins fixtures/dataclasses.pyi]
 
 [case testDataclassFieldDoesNotFailOnKwargsUnpacking]
diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test
--- a/test-data/unit/check-functions.test
+++ b/test-data/unit/check-functions.test
@@ -571,12 +571,12 @@ A().f('') # E: Argument 1 to "f" of "A" has incompatible type "str"; expected "i
 
 
 [case testMethodAsDataAttribute]
-from typing import Any, Callable
+from typing import Any, Callable, ClassVar
 class B: pass
 x = None # type: Any
 class A:
-    f = x # type: Callable[[A], None]
-    g = x # type: Callable[[A, B], None]
+    f = x # type: ClassVar[Callable[[A], None]]
+    g = x # type: ClassVar[Callable[[A, B], None]]
 a = None # type: A
 a.f()
 a.g(B())
@@ -584,26 +584,38 @@ a.f(a) # E: Too many arguments
 a.g()  # E: Too few arguments
 
 [case testMethodWithInvalidMethodAsDataAttribute]
-from typing import Any, Callable
+from typing import Any, Callable, ClassVar
 class B: pass
 x = None # type: Any
 class A:
-    f = x # type: Callable[[], None]
-    g = x # type: Callable[[B], None]
+    f = x # type: ClassVar[Callable[[], None]]
+    g = x # type: ClassVar[Callable[[B], None]]
 a = None # type: A
 a.f() # E: Attribute function "f" with type "Callable[[], None]" does not accept self argument
 a.g() # E: Invalid self argument "A" to attribute function "g" with type "Callable[[B], None]"
 
 [case testMethodWithDynamicallyTypedMethodAsDataAttribute]
-from typing import Any, Callable
+from typing import Any, Callable, ClassVar
 class B: pass
 x = None # type: Any
 class A:
-    f = x # type: Callable[[Any], Any]
+    f = x # type: ClassVar[Callable[[Any], Any]]
 a = None # type: A
 a.f()
 a.f(a) # E: Too many arguments
 
+[case testMethodWithInferredMethodAsDataAttribute]
+from typing import Any
+def m(self: "A") -> int: ...
+
+class A:
+    n = m
+
+a = A()
+reveal_type(a.n())  # N: Revealed type is "builtins.int"
+reveal_type(A.n(a))  # N: Revealed type is "builtins.int"
+A.n()  # E: Too few arguments
+
 [case testOverloadedMethodAsDataAttribute]
 from foo import *
 [file foo.pyi]
@@ -645,35 +657,35 @@ a.g(B())
 a.g(a)   # E: Argument 1 has incompatible type "A[B]"; expected "B"
 
 [case testInvalidMethodAsDataAttributeInGenericClass]
-from typing import Any, TypeVar, Generic, Callable
+from typing import Any, TypeVar, Generic, Callable, ClassVar
 t = TypeVar('t')
 class B: pass
 class C: pass
 x = None # type: Any
 class A(Generic[t]):
-    f = x # type: Callable[[A[B]], None]
+    f = x # type: ClassVar[Callable[[A[B]], None]]
 ab = None # type: A[B]
 ac = None # type: A[C]
 ab.f()
 ac.f()   # E: Invalid self argument "A[C]" to attribute function "f" with type "Callable[[A[B]], None]"
 
 [case testPartiallyTypedSelfInMethodDataAttribute]
-from typing import Any, TypeVar, Generic, Callable
+from typing import Any, TypeVar, Generic, Callable, ClassVar
 t = TypeVar('t')
 class B: pass
 class C: pass
 x = None # type: Any
 class A(Generic[t]):
-    f = x # type: Callable[[A], None]
+    f = x # type: ClassVar[Callable[[A], None]]
 ab = None # type: A[B]
 ac = None # type: A[C]
 ab.f()
 ac.f()
 
 [case testCallableDataAttribute]
-from typing import Callable
+from typing import Callable, ClassVar
 class A:
-    g = None # type: Callable[[A], None]
+    g = None # type: ClassVar[Callable[[A], None]]
     def __init__(self, f: Callable[[], None]) -> None:
         self.f = f
 a = A(None)
diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test
--- a/test-data/unit/check-functools.test
+++ b/test-data/unit/check-functools.test
@@ -25,7 +25,7 @@ Ord() >= 1  # E: Unsupported operand types for >= ("Ord" and "int")
 
 [case testTotalOrderingLambda]
 from functools import total_ordering
-from typing import Any, Callable
+from typing import Any, Callable, ClassVar
 
 @total_ordering
 class Ord:
diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test
--- a/test-data/unit/check-incremental.test
+++ b/test-data/unit/check-incremental.test
@@ -5659,6 +5659,23 @@ class D(C):
 [out2]
 tmp/a.py:9: error: Trying to assign name "z" that is not in "__slots__" of type "a.D"
 
+[case testMethodAliasIncremental]
+import b
+[file a.py]
+class A:
+    def f(self) -> None: pass
+    g = f
+
+[file b.py]
+from a import A
+A().g()
+[file b.py.2]
+# trivial change
+from a import A
+A().g()
+[out]
+[out2]
+
 [case testIncrementalWithDifferentKindsOfNestedTypesWithinMethod]
 # flags: --python-version 3.7
 
diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test
--- a/test-data/unit/check-selftype.test
+++ b/test-data/unit/check-selftype.test
@@ -366,7 +366,7 @@ reveal_type(x.f)  # N: Revealed type is "builtins.int"
 [builtins fixtures/property.pyi]
 
 [case testSelfTypeProperSupertypeAttribute]
-from typing import Callable, TypeVar
+from typing import Callable, TypeVar, ClassVar
 class K: pass
 T = TypeVar('T', bound=K)
 class A(K):
@@ -374,8 +374,8 @@ class A(K):
     def g(self: K) -> int: return 0
     @property
     def gt(self: T) -> T: return self
-    f: Callable[[object], int]
-    ft: Callable[[T], T]
+    f: ClassVar[Callable[[object], int]]
+    ft: ClassVar[Callable[[T], T]]
 
 class B(A):
     pass
@@ -392,15 +392,15 @@ reveal_type(B().ft())  # N: Revealed type is "__main__.B"
 [builtins fixtures/property.pyi]
 
 [case testSelfTypeProperSupertypeAttributeTuple]
-from typing import Callable, TypeVar, Tuple
+from typing import Callable, TypeVar, Tuple, ClassVar
 T = TypeVar('T')
 class A(Tuple[int, int]):
     @property
     def g(self: object) -> int: return 0
     @property
     def gt(self: T) -> T: return self
-    f: Callable[[object], int]
-    ft: Callable[[T], T]
+    f: ClassVar[Callable[[object], int]]
+    ft: ClassVar[Callable[[T], T]]
 
 class B(A):
     pass
@@ -450,7 +450,7 @@ reveal_type(X1.ft())  # N: Revealed type is "Type[__main__.X]"
 [builtins fixtures/property.pyi]
 
 [case testSelfTypeProperSupertypeAttributeGeneric]
-from typing import Callable, TypeVar, Generic
+from typing import Callable, TypeVar, Generic, ClassVar
 Q = TypeVar('Q', covariant=True)
 class K(Generic[Q]):
     q: Q
@@ -460,8 +460,8 @@ class A(K[Q]):
     def g(self: K[object]) -> int: return 0
     @property
     def gt(self: K[T]) -> T: return self.q
-    f: Callable[[object], int]
-    ft: Callable[[T], T]
+    f: ClassVar[Callable[[object], int]]
+    ft: ClassVar[Callable[[T], T]]
 
 class B(A[Q]):
     pass
diff --git a/test-data/unit/check-slots.test b/test-data/unit/check-slots.test
--- a/test-data/unit/check-slots.test
+++ b/test-data/unit/check-slots.test
@@ -361,8 +361,7 @@ a.a = 1
 a.b = custom_obj
 a.c = custom_obj
 a.d = custom_obj
-# TODO: Should this be allowed?
-a.e = custom_obj  # E: Cannot assign to a method
+a.e = custom_obj
 [out]
 [builtins fixtures/tuple.pyi]
 [builtins fixtures/dict.pyi]