Skip to content

Commit 2ee391c

Browse files
committed
support for evolving type-vars
1 parent 9bf4733 commit 2ee391c

File tree

2 files changed

+88
-3
lines changed

2 files changed

+88
-3
lines changed

mypy/plugins/attrs.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -965,17 +965,20 @@ def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> Callabl
965965
inst_type = get_proper_type(inst_type)
966966
if isinstance(inst_type, AnyType):
967967
return ctx.default_signature # evolve(Any, ....) -> Any
968+
# We stringify it first, so that TypeVars maintain their name.
968969
inst_type_str = format_type_bare(inst_type)
970+
upper_bound = inst_type.upper_bound if isinstance(inst_type, TypeVarType) else inst_type
969971
if not (
970-
isinstance(inst_type, Instance) and (attrs_init_type := _get_attrs_init_type(inst_type))
972+
isinstance(upper_bound, Instance)
973+
and (attrs_init_type := _get_attrs_init_type(upper_bound))
971974
):
972975
ctx.api.fail(
973976
f'Argument 1 to "evolve" has incompatible type "{inst_type_str}"; expected an attrs class',
974977
ctx.context,
975978
)
976979
return ctx.default_signature
977980

978-
attrs_init_type = expand_type_by_instance(attrs_init_type, inst_type)
981+
attrs_init_type = expand_type_by_instance(attrs_init_type, upper_bound)
979982

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

test-data/unit/check-attr.test

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1972,7 +1972,7 @@ reveal_type(ret) # N: Revealed type is "Any"
19721972

19731973
[case testEvolveGeneric]
19741974
import attrs
1975-
from typing import ClassVar, Generic, TypeVar
1975+
from typing import Generic, TypeVar
19761976

19771977
T = TypeVar('T')
19781978

@@ -1991,6 +1991,88 @@ reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]"
19911991
[builtins fixtures/attr.pyi]
19921992
[typing fixtures/typing-medium.pyi]
19931993

1994+
[case testEvolveTypeVarWithAttrsUpperBound]
1995+
import attrs
1996+
from typing import TypeVar
1997+
1998+
1999+
@attrs.define
2000+
class A:
2001+
x: int
2002+
2003+
2004+
@attrs.define
2005+
class B(A):
2006+
pass
2007+
2008+
2009+
T = TypeVar('T', bound=A)
2010+
2011+
2012+
def f(t: T) -> T:
2013+
t2 = attrs.evolve(t, x=42)
2014+
reveal_type(t2) # N: Revealed type is "T`-1"
2015+
t3 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "T" has incompatible type "str"; expected "int"
2016+
return t2
2017+
2018+
f(A(x=42))
2019+
f(B(x=42))
2020+
2021+
[builtins fixtures/attr.pyi]
2022+
[typing fixtures/typing-medium.pyi]
2023+
2024+
[case testEvolveTypeVarWithAttrsGenericUpperBound]
2025+
import attrs
2026+
from typing import Generic, TypeVar
2027+
2028+
Q = TypeVar('Q', bound=str)
2029+
2030+
@attrs.define
2031+
class A(Generic[Q]):
2032+
x: Q
2033+
2034+
2035+
T = TypeVar('T', bound=A[str])
2036+
2037+
2038+
def f(t: T) -> T:
2039+
t = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "T" has incompatible type "int"; expected "str"
2040+
return t
2041+
2042+
f(A(x='42'))
2043+
2044+
[builtins fixtures/attr.pyi]
2045+
[typing fixtures/typing-medium.pyi]
2046+
2047+
[case testEvolveTypeVarWithAttrsValueRestrictions]
2048+
import attrs
2049+
from typing import TypeVar
2050+
2051+
@attrs.define
2052+
class A:
2053+
x: int
2054+
2055+
2056+
@attrs.define
2057+
class B:
2058+
x: str # conflicting with A.x
2059+
2060+
2061+
T = TypeVar('T', A, B)
2062+
2063+
2064+
def f(t: T) -> T:
2065+
t2 = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "B" has incompatible type "int"; expected "str"
2066+
reveal_type(t2) # N: Revealed type is "__main__.A" # N: Revealed type is "__main__.B"
2067+
t2 = attrs.evolve(t, x='42') # E: Argument "x" to "evolve" of "A" has incompatible type "str"; expected "int"
2068+
return t2
2069+
2070+
f(A(x=42))
2071+
f(B(x='42'))
2072+
2073+
[builtins fixtures/attr.pyi]
2074+
[typing fixtures/typing-medium.pyi]
2075+
19942076
[case testEvolveVariants]
19952077
from typing import Any
19962078
import attr

0 commit comments

Comments
 (0)