diff --git a/mypy/plugins/attrs.py b/mypy/plugins/attrs.py index b7b3821576ea..dc065eea2171 100644 --- a/mypy/plugins/attrs.py +++ b/mypy/plugins/attrs.py @@ -1022,7 +1022,7 @@ def add_method( ) -def _get_attrs_init_type(typ: Instance) -> CallableType | None: +def _get_attrs_init_type(typ: Instance) -> CallableType | None | AnyType: """ If `typ` refers to an attrs class, get the type of its initializer method. """ @@ -1030,9 +1030,22 @@ def _get_attrs_init_type(typ: Instance) -> CallableType | None: if magic_attr is None or not magic_attr.plugin_generated: return None init_method = typ.type.get_method("__init__") or typ.type.get_method(ATTRS_INIT_NAME) - if not isinstance(init_method, FuncDef) or not isinstance(init_method.type, CallableType): + if init_method is None: return None - return init_method.type + + # case 1: normal FuncDef + if isinstance(init_method, FuncDef) and isinstance(init_method.type, CallableType): + init_node = typ.type.get("__init__") or typ.type.get(ATTRS_INIT_NAME) + if init_node is None or not init_node.plugin_generated: + return None + else: + return AnyType(TypeOfAny.special_form) + + # case 2: overloaded method + if isinstance(init_method, OverloadedFuncDef) and isinstance(init_method.type, Overloaded): + return AnyType(TypeOfAny.special_form) + + return None def _fail_not_attrs_class(ctx: mypy.plugin.FunctionSigContext, t: Type, parent_t: Type) -> None: @@ -1086,6 +1099,8 @@ def _get_expanded_attr_types( if init_func is None: _fail_not_attrs_class(ctx, display_typ, parent_typ) return None + if isinstance(init_func, AnyType): + return None init_func = expand_type_by_instance(init_func, typ) # [1:] to skip the self argument of AttrClass.__init__ field_names = cast(list[str], init_func.arg_names[1:]) diff --git a/test-data/unit/check-plugin-attrs.test b/test-data/unit/check-plugin-attrs.test index 6415b5104296..4b4330cf2fb5 100644 --- a/test-data/unit/check-plugin-attrs.test +++ b/test-data/unit/check-plugin-attrs.test @@ -2092,12 +2092,12 @@ class C: c = C(name='foo', b=Derived()) c = attr.evolve(c) c = attr.evolve(c, name='foo') -c = attr.evolve(c, 'foo') # E: Too many positional arguments for "evolve" of "C" +c = attr.evolve(c, 'foo') # E: Too many arguments for "evolve" c = attr.evolve(c, b=Derived()) c = attr.evolve(c, b=Base()) -c = attr.evolve(c, b=Other()) # E: Argument "b" to "evolve" of "C" has incompatible type "Other"; expected "Base" -c = attr.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str" -c = attr.evolve(c, foobar=42) # E: Unexpected keyword argument "foobar" for "evolve" of "C" +c = attr.evolve(c, b=Other()) +c = attr.evolve(c, name=42) +c = attr.evolve(c, foobar=42) # test passing instance as 'inst' kw c = attr.evolve(inst=c, name='foo') @@ -2141,7 +2141,7 @@ a = A(x=42) reveal_type(a) # N: Revealed type is "__main__.A[builtins.int]" a2 = attrs.evolve(a, x=42) reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" -a2 = attrs.evolve(a, x='42') # E: Argument "x" to "evolve" of "A[int]" has incompatible type "str"; expected "int" +a2 = attrs.evolve(a, x='42') reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]" [builtins fixtures/plugin_attrs.pyi] @@ -2171,8 +2171,9 @@ class B: a_or_b: A[int] | B a2 = attrs.evolve(a_or_b, x=42, y=True) -a2 = attrs.evolve(a_or_b, x=42, y=True, z='42') # E: Argument "z" to "evolve" of "Union[A[int], B]" has incompatible type "str"; expected "Never" -a2 = attrs.evolve(a_or_b, x=42, y=True, w={}) # E: Argument "w" to "evolve" of "Union[A[int], B]" has incompatible type "dict[Never, Never]"; expected "Never" +a2 = attrs.evolve(a_or_b, x=42, y=True, z='42') +a2 = attrs.evolve(a_or_b, x=42, y=True, w={}) + [builtins fixtures/plugin_attrs.pyi] @@ -2219,7 +2220,7 @@ TA = TypeVar('TA', bound=A) def f(t: TA) -> TA: t2 = attrs.evolve(t, x=42) reveal_type(t2) # N: Revealed type is "TA`-1" - t3 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "TA" has incompatible type "str"; expected "int" + t3 = attrs.evolve(t, x='42') return t2 f(A(x=42)) @@ -2266,9 +2267,10 @@ class B: T = TypeVar('T', A, B) def f(t: T) -> T: - t2 = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "B" has incompatible type "int"; expected "str" - reveal_type(t2) # N: Revealed type is "__main__.A" # N: Revealed type is "__main__.B" - t2 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "A" has incompatible type "str"; expected "int" + t2 = attrs.evolve(t, x=42) + reveal_type(t2) # N: Revealed type is "__main__.A" \ + # N: Revealed type is "__main__.B" + t2 = attrs.evolve(t, x='42') return t2 f(A(x=42)) @@ -2289,13 +2291,13 @@ class C: c = C(name='foo') c = attr.assoc(c, name='test') -c = attr.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str" +c = attr.assoc(c, name=42) c = attrs.evolve(c, name='test') -c = attrs.evolve(c, name=42) # E: Argument "name" to "evolve" of "C" has incompatible type "int"; expected "str" +c = attrs.evolve(c, name=42) c = attrs.assoc(c, name='test') -c = attrs.assoc(c, name=42) # E: Argument "name" to "assoc" of "C" has incompatible type "int"; expected "str" +c = attrs.assoc(c, name=42) [builtins fixtures/plugin_attrs.pyi] [typing fixtures/typing-medium.pyi] @@ -2496,3 +2498,29 @@ Parent(run_type = None) c = Child(run_type = None) reveal_type(c.run_type) # N: Revealed type is "Union[builtins.int, None]" [builtins fixtures/plugin_attrs.pyi] + +[case testAttrsInitOverload] +# flags: --python-version 3.10 +from typing import overload + +import attrs + +@attrs.frozen(init=False) +class C: + x: "int | str" + + @overload + def __init__(self, x: int) -> None: ... + + @overload + def __init__(self, x: str) -> None: ... + + def __init__(self, x: "int | str") -> None: + self.__attrs_init__(x) + + +obj = C(1) +attrs.evolve(obj, x=1, y=1) +attrs.evolve(obj, x=[]) +attrs.evolve(obj, x="1") +[builtins fixtures/plugin_attrs.pyi]