Skip to content

Commit

Permalink
Revert "Add support for attrs.fields (#15021)"
Browse files Browse the repository at this point in the history
This reverts commit 391ed85.

The use of a Union item type for `attr.Attribute` would likely be
more useful to callsites of `attr.fields` - otherwise this currently
results in inferring `builtins.object` in many cases where we treat
the fields as a variable-length tuple, rather than a fixed-length
one. Applications of the fixed-length tuple seem limited since you
would need to access it by index.

@Tinche as the original author of the commit.
  • Loading branch information
jhance committed Jun 8, 2023
1 parent 2ab8849 commit 3bf9fdc
Show file tree
Hide file tree
Showing 5 changed files with 3 additions and 109 deletions.
52 changes: 3 additions & 49 deletions mypy/plugins/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
TupleType,
Type,
TypeOfAny,
TypeType,
TypeVarType,
UninhabitedType,
UnionType,
Expand Down Expand Up @@ -945,7 +944,7 @@ def add_method(

def _get_attrs_init_type(typ: Instance) -> CallableType | None:
"""
If `typ` refers to an attrs class, get the type of its initializer method.
If `typ` refers to an attrs class, gets the type of its initializer method.
"""
magic_attr = typ.type.get(MAGIC_ATTR_NAME)
if magic_attr is None or not magic_attr.plugin_generated:
Expand Down Expand Up @@ -1019,7 +1018,7 @@ def _get_expanded_attr_types(

def _meet_fields(types: list[Mapping[str, Type]]) -> Mapping[str, Type]:
"""
"Meet" the fields of a list of attrs classes, i.e. for each field, its new type will be the lower bound.
"Meets" the fields of a list of attrs classes, i.e. for each field, its new type will be the lower bound.
"""
field_to_types = defaultdict(list)
for fields in types:
Expand All @@ -1036,7 +1035,7 @@ def _meet_fields(types: list[Mapping[str, Type]]) -> Mapping[str, Type]:

def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> CallableType:
"""
Generate a signature for the 'attr.evolve' function that's specific to the call site
Generates a signature for the 'attr.evolve' function that's specific to the call site
and dependent on the type of the first argument.
"""
if len(ctx.args) != 2:
Expand Down Expand Up @@ -1070,48 +1069,3 @@ def evolve_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> Callabl
fallback=ctx.default_signature.fallback,
name=f"{ctx.default_signature.name} of {inst_type_str}",
)


def fields_function_sig_callback(ctx: mypy.plugin.FunctionSigContext) -> CallableType:
"""Provide the signature for `attrs.fields`."""
if not ctx.args or len(ctx.args) != 1 or not ctx.args[0] or not ctx.args[0][0]:
return ctx.default_signature

# <hack>
assert isinstance(ctx.api, TypeChecker)
inst_type = ctx.api.expr_checker.accept(ctx.args[0][0])
# </hack>
proper_type = get_proper_type(inst_type)

# fields(Any) -> Any, fields(type[Any]) -> Any
if (
isinstance(proper_type, AnyType)
or isinstance(proper_type, TypeType)
and isinstance(proper_type.item, AnyType)
):
return ctx.default_signature

cls = None
arg_types = ctx.default_signature.arg_types

if isinstance(proper_type, TypeVarType):
inner = get_proper_type(proper_type.upper_bound)
if isinstance(inner, Instance):
# We need to work arg_types to compensate for the attrs stubs.
arg_types = [inst_type]
cls = inner.type
elif isinstance(proper_type, CallableType):
cls = proper_type.type_object()

if cls is not None and MAGIC_ATTR_NAME in cls.names:
# This is a proper attrs class.
ret_type = cls.names[MAGIC_ATTR_NAME].type
assert ret_type is not None
return ctx.default_signature.copy_modified(arg_types=arg_types, ret_type=ret_type)

ctx.api.fail(
f'Argument 1 to "fields" has incompatible type "{format_type_bare(proper_type, ctx.api.options)}"; expected an attrs class',
ctx.context,
)

return ctx.default_signature
3 changes: 0 additions & 3 deletions mypy/plugins/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type]
return ctypes.array_constructor_callback
elif fullname == "functools.singledispatch":
return singledispatch.create_singledispatch_function_callback

return None

def get_function_signature_hook(
Expand All @@ -55,8 +54,6 @@ def get_function_signature_hook(

if fullname in ("attr.evolve", "attrs.evolve", "attr.assoc", "attrs.assoc"):
return attrs.evolve_function_sig_callback
elif fullname in ("attr.fields", "attrs.fields"):
return attrs.fields_function_sig_callback
return None

def get_method_signature_hook(
Expand Down
53 changes: 0 additions & 53 deletions test-data/unit/check-plugin-attrs.test
Original file line number Diff line number Diff line change
Expand Up @@ -1553,59 +1553,6 @@ takes_attrs_cls(A(1, "")) # E: Argument 1 to "takes_attrs_cls" has incompatible
takes_attrs_instance(A) # E: Argument 1 to "takes_attrs_instance" has incompatible type "Type[A]"; expected "AttrsInstance" # N: ClassVar protocol member AttrsInstance.__attrs_attrs__ can never be matched by a class object
[builtins fixtures/plugin_attrs.pyi]

[case testAttrsFields]
import attr
from attrs import fields as f # Common usage.

@attr.define
class A:
b: int
c: str

reveal_type(f(A)) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]"
reveal_type(f(A)[0]) # N: Revealed type is "attr.Attribute[builtins.int]"
reveal_type(f(A).b) # N: Revealed type is "attr.Attribute[builtins.int]"
f(A).x # E: "____main___A_AttrsAttributes__" has no attribute "x"

[builtins fixtures/plugin_attrs.pyi]

[case testAttrsGenericFields]
from typing import TypeVar

import attr
from attrs import fields

@attr.define
class A:
b: int
c: str

TA = TypeVar('TA', bound=A)

def f(t: TA) -> None:
reveal_type(fields(t)) # N: Revealed type is "Tuple[attr.Attribute[builtins.int], attr.Attribute[builtins.str], fallback=__main__.A.____main___A_AttrsAttributes__]"
reveal_type(fields(t)[0]) # N: Revealed type is "attr.Attribute[builtins.int]"
reveal_type(fields(t).b) # N: Revealed type is "attr.Attribute[builtins.int]"
fields(t).x # E: "____main___A_AttrsAttributes__" has no attribute "x"


[builtins fixtures/plugin_attrs.pyi]

[case testNonattrsFields]
from typing import Any, cast, Type
from attrs import fields

class A:
b: int
c: str

fields(A) # E: Argument 1 to "fields" has incompatible type "Type[A]"; expected an attrs class
fields(None) # E: Argument 1 to "fields" has incompatible type "None"; expected an attrs class
fields(cast(Any, 42))
fields(cast(Type[Any], 43))

[builtins fixtures/plugin_attrs.pyi]

[case testAttrsInitMethodAlwaysGenerates]
from typing import Tuple
import attr
Expand Down
2 changes: 0 additions & 2 deletions test-data/unit/lib-stub/attr/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -247,5 +247,3 @@ def field(

def evolve(inst: _T, **changes: Any) -> _T: ...
def assoc(inst: _T, **changes: Any) -> _T: ...

def fields(cls: type) -> Any: ...
2 changes: 0 additions & 2 deletions test-data/unit/lib-stub/attrs/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,3 @@ def field(

def evolve(inst: _T, **changes: Any) -> _T: ...
def assoc(inst: _T, **changes: Any) -> _T: ...

def fields(cls: type) -> Any: ...

0 comments on commit 3bf9fdc

Please sign in to comment.