diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index 87de287923cc..78afc422add6 100644 --- 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]]: diff --git a/test-data/unit/check-attr.test b/test-data/unit/check-attr.test index e97f54c4bb68..6202b4e6aed4 100644 --- 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: