Skip to content

Commit

Permalink
Fix attrs.evolve on bound TypeVar
Browse files Browse the repository at this point in the history
  • Loading branch information
ikonst committed Apr 9, 2023
1 parent e9f876e commit 3a8b535
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 62 deletions.
4 changes: 1 addition & 3 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from mypy.applytype import apply_generic_arguments
from mypy.checker import TypeChecker
from mypy.errorcodes import LITERAL_REQ
from mypy.expandtype import expand_type, expand_type_by_instance
from mypy.expandtype import expand_type
from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
from mypy.messages import format_type_bare
from mypy.nodes import (
Expand Down Expand Up @@ -982,8 +982,6 @@ def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> Callabl
return ctx.default_signature
assert isinstance(attrs_type, Instance)

attrs_init_type = expand_type_by_instance(attrs_init_type, attrs_type)

# AttrClass.__init__ has the following signature (or similar, if having kw-only & defaults):
# def __init__(self, attr1: Type1, attr2: Type2) -> None:
# We want to generate a signature for evolve that looks like this:
Expand Down
72 changes: 13 additions & 59 deletions test-data/unit/check-attr.test
Original file line number Diff line number Diff line change
Expand Up @@ -1970,47 +1970,19 @@ reveal_type(ret) # N: Revealed type is "Any"

[typing fixtures/typing-medium.pyi]

[case testEvolveGeneric]
import attrs
from typing import Generic, TypeVar

T = TypeVar('T')

@attrs.define
class A(Generic[T]):
x: T


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"
reveal_type(a2) # N: Revealed type is "__main__.A[builtins.int]"

[builtins fixtures/attr.pyi]
[typing fixtures/typing-medium.pyi]

[case testEvolveTypeVarWithAttrsUpperBound]
[case testEvolveTypeVarBound]
import attrs
from typing import TypeVar


@attrs.define
class A:
x: int


@attrs.define
class B(A):
pass


TA = TypeVar('TA', bound=A)
TInt = TypeVar('TInt', bound=int)
TAny = TypeVar('TAny')
TNone = TypeVar('TNone', bound=None)


def f(t: TA) -> TA:
t2 = attrs.evolve(t, x=42)
Expand All @@ -2021,58 +1993,41 @@ def f(t: TA) -> TA:
f(A(x=42))
f(B(x=42))

def g(t: TInt) -> None:
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TInt" not bound to an attrs class

def h(t: TAny) -> None:
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TAny" not bound to an attrs class

def q(t: TNone) -> None:
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TNone" not bound to an attrs class

[builtins fixtures/attr.pyi]
[typing fixtures/typing-medium.pyi]

[case testEvolveTypeVarWithAttrsGenericUpperBound]
[case testEvolveTypeVarBoundNonAttrs]
import attrs
from typing import Generic, TypeVar

Q = TypeVar('Q', bound=str)

@attrs.define
class A(Generic[Q]):
x: Q

from typing import TypeVar

T = TypeVar('T', bound=A[str])
TInt = TypeVar('TInt', bound=int)
TAny = TypeVar('TAny')
TNone = TypeVar('TNone', bound=None)

def f(t: TInt) -> None:
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TInt" not bound to an attrs class

def f(t: T) -> T:
t = attrs.evolve(t, x=42) # E: Argument "x" to "evolve" of "T" has incompatible type "int"; expected "str"
return t
def g(t: TAny) -> None:
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TAny" not bound to an attrs class

f(A(x='42'))
def h(t: TNone) -> None:
_ = attrs.evolve(t, x=42) # E: Argument 1 to "evolve" has a variable type "TNone" not bound to an attrs class

[builtins fixtures/attr.pyi]
[typing fixtures/typing-medium.pyi]

[case testEvolveTypeVarWithAttrsValueRestrictions]
[case testEvolveTypeVarConstrained]
import attrs
from typing import TypeVar

@attrs.define
class A:
x: int


@attrs.define
class B:
x: str # conflicting with A.x


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"
Expand All @@ -2083,7 +2038,6 @@ f(A(x=42))
f(B(x='42'))

[builtins fixtures/attr.pyi]
[typing fixtures/typing-medium.pyi]

[case testEvolveVariants]
from typing import Any
Expand Down

0 comments on commit 3a8b535

Please sign in to comment.