10000 Fix attrs.evolve on bound TypeVar · python/mypy@dfd155b · GitHub
[go: up one dir, main page]

Skip to content

Commit dfd155b

Browse files
committed
Fix attrs.evolve on bound TypeVar
1 parent e21ddbf commit dfd155b

File tree

2 files changed

+82
-9
lines changed

2 files changed

+82
-9
lines changed

mypy/plugins/attrs.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -929,13 +929,10 @@ def add_method(
929929
add_method(self.ctx, method_name, args, ret_type, self_type, tvd)
930930

931931

932-
def _get_attrs_init_type(typ: Type) -> CallableType | None:
932+
def _get_attrs_init_type(typ: Instance) -> CallableType | None:
933933
"""
934934
If `typ` refers to an attrs class, gets the type of its initializer method.
935935
"""
936-
typ = get_proper_type(typ)
937-
if not isinstance(typ, Instance):
938-
return None
939936
magic_attr = typ.type.get(MAGIC_ATTR_NAME)
940937
if magic_attr is None or not magic_attr.plugin_generated:
941938
return None
@@ -967,16 +964,23 @@ def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> Callabl
967964

968965
inst_type = get_proper_type(inst_type)
969966
if isinstance(inst_type, AnyType):
970-
return ctx.default_signature
967+
return ctx.default_signature # evolve(Any, ....) -> Any
971968
inst_type_str = format_type_bare(inst_type)
972-
973-
attrs_init_type = _get_attrs_init_type(inst_type)
974-
if not attrs_init_type:
969+
attrs_type = get_proper_type(
970+
inst_type.upper_bound if isinstance(inst_type, TypeVarType) else inst_type
971+
)
972+
attrs_init_type = None
973+
if isinstance(attrs_type, Instance):
974+
attrs_init_type = _get_attrs_init_type(attrs_type)
975+
if attrs_init_type is None:
975976
ctx.api.fail(
976-
f'Argument 1 to "evolve" has incompatible type "{inst_type_str}"; expected an attrs class',
977+
f'Argument 1 to "evolve" has a variable type "{inst_type_str}" not bound to an attrs class'
978+
if isinstance(inst_type, TypeVarType)
979+
else f'Argument 1 to "evolve" has incompatible type "{inst_type_str}"; expected an attrs class',
977980
ctx.context,
978981
)
979982
return ctx.default_signature
983+
assert isinstance(attrs_type, Instance)
980984

981985
# AttrClass.__init__ has the following signature (or similar, if having kw-only & defaults):
982986
# def __init__(self, attr1: Type1, attr2: Type2) -> None:

test-data/unit/check-attr.test

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,6 +1970,75 @@ reveal_type(ret) # N: Revealed type is "Any"
19701970

19711971
[typing fixtures/typing-medium.pyi]
19721972

1973+
[case testEvolveTypeVarBound]
1974+
import attrs
1975+
from typing import TypeVar
1976+
1977+
@attrs.define
1978+
class A:
1979+
x: int
1980+
1981+
@attrs.define
1982+
class B(A):
1983+
pass
1984+
1985+
TA = TypeVar('TA', bound=A)
1986+
1987+
def f(t: TA) -> TA:
1988+
t2 = attrs.evolve(t, x=42)
1989+
reveal_type(t2) # N: Revealed type is "TA`-1"
1990+
t3 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "TA" has incompatible type "str"; expected "int"
1991+
return t2
1992+
1993+
f(A(x=42))
1994+
f(B(x=42))
1995+
1996+
[builtins fixtures/attr.pyi]
1997+
1998+
[case testEvolveTypeVarBoundNonAttrs]
1999+
import attrs
2000+
from typing import TypeVar
2001+
2002+
TInt = TypeVar('TInt', bound=int)
2003+
TAny = TypeVar('TAny')
2004+
TNone = TypeVar('TNone', bound=None)
2005+
2006+
def f(t: TInt) -> None:
2007+
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TInt" not bound to an attrs class
2008+
2009+
def g(t: TAny) -> None:
2010+
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TAny" not bound to an attrs class
2011+
2012+
def h(t: TNone) -> None:
2013+
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TNone" not bound to an attrs class
2014+
2015+
[builtins fixtures/attr.pyi]
2016+
2017+
[case testEvolveTypeVarConstrained]
2018+
import attrs
2019+
from typing import TypeVar
2020+
2021+
@attrs.define
2022+
class A:
2023+
x: int
2024+
2025+
@attrs.define
2026+
class B:
2027+
x: str # conflicting with A.x
2028+
2029+
T = TypeVar('T', A, B)
2030+
2031+
def f(t: T) -> T:
2032+
t2 = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "B" has incompatible type "int"; expected "str"
2033+
reveal_type(t2) # N: Revealed type is "__main__.A" # N: Revealed type is "__main__.B"
2034+
t2 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "A" has incompatible type "str"; expected "int"
2035+
return t2
2036+
2037+
f(A(x=42))
2038+
f(B(x='42'))
2039+
2040+
[builtins fixtures/attr.pyi]
2041+
19732042
[case testEvolveVariants]
19742043
from typing import Any
19752044
import attr

0 commit comments

Comments
 (0)
0