Repository: python/mypy

Issue:
Support attr converters with new semantic analyzer
This is a follow up for https://github.com/python/mypy/pull/6435

The `attrs` plugin does some tricky things to support converters, in particular although the plugin is called during semantic analysis, it look like some parts belong to type checking. In particular, we need to know analyzed types of overloaded functions while still semantically analyzing top levels, which is especially tricky with the new semantic analyzer.

See also `testAttrsUsingBadConverter`.


Discussion:
I've just ran with an issue with `converter`. Just a sample piece of code:
```python
from distutils.version import LooseVersion
import attr

@attr.s
class Manifest:
    version: LooseVersion = attr.ib(converter=LooseVersion, default='1.0')
```

And the output from mypy:
```
tst.py:6: error: Incompatible types in assignment (expression has type "str", variable has type "LooseVersion")
```



@danie1k your problem is unrelated to this issue and is a duplicate of https://github.com/python-attrs/attrs/issues/518
@ilevkivskyi Thanks! :+1: 
I didn't thought about searching `attrs` issues in this case.

Resolving patch:
diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py
--- a/mypy/plugins/attrs.py
+++ b/mypy/plugins/attrs.py
@@ -1,4 +1,5 @@
 """Plugin for supporting the attrs library (http://www.attrs.org)"""
+
 from collections import OrderedDict
 
 from typing import Optional, Dict, List, cast, Tuple, Iterable
@@ -9,7 +10,7 @@
 from mypy.fixup import lookup_qualified_stnode
 from mypy.nodes import (
     Context, Argument, Var, ARG_OPT, ARG_POS, TypeInfo, AssignmentStmt,
-    TupleExpr, ListExpr, NameExpr, CallExpr, RefExpr, FuncBase,
+    TupleExpr, ListExpr, NameExpr, CallExpr, RefExpr, FuncDef,
     is_class_var, TempNode, Decorator, MemberExpr, Expression,
     SymbolTableNode, MDEF, JsonDict, OverloadedFuncDef, ARG_NAMED_OPT, ARG_NAMED,
     TypeVarExpr
@@ -463,10 +464,13 @@ def _parse_converter(ctx: 'mypy.plugin.ClassDefContext',
     # TODO: Support complex converters, e.g. lambdas, calls, etc.
     if converter:
         if isinstance(converter, RefExpr) and converter.node:
-            if (isinstance(converter.node, FuncBase)
+            if (isinstance(converter.node, FuncDef)
                     and converter.node.type
                     and isinstance(converter.node.type, FunctionLike)):
                 return Converter(converter.node.fullname())
+            elif (isinstance(converter.node, OverloadedFuncDef)
+                    and is_valid_overloaded_converter(converter.node)):
+                return Converter(converter.node.fullname())
             elif isinstance(converter.node, TypeInfo):
                 return Converter(converter.node.fullname())
 
@@ -490,6 +494,11 @@ def _parse_converter(ctx: 'mypy.plugin.ClassDefContext',
     return Converter(None)
 
 
+def is_valid_overloaded_converter(defn: OverloadedFuncDef) -> bool:
+    return all((not isinstance(item, Decorator) or isinstance(item.func.type, FunctionLike))
+               for item in defn.items)
+
+
 def _parse_assignments(
         lvalue: Expression,
         stmt: AssignmentStmt) -> Tuple[List[NameExpr], List[Expression]]:


Test patch:
diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test
--- a/test-data/unit/check-attr.test
+++ b/test-data/unit/check-attr.test
@@ -577,10 +577,8 @@ class C:
 
 [builtins fixtures/list.pyi]
 
--- This is tricky with new analyzer:
--- We need to know the analyzed type of a function while still processing top-level.
 [case testAttrsUsingBadConverter]
-# flags: --no-new-semantic-analyzer --no-strict-optional
+# flags: --no-strict-optional
 import attr
 from typing import overload
 @overload
@@ -606,6 +604,34 @@ main:17: error: Argument "converter" has incompatible type overloaded function;
 main:18: note: Revealed type is 'def (bad: Any, bad_overloaded: Any) -> __main__.A'
 [builtins fixtures/list.pyi]
 
+[case testAttrsUsingBadConverterReprocess]
+# flags: --no-strict-optional
+import attr
+from typing import overload
+forward: 'A'
+@overload
+def bad_overloaded_converter(x: int, y: int) -> int:
+    ...
+@overload
+def bad_overloaded_converter(x: str, y: str) -> str:
+    ...
+def bad_overloaded_converter(x, y=7):
+    return x
+def bad_converter() -> str:
+    return ''
+@attr.dataclass
+class A:
+    bad: str = attr.ib(converter=bad_converter)
+    bad_overloaded: int = attr.ib(converter=bad_overloaded_converter)
+reveal_type(A)
+[out]
+main:17: error: Cannot determine __init__ type from converter
+main:17: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], str]"
+main:18: error: Cannot determine __init__ type from converter
+main:18: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], int]"
+main:19: note: Revealed type is 'def (bad: Any, bad_overloaded: Any) -> __main__.A'
+[builtins fixtures/list.pyi]
+
 [case testAttrsUsingUnsupportedConverter]
 import attr
 class Thing: