diff --git a/src/attr/__init__.pyi b/src/attr/__init__.pyi index 133e50105..2146143cb 100644 --- a/src/attr/__init__.pyi +++ b/src/attr/__init__.pyi @@ -95,13 +95,16 @@ def Factory( In = TypeVar("In") Out = TypeVar("Out") +# Callable might type check against the specific attrs-defined class. +_AttrsInstance = TypeVar("_AttrsInstance", bound=AttrsInstance) + class Converter(Generic[In, Out]): @overload def __init__(self, converter: Callable[[In], Out]) -> None: ... @overload def __init__( self, - converter: Callable[[In, AttrsInstance, Attribute], Out], + converter: Callable[[In, _AttrsInstance, Attribute], Out], *, takes_self: Literal[True], takes_field: Literal[True], @@ -116,7 +119,7 @@ class Converter(Generic[In, Out]): @overload def __init__( self, - converter: Callable[[In, AttrsInstance], Out], + converter: Callable[[In, _AttrsInstance], Out], *, takes_self: Literal[True], ) -> None: ... diff --git a/tests/test_mypy.yml b/tests/test_mypy.yml index 41c5029f3..c158b3a4d 100644 --- a/tests/test_mypy.yml +++ b/tests/test_mypy.yml @@ -911,28 +911,28 @@ main:22: error: No overload variant of "Converter" matches argument types "Callable[[Any], str]", "bool" [call-overload] main:22: note: Possible overload variants: main:22: note: def [In, Out] Converter(self, converter: Callable[[In], Out]) -> Converter[In, Out] - main:22: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] + main:22: note: def [In, Out, _AttrsInstance: AttrsInstance] Converter(self, converter: Callable[[In, _AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] main:22: note: def [In, Out] Converter(self, converter: Callable[[In, Attribute[Any]], Out], *, takes_field: Literal[True]) -> Converter[In, Out] - main:22: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] + main:22: note: def [In, Out, _AttrsInstance: AttrsInstance] Converter(self, converter: Callable[[In, _AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] main:23: error: No overload variant of "Converter" matches argument types "Callable[[Any, Attribute[Any]], str]", "bool" [call-overload] main:23: note: Possible overload variants: main:23: note: def [In, Out] Converter(self, converter: Callable[[In], Out]) -> Converter[In, Out] - main:23: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] + main:23: note: def [In, Out, _AttrsInstance: AttrsInstance] Converter(self, converter: Callable[[In, _AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] main:23: note: def [In, Out] Converter(self, converter: Callable[[In, Attribute[Any]], Out], *, takes_field: Literal[True]) -> Converter[In, Out] - main:23: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] + main:23: note: def [In, Out, _AttrsInstance: AttrsInstance] Converter(self, converter: Callable[[In, _AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] main:25: note: Revealed type is "attr.Converter[Any, builtins.str]" main:26: error: No overload variant of "Converter" matches argument types "Callable[[Any], str]", "bool" [call-overload] main:26: note: Possible overload variants: main:26: note: def [In, Out] Converter(self, converter: Callable[[In], Out]) -> Converter[In, Out] - main:26: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] + main:26: note: def [In, Out, _AttrsInstance: AttrsInstance] Converter(self, converter: Callable[[In, _AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] main:26: note: def [In, Out] Converter(self, converter: Callable[[In, Attribute[Any]], Out], *, takes_field: Literal[True]) -> Converter[In, Out] - main:26: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] + main:26: note: def [In, Out, _AttrsInstance: AttrsInstance] Converter(self, converter: Callable[[In, _AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] main:27: error: No overload variant of "Converter" matches argument types "Callable[[Any, AttrsInstance], str]", "bool" [call-overload] main:27: note: Possible overload variants: main:27: note: def [In, Out] Converter(self, converter: Callable[[In], Out]) -> Converter[In, Out] - main:27: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] + main:27: note: def [In, Out, _AttrsInstance: AttrsInstance] Converter(self, converter: Callable[[In, _AttrsInstance, Attribute[Any]], Out], *, takes_self: Literal[True], takes_field: Literal[True]) -> Converter[In, Out] main:27: note: def [In, Out] Converter(self, converter: Callable[[In, Attribute[Any]], Out], *, takes_field: Literal[True]) -> Converter[In, Out] - main:27: note: def [In, Out] Converter(self, converter: Callable[[In, AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] + main:27: note: def [In, Out, _AttrsInstance: AttrsInstance] Converter(self, converter: Callable[[In, _AttrsInstance], Out], *, takes_self: Literal[True]) -> Converter[In, Out] - case: testAttrsCmpWithSubclasses regex: true diff --git a/tests/typing_example.py b/tests/typing_example.py index cfeb95188..29cabe491 100644 --- a/tests/typing_example.py +++ b/tests/typing_example.py @@ -181,6 +181,46 @@ class ConvCToBool: ConvCToBool("n") +# Converter instances with takes_self=True and/or takes_field=True +def str2int_self(s: str | int, self_: "CCC") -> int: + return int(s) + + +def str2int_field(s: str | int, field_: attrs.Attribute) -> int: + return int(s) + + +def str2int_both(s: str | int, self_: "CCC", field_: attrs.Attribute) -> int: + return int(s) + + +# XXX: Fails with E: Unsupported converter, only named functions, types and lambdas are currently supported [misc] +# See https://github.com/python/mypy/issues/15736 +# +# So let's suppress the [misc] category, which still tests the [call-override] matching the callables. +@attrs.define +class CCC: + x: int = attrs.field( + converter=attrs.Converter( # type: ignore[misc] + str2int_self, + takes_self=True, + ) + ) + y: int = attrs.field( + converter=attrs.Converter( # type: ignore[misc] + str2int_field, + takes_field=True, + ) + ) + z: int = attrs.field( + converter=attrs.Converter( # type: ignore[misc] + str2int_both, + takes_self=True, + takes_field=True, + ) + ) + + # Validators @attr.s class Validated: